diff --git a/CMakeLists.txt b/CMakeLists.txt
index 31597f399e5d90c32cdf4471341ef6b134b8084a..f9364fdd2c1b20ea8c3fa2afa744cbe8360a62de 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,6 @@
 cmake_minimum_required(VERSION 3.0)
 project(SRB2
-	VERSION 2.1.17
+	VERSION 2.1.19
 	LANGUAGES C)
 
 if(${PROJECT_SOURCE_DIR} MATCHES ${PROJECT_BINARY_DIR})
diff --git a/SRB2.cbp b/SRB2.cbp
index 88dc400fee58d949fe3f59eb968662e45702ffd1..2a1eb87b8565b3d2d9c13216c23d39dceecd6f92 100644
--- a/SRB2.cbp
+++ b/SRB2.cbp
@@ -1174,6 +1174,39 @@ HW3SOUND for 3D hardware sound  support
 			<Option target="Debug Mingw64/DirectX" />
 			<Option target="Release Mingw64/DirectX" />
 		</Unit>
+		<Unit filename="src/hardware/hw_clip.c">
+			<Option compilerVar="CC" />
+			<Option target="Debug Native/SDL" />
+			<Option target="Release Native/SDL" />
+			<Option target="Debug Mingw/SDL" />
+			<Option target="Release Mingw/SDL" />
+			<Option target="Debug Mingw/DirectX" />
+			<Option target="Release Mingw/DirectX" />
+			<Option target="Debug Any/Dummy" />
+			<Option target="Release Any/Dummy" />
+			<Option target="Debug Linux/SDL" />
+			<Option target="Release Linux/SDL" />
+			<Option target="Debug Mingw64/SDL" />
+			<Option target="Release Mingw64/SDL" />
+			<Option target="Debug Mingw64/DirectX" />
+			<Option target="Release Mingw64/DirectX" />
+		</Unit>
+		<Unit filename="src/hardware/hw_clip.h">
+			<Option target="Debug Native/SDL" />
+			<Option target="Release Native/SDL" />
+			<Option target="Debug Mingw/SDL" />
+			<Option target="Release Mingw/SDL" />
+			<Option target="Debug Mingw/DirectX" />
+			<Option target="Release Mingw/DirectX" />
+			<Option target="Debug Any/Dummy" />
+			<Option target="Release Any/Dummy" />
+			<Option target="Debug Linux/SDL" />
+			<Option target="Release Linux/SDL" />
+			<Option target="Debug Mingw64/SDL" />
+			<Option target="Release Mingw64/SDL" />
+			<Option target="Debug Mingw64/DirectX" />
+			<Option target="Release Mingw64/DirectX" />
+		</Unit>
 		<Unit filename="src/hardware/hw_data.h">
 			<Option target="Debug Native/SDL" />
 			<Option target="Release Native/SDL" />
diff --git a/appveyor.yml b/appveyor.yml
index b0544a90b549ab0e526e2f0902a8b4093837478f..23b9b62815b597867a56957bcdde2239808cbdb2 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,4 @@
-version: 2.1.17.{branch}-{build}
+version: 2.1.19.{branch}-{build}
 os: MinGW
 
 environment:
@@ -47,7 +47,7 @@ before_build:
 - upx -V
 - ccache -V
 - ccache -s
-- set SRB2_MFLAGS=-C src MINGW=1 WARNINGMODE=1 GCC63=1 CCACHE=1
+- set SRB2_MFLAGS=-C src MINGW=1 WARNINGMODE=1 GCC63=1 CCACHE=1 NOOBJDUMP=1
 
 build_script:
 - cmd: mingw32-make.exe %SRB2_MFLAGS% %CONFIGURATION%=1 clean
@@ -58,26 +58,29 @@ after_build:
 - cmd: git rev-parse --short %APPVEYOR_REPO_COMMIT%>%TMP%/gitshort.txt
 - cmd: set /P GITSHORT=<%TMP%/gitshort.txt
 - set BUILD_ARCHIVE=%APPVEYOR_REPO_BRANCH%-%GITSHORT%-%CONFIGURATION%.7z
+- set BUILDSARCHIVE=%APPVEYOR_REPO_BRANCH%-%CONFIGURATION%.7z
 - cmd: 7z a %BUILD_ARCHIVE% bin\Mingw\Release -xr!.gitignore
 - appveyor PushArtifact %BUILD_ARCHIVE%
+- cmd: copy %BUILD_ARCHIVE% %BUILDSARCHIVE%
+- appveyor PushArtifact %BUILDSARCHIVE%
 
 test: off
 
-deploy:
-  - provider: FTP
-    protocol: ftps
-    host: 
-      secure: NsLJEPIBvmwCOj8Tg8RoRQ==
-    username:
-      secure: ejxi5mvk7oLYu7QtbYojajEPigMy0mokaKhuEVuDZcA=
-    password:
-      secure: Hbn6Uy3lT0YZ88yFJ3aW4w==
-    folder: appveyor
-    application:
-    active_mode: false
-    on:
-      branch: master
-      appveyor_repo_tag: true
+#deploy:
+#  - provider: FTP
+#    protocol: ftps
+#    host: 
+#      secure: NsLJEPIBvmwCOj8Tg8RoRQ==
+#    username:
+#      secure: ejxi5mvk7oLYu7QtbYojajEPigMy0mokaKhuEVuDZcA=
+#    password:
+#      secure: Hbn6Uy3lT0YZ88yFJ3aW4w==
+#    folder: appveyor
+#    application:
+#    active_mode: false
+#    on:
+#      branch: master
+#      appveyor_repo_tag: true
 
 
 on_finish:
diff --git a/bin/Mingw/Debug/.gitignore b/bin/Mingw/Debug/.gitignore
index e431dca5d25bfe0ea739d34ca086c9e87b2c13ab..834f313e3eae612617885430c8071e6e41483d88 100644
--- a/bin/Mingw/Debug/.gitignore
+++ b/bin/Mingw/Debug/.gitignore
@@ -1,3 +1,3 @@
-/srb2sdl.exe
-/srb2win.exe
-/r_opengl.dll
+*.exe
+*.mo
+r_opengl.dll
diff --git a/bin/Mingw/Release/.gitignore b/bin/Mingw/Release/.gitignore
index e431dca5d25bfe0ea739d34ca086c9e87b2c13ab..834f313e3eae612617885430c8071e6e41483d88 100644
--- a/bin/Mingw/Release/.gitignore
+++ b/bin/Mingw/Release/.gitignore
@@ -1,3 +1,3 @@
-/srb2sdl.exe
-/srb2win.exe
-/r_opengl.dll
+*.exe
+*.mo
+r_opengl.dll
diff --git a/objs/.gitignore b/objs/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..35ecd6def21e7cdb60882510005e3b9833df5a08
--- /dev/null
+++ b/objs/.gitignore
@@ -0,0 +1,8 @@
+#All folders
+SRB2.res
+depend.dep
+depend.ped
+*.o
+#VC9 folder only
+/VC9/Win32
+/VC9/x64
diff --git a/objs/DC/SDL/Debug/.gitignore b/objs/DC/SDL/Debug/.gitignore
index 867fcb4e0398725385e346dbc7352fe8b3b9f0e7..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/DC/SDL/Debug/.gitignore
+++ b/objs/DC/SDL/Debug/.gitignore
@@ -1 +1,2 @@
-/depend.dep
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/DC/SDL/Release/.gitignore b/objs/DC/SDL/Release/.gitignore
index 867fcb4e0398725385e346dbc7352fe8b3b9f0e7..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/DC/SDL/Release/.gitignore
+++ b/objs/DC/SDL/Release/.gitignore
@@ -1 +1,2 @@
-/depend.dep
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Linux/SDL/Debug/.gitignore b/objs/Linux/SDL/Debug/.gitignore
index 8f6d0bdcdcdb8a1f18112fb0893d8f1338f7088b..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/Linux/SDL/Debug/.gitignore
+++ b/objs/Linux/SDL/Debug/.gitignore
@@ -1,2 +1,2 @@
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Linux/SDL/Release/.gitignore b/objs/Linux/SDL/Release/.gitignore
index 8f6d0bdcdcdb8a1f18112fb0893d8f1338f7088b..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/Linux/SDL/Release/.gitignore
+++ b/objs/Linux/SDL/Release/.gitignore
@@ -1,2 +1,2 @@
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Linux64/SDL/Debug/.gitignore b/objs/Linux64/SDL/Debug/.gitignore
index 8f6d0bdcdcdb8a1f18112fb0893d8f1338f7088b..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/Linux64/SDL/Debug/.gitignore
+++ b/objs/Linux64/SDL/Debug/.gitignore
@@ -1,2 +1,2 @@
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Linux64/SDL/Release/.gitignore b/objs/Linux64/SDL/Release/.gitignore
index 8f6d0bdcdcdb8a1f18112fb0893d8f1338f7088b..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/Linux64/SDL/Release/.gitignore
+++ b/objs/Linux64/SDL/Release/.gitignore
@@ -1,2 +1,2 @@
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Mingw/Debug/.gitignore b/objs/Mingw/Debug/.gitignore
index da4b3e912326f064eec51832cf2455498147fada..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/Mingw/Debug/.gitignore
+++ b/objs/Mingw/Debug/.gitignore
@@ -1,3 +1,2 @@
-/SRB2.res
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Mingw/Release/.gitignore b/objs/Mingw/Release/.gitignore
index da4b3e912326f064eec51832cf2455498147fada..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/Mingw/Release/.gitignore
+++ b/objs/Mingw/Release/.gitignore
@@ -1,3 +1,2 @@
-/SRB2.res
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Mingw/SDL/Debug/.gitignore b/objs/Mingw/SDL/Debug/.gitignore
index da4b3e912326f064eec51832cf2455498147fada..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/Mingw/SDL/Debug/.gitignore
+++ b/objs/Mingw/SDL/Debug/.gitignore
@@ -1,3 +1,2 @@
-/SRB2.res
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Mingw/SDL/Release/.gitignore b/objs/Mingw/SDL/Release/.gitignore
index da4b3e912326f064eec51832cf2455498147fada..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/Mingw/SDL/Release/.gitignore
+++ b/objs/Mingw/SDL/Release/.gitignore
@@ -1,3 +1,2 @@
-/SRB2.res
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Mingw64/Debug/.gitignore b/objs/Mingw64/Debug/.gitignore
index da4b3e912326f064eec51832cf2455498147fada..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/Mingw64/Debug/.gitignore
+++ b/objs/Mingw64/Debug/.gitignore
@@ -1,3 +1,2 @@
-/SRB2.res
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Mingw64/Release/.gitignore b/objs/Mingw64/Release/.gitignore
index da4b3e912326f064eec51832cf2455498147fada..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/Mingw64/Release/.gitignore
+++ b/objs/Mingw64/Release/.gitignore
@@ -1,3 +1,2 @@
-/SRB2.res
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Mingw64/SDL/Debug/.gitignore b/objs/Mingw64/SDL/Debug/.gitignore
index da4b3e912326f064eec51832cf2455498147fada..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/Mingw64/SDL/Debug/.gitignore
+++ b/objs/Mingw64/SDL/Debug/.gitignore
@@ -1,3 +1,2 @@
-/SRB2.res
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Mingw64/SDL/Release/.gitignore b/objs/Mingw64/SDL/Release/.gitignore
index da4b3e912326f064eec51832cf2455498147fada..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/Mingw64/SDL/Release/.gitignore
+++ b/objs/Mingw64/SDL/Release/.gitignore
@@ -1,3 +1,2 @@
-/SRB2.res
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/PS3/SDL/Debug/.gitignore b/objs/PS3/SDL/Debug/.gitignore
index 8f6d0bdcdcdb8a1f18112fb0893d8f1338f7088b..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/PS3/SDL/Debug/.gitignore
+++ b/objs/PS3/SDL/Debug/.gitignore
@@ -1,2 +1,2 @@
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/PS3/SDL/Release/.gitignore b/objs/PS3/SDL/Release/.gitignore
index 8f6d0bdcdcdb8a1f18112fb0893d8f1338f7088b..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/PS3/SDL/Release/.gitignore
+++ b/objs/PS3/SDL/Release/.gitignore
@@ -1,2 +1,2 @@
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/PSP/SDL/Release/.gitignore b/objs/PSP/SDL/Release/.gitignore
index 867fcb4e0398725385e346dbc7352fe8b3b9f0e7..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/PSP/SDL/Release/.gitignore
+++ b/objs/PSP/SDL/Release/.gitignore
@@ -1 +1,2 @@
-/depend.dep
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/SDL/Release/.gitignore b/objs/SDL/Release/.gitignore
index 4a262f94f9de50a50677b8bba7df91214b5d5684..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/SDL/Release/.gitignore
+++ b/objs/SDL/Release/.gitignore
@@ -1 +1,2 @@
-/depend.ped
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/VC/.gitignore b/objs/VC/.gitignore
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/VC/.gitignore
+++ b/objs/VC/.gitignore
@@ -0,0 +1,2 @@
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/VC9/.gitignore b/objs/VC9/.gitignore
index 205fe45deb9ebe556ff38988507a10183a30feb7..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/VC9/.gitignore
+++ b/objs/VC9/.gitignore
@@ -1,2 +1,2 @@
-/Win32
-/x64
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Wii/SDL/Debug/.gitignore b/objs/Wii/SDL/Debug/.gitignore
index 8f6d0bdcdcdb8a1f18112fb0893d8f1338f7088b..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/Wii/SDL/Debug/.gitignore
+++ b/objs/Wii/SDL/Debug/.gitignore
@@ -1,2 +1,2 @@
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/Wii/SDL/Release/.gitignore b/objs/Wii/SDL/Release/.gitignore
index 8f6d0bdcdcdb8a1f18112fb0893d8f1338f7088b..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/Wii/SDL/Release/.gitignore
+++ b/objs/Wii/SDL/Release/.gitignore
@@ -1,2 +1,2 @@
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/WinCE/SDL/Release/.gitignore b/objs/WinCE/SDL/Release/.gitignore
index 867fcb4e0398725385e346dbc7352fe8b3b9f0e7..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/WinCE/SDL/Release/.gitignore
+++ b/objs/WinCE/SDL/Release/.gitignore
@@ -1 +1,2 @@
-/depend.dep
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/djgppdos/Debug/.gitignore b/objs/djgppdos/Debug/.gitignore
index 867fcb4e0398725385e346dbc7352fe8b3b9f0e7..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/djgppdos/Debug/.gitignore
+++ b/objs/djgppdos/Debug/.gitignore
@@ -1 +1,2 @@
-/depend.dep
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/djgppdos/Release/.gitignore b/objs/djgppdos/Release/.gitignore
index 867fcb4e0398725385e346dbc7352fe8b3b9f0e7..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/djgppdos/Release/.gitignore
+++ b/objs/djgppdos/Release/.gitignore
@@ -1 +1,2 @@
-/depend.dep
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/nds/Debug/.gitignore b/objs/nds/Debug/.gitignore
index 8f6d0bdcdcdb8a1f18112fb0893d8f1338f7088b..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/nds/Debug/.gitignore
+++ b/objs/nds/Debug/.gitignore
@@ -1,2 +1,2 @@
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/objs/nds/Release/.gitignore b/objs/nds/Release/.gitignore
index 8f6d0bdcdcdb8a1f18112fb0893d8f1338f7088b..42c6dc2c662642792a8860e166dfd81126695e8f 100644
--- a/objs/nds/Release/.gitignore
+++ b/objs/nds/Release/.gitignore
@@ -1,2 +1,2 @@
-/depend.dep
-/*.o
+# DON'T REMOVE
+# This keeps the folder from disappearing
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index da8438a59ef123836a88593f30c1438f2b69e4da..6a8b7e3f1228f2e7028a9f9cd324768c85182025 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -351,6 +351,7 @@ if(${SRB2_CONFIG_HWRENDER})
 	set(SRB2_HWRENDER_SOURCES
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_bsp.c
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_cache.c
+		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_clip.c
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_draw.c
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_light.c
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_main.c
@@ -359,6 +360,7 @@ if(${SRB2_CONFIG_HWRENDER})
 	)
 
 	set (SRB2_HWRENDER_HEADERS
+		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_clip.h
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_data.h
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_defs.h
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_dll.h
diff --git a/src/Makefile b/src/Makefile
index 76f013c5285aaf684c2517cb0cf05a0069f08d42..27569b36ca772f45fffc26b09625498e16a058d2 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -259,7 +259,7 @@ ifndef DC
 endif
 	OPTS+=-DHWRENDER
 	OBJS+=$(OBJDIR)/hw_bsp.o $(OBJDIR)/hw_draw.o $(OBJDIR)/hw_light.o \
-		 $(OBJDIR)/hw_main.o $(OBJDIR)/hw_md2.o $(OBJDIR)/hw_cache.o $(OBJDIR)/hw_trick.o
+		 $(OBJDIR)/hw_main.o $(OBJDIR)/hw_clip.o $(OBJDIR)/hw_md2.o $(OBJDIR)/hw_cache.o $(OBJDIR)/hw_trick.o
 endif
 
 ifdef NOHS
@@ -511,13 +511,11 @@ OBJS:=$(i_main_o) \
 # For reference, this is the command I use to build a srb2.pot file from the source code.
 # (The listed source files are the ones containing translated strings).
 # FILES=""; for file in `find ./ | grep "\.c" | grep -v svn`; do [ "`grep "M_GetText(" $file`" ] && FILES="$FILES $file"; done; xgettext -d srb2 -o locale/srb2.pot -kM_GetText -F --no-wrap $FILES
-ifndef NOGETTEXT
 ifdef GETTEXT
 POS:=$(BIN)/en.mo
 
 OPTS+=-DGETTEXT
 endif
-endif
 
 ifdef DJGPPDOS
 all:	 pre-build $(BIN)/$(EXENAME)
@@ -705,7 +703,7 @@ ifdef MINGW
 $(OBJDIR)/r_opengl.o: hardware/r_opengl/r_opengl.c hardware/r_opengl/r_opengl.h \
  doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_drv.h screen.h \
  command.h hardware/hw_data.h hardware/hw_glide.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h am_map.h \
+ hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h am_map.h \
  d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
  p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
 	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
@@ -713,7 +711,7 @@ else
 $(OBJDIR)/r_opengl.o: hardware/r_opengl/r_opengl.c hardware/r_opengl/r_opengl.h \
  doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_drv.h screen.h \
  command.h hardware/hw_data.h hardware/hw_glide.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h am_map.h \
+ hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h am_map.h \
  d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
  p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
 	$(CC) $(CFLAGS) $(WFLAGS) -I/usr/X11R6/include -c $< -o $@
@@ -866,7 +864,7 @@ ifndef NOHW
 $(OBJDIR)/r_opengl.o: hardware/r_opengl/r_opengl.c hardware/r_opengl/r_opengl.h \
  doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_drv.h screen.h \
  command.h hardware/hw_data.h hardware/hw_glide.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h am_map.h \
+ hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h am_map.h \
  d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
  p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
 	$(CC) $(CFLAGS) $(WFLAGS) -D_WINDOWS -mwindows -c $< -o $@
@@ -874,7 +872,7 @@ $(OBJDIR)/r_opengl.o: hardware/r_opengl/r_opengl.c hardware/r_opengl/r_opengl.h
 $(OBJDIR)/ogl_win.o: hardware/r_opengl/ogl_win.c hardware/r_opengl/r_opengl.h \
  doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_drv.h screen.h \
  command.h hardware/hw_data.h hardware/hw_glide.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h am_map.h \
+ hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h am_map.h \
  d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
  p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
 	$(CC) $(CFLAGS) $(WFLAGS) -D_WINDOWS -mwindows -c $< -o $@
@@ -882,7 +880,7 @@ $(OBJDIR)/ogl_win.o: hardware/r_opengl/ogl_win.c hardware/r_opengl/r_opengl.h \
 $(OBJDIR)/r_minigl.o: hardware/r_minigl/r_minigl.c hardware/r_opengl/r_opengl.h \
  doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_drv.h screen.h \
  command.h hardware/hw_data.h hardware/hw_glide.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h am_map.h \
+ hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h am_map.h \
  d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
  p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
 	$(CC) $(CFLAGS) $(WFLAGS) -D_WINDOWS -mwindows -c $< -o $@
diff --git a/src/Makefile.cfg b/src/Makefile.cfg
index 80d018c4b5993ad01e1ddbfd34cff1f2564a3398..5bf7f247dfc6c0f0dea25405eaa4eb8e36f8e75b 100644
--- a/src/Makefile.cfg
+++ b/src/Makefile.cfg
@@ -283,9 +283,6 @@ else
 ifdef LINUX
 	NASMFORMAT=elf -DLINUX
 	SDL=1
-ifndef NOGETTEXT
-	GETTEXT=1
-endif
 ifdef LINUX64
 	OBJDIR:=$(OBJDIR)/Linux64
 	BIN:=$(BIN)/Linux64
@@ -321,9 +318,6 @@ else
 ifdef MINGW64
 	INTERFACE=win32
 	#NASMFORMAT=win64
-ifndef NOGETTEXT
-	#GETTEXT=1
-endif
 	OBJDIR:=$(OBJDIR)/Mingw64
 	BIN:=$(BIN)/Mingw64
 else
@@ -354,9 +348,6 @@ else
 ifdef MINGW
 	INTERFACE=win32
 	NASMFORMAT=win32
-ifndef NOGETTEXT
-	GETTEXT=1
-endif
 	OBJDIR:=$(OBJDIR)/Mingw
 	BIN:=$(BIN)/Mingw
 else
diff --git a/src/console.c b/src/console.c
index 3702dd5608f21ab611570e0a2e837b26326b5508..54fde7af7235cb75193c73d7cdfadfaa6508e20f 100644
--- a/src/console.c
+++ b/src/console.c
@@ -33,6 +33,7 @@
 #include "i_system.h"
 #include "d_main.h"
 #include "m_menu.h"
+#include "filesrch.h"
 
 #ifdef _WINDOWS
 #include "win32/win_main.h"
@@ -1275,12 +1276,15 @@ void CONS_Alert(alerttype_t level, const char *fmt, ...)
 	switch (level)
 	{
 		case CONS_NOTICE:
+			// no notice for notices, hehe
 			CONS_Printf("\x83" "%s" "\x80 ", M_GetText("NOTICE:"));
 			break;
 		case CONS_WARNING:
+			refreshdirmenu |= REFRESHDIR_WARNING;
 			CONS_Printf("\x82" "%s" "\x80 ", M_GetText("WARNING:"));
 			break;
 		case CONS_ERROR:
+			refreshdirmenu |= REFRESHDIR_ERROR;
 			CONS_Printf("\x85" "%s" "\x80 ", M_GetText("ERROR:"));
 			break;
 	}
@@ -1394,32 +1398,32 @@ static void CON_DrawInput(void)
 		if (input_sel < c)
 			V_DrawFill(x, y, charwidth*3, (10 * con_scalefactor), 77 | V_NOSCALESTART);
 		for (i = 0; i < 3; ++i, x += charwidth)
-			V_DrawCharacter(x, y, '.' | cv_constextsize.value | V_GRAYMAP | V_NOSCALESTART, !cv_allcaps.value);
+			V_DrawCharacter(x, y, '.' | cv_constextsize.value | V_GRAYMAP | V_NOSCALESTART, true);
 	}
 	else
-		V_DrawCharacter(x-charwidth, y, CON_PROMPTCHAR | cv_constextsize.value | V_GRAYMAP | V_NOSCALESTART, !cv_allcaps.value);
+		V_DrawCharacter(x-charwidth, y, CON_PROMPTCHAR | cv_constextsize.value | V_GRAYMAP | V_NOSCALESTART, true);
 
 	for (cend = c + clen; c < cend; ++c, x += charwidth)
 	{
 		if ((input_sel > c && input_cur <= c) || (input_sel <= c && input_cur > c))
 		{
 			V_DrawFill(x, y, charwidth, (10 * con_scalefactor), 77 | V_NOSCALESTART);
-			V_DrawCharacter(x, y, p[c] | cv_constextsize.value | V_YELLOWMAP | V_NOSCALESTART, !cv_allcaps.value);
+			V_DrawCharacter(x, y, p[c] | cv_constextsize.value | V_YELLOWMAP | V_NOSCALESTART, true);
 		}
 		else
-			V_DrawCharacter(x, y, p[c] | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value);
+			V_DrawCharacter(x, y, p[c] | cv_constextsize.value | V_NOSCALESTART, true);
 
 		if (c == input_cur && con_tick >= 4)
-			V_DrawCharacter(x, y + (con_scalefactor*2), '_' | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value);
+			V_DrawCharacter(x, y + (con_scalefactor*2), '_' | cv_constextsize.value | V_NOSCALESTART, true);
 	}
 	if (cend == input_cur && con_tick >= 4)
-		V_DrawCharacter(x, y + (con_scalefactor*2), '_' | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value);
+		V_DrawCharacter(x, y + (con_scalefactor*2), '_' | cv_constextsize.value | V_NOSCALESTART, true);
 	if (rellip)
 	{
 		if (input_sel > cend)
 			V_DrawFill(x, y, charwidth*3, (10 * con_scalefactor), 77 | V_NOSCALESTART);
 		for (i = 0; i < 3; ++i, x += charwidth)
-			V_DrawCharacter(x, y, '.' | cv_constextsize.value | V_GRAYMAP | V_NOSCALESTART, !cv_allcaps.value);
+			V_DrawCharacter(x, y, '.' | cv_constextsize.value | V_GRAYMAP | V_NOSCALESTART, true);
 	}
 }
 
@@ -1465,11 +1469,11 @@ static void CON_DrawHudlines(void)
 			else
 			{
 				//charwidth = SHORT(hu_font['A'-HU_FONTSTART]->width) * con_scalefactor;
-				V_DrawCharacter(x, y, (INT32)(*p) | charflags | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value);
+				V_DrawCharacter(x, y, (INT32)(*p) | charflags | cv_constextsize.value | V_NOSCALESTART, true);
 			}
 		}
 
-		//V_DrawCharacter(x, y, (p[c]&0xff) | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value);
+		//V_DrawCharacter(x, y, (p[c]&0xff) | cv_constextsize.value | V_NOSCALESTART, true);
 		y += charheight;
 	}
 
@@ -1607,7 +1611,7 @@ static void CON_DrawConsole(void)
 				charflags = (*p & 0x7f) << V_CHARCOLORSHIFT;
 				p++;
 			}
-			V_DrawCharacter(x, y, (INT32)(*p) | charflags | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value);
+			V_DrawCharacter(x, y, (INT32)(*p) | charflags | cv_constextsize.value | V_NOSCALESTART, true);
 		}
 	}
 
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 2db27a693cda8f4f4041265cc2c605e1afa0cf77..3878d879560d0a0991a0191cfdabd9c6188c8658 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -768,8 +768,16 @@ static void resynch_read_player(resynch_pak *rsp)
 	players[i].mo->scalespeed = LONG(rsp->scalespeed);
 
 	// And finally, SET THE MOBJ SKIN damn it.
-	players[i].mo->skin = &skins[players[i].skin];
-	players[i].mo->color = players[i].skincolor;
+	if ((players[i].powers[pw_carry] == CR_NIGHTSMODE) && (skins[players[i].skin].sprites[SPR2_NGT0].numframes == 0))
+	{
+		players[i].mo->skin = &skins[DEFAULTNIGHTSSKIN];
+		players[i].mo->color = skins[DEFAULTNIGHTSSKIN].prefcolor; // this will be corrected by thinker to super flash
+	}
+	else
+	{
+		players[i].mo->skin = &skins[players[i].skin];
+		players[i].mo->color = players[i].skincolor; // this will be corrected by thinker to super flash/mario star
+	}
 
 	P_SetThingPosition(players[i].mo);
 }
@@ -883,6 +891,7 @@ static inline void resynch_write_others(resynchend_pak *rst)
 	UINT8 i;
 
 	rst->ingame = 0;
+	rst->outofcoop = 0;
 
 	for (i = 0; i < MAXPLAYERS; ++i)
 	{
@@ -899,6 +908,8 @@ static inline void resynch_write_others(resynchend_pak *rst)
 
 		if (!players[i].spectator)
 			rst->ingame |= (1<<i);
+		if (players[i].outofcoop)
+			rst->outofcoop |= (1<<i);
 		rst->ctfteam[i] = (INT32)LONG(players[i].ctfteam);
 		rst->score[i] = (UINT32)LONG(players[i].score);
 		rst->numboxes[i] = SHORT(players[i].numboxes);
@@ -915,11 +926,13 @@ static inline void resynch_read_others(resynchend_pak *p)
 {
 	UINT8 i;
 	UINT32 loc_ingame = (UINT32)LONG(p->ingame);
+	UINT32 loc_outofcoop = (UINT32)LONG(p->outofcoop);
 
 	for (i = 0; i < MAXPLAYERS; ++i)
 	{
 		// We don't care if they're in the game or not, just write all the data.
 		players[i].spectator = !(loc_ingame & (1<<i));
+		players[i].outofcoop = (loc_outofcoop & (1<<i));
 		players[i].ctfteam = (INT32)LONG(p->ctfteam[i]); // no, 0 does not mean spectator, at least not in Match
 		players[i].score = (UINT32)LONG(p->score[i]);
 		players[i].numboxes = SHORT(p->numboxes[i]);
@@ -1319,7 +1332,7 @@ static void SV_SendPlayerInfo(INT32 node)
 		netbuffer->u.playerinfo[i].skin = (UINT8)players[i].skin;
 
 		// Extra data
-		netbuffer->u.playerinfo[i].data = players[i].skincolor;
+		netbuffer->u.playerinfo[i].data = 0; //players[i].skincolor;
 
 		if (players[i].pflags & PF_TAGIT)
 			netbuffer->u.playerinfo[i].data |= 0x20;
@@ -1563,8 +1576,6 @@ static void CL_LoadReceivedSavegame(void)
 	automapactive = false;
 
 	// load a base level
-	playerdeadview = false;
-
 	if (P_LoadNetGame())
 	{
 		const INT32 actnum = mapheaderinfo[gamemap-1]->actnum;
@@ -1742,9 +1753,7 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent)
 {
 #ifndef NONET
 	INT32 i;
-#endif
 
-#ifndef NONET
 	// serverlist is updated by GetPacket function
 	if (serverlistcount > 0)
 	{
@@ -1778,7 +1787,20 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent)
 				serverlist[i].info.fileneeded);
 			CONS_Printf(M_GetText("Checking files...\n"));
 			i = CL_CheckFiles();
-			if (i == 2) // cannot join for some reason
+			if (i == 3) // too many files
+			{
+				D_QuitNetGame();
+				CL_Reset();
+				D_StartTitle();
+				M_StartMessage(M_GetText(
+					"You have too many WAD files loaded\n"
+					"to add ones the server is using.\n"
+					"Please restart SRB2 before connecting.\n\n"
+					"Press ESC\n"
+				), NULL, MM_NOTHING);
+				return false;
+			}
+			else if (i == 2) // cannot join for some reason
 			{
 				D_QuitNetGame();
 				CL_Reset();
@@ -2518,12 +2540,18 @@ static void Command_Nodes(void)
 
 static void Command_Ban(void)
 {
-	if (COM_Argc() == 1)
+	if (COM_Argc() < 2)
 	{
 		CONS_Printf(M_GetText("Ban <playername/playernum> <reason>: ban and kick a player\n"));
 		return;
 	}
 
+	if (!netgame) // Don't kick Tails in splitscreen!
+	{
+		CONS_Printf(M_GetText("This only works in a netgame.\n"));
+		return;
+	}
+
 	if (server || adminplayer == consoleplayer)
 	{
 		XBOXSTATIC UINT8 buf[3 + MAX_REASONLENGTH];
@@ -2533,9 +2561,10 @@ static void Command_Ban(void)
 
 		if (pn == -1 || pn == 0)
 			return;
-		else
-			WRITEUINT8(p, pn);
-		if (I_Ban && !I_Ban(node))
+
+		WRITEUINT8(p, pn);
+
+		if (server && I_Ban && !I_Ban(node)) // only the server is allowed to do this right now
 		{
 			CONS_Alert(CONS_WARNING, M_GetText("Too many bans! Geez, that's a lot of people you're excluding...\n"));
 			WRITEUINT8(p, KICK_MSG_GO_AWAY);
@@ -2543,7 +2572,8 @@ static void Command_Ban(void)
 		}
 		else
 		{
-			Ban_Add(COM_Argv(2));
+			if (server) // only the server is allowed to do this right now
+				Ban_Add(COM_Argv(2));
 
 			if (COM_Argc() == 2)
 			{
@@ -2576,21 +2606,27 @@ static void Command_Ban(void)
 
 static void Command_Kick(void)
 {
-	XBOXSTATIC UINT8 buf[3 + MAX_REASONLENGTH];
-	UINT8 *p = buf;
-
-	if (COM_Argc() == 1)
+	if (COM_Argc() < 2)
 	{
 		CONS_Printf(M_GetText("kick <playername/playernum> <reason>: kick a player\n"));
 		return;
 	}
 
+	if (!netgame) // Don't kick Tails in splitscreen!
+	{
+		CONS_Printf(M_GetText("This only works in a netgame.\n"));
+		return;
+	}
+
 	if (server || adminplayer == consoleplayer)
 	{
+		XBOXSTATIC UINT8 buf[3 + MAX_REASONLENGTH];
+		UINT8 *p = buf;
 		const SINT8 pn = nametonum(COM_Argv(1));
-		WRITESINT8(p, pn);
+
 		if (pn == -1 || pn == 0)
 			return;
+
 		// Special case if we are trying to kick a player who is downloading the game state:
 		// trigger a timeout instead of kicking them, because a kick would only
 		// take effect after they have finished downloading
@@ -2599,6 +2635,9 @@ static void Command_Kick(void)
 			Net_ConnectionTimeout(playernode[pn]);
 			return;
 		}
+
+		WRITESINT8(p, pn);
+
 		if (COM_Argc() == 2)
 		{
 			WRITEUINT8(p, KICK_MSG_GO_AWAY);
@@ -2700,12 +2739,14 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 
 	// If a verified admin banned someone, the server needs to know about it.
 	// If the playernum isn't zero (the server) then the server needs to record the ban.
-	if (server && playernum && msg == KICK_MSG_BANNED)
+	if (server && playernum && (msg == KICK_MSG_BANNED || msg == KICK_MSG_CUSTOM_BAN))
 	{
 		if (I_Ban && !I_Ban(playernode[(INT32)pnum]))
-		{
 			CONS_Alert(CONS_WARNING, M_GetText("Too many bans! Geez, that's a lot of people you're excluding...\n"));
-		}
+#ifndef NONET
+		else
+			Ban_Add(reason);
+#endif
 	}
 
 	switch (msg)
@@ -3403,17 +3444,42 @@ static void HandlePacketFromAwayNode(SINT8 node)
 	if (node != servernode)
 		DEBFILE(va("Received packet from unknown host %d\n", node));
 
+// macro for packets that should only be sent by the server
+// if it is NOT from the server, bail out and close the connection!
+#define SERVERONLY \
+			if (node != servernode) \
+			{ \
+				Net_CloseConnection(node); \
+				break; \
+			}
 	switch (netbuffer->packettype)
 	{
 		case PT_ASKINFOVIAMS:
+#if 0
 			if (server && serverrunning)
 			{
-				INT32 clientnode = I_NetMakeNode(netbuffer->u.msaskinfo.clientaddr);
-				SV_SendServerInfo(clientnode, (tic_t)LONG(netbuffer->u.msaskinfo.time));
-				SV_SendPlayerInfo(clientnode); // Send extra info
-				Net_CloseConnection(clientnode);
-				// Don't close connection to MS.
+				INT32 clientnode;
+				if (ms_RoomId < 0) // ignore if we're not actually on the MS right now
+				{
+					Net_CloseConnection(node); // and yes, close connection
+					return;
+				}
+				clientnode = I_NetMakeNode(netbuffer->u.msaskinfo.clientaddr);
+				if (clientnode != -1)
+				{
+					SV_SendServerInfo(clientnode, (tic_t)LONG(netbuffer->u.msaskinfo.time));
+					SV_SendPlayerInfo(clientnode); // Send extra info
+					Net_CloseConnection(clientnode);
+					// Don't close connection to MS...
+				}
+				else
+					Net_CloseConnection(node); // ...unless the IP address is not valid
 			}
+			else
+				Net_CloseConnection(node); // you're not supposed to get it, so ignore it
+#else
+			Net_CloseConnection(node);
+#endif
 			break;
 
 		case PT_ASKINFO:
@@ -3421,8 +3487,8 @@ static void HandlePacketFromAwayNode(SINT8 node)
 			{
 				SV_SendServerInfo(node, (tic_t)LONG(netbuffer->u.askinfo.time));
 				SV_SendPlayerInfo(node); // Send extra info
-				Net_CloseConnection(node);
 			}
+			Net_CloseConnection(node);
 			break;
 
 		case PT_SERVERREFUSE: // Negative response of client join request
@@ -3431,6 +3497,7 @@ static void HandlePacketFromAwayNode(SINT8 node)
 				Net_CloseConnection(node);
 				break;
 			}
+			SERVERONLY
 			if (cl_mode == CL_WAITJOINRESPONSE)
 			{
 				// Save the reason so it can be displayed after quitting the netgame
@@ -3462,6 +3529,7 @@ static void HandlePacketFromAwayNode(SINT8 node)
 				Net_CloseConnection(node);
 				break;
 			}
+			SERVERONLY
 			/// \note how would this happen? and is it doing the right thing if it does?
 			if (cl_mode != CL_WAITJOINRESPONSE)
 				break;
@@ -3527,13 +3595,18 @@ static void HandlePacketFromAwayNode(SINT8 node)
 				Net_CloseConnection(node);
 				break;
 			}
-			else
-				Got_Filetxpak();
+			SERVERONLY
+			Got_Filetxpak();
 			break;
 
 		case PT_REQUESTFILE:
 			if (server)
-				Got_RequestFilePak(node);
+			{
+				if (!cv_downloading.value || !Got_RequestFilePak(node))
+					Net_CloseConnection(node); // close connection if one of the requested files could not be sent, or you disabled downloading anyway
+			}
+			else
+				Net_CloseConnection(node); // nope
 			break;
 
 		case PT_NODETIMEOUT:
@@ -3556,6 +3629,7 @@ static void HandlePacketFromAwayNode(SINT8 node)
 			break; // Ignore it
 
 	}
+#undef SERVERONLY
 }
 
 /** Handles a packet received from a node that is in game
@@ -3588,6 +3662,8 @@ FILESTAMP
 	{
 // -------------------------------------------- SERVER RECEIVE ----------
 		case PT_RESYNCHGET:
+			if (client)
+				break;
 			SV_AcknowledgeResynchAck(netconsole, netbuffer->u.resynchgot);
 			break;
 		case PT_CLIENTCMD:
@@ -3654,7 +3730,8 @@ FILESTAMP
 			}
 
 			// Splitscreen cmd
-			if (netbuffer->packettype == PT_CLIENT2CMD && nodetoplayer2[node] >= 0)
+			if ((netbuffer->packettype == PT_CLIENT2CMD || netbuffer->packettype == PT_CLIENT2MIS)
+				&& nodetoplayer2[node] >= 0)
 				G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][(UINT8)nodetoplayer2[node]],
 					&netbuffer->u.client2pak.cmd2, 1);
 
@@ -3713,6 +3790,27 @@ FILESTAMP
 				tic_t tic = maketic;
 				UINT8 *textcmd;
 
+				// ignore if the textcmd has a reported size of zero
+				// this shouldn't be sent at all
+				if (!netbuffer->u.textcmd[0])
+				{
+					DEBFILE(va("GetPacket: Textcmd with size 0 detected! (node %u, player %d)\n",
+						node, netconsole));
+					Net_UnAcknowledgePacket(node);
+					break;
+				}
+
+				// ignore if the textcmd size var is actually larger than it should be
+				// BASEPACKETSIZE + 1 (for size) + textcmd[0] should == datalength
+				if (netbuffer->u.textcmd[0] > (size_t)doomcom->datalength-BASEPACKETSIZE-1)
+				{
+					DEBFILE(va("GetPacket: Bad Textcmd packet size! (expected %d, actual %s, node %u, player %d)\n",
+					netbuffer->u.textcmd[0], sizeu1((size_t)doomcom->datalength-BASEPACKETSIZE-1),
+						node, netconsole));
+					Net_UnAcknowledgePacket(node);
+					break;
+				}
+
 				// check if tic that we are making isn't too large else we cannot send it :(
 				// doomcom->numslots+1 "+1" since doomcom->numslots can change within this time and sent time
 				j = software_MAXPACKETLENGTH
@@ -3906,7 +4004,7 @@ FILESTAMP
 			if (client)
 			{
 				INT32 i;
-				for (i = 0; i < MAXNETNODES; i++)
+				for (i = 0; i < MAXPLAYERS; i++)
 					if (playeringame[i])
 						playerpingtable[i] = (tic_t)netbuffer->u.pingtable[i];
 			}
@@ -3916,6 +4014,21 @@ FILESTAMP
 		case PT_SERVERCFG:
 			break;
 		case PT_FILEFRAGMENT:
+			// Only accept PT_FILEFRAGMENT from the server.
+			if (node != servernode)
+			{
+				CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_FILEFRAGMENT", node);
+
+				if (server)
+				{
+					XBOXSTATIC UINT8 buf[2];
+					buf[0] = (UINT8)node;
+					buf[1] = KICK_MSG_CON_FAIL;
+					SendNetXCmd(XD_KICK, &buf, 2);
+				}
+
+				break;
+			}
 			if (client)
 				Got_Filetxpak();
 			break;
@@ -4449,8 +4562,8 @@ static inline void PingUpdate(void)
 	}
 
 	//send out our ping packets
-	for (i = 0; i < MAXPLAYERS; i++)
-		if (playeringame[i])
+	for (i = 0; i < MAXNETNODES; i++)
+		if (nodeingame[i])
 			HSendPacket(i, true, 0, sizeof(INT32) * MAXPLAYERS);
 
 	pingmeasurecount = 1; //Reset count
@@ -4480,20 +4593,15 @@ void NetUpdate(void)
 
 	gametime = nowtime;
 
-	if (!(gametime % 255) && netgame && server)
-	{
-#ifdef NEWPING
-		PingUpdate();
-#endif
-	}
-
 #ifdef NEWPING
 	if (server)
 	{
+		if (netgame && !(gametime % 255))
+			PingUpdate();
 		// update node latency values so we can take an average later.
-		for (i = 0; i < MAXNETNODES; i++)
+		for (i = 0; i < MAXPLAYERS; i++)
 			if (playeringame[i])
-				realpingtable[i] += G_TicsToMilliseconds(GetLag(i));
+				realpingtable[i] += G_TicsToMilliseconds(GetLag(playernode[i]));
 		pingmeasurecount++;
 	}
 #endif
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index 1ca82fdc517026f314fdd8c16b568e06666e406f..b9a4eec3e56e0888d578556cdc6a218505bdf2a9 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -136,6 +136,7 @@ typedef struct
 	fixed_t flagz[2];
 
 	UINT32 ingame;  // Spectator bit for each player
+	UINT32 outofcoop;  // outofcoop bit for each player
 	INT32 ctfteam[MAXPLAYERS]; // Which team? (can't be 1 bit, since in regular Match there are no teams)
 
 	// Resynch game scores and the like all at once
@@ -315,6 +316,7 @@ typedef struct
 } ATTRPACK clientconfig_pak;
 
 #define MAXSERVERNAME 32
+#define MAXFILENEEDED 915
 // This packet is too large
 typedef struct
 {
@@ -336,7 +338,7 @@ typedef struct
 	unsigned char mapmd5[16];
 	UINT8 actnum;
 	UINT8 iszone;
-	UINT8 fileneeded[915]; // is filled with writexxx (byteptr.h)
+	UINT8 fileneeded[MAXFILENEEDED]; // is filled with writexxx (byteptr.h)
 } ATTRPACK serverinfo_pak;
 
 typedef struct
diff --git a/src/d_main.c b/src/d_main.c
index b23ffebb476dc6e80b6d8e138ddc28a4aa792cf0..7a8a85f255e96db1f7e4f5ad3c86963131df7325 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -74,6 +74,7 @@ int	snprintf(char *str, size_t n, const char *fmt, ...);
 #include "m_cond.h" // condition initialization
 #include "fastcmp.h"
 #include "keys.h"
+#include "filesrch.h" // refreshdirmenu, mainwadstally
 
 #ifdef CMAKECONFIG
 #include "config.h"
@@ -107,8 +108,6 @@ UINT8 window_notinfocus = false;
 //
 // DEMO LOOP
 //
-//static INT32 demosequence;
-static const char *pagename = "MAP1PIC";
 static char *startupwadfiles[MAX_WADFILES];
 
 boolean devparm = false; // started game with -devparm
@@ -420,10 +419,13 @@ static void D_Display(void)
 			}
 
 			// Image postprocessing effect
-			if (postimgtype)
-				V_DoPostProcessor(0, postimgtype, postimgparam);
-			if (postimgtype2)
-				V_DoPostProcessor(1, postimgtype2, postimgparam2);
+			if (rendermode == render_soft)
+			{
+				if (postimgtype)
+					V_DoPostProcessor(0, postimgtype, postimgparam);
+				if (postimgtype2)
+					V_DoPostProcessor(1, postimgtype2, postimgparam2);
+			}
 		}
 
 		if (lastdraw)
@@ -586,6 +588,8 @@ void D_SRB2Loop(void)
 		realtics = entertic - oldentertics;
 		oldentertics = entertic;
 
+		refreshdirmenu = 0; // not sure where to put this, here as good as any?
+
 #ifdef DEBUGFILE
 		if (!realtics)
 			if (debugload)
@@ -711,6 +715,7 @@ void D_StartTitle(void)
 	botskin = 0;
 	cv_debug = 0;
 	emeralds = 0;
+	lastmaploaded = 0;
 
 	// In case someone exits out at the same time they start a time attack run,
 	// reset modeattacking
@@ -719,10 +724,16 @@ void D_StartTitle(void)
 	// empty maptol so mario/etc sounds don't play in sound test when they shouldn't
 	maptol = 0;
 
+	// reset to default player stuff
+	COM_BufAddText (va("%s \"%s\"\n",cv_playername.name,cv_defaultplayername.string));
+	COM_BufAddText (va("%s \"%s\"\n",cv_skin.name,cv_defaultskin.string));
+	COM_BufAddText (va("%s \"%s\"\n",cv_playercolor.name,cv_defaultplayercolor.string));
+	COM_BufAddText (va("%s \"%s\"\n",cv_playername2.name,cv_defaultplayername2.string));
+	COM_BufAddText (va("%s \"%s\"\n",cv_skin2.name,cv_defaultskin2.string));
+	COM_BufAddText (va("%s \"%s\"\n",cv_playercolor2.name,cv_defaultplayercolor2.string));
+
 	gameaction = ga_nothing;
-	playerdeadview = false;
 	displayplayer = consoleplayer = 0;
-	//demosequence = -1;
 	gametype = GT_COOP;
 	paused = false;
 	advancedemo = false;
@@ -874,7 +885,7 @@ static void IdentifyVersion(void)
 	}
 #endif
 
-#if 1 // This section can be deleted when music_new is merged with music.dta
+#ifdef DEVELOP // This section can be deleted when music_new is merged with music.dta
 	{
 		const char *musicfile = "music_new.dta";
 		const char *musicpath = va(pandf,srb2waddir,musicfile);
@@ -887,27 +898,10 @@ static void IdentifyVersion(void)
 #endif
 }
 
+#ifdef PC_DOS
 /* ======================================================================== */
-// Just print the nice red titlebar like the original SRB2 for DOS.
+// Code for printing SRB2's title bar in DOS
 /* ======================================================================== */
-#ifdef PC_DOS
-static inline void D_Titlebar(char *title1, char *title2)
-{
-	// SRB2 banner
-	clrscr();
-	textattr((BLUE<<4)+WHITE);
-	clreol();
-	cputs(title1);
-
-	// standard srb2 banner
-	textattr((RED<<4)+WHITE);
-	clreol();
-	gotoxy((80-strlen(title2))/2, 2);
-	cputs(title2);
-	normvideo();
-	gotoxy(1,3);
-}
-#endif
 
 //
 // Center the title string, then add the date and time of compilation.
@@ -936,6 +930,31 @@ static inline void D_MakeTitleString(char *s)
 	strcpy(s, temp);
 }
 
+static inline void D_Titlebar(void)
+{
+	char title1[82]; // srb2 title banner
+	char title2[82];
+
+	strcpy(title1, "Sonic Robo Blast 2");
+	strcpy(title2, "Sonic Robo Blast 2");
+
+	D_MakeTitleString(title1);
+
+	// SRB2 banner
+	clrscr();
+	textattr((BLUE<<4)+WHITE);
+	clreol();
+	cputs(title1);
+
+	// standard srb2 banner
+	textattr((RED<<4)+WHITE);
+	clreol();
+	gotoxy((80-strlen(title2))/2, 2);
+	cputs(title2);
+	normvideo();
+	gotoxy(1,3);
+}
+#endif
 
 //
 // D_SRB2Main
@@ -943,8 +962,6 @@ static inline void D_MakeTitleString(char *s)
 void D_SRB2Main(void)
 {
 	INT32 p;
-	char srb2[82]; // srb2 title banner
-	char title[82];
 
 	INT32 pstartmap = 1;
 	boolean autostart = false;
@@ -987,20 +1004,8 @@ void D_SRB2Main(void)
 	dedicated = M_CheckParm("-dedicated") != 0;
 #endif
 
-	strcpy(title, "Sonic Robo Blast 2");
-	strcpy(srb2, "Sonic Robo Blast 2");
-	D_MakeTitleString(srb2);
-
 #ifdef PC_DOS
-	D_Titlebar(srb2, title);
-#endif
-
-#if defined (__OS2__) && !defined (HAVE_SDL)
-	// set PM window title
-	snprintf(pmData->title, sizeof (pmData->title),
-		"Sonic Robo Blast 2" VERSIONSTRING ": %s",
-		title);
-	pmData->title[sizeof (pmData->title) - 1] = '\0';
+	D_Titlebar();
 #endif
 
 	if (devparm)
@@ -1172,6 +1177,11 @@ void D_SRB2Main(void)
 #ifdef USE_PATCH_DTA
 	++mainwads; // patch.dta adds one more
 #endif
+#ifdef DEVELOP
+	++mainwads; // music_new, too
+#endif
+
+	mainwadstally = packetsizetally;
 
 	cht_Init();
 
@@ -1400,7 +1410,6 @@ void D_SRB2Main(void)
 
 	if (dedicated && server)
 	{
-		pagename = "TITLESKY";
 		levelstarttic = gametic;
 		G_SetGamestate(GS_LEVEL);
 		if (!P_SetupLevel(false))
diff --git a/src/d_main.h b/src/d_main.h
index 6dc273b1558d8ac6329a15100946dc889095eebf..d73b19d1f6e5fd34ab5fd0470a5c762379c6fd8b 100644
--- a/src/d_main.h
+++ b/src/d_main.h
@@ -34,7 +34,7 @@ void D_SRB2Loop(void) FUNCNORETURN;
 // D_SRB2Main()
 // Not a globally visible function, just included for source reference,
 // calls all startup code, parses command line options.
-// If not overrided by user input, calls N_AdvanceDemo.
+// If not overrided by user input, calls D_AdvanceDemo.
 //
 void D_SRB2Main(void);
 
@@ -51,9 +51,6 @@ const char *D_Home(void);
 //
 // BASE LEVEL
 //
-void D_PageTicker(void);
-// pagename is lumpname of a 320x200 patch to fill the screen
-void D_PageDrawer(const char *pagename);
 void D_AdvanceDemo(void);
 void D_StartTitle(void);
 
diff --git a/src/d_net.c b/src/d_net.c
index 7f16c302d1c3267c0f1b5476b3e4b10cbfb6593f..48c1d60ea7bd918840da01d31f0e488020f9cef6 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -49,7 +49,9 @@ doomcom_t *doomcom = NULL;
 /// \brief network packet data, points inside doomcom
 doomdata_t *netbuffer = NULL;
 
+#ifdef DEBUGFILE
 FILE *debugfile = NULL; // put some net info in a file during the game
+#endif
 
 #define MAXREBOUND 8
 static doomdata_t reboundstore[MAXREBOUND];
@@ -711,11 +713,24 @@ void Net_CloseConnection(INT32 node)
 #else
 	INT32 i;
 	boolean forceclose = (node & FORCECLOSE) != 0;
+
+	if (node == -1)
+	{
+		DEBFILE(M_GetText("Net_CloseConnection: node -1 detected!\n"));
+		return; // nope, just ignore it
+	}
+
 	node &= ~FORCECLOSE;
 
 	if (!node)
 		return;
 
+	if (node < 0 || node >= MAXNETNODES) // prevent invalid nodes from crashing the game
+	{
+		DEBFILE(va(M_GetText("Net_CloseConnection: invalid node %d detected!\n"), node));
+		return;
+	}
+
 	nodes[node].flags |= NF_CLOSE;
 
 	// try to Send ack back (two army problem)
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 43f04bae309771d54fbcae1e62ed79b0e635eada..7f408a2b5d8b28148dd88ca30a938f07626729bd 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -37,6 +37,7 @@
 #include "d_main.h"
 #include "m_random.h"
 #include "f_finale.h"
+#include "filesrch.h"
 #include "mserv.h"
 #include "md5.h"
 #include "z_zone.h"
@@ -60,9 +61,6 @@ static void Got_WeaponPref(UINT8 **cp, INT32 playernum);
 static void Got_Mapcmd(UINT8 **cp, INT32 playernum);
 static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum);
 static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum);
-#ifdef DELFILE
-static void Got_Delfilecmd(UINT8 **cp, INT32 playernum);
-#endif
 static void Got_Addfilecmd(UINT8 **cp, INT32 playernum);
 static void Got_Pause(UINT8 **cp, INT32 playernum);
 static void Got_Suicide(UINT8 **cp, INT32 playernum);
@@ -84,6 +82,9 @@ static void TeamScramble_OnChange(void);
 static void NetTimeout_OnChange(void);
 static void JoinTimeout_OnChange(void);
 
+static void CoopStarposts_OnChange(void);
+static void CoopLives_OnChange(void);
+
 static void Ringslinger_OnChange(void);
 static void Gravity_OnChange(void);
 static void ForceSkin_OnChange(void);
@@ -111,9 +112,6 @@ static void Command_ResetCamera_f(void);
 
 static void Command_Addfile(void);
 static void Command_ListWADS_f(void);
-#ifdef DELFILE
-static void Command_Delfile(void);
-#endif
 static void Command_RunSOC(void);
 static void Command_Pause(void);
 static void Command_Suicide(void);
@@ -184,19 +182,17 @@ static CV_PossibleValue_t joyport_cons_t[] = {{1, "/dev/js0"}, {2, "/dev/js1"},
 #define usejoystick_cons_t NULL
 #endif
 
-static CV_PossibleValue_t autobalance_cons_t[] = {{0, "MIN"}, {4, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t teamscramble_cons_t[] = {{0, "Off"}, {1, "Random"}, {2, "Points"}, {0, NULL}};
 
 static CV_PossibleValue_t startingliveslimit_cons_t[] = {{1, "MIN"}, {99, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t sleeping_cons_t[] = {{-1, "MIN"}, {1000/TICRATE, "MAX"}, {0, NULL}};
-static CV_PossibleValue_t competitionboxes_cons_t[] = {{0, "Normal"}, {1, "Random"}, {2, "Teleports"},
+static CV_PossibleValue_t competitionboxes_cons_t[] = {{0, "Normal"}, {1, "Mystery"}, //{2, "Teleport"},
 	{3, "None"}, {0, NULL}};
 
-static CV_PossibleValue_t matchboxes_cons_t[] = {{0, "Normal"}, {1, "Random"}, {2, "Non-Random"},
+static CV_PossibleValue_t matchboxes_cons_t[] = {{0, "Normal"}, {1, "Mystery"}, {2, "Unchanging"},
 	{3, "None"}, {0, NULL}};
 
 static CV_PossibleValue_t chances_cons_t[] = {{0, "MIN"}, {9, "MAX"}, {0, NULL}};
-static CV_PossibleValue_t match_scoring_cons_t[] = {{0, "Normal"}, {1, "Classic"}, {0, NULL}};
 static CV_PossibleValue_t pause_cons_t[] = {{0, "Server"}, {1, "All"}, {0, NULL}};
 
 static CV_PossibleValue_t timetic_cons_t[] = {{0, "Normal"}, {1, "Tics"}, {2, "Centiseconds"}, {0, NULL}};
@@ -215,7 +211,7 @@ consvar_t cv_startinglives = {"startinglives", "3", CV_NETVAR|CV_CHEAT, starting
 static CV_PossibleValue_t respawntime_cons_t[] = {{0, "MIN"}, {30, "MAX"}, {0, NULL}};
 consvar_t cv_respawntime = {"respawndelay", "3", CV_NETVAR|CV_CHEAT, respawntime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
-consvar_t cv_competitionboxes = {"competitionboxes", "Random", CV_NETVAR|CV_CHEAT, competitionboxes_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_competitionboxes = {"competitionboxes", "Mystery", CV_NETVAR|CV_CHEAT, competitionboxes_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 #ifdef SEENAMES
 static CV_PossibleValue_t seenames_cons_t[] = {{0, "Off"}, {1, "Colorless"}, {2, "Team"}, {3, "Ally/Foe"}, {0, NULL}};
@@ -223,9 +219,9 @@ consvar_t cv_seenames = {"seenames", "Ally/Foe", CV_SAVE, seenames_cons_t, 0, 0,
 consvar_t cv_allowseenames = {"allowseenames", "Yes", CV_NETVAR, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
 #endif
 
-// these are just meant to be saved to the config
-consvar_t cv_playername = {"name", "Sonic", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Name_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_playername2 = {"name2", "Tails", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Name2_OnChange, 0, NULL, NULL, 0, 0, NULL};
+// names
+consvar_t cv_playername = {"name", "Sonic", CV_CALL|CV_NOINIT, NULL, Name_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_playername2 = {"name2", "Tails", CV_CALL|CV_NOINIT, NULL, Name2_OnChange, 0, NULL, NULL, 0, 0, NULL};
 // player colors
 consvar_t cv_playercolor = {"color", "Blue", CV_CALL|CV_NOINIT, Color_cons_t, Color_OnChange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_playercolor2 = {"color2", "Orange", CV_CALL|CV_NOINIT, Color_cons_t, Color2_OnChange, 0, NULL, NULL, 0, 0, NULL};
@@ -233,6 +229,14 @@ consvar_t cv_playercolor2 = {"color2", "Orange", CV_CALL|CV_NOINIT, Color_cons_t
 consvar_t cv_skin = {"skin", DEFAULTSKIN, CV_CALL|CV_NOINIT, NULL, Skin_OnChange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_skin2 = {"skin2", DEFAULTSKIN2, CV_CALL|CV_NOINIT, NULL, Skin2_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
+// saved versions of the above six
+consvar_t cv_defaultplayername = {"defaultname", "Sonic", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_defaultplayername2 = {"defaultname2", "Tails", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_defaultplayercolor = {"defaultcolor", "Blue", CV_SAVE, Color_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_defaultplayercolor2 = {"defaultcolor2", "Orange", CV_SAVE, Color_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_defaultskin = {"defaultskin", DEFAULTSKIN, CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_defaultskin2 = {"defaultskin2", DEFAULTSKIN2, CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+
 consvar_t cv_skipmapcheck = {"skipmapcheck", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 INT32 cv_debug;
@@ -303,7 +307,7 @@ consvar_t cv_countdowntime = {"countdowntime", "60", CV_NETVAR|CV_CHEAT, minitim
 consvar_t cv_touchtag = {"touchtag", "Off", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_hidetime = {"hidetime", "30", CV_NETVAR|CV_CALL, minitimelimit_cons_t, Hidetime_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
-consvar_t cv_autobalance = {"autobalance", "0", CV_NETVAR|CV_CALL, autobalance_cons_t, AutoBalance_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_autobalance = {"autobalance", "Off", CV_NETVAR|CV_CALL, CV_OnOff, AutoBalance_OnChange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_teamscramble = {"teamscramble", "Off", CV_NETVAR|CV_CALL|CV_NOINIT, teamscramble_cons_t, TeamScramble_OnChange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_scrambleonchange = {"scrambleonchange", "Off", CV_NETVAR, teamscramble_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
@@ -311,7 +315,6 @@ consvar_t cv_friendlyfire = {"friendlyfire", "Off", CV_NETVAR, CV_OnOff, NULL, 0
 consvar_t cv_itemfinder = {"itemfinder", "Off", CV_CALL, CV_OnOff, ItemFinder_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
 // Scoring type options
-consvar_t cv_match_scoring = {"matchscoring", "Normal", CV_NETVAR|CV_CHEAT, match_scoring_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_overtime = {"overtime", "Yes", CV_NETVAR, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 consvar_t cv_rollingdemos = {"rollingdemos", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -349,12 +352,18 @@ consvar_t cv_maxping = {"maxping", "0", CV_SAVE, CV_Unsigned, NULL, 0, NULL, NUL
 #endif
 // Intermission time Tails 04-19-2002
 static CV_PossibleValue_t inttime_cons_t[] = {{0, "MIN"}, {3600, "MAX"}, {0, NULL}};
-consvar_t cv_inttime = {"inttime", "20", CV_NETVAR, inttime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_inttime = {"inttime", "10", CV_NETVAR, inttime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+static CV_PossibleValue_t coopstarposts_cons_t[] = {{0, "Per-player"}, {1, "Shared"}, {2, "Teamwork"}, {0, NULL}};
+consvar_t cv_coopstarposts = {"coopstarposts", "Teamwork", CV_NETVAR|CV_CALL|CV_CHEAT, coopstarposts_cons_t, CoopStarposts_OnChange, 0, NULL, NULL, 0, 0, NULL};
+
+static CV_PossibleValue_t cooplives_cons_t[] = {{0, "Infinite"}, {1, "Per-player"}, {2, "Avoid Game Over"}, {3, "Single pool"}, {0, NULL}};
+consvar_t cv_cooplives = {"cooplives", "Avoid Game Over", CV_NETVAR|CV_CALL|CV_CHEAT, cooplives_cons_t, CoopLives_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
 static CV_PossibleValue_t advancemap_cons_t[] = {{0, "Off"}, {1, "Next"}, {2, "Random"}, {0, NULL}};
 consvar_t cv_advancemap = {"advancemap", "Next", CV_NETVAR, advancemap_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-static CV_PossibleValue_t playersforexit_cons_t[] = {{0, "One"}, {1, "All"}, {0, NULL}};
-consvar_t cv_playersforexit = {"playersforexit", "One", CV_NETVAR, playersforexit_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+static CV_PossibleValue_t playersforexit_cons_t[] = {{0, "One"}, {1, "1/4"}, {2, "Half"}, {3, "3/4"}, {4, "All"}, {0, NULL}};
+consvar_t cv_playersforexit = {"playersforexit", "All", CV_NETVAR, playersforexit_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 consvar_t cv_runscripts = {"runscripts", "Yes", 0, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
 
@@ -388,7 +397,7 @@ const char *netxcmdnames[MAXNETXCMD - 1] =
 	"RANDOMSEED",
 	"RUNSOC",
 	"REQADDFILE",
-	"DELFILE",
+	"DELFILE", // replace next time we add an XD
 	"SETMOTD",
 	"SUICIDE",
 #ifdef HAVE_BLUA
@@ -414,9 +423,6 @@ void D_RegisterServerCommands(void)
 	RegisterNetXCmd(XD_EXITLEVEL, Got_ExitLevelcmd);
 	RegisterNetXCmd(XD_ADDFILE, Got_Addfilecmd);
 	RegisterNetXCmd(XD_REQADDFILE, Got_RequestAddfilecmd);
-#ifdef DELFILE
-	RegisterNetXCmd(XD_DELFILE, Got_Delfilecmd);
-#endif
 	RegisterNetXCmd(XD_PAUSE, Got_Pause);
 	RegisterNetXCmd(XD_SUICIDE, Got_Suicide);
 	RegisterNetXCmd(XD_RUNSOC, Got_RunSOCcmd);
@@ -450,9 +456,6 @@ void D_RegisterServerCommands(void)
 	COM_AddCommand("addfile", Command_Addfile);
 	COM_AddCommand("listwad", Command_ListWADS_f);
 
-#ifdef DELFILE
-	COM_AddCommand("delfile", Command_Delfile);
-#endif
 	COM_AddCommand("runsoc", Command_RunSOC);
 	COM_AddCommand("pause", Command_Pause);
 	COM_AddCommand("suicide", Command_Suicide);
@@ -485,7 +488,6 @@ void D_RegisterServerCommands(void)
 	CV_RegisterVar(&cv_itemrespawntime);
 	CV_RegisterVar(&cv_itemrespawn);
 	CV_RegisterVar(&cv_flagtime);
-	CV_RegisterVar(&cv_suddendeath);
 
 	// misc
 	CV_RegisterVar(&cv_friendlyfire);
@@ -510,6 +512,9 @@ void D_RegisterServerCommands(void)
 	CV_RegisterVar(&cv_forceskin);
 	CV_RegisterVar(&cv_downloading);
 
+	CV_RegisterVar(&cv_coopstarposts);
+	CV_RegisterVar(&cv_cooplives);
+
 	CV_RegisterVar(&cv_specialrings);
 	CV_RegisterVar(&cv_powerstones);
 	CV_RegisterVar(&cv_competitionboxes);
@@ -533,7 +538,6 @@ void D_RegisterServerCommands(void)
 	CV_RegisterVar(&cv_startinglives);
 	CV_RegisterVar(&cv_countdowntime);
 	CV_RegisterVar(&cv_runscripts);
-	CV_RegisterVar(&cv_match_scoring);
 	CV_RegisterVar(&cv_overtime);
 	CV_RegisterVar(&cv_pause);
 	CV_RegisterVar(&cv_mute);
@@ -616,6 +620,7 @@ void D_RegisterClientCommands(void)
 
 	CV_RegisterVar(&cv_screenshot_option);
 	CV_RegisterVar(&cv_screenshot_folder);
+	CV_RegisterVar(&cv_screenshot_colorprofile);
 	CV_RegisterVar(&cv_moviemode);
 	// PNG variables
 	CV_RegisterVar(&cv_zlib_level);
@@ -638,7 +643,7 @@ void D_RegisterClientCommands(void)
 
 	// register these so it is saved to config
 	if ((username = I_GetUserName()))
-		cv_playername.defaultvalue = username;
+		cv_playername.defaultvalue = cv_defaultplayername.defaultvalue = username;
 	CV_RegisterVar(&cv_playername);
 	CV_RegisterVar(&cv_playercolor);
 	CV_RegisterVar(&cv_skin); // r_things.c (skin NAME)
@@ -646,6 +651,13 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_playername2);
 	CV_RegisterVar(&cv_playercolor2);
 	CV_RegisterVar(&cv_skin2);
+	// saved versions of the above six
+	CV_RegisterVar(&cv_defaultplayername);
+	CV_RegisterVar(&cv_defaultplayercolor);
+	CV_RegisterVar(&cv_defaultskin);
+	CV_RegisterVar(&cv_defaultplayername2);
+	CV_RegisterVar(&cv_defaultplayercolor2);
+	CV_RegisterVar(&cv_defaultskin2);
 
 #ifdef SEENAMES
 	CV_RegisterVar(&cv_seenames);
@@ -673,7 +685,29 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_resetmusic);
 
 	// FIXME: not to be here.. but needs be done for config loading
-	CV_RegisterVar(&cv_usegamma);
+	CV_RegisterVar(&cv_globalgamma);
+	CV_RegisterVar(&cv_globalsaturation);
+
+	CV_RegisterVar(&cv_rhue);
+	CV_RegisterVar(&cv_yhue);
+	CV_RegisterVar(&cv_ghue);
+	CV_RegisterVar(&cv_chue);
+	CV_RegisterVar(&cv_bhue);
+	CV_RegisterVar(&cv_mhue);
+
+	CV_RegisterVar(&cv_rgamma);
+	CV_RegisterVar(&cv_ygamma);
+	CV_RegisterVar(&cv_ggamma);
+	CV_RegisterVar(&cv_cgamma);
+	CV_RegisterVar(&cv_bgamma);
+	CV_RegisterVar(&cv_mgamma);
+
+	CV_RegisterVar(&cv_rsaturation);
+	CV_RegisterVar(&cv_ysaturation);
+	CV_RegisterVar(&cv_gsaturation);
+	CV_RegisterVar(&cv_csaturation);
+	CV_RegisterVar(&cv_bsaturation);
+	CV_RegisterVar(&cv_msaturation);
 
 	// m_menu.c
 	CV_RegisterVar(&cv_crosshair);
@@ -695,6 +729,14 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_firenaxis);
 	CV_RegisterVar(&cv_firenaxis2);
 
+	// filesrch.c
+	CV_RegisterVar(&cv_addons_option);
+	CV_RegisterVar(&cv_addons_folder);
+	CV_RegisterVar(&cv_addons_md5);
+	CV_RegisterVar(&cv_addons_showall);
+	CV_RegisterVar(&cv_addons_search_type);
+	CV_RegisterVar(&cv_addons_search_case);
+
 	// WARNING: the order is important when initialising mouse2
 	// we need the mouse2port
 	CV_RegisterVar(&cv_mouse2port);
@@ -731,6 +773,7 @@ void D_RegisterClientCommands(void)
 
 	// s_sound.c
 	CV_RegisterVar(&cv_soundvolume);
+	CV_RegisterVar(&cv_closedcaptioning);
 	CV_RegisterVar(&cv_digmusicvolume);
 	CV_RegisterVar(&cv_midimusicvolume);
 	CV_RegisterVar(&cv_numChannels);
@@ -1144,7 +1187,7 @@ static void SendNameAndColor(void)
 			{
 				CV_StealthSetValue(&cv_playercolor, skins[cv_skin.value].prefcolor);
 
-				players[consoleplayer].skincolor = (cv_playercolor.value&0x1F) % MAXSKINCOLORS;
+				players[consoleplayer].skincolor = cv_playercolor.value % MAXSKINCOLORS;
 
 				if (players[consoleplayer].mo)
 					players[consoleplayer].mo->color = (UINT8)players[consoleplayer].skincolor;
@@ -1271,7 +1314,7 @@ static void SendNameAndColor2(void)
 			{
 				CV_StealthSetValue(&cv_playercolor2, skins[players[secondplaya].skin].prefcolor);
 
-				players[secondplaya].skincolor = (cv_playercolor2.value&0x1F) % MAXSKINCOLORS;
+				players[secondplaya].skincolor = cv_playercolor2.value % MAXSKINCOLORS;
 
 				if (players[secondplaya].mo)
 					players[secondplaya].mo->color = players[secondplaya].skincolor;
@@ -1589,8 +1632,13 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese
 		mapchangepending = 0;
 		// spawn the server if needed
 		// reset players if there is a new one
-		if (!(adminplayer == consoleplayer) && SV_SpawnServer())
-			buf[0] &= ~(1<<1);
+		if (!(adminplayer == consoleplayer))
+		{
+			if (SV_SpawnServer())
+				buf[0] &= ~(1<<1);
+			if (!Playing()) // you failed to start a server somehow, so cancel the map change
+				return;
+		}
 
 		// Kick bot from special stages
 		if (botskin)
@@ -2134,7 +2182,7 @@ static void Command_Teamchange_f(void)
 		return;
 	}
 
-	if (!cv_allowteamchange.value && !NetPacket.packet.newteam) // allow swapping to spectator even in locked teams.
+	if (!cv_allowteamchange.value && NetPacket.packet.newteam) // allow swapping to spectator even in locked teams.
 	{
 		CONS_Alert(CONS_NOTICE, M_GetText("The server is not allowing team changes at the moment.\n"));
 		return;
@@ -2231,7 +2279,7 @@ static void Command_Teamchange2_f(void)
 		return;
 	}
 
-	if (!cv_allowteamchange.value && !NetPacket.packet.newteam) // allow swapping to spectator even in locked teams.
+	if (!cv_allowteamchange.value && NetPacket.packet.newteam) // allow swapping to spectator even in locked teams.
 	{
 		CONS_Alert(CONS_NOTICE, M_GetText("The server is not allowing team changes at the moment.\n"));
 		return;
@@ -2985,6 +3033,7 @@ static void Command_Addfile(void)
 	XBOXSTATIC char buf[256];
 	char *buf_p = buf;
 	INT32 i;
+	int musiconly; // W_VerifyNMUSlumps isn't boolean
 
 	if (COM_Argc() != 2)
 	{
@@ -2999,7 +3048,9 @@ static void Command_Addfile(void)
 		if (!isprint(fn[i]) || fn[i] == ';')
 			return;
 
-	if (!W_VerifyNMUSlumps(fn))
+	musiconly = W_VerifyNMUSlumps(fn);
+
+	if (!musiconly)
 	{
 		// ... But only so long as they contain nothing more then music and sprites.
 		if (netgame && !(server || adminplayer == consoleplayer))
@@ -3011,7 +3062,7 @@ static void Command_Addfile(void)
 	}
 
 	// Add file on your client directly if it is trivial, or you aren't in a netgame.
-	if (!(netgame || multiplayer) || W_VerifyNMUSlumps(fn))
+	if (!(netgame || multiplayer) || musiconly)
 	{
 		P_AddWadFile(fn, NULL);
 		return;
@@ -3031,9 +3082,7 @@ static void Command_Addfile(void)
 #else
 		FILE *fhandle;
 
-		fhandle = fopen(fn, "rb");
-
-		if (fhandle)
+		if ((fhandle = W_OpenWadFile(&fn, true)) != NULL)
 		{
 			tic_t t = I_GetTime();
 			CONS_Debug(DBG_SETUP, "Making MD5 for %s\n",fn);
@@ -3041,11 +3090,8 @@ static void Command_Addfile(void)
 			CONS_Debug(DBG_SETUP, "MD5 calc for %s took %f second\n", fn, (float)(I_GetTime() - t)/TICRATE);
 			fclose(fhandle);
 		}
-		else
-		{
-			CONS_Printf(M_GetText("File %s not found.\n"), fn);
+		else // file not found
 			return;
-		}
 #endif
 		WRITEMEM(buf_p, md5sum, 16);
 	}
@@ -3056,49 +3102,19 @@ static void Command_Addfile(void)
 		SendNetXCmd(XD_ADDFILE, buf, buf_p - buf);
 }
 
-#ifdef DELFILE
-/** removes the last added pwad at runtime.
-  * Searches for sounds, maps, music and images to remove
-  */
-static void Command_Delfile(void)
-{
-	if (gamestate == GS_LEVEL)
-	{
-		CONS_Printf(M_GetText("You must NOT be in a level to use this.\n"));
-		return;
-	}
-
-	if (netgame && !(server || adminplayer == consoleplayer))
-	{
-		CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
-		return;
-	}
-
-	if (numwadfiles <= mainwads)
-	{
-		CONS_Printf(M_GetText("No additional WADs are loaded.\n"));
-		return;
-	}
-
-	if (!(netgame || multiplayer))
-	{
-		P_DelWadFile();
-		if (mainwads == numwadfiles && modifiedgame)
-			modifiedgame = false;
-		return;
-	}
-
-	SendNetXCmd(XD_DELFILE, NULL, 0);
-}
-#endif
-
 static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum)
 {
 	char filename[241];
 	filestatus_t ncs = FS_NOTFOUND;
 	UINT8 md5sum[16];
 	boolean kick = false;
+	boolean toomany = false;
 	INT32 i;
+	size_t packetsize = 0;
+	serverinfo_pak *dummycheck = NULL;
+
+	// Shut the compiler up.
+	(void)dummycheck;
 
 	READSTRINGN(*cp, filename, 240);
 	READMEM(*cp, md5sum, 16);
@@ -3124,13 +3140,25 @@ static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum)
 		return;
 	}
 
-	ncs = findfile(filename,md5sum,true);
+	// See W_LoadWadFile in w_wad.c
+	for (i = 0; i < numwadfiles; i++)
+		packetsize += nameonlylength(wadfiles[i]->filename) + 22;
+
+	packetsize += nameonlylength(filename) + 22;
 
-	if (ncs != FS_FOUND)
+	if ((numwadfiles >= MAX_WADFILES)
+	|| (packetsize > sizeof(dummycheck->fileneeded)))
+		toomany = true;
+	else
+		ncs = findfile(filename,md5sum,true);
+
+	if (ncs != FS_FOUND || toomany)
 	{
 		char message[256];
 
-		if (ncs == FS_NOTFOUND)
+		if (toomany)
+			sprintf(message, M_GetText("Too many files loaded to add %s\n"), filename);
+		else if (ncs == FS_NOTFOUND)
 			sprintf(message, M_GetText("The server doesn't have %s\n"), filename);
 		else if (ncs == FS_MD5SUMBAD)
 			sprintf(message, M_GetText("Checksum mismatch on %s\n"), filename);
@@ -3148,33 +3176,6 @@ static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum)
 	COM_BufAddText(va("addfile %s\n", filename));
 }
 
-#ifdef DELFILE
-static void Got_Delfilecmd(UINT8 **cp, INT32 playernum)
-{
-	if (playernum != serverplayer && playernum != adminplayer)
-	{
-		CONS_Alert(CONS_WARNING, M_GetText("Illegal delfile 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;
-	}
-	(void)cp;
-
-	if (numwadfiles <= mainwads) //sanity
-		return;
-
-	P_DelWadFile();
-	if (mainwads == numwadfiles && modifiedgame)
-		modifiedgame = false;
-}
-#endif
-
 static void Got_Addfilecmd(UINT8 **cp, INT32 playernum)
 {
 	char filename[241];
@@ -3200,10 +3201,15 @@ static void Got_Addfilecmd(UINT8 **cp, INT32 playernum)
 
 	ncs = findfile(filename,md5sum,true);
 
-	if (ncs != FS_FOUND)
+	if (ncs != FS_FOUND || !P_AddWadFile(filename, NULL))
 	{
 		Command_ExitGame_f();
-		if (ncs == FS_NOTFOUND)
+		if (ncs == FS_FOUND)
+		{
+			CONS_Printf(M_GetText("The server tried to add %s,\nbut you have too many files added.\nRestart the game to clear loaded files\nand play on this server."), filename);
+			M_StartMessage(va("The server added a file \n(%s)\nbut you have too many files added.\nRestart the game to clear loaded files.\n\nPress ESC\n",filename), NULL, MM_NOTHING);
+		}
+		else if (ncs == FS_NOTFOUND)
 		{
 			CONS_Printf(M_GetText("The server tried to add %s,\nbut you don't have this file.\nYou need to find it in order\nto play on this server."), filename);
 			M_StartMessage(va("The server added a file \n(%s)\nthat you do not have.\n\nPress ESC\n",filename), NULL, MM_NOTHING);
@@ -3221,7 +3227,6 @@ static void Got_Addfilecmd(UINT8 **cp, INT32 playernum)
 		return;
 	}
 
-	P_AddWadFile(filename, NULL);
 	G_SetGameModified(true);
 }
 
@@ -3376,6 +3381,102 @@ static void JoinTimeout_OnChange(void)
 	jointimeout = (tic_t)cv_jointimeout.value;
 }
 
+static void CoopStarposts_OnChange(void)
+{
+	INT32 i;
+
+	if (!(netgame || multiplayer) || gametype != GT_COOP)
+		return;
+
+	switch (cv_coopstarposts.value)
+	{
+		case 0:
+			CONS_Printf(M_GetText("Starposts are now per-player.\n"));
+			break;
+		case 1:
+			CONS_Printf(M_GetText("Starposts are now shared between players.\n"));
+			break;
+		case 2:
+			CONS_Printf(M_GetText("Players now only spawn when starposts are hit.\n"));
+			return;
+	}
+
+	if (G_IsSpecialStage(gamemap))
+		return;
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!playeringame[i])
+			continue;
+
+		if (!players[i].spectator)
+			continue;
+
+		if (players[i].lives <= 0)
+			continue;
+
+		break;
+	}
+
+	if (i == MAXPLAYERS)
+		return;
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!playeringame[i])
+			continue;
+
+		if (!players[i].spectator)
+			continue;
+
+		if (players[i].lives <= 0 && (cv_cooplives.value == 1))
+			continue;
+
+		P_SpectatorJoinGame(&players[i]);
+	}
+}
+
+static void CoopLives_OnChange(void)
+{
+	INT32 i;
+
+	if (!(netgame || multiplayer) || gametype != GT_COOP)
+		return;
+
+	switch (cv_cooplives.value)
+	{
+		case 0:
+			CONS_Printf(M_GetText("Players can now respawn indefinitely.\n"));
+			return;
+		case 1:
+			CONS_Printf(M_GetText("Lives are now per-player.\n"));
+			return;
+		case 2:
+			CONS_Printf(M_GetText("Players can now steal lives to avoid game over.\n"));
+			break;
+		case 3:
+			CONS_Printf(M_GetText("Lives are now shared between players.\n"));
+			break;
+	}
+
+	if (cv_coopstarposts.value == 2)
+		return;
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!playeringame[i])
+			continue;
+
+		if (!players[i].spectator)
+			continue;
+
+		if (players[i].lives > 0)
+			continue;
+
+		P_SpectatorJoinGame(&players[i]);
+	}
+}
+
 UINT32 timelimitintics = 0;
 
 /** Deals with a timelimit change by printing the change to the console.
@@ -3666,7 +3767,7 @@ retryscramble:
 			{
 				if (red == maxcomposition)
 					newteam = 2;
-				else if (blue == maxcomposition)
+				else //if (blue == maxcomposition)
 					newteam = 1;
 
 				repick = false;
@@ -3707,14 +3808,11 @@ retryscramble:
 				newteam = (INT16)((M_RandomByte() % 2) + 1);
 				repick = false;
 			}
-			else
+			else if (i != 2) // Mystic's secret sauce - ABBA is better than ABAB, so team B doesn't get worse players all around
 			{
 				// We will only randomly pick the team for the first guy.
 				// Otherwise, just alternate back and forth, distributing players.
-				if (newteam == 1)
-					newteam = 2;
-				else
-					newteam = 1;
+				newteam = 3 - newteam;
 			}
 
 			scrambleteams[i] = newteam;
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index 08fc8b831a507bb0d4662fc9d61e4141820322a6..57e23b0f1e0752d95576e197fe3de8aaadb1806b 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -20,6 +20,19 @@
 // console vars
 extern consvar_t cv_playername;
 extern consvar_t cv_playercolor;
+extern consvar_t cv_skin;
+// secondary splitscreen player
+extern consvar_t cv_playername2;
+extern consvar_t cv_playercolor2;
+extern consvar_t cv_skin2;
+// saved versions of the above six
+extern consvar_t cv_defaultplayername;
+extern consvar_t cv_defaultplayercolor;
+extern consvar_t cv_defaultskin;
+extern consvar_t cv_defaultplayername2;
+extern consvar_t cv_defaultplayercolor2;
+extern consvar_t cv_defaultskin2;
+
 #ifdef SEENAMES
 extern consvar_t cv_seenames, cv_allowseenames;
 #endif
@@ -32,7 +45,6 @@ extern consvar_t cv_joyport2;
 #endif
 extern consvar_t cv_joyscale;
 extern consvar_t cv_joyscale2;
-extern consvar_t cv_controlperkey;
 
 // splitscreen with second mouse
 extern consvar_t cv_mouse2port;
@@ -40,25 +52,12 @@ extern consvar_t cv_usemouse2;
 #if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON)
 extern consvar_t cv_mouse2opt;
 #endif
-extern consvar_t cv_invertmouse2;
-extern consvar_t cv_alwaysfreelook2;
-extern consvar_t cv_mousemove2;
-extern consvar_t cv_mousesens2;
-extern consvar_t cv_mouseysens2;
 
 // normally in p_mobj but the .h is not read
 extern consvar_t cv_itemrespawntime;
 extern consvar_t cv_itemrespawn;
 
 extern consvar_t cv_flagtime;
-extern consvar_t cv_suddendeath;
-
-extern consvar_t cv_skin;
-
-// secondary splitscreen player
-extern consvar_t cv_playername2;
-extern consvar_t cv_playercolor2;
-extern consvar_t cv_skin2;
 
 extern consvar_t cv_touchtag;
 extern consvar_t cv_hidetime;
@@ -77,9 +76,6 @@ extern consvar_t cv_autobalance;
 extern consvar_t cv_teamscramble;
 extern consvar_t cv_scrambleonchange;
 
-extern consvar_t cv_useranalog, cv_useranalog2;
-extern consvar_t cv_analog, cv_analog2;
-
 extern consvar_t cv_netstat;
 #ifdef WALLSPLATS
 extern consvar_t cv_splats;
@@ -100,8 +96,7 @@ extern consvar_t cv_recycler;
 
 extern consvar_t cv_itemfinder;
 
-extern consvar_t cv_inttime, cv_advancemap, cv_playersforexit;
-extern consvar_t cv_match_scoring;
+extern consvar_t cv_inttime, cv_coopstarposts, cv_cooplives, cv_advancemap, cv_playersforexit;
 extern consvar_t cv_overtime;
 extern consvar_t cv_startinglives;
 
@@ -120,17 +115,7 @@ extern consvar_t cv_maxping;
 
 extern consvar_t cv_skipmapcheck;
 
-extern consvar_t cv_sleep, cv_screenshot_option, cv_screenshot_folder;
-
-extern consvar_t cv_moviemode;
-
-extern consvar_t cv_zlib_level, cv_zlib_memory, cv_zlib_strategy;
-
-extern consvar_t cv_zlib_window_bits, cv_zlib_levela, cv_zlib_memorya;
-
-extern consvar_t cv_zlib_strategya, cv_zlib_window_bitsa;
-
-extern consvar_t cv_apng_delay;
+extern consvar_t cv_sleep;
 
 typedef enum
 {
@@ -151,7 +136,7 @@ typedef enum
 	XD_RANDOMSEED,  // 15
 	XD_RUNSOC,      // 16
 	XD_REQADDFILE,  // 17
-	XD_DELFILE,     // 18
+	XD_DELFILE,     // 18 - replace next time we add an XD
 	XD_SETMOTD,     // 19
 	XD_SUICIDE,     // 20
 #ifdef HAVE_BLUA
@@ -211,7 +196,6 @@ void Command_ExitGame_f(void);
 void Command_Retry_f(void);
 void D_GameTypeChanged(INT32 lastgametype); // not a real _OnChange function anymore
 void D_MapChange(INT32 pmapnum, INT32 pgametype, boolean pultmode, boolean presetplayers, INT32 pdelay, boolean pskipprecutscene, boolean pfromlevelselect);
-void ObjectPlace_OnChange(void);
 void ItemFinder_OnChange(void);
 void D_SetPassword(const char *pw);
 
diff --git a/src/d_netfil.c b/src/d_netfil.c
index bf4e5987825b2ca40c9fb91f4b28f42aef096e12..9f2446e7e51ee13cf90ef7b2524c325af7bcfbfb 100644
--- a/src/d_netfil.c
+++ b/src/d_netfil.c
@@ -62,7 +62,8 @@
 
 #include <errno.h>
 
-static void SV_SendFile(INT32 node, const char *filename, UINT8 fileid);
+// Prototypes
+static boolean SV_SendFile(INT32 node, const char *filename, UINT8 fileid);
 
 // Sender structure
 typedef struct filetx_s
@@ -103,6 +104,7 @@ INT32 lastfilenum = -1;
 /** Fills a serverinfo packet with information about wad files loaded.
   *
   * \todo Give this function a better name since it is in global scope.
+  * Used to have size limiting built in - now handled via W_LoadWadFile in w_wad.c
   *
   */
 UINT8 *PutFileNeeded(void)
@@ -111,29 +113,22 @@ UINT8 *PutFileNeeded(void)
 	UINT8 *p = netbuffer->u.serverinfo.fileneeded;
 	char wadfilename[MAX_WADPATH] = "";
 	UINT8 filestatus;
-	size_t bytesused = 0;
 
 	for (i = 0; i < numwadfiles; i++)
 	{
-		// If it has only music/sound lumps, mark it as unimportant
-		if (W_VerifyNMUSlumps(wadfiles[i]->filename))
-			filestatus = 0;
-		else
-			filestatus = 1; // Important
+		// If it has only music/sound lumps, don't put it in the list
+		if (!wadfiles[i]->important)
+			continue;
+
+		filestatus = 1; // Importance - not really used any more, holds 1 by default for backwards compat with MS
 
 		// Store in the upper four bits
 		if (!cv_downloading.value)
 			filestatus += (2 << 4); // Won't send
-		else if ((wadfiles[i]->filesize > (UINT32)cv_maxsend.value * 1024))
-			filestatus += (0 << 4); // Won't send
-		else
+		else if ((wadfiles[i]->filesize <= (UINT32)cv_maxsend.value * 1024))
 			filestatus += (1 << 4); // Will send if requested
-
-		bytesused += (nameonlylength(wadfilename) + 22);
-
-		// Don't write too far...
-		if (bytesused > sizeof(netbuffer->u.serverinfo.fileneeded))
-			I_Error("Too many wad files added to host a game. (%s, stopped on %s)\n", sizeu1(bytesused), wadfilename);
+		// else
+			// filestatus += (0 << 4); -- Won't send, too big
 
 		WRITEUINT8(p, filestatus);
 
@@ -166,7 +161,6 @@ void D_ParseFileneeded(INT32 fileneedednum_parm, UINT8 *fileneededstr)
 	{
 		fileneeded[i].status = FS_NOTFOUND; // We haven't even started looking for the file yet
 		filestatus = READUINT8(p); // The first byte is the file status
-		fileneeded[i].important = (UINT8)(filestatus & 3);
 		fileneeded[i].willsend = (UINT8)(filestatus >> 4);
 		fileneeded[i].totalsize = READUINT32(p); // The four next bytes are the file size
 		fileneeded[i].file = NULL; // The file isn't open yet
@@ -196,7 +190,7 @@ boolean CL_CheckDownloadable(void)
 	UINT8 i,dlstatus = 0;
 
 	for (i = 0; i < fileneedednum; i++)
-		if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN && fileneeded[i].important)
+		if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN)
 		{
 			if (fileneeded[i].willsend == 1)
 				continue;
@@ -217,7 +211,7 @@ boolean CL_CheckDownloadable(void)
 	// not downloadable, put reason in console
 	CONS_Alert(CONS_NOTICE, M_GetText("You need additional files to connect to this server:\n"));
 	for (i = 0; i < fileneedednum; i++)
-		if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN && fileneeded[i].important)
+		if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN)
 		{
 			CONS_Printf(" * \"%s\" (%dK)", fileneeded[i].filename, fileneeded[i].totalsize >> 10);
 
@@ -270,7 +264,7 @@ boolean CL_SendRequestFile(void)
 
 	for (i = 0; i < fileneedednum; i++)
 		if (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN
-			&& fileneeded[i].important && (fileneeded[i].willsend == 0 || fileneeded[i].willsend == 2))
+			&& (fileneeded[i].willsend == 0 || fileneeded[i].willsend == 2))
 		{
 			I_Error("Attempted to download files that were not sendable");
 		}
@@ -279,8 +273,7 @@ boolean CL_SendRequestFile(void)
 	netbuffer->packettype = PT_REQUESTFILE;
 	p = (char *)netbuffer->u.textcmd;
 	for (i = 0; i < fileneedednum; i++)
-		if ((fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD)
-			&& fileneeded[i].important)
+		if ((fileneeded[i].status == FS_NOTFOUND || fileneeded[i].status == FS_MD5SUMBAD))
 		{
 			totalfreespaceneeded += fileneeded[i].totalsize;
 			nameonly(fileneeded[i].filename);
@@ -303,7 +296,8 @@ boolean CL_SendRequestFile(void)
 }
 
 // get request filepak and put it on the send queue
-void Got_RequestFilePak(INT32 node)
+// returns false if a requested file was not found or cannot be sent
+boolean Got_RequestFilePak(INT32 node)
 {
 	char wad[MAX_WADPATH+1];
 	UINT8 *p = netbuffer->u.textcmd;
@@ -314,8 +308,13 @@ void Got_RequestFilePak(INT32 node)
 		if (id == 0xFF)
 			break;
 		READSTRINGN(p, wad, MAX_WADPATH);
-		SV_SendFile(node, wad, id);
+		if (!SV_SendFile(node, wad, id))
+		{
+			SV_AbortSendFiles(node);
+			return false; // don't read the rest of the files
+		}
 	}
+	return true; // no problems with any files
 }
 
 /** Checks if the files needed aren't already loaded or on the disk
@@ -330,6 +329,12 @@ INT32 CL_CheckFiles(void)
 	INT32 i, j;
 	char wadfilename[MAX_WADPATH];
 	INT32 ret = 1;
+	size_t packetsize = 0;
+	size_t filestoget = 0;
+	serverinfo_pak *dummycheck = NULL;
+
+	// Shut the compiler up.
+	(void)dummycheck;
 
 //	if (M_CheckParm("-nofiles"))
 //		return 1;
@@ -347,15 +352,9 @@ INT32 CL_CheckFiles(void)
 		CONS_Debug(DBG_NETPLAY, "game is modified; only doing basic checks\n");
 		for (i = 1, j = 1; i < fileneedednum || j < numwadfiles;)
 		{
-			if (i < fileneedednum && !fileneeded[i].important)
+			if (j < numwadfiles && !wadfiles[j]->important)
 			{
-				// Eh whatever, don't care
-				++i;
-				continue;
-			}
-			if (j < numwadfiles && W_VerifyNMUSlumps(wadfiles[j]->filename))
-			{
-				// Unimportant on our side. still don't care.
+				// Unimportant on our side.
 				++j;
 				continue;
 			}
@@ -378,6 +377,10 @@ INT32 CL_CheckFiles(void)
 		return 1;
 	}
 
+	// See W_LoadWadFile in w_wad.c
+	for (i = 0; i < numwadfiles; i++)
+		packetsize += nameonlylength(wadfiles[i]->filename) + 22;
+
 	for (i = 1; i < fileneedednum; i++)
 	{
 		CONS_Debug(DBG_NETPLAY, "searching for '%s' ", fileneeded[i].filename);
@@ -394,9 +397,17 @@ INT32 CL_CheckFiles(void)
 				break;
 			}
 		}
-		if (fileneeded[i].status != FS_NOTFOUND || !fileneeded[i].important)
+		if (fileneeded[i].status != FS_NOTFOUND)
 			continue;
 
+		packetsize += nameonlylength(fileneeded[i].filename) + 22;
+
+		if ((numwadfiles+filestoget >= MAX_WADFILES)
+		|| (packetsize > sizeof(dummycheck->fileneeded)))
+			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)
@@ -424,27 +435,8 @@ void CL_LoadServerFiles(void)
 			fileneeded[i].status = FS_OPEN;
 		}
 		else if (fileneeded[i].status == FS_MD5SUMBAD)
-		{
-			// If the file is marked important, don't even bother proceeding.
-			if (fileneeded[i].important)
-				I_Error("Wrong version of important file %s", fileneeded[i].filename);
-
-			// If it isn't, no need to worry the user with a console message,
-			// although it can't hurt to put something in the debug file.
-
-			// ...but wait a second. What if the local version is "important"?
-			if (!W_VerifyNMUSlumps(fileneeded[i].filename))
-				I_Error("File %s should only contain music and sound effects!",
-					fileneeded[i].filename);
-
-			// Okay, NOW we know it's safe. Whew.
-			P_AddWadFile(fileneeded[i].filename, NULL);
-			if (fileneeded[i].important)
-				G_SetGameModified(true);
-			fileneeded[i].status = FS_OPEN;
-			DEBFILE(va("File %s found but with different md5sum\n", fileneeded[i].filename));
-		}
-		else if (fileneeded[i].important)
+			I_Error("Wrong version of file %s", fileneeded[i].filename);
+		else
 		{
 			const char *s;
 			switch(fileneeded[i].status)
@@ -480,7 +472,7 @@ static INT32 filestosend = 0;
   * \sa SV_SendRam
   *
   */
-static void SV_SendFile(INT32 node, const char *filename, UINT8 fileid)
+static boolean SV_SendFile(INT32 node, const char *filename, UINT8 fileid)
 {
 	filetx_t **q; // A pointer to the "next" field of the last file in the list
 	filetx_t *p; // The new file request
@@ -488,7 +480,7 @@ static void SV_SendFile(INT32 node, const char *filename, UINT8 fileid)
 	char wadfilename[MAX_WADPATH];
 
 	if (cv_noticedownload.value)
-		CONS_Printf("Sending file \"%s\" to node %d\n", filename, node);
+		CONS_Printf("Sending file \"%s\" to node %d (%s)\n", filename, node, I_GetNodeAddress(node));
 
 	// Find the last file in the list and set a pointer to its "next" field
 	q = &transfer[node].txlist;
@@ -537,7 +529,7 @@ static void SV_SendFile(INT32 node, const char *filename, UINT8 fileid)
 		free(p->id.filename);
 		free(p);
 		*q = NULL;
-		return;
+		return false; // cancel the rest of the requests
 	}
 
 	// Handle huge file requests (i.e. bigger than cv_maxsend.value KB)
@@ -549,7 +541,7 @@ static void SV_SendFile(INT32 node, const char *filename, UINT8 fileid)
 		free(p->id.filename);
 		free(p);
 		*q = NULL;
-		return;
+		return false; // cancel the rest of the requests
 	}
 
 	DEBFILE(va("Sending file %s (id=%d) to %d\n", filename, fileid, node));
@@ -557,6 +549,7 @@ static void SV_SendFile(INT32 node, const char *filename, UINT8 fileid)
 	p->fileid = fileid;
 	p->next = NULL; // End of list
 	filestosend++;
+	return true;
 }
 
 /** Adds a memory block to the file list for a node
diff --git a/src/d_netfil.h b/src/d_netfil.h
index c9085a5b0ecdc38ed3fb8a0ecb9278ef37369749..6fdd0a8a1e3cbf2995a748c91e865fe7a06ff1fb 100644
--- a/src/d_netfil.h
+++ b/src/d_netfil.h
@@ -35,7 +35,6 @@ typedef enum
 
 typedef struct
 {
-	UINT8 important;
 	UINT8 willsend; // Is the server willing to send it?
 	char filename[MAX_WADPATH];
 	UINT8 md5sum[16];
@@ -69,7 +68,7 @@ boolean SV_SendingFile(INT32 node);
 
 boolean CL_CheckDownloadable(void);
 boolean CL_SendRequestFile(void);
-void Got_RequestFilePak(INT32 node);
+boolean Got_RequestFilePak(INT32 node);
 
 void SV_AbortSendFiles(INT32 node);
 void CloseNetFile(void);
diff --git a/src/d_player.h b/src/d_player.h
index 05a99db2ae45dffa7db9d3946159eeac9113cdd5..c10e59405e4fe44c48bacb130421a41217f3a9b1 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -181,6 +181,14 @@ typedef enum
 	PA_RIDE
 } panim_t;
 
+//
+// All of the base srb2 shields are either a single constant,
+// or use damagetype-protecting flags applied to a constant,
+// or are the force shield (which does everything weirdly).
+//
+// Base flags by themselves aren't used so modders can make
+// abstract, ability-less shields should they so choose.
+//
 typedef enum
 {
 	SH_NONE = 0,
@@ -189,19 +197,21 @@ typedef enum
 	SH_PROTECTFIRE = 0x400,
 	SH_PROTECTWATER = 0x800,
 	SH_PROTECTELECTRIC = 0x1000,
+	SH_PROTECTSPIKE = 0x2000, // cactus shield one day? thanks, subarashii
+	//SH_PROTECTNUKE = 0x4000, // intentionally no hardcoded defense against nukes
 
 	// Indivisible shields
 	SH_PITY = 1, // the world's most basic shield ever, given to players who suck at Match
 	SH_WHIRLWIND,
 	SH_ARMAGEDDON,
 
-	// normal shields that use flags
-	SH_ATTRACT = SH_PROTECTELECTRIC,
-	SH_ELEMENTAL = SH_PROTECTFIRE|SH_PROTECTWATER,
+	// Normal shields that use flags
+	SH_ATTRACT = SH_PITY|SH_PROTECTELECTRIC,
+	SH_ELEMENTAL = SH_PITY|SH_PROTECTFIRE|SH_PROTECTWATER,
 
 	// Sonic 3 shields
-	SH_FLAMEAURA = SH_PROTECTFIRE,
-	SH_BUBBLEWRAP = SH_PROTECTWATER,
+	SH_FLAMEAURA = SH_PITY|SH_PROTECTFIRE,
+	SH_BUBBLEWRAP = SH_PITY|SH_PROTECTWATER,
 	SH_THUNDERCOIN = SH_WHIRLWIND|SH_PROTECTELECTRIC,
 
 	// The force shield uses the lower 8 bits to count how many extra hits are left.
@@ -475,6 +485,7 @@ typedef struct player_s
 	angle_t awayviewaiming; // Used for cut-away view
 
 	boolean spectator;
+	boolean outofcoop;
 	UINT8 bot;
 
 	tic_t jointime; // Timer when player joins game to change skin/color
diff --git a/src/dehacked.c b/src/dehacked.c
index 8657e8de3a65dcbad7dd368e86abc688dd803c1a..5867eb90af49983e63ac0ea45f84f240be276fac 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -78,97 +78,6 @@ static int dbg_line;
 
 static boolean gamedataadded = false;
 
-#ifdef DELFILE
-typedef struct undehacked_s
-{
-	char *undata;
-	struct undehacked_s *next;
-} undehacked_t;
-
-static UINT16 unsocwad;
-static undehacked_t *unsocdata[MAX_WADFILES];
-static boolean disableundo = false;
-
-void DEH_WriteUndoline(const char *value, const char *data, undotype_f flags)
-{
-	const char *eqstr = " = ";
-	const char *space = " ";
-	const char *pader = eqstr;
-	undehacked_t *newdata;
-
-	if (disableundo || !unsocwad)
-		return;
-
-	if ((newdata = malloc(sizeof(*newdata))) == NULL)
-		I_Error("Out of memory for unsoc line");
-
-	if (flags & UNDO_SPACE)
-		pader = space;
-
-	if (flags & UNDO_ENDTEXT && !data)
-		data = space;
-
-	if (value)
-	{
-		const size_t plen = strlen(pader);
-		const char *pound = "#";
-		char *undata = NULL;
-		const size_t elen = strlen(pound);
-		size_t vlen = strlen(value), dlen = 0, len = 1;
-
-		if (*(value+vlen-1) == '\n')
-			vlen--; // lnet not copy the ending \n
-
-		if (flags & UNDO_ENDTEXT)
-			len += elen; // let malloc more space
-
-		if (flags & UNDO_NEWLINE)
-			len++; // more space for the beginning \n
-
-		if (data)
-		{
-			dlen = strlen(data);
-			if (flags & UNDO_CUTLINE && *(data+dlen-1) == '\n')
-				dlen--; // let not copy the ending \n
-			newdata->undata = malloc(vlen+plen+dlen+len);
-			newdata->undata[vlen+plen+dlen+len-1] = '\0';
-		}
-		else
-		{
-			newdata->undata = malloc(vlen+len);
-			newdata->undata[vlen+len-1] = '\0';
-		}
-
-		if (newdata->undata)
-		{
-			undata = newdata->undata;
-			*undata = '\0';
-		}
-		else
-		{
-			free(newdata);
-			I_Error("Out of memory for unsoc data");
-		}
-
-		if (flags & UNDO_NEWLINE) // let start with \n
-			strcat(undata, "\n");
-
-		strncat(undata, value, vlen);
-
-		if (data) // value+pader+data
-			strncat(strncat(undata, pader, plen), data, dlen);
-
-		if (flags & UNDO_ENDTEXT) // let end the text
-			strncat(undata, pound, elen);
-	}
-	else
-		newdata->undata = NULL;
-
-	newdata->next = unsocdata[unsocwad];
-	unsocdata[unsocwad] = newdata;
-}
-#endif
-
 ATTRINLINE static FUNCINLINE char myfget_color(MYFILE *f)
 {
 	char c = *f->curpos++;
@@ -382,56 +291,6 @@ static void clear_levels(void)
 	P_AllocMapHeader(gamemap-1);
 }
 
-/*
-// Edits an animated texture slot on the array
-// Tails 12-27-2003
-static void readAnimTex(MYFILE *f, INT32 num)
-{
-	char s[MAXLINELEN];
-	char *word;
-	char *word2;
-	INT32 i;
-
-	do {
-		if (myfgets(s, sizeof s, f) != NULL)
-		{
-			if (s[0] == '\n') break;
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-			// set the value in the appropriate field
-
-			word = strtok(s, " ");
-			if (word)
-				strupr(word);
-			else
-				break;
-
-			word2 = strtok(NULL, " = ");
-			if (word2)
-				strupr(word2);
-			else
-				break;
-
-			if (word2[strlen(word2)-1] == '\n')
-				word2[strlen(word2)-1] = '\0';
-
-			i = atoi(word2);
-
-			if (fastcmp(word, "START"))
-				strncpy(harddefs[num].startname, word2, 8);
-			if (fastcmp(word, "END"))
-				strncpy(harddefs[num].endname, word2, 8);
-			else if (fastcmp(word, "SPEED")) harddefs[num].speed = i;
-			else if (fastcmp(word, "ISTEXTURE")) harddefs[num].istexture = i;
-
-			else deh_warning("readAnimTex %d: unknown word '%s'", num, word);
-		}
-	} while (s[0] != '\n' && !myfeof(f)); //finish when the line is empty
-}
-*/
-
 static boolean findFreeSlot(INT32 *num)
 {
 	// Send the character select entry to a free slot.
@@ -458,8 +317,6 @@ static void readPlayer(MYFILE *f, INT32 num)
 	INT32 i;
 	boolean slotfound = false;
 
-	DEH_WriteUndoline("PLAYERTEXT", description[num].notes, UNDO_ENDTEXT);
-
 	do
 	{
 		if (myfgets(s, MAXLINELEN, f))
@@ -528,7 +385,6 @@ static void readPlayer(MYFILE *f, INT32 num)
 			{
 				if (!slotfound && (slotfound = findFreeSlot(&num)) == false)
 					goto done;
-				DEH_WriteUndoline(word, &description[num].picname[0], UNDO_NONE);
 
 				strncpy(description[num].picname, word2, 8);
 			}
@@ -543,7 +399,7 @@ static void readPlayer(MYFILE *f, INT32 num)
 				*/
 				if (i && !slotfound && (slotfound = findFreeSlot(&num)) == false)
 					goto done;
-				DEH_WriteUndoline(word, va("%d", description[num].used), UNDO_NONE);
+
 				description[num].used = (!!i);
 			}
 			else if (fastcmp(word, "SKINNAME"))
@@ -551,7 +407,6 @@ static void readPlayer(MYFILE *f, INT32 num)
 				// Send to free slot.
 				if (!slotfound && (slotfound = findFreeSlot(&num)) == false)
 					goto done;
-				DEH_WriteUndoline(word, description[num].skinname, UNDO_NONE);
 
 				strlcpy(description[num].skinname, word2, sizeof description[num].skinname);
 				strlwr(description[num].skinname);
@@ -561,11 +416,6 @@ static void readPlayer(MYFILE *f, INT32 num)
 		}
 	} while (!myfeof(f)); // finish when the line is empty
 
-#ifdef DELFILE
-	if (slotfound)
-		DEH_WriteUndoline("MENUPOSITION", va("%d", num), UNDO_NONE);
-#endif
-
 done:
 	Z_Free(s);
 }
@@ -703,122 +553,98 @@ static void readthing(MYFILE *f, INT32 num)
 
 			if (fastcmp(word, "MAPTHINGNUM") || fastcmp(word, "DOOMEDNUM"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].doomednum), UNDO_NONE);
 				mobjinfo[num].doomednum = (INT32)atoi(word2);
 			}
 			else if (fastcmp(word, "SPAWNSTATE"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].spawnstate), UNDO_NONE);
 				mobjinfo[num].spawnstate = get_number(word2);
 			}
 			else if (fastcmp(word, "SPAWNHEALTH"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].spawnhealth), UNDO_NONE);
 				mobjinfo[num].spawnhealth = (INT32)get_number(word2);
 			}
 			else if (fastcmp(word, "SEESTATE"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].seestate), UNDO_NONE);
 				mobjinfo[num].seestate = get_number(word2);
 			}
 			else if (fastcmp(word, "SEESOUND"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].seesound), UNDO_NONE);
 				mobjinfo[num].seesound = get_number(word2);
 			}
 			else if (fastcmp(word, "REACTIONTIME"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].reactiontime), UNDO_NONE);
 				mobjinfo[num].reactiontime = (INT32)get_number(word2);
 			}
 			else if (fastcmp(word, "ATTACKSOUND"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].attacksound), UNDO_NONE);
 				mobjinfo[num].attacksound = get_number(word2);
 			}
 			else if (fastcmp(word, "PAINSTATE"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].painstate), UNDO_NONE);
 				mobjinfo[num].painstate = get_number(word2);
 			}
 			else if (fastcmp(word, "PAINCHANCE"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].painchance), UNDO_NONE);
 				mobjinfo[num].painchance = (INT32)get_number(word2);
 			}
 			else if (fastcmp(word, "PAINSOUND"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].painsound), UNDO_NONE);
 				mobjinfo[num].painsound = get_number(word2);
 			}
 			else if (fastcmp(word, "MELEESTATE"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].meleestate), UNDO_NONE);
 				mobjinfo[num].meleestate = get_number(word2);
 			}
 			else if (fastcmp(word, "MISSILESTATE"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].missilestate), UNDO_NONE);
 				mobjinfo[num].missilestate = get_number(word2);
 			}
 			else if (fastcmp(word, "DEATHSTATE"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].deathstate), UNDO_NONE);
 				mobjinfo[num].deathstate = get_number(word2);
 			}
 			else if (fastcmp(word, "DEATHSOUND"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].deathsound), UNDO_NONE);
 				mobjinfo[num].deathsound = get_number(word2);
 			}
 			else if (fastcmp(word, "XDEATHSTATE"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].xdeathstate), UNDO_NONE);
 				mobjinfo[num].xdeathstate = get_number(word2);
 			}
 			else if (fastcmp(word, "SPEED"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].speed), UNDO_NONE);
 				mobjinfo[num].speed = get_number(word2);
 			}
 			else if (fastcmp(word, "RADIUS"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].radius), UNDO_NONE);
 				mobjinfo[num].radius = get_number(word2);
 			}
 			else if (fastcmp(word, "HEIGHT"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].height), UNDO_NONE);
 				mobjinfo[num].height = get_number(word2);
 			}
 			else if (fastcmp(word, "DISPOFFSET"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].dispoffset), UNDO_NONE);
 				mobjinfo[num].dispoffset = get_number(word2);
 			}
 			else if (fastcmp(word, "MASS"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].mass), UNDO_NONE);
 				mobjinfo[num].mass = (INT32)get_number(word2);
 			}
 			else if (fastcmp(word, "DAMAGE"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].damage), UNDO_NONE);
 				mobjinfo[num].damage = (INT32)get_number(word2);
 			}
 			else if (fastcmp(word, "ACTIVESOUND"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].activesound), UNDO_NONE);
 				mobjinfo[num].activesound = get_number(word2);
 			}
 			else if (fastcmp(word, "FLAGS"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].flags), UNDO_NONE);
 				mobjinfo[num].flags = (INT32)get_number(word2);
 			}
 			else if (fastcmp(word, "RAISESTATE"))
 			{
-				DEH_WriteUndoline(word, va("%d", mobjinfo[num].raisestate), UNDO_NONE);
 				mobjinfo[num].raisestate = get_number(word2);
 			}
 			else
@@ -862,37 +688,30 @@ static void readlight(MYFILE *f, INT32 num)
 
 			if (fastcmp(word, "TYPE"))
 			{
-				DEH_WriteUndoline(word, va("%d", lspr[num].type), UNDO_NONE);
 				lspr[num].type = (UINT16)value;
 			}
 			else if (fastcmp(word, "OFFSETX"))
 			{
-				DEH_WriteUndoline(word, va("%f", lspr[num].light_xoffset), UNDO_NONE);
 				lspr[num].light_xoffset = fvalue;
 			}
 			else if (fastcmp(word, "OFFSETY"))
 			{
-				DEH_WriteUndoline(word, va("%f", lspr[num].light_yoffset), UNDO_NONE);
 				lspr[num].light_yoffset = fvalue;
 			}
 			else if (fastcmp(word, "CORONACOLOR"))
 			{
-				DEH_WriteUndoline(word, va("%u", lspr[num].corona_color), UNDO_NONE);
 				lspr[num].corona_color = value;
 			}
 			else if (fastcmp(word, "CORONARADIUS"))
 			{
-				DEH_WriteUndoline(word, va("%f", lspr[num].corona_radius), UNDO_NONE);
 				lspr[num].corona_radius = fvalue;
 			}
 			else if (fastcmp(word, "DYNAMICCOLOR"))
 			{
-				DEH_WriteUndoline(word, va("%u", lspr[num].dynamic_color), UNDO_NONE);
 				lspr[num].dynamic_color = value;
 			}
 			else if (fastcmp(word, "DYNAMICRADIUS"))
 			{
-				DEH_WriteUndoline(word, va("%f", lspr[num].dynamic_radius), UNDO_NONE);
 				lspr[num].dynamic_radius = fvalue;
 
 				/// \note Update the sqrradius! unnecessary?
@@ -939,7 +758,6 @@ static void readspritelight(MYFILE *f, INT32 num)
 				INT32 oldvar;
 				for (oldvar = 0; t_lspr[num] != &lspr[oldvar]; oldvar++)
 					;
-				DEH_WriteUndoline(word, va("%d", oldvar), UNDO_NONE);
 				t_lspr[num] = &lspr[value];
 			}
 			else
@@ -1022,8 +840,6 @@ static void readlevelheader(MYFILE *f, INT32 num)
 	INT32 i;
 
 	// Reset all previous map header information
-	// This call automatically saves all previous information when DELFILE is defined.
-	// We don't need to do it ourselves.
 	P_AllocMapHeader((INT16)(num-1));
 
 	do
@@ -1379,6 +1195,13 @@ static void readlevelheader(MYFILE *f, INT32 num)
 				else
 					mapheaderinfo[num-1]->levelflags &= ~LF_NOZONE;
 			}
+			else if (fastcmp(word, "SAVEGAME"))
+			{
+				if (i || word2[0] == 'T' || word2[0] == 'Y')
+					mapheaderinfo[num-1]->levelflags |= LF_SAVEGAME;
+				else
+					mapheaderinfo[num-1]->levelflags &= ~LF_SAVEGAME;
+			}
 
 			// Individual triggers for menu flags
 			else if (fastcmp(word, "HIDDEN"))
@@ -1442,8 +1265,6 @@ static void readcutscenescene(MYFILE *f, INT32 num, INT32 scenenum)
 	UINT16 usi;
 	UINT8 picid;
 
-	DEH_WriteUndoline("SCENETEXT", cutscenes[num]->scene[scenenum].text, UNDO_ENDTEXT);
-
 	do
 	{
 		if (myfgets(s, MAXLINELEN, f))
@@ -1520,7 +1341,6 @@ static void readcutscenescene(MYFILE *f, INT32 num, INT32 scenenum)
 
 			if (fastcmp(word, "NUMBEROFPICS"))
 			{
-				DEH_WriteUndoline(word, va("%d", cutscenes[num]->scene[scenenum].numpics), UNDO_NONE);
 				cutscenes[num]->scene[scenenum].numpics = (UINT8)i;
 			}
 			else if (fastncmp(word, "PIC", 3))
@@ -1535,27 +1355,22 @@ static void readcutscenescene(MYFILE *f, INT32 num, INT32 scenenum)
 
 				if (fastcmp(word+4, "NAME"))
 				{
-					DEH_WriteUndoline(word, cutscenes[num]->scene[scenenum].picname[picid], UNDO_NONE);
 					strncpy(cutscenes[num]->scene[scenenum].picname[picid], word2, 8);
 				}
 				else if (fastcmp(word+4, "HIRES"))
 				{
-					DEH_WriteUndoline(word, va("%d", cutscenes[num]->scene[scenenum].pichires[picid]), UNDO_NONE);
 					cutscenes[num]->scene[scenenum].pichires[picid] = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
 				}
 				else if (fastcmp(word+4, "DURATION"))
 				{
-					DEH_WriteUndoline(word, va("%u", cutscenes[num]->scene[scenenum].picduration[picid]), UNDO_NONE);
 					cutscenes[num]->scene[scenenum].picduration[picid] = usi;
 				}
 				else if (fastcmp(word+4, "XCOORD"))
 				{
-					DEH_WriteUndoline(word, va("%u", cutscenes[num]->scene[scenenum].xcoord[picid]), UNDO_NONE);
 					cutscenes[num]->scene[scenenum].xcoord[picid] = usi;
 				}
 				else if (fastcmp(word+4, "YCOORD"))
 				{
-					DEH_WriteUndoline(word, va("%u", cutscenes[num]->scene[scenenum].ycoord[picid]), UNDO_NONE);
 					cutscenes[num]->scene[scenenum].ycoord[picid] = usi;
 				}
 				else
@@ -1563,14 +1378,12 @@ static void readcutscenescene(MYFILE *f, INT32 num, INT32 scenenum)
 			}
 			else if (fastcmp(word, "MUSIC"))
 			{
-				DEH_WriteUndoline(word, cutscenes[num]->scene[scenenum].musswitch, UNDO_NONE);
 				strncpy(cutscenes[num]->scene[scenenum].musswitch, word2, 7);
 				cutscenes[num]->scene[scenenum].musswitch[6] = 0;
 			}
 #ifdef MUSICSLOT_COMPATIBILITY
 			else if (fastcmp(word, "MUSICSLOT"))
 			{
-				DEH_WriteUndoline(word, cutscenes[num]->scene[scenenum].musswitch, UNDO_NONE);
 				i = get_mus(word2, true);
 				if (i && i <= 1035)
 					snprintf(cutscenes[num]->scene[scenenum].musswitch, 7, "%sM", G_BuildMapName(i));
@@ -1583,37 +1396,30 @@ static void readcutscenescene(MYFILE *f, INT32 num, INT32 scenenum)
 #endif
 			else if (fastcmp(word, "MUSICTRACK"))
 			{
-				DEH_WriteUndoline(word, va("%u", cutscenes[num]->scene[scenenum].musswitchflags), UNDO_NONE);
 				cutscenes[num]->scene[scenenum].musswitchflags = ((UINT16)i) & MUSIC_TRACKMASK;
 			}
 			else if (fastcmp(word, "MUSICLOOP"))
 			{
-				DEH_WriteUndoline(word, va("%u", cutscenes[num]->scene[scenenum].musicloop), UNDO_NONE);
 				cutscenes[num]->scene[scenenum].musicloop = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
 			}
 			else if (fastcmp(word, "TEXTXPOS"))
 			{
-				DEH_WriteUndoline(word, va("%u", cutscenes[num]->scene[scenenum].textxpos), UNDO_NONE);
 				cutscenes[num]->scene[scenenum].textxpos = usi;
 			}
 			else if (fastcmp(word, "TEXTYPOS"))
 			{
-				DEH_WriteUndoline(word, va("%u", cutscenes[num]->scene[scenenum].textypos), UNDO_NONE);
 				cutscenes[num]->scene[scenenum].textypos = usi;
 			}
 			else if (fastcmp(word, "FADEINID"))
 			{
-				DEH_WriteUndoline(word, va("%u", cutscenes[num]->scene[scenenum].fadenum), UNDO_NONE);
 				cutscenes[num]->scene[scenenum].fadeinid = (UINT8)i;
 			}
 			else if (fastcmp(word, "FADEOUTID"))
 			{
-				DEH_WriteUndoline(word, va("%u", cutscenes[num]->scene[scenenum].fadenum), UNDO_NONE);
 				cutscenes[num]->scene[scenenum].fadeoutid = (UINT8)i;
 			}
 			else if (fastcmp(word, "FADECOLOR"))
 			{
-				DEH_WriteUndoline(word, va("%u", cutscenes[num]->scene[scenenum].fadenum), UNDO_NONE);
 				cutscenes[num]->scene[scenenum].fadecolor = (UINT8)i;
 			}
 			else
@@ -1631,9 +1437,6 @@ static void readcutscene(MYFILE *f, INT32 num)
 	char *word2;
 	char *tmp;
 	INT32 value;
-#ifdef DELFILE
-	const INT32 oldnumscenes = cutscenes[num]->numscenes;
-#endif
 
 	// Allocate memory for this cutscene if we don't yet have any
 	if (!cutscenes[num])
@@ -1676,8 +1479,6 @@ static void readcutscene(MYFILE *f, INT32 num)
 				if (1 <= value && value <= 128)
 				{
 					readcutscenescene(f, num, value - 1);
-					DEH_WriteUndoline(word, word2, UNDO_SPACE|UNDO_CUTLINE);
-					DEH_WriteUndoline("NUMSCENES", va("%d", oldnumscenes), UNDO_SPACE);
 				}
 				else
 					deh_warning("Scene number %d out of range (1 - 128)", value);
@@ -1730,12 +1531,10 @@ static void readhuditem(MYFILE *f, INT32 num)
 
 			if (fastcmp(word, "X"))
 			{
-				DEH_WriteUndoline(word, va("%d", hudinfo[num].x), UNDO_NONE);
 				hudinfo[num].x = i;
 			}
 			else if (fastcmp(word, "Y"))
 			{
-				DEH_WriteUndoline(word, va("%d", hudinfo[num].y), UNDO_NONE);
 				hudinfo[num].y = i;
 			}
 			else
@@ -1782,7 +1581,6 @@ static actionpointer_t actionpointers[] =
 	{{A_BossDeath},            "A_BOSSDEATH"},
 	{{A_CustomPower},          "A_CUSTOMPOWER"},
 	{{A_GiveWeapon},           "A_GIVEWEAPON"},
-	{{A_RingShield},           "A_RINGSHIELD"},
 	{{A_RingBox},              "A_RINGBOX"},
 	{{A_Invincibility},        "A_INVINCIBILITY"},
 	{{A_SuperSneakers},        "A_SUPERSNEAKERS"},
@@ -1793,14 +1591,7 @@ static actionpointer_t actionpointers[] =
 	{{A_BubbleCheck},          "A_BUBBLECHECK"},
 	{{A_AwardScore},           "A_AWARDSCORE"},
 	{{A_ExtraLife},            "A_EXTRALIFE"},
-	{{A_BombShield},           "A_BOMBSHIELD"},
-	{{A_JumpShield},           "A_JUMPSHIELD"},
-	{{A_WaterShield},          "A_WATERSHIELD"},
-	{{A_ForceShield},          "A_FORCESHIELD"},
-	{{A_PityShield},           "A_PITYSHIELD"},
-	{{A_FlameShield},          "A_FLAMESHIELD"},
-	{{A_BubbleShield},         "A_BUBBLESHIELD"},
-	{{A_ThunderShield},        "A_THUNDERSHIELD"},
+	{{A_GiveShield},           "A_GIVESHIELD"},
 	{{A_GravityBox},           "A_GRAVITYBOX"},
 	{{A_ScoreRise},            "A_SCORERISE"},
 	{{A_ParticleSpawn},        "A_PARTICLESPAWN"},
@@ -1826,7 +1617,6 @@ static actionpointer_t actionpointers[] =
 	{{A_CapeChase},            "A_CAPECHASE"},
 	{{A_RotateSpikeBall},      "A_ROTATESPIKEBALL"},
 	{{A_SlingAppear},          "A_SLINGAPPEAR"},
-	{{A_MaceRotate},           "A_MACEROTATE"},
 	{{A_UnidusBall},           "A_UNIDUSBALL"},
 	{{A_RockSpawn},            "A_ROCKSPAWN"},
 	{{A_SetFuse},              "A_SETFUSE"},
@@ -2021,32 +1811,26 @@ static void readframe(MYFILE *f, INT32 num)
 
 			if (fastcmp(word1, "SPRITENUMBER") || fastcmp(word1, "SPRITENAME"))
 			{
-				DEH_WriteUndoline(word1, va("%u", states[num].sprite), UNDO_NONE);
 				states[num].sprite = get_sprite(word2);
 			}
 			else if (fastcmp(word1, "SPRITESUBNUMBER") || fastcmp(word1, "SPRITEFRAME"))
 			{
-				DEH_WriteUndoline(word1, va("%d", states[num].frame), UNDO_NONE);
 				states[num].frame = (INT32)get_number(word2); // So the FF_ flags get calculated
 			}
 			else if (fastcmp(word1, "DURATION"))
 			{
-				DEH_WriteUndoline(word1, va("%u", states[num].tics), UNDO_NONE);
 				states[num].tics = (INT32)get_number(word2); // So TICRATE can be used
 			}
 			else if (fastcmp(word1, "NEXT"))
 			{
-				DEH_WriteUndoline(word1, va("%d", states[num].nextstate), UNDO_NONE);
 				states[num].nextstate = get_state(word2);
 			}
 			else if (fastcmp(word1, "VAR1"))
 			{
-				DEH_WriteUndoline(word1, va("%d", states[num].var1), UNDO_NONE);
 				states[num].var1 = (INT32)get_number(word2);
 			}
 			else if (fastcmp(word1, "VAR2"))
 			{
-				DEH_WriteUndoline(word1, va("%d", states[num].var2), UNDO_NONE);
 				states[num].var2 = (INT32)get_number(word2);
 			}
 			else if (fastcmp(word1, "ACTION"))
@@ -2071,10 +1855,7 @@ static void readframe(MYFILE *f, INT32 num)
 				for (z = 0; actionpointers[z].name; z++)
 				{
 					if (actionpointers[z].action.acv == states[num].action.acv)
-					{
-						DEH_WriteUndoline(word1, actionpointers[z].name, UNDO_NONE);
 						break;
-					}
 				}
 
 				z = 0;
@@ -2106,10 +1887,11 @@ static void readframe(MYFILE *f, INT32 num)
 	Z_Free(s);
 }
 
-static void readsound(MYFILE *f, INT32 num, const char *savesfxnames[])
+static void readsound(MYFILE *f, INT32 num)
 {
 	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
 	char *word;
+	char *word2;
 	char *tmp;
 	INT32 value;
 
@@ -2123,8 +1905,8 @@ static void readsound(MYFILE *f, INT32 num, const char *savesfxnames[])
 			tmp = strchr(s, '#');
 			if (tmp)
 				*tmp = '\0';
-
-			value = searchvalue(s);
+			if (s == tmp)
+				continue; // Skip comment lines, but don't break.
 
 			word = strtok(s, " ");
 			if (word)
@@ -2132,43 +1914,38 @@ static void readsound(MYFILE *f, INT32 num, const char *savesfxnames[])
 			else
 				break;
 
-/*			if (fastcmp(word, "OFFSET"))
+			word2 = strtok(NULL, " ");
+			if (word2)
+				value = atoi(word2);
+			else
 			{
-				value -= 150360;
-				if (value <= 64)
-					value /= 8;
-				else if (value <= 260)
-					value = (value+4)/8;
-				else
-					value = (value+8)/8;
-				if (value >= -1 && value < sfx_freeslot0 - 1)
-					S_sfx[num].name = savesfxnames[value+1];
-				else
-					deh_warning("Sound %d: offset out of bounds", num);
+				deh_warning("No value for token %s", word);
+				continue;
 			}
-			else */if (fastcmp(word, "SINGULAR"))
+
+			if (fastcmp(word, "SINGULAR"))
 			{
-				DEH_WriteUndoline(word, va("%d", S_sfx[num].singularity), UNDO_NONE);
 				S_sfx[num].singularity = value;
 			}
 			else if (fastcmp(word, "PRIORITY"))
 			{
-				DEH_WriteUndoline(word, va("%d", S_sfx[num].priority), UNDO_NONE);
 				S_sfx[num].priority = value;
 			}
 			else if (fastcmp(word, "FLAGS"))
 			{
-				DEH_WriteUndoline(word, va("%d", S_sfx[num].pitch), UNDO_NONE);
 				S_sfx[num].pitch = value;
 			}
+			else if (fastcmp(word, "CAPTION") || fastcmp(word, "DESCRIPTION"))
+			{
+				deh_strlcpy(S_sfx[num].caption, word2,
+					sizeof(S_sfx[num].caption), va("Sound effect %d: caption", num));
+			}
 			else
 				deh_warning("Sound %d : unknown word '%s'",num,word);
 		}
 	} while (!myfeof(f));
 
 	Z_Free(s);
-
-	(void)savesfxnames;
 }
 
 /** Checks if a game data file name for a mod is good.
@@ -2199,11 +1976,7 @@ static boolean GoodDataFileName(const char *s)
 	p = s + strlen(s) - strlen(tail);
 	if (p <= s) return false; // too short
 	if (!fasticmp(p, tail)) return false; // doesn't end in .dat
-#ifdef DELFILE
-	if (fasticmp(s, "gamedata.dat") && !disableundo) return false;
-#else
 	if (fasticmp(s, "gamedata.dat")) return false;
-#endif
 
 	return true;
 }
@@ -2216,17 +1989,6 @@ static void reademblemdata(MYFILE *f, INT32 num)
 	char *tmp;
 	INT32 value;
 
-	// Reset all data initially
-	DEH_WriteUndoline("TYPE", va("%d", emblemlocations[num-1].type), UNDO_NONE);
-	DEH_WriteUndoline("X", va("%d", emblemlocations[num-1].x), UNDO_NONE);
-	DEH_WriteUndoline("Y", va("%d", emblemlocations[num-1].y), UNDO_NONE);
-	DEH_WriteUndoline("Z", va("%d", emblemlocations[num-1].z), UNDO_NONE);
-	DEH_WriteUndoline("MAPNUM", va("%d", emblemlocations[num-1].level), UNDO_NONE);
-	DEH_WriteUndoline("VAR", va("%d", emblemlocations[num-1].var), UNDO_NONE);
-	DEH_WriteUndoline("SPRITE", va("%d", emblemlocations[num-1].sprite), UNDO_NONE);
-	DEH_WriteUndoline("COLOR", va("%d", emblemlocations[num-1].color), UNDO_NONE);
-	DEH_WriteUndoline("HINT", extraemblems[num-1].hint, UNDO_NONE);
-
 	memset(&emblemlocations[num-1], 0, sizeof(emblem_t));
 
 	do
@@ -2278,12 +2040,12 @@ static void reademblemdata(MYFILE *f, INT32 num)
 					emblemlocations[num-1].type = ET_TIME;
 				else if (fastcmp(word2, "RINGS"))
 					emblemlocations[num-1].type = ET_RINGS;
+				else if (fastcmp(word2, "MAP"))
+					emblemlocations[num-1].type = ET_MAP;
 				else if (fastcmp(word2, "NGRADE"))
 					emblemlocations[num-1].type = ET_NGRADE;
 				else if (fastcmp(word2, "NTIME"))
 					emblemlocations[num-1].type = ET_NTIME;
-				else if (fastcmp(word2, "MAP"))
-					emblemlocations[num-1].type = ET_MAP;
 				else
 					emblemlocations[num-1].type = (UINT8)value;
 			}
@@ -2362,13 +2124,6 @@ static void readextraemblemdata(MYFILE *f, INT32 num)
 	char *tmp;
 	INT32 value;
 
-	// Reset all data initially
-	DEH_WriteUndoline("NAME", extraemblems[num-1].name, UNDO_NONE);
-	DEH_WriteUndoline("OBJECTIVE", extraemblems[num-1].description, UNDO_NONE);
-	DEH_WriteUndoline("CONDITIONSET", va("%d", extraemblems[num-1].conditionset), UNDO_NONE);
-	DEH_WriteUndoline("SPRITE", va("%d", extraemblems[num-1].sprite), UNDO_NONE);
-	DEH_WriteUndoline("COLOR", va("%d", extraemblems[num-1].color), UNDO_NONE);
-
 	memset(&extraemblems[num-1], 0, sizeof(extraemblem_t));
 
 	do
@@ -2443,17 +2198,8 @@ static void readunlockable(MYFILE *f, INT32 num)
 	char *tmp;
 	INT32 i;
 
-	// Same deal with unlockables, clear all first
-	DEH_WriteUndoline("NAME", unlockables[num].name, UNDO_NONE);
-	DEH_WriteUndoline("OBJECTIVE", unlockables[num].objective, UNDO_NONE);
-	DEH_WriteUndoline("HEIGHT", va("%d", unlockables[num].height), UNDO_NONE);
-	DEH_WriteUndoline("CONDITIONSET", va("%d", unlockables[num].conditionset), UNDO_NONE);
-	DEH_WriteUndoline("TYPE", va("%d", unlockables[num].type), UNDO_NONE);
-	DEH_WriteUndoline("NOCECHO", va("%d", unlockables[num].nocecho), UNDO_NONE);
-	DEH_WriteUndoline("NOCHECKLIST", va("%d", unlockables[num].nochecklist), UNDO_NONE);
-	DEH_WriteUndoline("VAR", va("%d", unlockables[num].variable), UNDO_NONE);
-
 	memset(&unlockables[num], 0, sizeof(unlockable_t));
+	unlockables[num].objective[0] = '/';
 
 	do
 	{
@@ -2795,190 +2541,6 @@ static void readconditionset(MYFILE *f, UINT8 setnum)
 	Z_Free(s);
 }
 
-static void readtexture(MYFILE *f, const char *name)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word;
-	char *word2;
-	char *tmp;
-	INT32 i, j, value;
-	UINT16 width = 0, height = 0;
-	INT16 patchcount = 0;
-	texture_t *texture;
-
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-
-			value = searchvalue(s);
-			word = strtok(s, " ");
-			if (word)
-				strupr(word);
-			else
-				break;
-
-			word2 = strtok(NULL, " ");
-			if (word2)
-				strupr(word2);
-			else
-				break;
-
-			// Width of the texture.
-			if (fastcmp(word, "WIDTH"))
-			{
-				DEH_WriteUndoline(word, va("%d", width), UNDO_NONE);
-				width = SHORT((UINT16)value);
-			}
-			// Height of the texture.
-			else if (fastcmp(word, "HEIGHT"))
-			{
-				DEH_WriteUndoline(word, va("%d", height), UNDO_NONE);
-				height = SHORT((UINT16)value);
-			}
-			// Number of patches the texture has.
-			else if (fastcmp(word, "NUMPATCHES"))
-			{
-				DEH_WriteUndoline(word, va("%d", patchcount), UNDO_NONE);
-				patchcount = SHORT((UINT16)value);
-			}
-			else
-				deh_warning("readtexture: unknown word '%s'", word);
-		}
-	} while (!myfeof(f));
-
-	// Error checking.
-	if (!width)
-		I_Error("Texture %s has no width!\n", name);
-
-	if (!height)
-		I_Error("Texture %s has no height!\n", name);
-
-	if (!patchcount)
-		I_Error("Texture %s has no patches!\n", name);
-
-	// Allocate memory for the texture, and fill in information.
-	texture = Z_Calloc(sizeof(texture_t) + (sizeof(texpatch_t) * SHORT(patchcount)), PU_STATIC, NULL);
-	M_Memcpy(texture->name, name, sizeof(texture->name));
-	texture->width = width;
-	texture->height = height;
-	texture->patchcount = patchcount;
-	texture->holes = false;
-	// Fill out the texture patches, to allow them to be detected
-	// accurately by readpatch.
-	for (i = 0; i < patchcount; i++)
-	{
-		texture->patches[i].originx = 0;
-		texture->patches[i].originy = 0;
-		texture->patches[i].wad = UINT16_MAX;
-		texture->patches[i].lump = UINT16_MAX;
-	}
-
-	// Jump to the next empty texture entry.
-	i = 0;
-	while (textures[i])
-		i++;
-
-	// Fill the global texture buffer entries.
-	j = 1;
-	while (j << 1 <= texture->width)
-		j <<= 1;
-
-	textures[i] = texture;
-	texturewidthmask[i] = j - 1;
-	textureheight[i] = texture->height << FRACBITS;
-
-	// Clean up.
-	Z_Free(s);
-}
-
-static void readpatch(MYFILE *f, const char *name, UINT16 wad)
-{
-	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
-	char *word;
-	char *word2;
-	char *tmp;
-	INT32 i = 0, j = 0, value;
-	texpatch_t patch = {0, 0, UINT16_MAX, UINT16_MAX, 0, 255, AST_COPY};
-
-	// Jump to the texture this patch belongs to, which,
-	// coincidentally, is always the last one on the buffer cache.
-	while (textures[i+1])
-		i++;
-
-	// Jump to the next empty patch entry.
-	while (memcmp(&(textures[i]->patches[j]), &patch, sizeof(patch)))
-		j++;
-
-	patch.wad = wad;
-	// Set the texture number, but only if the lump exists.
-	if ((patch.lump = W_CheckNumForNamePwad(name, wad, 0)) == INT16_MAX)
-		I_Error("readpatch: Missing patch in texture %s", textures[i]->name);
-
-	// note: undoing this patch will be done by other means
-	do
-	{
-		if (myfgets(s, MAXLINELEN, f))
-		{
-			if (s[0] == '\n')
-				break;
-
-			tmp = strchr(s, '#');
-			if (tmp)
-				*tmp = '\0';
-
-			value = searchvalue(s);
-			word = strtok(s, " ");
-			if (word)
-				strupr(word);
-			else
-				break;
-
-			word2 = strtok(NULL, " ");
-			if (word2)
-				strupr(word2);
-			else
-				break;
-
-			// X position of the patch in the texture.
-			if (fastcmp(word, "X"))
-			{
-				//DEH_WriteUndoline(word, va("%d", patch->originx), UNDO_NONE);
-				patch.originx = (INT16)value;
-			}
-			// Y position of the patch in the texture.
-			else if (fastcmp(word, "Y"))
-			{
-				//DEH_WriteUndoline(word, va("%d", patch->originy), UNDO_NONE);
-				patch.originy = (INT16)value;
-			}
-			else
-				deh_warning("readpatch: unknown word '%s'", word);
-		}
-	} while (!myfeof(f));
-
-	// Error checking.
-	/* // Irrelevant. Origins cannot be unsigned.
-	if (patch.originx == UINT16_MAX)
-		I_Error("Patch %s on texture %s has no X position!\n", name, textures[i]->name);
-
-	if (patch.originy == UINT16_MAX)
-		I_Error("Patch %s on texture %s has no Y position!\n", name, textures[i]->name);
-*/
-
-	// Set the patch as that patch number.
-	textures[i]->patches[j] = patch;
-
-	// Clean up.
-	Z_Free(s);
-}
-
 static void readmaincfg(MYFILE *f)
 {
 	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
@@ -3030,7 +2592,6 @@ static void readmaincfg(MYFILE *f)
 				else
 					value = get_number(word2);
 
-				DEH_WriteUndoline(word, va("%d", spstage_start), UNDO_NONE);
 				spstage_start = (INT16)value;
 			}
 			else if (fastcmp(word, "SSTAGE_START"))
@@ -3044,79 +2605,64 @@ static void readmaincfg(MYFILE *f)
 				else
 					value = get_number(word2);
 
-				DEH_WriteUndoline(word, va("%d", sstage_start), UNDO_NONE);
 				sstage_start = (INT16)value;
 				sstage_end = (INT16)(sstage_start+6); // 7 special stages total
 			}
 			else if (fastcmp(word, "USENIGHTSSS"))
 			{
-				DEH_WriteUndoline(word, va("%d", useNightsSS), UNDO_NONE);
 				useNightsSS = (UINT8)(value || word2[0] == 'T' || word2[0] == 'Y');
 			}
 			else if (fastcmp(word, "REDTEAM"))
 			{
-				DEH_WriteUndoline(word, va("%d", skincolor_redteam), UNDO_NONE);
 				skincolor_redteam = (UINT8)get_number(word2);
 			}
 			else if (fastcmp(word, "BLUETEAM"))
 			{
-				DEH_WriteUndoline(word, va("%d", skincolor_blueteam), UNDO_NONE);
 				skincolor_blueteam = (UINT8)get_number(word2);
 			}
 			else if (fastcmp(word, "REDRING"))
 			{
-				DEH_WriteUndoline(word, va("%d", skincolor_redring), UNDO_NONE);
 				skincolor_redring = (UINT8)get_number(word2);
 			}
 			else if (fastcmp(word, "BLUERING"))
 			{
-				DEH_WriteUndoline(word, va("%d", skincolor_bluering), UNDO_NONE);
 				skincolor_bluering = (UINT8)get_number(word2);
 			}
 			else if (fastcmp(word, "INVULNTICS"))
 			{
-				DEH_WriteUndoline(word, va("%u", invulntics), UNDO_NONE);
 				invulntics = (UINT16)get_number(word2);
 			}
 			else if (fastcmp(word, "SNEAKERTICS"))
 			{
-				DEH_WriteUndoline(word, va("%u", sneakertics), UNDO_NONE);
 				sneakertics = (UINT16)get_number(word2);
 			}
 			else if (fastcmp(word, "FLASHINGTICS"))
 			{
-				DEH_WriteUndoline(word, va("%u", flashingtics), UNDO_NONE);
 				flashingtics = (UINT16)get_number(word2);
 			}
 			else if (fastcmp(word, "TAILSFLYTICS"))
 			{
-				DEH_WriteUndoline(word, va("%u", tailsflytics), UNDO_NONE);
 				tailsflytics = (UINT16)get_number(word2);
 			}
 			else if (fastcmp(word, "UNDERWATERTICS"))
 			{
-				DEH_WriteUndoline(word, va("%u", underwatertics), UNDO_NONE);
 				underwatertics = (UINT16)get_number(word2);
 			}
 			else if (fastcmp(word, "SPACETIMETICS"))
 			{
-				DEH_WriteUndoline(word, va("%u", spacetimetics), UNDO_NONE);
 				spacetimetics = (UINT16)get_number(word2);
 			}
 			else if (fastcmp(word, "EXTRALIFETICS"))
 			{
-				DEH_WriteUndoline(word, va("%u", extralifetics), UNDO_NONE);
 				extralifetics = (UINT16)get_number(word2);
 			}
 			else if (fastcmp(word, "GAMEOVERTICS"))
 			{
-				DEH_WriteUndoline(word, va("%u", gameovertics), UNDO_NONE);
 				gameovertics = get_number(word2);
 			}
 
 			else if (fastcmp(word, "INTROTOPLAY"))
 			{
-				DEH_WriteUndoline(word, va("%d", introtoplay), UNDO_NONE);
 				introtoplay = (UINT8)get_number(word2);
 				// range check, you morons.
 				if (introtoplay > 128)
@@ -3124,17 +2670,14 @@ static void readmaincfg(MYFILE *f)
 			}
 			else if (fastcmp(word, "LOOPTITLE"))
 			{
-				DEH_WriteUndoline(word, va("%d", looptitle), UNDO_NONE);
 				looptitle = (boolean)(value || word2[0] == 'T' || word2[0] == 'Y');
 			}
 			else if (fastcmp(word, "TITLESCROLLSPEED"))
 			{
-				DEH_WriteUndoline(word, va("%d", titlescrollspeed), UNDO_NONE);
 				titlescrollspeed = get_number(word2);
 			}
 			else if (fastcmp(word, "CREDITSCUTSCENE"))
 			{
-				DEH_WriteUndoline(word, va("%d", creditscutscene), UNDO_NONE);
 				creditscutscene = (UINT8)get_number(word2);
 				// range check, you morons.
 				if (creditscutscene > 128)
@@ -3142,32 +2685,26 @@ static void readmaincfg(MYFILE *f)
 			}
 			else if (fastcmp(word, "DISABLESPEEDADJUST"))
 			{
-				DEH_WriteUndoline(word, va("%d", disableSpeedAdjust), UNDO_NONE);
 				disableSpeedAdjust = (UINT8)get_number(word2);
 			}
 			else if (fastcmp(word, "NUMDEMOS"))
 			{
-				DEH_WriteUndoline(word, va("%d", numDemos), UNDO_NONE);
 				numDemos = (UINT8)get_number(word2);
 			}
 			else if (fastcmp(word, "DEMODELAYTIME"))
 			{
-				DEH_WriteUndoline(word, va("%d", demoDelayTime), UNDO_NONE);
 				demoDelayTime = get_number(word2);
 			}
 			else if (fastcmp(word, "DEMOIDLETIME"))
 			{
-				DEH_WriteUndoline(word, va("%d", demoIdleTime), UNDO_NONE);
 				demoIdleTime = get_number(word2);
 			}
 			else if (fastcmp(word, "USE1UPSOUND"))
 			{
-				DEH_WriteUndoline(word, va("%u", use1upSound), UNDO_NONE);
 				use1upSound = (UINT8)(value || word2[0] == 'T' || word2[0] == 'Y');
 			}
 			else if (fastcmp(word, "MAXXTRALIFE"))
 			{
-				DEH_WriteUndoline(word, va("%u", maxXtraLife), UNDO_NONE);
 				maxXtraLife = (UINT8)get_number(word2);
 			}
 
@@ -3181,7 +2718,6 @@ static void readmaincfg(MYFILE *f)
 					I_Error("Maincfg: bad data file name '%s'\n", word2);
 
 				G_SaveGameData();
-				DEH_WriteUndoline(word, gamedatafilename, UNDO_NONE);
 				strlcpy(gamedatafilename, word2, sizeof (gamedatafilename));
 				strlwr(gamedatafilename);
 				savemoddata = true;
@@ -3198,12 +2734,10 @@ static void readmaincfg(MYFILE *f)
 			}
 			else if (fastcmp(word, "RESETDATA"))
 			{
-				DEH_WriteUndoline(word, "0", UNDO_TODO); /// \todo
 				P_ResetData(value);
 			}
 			else if (fastcmp(word, "CUSTOMVERSION"))
 			{
-				DEH_WriteUndoline(word, customversionstring, UNDO_NONE);
 				strlcpy(customversionstring, word2, sizeof (customversionstring));
 			}
 			else
@@ -3389,30 +2923,17 @@ static void ignorelines(MYFILE *f)
 	Z_Free(s);
 }
 
-static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
+static void DEH_LoadDehackedFile(MYFILE *f)
 {
 	char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
 	char *word;
 	char *word2;
 	INT32 i;
-	// do a copy of this for cross references probleme
-	//XBOXSTATIC actionf_t saveactions[NUMSTATES];
-	//XBOXSTATIC const char *savesprnames[NUMSPRITES];
-	XBOXSTATIC const char *savesfxnames[NUMSFX];
 
 	if (!deh_loaded)
 		initfreeslots();
 
 	deh_num_warning = 0;
-	// save values for cross reference
-	/*
-	for (i = 0; i < NUMSTATES; i++)
-		saveactions[i] = states[i].action;
-	for (i = 0; i < NUMSPRITES; i++)
-		savesprnames[i] = sprnames[i];
-	*/
-	for (i = 0; i < NUMSFX; i++)
-		savesfxnames[i] = S_sfx[i].name;
 
 	gamedataadded = false;
 
@@ -3454,13 +2975,11 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 			else if (fastcmp(word, "MAINCFG"))
 			{
 				readmaincfg(f);
-				DEH_WriteUndoline(word, "", UNDO_HEADER);
 				continue;
 			}
 			else if (fastcmp(word, "WIPES"))
 			{
 				readwipes(f);
-				DEH_WriteUndoline(word, "", UNDO_HEADER);
 				continue;
 			}
 			word2 = strtok(NULL, " ");
@@ -3480,7 +2999,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 					deh_warning("Character %d out of range (0 - 31)", i);
 					ignorelines(f);
 				}
-				DEH_WriteUndoline(word, word2, UNDO_HEADER);
 				continue;
 			}
 			if (word2)
@@ -3489,19 +3007,8 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 				if (word2[strlen(word2)-1] == '\n')
 					word2[strlen(word2)-1] = '\0';
 				i = atoi(word2);
-				if (fastcmp(word, "TEXTURE"))
-				{
-					// Read texture from spec file.
-					readtexture(f, word2);
-					DEH_WriteUndoline(word, word2, UNDO_HEADER);
-				}
-				else if (fastcmp(word, "PATCH"))
-				{
-					// Read patch from spec file.
-					readpatch(f, word2, wad);
-					DEH_WriteUndoline(word, word2, UNDO_HEADER);
-				}
-				else if (fastcmp(word, "THING") || fastcmp(word, "MOBJ") || fastcmp(word, "OBJECT"))
+
+				if (fastcmp(word, "THING") || fastcmp(word, "MOBJ") || fastcmp(word, "OBJECT"))
 				{
 					if (i == 0 && word2[0] != '0') // If word2 isn't a number
 						i = get_mobjtype(word2); // find a thing by name
@@ -3512,12 +3019,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						deh_warning("Thing %d out of range (1 - %d)", i, NUMMOBJTYPES-1);
 						ignorelines(f);
 					}
-					DEH_WriteUndoline(word, word2, UNDO_HEADER);
 				}
-/*				else if (fastcmp(word, "ANIMTEX"))
-				{
-					readAnimTex(f, i);
-				}*/
 				else if (fastcmp(word, "LIGHT"))
 				{
 #ifdef HWRENDER
@@ -3529,7 +3031,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						deh_warning("Light number %d out of range (1 - %d)", i, NUMLIGHTS-1);
 						ignorelines(f);
 					}
-					DEH_WriteUndoline(word, word2, UNDO_HEADER);
 #endif
 				}
 				else if (fastcmp(word, "SPRITE"))
@@ -3544,7 +3045,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						deh_warning("Sprite number %d out of range (0 - %d)", i, NUMSPRITES-1);
 						ignorelines(f);
 					}
-					DEH_WriteUndoline(word, word2, UNDO_HEADER);
 #endif
 				}
 				else if (fastcmp(word, "LEVEL"))
@@ -3563,7 +3063,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						deh_warning("Level number %d out of range (1 - %d)", i, NUMMAPS);
 						ignorelines(f);
 					}
-					DEH_WriteUndoline(word, word2, UNDO_HEADER);
 				}
 				else if (fastcmp(word, "CUTSCENE"))
 				{
@@ -3574,7 +3073,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						deh_warning("Cutscene number %d out of range (1 - 128)", i);
 						ignorelines(f);
 					}
-					DEH_WriteUndoline(word, word2, UNDO_HEADER);
 				}
 				else if (fastcmp(word, "FRAME") || fastcmp(word, "STATE"))
 				{
@@ -3587,63 +3085,19 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						deh_warning("Frame %d out of range (0 - %d)", i, NUMSTATES-1);
 						ignorelines(f);
 					}
-					DEH_WriteUndoline(word, word2, UNDO_HEADER);
 				}
-				// <Callum> Added translations to this just in case its re-enabled
-/*				else if (fastcmp(word, "POINTER"))
-				{
-					word = strtok(NULL, " "); // get frame
-					word = strtok(NULL, ")");
-					if (word)
-					{
-						i = atoi(word);
-						if (i < NUMSTATES && i >= 0)
-						{
-							if (myfgets(s, MAXLINELEN, f))
-								states[i].action = saveactions[searchvalue(s)];
-						}
-						else
-						{
-							deh_warning("Pointer: Frame %d doesn't exist", i);
-							ignorelines(f);
-						}
-					}
-					else
-						deh_warning("pointer (Frame %d) : missing ')'", i);
-				}*/
 				else if (fastcmp(word, "SOUND"))
 				{
 					if (i == 0 && word2[0] != '0') // If word2 isn't a number
 						i = get_sfx(word2); // find a sound by name
-					if (i < NUMSFX && i >= 0)
-						readsound(f, i, savesfxnames);
+					if (i < NUMSFX && i > 0)
+						readsound(f, i);
 					else
 					{
-						deh_warning("Sound %d out of range (0 - %d)", i, NUMSFX-1);
+						deh_warning("Sound %d out of range (1 - %d)", i, NUMSFX-1);
 						ignorelines(f);
 					}
-					DEH_WriteUndoline(word, word2, UNDO_HEADER);
 				}
-/*				else if (fastcmp(word, "SPRITE"))
-				{
-					if (i < NUMSPRITES && i >= 0)
-					{
-						if (myfgets(s, MAXLINELEN, f))
-						{
-							INT32 k;
-							k = (searchvalue(s) - 151328)/8;
-							if (k >= 0 && k < NUMSPRITES)
-								sprnames[i] = savesprnames[k];
-							else
-							{
-								deh_warning("Sprite %d: offset out of bounds", i);
-								ignorelines(f);
-							}
-						}
-					}
-					else
-						deh_warning("Sprite %d doesn't exist",i);
-				}*/
 				else if (fastcmp(word, "HUDITEM"))
 				{
 					if (i == 0 && word2[0] != '0') // If word2 isn't a number
@@ -3655,7 +3109,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						deh_warning("HUD item number %d out of range (0 - %d)", i, NUMHUDITEMS-1);
 						ignorelines(f);
 					}
-					DEH_WriteUndoline(word, word2, UNDO_HEADER);
 				}
 				else if (fastcmp(word, "EMBLEM"))
 				{
@@ -3675,7 +3128,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						deh_warning("Emblem number %d out of range (1 - %d)", i, MAXEMBLEMS);
 						ignorelines(f);
 					}
-					DEH_WriteUndoline(word, word2, UNDO_HEADER);
 				}
 				else if (fastcmp(word, "EXTRAEMBLEM"))
 				{
@@ -3695,7 +3147,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						deh_warning("Extra emblem number %d out of range (1 - %d)", i, MAXEXTRAEMBLEMS);
 						ignorelines(f);
 					}
-					DEH_WriteUndoline(word, word2, UNDO_HEADER);
 				}
 				else if (fastcmp(word, "UNLOCKABLE"))
 				{
@@ -3711,7 +3162,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						deh_warning("Unlockable number %d out of range (1 - %d)", i, MAXUNLOCKABLES);
 						ignorelines(f);
 					}
-					DEH_WriteUndoline(word, word2, UNDO_HEADER);
 				}
 				else if (fastcmp(word, "CONDITIONSET"))
 				{
@@ -3727,20 +3177,21 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 						deh_warning("Condition set number %d out of range (1 - %d)", i, MAXCONDITIONSETS);
 						ignorelines(f);
 					}
-					// no undo support for this insanity yet
-					//DEH_WriteUndoline(word, word2, UNDO_HEADER);
 				}
-				else if (fastcmp(word, "SRB2"))
+				// Last I heard this crashes the game if you try to use it
+				// so this is disabled for now
+				// -- Monster Iestyn
+/*				else if (fastcmp(word, "SRB2"))
 				{
 					INT32 ver = searchvalue(strtok(NULL, "\n"));
 					if (ver != PATCHVERSION)
 						deh_warning("Patch is for SRB2 version %d,\nonly version %d is supported", ver, PATCHVERSION);
-					//DEH_WriteUndoline(word, va("%d", ver), UNDO_NONE);
 				}
 				// Clear all data in certain locations (mostly for unlocks)
 				// Unless you REALLY want to piss people off,
 				// define a custom gamedata /before/ doing this!!
 				// (then again, modifiedgame will prevent game data saving anyway)
+*/
 				else if (fastcmp(word, "CLEAR"))
 				{
 					boolean clearall = (fastcmp(word2, "ALL"));
@@ -3805,17 +3256,13 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 void DEH_LoadDehackedLumpPwad(UINT16 wad, UINT16 lump)
 {
 	MYFILE f;
-#ifdef DELFILE
-	unsocwad = wad;
-#endif
 	f.wad = wad;
 	f.size = W_LumpLengthPwad(wad, lump);
 	f.data = Z_Malloc(f.size + 1, PU_STATIC, NULL);
 	W_ReadLumpPwad(wad, lump, f.data);
 	f.curpos = f.data;
 	f.data[f.size] = 0;
-	DEH_LoadDehackedFile(&f, wad);
-	DEH_WriteUndoline(va("# uload for wad: %u, lump: %u", wad, lump), NULL, UNDO_DONE);
+	DEH_LoadDehackedFile(&f);
 	Z_Free(f.data);
 }
 
@@ -3824,67 +3271,6 @@ void DEH_LoadDehackedLump(lumpnum_t lumpnum)
 	DEH_LoadDehackedLumpPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum));
 }
 
-#ifdef DELFILE
-#define DUMPUNDONE
-
-// read (un)dehacked lump in wad's memory
-void DEH_UnloadDehackedWad(UINT16 wad)
-{
-	undehacked_t *tmp, *curundo = unsocdata[wad];
-	MYFILE f;
-	size_t len = 0;
-	char *data;
-#ifdef DUMPUNDONE
-	FILE *UNDO = fopen("undo.soc", "wt");
-#endif
-	CONS_Printf(M_GetText("Unloading WAD SOC edits\n"));
-	while (curundo)
-	{
-		data = curundo->undata;
-		curundo = curundo->next;
-		if (data)
-			len += strlen(data);
-		len += 1;
-#ifdef DUMPUNDONE
-		if (UNDO)
-		{
-			if (data)
-				fprintf(UNDO, "%s\n", data);
-			else
-				fprintf(UNDO, "\n");
-		}
-#endif
-	}
-#ifndef DUMPUNDONE
-	if (UNDO) fclose(UNDO);
-#endif
-	if (!len) return;
-	f.size = len;
-	data = f.data = Z_Malloc(f.size + 1, PU_STATIC, NULL);
-	curundo = unsocdata[wad];
-	unsocdata[wad] = NULL;
-	while (curundo)
-	{
-		tmp = curundo;
-		curundo = curundo->next;
-		if (tmp->undata)
-			data += sprintf(data, "%s\n", tmp->undata);
-		else
-			data += sprintf(data, "\n");
-		if (tmp->undata) free(tmp->undata);
-		free(tmp);
-	}
-	f.wad = wad;
-	f.curpos = f.data;
-	f.data[f.size] = 0;
-	disableundo = true;
-	DEH_LoadDehackedFile(&f);
-	disableundo = false;
-	Z_Free(f.data);
-}
-#endif //DELFILE
-
-
 ////////////////////////////////////////////////////////////////////////////////
 // CRAZY LIST OF STATE NAMES AND ALL FROM HERE DOWN
 // TODO: Make this all a seperate file or something, like part of info.c??
@@ -4822,12 +4208,8 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	// Individual Team Rings
 	"S_TEAMRING",
 
-	// Special Stage Token
-	"S_EMMY",
-
 	// Special Stage Token
 	"S_TOKEN",
-	"S_MOVINGTOKEN",
 
 	// CTF Flags
 	"S_REDFLAG",
@@ -4978,6 +4360,17 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_SPIKED1",
 	"S_SPIKED2",
 
+	// Wall spikes
+	"S_WALLSPIKE1",
+	"S_WALLSPIKE2",
+	"S_WALLSPIKE3",
+	"S_WALLSPIKE4",
+	"S_WALLSPIKE5",
+	"S_WALLSPIKE6",
+	"S_WALLSPIKEBASE",
+	"S_WALLSPIKED1",
+	"S_WALLSPIKED2",
+
 	// Starpost
 	"S_STARPOST_IDLE",
 	"S_STARPOST_FLASH",
@@ -5164,6 +4557,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_DEMONFIRE5",
 	"S_DEMONFIRE6",
 
+	// GFZ flowers
 	"S_GFZFLOWERA",
 	"S_GFZFLOWERB",
 	"S_GFZFLOWERC",
@@ -5171,6 +4565,18 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_BERRYBUSH",
 	"S_BUSH",
 
+	// Trees (both GFZ and misc)
+	"S_GFZTREE",
+	"S_GFZBERRYTREE",
+	"S_GFZCHERRYTREE",
+	"S_CHECKERTREE",
+	"S_CHECKERSUNSETTREE",
+	"S_FHZTREE", // Frozen Hillside
+	"S_FHZPINKTREE",
+	"S_POLYGONTREE",
+	"S_BUSHTREE",
+	"S_BUSHREDTREE",
+
 	// THZ Plant
 	"S_THZFLOWERA",
 	"S_THZFLOWERB",
@@ -5180,6 +4586,7 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 
 	// Deep Sea Gargoyle
 	"S_GARGOYLE",
+	"S_BIGGARGOYLE",
 
 	// DSZ Seaweed
 	"S_SEAWEED1",
@@ -5231,18 +4638,62 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_SLING1",
 	"S_SLING2",
 
-	// CEZ Small Mace Chain
+	// CEZ maces and chains
 	"S_SMALLMACECHAIN",
-
-	// CEZ Big Mace Chain
 	"S_BIGMACECHAIN",
-
-	// CEZ Small Mace
 	"S_SMALLMACE",
-
-	// CEZ Big Mace
 	"S_BIGMACE",
 
+	// Yellow spring on a ball
+	"S_YELLOWSPRINGBALL",
+	"S_YELLOWSPRINGBALL2",
+	"S_YELLOWSPRINGBALL3",
+	"S_YELLOWSPRINGBALL4",
+	"S_YELLOWSPRINGBALL5",
+
+	// Red spring on a ball
+	"S_REDSPRINGBALL",
+	"S_REDSPRINGBALL2",
+	"S_REDSPRINGBALL3",
+	"S_REDSPRINGBALL4",
+	"S_REDSPRINGBALL5",
+
+	// Small Firebar
+	"S_SMALLFIREBAR1",
+	"S_SMALLFIREBAR2",
+	"S_SMALLFIREBAR3",
+	"S_SMALLFIREBAR4",
+	"S_SMALLFIREBAR5",
+	"S_SMALLFIREBAR6",
+	"S_SMALLFIREBAR7",
+	"S_SMALLFIREBAR8",
+	"S_SMALLFIREBAR9",
+	"S_SMALLFIREBAR10",
+	"S_SMALLFIREBAR11",
+	"S_SMALLFIREBAR12",
+	"S_SMALLFIREBAR13",
+	"S_SMALLFIREBAR14",
+	"S_SMALLFIREBAR15",
+	"S_SMALLFIREBAR16",
+
+	// Big Firebar
+	"S_BIGFIREBAR1",
+	"S_BIGFIREBAR2",
+	"S_BIGFIREBAR3",
+	"S_BIGFIREBAR4",
+	"S_BIGFIREBAR5",
+	"S_BIGFIREBAR6",
+	"S_BIGFIREBAR7",
+	"S_BIGFIREBAR8",
+	"S_BIGFIREBAR9",
+	"S_BIGFIREBAR10",
+	"S_BIGFIREBAR11",
+	"S_BIGFIREBAR12",
+	"S_BIGFIREBAR13",
+	"S_BIGFIREBAR14",
+	"S_BIGFIREBAR15",
+	"S_BIGFIREBAR16",
+
 	"S_CEZFLOWER1",
 
 	// Big Tumbleweed
@@ -5338,7 +4789,14 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	// Xmas-specific stuff
 	"S_XMASPOLE",
 	"S_CANDYCANE",
-	"S_SNOWMAN",
+	"S_SNOWMAN",    // normal
+	"S_SNOWMANHAT", // with hat + scarf
+	"S_LAMPPOST1",  // normal
+	"S_LAMPPOST2",  // with snow
+	"S_HANGSTAR",
+	// Xmas GFZ bushes
+	"S_XMASBERRYBUSH",
+	"S_XMASBUSH",
 
 	// Botanic Serenity's loads of scenery states
 	"S_BSZTALLFLOWER_RED",
@@ -5530,10 +4988,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_PITY4",
 	"S_PITY5",
 	"S_PITY6",
-	"S_PITY7",
-	"S_PITY8",
-	"S_PITY9",
-	"S_PITY10",
 
 	"S_FIRS1",
 	"S_FIRS2",
@@ -6213,16 +5667,11 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_NIGHTSWING_XMAS",
 
 	// NiGHTS Paraloop Powerups
-	"S_NIGHTSPOWERUP1",
-	"S_NIGHTSPOWERUP2",
-	"S_NIGHTSPOWERUP3",
-	"S_NIGHTSPOWERUP4",
-	"S_NIGHTSPOWERUP5",
-	"S_NIGHTSPOWERUP6",
-	"S_NIGHTSPOWERUP7",
-	"S_NIGHTSPOWERUP8",
-	"S_NIGHTSPOWERUP9",
-	"S_NIGHTSPOWERUP10",
+	"S_NIGHTSSUPERLOOP",
+	"S_NIGHTSDRILLREFILL",
+	"S_NIGHTSHELPER",
+	"S_NIGHTSEXTRATIME",
+	"S_NIGHTSLINKFREEZE",
 	"S_EGGCAPSULE",
 
 	// Orbiting Chaos Emeralds
@@ -6419,8 +5868,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_BLUEBALL",  // Blue sphere replacement for special stages
 	"MT_REDTEAMRING",  //Rings collectable by red team.
 	"MT_BLUETEAMRING", //Rings collectable by blue team.
-	"MT_EMMY", // emerald token for special stage
-	"MT_TOKEN", // Special Stage Token (uncollectible part)
+	"MT_TOKEN", // Special Stage Token
 	"MT_REDFLAG", // Red CTF Flag
 	"MT_BLUEFLAG", // Blue CTF Flag
 	"MT_EMBLEM",
@@ -6454,6 +5902,8 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_SPECIALSPIKEBALL",
 	"MT_SPINFIRE",
 	"MT_SPIKE",
+	"MT_WALLSPIKE",
+	"MT_WALLSPIKEBASE",
 	"MT_STARPOST",
 	"MT_BIGMINE",
 	"MT_BIGAIRMINE",
@@ -6544,6 +5994,17 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_GFZFLOWER3",
 	"MT_BERRYBUSH",
 	"MT_BUSH",
+	// Trees (both GFZ and misc)
+	"MT_GFZTREE",
+	"MT_GFZBERRYTREE",
+	"MT_GFZCHERRYTREE",
+	"MT_CHECKERTREE",
+	"MT_CHECKERSUNSETTREE",
+	"MT_FHZTREE", // Frozen Hillside
+	"MT_FHZPINKTREE",
+	"MT_POLYGONTREE",
+	"MT_BUSHTREE",
+	"MT_BUSHREDTREE",
 
 	// Techno Hill Scenery
 	"MT_THZFLOWER1",
@@ -6552,6 +6013,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 
 	// Deep Sea Scenery
 	"MT_GARGOYLE", // Deep Sea Gargoyle
+	"MT_BIGGARGOYLE", // Deep Sea Gargoyle (Big)
 	"MT_SEAWEED", // DSZ Seaweed
 	"MT_WATERDRIP", // Dripping Water source
 	"MT_WATERDROP", // Water drop from dripping water
@@ -6566,14 +6028,20 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_FLAMEPARTICLE",
 	"MT_EGGSTATUE", // Eggman Statue
 	"MT_MACEPOINT", // Mace rotation point
-	"MT_SWINGMACEPOINT", // Mace swinging point
-	"MT_HANGMACEPOINT", // Hangable mace chain
-	"MT_SPINMACEPOINT", // Spin/Controllable mace chain
+	"MT_CHAINMACEPOINT", // Combination of chains and maces point
+	"MT_SPRINGBALLPOINT", // Spring ball point
+	"MT_CHAINPOINT", // Mace chain
 	"MT_HIDDEN_SLING", // Spin mace chain (activatable)
+	"MT_FIREBARPOINT", // Firebar
+	"MT_CUSTOMMACEPOINT", // Custom mace
 	"MT_SMALLMACECHAIN", // Small Mace Chain
 	"MT_BIGMACECHAIN", // Big Mace Chain
 	"MT_SMALLMACE", // Small Mace
 	"MT_BIGMACE", // Big Mace
+	"MT_YELLOWSPRINGBALL", // Yellow spring on a ball
+	"MT_REDSPRINGBALL", // Red spring on a ball
+	"MT_SMALLFIREBAR", // Small Firebar
+	"MT_BIGFIREBAR", // Big Firebar
 	"MT_CEZFLOWER",
 
 	// Arid Canyon Scenery
@@ -6620,7 +6088,14 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	// Christmas Scenery
 	"MT_XMASPOLE",
 	"MT_CANDYCANE",
-	"MT_SNOWMAN",
+	"MT_SNOWMAN",    // normal
+	"MT_SNOWMANHAT", // with hat + scarf
+	"MT_LAMPPOST1",  // normal
+	"MT_LAMPPOST2",  // with snow
+	"MT_HANGSTAR",
+	// Xmas GFZ bushes
+	"MT_XMASBERRYBUSH",
+	"MT_XMASBUSH",
 
 	// Botanic Serenity
 	"MT_BSZTALLFLOWER_RED",
@@ -6919,6 +6394,7 @@ static const char *const MOBJFLAG2_LIST[] = {
 	"AMBUSH",         // Alternate behaviour typically set by MTF_AMBUSH
 	"LINKDRAW",       // Draw vissprite of mobj immediately before/after tracer's vissprite (dependent on dispoffset and position)
 	"SHIELD",         // Thinker calls P_AddShield/P_ShieldLook (must be partnered with MF_SCENERY to use)
+	"MACEROTATE",     // Thinker calls P_MaceRotate around tracer
 	NULL
 };
 
@@ -7029,91 +6505,130 @@ static const char *const ML_LIST[16] = {
 // This DOES differ from r_draw's Color_Names, unfortunately.
 // Also includes Super colors
 static const char *COLOR_ENUMS[] = {
-	"NONE",     	// SKINCOLOR_NONE
-	"WHITE",    	// SKINCOLOR_WHITE
-	"SILVER",   	// SKINCOLOR_SILVER
-	"GREY",	    	// SKINCOLOR_GREY
-	"BLACK",    	// SKINCOLOR_BLACK
-	"BEIGE",    	// SKINCOLOR_BEIGE
-	"PEACH",    	// SKINCOLOR_PEACH
-	"BROWN",    	// SKINCOLOR_BROWN
-	"RED",      	// SKINCOLOR_RED
-	"CRIMSON",     	// SKINCOLOR_CRIMSON
-	"ORANGE",   	// SKINCOLOR_ORANGE
-	"RUST",     	// SKINCOLOR_RUST
-	"GOLD",      	// SKINCOLOR_GOLD
-	"YELLOW",   	// SKINCOLOR_YELLOW
-	"TAN",      	// SKINCOLOR_TAN
-	"MOSS",      	// SKINCOLOR_MOSS
-	"PERIDOT",    	// SKINCOLOR_PERIDOT
-	"GREEN",    	// SKINCOLOR_GREEN
-	"EMERALD",  	// SKINCOLOR_EMERALD
-	"AQUA",     	// SKINCOLOR_AQUA
-	"TEAL",     	// SKINCOLOR_TEAL
-	"CYAN",     	// SKINCOLOR_CYAN
-	"BLUE",     	// SKINCOLOR_BLUE
-	"AZURE",    	// SKINCOLOR_AZURE
-	"PASTEL",		// SKINCOLOR_PASTEL
-	"PURPLE",   	// SKINCOLOR_PURPLE
-	"LAVENDER", 	// SKINCOLOR_LAVENDER
-	"MAGENTA",   	// SKINCOLOR_MAGENTA
-	"PINK",     	// SKINCOLOR_PINK
-	"ROSY",     	// SKINCOLOR_ROSY
+	"NONE",			// SKINCOLOR_NONE,
+
+	// Greyscale ranges
+	"WHITE",     	// SKINCOLOR_WHITE,
+	"BONE",     	// SKINCOLOR_BONE,
+	"CLOUDY",     	// SKINCOLOR_CLOUDY,
+	"GREY",     	// SKINCOLOR_GREY,
+	"SILVER",     	// SKINCOLOR_SILVER,
+	"CARBON",     	// SKINCOLOR_CARBON,
+	"JET",     		// SKINCOLOR_JET,
+	"BLACK",     	// SKINCOLOR_BLACK,
+
+	// Desaturated
+	"AETHER",     	// SKINCOLOR_AETHER,
+	"SLATE",     	// SKINCOLOR_SLATE,
+	"PINK",     	// SKINCOLOR_PINK,
+	"YOGURT",     	// SKINCOLOR_YOGURT,
+	"BROWN",     	// SKINCOLOR_BROWN,
+	"TAN",     		// SKINCOLOR_TAN,
+	"BEIGE",     	// SKINCOLOR_BEIGE,
+	"MOSS",     	// SKINCOLOR_MOSS,
+	"AZURE",     	// SKINCOLOR_AZURE,
+	"LAVENDER",     // SKINCOLOR_LAVENDER,
+
+	// Viv's vivid colours (toast 21/07/17)
+	"RUBY",     	// SKINCOLOR_RUBY,
+	"SALMON",     	// SKINCOLOR_SALMON,
+	"RED",     		// SKINCOLOR_RED,
+	"CRIMSON",     	// SKINCOLOR_CRIMSON,
+	"FLAME",     	// SKINCOLOR_FLAME,
+	"PEACHY",     	// SKINCOLOR_PEACHY,
+	"QUAIL",     	// SKINCOLOR_QUAIL,
+	"SUNSET",     	// SKINCOLOR_SUNSET,
+	"APRICOT",     	// SKINCOLOR_APRICOT,
+	"ORANGE",     	// SKINCOLOR_ORANGE,
+	"RUST",     	// SKINCOLOR_RUST,
+	"GOLD",     	// SKINCOLOR_GOLD,
+	"SANDY",     	// SKINCOLOR_SANDY,
+	"YELLOW",     	// SKINCOLOR_YELLOW,
+	"OLIVE",     	// SKINCOLOR_OLIVE,
+	"LIME",     	// SKINCOLOR_LIME,
+	"PERIDOT",     	// SKINCOLOR_PERIDOT,
+	"GREEN",     	// SKINCOLOR_GREEN,
+	"FOREST",     	// SKINCOLOR_FOREST,
+	"EMERALD",     	// SKINCOLOR_EMERALD,
+	"MINT",     	// SKINCOLOR_MINT,
+	"SEAFOAM",     	// SKINCOLOR_SEAFOAM,
+	"AQUA",     	// SKINCOLOR_AQUA,
+	"TEAL",     	// SKINCOLOR_TEAL,
+	"WAVE",     	// SKINCOLOR_WAVE,
+	"CYAN",     	// SKINCOLOR_CYAN,
+	"SKY",     		// SKINCOLOR_SKY,
+	"CERULEAN",     // SKINCOLOR_CERULEAN,
+	"ICY",     		// SKINCOLOR_ICY,
+	"SAPPHIRE",     // SKINCOLOR_SAPPHIRE,
+	"CORNFLOWER",   // SKINCOLOR_CORNFLOWER,
+	"BLUE",     	// SKINCOLOR_BLUE,
+	"COBALT",     	// SKINCOLOR_COBALT,
+	"VAPOR",     	// SKINCOLOR_VAPOR,
+	"DUSK",     	// SKINCOLOR_DUSK,
+	"PASTEL",     	// SKINCOLOR_PASTEL,
+	"PURPLE",     	// SKINCOLOR_PURPLE,
+	"BUBBLEGUM",    // SKINCOLOR_BUBBLEGUM,
+	"MAGENTA",     	// SKINCOLOR_MAGENTA,
+	"NEON",     	// SKINCOLOR_NEON,
+	"VIOLET",     	// SKINCOLOR_VIOLET,
+	"LILAC",     	// SKINCOLOR_LILAC,
+	"PLUM",     	// SKINCOLOR_PLUM,
+	"ROSY",     	// SKINCOLOR_ROSY,
 
 	// Super special awesome Super flashing colors!
-	"SUPERSILVER1", // SKINCOLOR_SUPERSILVER1
-	"SUPERSILVER2", // SKINCOLOR_SUPERSILVER2,
-	"SUPERSILVER3", // SKINCOLOR_SUPERSILVER3,
-	"SUPERSILVER4", // SKINCOLOR_SUPERSILVER4,
-	"SUPERSILVER5", // SKINCOLOR_SUPERSILVER5,
-
-	"SUPERRED1", // SKINCOLOR_SUPERRED1
-	"SUPERRED2", // SKINCOLOR_SUPERRED2,
-	"SUPERRED3", // SKINCOLOR_SUPERRED3,
-	"SUPERRED4", // SKINCOLOR_SUPERRED4,
-	"SUPERRED5", // SKINCOLOR_SUPERRED5,
-
-	"SUPERORANGE1", // SKINCOLOR_SUPERORANGE1
-	"SUPERORANGE2", // SKINCOLOR_SUPERORANGE2,
-	"SUPERORANGE3", // SKINCOLOR_SUPERORANGE3,
-	"SUPERORANGE4", // SKINCOLOR_SUPERORANGE4,
-	"SUPERORANGE5", // SKINCOLOR_SUPERORANGE5,
-
-	"SUPERGOLD1", // SKINCOLOR_SUPERGOLD1
-	"SUPERGOLD2", // SKINCOLOR_SUPERGOLD2,
-	"SUPERGOLD3", // SKINCOLOR_SUPERGOLD3,
-	"SUPERGOLD4", // SKINCOLOR_SUPERGOLD4,
-	"SUPERGOLD5", // SKINCOLOR_SUPERGOLD5,
-
-	"SUPERPERIDOT1", // SKINCOLOR_SUPERPERIDOT1
-	"SUPERPERIDOT2", // SKINCOLOR_SUPERPERIDOT2,
-	"SUPERPERIDOT3", // SKINCOLOR_SUPERPERIDOT3,
-	"SUPERPERIDOT4", // SKINCOLOR_SUPERPERIDOT4,
-	"SUPERPERIDOT5", // SKINCOLOR_SUPERPERIDOT5,
-
-	"SUPERCYAN1", // SKINCOLOR_SUPERCYAN1
-	"SUPERCYAN2", // SKINCOLOR_SUPERCYAN2,
-	"SUPERCYAN3", // SKINCOLOR_SUPERCYAN3,
-	"SUPERCYAN4", // SKINCOLOR_SUPERCYAN4,
-	"SUPERCYAN5", // SKINCOLOR_SUPERCYAN5,
-
-	"SUPERPURPLE1",  	// SKINCOLOR_SUPERPURPLE1,
-	"SUPERPURPLE2",  	// SKINCOLOR_SUPERPURPLE2,
-	"SUPERPURPLE3",  	// SKINCOLOR_SUPERPURPLE3,
-	"SUPERPURPLE4",  	// SKINCOLOR_SUPERPURPLE4,
-	"SUPERPURPLE5",   	// SKINCOLOR_SUPERPURPLE5,
-
-	"SUPERRUST1", // SKINCOLOR_SUPERRUST1
-	"SUPERRUST2", // SKINCOLOR_SUPERRUST2,
-	"SUPERRUST3", // SKINCOLOR_SUPERRUST3,
-	"SUPERRUST4", // SKINCOLOR_SUPERRUST4,
-	"SUPERRUST5", // SKINCOLOR_SUPERRUST5,
-
-	"SUPERTAN1", // SKINCOLOR_SUPERTAN1
-	"SUPERTAN2", // SKINCOLOR_SUPERTAN2,
-	"SUPERTAN3", // SKINCOLOR_SUPERTAN3,
-	"SUPERTAN4", // SKINCOLOR_SUPERTAN4,
-	"SUPERTAN5" // SKINCOLOR_SUPERTAN5,
+	"SUPERSILVER1",	// SKINCOLOR_SUPERSILVER1
+	"SUPERSILVER2",	// SKINCOLOR_SUPERSILVER2,
+	"SUPERSILVER3",	// SKINCOLOR_SUPERSILVER3,
+	"SUPERSILVER4",	// SKINCOLOR_SUPERSILVER4,
+	"SUPERSILVER5",	// SKINCOLOR_SUPERSILVER5,
+
+	"SUPERRED1",	// SKINCOLOR_SUPERRED1
+	"SUPERRED2",	// SKINCOLOR_SUPERRED2,
+	"SUPERRED3",	// SKINCOLOR_SUPERRED3,
+	"SUPERRED4",	// SKINCOLOR_SUPERRED4,
+	"SUPERRED5",	// SKINCOLOR_SUPERRED5,
+
+	"SUPERORANGE1",	// SKINCOLOR_SUPERORANGE1
+	"SUPERORANGE2",	// SKINCOLOR_SUPERORANGE2,
+	"SUPERORANGE3",	// SKINCOLOR_SUPERORANGE3,
+	"SUPERORANGE4",	// SKINCOLOR_SUPERORANGE4,
+	"SUPERORANGE5",	// SKINCOLOR_SUPERORANGE5,
+
+	"SUPERGOLD1",	// SKINCOLOR_SUPERGOLD1
+	"SUPERGOLD2",	// SKINCOLOR_SUPERGOLD2,
+	"SUPERGOLD3",	// SKINCOLOR_SUPERGOLD3,
+	"SUPERGOLD4",	// SKINCOLOR_SUPERGOLD4,
+	"SUPERGOLD5",	// SKINCOLOR_SUPERGOLD5,
+
+	"SUPERPERIDOT1",	// SKINCOLOR_SUPERPERIDOT1
+	"SUPERPERIDOT2",	// SKINCOLOR_SUPERPERIDOT2,
+	"SUPERPERIDOT3",	// SKINCOLOR_SUPERPERIDOT3,
+	"SUPERPERIDOT4",	// SKINCOLOR_SUPERPERIDOT4,
+	"SUPERPERIDOT5",	// SKINCOLOR_SUPERPERIDOT5,
+
+	"SUPERSKY1",	// SKINCOLOR_SUPERSKY1
+	"SUPERSKY2",	// SKINCOLOR_SUPERSKY2,
+	"SUPERSKY3",	// SKINCOLOR_SUPERSKY3,
+	"SUPERSKY4",	// SKINCOLOR_SUPERSKY4,
+	"SUPERSKY5",	// SKINCOLOR_SUPERSKY5,
+
+	"SUPERPURPLE1",	// SKINCOLOR_SUPERPURPLE1,
+	"SUPERPURPLE2",	// SKINCOLOR_SUPERPURPLE2,
+	"SUPERPURPLE3",	// SKINCOLOR_SUPERPURPLE3,
+	"SUPERPURPLE4",	// SKINCOLOR_SUPERPURPLE4,
+	"SUPERPURPLE5",	// SKINCOLOR_SUPERPURPLE5,
+
+	"SUPERRUST1",	// SKINCOLOR_SUPERRUST1
+	"SUPERRUST2",	// SKINCOLOR_SUPERRUST2,
+	"SUPERRUST3",	// SKINCOLOR_SUPERRUST3,
+	"SUPERRUST4",	// SKINCOLOR_SUPERRUST4,
+	"SUPERRUST5",	// SKINCOLOR_SUPERRUST5,
+
+	"SUPERTAN1",	// SKINCOLOR_SUPERTAN1
+	"SUPERTAN2",	// SKINCOLOR_SUPERTAN2,
+	"SUPERTAN3",	// SKINCOLOR_SUPERTAN3,
+	"SUPERTAN4",	// SKINCOLOR_SUPERTAN4,
+	"SUPERTAN5"		// SKINCOLOR_SUPERTAN5,
 };
 
 static const char *const POWERS_LIST[] = {
@@ -7306,6 +6821,7 @@ struct {
 	{"LF_NOSSMUSIC",LF_NOSSMUSIC},
 	{"LF_NORELOAD",LF_NORELOAD},
 	{"LF_NOZONE",LF_NOZONE},
+	{"LF_SAVEGAME",LF_SAVEGAME},
 	// And map flags
 	{"LF2_HIDEINMENU",LF2_HIDEINMENU},
 	{"LF2_HIDEINSTATS",LF2_HIDEINSTATS},
@@ -7351,6 +6867,7 @@ struct {
 	{"SH_PROTECTFIRE",SH_PROTECTFIRE},
 	{"SH_PROTECTWATER",SH_PROTECTWATER},
 	{"SH_PROTECTELECTRIC",SH_PROTECTELECTRIC},
+	{"SH_PROTECTSPIKE",SH_PROTECTSPIKE},
 	// Indivisible shields
 	{"SH_PITY",SH_PITY},
 	{"SH_WHIRLWIND",SH_WHIRLWIND},
@@ -7438,7 +6955,11 @@ struct {
 	{"SF_X8AWAYSOUND",SF_X8AWAYSOUND},
 	{"SF_NOINTERRUPT",SF_NOINTERRUPT},
 	{"SF_X2AWAYSOUND",SF_X2AWAYSOUND},
-	
+
+	// Global emblem var flags
+	{"GE_NIGHTSPULL",GE_NIGHTSPULL},
+	{"GE_NIGHTSITEM",GE_NIGHTSITEM},
+
 	// Map emblem var flags
 	{"ME_ALLEMERALDS",ME_ALLEMERALDS},
 	{"ME_ULTIMATE",ME_ULTIMATE},
@@ -8120,11 +7641,14 @@ void FUNCMATH DEH_Check(void)
 static inline int lib_freeslot(lua_State *L)
 {
 	int n = lua_gettop(L);
-  int r = 0; // args returned
+	int r = 0; // args returned
 	char *s, *type,*word;
 
-  while (n-- > 0)
-  {
+	if (!lua_lumploading)
+		return luaL_error(L, "This function cannot be called from within a hook or coroutine!");
+
+	while (n-- > 0)
+	{
 		s = Z_StrDup(luaL_checkstring(L,1));
 		type = strtok(s, "_");
 		if (type)
diff --git a/src/dehacked.h b/src/dehacked.h
index 8832216b84799995cf38c3b1485ea27289912f00..dfce996a2c5f2fe0ca38221ee52983a8943fa4a1 100644
--- a/src/dehacked.h
+++ b/src/dehacked.h
@@ -27,13 +27,6 @@ typedef enum
 	UNDO_DONE = 0,
 } undotype_f;
 
-#ifdef DELFILE
-void DEH_WriteUndoline(const char *value, const char *data, undotype_f flags);
-void DEH_UnloadDehackedWad(UINT16 wad);
-#else // null the undo lines
-#define DEH_WriteUndoline(a,b,c)
-#endif
-
 void DEH_LoadDehackedLump(lumpnum_t lumpnum);
 void DEH_LoadDehackedLumpPwad(UINT16 wad, UINT16 lump);
 
diff --git a/src/djgppdos/i_video.c b/src/djgppdos/i_video.c
index 612c722151967a614d33a3b476da42652e46491f..6a7641174491d47793895c24677c6221c1f9e73b 100644
--- a/src/djgppdos/i_video.c
+++ b/src/djgppdos/i_video.c
@@ -90,6 +90,10 @@ static   unsigned long  nombre = NEWTICRATE*10;
 static void I_BlitScreenVesa1(void);   //see later
 void I_FinishUpdate (void)
 {
+	// draw captions if enabled
+	if (cv_closedcaptioning.value)
+		SCR_ClosedCaptions();
+
 	// draw FPS if enabled
 	if (cv_ticrate.value)
 		SCR_DisplayTicRate();
diff --git a/src/doomdata.h b/src/doomdata.h
index 033cc71b3e8c75413aeec0cb0ff0c71e80cb11fe..c0586fd65620f44d8b0e0f49e56d629d210772ae 100644
--- a/src/doomdata.h
+++ b/src/doomdata.h
@@ -207,8 +207,9 @@ typedef struct
 
 #define ZSHIFT 4
 
+extern const UINT8 Color_Index[MAXTRANSLATIONS-1][16];
 extern const char *Color_Names[MAXSKINCOLORS + NUMSUPERCOLORS];
-extern const UINT8 Color_Opposite[MAXSKINCOLORS*2];
+extern const UINT8 Color_Opposite[(MAXSKINCOLORS - 1)*2];
 
 #define NUMMAPS 1035
 
diff --git a/src/doomdef.h b/src/doomdef.h
index 73ecf7dc9daec2f20ee057d31d638df4f2f5e8b8..4d6bf75bbb65c0089d5d51dd98a7eb81de071c7b 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -214,7 +214,7 @@ extern FILE *logstream;
 // 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 22
+#define MODVERSION 24
 
 // =========================================================================
 
@@ -229,39 +229,77 @@ extern FILE *logstream;
 typedef enum
 {
 	SKINCOLOR_NONE = 0,
+
+	// Greyscale ranges
 	SKINCOLOR_WHITE,
-	SKINCOLOR_SILVER,
+	SKINCOLOR_BONE,
+	SKINCOLOR_CLOUDY,
 	SKINCOLOR_GREY,
+	SKINCOLOR_SILVER,
+	SKINCOLOR_CARBON,
+	SKINCOLOR_JET,
 	SKINCOLOR_BLACK,
-	SKINCOLOR_BEIGE,
-	SKINCOLOR_PEACH,
+
+	// Desaturated
+	SKINCOLOR_AETHER,
+	SKINCOLOR_SLATE,
+	SKINCOLOR_PINK,
+	SKINCOLOR_YOGURT,
 	SKINCOLOR_BROWN,
+	SKINCOLOR_TAN,
+	SKINCOLOR_BEIGE,
+	SKINCOLOR_MOSS,
+	SKINCOLOR_AZURE,
+	SKINCOLOR_LAVENDER,
+
+	// Viv's vivid colours (toast 21/07/17)
+	SKINCOLOR_RUBY,
+	SKINCOLOR_SALMON,
 	SKINCOLOR_RED,
 	SKINCOLOR_CRIMSON,
+	SKINCOLOR_FLAME,
+	SKINCOLOR_PEACHY,
+	SKINCOLOR_QUAIL,
+	SKINCOLOR_SUNSET,
+	SKINCOLOR_APRICOT,
 	SKINCOLOR_ORANGE,
 	SKINCOLOR_RUST,
 	SKINCOLOR_GOLD,
+	SKINCOLOR_SANDY,
 	SKINCOLOR_YELLOW,
-	SKINCOLOR_TAN,
-	SKINCOLOR_MOSS,
+	SKINCOLOR_OLIVE,
+	SKINCOLOR_LIME,
 	SKINCOLOR_PERIDOT,
 	SKINCOLOR_GREEN,
+	SKINCOLOR_FOREST,
 	SKINCOLOR_EMERALD,
+	SKINCOLOR_MINT,
+	SKINCOLOR_SEAFOAM,
 	SKINCOLOR_AQUA,
 	SKINCOLOR_TEAL,
+	SKINCOLOR_WAVE,
 	SKINCOLOR_CYAN,
+	SKINCOLOR_SKY,
+	SKINCOLOR_CERULEAN,
+	SKINCOLOR_ICY,
+	SKINCOLOR_SAPPHIRE, // sweet mother, i cannot weave – slender aphrodite has overcome me with longing for a girl
+	SKINCOLOR_CORNFLOWER,
 	SKINCOLOR_BLUE,
-	SKINCOLOR_AZURE,
+	SKINCOLOR_COBALT,
+	SKINCOLOR_VAPOR,
+	SKINCOLOR_DUSK,
 	SKINCOLOR_PASTEL,
 	SKINCOLOR_PURPLE,
-	SKINCOLOR_LAVENDER,
+	SKINCOLOR_BUBBLEGUM,
 	SKINCOLOR_MAGENTA,
-	SKINCOLOR_PINK,
+	SKINCOLOR_NEON,
+	SKINCOLOR_VIOLET,
+	SKINCOLOR_LILAC,
+	SKINCOLOR_PLUM,
 	SKINCOLOR_ROSY,
-	//SKINCOLOR_?
-	//SKINCOLOR_?
 
-	// Careful! MAXSKINCOLORS cannot be greater than 0x20! Two slots left...
+	// SKINCOLOR_? - one left before we bump up against 0x39, which isn't a HARD limit anymore but would be excessive
+
 	MAXSKINCOLORS,
 
 	// Super special awesome Super flashing colors!
@@ -295,11 +333,11 @@ typedef enum
 	SKINCOLOR_SUPERPERIDOT4,
 	SKINCOLOR_SUPERPERIDOT5,
 
-	SKINCOLOR_SUPERCYAN1,
-	SKINCOLOR_SUPERCYAN2,
-	SKINCOLOR_SUPERCYAN3,
-	SKINCOLOR_SUPERCYAN4,
-	SKINCOLOR_SUPERCYAN5,
+	SKINCOLOR_SUPERSKY1,
+	SKINCOLOR_SUPERSKY2,
+	SKINCOLOR_SUPERSKY3,
+	SKINCOLOR_SUPERSKY4,
+	SKINCOLOR_SUPERSKY5,
 
 	SKINCOLOR_SUPERPURPLE1,
 	SKINCOLOR_SUPERPURPLE2,
@@ -483,10 +521,6 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 #define ESLOPE_TYPESHIM
 #endif
 
-///	Delete file while the game is running.
-///	\note	EXTREMELY buggy, tends to crash game.
-//#define DELFILE
-
 ///	Allows the use of devmode in multiplayer. AKA "fishcake"
 //#define NETGAME_DEVMODE
 
@@ -546,4 +580,15 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 /// Hudname padding.
 #define SKINNAMEPADDING
 
+/// FINALLY some real clipping that doesn't make walls dissappear AND speeds the game up
+/// (that was the original comment from SRB2CB, sadly it is a lie and actually slows game down)
+/// on the bright side it fixes some weird issues with translucent walls
+/// \note	SRB2CB port.
+///      	SRB2CB itself ported this from PrBoom+
+#define NEWCLIP
+
+/// Handle touching sector specials in P_PlayerAfterThink instead of P_PlayerThink.
+/// \note   Required for proper collision with moving sloped surfaces that have sector specials on them.
+//#define SECTORSPECIALSAFTERTHINK
+
 #endif // __DOOMDEF__
diff --git a/src/doomstat.h b/src/doomstat.h
index 7ee0382b25a0c01abc08b70877b9ffcab9b7c5dd..a24bad79d73dc21dbd1b7b5b63728e4efb4862f0 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -41,7 +41,8 @@ extern INT16 maptol;
 extern UINT8 globalweather;
 extern INT32 curWeather;
 extern INT32 cursaveslot;
-extern INT16 lastmapsaved;
+//extern INT16 lastmapsaved;
+extern INT16 lastmaploaded;
 extern boolean gamecomplete;
 
 #define PRECIP_NONE  0
@@ -263,6 +264,7 @@ typedef struct
 #define LF_NOSSMUSIC      4 ///< Disable Super Sonic music
 #define LF_NORELOAD       8 ///< Don't reload level on death
 #define LF_NOZONE        16 ///< Don't include "ZONE" on level title
+#define LF_SAVEGAME      32 ///< Save the game upon loading this level
 
 #define LF2_HIDEINMENU     1 ///< Hide in the multiplayer menu
 #define LF2_HIDEINSTATS    2 ///< Hide in the statistics screen
@@ -379,6 +381,7 @@ nightsdata_t ntemprecords;
 
 extern UINT32 token; ///< Number of tokens collected in a level
 extern UINT32 tokenlist; ///< List of tokens collected
+extern boolean gottoken; ///< Did you get a token? Used for end of act
 extern INT32 tokenbits; ///< Used for setting token bits
 extern INT32 sstimer; ///< Time allotted in the special stage
 extern UINT32 bluescore; ///< Blue Team Scores
@@ -452,19 +455,17 @@ extern mapthing_t *redctfstarts[MAXPLAYERS]; // CTF
 
 #if defined (macintosh)
 #define DEBFILE(msg) I_OutputMsg(msg)
-extern FILE *debugfile;
 #else
 #define DEBUGFILE
 #ifdef DEBUGFILE
 #define DEBFILE(msg) { if (debugfile) { fputs(msg, debugfile); fflush(debugfile); } }
-extern FILE *debugfile;
 #else
 #define DEBFILE(msg) {}
-extern FILE *debugfile;
 #endif
 #endif
 
 #ifdef DEBUGFILE
+extern FILE *debugfile;
 extern INT32 debugload;
 #endif
 
diff --git a/src/f_finale.c b/src/f_finale.c
index 167fdd880f7dd9bb901b85877c3248009d870c87..db497daf7f26d59057e24c32f27c45fb521c73de 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -434,7 +434,6 @@ void F_StartIntro(void)
 
 	G_SetGamestate(GS_INTRO);
 	gameaction = ga_nothing;
-	playerdeadview = false;
 	paused = false;
 	CON_ToggleOff();
 	CON_ClearHUD();
@@ -1125,7 +1124,6 @@ void F_StartCredits(void)
 	}
 
 	gameaction = ga_nothing;
-	playerdeadview = false;
 	paused = false;
 	CON_ToggleOff();
 	CON_ClearHUD();
@@ -1272,7 +1270,6 @@ void F_StartGameEvaluation(void)
 		G_SaveGame((UINT32)cursaveslot);
 
 	gameaction = ga_nothing;
-	playerdeadview = false;
 	paused = false;
 	CON_ToggleOff();
 	CON_ClearHUD();
@@ -1383,7 +1380,6 @@ void F_StartGameEnd(void)
 	G_SetGamestate(GS_GAMEEND);
 
 	gameaction = ga_nothing;
-	playerdeadview = false;
 	paused = false;
 	CON_ToggleOff();
 	CON_ClearHUD();
@@ -1586,7 +1582,6 @@ void F_StartContinue(void)
 	gameaction = ga_nothing;
 
 	keypressed = false;
-	playerdeadview = false;
 	paused = false;
 	CON_ToggleOff();
 	CON_ClearHUD();
@@ -1728,7 +1723,7 @@ static void F_AdvanceToNextScene(void)
 
 void F_EndCutScene(void)
 {
-	cutsceneover = true; // do this first, just in case Y_EndGame or something wants to turn it back false later
+	cutsceneover = true; // do this first, just in case G_EndGame or something wants to turn it back false later
 	if (runningprecutscene)
 	{
 		if (server)
@@ -1743,7 +1738,7 @@ void F_EndCutScene(void)
 		else if (nextmap < 1100-1)
 			G_NextLevel();
 		else
-			Y_EndGame();
+			G_EndGame();
 	}
 }
 
@@ -1755,7 +1750,6 @@ void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean reset
 	G_SetGamestate(GS_CUTSCENE);
 
 	gameaction = ga_nothing;
-	playerdeadview = false;
 	paused = false;
 	CON_ToggleOff();
 
diff --git a/src/f_wipe.c b/src/f_wipe.c
index a0b685a3282f61e1d9fdd42c4ed4394ce5d227df..acc4efaaa6d446b7f383766c1977d5311d0c77a9 100644
--- a/src/f_wipe.c
+++ b/src/f_wipe.c
@@ -86,7 +86,7 @@ INT32 lastwipetic = 0;
 static UINT8 *wipe_scr_start; //screen 3
 static UINT8 *wipe_scr_end; //screen 4
 static UINT8 *wipe_scr; //screen 0 (main drawing)
-static fixed_t paldiv;
+static fixed_t paldiv = 0;
 
 /** Create fademask_t from lump
   *
@@ -145,7 +145,7 @@ static fademask_t *F_GetFadeMask(UINT8 masknum, UINT8 scrnnum) {
 	while (lsize--)
 	{
 		// Determine pixel to use from fademask
-		pcolor = &pLocalPalette[*lump++];
+		pcolor = &pMasterPalette[*lump++];
 		*mask++ = FixedDiv((pcolor->s.red+1)<<FRACBITS, paldiv)>>FRACBITS;
 	}
 
@@ -337,7 +337,8 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu)
 	UINT8 wipeframe = 0;
 	fademask_t *fmask;
 
-	paldiv = FixedDiv(257<<FRACBITS, 11<<FRACBITS);
+	if (!paldiv)
+		paldiv = FixedDiv(257<<FRACBITS, 11<<FRACBITS);
 
 	// Init the wipe
 	WipeInAction = true;
diff --git a/src/filesrch.c b/src/filesrch.c
index acc176d6a3018392fcbf1724a43072072748b7ba..a28c4d83c24f03564d456fa3094decb07a1139d7 100644
--- a/src/filesrch.c
+++ b/src/filesrch.c
@@ -31,6 +31,8 @@
 #include "filesrch.h"
 #include "d_netfil.h"
 #include "m_misc.h"
+#include "z_zone.h"
+#include "m_menu.h" // Addons_option_Onchange
 
 #if (defined (_WIN32) && !defined (_WIN32_WCE)) && defined (_MSC_VER) && !defined (_XBOX)
 
@@ -255,6 +257,28 @@ readdir (DIR * dirp)
   return (struct dirent *) 0;
 }
 
+/*
+ * rewinddir
+ *
+ * Makes the next readdir start from the beginning.
+ */
+int
+rewinddir (DIR * dirp)
+{
+  errno = 0;
+
+  /* Check for valid DIR struct. */
+  if (!dirp)
+    {
+      errno = EFAULT;
+      return -1;
+    }
+
+  dirp->dd_stat = 0;
+
+  return 0;
+}
+
 /*
  * closedir
  *
@@ -285,6 +309,35 @@ closedir (DIR * dirp)
   return rc;
 }
 #endif
+
+static CV_PossibleValue_t addons_cons_t[] = {{0, "SRB2 Folder"}, /*{1, "HOME"}, {2, "SRB2 Folder"},*/ {3, "CUSTOM"}, {0, NULL}};
+consvar_t cv_addons_option = {"addons_option", "SRB2 Folder", CV_SAVE|CV_CALL, addons_cons_t, Addons_option_Onchange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_addons_folder = {"addons_folder", "./", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+static CV_PossibleValue_t addons_md5_cons_t[] = {{0, "Name"}, {1, "Contents"}, {0, NULL}};
+consvar_t cv_addons_md5 = {"addons_md5", "Name", CV_SAVE, addons_md5_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+consvar_t cv_addons_showall = {"addons_showall", "No", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+consvar_t cv_addons_search_case = {"addons_search_case", "No", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+static CV_PossibleValue_t addons_search_type_cons_t[] = {{0, "Start"}, {1, "Anywhere"}, {0, NULL}};
+consvar_t cv_addons_search_type = {"addons_search_type", "Anywhere", CV_SAVE, addons_search_type_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+char menupath[1024];
+size_t menupathindex[menudepth];
+size_t menudepthleft = menudepth;
+
+char menusearch[MAXSTRINGLENGTH+1];
+
+char **dirmenu;
+size_t sizedirmenu;
+size_t dir_on[menudepth];
+UINT8 refreshdirmenu = 0;
+
+size_t packetsizetally = 0;
+size_t mainwadstally = 0;
+
 #if defined (_XBOX) && defined (_MSC_VER)
 filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *wantedmd5sum,
 	boolean completepath, int maxsearchdepth)
@@ -296,6 +349,13 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want
 	completepath = false;
 	return FS_NOTFOUND;
 }
+
+boolean preparefilemenu(boolean samedepth)
+{
+	(void)samedepth;
+	return false;
+}
+
 #elif defined (_WIN32_WCE)
 filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *wantedmd5sum,
 	boolean completepath, int maxsearchdepth)
@@ -346,6 +406,12 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want
 #endif
 	return FS_NOTFOUND;
 }
+
+boolean preparefilemenu(boolean samedepth)
+{
+	(void)samedepth;
+	return false;
+}
 #else
 filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *wantedmd5sum, boolean completepath, int maxsearchdepth)
 {
@@ -387,25 +453,29 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want
 	{
 		searchpath[searchpathindex[depthleft]]=0;
 		dent = readdir(dirhandle[depthleft]);
-		if (dent)
-			strcpy(&searchpath[searchpathindex[depthleft]],dent->d_name);
 
 		if (!dent)
+		{
 			closedir(dirhandle[depthleft++]);
-		else if (dent->d_name[0]=='.' &&
+			continue;
+		}
+
+		if (dent->d_name[0]=='.' &&
 				(dent->d_name[1]=='\0' ||
 					(dent->d_name[1]=='.' &&
 						dent->d_name[2]=='\0')))
 		{
 			// we don't want to scan uptree
+			continue;
 		}
-		else if (stat(searchpath,&fsstat) < 0) // do we want to follow symlinks? if not: change it to lstat
-		{
-			// was the file (re)moved? can't stat it
-		}
+
+		// okay, now we actually want searchpath to incorporate d_name
+		strcpy(&searchpath[searchpathindex[depthleft]],dent->d_name);
+
+		if (stat(searchpath,&fsstat) < 0) // do we want to follow symlinks? if not: change it to lstat
+			; // was the file (re)moved? can't stat it
 		else if (S_ISDIR(fsstat.st_mode) && depthleft)
 		{
-			strcpy(&searchpath[searchpathindex[depthleft]],dent->d_name);
 			searchpathindex[--depthleft] = strlen(searchpath) + 1;
 			dirhandle[depthleft] = opendir(searchpath);
 			if (!dirhandle[depthleft])
@@ -444,6 +514,255 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want
 	free(searchname);
 	free(searchpathindex);
 	free(dirhandle);
+
 	return retval;
 }
+
+char exttable[NUM_EXT_TABLE][5] = {
+	".txt", ".cfg", // exec
+	".wad", ".soc", ".lua"}; // addfile
+
+char filenamebuf[MAX_WADFILES][MAX_WADPATH];
+
+
+static boolean filemenusearch(char *haystack, char *needle)
+{
+	static char localhaystack[128];
+	strlcpy(localhaystack, haystack, 128);
+	if (!cv_addons_search_case.value)
+		strupr(localhaystack);
+	return ((cv_addons_search_type.value)
+		? (strstr(localhaystack, needle) != 0)
+		: (!strncmp(localhaystack, needle, menusearch[0])));
+}
+
+#define searchdir if (menusearch[0] && !filemenusearch(dent->d_name, localmenusearch))\
+					{\
+						rejected++;\
+						continue;\
+					}\
+
+boolean preparefilemenu(boolean samedepth)
+{
+	DIR *dirhandle;
+	struct dirent *dent;
+	struct stat fsstat;
+	size_t pos = 0, folderpos = 0, numfolders = 0, rejected = 0;
+	char *tempname = NULL;
+	boolean noresults = false;
+	char localmenusearch[MAXSTRINGLENGTH] = "";
+
+	if (samedepth)
+	{
+		if (dirmenu && dirmenu[dir_on[menudepthleft]])
+			tempname = Z_StrDup(dirmenu[dir_on[menudepthleft]]+DIR_STRING); // don't need to I_Error if can't make - not important, just QoL
+	}
+	else
+		menusearch[0] = menusearch[1] = 0; // clear search
+
+	for (; sizedirmenu > 0; sizedirmenu--) // clear out existing items
+	{
+		Z_Free(dirmenu[sizedirmenu-1]);
+		dirmenu[sizedirmenu-1] = NULL;
+	}
+
+	if (!(dirhandle = opendir(menupath))) // get directory
+		return false;
+
+	if (menusearch[0])
+	{
+		strcpy(localmenusearch, menusearch+1);
+		if (!cv_addons_search_case.value)
+			strupr(localmenusearch);
+	}
+
+	while (true)
+	{
+		menupath[menupathindex[menudepthleft]] = 0;
+		dent = readdir(dirhandle);
+
+		if (!dent)
+			break;
+		else if (dent->d_name[0]=='.' &&
+				(dent->d_name[1]=='\0' ||
+					(dent->d_name[1]=='.' &&
+						dent->d_name[2]=='\0')))
+			continue; // we don't want to scan uptree
+
+		strcpy(&menupath[menupathindex[menudepthleft]],dent->d_name);
+
+		if (stat(menupath,&fsstat) < 0) // do we want to follow symlinks? if not: change it to lstat
+			; // was the file (re)moved? can't stat it
+		else // is a file or directory
+		{
+			if (!S_ISDIR(fsstat.st_mode)) // file
+			{
+				if (!cv_addons_showall.value)
+				{
+					size_t len = strlen(dent->d_name)+1;
+					UINT8 ext;
+					for (ext = 0; ext < NUM_EXT_TABLE; ext++)
+						if (!strcasecmp(exttable[ext], dent->d_name+len-5)) break; // extension comparison
+					if (ext == NUM_EXT_TABLE) continue; // not an addfile-able (or exec-able) file
+				}
+				searchdir;
+			}
+			else // directory
+			{
+				searchdir;
+				numfolders++;
+			}
+			sizedirmenu++;
+		}
+	}
+
+	if (!rejected && !sizedirmenu)
+	{
+		if (tempname)
+			Z_Free(tempname);
+		closedir(dirhandle);
+		return false;
+	}
+
+	if (((noresults = (menusearch[0] && !sizedirmenu)))
+		|| (!menusearch[0] && menudepthleft != menudepth-1)) // Make room for UP... or search entry
+	{
+		sizedirmenu++;
+		numfolders++;
+		folderpos++;
+	}
+
+	if (!(dirmenu = Z_Realloc(dirmenu, sizedirmenu*sizeof(char *), PU_STATIC, NULL)))
+	{
+		closedir(dirhandle); // just in case
+		I_Error("Ran out of memory whilst preparing add-ons menu");
+	}
+
+	rejected = 0;
+	rewinddir(dirhandle);
+
+	while ((pos+folderpos) < sizedirmenu)
+	{
+		menupath[menupathindex[menudepthleft]] = 0;
+		dent = readdir(dirhandle);
+
+		if (!dent)
+			break;
+		else if (dent->d_name[0]=='.' &&
+				(dent->d_name[1]=='\0' ||
+					(dent->d_name[1]=='.' &&
+						dent->d_name[2]=='\0')))
+			continue; // we don't want to scan uptree
+
+		strcpy(&menupath[menupathindex[menudepthleft]],dent->d_name);
+
+		if (stat(menupath,&fsstat) < 0) // do we want to follow symlinks? if not: change it to lstat
+			; // was the file (re)moved? can't stat it
+		else // is a file or directory
+		{
+			char *temp;
+			size_t len = strlen(dent->d_name)+1;
+			UINT8 ext = EXT_FOLDER;
+			UINT8 folder;
+
+			if (!S_ISDIR(fsstat.st_mode)) // file
+			{
+				if (!((numfolders+pos) < sizedirmenu)) continue; // crash prevention
+				for (; ext < NUM_EXT_TABLE; ext++)
+					if (!strcasecmp(exttable[ext], dent->d_name+len-5)) break; // extension comparison
+				if (ext == NUM_EXT_TABLE && !cv_addons_showall.value) continue; // not an addfile-able (or exec-able) file
+				ext += EXT_START; // moving to be appropriate position
+
+				searchdir;
+
+				if (ext >= EXT_LOADSTART)
+				{
+					size_t i;
+					for (i = 0; i < numwadfiles; i++)
+					{
+						if (!filenamebuf[i][0])
+						{
+							strncpy(filenamebuf[i], wadfiles[i]->filename, MAX_WADPATH);
+							filenamebuf[i][MAX_WADPATH - 1] = '\0';
+							nameonly(filenamebuf[i]);
+						}
+
+						if (strcmp(dent->d_name, filenamebuf[i]))
+							continue;
+						if (cv_addons_md5.value && !checkfilemd5(menupath, wadfiles[i]->md5sum))
+							continue;
+
+						ext |= EXT_LOADED;
+					}
+				}
+				else if (ext == EXT_TXT)
+				{
+					if (!strcmp(dent->d_name, "log.txt") || !strcmp(dent->d_name, "errorlog.txt"))
+						ext |= EXT_LOADED;
+				}
+
+				if (!strcmp(dent->d_name, configfile))
+					ext |= EXT_LOADED;
+
+				folder = 0;
+			}
+			else // directory
+			{
+				searchdir;
+				len += (folder = 1);
+			}
+
+			if (len > 255)
+				len = 255;
+
+			if (!(temp = Z_Malloc((len+DIR_STRING+folder) * sizeof (char), PU_STATIC, NULL)))
+				I_Error("Ran out of memory whilst preparing add-ons menu");
+			temp[DIR_TYPE] = ext;
+			temp[DIR_LEN] = (UINT8)(len);
+			strlcpy(temp+DIR_STRING, dent->d_name, len);
+			if (folder)
+			{
+				strcpy(temp+len, "/");
+				dirmenu[folderpos++] = temp;
+			}
+			else
+				dirmenu[numfolders + pos++] = temp;
+		}
+	}
+
+	closedir(dirhandle);
+
+	if (noresults) // no results
+		dirmenu[0] = Z_StrDup(va("%c\13No results...", EXT_NORESULTS));
+	else if (!menusearch[0] &&menudepthleft != menudepth-1) // now for UP... entry
+		dirmenu[0] = Z_StrDup(va("%c\5UP...", EXT_UP));
+
+	menupath[menupathindex[menudepthleft]] = 0;
+	sizedirmenu = (numfolders+pos); // just in case things shrink between opening and rewind
+
+	if (tempname)
+	{
+		size_t i;
+		for (i = 0; i < sizedirmenu; i++)
+		{
+			if (!strcmp(dirmenu[i]+DIR_STRING, tempname))
+			{
+				dir_on[menudepthleft] = i;
+				break;
+			}
+		}
+		Z_Free(tempname);
+	}
+
+	if (!sizedirmenu)
+	{
+		dir_on[menudepthleft] = 0;
+		Z_Free(dirmenu);
+		return false;
+	}
+	else if (dir_on[menudepthleft] >= sizedirmenu)
+		dir_on[menudepthleft] = sizedirmenu-1;
+
+	return true;
+}
 #endif
diff --git a/src/filesrch.h b/src/filesrch.h
index 33b148d4b8c0ce668aaf08dd1c3cdcfeec87eb42..c2201b45306686a8b17220a883ff7b2864c5fc9c 100644
--- a/src/filesrch.h
+++ b/src/filesrch.h
@@ -6,6 +6,9 @@
 
 #include "doomdef.h"
 #include "d_netfil.h"
+#include "m_menu.h" // MAXSTRINGLENGTH
+
+extern consvar_t cv_addons_option, cv_addons_folder, cv_addons_md5, cv_addons_showall, cv_addons_search_case, cv_addons_search_type;
 
 /**	\brief	The filesearch function
 
@@ -25,4 +28,64 @@
 filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *wantedmd5sum,
 	boolean completepath, int maxsearchdepth);
 
+#define menudepth 20
+
+extern char menupath[1024];
+extern size_t menupathindex[menudepth];
+extern size_t menudepthleft;
+
+extern char menusearch[MAXSTRINGLENGTH+1];
+
+extern char **dirmenu;
+extern size_t sizedirmenu;
+extern size_t dir_on[menudepth];
+extern UINT8 refreshdirmenu;
+
+extern size_t packetsizetally;
+extern size_t mainwadstally;
+
+typedef enum
+{
+	EXT_FOLDER = 0,
+	EXT_UP,
+	EXT_NORESULTS,
+	EXT_START,
+	EXT_TXT = EXT_START,
+	EXT_CFG,
+	EXT_LOADSTART,
+	EXT_WAD = EXT_LOADSTART,
+	EXT_SOC,
+	EXT_LUA, // allowed even if not HAVE_BLUA so that we can yell on load attempt
+	NUM_EXT,
+	NUM_EXT_TABLE = NUM_EXT-EXT_START,
+	EXT_LOADED = 0x80
+	/*
+	obviously there can only be 0x7F supported extensions in
+	addons menu because we're cramming this into a char out of
+	laziness/easy memory allocation (what's the difference?)
+	and have stolen a bit to show whether it's loaded or not
+	in practice the size of the data type is probably overkill
+	toast 02/05/17
+	*/
+} ext_enum;
+
+typedef enum
+{
+	DIR_TYPE = 0,
+	DIR_LEN,
+	DIR_STRING
+} dirname_enum;
+
+typedef enum
+{
+	REFRESHDIR_NORMAL = 1,
+	REFRESHDIR_ADDFILE = 2,
+	REFRESHDIR_WARNING = 4,
+	REFRESHDIR_ERROR = 8,
+	REFRESHDIR_NOTLOADED = 16,
+	REFRESHDIR_MAX = 32
+} refreshdir_enum;
+
+boolean preparefilemenu(boolean samedepth);
+
 #endif // __FILESRCH_H__
diff --git a/src/g_game.c b/src/g_game.c
index 8bd71d123dfd0905cbf8ebea8434bb6c67d43bcb..e996938ab30ac44cc2d655406e8008e46aaca748 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -77,7 +77,8 @@ INT16 maptol;
 UINT8 globalweather = 0;
 INT32 curWeather = PRECIP_NONE;
 INT32 cursaveslot = -1; // Auto-save 1p savegame slot
-INT16 lastmapsaved = 0; // Last map we auto-saved at
+//INT16 lastmapsaved = 0; // Last map we auto-saved at
+INT16 lastmaploaded = 0; // Last map the game loaded
 boolean gamecomplete = false;
 
 UINT16 mainwads = 0;
@@ -156,6 +157,7 @@ UINT8 stagefailed; // Used for GEMS BONUS? Also to see if you beat the stage.
 UINT16 emeralds;
 UINT32 token; // Number of tokens collected in a level
 UINT32 tokenlist; // List of tokens collected
+boolean gottoken; // Did you get a token? Used for end of act
 INT32 tokenbits; // Used for setting token bits
 
 // Old Special Stage
@@ -1011,13 +1013,13 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 		if (turnleft)
 			cmd->angleturn = (INT16)(cmd->angleturn + angleturn[tspeed]);
 	}
-	if (cv_analog.value || twodlevel
+	if (twodlevel
 		|| (player->mo && (player->mo->flags2 & MF2_TWOD))
 		|| (!demoplayback && (player->climbing
 		|| (player->powers[pw_carry] == CR_NIGHTSMODE)
 		|| (player->pflags & (PF_SLIDING|PF_FORCESTRAFE))))) // Analog
 			forcestrafe = true;
-	if (forcestrafe) // Analog
+	if (forcestrafe)
 	{
 		if (turnright)
 			side += sidemove[speed];
@@ -1030,6 +1032,13 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 			side += ((axis * sidemove[1]) >> 10);
 		}
 	}
+	else if (cv_analog.value) // Analog
+	{
+		if (turnright)
+			cmd->buttons |= BT_CAMRIGHT;
+		if (turnleft)
+			cmd->buttons |= BT_CAMLEFT;
+	}
 	else
 	{
 		if (turnright)
@@ -1117,15 +1126,6 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 	if (PLAYER1INPUTDOWN(gc_use))
 		cmd->buttons |= BT_USE;
 
-	// Camera Controls
-	if (cv_debug || cv_analog.value || demoplayback || objectplacing || player->powers[pw_carry] == CR_NIGHTSMODE)
-	{
-		if (PLAYER1INPUTDOWN(gc_camleft))
-			cmd->buttons |= BT_CAMLEFT;
-		if (PLAYER1INPUTDOWN(gc_camright))
-			cmd->buttons |= BT_CAMRIGHT;
-	}
-
 	if (PLAYER1INPUTDOWN(gc_camreset))
 	{
 		if (camera.chase && !resetdown)
@@ -1187,10 +1187,19 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 	if (!mouseaiming && cv_mousemove.value)
 		forward += mousey;
 
-	if (cv_analog.value ||
-		(!demoplayback && (player->climbing
+	if ((!demoplayback && (player->climbing
 		|| (player->pflags & PF_SLIDING)))) // Analog for mouse
 		side += mousex*2;
+	else if (cv_analog.value)
+	{
+		if (mousex)
+		{
+			if (mousex > 0)
+				cmd->buttons |= BT_CAMRIGHT;
+			else
+				cmd->buttons |= BT_CAMLEFT;
+		}
+	}
 	else
 		cmd->angleturn = (INT16)(cmd->angleturn - (mousex*8));
 
@@ -1225,9 +1234,10 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 	cmd->sidemove = (SINT8)(cmd->sidemove + side);
 
 	if (cv_analog.value) {
-		cmd->angleturn = (INT16)(thiscam->angle >> 16);
 		if (player->awayviewtics)
 			cmd->angleturn = (INT16)(player->awayviewmobj->angle >> 16);
+		else
+			cmd->angleturn = (INT16)(thiscam->angle >> 16);
 	}
 	else
 	{
@@ -1301,7 +1311,7 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 		if (turnleft)
 			cmd->angleturn = (INT16)(cmd->angleturn + angleturn[tspeed]);
 	}
-	if (cv_analog2.value || twodlevel
+	if (twodlevel
 		|| (player->mo && (player->mo->flags2 & MF2_TWOD))
 		|| player->climbing
 		|| (player->powers[pw_carry] == CR_NIGHTSMODE)
@@ -1320,6 +1330,13 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 			side += ((axis * sidemove[1]) >> 10);
 		}
 	}
+	else if (cv_analog2.value) // Analog
+	{
+		if (turnright)
+			cmd->buttons |= BT_CAMRIGHT;
+		if (turnleft)
+			cmd->buttons |= BT_CAMLEFT;
+	}
 	else
 	{
 		if (turnright)
@@ -1404,15 +1421,6 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 	if (PLAYER2INPUTDOWN(gc_use))
 		cmd->buttons |= BT_USE;
 
-	// Camera Controls
-	if (cv_debug || cv_analog2.value || player->powers[pw_carry] == CR_NIGHTSMODE)
-	{
-		if (PLAYER2INPUTDOWN(gc_camleft))
-			cmd->buttons |= BT_CAMLEFT;
-		if (PLAYER2INPUTDOWN(gc_camright))
-			cmd->buttons |= BT_CAMRIGHT;
-	}
-
 	if (PLAYER2INPUTDOWN(gc_camreset))
 	{
 		if (camera2.chase && !resetdown)
@@ -1474,9 +1482,19 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 	if (!mouseaiming && cv_mousemove2.value)
 		forward += mouse2y;
 
-	if (cv_analog2.value || player->climbing
+	if (player->climbing
 		|| (player->pflags & PF_SLIDING)) // Analog for mouse
 		side += mouse2x*2;
+	else if (cv_analog2.value)
+	{
+		if (mouse2x)
+		{
+			if (mouse2x > 0)
+				cmd->buttons |= BT_CAMRIGHT;
+			else
+				cmd->buttons |= BT_CAMLEFT;
+		}
+	}
 	else
 		cmd->angleturn = (INT16)(cmd->angleturn - (mouse2x*8));
 
@@ -1524,9 +1542,10 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 	}
 
 	if (cv_analog2.value) {
-		cmd->angleturn = (INT16)(thiscam->angle >> 16);
 		if (player->awayviewtics)
 			cmd->angleturn = (INT16)(player->awayviewmobj->angle >> 16);
+		else
+			cmd->angleturn = (INT16)(thiscam->angle >> 16);
 	}
 	else
 	{
@@ -2072,6 +2091,7 @@ void G_PlayerReborn(INT32 player)
 	UINT32 availabilities;
 	tic_t jointime;
 	boolean spectator;
+	boolean outofcoop;
 	INT16 bot;
 	SINT8 pity;
 
@@ -2082,6 +2102,7 @@ void G_PlayerReborn(INT32 player)
 	exiting = players[player].exiting;
 	jointime = players[player].jointime;
 	spectator = players[player].spectator;
+	outofcoop = players[player].outofcoop;
 	pflags = (players[player].pflags & (PF_TIMEOVER|PF_FLIPCAM|PF_TAGIT|PF_TAGGED|PF_ANALOGMODE));
 
 	// As long as we're not in multiplayer, carry over cheatcodes from map to map
@@ -2136,6 +2157,7 @@ void G_PlayerReborn(INT32 player)
 	p->ctfteam = ctfteam;
 	p->jointime = jointime;
 	p->spectator = spectator;
+	p->outofcoop = outofcoop;
 
 	// save player config truth reborn
 	p->skincolor = skincolor;
@@ -2187,8 +2209,8 @@ void G_PlayerReborn(INT32 player)
 	p->rings = 0; // 0 rings
 	p->panim = PA_IDLE; // standing animation
 
-	if ((netgame || multiplayer) && !p->spectator)
-		p->powers[pw_flashing] = flashingtics-1; // Babysitting deterrent
+	//if ((netgame || multiplayer) && !p->spectator) -- moved into P_SpawnPlayer to account for forced changes there
+		//p->powers[pw_flashing] = flashingtics-1; // Babysitting deterrent
 
 	if (p-players == consoleplayer)
 	{
@@ -2291,6 +2313,9 @@ void G_SpawnPlayer(INT32 playernum, boolean starpost)
 	if (starpost) //Don't even bother with looking for a place to spawn.
 	{
 		P_MovePlayerToStarpost(playernum);
+#ifdef HAVE_BLUA
+		LUAh_PlayerSpawn(&players[playernum]); // Lua hook for player spawning :)
+#endif
 		return;
 	}
 
@@ -2474,7 +2499,8 @@ void G_ChangePlayerReferences(mobj_t *oldmo, mobj_t *newmo)
 void G_DoReborn(INT32 playernum)
 {
 	player_t *player = &players[playernum];
-	boolean starpost = false;
+	boolean resetlevel = false;
+	INT32 i;
 
 	if (modeattacking)
 	{
@@ -2500,35 +2526,98 @@ void G_DoReborn(INT32 playernum)
 		B_RespawnBot(playernum);
 		if (oldmo)
 			G_ChangePlayerReferences(oldmo, players[playernum].mo);
+
+		return;
+	}
+
+	if (countdowntimeup || (!(netgame || multiplayer) && gametype == GT_COOP))
+		resetlevel = true;
+	else if (gametype == GT_COOP && (netgame || multiplayer))
+	{
+		boolean notgameover = true;
+
+		if (cv_cooplives.value != 0 && player->lives <= 0) // consider game over first
+		{
+			for (i = 0; i < MAXPLAYERS; i++)
+			{
+				if (!playeringame[i])
+					continue;
+				if (players[i].exiting || players[i].lives > 0)
+					break;
+			}
+
+			if (i == MAXPLAYERS)
+			{
+				notgameover = false;
+				if (!countdown2)
+				{
+					// They're dead, Jim.
+					//nextmapoverride = spstage_start;
+					nextmapoverride = gamemap;
+					countdown2 = TICRATE;
+					skipstats = true;
+
+					for (i = 0; i < MAXPLAYERS; i++)
+					{
+						if (playeringame[i])
+							players[i].score = 0;
+					}
+
+					//emeralds = 0;
+					tokenbits = 0;
+					tokenlist = 0;
+					token = 0;
+				}
+			}
+		}
+
+		if (notgameover && cv_coopstarposts.value == 2)
+		{
+			for (i = 0; i < MAXPLAYERS; i++)
+			{
+				if (!playeringame[i])
+					continue;
+
+				if (players[i].playerstate != PST_DEAD && !players[i].spectator && players[i].mo && players[i].mo->health)
+					break;
+			}
+			if (i == MAXPLAYERS)
+				resetlevel = true;
+		}
 	}
-	else if (countdowntimeup || (!multiplayer && gametype == GT_COOP))
+
+	if (resetlevel)
 	{
 		// reload the level from scratch
 		if (countdowntimeup)
 		{
-			player->starpostangle = 0;
-			player->starposttime = 0;
-			player->starpostx = 0;
-			player->starposty = 0;
-			player->starpostz = 0;
-			player->starpostnum = 0;
+			for (i = 0; i < MAXPLAYERS; i++)
+			{
+				if (!playeringame[i])
+					continue;
+				players[i].starpostangle = 0;
+				players[i].starposttime = 0;
+				players[i].starpostx = 0;
+				players[i].starposty = 0;
+				players[i].starpostz = 0;
+				players[i].starpostnum = 0;
+			}
 		}
 		if (!countdowntimeup && (mapheaderinfo[gamemap-1]->levelflags & LF_NORELOAD))
 		{
-			INT32 i;
-
-			player->playerstate = PST_REBORN;
-
 			P_LoadThingsOnly();
 
-			P_ClearStarPost(player->starpostnum);
+			for (i = 0; i < MAXPLAYERS; i++)
+			{
+				if (!playeringame[i])
+					continue;
+				players[i].playerstate = PST_REBORN;
+				P_ClearStarPost(players[i].starpostnum);
+			}
 
 			// Do a wipe
 			wipegamestate = -1;
 
-			if (player->starposttime)
-				starpost = true;
-
 			if (camera.chase)
 				P_ResetCamera(&players[displayplayer], &camera);
 			if (camera2.chase && splitscreen)
@@ -2536,7 +2625,7 @@ void G_DoReborn(INT32 playernum)
 
 			// clear cmd building stuff
 			memset(gamekeydown, 0, sizeof (gamekeydown));
-			for (i = 0;i < JOYAXISSET; i++)
+			for (i = 0; i < JOYAXISSET; i++)
 			{
 				joyxmove[i] = joyymove[i] = 0;
 				joy2xmove[i] = joy2ymove[i] = 0;
@@ -2548,31 +2637,45 @@ void G_DoReborn(INT32 playernum)
 			CON_ClearHUD();
 
 			// Starpost support
-			G_SpawnPlayer(playernum, starpost);
+			for (i = 0; i < MAXPLAYERS; i++)
+			{
+				if (!playeringame[i])
+					continue;
+				G_SpawnPlayer(i, (players[i].starposttime));
+			}
 
-			if (botingame)
-			{ // Bots respawn next to their master.
-				players[secondarydisplayplayer].playerstate = PST_REBORN;
-				G_SpawnPlayer(secondarydisplayplayer, false);
+			// restore time in netgame (see also p_setup.c)
+			if ((netgame || multiplayer) && gametype == GT_COOP && cv_coopstarposts.value == 2)
+			{
+				// is this a hack? maybe
+				tic_t maxstarposttime = 0;
+				for (i = 0; i < MAXPLAYERS; i++)
+				{
+					if (playeringame[i] && players[i].starposttime > maxstarposttime)
+						maxstarposttime = players[i].starposttime;
+				}
+				leveltime = maxstarposttime;
 			}
 		}
 		else
-#ifdef HAVE_BLUA
 		{
+#ifdef HAVE_BLUA
 			LUAh_MapChange();
 #endif
 			G_DoLoadLevel(true);
-#ifdef HAVE_BLUA
+			return;
 		}
-#endif
 	}
 	else
 	{
 		// respawn at the start
 		mobj_t *oldmo = NULL;
 
-		if (player->starposttime)
-			starpost = true;
+		// Not resetting map, so return to level music
+		if (!countdown2
+		&& player->lives <= 0
+		&& cv_cooplives.value == 1) // not allowed for life steal because no way to come back from zero group lives without addons, which should call this anyways
+			P_RestoreMultiMusic(player);
 
 		// first dissasociate the corpse
 		if (player->mo)
@@ -2582,7 +2685,7 @@ void G_DoReborn(INT32 playernum)
 			P_RemoveMobj(player->mo);
 		}
 
-		G_SpawnPlayer(playernum, starpost);
+		G_SpawnPlayer(playernum, (player->starposttime));
 		if (oldmo)
 			G_ChangePlayerReferences(oldmo, players[playernum].mo);
 	}
@@ -2590,10 +2693,49 @@ void G_DoReborn(INT32 playernum)
 
 void G_AddPlayer(INT32 playernum)
 {
+	INT32 countplayers = 0, notexiting = 0;
+
 	player_t *p = &players[playernum];
 
+	// Go through the current players and make sure you have the latest starpost set
+	if (G_PlatformGametype() && (netgame || multiplayer))
+	{
+		INT32 i;
+		for (i = 0; i < MAXPLAYERS; i++)
+		{
+			if (!playeringame[i])
+				continue;
+
+			if (players[i].bot) // ignore dumb, stupid tails
+				continue;
+
+			countplayers++;
+
+			if (!players->exiting)
+				notexiting++;
+
+			if (!(cv_coopstarposts.value && (gametype == GT_COOP) && (p->starpostnum < players[i].starpostnum)))
+				continue;
+
+			p->starposttime = players[i].starposttime;
+			p->starpostx = players[i].starpostx;
+			p->starposty = players[i].starposty;
+			p->starpostz = players[i].starpostz;
+			p->starpostangle = players[i].starpostangle;
+			p->starpostnum = players[i].starpostnum;
+		}
+	}
+
 	p->jointime = 0;
 	p->playerstate = PST_REBORN;
+
+	p->height = mobjinfo[MT_PLAYER].height;
+
+	if (G_GametypeUsesLives() || ((netgame || multiplayer) && gametype == GT_COOP))
+		p->lives = cv_startinglives.value;
+
+	if (countplayers && !notexiting)
+		P_DoPlayerExit(p);
 }
 
 void G_ExitLevel(void)
@@ -2615,7 +2757,7 @@ void G_ExitLevel(void)
 			CONS_Printf(M_GetText("The round has ended.\n"));
 
 		// Remove CEcho text on round end.
-		HU_DoCEcho("");
+		HU_ClearCEcho();
 	}
 }
 
@@ -2763,7 +2905,6 @@ static INT16 RandMap(INT16 tolflags, INT16 pprevmap)
 static void G_DoCompleted(void)
 {
 	INT32 i;
-	boolean gottoken = false;
 
 	tokenlist = 0; // Reset the list
 
@@ -2849,10 +2990,9 @@ static void G_DoCompleted(void)
 	if (nextmap >= 1100-1 && nextmap <= 1102-1 && (gametype == GT_RACE || gametype == GT_COMPETITION))
 		nextmap = (INT16)(spstage_start-1);
 
-	if (gametype == GT_COOP && token)
+	if ((gottoken = (gametype == GT_COOP && token)))
 	{
 		token--;
-		gottoken = true;
 
 		if (!(emeralds & EMERALD1))
 			nextmap = (INT16)(sstage_start - 1); // Special Stage 1
@@ -2911,7 +3051,7 @@ void G_AfterIntermission(void)
 		if (nextmap < 1100-1)
 			G_NextLevel();
 		else
-			Y_EndGame();
+			G_EndGame();
 	}
 }
 
@@ -2997,6 +3137,38 @@ static void G_DoContinued(void)
 	gameaction = ga_nothing;
 }
 
+//
+// G_EndGame (formerly Y_EndGame)
+// Frankly this function fits better in g_game.c than it does in y_inter.c
+//
+// ...Gee, (why) end the game?
+// Because G_AfterIntermission and F_EndCutscene would
+// both do this exact same thing *in different ways* otherwise,
+// which made it so that you could only unlock Ultimate mode
+// if you had a cutscene after the final level and crap like that.
+// This function simplifies it so only one place has to be updated
+// when something new is added.
+void G_EndGame(void)
+{
+	// Only do evaluation and credits in coop games.
+	if (gametype == GT_COOP)
+	{
+		if (nextmap == 1102-1) // end game with credits
+		{
+			F_StartCredits();
+			return;
+		}
+		if (nextmap == 1101-1) // end game with evaluation
+		{
+			F_StartGameEvaluation();
+			return;
+		}
+	}
+
+	// 1100 or competitive multiplayer, so go back to title screen.
+	D_StartTitle();
+}
+
 //
 // G_LoadGameSettings
 //
@@ -3561,7 +3733,7 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 
 			if (netgame || multiplayer)
 			{
-				if (!FLS || (players[i].lives < cv_startinglives.value))
+				if (!FLS || (players[i].lives < 1))
 					players[i].lives = cv_startinglives.value;
 				players[i].continues = 0;
 			}
@@ -3619,7 +3791,6 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 	mapmusflags |= MUSIC_RELOADRESET;
 
 	ultimatemode = pultmode;
-	playerdeadview = false;
 	automapactive = false;
 	imcontinuing = false;
 
@@ -3647,6 +3818,9 @@ char *G_BuildMapTitle(INT32 mapnum)
 {
 	char *title = NULL;
 
+	if (!mapheaderinfo[mapnum-1])
+		P_AllocMapHeader(mapnum-1);
+
 	if (strcmp(mapheaderinfo[mapnum-1]->lvlttl, ""))
 	{
 		size_t len = 1;
@@ -3880,7 +4054,7 @@ void G_GhostAddColor(ghostcolor_t color)
 	ghostext.color = (UINT8)color;
 }
 
-void G_GhostAddScale(UINT16 scale)
+void G_GhostAddScale(fixed_t scale)
 {
 	if (!demorecording || !(demoflags & DF_GHOST))
 		return;
@@ -4361,7 +4535,7 @@ void G_GhostTicker(void)
 			g->mo->color += abs( ( (signed)( (unsigned)leveltime >> 1 ) % 9) - 4);
 			break;
 		case GHC_INVINCIBLE: // Mario invincibility (P_CheckInvincibilityTimer)
-			g->mo->color = (UINT8)(SKINCOLOR_RED + (leveltime % (MAXSKINCOLORS - SKINCOLOR_RED))); // Passes through all saturated colours
+			g->mo->color = (UINT8)(SKINCOLOR_RUBY + (leveltime % (MAXSKINCOLORS - SKINCOLOR_RUBY))); // Passes through all saturated colours
 			break;
 		default:
 			break;
diff --git a/src/g_game.h b/src/g_game.h
index bfde7698a367de43067de3a6b8c9ac5f1a861c2a..72a6f3d6e56e2e73f909134bbdfe39c76068fdc2 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -56,6 +56,9 @@ extern INT16 rw_maximums[NUM_WEAPONS];
 // used in game menu
 extern consvar_t cv_crosshair, cv_crosshair2;
 extern consvar_t cv_invertmouse, cv_alwaysfreelook, cv_mousemove;
+extern consvar_t cv_invertmouse2, cv_alwaysfreelook2, cv_mousemove2;
+extern consvar_t cv_useranalog, cv_useranalog2;
+extern consvar_t cv_analog, cv_analog2;
 extern consvar_t cv_sideaxis,cv_turnaxis,cv_moveaxis,cv_lookaxis,cv_fireaxis,cv_firenaxis;
 extern consvar_t cv_sideaxis2,cv_turnaxis2,cv_moveaxis2,cv_lookaxis2,cv_fireaxis2,cv_firenaxis2;
 extern consvar_t cv_ghost_bestscore, cv_ghost_besttime, cv_ghost_bestrings, cv_ghost_last, cv_ghost_guest;
@@ -139,7 +142,7 @@ void G_GhostAddSpin(void);
 void G_GhostAddRev(void);
 void G_GhostAddColor(ghostcolor_t color);
 void G_GhostAddFlip(void);
-void G_GhostAddScale(UINT16 scale);
+void G_GhostAddScale(fixed_t scale);
 void G_GhostAddHit(mobj_t *victim);
 void G_WriteGhostTic(mobj_t *ghost);
 void G_ConsGhostTic(void);
@@ -171,6 +174,7 @@ void G_NextLevel(void);
 void G_Continue(void);
 void G_UseContinue(void);
 void G_AfterIntermission(void);
+void G_EndGame(void); // moved from y_inter.c/h and renamed
 
 void G_Ticker(boolean run);
 boolean G_Responder(event_t *ev);
diff --git a/src/g_input.c b/src/g_input.c
index a538df06cda1fc049da90892777f18da25cab994..36b8373aa7c9ef5c7eb39f53cdeb9206186d580f 100644
--- a/src/g_input.c
+++ b/src/g_input.c
@@ -977,8 +977,6 @@ static const char *gamecontrolname[num_gamecontrols] =
 	"tossflag",
 	"use",
 	"camtoggle",
-	"camleft",
-	"camright",
 	"camreset",
 	"lookup",
 	"lookdown",
@@ -1074,8 +1072,6 @@ void G_Controldefault(void)
 	gamecontrol[gc_use        ][0] = KEY_JOY1+1; //B
 	gamecontrol[gc_use        ][1] = '.';
 	gamecontrol[gc_camtoggle  ][1] = ',';
-	gamecontrol[gc_camleft    ][0] = 'o';
-	gamecontrol[gc_camright   ][0] = 'p';
 	gamecontrol[gc_camreset   ][0] = 'c';
 	gamecontrol[gc_lookup     ][0] = KEY_PGUP;
 	gamecontrol[gc_lookdown   ][0] = KEY_PGDN;
@@ -1178,8 +1174,6 @@ void G_Controldefault(void)
 	gamecontrol[gc_tossflag   ][0] = '\'';
 	gamecontrol[gc_use        ][0] = KEY_LSHIFT;
 	gamecontrol[gc_camtoggle  ][0] = 'v';
-	gamecontrol[gc_camleft    ][0] = '[';
-	gamecontrol[gc_camright   ][0] = ']';
 	gamecontrol[gc_camreset   ][0] = 'r';
 	gamecontrol[gc_lookup     ][0] = KEY_UPARROW;
 	gamecontrol[gc_lookdown   ][0] = KEY_DOWNARROW;
diff --git a/src/g_input.h b/src/g_input.h
index d65339321060f11f41b405efe43289381db55c16..8083974389d2eeda1587f566d85fe18015ae6fbc 100644
--- a/src/g_input.h
+++ b/src/g_input.h
@@ -105,8 +105,6 @@ typedef enum
 	gc_tossflag,
 	gc_use,
 	gc_camtoggle,
-	gc_camleft,
-	gc_camright,
 	gc_camreset,
 	gc_lookup,
 	gc_lookdown,
@@ -126,6 +124,8 @@ typedef enum
 
 // mouse values are used once
 extern consvar_t cv_mousesens, cv_mouseysens;
+extern consvar_t cv_mousesens2, cv_mouseysens2;
+extern consvar_t cv_controlperkey;
 
 extern INT32 mousex, mousey;
 extern INT32 mlooky; //mousey with mlookSensitivity
diff --git a/src/hardware/hw_bsp.c b/src/hardware/hw_bsp.c
index 17eb8761c9650eea7ca4b87d6dc157a3dd078979..fa5bce308eff82bf52021c98bc19e1797ceb87ee 100644
--- a/src/hardware/hw_bsp.c
+++ b/src/hardware/hw_bsp.c
@@ -564,8 +564,6 @@ static inline void HWR_SubsecPoly(INT32 num, poly_t *poly)
 	subsector_t *sub;
 	seg_t *lseg;
 
-	sscount++;
-
 	sub = &subsectors[num];
 	count = sub->numlines;
 	lseg = &segs[sub->firstline];
@@ -880,8 +878,8 @@ static void AdjustSegs(void)
 		count = subsectors[i].numlines;
 		lseg = &segs[subsectors[i].firstline];
 		p = extrasubsectors[i].planepoly;
-		if (!p)
-			continue;
+		//if (!p)
+			//continue;
 		for (; count--; lseg++)
 		{
 			float distv1,distv2,tmp;
@@ -894,29 +892,31 @@ static void AdjustSegs(void)
 				continue;
 #endif
 
-			for (j = 0; j < p->numpts; j++)
-			{
-				distv1 = p->pts[j].x - FIXED_TO_FLOAT(lseg->v1->x);
-				tmp    = p->pts[j].y - FIXED_TO_FLOAT(lseg->v1->y);
-				distv1 = distv1*distv1+tmp*tmp;
-				if (distv1 <= nearv1)
-				{
-					v1found = j;
-					nearv1 = distv1;
-				}
-				// the same with v2
-				distv2 = p->pts[j].x - FIXED_TO_FLOAT(lseg->v2->x);
-				tmp    = p->pts[j].y - FIXED_TO_FLOAT(lseg->v2->y);
-				distv2 = distv2*distv2+tmp*tmp;
-				if (distv2 <= nearv2)
+			if (p) {
+				for (j = 0; j < p->numpts; j++)
 				{
-					v2found = j;
-					nearv2 = distv2;
+					distv1 = p->pts[j].x - FIXED_TO_FLOAT(lseg->v1->x);
+					tmp    = p->pts[j].y - FIXED_TO_FLOAT(lseg->v1->y);
+					distv1 = distv1*distv1+tmp*tmp;
+					if (distv1 <= nearv1)
+					{
+						v1found = j;
+						nearv1 = distv1;
+					}
+					// the same with v2
+					distv2 = p->pts[j].x - FIXED_TO_FLOAT(lseg->v2->x);
+					tmp    = p->pts[j].y - FIXED_TO_FLOAT(lseg->v2->y);
+					distv2 = distv2*distv2+tmp*tmp;
+					if (distv2 <= nearv2)
+					{
+						v2found = j;
+						nearv2 = distv2;
+					}
 				}
 			}
-			if (nearv1 <= NEARDIST*NEARDIST)
+			if (p && nearv1 <= NEARDIST*NEARDIST)
 				// share vertice with segs
-				lseg->v1 = (vertex_t *)&(p->pts[v1found]);
+				lseg->pv1 = &(p->pts[v1found]);
 			else
 			{
 				// BP: here we can do better, using PointInSeg and compute
@@ -927,24 +927,24 @@ static void AdjustSegs(void)
 				polyvertex_t *pv = HWR_AllocVertex();
 				pv->x = FIXED_TO_FLOAT(lseg->v1->x);
 				pv->y = FIXED_TO_FLOAT(lseg->v1->y);
-				lseg->v1 = (vertex_t *)pv;
+				lseg->pv1 = pv;
 			}
-			if (nearv2 <= NEARDIST*NEARDIST)
-				lseg->v2 = (vertex_t *)&(p->pts[v2found]);
+			if (p && nearv2 <= NEARDIST*NEARDIST)
+				lseg->pv2 = &(p->pts[v2found]);
 			else
 			{
 				polyvertex_t *pv = HWR_AllocVertex();
 				pv->x = FIXED_TO_FLOAT(lseg->v2->x);
 				pv->y = FIXED_TO_FLOAT(lseg->v2->y);
-				lseg->v2 = (vertex_t *)pv;
+				lseg->pv2 = pv;
 			}
 
 			// recompute length
 			{
 				float x,y;
-				x = ((polyvertex_t *)lseg->v2)->x - ((polyvertex_t *)lseg->v1)->x
+				x = ((polyvertex_t *)lseg->pv2)->x - ((polyvertex_t *)lseg->pv1)->x
 					+ FIXED_TO_FLOAT(FRACUNIT/2);
-				y = ((polyvertex_t *)lseg->v2)->y - ((polyvertex_t *)lseg->v1)->y
+				y = ((polyvertex_t *)lseg->pv2)->y - ((polyvertex_t *)lseg->pv1)->y
 					+ FIXED_TO_FLOAT(FRACUNIT/2);
 				lseg->flength = (float)hypot(x, y);
 				// BP: debug see this kind of segs
diff --git a/src/hardware/hw_clip.c b/src/hardware/hw_clip.c
new file mode 100644
index 0000000000000000000000000000000000000000..8b01cabd5ed37696652b931548eed18de3aea344
--- /dev/null
+++ b/src/hardware/hw_clip.c
@@ -0,0 +1,465 @@
+/* Emacs style mode select   -*- C++ -*-
+ *-----------------------------------------------------------------------------
+ *
+ *
+ *  PrBoom: a Doom port merged with LxDoom and LSDLDoom
+ *  based on BOOM, a modified and improved DOOM engine
+ *  Copyright (C) 1999 by
+ *  id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman
+ *  Copyright (C) 1999-2000 by
+ *  Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze
+ *  Copyright 2005, 2006 by
+ *  Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU General Public License
+ *  as published by the Free Software Foundation; either version 2
+ *  of the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ *  02111-1307, USA.
+ *
+ * DESCRIPTION:
+ *
+ *---------------------------------------------------------------------
+ */
+
+/*
+ *
+ ** gl_clipper.cpp
+ **
+ ** Handles visibility checks.
+ ** Loosely based on the JDoom clipper.
+ **
+ **---------------------------------------------------------------------------
+ ** Copyright 2003 Tim Stump
+ ** All rights reserved.
+ **
+ ** Redistribution and use in source and binary forms, with or without
+ ** modification, are permitted provided that the following conditions
+ ** are met:
+ **
+ ** 1. Redistributions of source code must retain the above copyright
+ **    notice, this list of conditions and the following disclaimer.
+ ** 2. Redistributions in binary form must reproduce the above copyright
+ **    notice, this list of conditions and the following disclaimer in the
+ **    documentation and/or other materials provided with the distribution.
+ ** 3. The name of the author may not be used to endorse or promote products
+ **    derived from this software without specific prior written permission.
+ **
+ ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ **---------------------------------------------------------------------------
+ **
+ */
+
+#include <math.h>
+#include "../v_video.h"
+#include "hw_clip.h"
+#include "hw_glob.h"
+#include "../r_state.h"
+#include "../tables.h"
+#include "r_opengl/r_opengl.h"
+
+#ifdef HAVE_SPHEREFRUSTRUM
+static GLdouble viewMatrix[16];
+static GLdouble projMatrix[16];
+float frustum[6][4];
+#endif
+
+typedef struct clipnode_s
+	{
+		struct clipnode_s *prev, *next;
+		angle_t start, end;
+	} clipnode_t;
+
+clipnode_t *freelist;
+clipnode_t *clipnodes;
+clipnode_t *cliphead;
+
+static clipnode_t * gld_clipnode_GetNew(void);
+static clipnode_t * gld_clipnode_NewRange(angle_t start, angle_t end);
+static boolean gld_clipper_IsRangeVisible(angle_t startAngle, angle_t endAngle);
+static void gld_clipper_AddClipRange(angle_t start, angle_t end);
+static void gld_clipper_RemoveRange(clipnode_t * range);
+static void gld_clipnode_Free(clipnode_t *node);
+
+static clipnode_t * gld_clipnode_GetNew(void)
+{
+	if (freelist)
+	{
+		clipnode_t * p = freelist;
+		freelist = p->next;
+		return p;
+	}
+	else
+	{
+		return (clipnode_t*)malloc(sizeof(clipnode_t));
+	}
+}
+
+static clipnode_t * gld_clipnode_NewRange(angle_t start, angle_t end)
+{
+	clipnode_t * c = gld_clipnode_GetNew();
+	c->start = start;
+	c->end = end;
+	c->next = c->prev=NULL;
+	return c;
+}
+
+boolean gld_clipper_SafeCheckRange(angle_t startAngle, angle_t endAngle)
+{
+	if(startAngle > endAngle)
+	{
+		return (gld_clipper_IsRangeVisible(startAngle, ANGLE_MAX) || gld_clipper_IsRangeVisible(0, endAngle));
+	}
+
+	return gld_clipper_IsRangeVisible(startAngle, endAngle);
+}
+
+static boolean gld_clipper_IsRangeVisible(angle_t startAngle, angle_t endAngle)
+{
+	clipnode_t *ci;
+	ci = cliphead;
+
+	if (endAngle == 0 && ci && ci->start == 0)
+		return false;
+
+	while (ci != NULL && ci->start < endAngle)
+	{
+		if (startAngle >= ci->start && endAngle <= ci->end)
+		{
+			return false;
+		}
+		ci = ci->next;
+	}
+
+	return true;
+}
+
+static void gld_clipnode_Free(clipnode_t *node)
+{
+	node->next = freelist;
+	freelist = node;
+}
+
+static void gld_clipper_RemoveRange(clipnode_t *range)
+{
+	if (range == cliphead)
+	{
+		cliphead = cliphead->next;
+	}
+	else
+	{
+		if (range->prev)
+		{
+			range->prev->next = range->next;
+		}
+		if (range->next)
+		{
+			range->next->prev = range->prev;
+		}
+	}
+
+	gld_clipnode_Free(range);
+}
+
+void gld_clipper_SafeAddClipRange(angle_t startangle, angle_t endangle)
+{
+	if(startangle > endangle)
+	{
+		// The range has to added in two parts.
+		gld_clipper_AddClipRange(startangle, ANGLE_MAX);
+		gld_clipper_AddClipRange(0, endangle);
+	}
+	else
+	{
+		// Add the range as usual.
+		gld_clipper_AddClipRange(startangle, endangle);
+	}
+}
+
+static void gld_clipper_AddClipRange(angle_t start, angle_t end)
+{
+	clipnode_t *node, *temp, *prevNode, *node2, *delnode;
+
+	if (cliphead)
+	{
+		//check to see if range contains any old ranges
+		node = cliphead;
+		while (node != NULL && node->start < end)
+		{
+			if (node->start >= start && node->end <= end)
+			{
+				temp = node;
+				node = node->next;
+				gld_clipper_RemoveRange(temp);
+			}
+			else
+			{
+				if (node->start <= start && node->end >= end)
+				{
+					return;
+				}
+				else
+				{
+					node = node->next;
+				}
+			}
+		}
+
+		//check to see if range overlaps a range (or possibly 2)
+		node = cliphead;
+		while (node != NULL && node->start <= end)
+		{
+			if (node->end >= start)
+			{
+				// we found the first overlapping node
+				if (node->start > start)
+				{
+					// the new range overlaps with this node's start point
+					node->start = start;
+				}
+				if (node->end < end)
+				{
+					node->end = end;
+				}
+
+				node2 = node->next;
+				while (node2 && node2->start <= node->end)
+				{
+					if (node2->end > node->end)
+					{
+						node->end = node2->end;
+					}
+
+					delnode = node2;
+					node2 = node2->next;
+					gld_clipper_RemoveRange(delnode);
+				}
+				return;
+			}
+			node = node->next;
+		}
+
+		//just add range
+		node = cliphead;
+		prevNode = NULL;
+		temp = gld_clipnode_NewRange(start, end);
+		while (node != NULL && node->start < end)
+		{
+			prevNode = node;
+			node = node->next;
+		}
+		temp->next = node;
+		if (node == NULL)
+		{
+			temp->prev = prevNode;
+			if (prevNode)
+			{
+				prevNode->next = temp;
+			}
+			if (!cliphead)
+			{
+				cliphead = temp;
+			}
+		}
+		else
+		{
+			if (node == cliphead)
+			{
+				cliphead->prev = temp;
+				cliphead = temp;
+			}
+			else
+			{
+				temp->prev = prevNode;
+				prevNode->next = temp;
+				node->prev = temp;
+			}
+		}
+	}
+	else
+	{
+		temp = gld_clipnode_NewRange(start, end);
+		cliphead = temp;
+		return;
+	}
+}
+
+void gld_clipper_Clear(void)
+{
+	clipnode_t *node = cliphead;
+	clipnode_t *temp;
+
+	while (node != NULL)
+	{
+		temp = node;
+		node = node->next;
+		gld_clipnode_Free(temp);
+	}
+
+	cliphead = NULL;
+}
+
+#define RMUL (1.6f/1.333333f)
+
+angle_t gld_FrustumAngle(void)
+{
+	double floatangle;
+	angle_t a1;
+
+	float tilt = (float)fabs(((double)(int)aimingangle) / ANG1);
+
+	// NEWCLIP TODO: SRB2CBTODO: make a global render_fov for this function
+
+	float render_fov = FIXED_TO_FLOAT(cv_grfov.value);
+	float render_fovratio = (float)BASEVIDWIDTH / (float)BASEVIDHEIGHT; // SRB2CBTODO: NEWCLIPTODO: Is this right?
+	float render_multiplier = 64.0f / render_fovratio / RMUL;
+
+	if (tilt > 90.0f)
+	{
+		tilt = 90.0f;
+	}
+
+	// If the pitch is larger than this you can look all around at a FOV of 90
+	if (abs(aimingangle) > 46 * ANG1)
+		return 0xffffffff;
+
+	// ok, this is a gross hack that barely works...
+	// but at least it doesn't overestimate too much...
+	floatangle = 2.0f + (45.0f + (tilt / 1.9f)) * (float)render_fov * 48.0f / render_multiplier / 90.0f;
+	a1 = ANG1 * (int)floatangle;
+	if (a1 >= ANGLE_180)
+		return 0xffffffff;
+	return a1;
+}
+
+// SRB2CB I don't think used any of this stuff, let's disable for now since SRB2 probably doesn't want it either
+// compiler complains about (p)glGetDoublev anyway, in case anyone wants this
+// only r_opengl.c can use the base gl funcs as it turns out, that's a problem for whoever wants sphere frustum checks
+// btw to renable define HAVE_SPHEREFRUSTRUM in hw_clip.h
+#ifdef HAVE_SPHEREFRUSTRUM
+//
+// gld_FrustrumSetup
+//
+
+#define CALCMATRIX(a, b, c, d, e, f, g, h)\
+(float)(viewMatrix[a] * projMatrix[b] + \
+viewMatrix[c] * projMatrix[d] + \
+viewMatrix[e] * projMatrix[f] + \
+viewMatrix[g] * projMatrix[h])
+
+#define NORMALIZE_PLANE(i)\
+t = (float)sqrt(\
+frustum[i][0] * frustum[i][0] + \
+frustum[i][1] * frustum[i][1] + \
+frustum[i][2] * frustum[i][2]); \
+frustum[i][0] /= t; \
+frustum[i][1] /= t; \
+frustum[i][2] /= t; \
+frustum[i][3] /= t
+
+void gld_FrustrumSetup(void)
+{
+	float t;
+	float clip[16];
+
+	pglGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
+	pglGetDoublev(GL_MODELVIEW_MATRIX, viewMatrix);
+
+	clip[0]  = CALCMATRIX(0, 0, 1, 4, 2, 8, 3, 12);
+	clip[1]  = CALCMATRIX(0, 1, 1, 5, 2, 9, 3, 13);
+	clip[2]  = CALCMATRIX(0, 2, 1, 6, 2, 10, 3, 14);
+	clip[3]  = CALCMATRIX(0, 3, 1, 7, 2, 11, 3, 15);
+
+	clip[4]  = CALCMATRIX(4, 0, 5, 4, 6, 8, 7, 12);
+	clip[5]  = CALCMATRIX(4, 1, 5, 5, 6, 9, 7, 13);
+	clip[6]  = CALCMATRIX(4, 2, 5, 6, 6, 10, 7, 14);
+	clip[7]  = CALCMATRIX(4, 3, 5, 7, 6, 11, 7, 15);
+
+	clip[8]  = CALCMATRIX(8, 0, 9, 4, 10, 8, 11, 12);
+	clip[9]  = CALCMATRIX(8, 1, 9, 5, 10, 9, 11, 13);
+	clip[10] = CALCMATRIX(8, 2, 9, 6, 10, 10, 11, 14);
+	clip[11] = CALCMATRIX(8, 3, 9, 7, 10, 11, 11, 15);
+
+	clip[12] = CALCMATRIX(12, 0, 13, 4, 14, 8, 15, 12);
+	clip[13] = CALCMATRIX(12, 1, 13, 5, 14, 9, 15, 13);
+	clip[14] = CALCMATRIX(12, 2, 13, 6, 14, 10, 15, 14);
+	clip[15] = CALCMATRIX(12, 3, 13, 7, 14, 11, 15, 15);
+
+	// Right plane
+	frustum[0][0] = clip[ 3] - clip[ 0];
+	frustum[0][1] = clip[ 7] - clip[ 4];
+	frustum[0][2] = clip[11] - clip[ 8];
+	frustum[0][3] = clip[15] - clip[12];
+	NORMALIZE_PLANE(0);
+
+	// Left plane
+	frustum[1][0] = clip[ 3] + clip[ 0];
+	frustum[1][1] = clip[ 7] + clip[ 4];
+	frustum[1][2] = clip[11] + clip[ 8];
+	frustum[1][3] = clip[15] + clip[12];
+	NORMALIZE_PLANE(1);
+
+	// Bottom plane
+	frustum[2][0] = clip[ 3] + clip[ 1];
+	frustum[2][1] = clip[ 7] + clip[ 5];
+	frustum[2][2] = clip[11] + clip[ 9];
+	frustum[2][3] = clip[15] + clip[13];
+	NORMALIZE_PLANE(2);
+
+	// Top plane
+	frustum[3][0] = clip[ 3] - clip[ 1];
+	frustum[3][1] = clip[ 7] - clip[ 5];
+	frustum[3][2] = clip[11] - clip[ 9];
+	frustum[3][3] = clip[15] - clip[13];
+	NORMALIZE_PLANE(3);
+
+	// Far plane
+	frustum[4][0] = clip[ 3] - clip[ 2];
+	frustum[4][1] = clip[ 7] - clip[ 6];
+	frustum[4][2] = clip[11] - clip[10];
+	frustum[4][3] = clip[15] - clip[14];
+	NORMALIZE_PLANE(4);
+
+	// Near plane
+	frustum[5][0] = clip[ 3] + clip[ 2];
+	frustum[5][1] = clip[ 7] + clip[ 6];
+	frustum[5][2] = clip[11] + clip[10];
+	frustum[5][3] = clip[15] + clip[14];
+	NORMALIZE_PLANE(5);
+}
+
+boolean gld_SphereInFrustum(float x, float y, float z, float radius)
+{
+	int p;
+
+	for (p = 0; p < 4; p++)
+	{
+		if (frustum[p][0] * x +
+			frustum[p][1] * y +
+			frustum[p][2] * z +
+			frustum[p][3] <= -radius)
+		{
+			return false;
+		}
+	}
+	return true;
+}
+#endif
diff --git a/src/hardware/hw_clip.h b/src/hardware/hw_clip.h
new file mode 100644
index 0000000000000000000000000000000000000000..3ba26e5e56f3dc053adc068078dc11859e8a86df
--- /dev/null
+++ b/src/hardware/hw_clip.h
@@ -0,0 +1,24 @@
+/*
+ *  hw_clip.h
+ *  SRB2CB
+ *
+ *  PrBoom's OpenGL clipping
+ *
+ *
+ */
+
+// OpenGL BSP clipping
+#include "../doomdef.h"
+#include "../tables.h"
+#include "../doomtype.h"
+
+//#define HAVE_SPHEREFRUSTRUM // enable if you want gld_SphereInFrustum and related code
+
+boolean gld_clipper_SafeCheckRange(angle_t startAngle, angle_t endAngle);
+void gld_clipper_SafeAddClipRange(angle_t startangle, angle_t endangle);
+void gld_clipper_Clear(void);
+angle_t gld_FrustumAngle(void);
+#ifdef HAVE_SPHEREFRUSTRUM
+void gld_FrustrumSetup(void);
+boolean gld_SphereInFrustum(float x, float y, float z, float radius);
+#endif
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index a00bf3aeb9f706a96182d33067de191865eb629b..9c912495ac12f391bd513f64d5a5f483757c7c1d 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -789,7 +789,7 @@ boolean HWR_Screenshot(const char *lbmname)
 	HWD.pfnReadRect(0, 0, vid.width, vid.height, vid.width * 3, (void *)buf);
 
 #ifdef USE_PNG
-	ret = M_SavePNG(lbmname, buf, vid.width, vid.height, NULL);
+	ret = M_SavePNG(lbmname, buf, vid.width, vid.height, false);
 #else
 	ret = saveTGA(lbmname, buf, vid.width, vid.height);
 #endif
diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h
index 94eef1d3e46e95f825f7ac89088cbb6674636225..5d1a81d4f4318826e3ce9c3b8d176a3b2baa00f5 100644
--- a/src/hardware/hw_glob.h
+++ b/src/hardware/hw_glob.h
@@ -78,6 +78,7 @@ typedef struct gr_vissprite_s
    //Hurdler: 25/04/2000: now support colormap in hardware mode
 	UINT8 *colormap;
 	INT32 dispoffset; // copy of info->dispoffset, affects ordering but not drawing
+	float z1, z2;
 } gr_vissprite_t;
 
 // --------
diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c
index a49a788e66c59713c6a14038fb36a7e2b2ef3528..267666749baf363ec02652fdb65f342a96f0d8f0 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -226,8 +226,7 @@ light_t *t_lspr[NUMSPRITES] =
 	// Collectible Items
 	&lspr[NOLIGHT],     // SPR_RING
 	&lspr[NOLIGHT],     // SPR_TRNG
-	&lspr[NOLIGHT],     // SPR_EMMY
-	&lspr[BLUEBALL_L],     // SPR_TOKE
+	&lspr[NOLIGHT],     // SPR_TOKE
 	&lspr[REDBALL_L],     // SPR_RFLG
 	&lspr[BLUEBALL_L],     // SPR_BFLG
 	&lspr[NOLIGHT],     // SPR_NWNG
@@ -243,6 +242,8 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_SPIK
 	&lspr[NOLIGHT],     // SPR_SFLM
 	&lspr[NOLIGHT],     // SPR_USPK
+	&lspr[NOLIGHT],     // SPR_WSPK
+	&lspr[NOLIGHT],     // SPR_WSPB
 	&lspr[NOLIGHT],     // SPR_STPT
 	&lspr[NOLIGHT],     // SPR_BMNE
 
@@ -293,6 +294,12 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_FWR4
 	&lspr[NOLIGHT],     // SPR_BUS1
 	&lspr[NOLIGHT],     // SPR_BUS2
+	// Trees (both GFZ and misc)
+	&lspr[NOLIGHT],     // SPR_TRE1
+	&lspr[NOLIGHT],     // SPR_TRE2
+	&lspr[NOLIGHT],     // SPR_TRE3
+	&lspr[NOLIGHT],     // SPR_TRE4
+	&lspr[NOLIGHT],     // SPR_TRE5
 
 	// Techno Hill Scenery
 	&lspr[NOLIGHT],     // SPR_THZP
@@ -316,6 +323,10 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_BMCH
 	&lspr[NOLIGHT],     // SPR_SMCE
 	&lspr[NOLIGHT],     // SPR_BMCE
+	&lspr[NOLIGHT],     // SPR_YSPB
+	&lspr[NOLIGHT],     // SPR_RSPB
+	&lspr[REDBALL_L],   // SPR_SFBR
+	&lspr[REDBALL_L],   // SPR_BFBR
 
 	// Arid Canyon Scenery
 	&lspr[NOLIGHT],     // SPR_BTBL
@@ -334,6 +345,8 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_XMS1
 	&lspr[NOLIGHT],     // SPR_XMS2
 	&lspr[NOLIGHT],     // SPR_XMS3
+	&lspr[NOLIGHT],     // SPR_XMS4
+	&lspr[NOLIGHT],     // SPR_XMS5
 
 	// Botanic Serenity Scenery
 	&lspr[NOLIGHT],     // SPR_BSZ1
@@ -345,13 +358,9 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_BSZ7
 	&lspr[NOLIGHT],     // SPR_BSZ8
 
-	// Stalagmites
+	// Misc Scenery
 	&lspr[NOLIGHT],     // SPR_STLG
-
-	// Disco Ball
 	&lspr[NOLIGHT],     // SPR_DBAL
-
-	// ATZ Red Crystal
 	&lspr[NOLIGHT],     // SPR_RCRY
 
 	// Powerup Indicators
@@ -396,8 +405,11 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_SPRB Graue
 	&lspr[NOLIGHT],     // SPR_YSPR
 	&lspr[NOLIGHT],     // SPR_RSPR
+	&lspr[NOLIGHT],     // SPR_SSWY
+	&lspr[NOLIGHT],     // SPR_SSWR
+	&lspr[NOLIGHT],     // SPR_SSWB
 
-	// Environmentals Effects
+	// Environmental Effects
 	&lspr[NOLIGHT],     // SPR_RAIN
 	&lspr[NOLIGHT],     // SPR_SNO1
 	&lspr[NOLIGHT],     // SPR_SPLH
@@ -405,6 +417,8 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_SMOK
 	&lspr[NOLIGHT],     // SPR_BUBL
 	&lspr[RINGLIGHT_L], // SPR_WZAP
+	&lspr[NOLIGHT],     // SPR_DUST
+	&lspr[NOLIGHT],     // SPR_FPRT
 	&lspr[SUPERSPARK_L], // SPR_TFOG
 	&lspr[NIGHTSLIGHT_L],     // SPR_SEED // Sonic CD flower seed
 	&lspr[NOLIGHT],     // SPR_PRTL
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index ac4c896bf29f2f52a87ce67cefa5d4b8c5cabb7a..f8ac272a4585d979bbe7b43b17fafcf0dfdcadf7 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -44,6 +44,10 @@
 #endif
 #include "hw_md2.h"
 
+#ifdef NEWCLIP
+#include "hw_clip.h"
+#endif
+
 #define R_FAKEFLOORS
 #define HWPRECIP
 #define SORTING
@@ -99,8 +103,9 @@ CV_PossibleValue_t granisotropicmode_cons_t[] = {{1, "MIN"}, {16, "MAX"}, {0, NU
 boolean drawsky = true;
 
 // needs fix: walls are incorrectly clipped one column less
+#ifndef NEWCLIP
 static consvar_t cv_grclipwalls = {"gr_clipwalls", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-
+#endif
 //development variables for diverse uses
 static consvar_t cv_gralpha = {"gr_alpha", "160", 0, CV_Unsigned, NULL, 0, NULL, NULL, 0, 0, NULL};
 static consvar_t cv_grbeta = {"gr_beta", "0", 0, CV_Unsigned, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -323,9 +328,6 @@ static angle_t gr_xtoviewangle[MAXVIDWIDTH+1];
 // test change fov when looking up/down but bsp projection messup :(
 //#define NOCRAPPYMLOOK
 
-/// \note crappy
-#define drawtextured true
-
 // base values set at SetViewSize
 static float gr_basecentery;
 
@@ -641,13 +643,13 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 		{
 			scrollx = FIXED_TO_FLOAT(FOFsector->floor_xoffs)/fflatsize;
 			scrolly = FIXED_TO_FLOAT(FOFsector->floor_yoffs)/fflatsize;
-			angle = FOFsector->floorpic_angle>>ANGLETOFINESHIFT;
+			angle = FOFsector->floorpic_angle;
 		}
 		else // it's a ceiling
 		{
 			scrollx = FIXED_TO_FLOAT(FOFsector->ceiling_xoffs)/fflatsize;
 			scrolly = FIXED_TO_FLOAT(FOFsector->ceiling_yoffs)/fflatsize;
-			angle = FOFsector->ceilingpic_angle>>ANGLETOFINESHIFT;
+			angle = FOFsector->ceilingpic_angle;
 		}
 	}
 	else if (gr_frontsector)
@@ -656,25 +658,19 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 		{
 			scrollx = FIXED_TO_FLOAT(gr_frontsector->floor_xoffs)/fflatsize;
 			scrolly = FIXED_TO_FLOAT(gr_frontsector->floor_yoffs)/fflatsize;
-			angle = gr_frontsector->floorpic_angle>>ANGLETOFINESHIFT;
+			angle = gr_frontsector->floorpic_angle;
 		}
 		else // it's a ceiling
 		{
 			scrollx = FIXED_TO_FLOAT(gr_frontsector->ceiling_xoffs)/fflatsize;
 			scrolly = FIXED_TO_FLOAT(gr_frontsector->ceiling_yoffs)/fflatsize;
-			angle = gr_frontsector->ceilingpic_angle>>ANGLETOFINESHIFT;
+			angle = gr_frontsector->ceilingpic_angle;
 		}
 	}
 
 	if (angle) // Only needs to be done if there's an altered angle
 	{
-
-		// This needs to be done so that it scrolls in a different direction after rotation like software
-		tempxsow = FLOAT_TO_FIXED(scrollx);
-		tempytow = FLOAT_TO_FIXED(scrolly);
-		scrollx = (FIXED_TO_FLOAT(FixedMul(tempxsow, FINECOSINE(angle)) - FixedMul(tempytow, FINESINE(angle))));
-		scrolly = (FIXED_TO_FLOAT(FixedMul(tempxsow, FINESINE(angle)) + FixedMul(tempytow, FINECOSINE(angle))));
-
+		angle = InvAngle(angle)>>ANGLETOFINESHIFT;
 		// This needs to be done so everything aligns after rotation
 		// It would be done so that rotation is done, THEN the translation, but I couldn't get it to rotate AND scroll like software does
 		tempxsow = FLOAT_TO_FIXED(flatxref);
@@ -687,7 +683,7 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 	{
 		// Hurdler: add scrolling texture on floor/ceiling
 		v3d->sow = (float)((pv->x / fflatsize) - flatxref + scrollx);
-		v3d->tow = (float)(flatyref - (pv->y / fflatsize) + scrolly);
+		v3d->tow = (float)(-(pv->y / fflatsize) + flatyref + scrolly);
 
 		//v3d->sow = (float)(pv->x / fflatsize);
 		//v3d->tow = (float)(pv->y / fflatsize);
@@ -698,7 +694,7 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 			tempxsow = FLOAT_TO_FIXED(v3d->sow);
 			tempytow = FLOAT_TO_FIXED(v3d->tow);
 			v3d->sow = (FIXED_TO_FLOAT(FixedMul(tempxsow, FINECOSINE(angle)) - FixedMul(tempytow, FINESINE(angle))));
-			v3d->tow = (FIXED_TO_FLOAT(-FixedMul(tempxsow, FINESINE(angle)) - FixedMul(tempytow, FINECOSINE(angle))));
+			v3d->tow = (FIXED_TO_FLOAT(FixedMul(tempxsow, FINESINE(angle)) + FixedMul(tempytow, FINECOSINE(angle))));
 		}
 
 		//v3d->sow = (float)(v3d->sow - flatxref + scrollx);
@@ -858,11 +854,11 @@ static void HWR_DrawSegsSplats(FSurfaceInfo * pSurf)
 
 	M_ClearBox(segbbox);
 	M_AddToBox(segbbox,
-		FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->v1)->x),
-		FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->v1)->y));
+		FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv1)->x),
+		FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv1)->y));
 	M_AddToBox(segbbox,
-		FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->v2)->x),
-		FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->v2)->y));
+		FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv2)->x),
+		FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv2)->y));
 
 	splat = (wallsplat_t *)gr_curline->linedef->splats;
 	for (; splat; splat = splat->next)
@@ -1035,6 +1031,7 @@ static void HWR_ProjectWall(wallVert3D   * wallVerts,
 // (in fact a clipping plane that has a constant, so can clip with simple 2d)
 // with the wall segment
 //
+#ifndef NEWCLIP
 static float HWR_ClipViewSegment(INT32 x, polyvertex_t *v1, polyvertex_t *v2)
 {
 	float num, den;
@@ -1063,6 +1060,7 @@ static float HWR_ClipViewSegment(INT32 x, polyvertex_t *v1, polyvertex_t *v2)
 
 	return num / den;
 }
+#endif
 
 //
 // HWR_SplitWall
@@ -1084,9 +1082,9 @@ static void HWR_SplitWall(sector_t *sector, wallVert3D *wallVerts, INT32 texnum,
 	float endheight = 0.0f, endbheight = 0.0f;
 
 	fixed_t v1x = FLOAT_TO_FIXED(wallVerts[0].x);
-	fixed_t v1y = FLOAT_TO_FIXED(wallVerts[0].y);
+	fixed_t v1y = FLOAT_TO_FIXED(wallVerts[0].z); // not a typo
 	fixed_t v2x = FLOAT_TO_FIXED(wallVerts[1].x);
-	fixed_t v2y = FLOAT_TO_FIXED(wallVerts[1].y);
+	fixed_t v2y = FLOAT_TO_FIXED(wallVerts[1].z); // not a typo
 	// compiler complains when P_GetZAt is used in FLOAT_TO_FIXED directly
 	// use this as a temp var to store P_GetZAt's return value each time
 	fixed_t temp;
@@ -1437,7 +1435,11 @@ static void HWR_DrawSkyWall(wallVert3D *wallVerts, FSurfaceInfo *Surf, fixed_t b
 // Anything between means the wall segment has been clipped with solidsegs,
 //  reducing wall overdraw to a minimum
 //
+#ifdef NEWCLIP
+static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
+#else
 static void HWR_StoreWallRange(double startfrac, double endfrac)
+#endif
 {
 	wallVert3D wallVerts[4];
 	v2d_t vs, ve; // start, end vertices of 2d line (view from above)
@@ -1462,16 +1464,18 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 	extracolormap_t *colormap;
 	FSurfaceInfo Surf;
 
+#ifndef NEWCLIP
 	if (startfrac > endfrac)
 		return;
+#endif
 
 	gr_sidedef = gr_curline->sidedef;
 	gr_linedef = gr_curline->linedef;
 
-	vs.x = ((polyvertex_t *)gr_curline->v1)->x;
-	vs.y = ((polyvertex_t *)gr_curline->v1)->y;
-	ve.x = ((polyvertex_t *)gr_curline->v2)->x;
-	ve.y = ((polyvertex_t *)gr_curline->v2)->y;
+	vs.x = ((polyvertex_t *)gr_curline->pv1)->x;
+	vs.y = ((polyvertex_t *)gr_curline->pv1)->y;
+	ve.x = ((polyvertex_t *)gr_curline->pv2)->x;
+	ve.y = ((polyvertex_t *)gr_curline->pv2)->y;
 
 #ifdef ESLOPE
 	v1x = FLOAT_TO_FIXED(vs.x);
@@ -1479,44 +1483,21 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 	v2x = FLOAT_TO_FIXED(ve.x);
 	v2y = FLOAT_TO_FIXED(ve.y);
 #endif
-
-	if (gr_frontsector->heightsec != -1)
-	{
 #ifdef ESLOPE
-		worldtop = worldtopslope = sectors[gr_frontsector->heightsec].ceilingheight;
-		worldbottom = worldbottomslope = sectors[gr_frontsector->heightsec].floorheight;
-#else
-		worldtop = sectors[gr_frontsector->heightsec].ceilingheight;
-		worldbottom = sectors[gr_frontsector->heightsec].floorheight;
-#endif
-	}
-	else
-	{
-#ifdef ESLOPE
-		if (gr_frontsector->c_slope)
-		{
-			worldtop      = P_GetZAt(gr_frontsector->c_slope, v1x, v1y);
-			worldtopslope = P_GetZAt(gr_frontsector->c_slope, v2x, v2y);
-		}
-		else
-		{
-			worldtop = worldtopslope = gr_frontsector->ceilingheight;
-		}
 
-		if (gr_frontsector->f_slope)
-		{
-			worldbottom      = P_GetZAt(gr_frontsector->f_slope, v1x, v1y);
-			worldbottomslope = P_GetZAt(gr_frontsector->f_slope, v2x, v2y);
-		}
-		else
-		{
-			worldbottom = worldbottomslope = gr_frontsector->floorheight;
-		}
+#define SLOPEPARAMS(slope, end1, end2, normalheight) \
+	if (slope) { \
+		end1 = P_GetZAt(slope, v1x, v1y); \
+		end2 = P_GetZAt(slope, v2x, v2y); \
+	} else \
+		end1 = end2 = normalheight;
+
+	SLOPEPARAMS(gr_frontsector->c_slope, worldtop,    worldtopslope,    gr_frontsector->ceilingheight)
+	SLOPEPARAMS(gr_frontsector->f_slope, worldbottom, worldbottomslope, gr_frontsector->floorheight)
 #else
-		worldtop    = gr_frontsector->ceilingheight;
-		worldbottom = gr_frontsector->floorheight;
+	worldtop    = gr_frontsector->ceilingheight;
+	worldbottom = gr_frontsector->floorheight;
 #endif
-	}
 
 	// remember vertices ordering
 	//  3--2
@@ -1531,20 +1512,23 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 	wallVerts[2].z = wallVerts[1].z = ve.y;
 	wallVerts[0].w = wallVerts[1].w = wallVerts[2].w = wallVerts[3].w = 1.0f;
 
-	if (drawtextured)
 	{
 		// x offset the texture
 		fixed_t texturehpeg = gr_sidedef->textureoffset + gr_curline->offset;
 
+#ifndef NEWCLIP
 		// clip texture s start/end coords with solidsegs
 		if (startfrac > 0.0f && startfrac < 1.0f)
 			cliplow = (float)(texturehpeg + (gr_curline->flength*FRACUNIT) * startfrac);
 		else
+#endif
 			cliplow = (float)texturehpeg;
 
+#ifndef NEWCLIP
 		if (endfrac > 0.0f && endfrac < 1.0f)
 			cliphigh = (float)(texturehpeg + (gr_curline->flength*FRACUNIT) * endfrac);
 		else
+#endif
 			cliphigh = (float)(texturehpeg + (gr_curline->flength*FRACUNIT));
 	}
 
@@ -1560,43 +1544,15 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 	{
 		INT32 gr_toptexture, gr_bottomtexture;
 		// two sided line
-		if (gr_backsector->heightsec != -1)
-		{
-#ifdef ESLOPE
-			worldhigh = worldhighslope = sectors[gr_backsector->heightsec].ceilingheight;
-			worldlow = worldlowslope = sectors[gr_backsector->heightsec].floorheight;
-#else
-			worldhigh = sectors[gr_backsector->heightsec].ceilingheight;
-			worldlow = sectors[gr_backsector->heightsec].floorheight;
-#endif
-		}
-		else
-		{
-#ifdef ESLOPE
-			if (gr_backsector->c_slope)
-			{
-				worldhigh      = P_GetZAt(gr_backsector->c_slope, v1x, v1y);
-				worldhighslope = P_GetZAt(gr_backsector->c_slope, v2x, v2y);
-			}
-			else
-			{
-				worldhigh = worldhighslope = gr_backsector->ceilingheight;
-			}
 
-			if (gr_backsector->f_slope)
-			{
-				worldlow      = P_GetZAt(gr_backsector->f_slope, v1x, v1y);
-				worldlowslope = P_GetZAt(gr_backsector->f_slope, v2x, v2y);
-			}
-			else
-			{
-				worldlow = worldlowslope = gr_backsector->floorheight;
-			}
+#ifdef ESLOPE
+		SLOPEPARAMS(gr_backsector->c_slope, worldhigh, worldhighslope, gr_backsector->ceilingheight)
+		SLOPEPARAMS(gr_backsector->f_slope, worldlow,  worldlowslope,  gr_backsector->floorheight)
+#undef SLOPEPARAMS
 #else
-			worldhigh = gr_backsector->ceilingheight;
-			worldlow  = gr_backsector->floorheight;
+		worldhigh = gr_backsector->ceilingheight;
+		worldlow  = gr_backsector->floorheight;
 #endif
-		}
 
 		// hack to allow height changes in outdoor areas
 		// This is what gets rid of the upper textures if there should be sky
@@ -1620,7 +1576,6 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
             worldhigh < worldtop
             ) && gr_toptexture)
 		{
-			if (drawtextured)
 			{
 				fixed_t texturevpegtop; // top
 
@@ -1701,7 +1656,6 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 #endif
             worldlow > worldbottom) && gr_bottomtexture) //only if VISIBLE!!!
 		{
-			if (drawtextured)
 			{
 				fixed_t texturevpegbottom = 0; // bottom
 
@@ -1893,7 +1847,6 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 			h = min(highcut, polytop);
 			l = max(polybottom, lowcut);
 
-			if (drawtextured)
 			{
 				// PEGGING
 #ifdef ESLOPE
@@ -1949,7 +1902,6 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 				h = min(highcut, polytop);
 				l = max(polybottom, lowcut);
 
-				if (drawtextured)
 				{
 					// PEGGING
 					if (!!(gr_linedef->flags & ML_DONTPEGBOTTOM) ^ !!(gr_linedef->flags & ML_EFFECT3))
@@ -2141,7 +2093,6 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 		gr_midtexture = R_GetTextureNum(gr_sidedef->midtexture);
 		if (gr_midtexture)
 		{
-			if (drawtextured)
 			{
 				fixed_t     texturevpeg;
 				// PEGGING
@@ -2282,7 +2233,7 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 					wallVerts[0].s = wallVerts[3].s = 0;
 					wallVerts[2].s = wallVerts[1].s = 0;
 				}
-				else if (drawtextured)
+				else
 				{
 #ifdef ESLOPE // P.S. this is better-organized than the old version
 					fixed_t offs = sides[(newline ? newline : rover->master)->sidenum[0]].rowoffset;
@@ -2415,7 +2366,7 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 					wallVerts[0].s = wallVerts[3].s = 0;
 					wallVerts[2].s = wallVerts[1].s = 0;
 				}
-				else if (drawtextured)
+				else
 				{
 					grTex = HWR_GetTexture(texnum);
 
@@ -2488,6 +2439,110 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 //Hurdler: end of 3d-floors test
 }
 
+// From PrBoom:
+//
+// e6y: Check whether the player can look beyond this line
+//
+#ifdef NEWCLIP
+boolean checkforemptylines = true;
+// Don't modify anything here, just check
+// Kalaron: Modified for sloped linedefs
+static boolean CheckClip(seg_t * seg, sector_t * afrontsector, sector_t * abacksector)
+{
+	fixed_t frontf1,frontf2, frontc1, frontc2; // front floor/ceiling ends
+	fixed_t backf1, backf2, backc1, backc2; // back floor ceiling ends
+
+	// GZDoom method of sloped line clipping
+
+#ifdef ESLOPE
+	if (afrontsector->f_slope || afrontsector->c_slope || abacksector->f_slope || abacksector->c_slope)
+	{
+		fixed_t v1x, v1y, v2x, v2y; // the seg's vertexes as fixed_t
+		v1x = FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv1)->x);
+		v1y = FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv1)->y);
+		v2x = FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv2)->x);
+		v2y = FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv2)->y);
+#define SLOPEPARAMS(slope, end1, end2, normalheight) \
+		if (slope) { \
+			end1 = P_GetZAt(slope, v1x, v1y); \
+			end2 = P_GetZAt(slope, v2x, v2y); \
+		} else \
+			end1 = end2 = normalheight;
+
+		SLOPEPARAMS(afrontsector->f_slope, frontf1, frontf2, afrontsector->floorheight)
+		SLOPEPARAMS(afrontsector->c_slope, frontc1, frontc2, afrontsector->ceilingheight)
+		SLOPEPARAMS( abacksector->f_slope, backf1,  backf2,  abacksector->floorheight)
+		SLOPEPARAMS( abacksector->c_slope, backc1,  backc2,  abacksector->ceilingheight)
+#undef SLOPEPARAMS
+	}
+	else
+#endif
+	{
+		frontf1 = frontf2 = afrontsector->floorheight;
+		frontc1 = frontc2 = afrontsector->ceilingheight;
+		backf1 = backf2 = abacksector->floorheight;
+		backc1 = backc2 = abacksector->ceilingheight;
+	}
+
+	// now check for closed sectors!
+	if (backc1 <= frontf1 && backc2 <= frontf2)
+	{
+		checkforemptylines = false;
+		if (!seg->sidedef->toptexture)
+			return false;
+
+		if (abacksector->ceilingpic == skyflatnum && afrontsector->ceilingpic == skyflatnum)
+			return false;
+
+		return true;
+	}
+
+	if (backf1 >= frontc1 && backf2 >= frontc2)
+	{
+		checkforemptylines = false;
+		if (!seg->sidedef->bottomtexture)
+			return false;
+
+		// properly render skies (consider door "open" if both floors are sky):
+		if (abacksector->ceilingpic == skyflatnum && afrontsector->ceilingpic == skyflatnum)
+			return false;
+
+		return true;
+	}
+
+	if (backc1 <= backf1 && backc2 <= backf2)
+	{
+		checkforemptylines = false;
+		// preserve a kind of transparent door/lift special effect:
+		if (backc1 < frontc1 || backc2 < frontc2)
+		{
+			if (!seg->sidedef->toptexture)
+				return false;
+		}
+		if (backf1 > frontf1 || backf2 > frontf2)
+		{
+			if (!seg->sidedef->bottomtexture)
+				return false;
+		}
+		if (abacksector->ceilingpic == skyflatnum && afrontsector->ceilingpic == skyflatnum)
+			return false;
+
+		if (abacksector->floorpic == skyflatnum && afrontsector->floorpic == skyflatnum)
+			return false;
+
+		return true;
+	}
+
+	if (backc1 != frontc1 || backc2 != frontc2
+		|| backf1 != frontf1 || backf2 != frontf2)
+		{
+			checkforemptylines = false;
+			return false;
+		}
+
+	return false;
+}
+#else
 //Hurdler: just like in r_bsp.c
 #if 1
 #define MAXSEGS         MAXVIDWIDTH/2+1
@@ -2559,7 +2614,7 @@ static void HWR_ClipSolidWallSegment(INT32 first, INT32 last)
 		}
 		else
 		{
-			highfrac = HWR_ClipViewSegment(start->first+1, (polyvertex_t *)gr_curline->v1, (polyvertex_t *)gr_curline->v2);
+			highfrac = HWR_ClipViewSegment(start->first+1, (polyvertex_t *)gr_curline->pv1, (polyvertex_t *)gr_curline->pv2);
 			HWR_StoreWallRange(0, highfrac);
 		}
 		// Now adjust the clip size.
@@ -2583,8 +2638,8 @@ static void HWR_ClipSolidWallSegment(INT32 first, INT32 last)
 		}
 		else
 		{
-			lowfrac  = HWR_ClipViewSegment(next->last-1, (polyvertex_t *)gr_curline->v1, (polyvertex_t *)gr_curline->v2);
-			highfrac = HWR_ClipViewSegment((next+1)->first+1, (polyvertex_t *)gr_curline->v1, (polyvertex_t *)gr_curline->v2);
+			lowfrac  = HWR_ClipViewSegment(next->last-1, (polyvertex_t *)gr_curline->pv1, (polyvertex_t *)gr_curline->pv2);
+			highfrac = HWR_ClipViewSegment((next+1)->first+1, (polyvertex_t *)gr_curline->pv1, (polyvertex_t *)gr_curline->pv2);
 			HWR_StoreWallRange(lowfrac, highfrac);
 		}
 		next++;
@@ -2618,7 +2673,7 @@ static void HWR_ClipSolidWallSegment(INT32 first, INT32 last)
 		}
 		else
 		{
-			lowfrac  = HWR_ClipViewSegment(next->last-1, (polyvertex_t *)gr_curline->v1, (polyvertex_t *)gr_curline->v2);
+			lowfrac  = HWR_ClipViewSegment(next->last-1, (polyvertex_t *)gr_curline->pv1, (polyvertex_t *)gr_curline->pv2);
 			HWR_StoreWallRange(lowfrac, 1);
 		}
 	}
@@ -2681,8 +2736,8 @@ static void HWR_ClipPassWallSegment(INT32 first, INT32 last)
 		else
 		{
 			highfrac = HWR_ClipViewSegment(min(start->first + 1,
-				start->last), (polyvertex_t *)gr_curline->v1,
-				(polyvertex_t *)gr_curline->v2);
+				start->last), (polyvertex_t *)gr_curline->pv1,
+				(polyvertex_t *)gr_curline->pv2);
 			HWR_StoreWallRange(0, highfrac);
 		}
 	}
@@ -2701,8 +2756,8 @@ static void HWR_ClipPassWallSegment(INT32 first, INT32 last)
 		}
 		else
 		{
-			lowfrac  = HWR_ClipViewSegment(max(start->last-1,start->first), (polyvertex_t *)gr_curline->v1, (polyvertex_t *)gr_curline->v2);
-			highfrac = HWR_ClipViewSegment(min((start+1)->first+1,(start+1)->last), (polyvertex_t *)gr_curline->v1, (polyvertex_t *)gr_curline->v2);
+			lowfrac  = HWR_ClipViewSegment(max(start->last-1,start->first), (polyvertex_t *)gr_curline->pv1, (polyvertex_t *)gr_curline->pv2);
+			highfrac = HWR_ClipViewSegment(min((start+1)->first+1,(start+1)->last), (polyvertex_t *)gr_curline->pv1, (polyvertex_t *)gr_curline->pv2);
 			HWR_StoreWallRange(lowfrac, highfrac);
 		}
 		start++;
@@ -2732,8 +2787,8 @@ static void HWR_ClipPassWallSegment(INT32 first, INT32 last)
 		else
 		{
 			lowfrac = HWR_ClipViewSegment(max(start->last - 1,
-				start->first), (polyvertex_t *)gr_curline->v1,
-				(polyvertex_t *)gr_curline->v2);
+				start->first), (polyvertex_t *)gr_curline->pv1,
+				(polyvertex_t *)gr_curline->pv2);
 			HWR_StoreWallRange(lowfrac, 1);
 		}
 	}
@@ -2773,6 +2828,7 @@ static void HWR_ClearClipSegs(void)
 	gr_solidsegs[1].last = 0x7fffffff;
 	hw_newend = gr_solidsegs+2;
 }
+#endif // NEWCLIP
 
 // -----------------+
 // HWR_AddLine      : Clips the given segment and adds any visible pieces to the line list.
@@ -2781,24 +2837,46 @@ static void HWR_ClearClipSegs(void)
 // -----------------+
 static void HWR_AddLine(seg_t * line)
 {
-	INT32 x1, x2;
 	angle_t angle1, angle2;
+#ifndef NEWCLIP
+	INT32 x1, x2;
 	angle_t span, tspan;
+#endif
 
 	// SoM: Backsector needs to be run through R_FakeFlat
 	sector_t tempsec;
 
+	fixed_t v1x, v1y, v2x, v2y; // the seg's vertexes as fixed_t
+#ifdef POLYOBJECTS
 	if (line->polyseg && !(line->polyseg->flags & POF_RENDERSIDES))
 		return;
+#endif
 
 	gr_curline = line;
 
+	v1x = FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv1)->x);
+	v1y = FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv1)->y);
+	v2x = FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv2)->x);
+	v2y = FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->pv2)->y);
+
 	// OPTIMIZE: quickly reject orthogonal back sides.
-	angle1 = R_PointToAngle(FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->v1)->x),
-	                        FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->v1)->y));
-	angle2 = R_PointToAngle(FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->v2)->x),
-	                        FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->v2)->y));
+	angle1 = R_PointToAngle(v1x, v1y);
+	angle2 = R_PointToAngle(v2x, v2y);
+
+#ifdef NEWCLIP
+	 // PrBoom: Back side, i.e. backface culling - read: endAngle >= startAngle!
+	if (angle2 - angle1 < ANGLE_180)
+		return;
+
+	// PrBoom: use REAL clipping math YAYYYYYYY!!!
 
+	if (!gld_clipper_SafeCheckRange(angle2, angle1))
+    {
+		return;
+    }
+
+	checkforemptylines = true;
+#else
 	// Clip to view edges.
 	span = angle1 - angle2;
 
@@ -2839,8 +2917,8 @@ static void HWR_AddLine(seg_t * line)
 		float fx1,fx2,fy1,fy2;
 		//BP: test with a better projection than viewangletox[R_PointToAngle(angle)]
 		// do not enable this at release 4 mul and 2 div
-		fx1 = ((polyvertex_t *)(line->v1))->x-gr_viewx;
-		fy1 = ((polyvertex_t *)(line->v1))->y-gr_viewy;
+		fx1 = ((polyvertex_t *)(line->pv1))->x-gr_viewx;
+		fy1 = ((polyvertex_t *)(line->pv1))->y-gr_viewy;
 		fy2 = (fx1 * gr_viewcos + fy1 * gr_viewsin);
 		if (fy2 < 0)
 			// the point is back
@@ -2848,8 +2926,8 @@ static void HWR_AddLine(seg_t * line)
 		else
 			fx1 = gr_windowcenterx + (fx1 * gr_viewsin - fy1 * gr_viewcos) * gr_centerx / fy2;
 
-		fx2 = ((polyvertex_t *)(line->v2))->x-gr_viewx;
-		fy2 = ((polyvertex_t *)(line->v2))->y-gr_viewy;
+		fx2 = ((polyvertex_t *)(line->pv2))->x-gr_viewx;
+		fy2 = ((polyvertex_t *)(line->pv2))->y-gr_viewy;
 		fy1 = (fx2 * gr_viewcos + fy2 * gr_viewsin);
 		if (fy1 < 0)
 			// the point is back
@@ -2877,8 +2955,34 @@ static void HWR_AddLine(seg_t * line)
 		return;
 	}
 */
+#endif
+
 	gr_backsector = line->backsector;
 
+#ifdef NEWCLIP
+	if (!line->backsector)
+    {
+		gld_clipper_SafeAddClipRange(angle2, angle1);
+    }
+    else
+    {
+		gr_backsector = R_FakeFlat(gr_backsector, &tempsec, NULL, NULL, true);
+		if (CheckClip(line, gr_frontsector, gr_backsector))
+		{
+			gld_clipper_SafeAddClipRange(angle2, angle1);
+			checkforemptylines = false;
+		}
+		// Reject empty lines used for triggers and special events.
+		// Identical floor and ceiling on both sides,
+		//  identical light levels on both sides,
+		//  and no middle texture.
+		if (checkforemptylines && R_IsEmptyLine(line, gr_frontsector, gr_backsector))
+			return;
+    }
+
+	HWR_ProcessSeg(); // Doesn't need arguments because they're defined globally :D
+	return;
+#else
 	// Single sided line?
 	if (!gr_backsector)
 		goto clipsolid;
@@ -2888,14 +2992,9 @@ static void HWR_AddLine(seg_t * line)
 #ifdef ESLOPE
 	if (gr_frontsector->f_slope || gr_frontsector->c_slope || gr_backsector->f_slope || gr_backsector->c_slope)
 	{
-		fixed_t v1x, v1y, v2x, v2y; // the seg's vertexes as fixed_t
 		fixed_t frontf1,frontf2, frontc1, frontc2; // front floor/ceiling ends
 		fixed_t backf1, backf2, backc1, backc2; // back floor ceiling ends
 
-		v1x = FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->v1)->x);
-		v1y = FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->v1)->y);
-		v2x = FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->v2)->x);
-		v2y = FLOAT_TO_FIXED(((polyvertex_t *)gr_curline->v2)->y);
 #define SLOPEPARAMS(slope, end1, end2, normalheight) \
 		if (slope) { \
 			end1 = P_GetZAt(slope, v1x, v1y); \
@@ -2916,6 +3015,13 @@ static void HWR_AddLine(seg_t * line)
 			goto clipsolid;
 		}
 
+		// Check for automap fix.
+		if (backc1 <= backf1 && backc2 <= backf2
+		&& ((backc1 >= frontc1 && backc2 >= frontc2) || gr_curline->sidedef->toptexture)
+		&& ((backf1 <= frontf1 && backf2 >= frontf2) || gr_curline->sidedef->bottomtexture)
+		&& (gr_backsector->ceilingpic != skyflatnum || gr_frontsector->ceilingpic != skyflatnum))
+			goto clipsolid;
+
 		// Window.
 		if (backc1 != frontc1 || backc2 != frontc2
 			|| backf1 != frontf1 || backf2 != frontf2)
@@ -2931,6 +3037,13 @@ static void HWR_AddLine(seg_t * line)
 			gr_backsector->floorheight >= gr_frontsector->ceilingheight)
 			goto clipsolid;
 
+		// Check for automap fix.
+		if (gr_backsector->ceilingheight <= gr_backsector->floorheight
+		&& ((gr_backsector->ceilingheight >= gr_frontsector->ceilingheight) || gr_curline->sidedef->toptexture)
+		&& ((gr_backsector->floorheight <= gr_backsector->floorheight) || gr_curline->sidedef->bottomtexture)
+		&& (gr_backsector->ceilingpic != skyflatnum || gr_frontsector->ceilingpic != skyflatnum))
+			goto clipsolid;
+
 		// Window.
 		if (gr_backsector->ceilingheight != gr_frontsector->ceilingheight ||
 			gr_backsector->floorheight != gr_frontsector->floorheight)
@@ -2941,25 +3054,8 @@ static void HWR_AddLine(seg_t * line)
 	// Identical floor and ceiling on both sides,
 	//  identical light levels on both sides,
 	//  and no middle texture.
-	if (
-#ifdef POLYOBJECTS
-		!line->polyseg &&
-#endif
-		gr_backsector->ceilingpic == gr_frontsector->ceilingpic
-		&& gr_backsector->floorpic == gr_frontsector->floorpic
-#ifdef ESLOPE
-		&& gr_backsector->f_slope == gr_frontsector->f_slope
-		&& gr_backsector->c_slope == gr_frontsector->c_slope
-#endif
-	    && gr_backsector->lightlevel == gr_frontsector->lightlevel
-		&& gr_curline->sidedef->midtexture == 0
-		&& !gr_backsector->ffloors && !gr_frontsector->ffloors)
-		// SoM: For 3D sides... Boris, would you like to take a
-		// crack at rendering 3D sides? You would need to add the
-		// above check and add code to HWR_StoreWallRange...
-	{
+	if (R_IsEmptyLine(gr_curline, gr_frontsector, gr_backsector))
 		return;
-	}
 
 clippass:
 	if (x1 == x2)
@@ -2971,6 +3067,7 @@ clipsolid:
 	if (x1 == x2)
 		goto clippass;
 	HWR_ClipSolidWallSegment(x1, x2-1);
+#endif
 }
 
 // HWR_CheckBBox
@@ -2982,9 +3079,13 @@ clipsolid:
 
 static boolean HWR_CheckBBox(fixed_t *bspcoord)
 {
-	INT32 boxpos, sx1, sx2;
+	INT32 boxpos;
 	fixed_t px1, py1, px2, py2;
-	angle_t angle1, angle2, span, tspan;
+	angle_t angle1, angle2;
+#ifndef NEWCLIP
+	INT32 sx1, sx2;
+	angle_t span, tspan;
+#endif
 
 	// Find the corners of the box
 	// that define the edges from current viewpoint.
@@ -3010,6 +3111,11 @@ static boolean HWR_CheckBBox(fixed_t *bspcoord)
 	px2 = bspcoord[checkcoord[boxpos][2]];
 	py2 = bspcoord[checkcoord[boxpos][3]];
 
+#ifdef NEWCLIP
+	angle1 = R_PointToAngle(px1, py1);
+	angle2 = R_PointToAngle(px2, py2);
+	return gld_clipper_SafeCheckRange(angle2, angle1);
+#else
 	// check clip list for an open space
 	angle1 = R_PointToAngle(px1, py1) - dup_viewangle;
 	angle2 = R_PointToAngle(px2, py2) - dup_viewangle;
@@ -3057,6 +3163,7 @@ static boolean HWR_CheckBBox(fixed_t *bspcoord)
 		return false;
 
 	return HWR_ClipToSolidSegs(sx1, sx2 - 1);
+#endif
 }
 
 #ifdef POLYOBJECTS
@@ -3090,8 +3197,8 @@ static inline void HWR_AddPolyObjectSegs(void)
 			pv2->x = FIXED_TO_FLOAT(gr_fakeline->v2->x);
 			pv2->y = FIXED_TO_FLOAT(gr_fakeline->v2->y);
 
-			gr_fakeline->v1 = (vertex_t *)pv1;
-			gr_fakeline->v2 = (vertex_t *)pv2;
+			gr_fakeline->pv1 = pv1;
+			gr_fakeline->pv2 = pv2;
 
 			HWR_AddLine(gr_fakeline);
 		}
@@ -3367,7 +3474,6 @@ static void HWR_Subsector(size_t num)
 
 	if (num < numsubsectors)
 	{
-		sscount++;
 		// subsector
 		sub = &subsectors[num];
 		// sector
@@ -3722,6 +3828,9 @@ static void HWR_Subsector(size_t num)
 
 		while (count--)
 		{
+#ifdef POLYOBJECTS
+				if (!line->polyseg) // ignore segs that belong to polyobjects
+#endif
 				HWR_AddLine(line);
 				line++;
 		}
@@ -4227,6 +4336,7 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 	GLPatch_t *gpatch; // sprite patch converted to hardware
 	FSurfaceInfo Surf;
 	const boolean hires = (spr->mobj && spr->mobj->skin && ((skin_t *)spr->mobj->skin)->flags & SF_HIRES);
+	//const boolean papersprite = (spr->mobj && (spr->mobj->frame & FF_PAPERSPRITE));
 	if (spr->mobj)
 		this_scale = FIXED_TO_FLOAT(spr->mobj->scale);
 	if (hires)
@@ -4270,7 +4380,8 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 
 	// make a wall polygon (with 2 triangles), using the floor/ceiling heights,
 	// and the 2d map coords of start/end vertices
-	wallVerts[0].z = wallVerts[1].z = wallVerts[2].z = wallVerts[3].z = spr->tz;
+	wallVerts[0].z = wallVerts[3].z = spr->z1;
+	wallVerts[2].z = wallVerts[1].z = spr->z2;
 
 	// transform
 	wv = wallVerts;
@@ -5066,6 +5177,10 @@ static void HWR_ProjectSprite(mobj_t *thing)
 
 	angle_t ang;
 	INT32 heightsec, phs;
+	const boolean papersprite = (thing->frame & FF_PAPERSPRITE);
+	float offset;
+	float ang_scale = 1.0f, ang_scalez = 0.0f;
+	float z1, z2;
 
 	if (!thing)
 		return;
@@ -5080,7 +5195,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	tz = (tr_x * gr_viewcos) + (tr_y * gr_viewsin);
 
 	// thing is behind view plane?
-	if (tz < ZCLIP_PLANE && (!cv_grmd2.value || md2_models[thing->sprite].notfound == true)) //Yellow: Only MD2's dont disappear
+	if (tz < ZCLIP_PLANE && !papersprite && (!cv_grmd2.value || md2_models[thing->sprite].notfound == true)) //Yellow: Only MD2's dont disappear
 		return;
 
 	tx = (tr_x * gr_viewsin) - (tr_y * gr_viewcos);
@@ -5118,6 +5233,27 @@ static void HWR_ProjectSprite(mobj_t *thing)
 		I_Error("sprframes NULL for sprite %d\n", thing->sprite);
 #endif
 
+	if (papersprite)
+	{
+		// Use the actual view angle, rather than the angle formed
+		// between the view point and the thing
+		// this makes sure paper sprites always appear at the right angle!
+		// Note: DO NOT do this in software mode version, it actually
+		// makes papersprites look WORSE there (I know, I've tried)
+		// Monster Iestyn - 13/05/17
+		ang = dup_viewangle - (thing->player ? thing->player->drawangle : thing->angle);
+		ang_scale = FIXED_TO_FLOAT(FINESINE(ang>>ANGLETOFINESHIFT));
+		ang_scalez = FIXED_TO_FLOAT(FINECOSINE(ang>>ANGLETOFINESHIFT));
+
+		if (ang_scale < 0)
+		{
+			ang_scale = -ang_scale;
+			ang_scalez = -ang_scalez;
+		}
+	}
+	else if (sprframe->rotate != SRF_SINGLE)
+		ang = R_PointToAngle (thing->x, thing->y) - (thing->player ? thing->player->drawangle : thing->angle);
+
 	if (sprframe->rotate == SRF_SINGLE)
 	{
 		// use single rotation for all views
@@ -5128,8 +5264,6 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	else
 	{
 		// choose a different rotation based on player view
-		ang = R_PointToAngle (thing->x, thing->y) - (thing->player ? thing->player->drawangle : thing->angle);
-
 		if ((sprframe->rotate & SRF_RIGHT) && (ang < ANGLE_180)) // See from right
 			rot = 6; // F7 slot
 		else if ((sprframe->rotate & SRF_LEFT) && (ang >= ANGLE_180)) // See from left
@@ -5147,9 +5281,12 @@ static void HWR_ProjectSprite(mobj_t *thing)
 
 	// calculate edges of the shape
 	if (flip)
-		tx -= FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width - spritecachedinfo[lumpoff].offset) * this_scale;
+		offset = FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width - spritecachedinfo[lumpoff].offset) * this_scale;
 	else
-		tx -= FIXED_TO_FLOAT(spritecachedinfo[lumpoff].offset) * this_scale;
+		offset = FIXED_TO_FLOAT(spritecachedinfo[lumpoff].offset) * this_scale;
+
+	z1 = tz - (offset * ang_scalez);
+	tx -= offset * ang_scale;
 
 	// project x
 	x1 = gr_windowcenterx + (tx * gr_centerx / tz);
@@ -5160,7 +5297,14 @@ static void HWR_ProjectSprite(mobj_t *thing)
 
 	x1 = tx;
 
-	tx += FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width) * this_scale;
+	offset = FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width) * this_scale;
+
+	z2 = z1 + (offset * ang_scalez);
+	tx += offset * ang_scale;
+
+	if (papersprite && max(z1, z2) < ZCLIP_PLANE)
+		return;
+
 	x2 = gr_windowcenterx + (tx * gr_centerx / tz);
 
 	if (vflip)
@@ -5209,6 +5353,8 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	vis->patchlumpnum = sprframe->lumppat[rot];
 	vis->flip = flip;
 	vis->mobj = thing;
+	vis->z1 = z1;
+	vis->z2 = z2;
 
 	//Hurdler: 25/04/2000: now support colormap in hardware mode
 	if ((vis->mobj->flags & MF_BOSS) && (vis->mobj->flags2 & MF2_FRET) && (leveltime & 1)) // Bosses "flash"
@@ -5597,7 +5743,19 @@ if (0)
 #ifdef SORTING
 	drawcount = 0;
 #endif
+#ifdef NEWCLIP
+	if (rendermode == render_opengl)
+	{
+		angle_t a1 = gld_FrustumAngle();
+		gld_clipper_Clear();
+		gld_clipper_SafeAddClipRange(viewangle + a1, viewangle - a1);
+#ifdef HAVE_SPHEREFRUSTRUM
+		gld_FrustrumSetup();
+#endif
+	}
+#else
 	HWR_ClearClipSegs();
+#endif
 
 	//04/01/2000: Hurdler: added for T&L
 	//                     Actually it only works on Walls and Planes
@@ -5607,6 +5765,7 @@ if (0)
 
 	HWR_RenderBSPNode((INT32)numnodes-1);
 
+#ifndef NEWCLIP
 	// Make a viewangle int so we can render things based on mouselook
 	if (player == &players[consoleplayer])
 		viewangle = localaiming;
@@ -5633,6 +5792,7 @@ if (0)
 
 		dup_viewangle += ANGLE_90;
 	}
+#endif
 
 	// Check for new console commands.
 	NetUpdate();
@@ -5827,7 +5987,19 @@ if (0)
 #ifdef SORTING
 	drawcount = 0;
 #endif
+#ifdef NEWCLIP
+	if (rendermode == render_opengl)
+	{
+		angle_t a1 = gld_FrustumAngle();
+		gld_clipper_Clear();
+		gld_clipper_SafeAddClipRange(viewangle + a1, viewangle - a1);
+#ifdef HAVE_SPHEREFRUSTRUM
+		gld_FrustrumSetup();
+#endif
+	}
+#else
 	HWR_ClearClipSegs();
+#endif
 
 	//04/01/2000: Hurdler: added for T&L
 	//                     Actually it only works on Walls and Planes
@@ -5837,6 +6009,7 @@ if (0)
 
 	HWR_RenderBSPNode((INT32)numnodes-1);
 
+#ifndef NEWCLIP
 	// Make a viewangle int so we can render things based on mouselook
 	if (player == &players[consoleplayer])
 		viewangle = localaiming;
@@ -5863,6 +6036,7 @@ if (0)
 
 		dup_viewangle += ANGLE_90;
 	}
+#endif
 
 	// Check for new console commands.
 	NetUpdate();
@@ -6008,7 +6182,9 @@ static inline void HWR_AddEngineCommands(void)
 {
 	// engine state variables
 	//CV_RegisterVar(&cv_grzbuffer);
+#ifndef NEWCLIP
 	CV_RegisterVar(&cv_grclipwalls);
+#endif
 
 	// engine development mode variables
 	// - usage may vary from version to version..
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 29a3e72db688ff61a4c27c5051de8c4c7fc044dd..6fa5c3afb1fe14cf113b380c0d88bcba2fffbcff 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -298,8 +298,8 @@ static md2_model_t *md2_readModel(const char *filename)
 	// initialize model and read header
 
 	if (fread(&model->header, sizeof (model->header), 1, file) != 1
-		|| model->header.magic !=
-		(INT32)(('2' << 24) + ('P' << 16) + ('D' << 8) + 'I'))
+		|| model->header.magic != MD2_IDENT
+		|| model->header.version != MD2_VERSION)
 	{
 		fclose(file);
 		free(model);
@@ -313,6 +313,7 @@ static md2_model_t *md2_readModel(const char *filename)
 	{ \
 		CONS_Alert(CONS_ERROR, "md2_readModel: %s has too many " msgname " (# found: %d, maximum: %d)\n", filename, field, max); \
 		md2_freeModel (model); \
+		fclose(file); \
 		return 0; \
 	}
 
@@ -334,6 +335,7 @@ static md2_model_t *md2_readModel(const char *filename)
 			fread(model->skins, sizeof (md2_skin_t), model->header.numSkins, file))
 		{
 			md2_freeModel (model);
+			fclose(file);
 			return 0;
 		}
 	}
@@ -347,6 +349,7 @@ static md2_model_t *md2_readModel(const char *filename)
 			fread(model->texCoords, sizeof (md2_textureCoordinate_t), model->header.numTexCoords, file))
 		{
 			md2_freeModel (model);
+			fclose(file);
 			return 0;
 		}
 	}
@@ -360,6 +363,7 @@ static md2_model_t *md2_readModel(const char *filename)
 			fread(model->triangles, sizeof (md2_triangle_t), model->header.numTriangles, file))
 		{
 			md2_freeModel (model);
+			fclose(file);
 			return 0;
 		}
 	}
@@ -372,6 +376,7 @@ static md2_model_t *md2_readModel(const char *filename)
 		if (!model->frames)
 		{
 			md2_freeModel (model);
+			fclose(file);
 			return 0;
 		}
 
@@ -385,6 +390,7 @@ static md2_model_t *md2_readModel(const char *filename)
 				fread(frame, 1, model->header.frameSize, file))
 			{
 				md2_freeModel (model);
+				fclose(file);
 				return 0;
 			}
 
@@ -410,6 +416,7 @@ static md2_model_t *md2_readModel(const char *filename)
 			fread(model->glCommandBuffer, sizeof (INT32), model->header.numGlCommands, file))
 		{
 			md2_freeModel (model);
+			fclose(file);
 			return 0;
 		}
 	}
@@ -961,244 +968,10 @@ static void HWR_CreateBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch,
 	image = gpatch->mipmap.grInfo.data;
 	blendimage = blendgpatch->mipmap.grInfo.data;
 
-	switch (color)
-	{
-		case SKINCOLOR_WHITE:
-			blendcolor = V_GetColor(3);
-			break;
-		case SKINCOLOR_SILVER:
-			blendcolor = V_GetColor(10);
-			break;
-		case SKINCOLOR_GREY:
-			blendcolor = V_GetColor(15);
-			break;
-		case SKINCOLOR_BLACK:
-			blendcolor = V_GetColor(27);
-			break;
-		case SKINCOLOR_BEIGE:
-			blendcolor = V_GetColor(247);
-			break;
-		case SKINCOLOR_PEACH:
-			blendcolor = V_GetColor(218);
-			break;
-		case SKINCOLOR_BROWN:
-			blendcolor = V_GetColor(234);
-			break;
-		case SKINCOLOR_RED:
-			blendcolor = V_GetColor(38);
-			break;
-		case SKINCOLOR_CRIMSON:
-			blendcolor = V_GetColor(45);
-			break;
-		case SKINCOLOR_ORANGE:
-			blendcolor = V_GetColor(54);
-			break;
-		case SKINCOLOR_RUST:
-			blendcolor = V_GetColor(60);
-			break;
-		case SKINCOLOR_GOLD:
-			blendcolor = V_GetColor(67);
-			break;
-		case SKINCOLOR_YELLOW:
-			blendcolor = V_GetColor(73);
-			break;
-		case SKINCOLOR_TAN:
-			blendcolor = V_GetColor(85);
-			break;
-		case SKINCOLOR_MOSS:
-			blendcolor = V_GetColor(92);
-			break;
-		case SKINCOLOR_PERIDOT:
-			blendcolor = V_GetColor(188);
-			break;
-		case SKINCOLOR_GREEN:
-			blendcolor = V_GetColor(101);
-			break;
-		case SKINCOLOR_EMERALD:
-			blendcolor = V_GetColor(112);
-			break;
-		case SKINCOLOR_AQUA:
-			blendcolor = V_GetColor(122);
-			break;
-		case SKINCOLOR_TEAL:
-			blendcolor = V_GetColor(141);
-			break;
-		case SKINCOLOR_CYAN:
-			blendcolor = V_GetColor(131);
-			break;
-		case SKINCOLOR_BLUE:
-			blendcolor = V_GetColor(152);
-			break;
-		case SKINCOLOR_AZURE:
-			blendcolor = V_GetColor(171);
-			break;
-		case SKINCOLOR_PASTEL:
-			blendcolor = V_GetColor(161);
-			break;
-		case SKINCOLOR_PURPLE:
-			blendcolor = V_GetColor(165);
-			break;
-		case SKINCOLOR_LAVENDER:
-			blendcolor = V_GetColor(195);
-			break;
-		case SKINCOLOR_MAGENTA:
-			blendcolor = V_GetColor(183);
-			break;
-		case SKINCOLOR_PINK:
-			blendcolor = V_GetColor(211);
-			break;
-		case SKINCOLOR_ROSY:
-			blendcolor = V_GetColor(202);
-			break;
-
-		case SKINCOLOR_SUPERSILVER1: // Super silver
-			blendcolor = V_GetColor(0);
-			break;
-		case SKINCOLOR_SUPERSILVER2:
-			blendcolor = V_GetColor(2);
-			break;
-		case SKINCOLOR_SUPERSILVER3:
-			blendcolor = V_GetColor(4);
-			break;
-		case SKINCOLOR_SUPERSILVER4:
-			blendcolor = V_GetColor(7);
-			break;
-		case SKINCOLOR_SUPERSILVER5:
-			blendcolor = V_GetColor(10);
-			break;
-
-		case SKINCOLOR_SUPERRED1: // Super red
-			blendcolor = V_GetColor(208);
-			break;
-		case SKINCOLOR_SUPERRED2:
-			blendcolor = V_GetColor(210);
-			break;
-		case SKINCOLOR_SUPERRED3:
-			blendcolor = V_GetColor(32);
-			break;
-		case SKINCOLOR_SUPERRED4:
-			blendcolor = V_GetColor(33);
-			break;
-		case SKINCOLOR_SUPERRED5:
-			blendcolor = V_GetColor(35);
-			break;
-
-		case SKINCOLOR_SUPERORANGE1: // Super orange
-			blendcolor = V_GetColor(208);
-			break;
-		case SKINCOLOR_SUPERORANGE2:
-			blendcolor = V_GetColor(48);
-			break;
-		case SKINCOLOR_SUPERORANGE3:
-			blendcolor = V_GetColor(50);
-			break;
-		case SKINCOLOR_SUPERORANGE4:
-			blendcolor = V_GetColor(54);
-			break;
-		case SKINCOLOR_SUPERORANGE5:
-			blendcolor = V_GetColor(58);
-			break;
-
-		case SKINCOLOR_SUPERGOLD1: // Super gold
-			blendcolor = V_GetColor(80);
-			break;
-		case SKINCOLOR_SUPERGOLD2:
-			blendcolor = V_GetColor(83);
-			break;
-		case SKINCOLOR_SUPERGOLD3:
-			blendcolor = V_GetColor(73);
-			break;
-		case SKINCOLOR_SUPERGOLD4:
-			blendcolor = V_GetColor(64);
-			break;
-		case SKINCOLOR_SUPERGOLD5:
-			blendcolor = V_GetColor(67);
-			break;
-
-		case SKINCOLOR_SUPERPERIDOT1: // Super peridot
-			blendcolor = V_GetColor(88);
-			break;
-		case SKINCOLOR_SUPERPERIDOT2:
-			blendcolor = V_GetColor(188);
-			break;
-		case SKINCOLOR_SUPERPERIDOT3:
-			blendcolor = V_GetColor(189);
-			break;
-		case SKINCOLOR_SUPERPERIDOT4:
-			blendcolor = V_GetColor(190);
-			break;
-		case SKINCOLOR_SUPERPERIDOT5:
-			blendcolor = V_GetColor(191);
-			break;
-
-		case SKINCOLOR_SUPERCYAN1: // Super cyan
-			blendcolor = V_GetColor(128);
-			break;
-		case SKINCOLOR_SUPERCYAN2:
-			blendcolor = V_GetColor(131);
-			break;
-		case SKINCOLOR_SUPERCYAN3:
-			blendcolor = V_GetColor(133);
-			break;
-		case SKINCOLOR_SUPERCYAN4:
-			blendcolor = V_GetColor(134);
-			break;
-		case SKINCOLOR_SUPERCYAN5:
-			blendcolor = V_GetColor(136);
-			break;
-
-		case SKINCOLOR_SUPERPURPLE1: // Super purple
-			blendcolor = V_GetColor(144);
-			break;
-		case SKINCOLOR_SUPERPURPLE2:
-			blendcolor = V_GetColor(162);
-			break;
-		case SKINCOLOR_SUPERPURPLE3:
-			blendcolor = V_GetColor(164);
-			break;
-		case SKINCOLOR_SUPERPURPLE4:
-			blendcolor = V_GetColor(166);
-			break;
-		case SKINCOLOR_SUPERPURPLE5:
-			blendcolor = V_GetColor(168);
-			break;
-
-		case SKINCOLOR_SUPERRUST1: // Super rust
-			blendcolor = V_GetColor(51);
-			break;
-		case SKINCOLOR_SUPERRUST2:
-			blendcolor = V_GetColor(54);
-			break;
-		case SKINCOLOR_SUPERRUST3:
-			blendcolor = V_GetColor(68);
-			break;
-		case SKINCOLOR_SUPERRUST4:
-			blendcolor = V_GetColor(70);
-			break;
-		case SKINCOLOR_SUPERRUST5:
-			blendcolor = V_GetColor(234);
-			break;
-
-		case SKINCOLOR_SUPERTAN1: // Super tan
-			blendcolor = V_GetColor(80);
-			break;
-		case SKINCOLOR_SUPERTAN2:
-			blendcolor = V_GetColor(82);
-			break;
-		case SKINCOLOR_SUPERTAN3:
-			blendcolor = V_GetColor(84);
-			break;
-		case SKINCOLOR_SUPERTAN4:
-			blendcolor = V_GetColor(87);
-			break;
-		case SKINCOLOR_SUPERTAN5:
-			blendcolor = V_GetColor(247);
-			break;
-
-		default:
-			blendcolor = V_GetColor(255);
-			break;
-	}
+	if (color == SKINCOLOR_NONE || color >= MAXTRANSLATIONS)
+		blendcolor = V_GetColor(0xff);
+	else
+		blendcolor = V_GetColor(Color_Index[color-1][4]);
 
 	while (size--)
 	{
diff --git a/src/hardware/hw_md2.h b/src/hardware/hw_md2.h
index 5a7e6d2b3cdaf0c3ae7d5813e835d5cf8711403e..299d1240005daba28769c1e8b5e12adfd1b67401 100644
--- a/src/hardware/hw_md2.h
+++ b/src/hardware/hw_md2.h
@@ -23,6 +23,11 @@
 
 #include "hw_glob.h"
 
+// magic number "IDP2" or 844121161
+#define MD2_IDENT                       (INT32)(('2' << 24) + ('P' << 16) + ('D' << 8) + 'I')
+// model version
+#define MD2_VERSION                     8
+
 #define MD2_MAX_TRIANGLES               8192
 #define MD2_MAX_VERTICES                4096
 #define MD2_MAX_TEXCOORDS               4096
diff --git a/src/hardware/hw_trick.c b/src/hardware/hw_trick.c
index e9ba19efb60a8fb8a6fe26c657866bcd2d764927..97d86b944890de2fae05d11bfd05ed607722b3d3 100644
--- a/src/hardware/hw_trick.c
+++ b/src/hardware/hw_trick.c
@@ -107,17 +107,17 @@ static void releaseLineChains(void)
 
 	for (i = 0; i < numsectors; i++)
 	{
-	sector = &sectors[i];
-	nextElem = sector->sectorLines;
+		sector = &sectors[i];
+		nextElem = sector->sectorLines;
 
-	while (nextElem)
-	{
-		thisElem = nextElem;
-		nextElem = thisElem->next;
-		free(thisElem);
-	}
+		while (nextElem)
+		{
+			thisElem = nextElem;
+			nextElem = thisElem->next;
+			free(thisElem);
+		}
 
-	sector->sectorLines = NULL;
+		sector->sectorLines = NULL;
 	}
 }
 
@@ -397,7 +397,7 @@ static void sortStacklist(sector_t *sector)
 		i = 0;
 		finished = true;
 
-		while (NULL != *(list+i+1))
+		while (*(list+i+1))
 		{
 			sec1 = *(list+i);
 			sec2 = *(list+i+1);
@@ -438,7 +438,7 @@ static double calcLineoutLength(sector_t *sector)
 	double length = 0.0L;
 	chain = sector->sectorLines;
 
-	while (NULL != chain) // sum up lengths of all lines
+	while (chain) // sum up lengths of all lines
 	{
 		length += lineLength(chain->line);
 		chain = chain->next;
@@ -454,7 +454,7 @@ static void calcLineouts(sector_t *sector)
 	size_t secCount = 0;
 	sector_t *encSector = *(sector->stackList);
 
-	while (NULL != encSector)
+	while (encSector)
 	{
 		if (encSector->lineoutLength < 0.0L) // if length has not yet been calculated
 		{
@@ -552,7 +552,7 @@ static boolean areBottomtexturesMissing(sector_t *thisSector)
 		if (frontSector == backSector) // skip damn renderer tricks here
 			continue;
 
-		if (frontSector == NULL || backSector == NULL)
+		if (!frontSector || !backSector)
 			continue;
 
 		sider = &sides[thisElem->line->sidenum[0]];
@@ -587,15 +587,14 @@ static boolean areBottomtexturesMissing(sector_t *thisSector)
 static boolean isCeilingFloating(sector_t *thisSector)
 {
 	sector_t *adjSector, *refSector = NULL, *frontSector, *backSector;
-	boolean floating = true;
 	linechain_t *thisElem, *nextElem;
 
 	if (!thisSector)
 		return false;
 
-	nextElem  = thisSector->sectorLines;
+	nextElem = thisSector->sectorLines;
 
-	while (NULL != nextElem) // walk through chain
+	while (nextElem) // walk through chain
 	{
 		thisElem = nextElem;
 		nextElem = thisElem->next;
@@ -609,10 +608,12 @@ static boolean isCeilingFloating(sector_t *thisSector)
 			adjSector = frontSector;
 
 		if (!adjSector) // assume floating sectors have surrounding sectors
-		{
-			floating = false;
-			break;
-		}
+			return false;
+
+#ifdef ESLOPE
+		if (adjSector->c_slope) // Don't bother with slopes
+			return false;
+#endif
 
 		if (!refSector)
 		{
@@ -621,23 +622,15 @@ static boolean isCeilingFloating(sector_t *thisSector)
 		}
 
 		// if adjacent sector has same height or more than one adjacent sector exists -> stop
-		if (thisSector->ceilingheight == adjSector->ceilingheight ||
-		   refSector != adjSector)
-		{
-			floating = false;
-			break;
-		}
+		if (thisSector->ceilingheight == adjSector->ceilingheight || refSector != adjSector)
+			return false;
 	}
 
 	// now check for walltextures
-	if (floating)
-	{
-		if (!areToptexturesMissing(thisSector))
-		{
-			floating = false;
-		}
-	}
-	return floating;
+	if (!areToptexturesMissing(thisSector))
+		return false;
+
+	return true;
 }
 
 //
@@ -647,13 +640,12 @@ static boolean isCeilingFloating(sector_t *thisSector)
 static boolean isFloorFloating(sector_t *thisSector)
 {
 	sector_t *adjSector, *refSector = NULL, *frontSector, *backSector;
-	boolean floating = true;
 	linechain_t *thisElem, *nextElem;
 
 	if (!thisSector)
 		return false;
 
-	nextElem  = thisSector->sectorLines;
+	nextElem = thisSector->sectorLines;
 
 	while (nextElem) // walk through chain
 	{
@@ -668,36 +660,30 @@ static boolean isFloorFloating(sector_t *thisSector)
 		else
 			adjSector = frontSector;
 
-		if (NULL == adjSector) // assume floating sectors have surrounding sectors
-		{
-			floating = false;
-			break;
-		}
+		if (!adjSector) // assume floating sectors have surrounding sectors
+			return false;
+
+#ifdef ESLOPE
+		if (adjSector->f_slope) // Don't bother with slopes
+			return false;
+#endif
 
-		if (NULL == refSector)
+		if (!refSector)
 		{
 			refSector = adjSector;
 			continue;
 		}
 
 		// if adjacent sector has same height or more than one adjacent sector exists -> stop
-		if (thisSector->floorheight == adjSector->floorheight ||
-		   refSector != adjSector)
-		{
-			floating = false;
-			break;
-		}
+		if (thisSector->floorheight == adjSector->floorheight || refSector != adjSector)
+			return false;
 	}
 
 	// now check for walltextures
-	if (floating)
-	{
-		if (!areBottomtexturesMissing(thisSector))
-		{
-			floating = false;
-		}
-	}
-	return floating;
+	if (!areBottomtexturesMissing(thisSector))
+		return false;
+
+	return true;
 }
 
 //
@@ -707,14 +693,12 @@ static fixed_t estimateCeilHeight(sector_t *thisSector)
 {
 	sector_t *adjSector;
 
-	if (!thisSector ||
-	 !thisSector->sectorLines ||
-	  !thisSector->sectorLines->line)
+	if (!thisSector || !thisSector->sectorLines || !thisSector->sectorLines->line)
 		return 0;
 
 	adjSector = thisSector->sectorLines->line->frontsector;
 	if (adjSector == thisSector)
-	adjSector = thisSector->sectorLines->line->backsector;
+		adjSector = thisSector->sectorLines->line->backsector;
 
 	if (!adjSector)
 		return 0;
@@ -729,17 +713,15 @@ static fixed_t estimateFloorHeight(sector_t *thisSector)
 {
 	sector_t *adjSector;
 
-	if (!thisSector ||
-	 !thisSector->sectorLines ||
-	  !thisSector->sectorLines->line)
-	return 0;
+	if (!thisSector || !thisSector->sectorLines || !thisSector->sectorLines->line)
+		return 0;
 
 	adjSector = thisSector->sectorLines->line->frontsector;
 	if (adjSector == thisSector)
-	adjSector = thisSector->sectorLines->line->backsector;
+		adjSector = thisSector->sectorLines->line->backsector;
 
-	if (NULL == adjSector)
-	return 0;
+	if (!adjSector)
+		return 0;
 
 	return adjSector->floorheight;
 }
@@ -845,18 +827,12 @@ void HWR_CorrectSWTricks(void)
 		// correct height of floating sectors
 		if (isCeilingFloating(floatSector))
 		{
-			fixed_t corrheight;
-
-			corrheight = estimateCeilHeight(floatSector);
-			floatSector->virtualCeilingheight = corrheight;
+			floatSector->virtualCeilingheight = estimateCeilHeight(floatSector);
 			floatSector->virtualCeiling = true;
 		}
 		if (isFloorFloating(floatSector))
 		{
-			fixed_t corrheight;
-
-			corrheight = estimateFloorHeight(floatSector);
-			floatSector->virtualFloorheight = corrheight;
+			floatSector->virtualFloorheight = estimateFloorHeight(floatSector);
 			floatSector->virtualFloor = true;
 		}
 	}
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 3a0bf70544b4b63e25f64b00595e5d2976eb758b..e6ff83e89698439c947fb7b73833e551c55387fd 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -244,6 +244,7 @@ FUNCPRINTF void DBG_Printf(const char *lpFmt, ...)
 #define pglMaterialfv glMaterialfv
 
 /* Raster functions */
+#define pglPixelStorei glPixelStorei
 #define pglReadPixels glReadPixels
 
 /* Texture mapping */
@@ -262,15 +263,8 @@ FUNCPRINTF void DBG_Printf(const char *lpFmt, ...)
 /* texture mapping */ //GL_EXT_copy_texture
 #ifndef KOS_GL_COMPATIBILITY
 #define pglCopyTexImage2D glCopyTexImage2D
-
-/* GLU functions */
-#define pgluBuild2DMipmaps gluBuild2DMipmaps
-#endif
-#ifndef MINI_GL_COMPATIBILITY
-/* 1.3 functions for multitexturing */
-#define pglActiveTexture glActiveTexture
-#define pglMultiTexCoord2f glMultiTexCoord2f
 #endif
+
 #else //!STATIC_OPENGL
 
 /* 1.0 functions */
@@ -365,6 +359,8 @@ typedef void (APIENTRY * PFNglMaterialfv) (GLint face, GLenum pname, GLfloat *pa
 static PFNglMaterialfv pglMaterialfv;
 
 /* Raster functions */
+typedef void (APIENTRY * PFNglPixelStorei) (GLenum pname, GLint param);
+static PFNglPixelStorei pglPixelStorei;
 typedef void (APIENTRY  * PFNglReadPixels) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels);
 static PFNglReadPixels pglReadPixels;
 
@@ -391,7 +387,7 @@ static PFNglBindTexture pglBindTexture;
 /* texture mapping */ //GL_EXT_copy_texture
 typedef void (APIENTRY * PFNglCopyTexImage2D) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border);
 static PFNglCopyTexImage2D pglCopyTexImage2D;
-
+#endif
 /* GLU functions */
 typedef GLint (APIENTRY * PFNgluBuild2DMipmaps) (GLenum target, GLint internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *data);
 static PFNgluBuild2DMipmaps pgluBuild2DMipmaps;
@@ -403,7 +399,6 @@ static PFNglActiveTexture pglActiveTexture;
 typedef void (APIENTRY *PFNglMultiTexCoord2f) (GLenum, GLfloat, GLfloat);
 static PFNglMultiTexCoord2f pglMultiTexCoord2f;
 #endif
-#endif
 
 #ifndef MINI_GL_COMPATIBILITY
 /* 1.2 Parms */
@@ -494,6 +489,7 @@ boolean SetupGLfunc(void)
 	GETOPENGLFUNC(pglLightModelfv , glLightModelfv)
 	GETOPENGLFUNC(pglMaterialfv , glMaterialfv)
 
+	GETOPENGLFUNC(pglPixelStorei , glPixelStorei)
 	GETOPENGLFUNC(pglReadPixels , glReadPixels)
 
 	GETOPENGLFUNC(pglTexEnvi , glTexEnvi)
@@ -519,35 +515,23 @@ boolean SetupGLfunc(void)
 // This has to be done after the context is created so the version number can be obtained
 boolean SetupGLFunc13(void)
 {
-	const GLubyte *version = pglGetString(GL_VERSION);
-	int glmajor, glminor;
-
-	gl13 = false;
 #ifdef MINI_GL_COMPATIBILITY
 	return false;
 #else
-#ifdef STATIC_OPENGL
-	gl13 = true;
-#else
+	const GLubyte *version = pglGetString(GL_VERSION);
+	int glmajor, glminor;
 
+	gl13 = false;
 	// Parse the GL version
 	if (version != NULL)
 	{
 		if (sscanf((const char*)version, "%d.%d", &glmajor, &glminor) == 2)
 		{
 			// Look, we gotta prepare for the inevitable arrival of GL 2.0 code...
-			switch (glmajor)
-			{
-				case 1:
-					if (glminor == 3) gl13 = true;
-					break;
-				case 2:
-				case 3:
-				case 4:
-					gl13 = true;
-				default:
-					break;
-			}
+			if (glmajor == 1 && glminor >= 3)
+				gl13 = true;
+			else if (glmajor > 1)
+				gl13 = true;
 		}
 	}
 
@@ -568,9 +552,6 @@ boolean SetupGLFunc13(void)
 	}
 	else
 		DBG_Printf("GL_ARB_multitexture support: disabled\n");
-#undef GETOPENGLFUNC
-
-#endif
 	return true;
 #endif
 }
@@ -897,7 +878,9 @@ EXPORT void HWRAPI(ReadRect) (INT32 x, INT32 y, INT32 width, INT32 height,
 		GLubyte*top = (GLvoid*)dst_data, *bottom = top + dst_stride * (height - 1);
 		GLubyte *row = malloc(dst_stride);
 		if (!row) return;
+		pglPixelStorei(GL_PACK_ALIGNMENT, 1);
 		pglReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, dst_data);
+		pglPixelStorei(GL_UNPACK_ALIGNMENT, 1);
 		for(i = 0; i < height/2; i++)
 		{
 			memcpy(row, top, dst_stride);
@@ -913,7 +896,9 @@ EXPORT void HWRAPI(ReadRect) (INT32 x, INT32 y, INT32 width, INT32 height,
 		INT32 j;
 		GLubyte *image = malloc(width*height*3*sizeof (*image));
 		if (!image) return;
+		pglPixelStorei(GL_PACK_ALIGNMENT, 1);
 		pglReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, image);
+		pglPixelStorei(GL_UNPACK_ALIGNMENT, 1);
 		for (i = height-1; i >= 0; i--)
 		{
 			for (j = 0; j < width; j++)
@@ -1815,13 +1800,11 @@ EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value)
 					min_filter = GL_NEAREST;
 #endif
 			}
-#ifndef STATIC_OPENGL
 			if (!pgluBuild2DMipmaps)
 			{
 				MipMap = GL_FALSE;
 				min_filter = GL_LINEAR;
 			}
-#endif
 			Flush(); //??? if we want to change filter mode by texture, remove this
 			break;
 
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index f6275631ca718d59695e9848df55d72aa8c04805..e92b969957d1b109290d4fca2fce70563c7d7ba4 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -91,7 +91,8 @@ patch_t *tallminus;
 patch_t *emeraldpics[7];
 patch_t *tinyemeraldpics[7];
 static patch_t *emblemicon;
-static patch_t *tokenicon;
+patch_t *tokenicon;
+static patch_t *exiticon;
 
 //-------------------------------------------
 //              misc vars
@@ -245,6 +246,7 @@ void HU_LoadGraphics(void)
 
 	emblemicon = W_CachePatchName("EMBLICON", PU_HUDGFX);
 	tokenicon = W_CachePatchName("TOKNICON", PU_HUDGFX);
+	exiticon = W_CachePatchName("EXITICON", PU_HUDGFX);
 
 	emeraldpics[0] = W_CachePatchName("CHAOS1", PU_HUDGFX);
 	emeraldpics[1] = W_CachePatchName("CHAOS2", PU_HUDGFX);
@@ -840,7 +842,7 @@ static void HU_DrawChat(void)
 		else
 		{
 			//charwidth = SHORT(hu_font[talk[i]-HU_FONTSTART]->width) * con_scalefactor;
-			V_DrawCharacter(HU_INPUTX + c, y, talk[i++] | cv_constextsize.value | V_NOSCALESTART, !cv_allcaps.value);
+			V_DrawCharacter(HU_INPUTX + c, y, talk[i++] | cv_constextsize.value | V_NOSCALESTART, true);
 		}
 		c += charwidth;
 	}
@@ -857,7 +859,7 @@ static void HU_DrawChat(void)
 		else
 		{
 			//charwidth = SHORT(hu_font[w_chat[i]-HU_FONTSTART]->width) * con_scalefactor;
-			V_DrawCharacter(HU_INPUTX + c, y, w_chat[i++] | cv_constextsize.value | V_NOSCALESTART | t, !cv_allcaps.value);
+			V_DrawCharacter(HU_INPUTX + c, y, w_chat[i++] | cv_constextsize.value | V_NOSCALESTART | t, true);
 		}
 
 		c += charwidth;
@@ -869,7 +871,7 @@ static void HU_DrawChat(void)
 	}
 
 	if (hu_tick < 4)
-		V_DrawCharacter(HU_INPUTX + c, y, '_' | cv_constextsize.value |V_NOSCALESTART|t, !cv_allcaps.value);
+		V_DrawCharacter(HU_INPUTX + c, y, '_' | cv_constextsize.value |V_NOSCALESTART|t, true);
 }
 
 
@@ -1176,6 +1178,9 @@ void HU_Erase(void)
 //                   IN-LEVEL MULTIPLAYER RANKINGS
 //======================================================================
 
+#define supercheckdef ((players[tab[i].num].powers[pw_super] && players[tab[i].num].mo && (players[tab[i].num].mo->state < &states[S_PLAY_SUPER_TRANS] || players[tab[i].num].mo->state > &states[S_PLAY_SUPER_TRANS9])) || (players[tab[i].num].powers[pw_carry] == CR_NIGHTSMODE && skins[players[tab[i].num].skin].flags & SF_SUPER))
+#define greycheckdef ((players[tab[i].num].mo && players[tab[i].num].mo->health <= 0) || players[tab[i].num].spectator)
+
 //
 // HU_DrawTabRankings
 //
@@ -1183,6 +1188,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 {
 	INT32 i;
 	const UINT8 *colormap;
+	boolean greycheck, supercheck;
 
 	//this function is designed for 9 or less score lines only
 	I_Assert(scorelines <= 9);
@@ -1191,12 +1197,15 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 
 	for (i = 0; i < scorelines; i++)
 	{
-		if (players[tab[i].num].spectator)
+		if (players[tab[i].num].spectator && gametype != GT_COOP)
 			continue; //ignore them.
 
+		greycheck = greycheckdef;
+		supercheck = supercheckdef;
+
 		V_DrawString(x + 20, y,
 		             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
-		             | ((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_60TRANS)
+		             | (greycheck ? V_60TRANS : 0)
 		             | V_ALLOWLOWERCASE, tab[i].name);
 
 		// Draw emeralds
@@ -1206,7 +1215,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 			HU_DrawEmeralds(x-12,y+2,tab[i].emeralds);
 		}
 
-		if (players[tab[i].num].mo && players[tab[i].num].mo->health <= 0)
+		if (greycheck)
 			V_DrawSmallTranslucentPatch (x, y-4, V_80TRANS, livesback);
 		else
 			V_DrawSmallScaledPatch (x, y-4, 0, livesback);
@@ -1214,11 +1223,11 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 		if (tab[i].color == 0)
 		{
 			colormap = colormaps;
-			if (players[tab[i].num].powers[pw_super])
+			if (supercheck)
 				V_DrawSmallScaledPatch(x, y-4, 0, superprefix[players[tab[i].num].skin]);
 			else
 			{
-				if (players[tab[i].num].mo && players[tab[i].num].mo->health <= 0)
+				if (greycheck)
 					V_DrawSmallTranslucentPatch(x, y-4, V_80TRANS, faceprefix[players[tab[i].num].skin]);
 				else
 					V_DrawSmallScaledPatch(x, y-4, 0, faceprefix[players[tab[i].num].skin]);
@@ -1226,7 +1235,7 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 		}
 		else
 		{
-			if (players[tab[i].num].powers[pw_super] && players[tab[i].num].mo && (players[tab[i].num].mo->state < &states[S_PLAY_SUPER_TRANS] || players[tab[i].num].mo->state > &states[S_PLAY_SUPER_TRANS9]))
+			if (supercheck)
 			{
 				colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE);
 				V_DrawSmallMappedPatch (x, y-4, 0, superprefix[players[tab[i].num].skin], colormap);
@@ -1234,23 +1243,26 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 			else
 			{
 				colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
-				if (players[tab[i].num].mo && players[tab[i].num].mo->health <= 0)
+				if (greycheck)
 					V_DrawSmallTranslucentMappedPatch (x, y-4, V_80TRANS, faceprefix[players[tab[i].num].skin], colormap);
 				else
 					V_DrawSmallMappedPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap);
 			}
 		}
 
-		if (G_GametypeUsesLives()) //show lives
-			V_DrawRightAlignedString(x, y+4, V_ALLOWLOWERCASE|((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_60TRANS), va("%dx", players[tab[i].num].lives));
+		if (G_GametypeUsesLives() && !(gametype == GT_COOP && (cv_cooplives.value == 0 || cv_cooplives.value == 3))) //show lives
+			V_DrawRightAlignedString(x, y+4, V_ALLOWLOWERCASE|(greycheck ? V_60TRANS : 0), va("%dx", players[tab[i].num].lives));
 		else if (G_TagGametype() && players[tab[i].num].pflags & PF_TAGIT)
 		{
-			if (players[tab[i].num].mo && players[tab[i].num].mo->health <= 0)
+			if (greycheck)
 				V_DrawSmallTranslucentPatch(x-32, y-4, V_60TRANS, tagico);
 			else
 				V_DrawSmallScaledPatch(x-32, y-4, 0, tagico);
 		}
 
+		if (players[tab[i].num].exiting)
+			V_DrawSmallScaledPatch(x - SHORT(exiticon->width)/2 - 1, y-3, 0, exiticon);
+
 		if (gametype == GT_RACE)
 		{
 			if (circuitmap)
@@ -1258,13 +1270,13 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I
 				if (players[tab[i].num].exiting)
 					V_DrawRightAlignedString(x+240, y, 0, va("%i:%02i.%02i", G_TicsToMinutes(players[tab[i].num].realtime,true), G_TicsToSeconds(players[tab[i].num].realtime), G_TicsToCentiseconds(players[tab[i].num].realtime)));
 				else
-					V_DrawRightAlignedString(x+240, y, ((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_60TRANS), va("%u", tab[i].count));
+					V_DrawRightAlignedString(x+240, y, (greycheck ? V_60TRANS : 0), va("%u", tab[i].count));
 			}
 			else
-				V_DrawRightAlignedString(x+240, y, ((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_60TRANS), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count)));
+				V_DrawRightAlignedString(x+240, y, (greycheck ? V_60TRANS : 0), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count)));
 		}
 		else
-			V_DrawRightAlignedString(x+240, y, ((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_60TRANS), va("%u", tab[i].count));
+			V_DrawRightAlignedString(x+240, y, (greycheck ? V_60TRANS : 0), va("%u", tab[i].count));
 
 		y += 16;
 	}
@@ -1279,6 +1291,7 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 	INT32 redplayers = 0, blueplayers = 0;
 	const UINT8 *colormap;
 	char name[MAXPLAYERNAME+1];
+	boolean greycheck, supercheck;
 
 	V_DrawFill(160, 26, 1, 154, 0); //Draw a vertical line to separate the two teams.
 	V_DrawFill(1, 26, 318, 1, 0); //And a horizontal line to make a T.
@@ -1306,10 +1319,13 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 		else //er?  not on red or blue, so ignore them
 			continue;
 
+		greycheck = greycheckdef;
+		supercheck = supercheckdef;
+
 		strlcpy(name, tab[i].name, 9);
 		V_DrawString(x + 20, y,
 		             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
-		             | ((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_TRANSLUCENT)
+		             | (greycheck ? V_TRANSLUCENT : 0)
 		             | V_ALLOWLOWERCASE, name);
 
 		if (gametype == GT_CTF)
@@ -1327,7 +1343,7 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 			HU_DrawEmeralds(x-12,y+2,tab[i].emeralds);
 		}
 
-		if (players[tab[i].num].powers[pw_super])
+		if (supercheck)
 		{
 			colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
 			V_DrawSmallMappedPatch (x, y-4, 0, superprefix[players[tab[i].num].skin], colormap);
@@ -1335,12 +1351,12 @@ void HU_DrawTeamTabRankings(playersort_t *tab, INT32 whiteplayer)
 		else
 		{
 			colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
-			if (players[tab[i].num].mo && players[tab[i].num].mo->health <= 0)
-				V_DrawSmallTranslucentMappedPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap);
+			if (greycheck)
+				V_DrawSmallTranslucentMappedPatch (x, y-4, V_80TRANS, faceprefix[players[tab[i].num].skin], colormap);
 			else
 				V_DrawSmallMappedPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap);
 		}
-		V_DrawRightAlignedThinString(x+120, y, ((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
+		V_DrawRightAlignedThinString(x+120, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
 	}
 }
 
@@ -1352,6 +1368,7 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 	INT32 i;
 	const UINT8 *colormap;
 	char name[MAXPLAYERNAME+1];
+	boolean greycheck, supercheck;
 
 	V_DrawFill(160, 26, 1, 154, 0); //Draw a vertical line to separate the two sides.
 	V_DrawFill(1, 26, 318, 1, 0); //And a horizontal line to make a T.
@@ -1359,20 +1376,26 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 
 	for (i = 0; i < scorelines; i++)
 	{
-		if (players[tab[i].num].spectator)
+		if (players[tab[i].num].spectator && gametype != GT_COOP)
 			continue; //ignore them.
 
+		greycheck = greycheckdef;
+		supercheck = supercheckdef;
+
 		strlcpy(name, tab[i].name, 9);
 		V_DrawString(x + 20, y,
 		             ((tab[i].num == whiteplayer) ? V_YELLOWMAP : 0)
-		             | ((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_TRANSLUCENT)
+		             | (greycheck ? V_TRANSLUCENT : 0)
 		             | V_ALLOWLOWERCASE, name);
 
-		if (G_GametypeUsesLives()) //show lives
+		if (G_GametypeUsesLives() && !(gametype == GT_COOP && (cv_cooplives.value == 0 || cv_cooplives.value == 3))) //show lives
 			V_DrawRightAlignedString(x, y+4, V_ALLOWLOWERCASE, va("%dx", players[tab[i].num].lives));
 		else if (G_TagGametype() && players[tab[i].num].pflags & PF_TAGIT)
 			V_DrawSmallScaledPatch(x-28, y-4, 0, tagico);
 
+		if (players[tab[i].num].exiting)
+			V_DrawSmallScaledPatch(x - SHORT(exiticon->width)/2 - 1, y-3, 0, exiticon);
+
 		// Draw emeralds
 		if (!players[tab[i].num].powers[pw_super]
 			|| ((leveltime/7) & 1))
@@ -1384,19 +1407,19 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 		if (tab[i].color == 0)
 		{
 			colormap = colormaps;
-			if (players[tab[i].num].powers[pw_super])
+			if (supercheck)
 				V_DrawSmallScaledPatch (x, y-4, 0, superprefix[players[tab[i].num].skin]);
 			else
 			{
-				if (players[tab[i].num].mo && players[tab[i].num].mo->health <= 0)
-					V_DrawSmallTranslucentPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin]);
+				if (greycheck)
+					V_DrawSmallTranslucentPatch (x, y-4, V_80TRANS, faceprefix[players[tab[i].num].skin]);
 				else
 					V_DrawSmallScaledPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin]);
 			}
 		}
 		else
 		{
-			if (players[tab[i].num].powers[pw_super])
+			if (supercheck)
 			{
 				colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
 				V_DrawSmallMappedPatch (x, y-4, 0, superprefix[players[tab[i].num].skin], colormap);
@@ -1404,8 +1427,8 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 			else
 			{
 				colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo ? players[tab[i].num].mo->color : tab[i].color, GTC_CACHE);
-				if (players[tab[i].num].mo && players[tab[i].num].mo->health <= 0)
-					V_DrawSmallTranslucentMappedPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap);
+				if (greycheck)
+					V_DrawSmallTranslucentMappedPatch (x, y-4, V_80TRANS, faceprefix[players[tab[i].num].skin], colormap);
 				else
 					V_DrawSmallMappedPatch (x, y-4, 0, faceprefix[players[tab[i].num].skin], colormap);
 			}
@@ -1419,13 +1442,13 @@ void HU_DrawDualTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scoreline
 				if (players[tab[i].num].exiting)
 					V_DrawRightAlignedThinString(x+156, y, 0, va("%i:%02i.%02i", G_TicsToMinutes(players[tab[i].num].realtime,true), G_TicsToSeconds(players[tab[i].num].realtime), G_TicsToCentiseconds(players[tab[i].num].realtime)));
 				else
-					V_DrawRightAlignedThinString(x+156, y, ((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
+					V_DrawRightAlignedThinString(x+156, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
 			}
 			else
-				V_DrawRightAlignedThinString(x+156, y, ((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_TRANSLUCENT), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count)));
+				V_DrawRightAlignedThinString(x+156, y, (greycheck ? V_TRANSLUCENT : 0), va("%i:%02i.%02i", G_TicsToMinutes(tab[i].count,true), G_TicsToSeconds(tab[i].count), G_TicsToCentiseconds(tab[i].count)));
 		}
 		else
-			V_DrawRightAlignedThinString(x+120, y, ((players[tab[i].num].mo && players[tab[i].num].mo->health > 0) ? 0 : V_TRANSLUCENT), va("%u", tab[i].count));
+			V_DrawRightAlignedThinString(x+120, y, (greycheck ? V_TRANSLUCENT : 0), va("%u", tab[i].count));
 
 		y += 16;
 		if (y > 160)
@@ -1622,61 +1645,67 @@ static void HU_DrawRankings(void)
 
 	for (j = 0; j < MAXPLAYERS; j++)
 	{
-		if (!playeringame[j] || players[j].spectator)
+		if (!playeringame[j])
+			continue;
+
+		if (gametype != GT_COOP && players[j].spectator)
 			continue;
 
 		for (i = 0; i < MAXPLAYERS; i++)
 		{
-			if (playeringame[i] && !players[i].spectator)
+			if (!playeringame[i])
+				continue;
+
+			if (gametype != GT_COOP && players[i].spectator)
+				continue;
+
+			if (gametype == GT_RACE)
 			{
-				if (gametype == GT_RACE)
+				if (circuitmap)
 				{
-					if (circuitmap)
+					if ((unsigned)players[i].laps+1 >= tab[scorelines].count && completed[i] == false)
 					{
-						if ((unsigned)players[i].laps+1 >= tab[scorelines].count && completed[i] == false)
-						{
-							tab[scorelines].count = players[i].laps+1;
-							tab[scorelines].num = i;
-							tab[scorelines].color = players[i].skincolor;
-							tab[scorelines].name = player_names[i];
-						}
-					}
-					else
-					{
-						if (players[i].realtime <= tab[scorelines].count && completed[i] == false)
-						{
-							tab[scorelines].count = players[i].realtime;
-							tab[scorelines].num = i;
-							tab[scorelines].color = players[i].skincolor;
-							tab[scorelines].name = player_names[i];
-						}
-					}
-				}
-				else if (gametype == GT_COMPETITION)
-				{
-					// todo put something more fitting for the gametype here, such as current
-					// number of categories led
-					if (players[i].score >= tab[scorelines].count && completed[i] == false)
-					{
-						tab[scorelines].count = players[i].score;
+						tab[scorelines].count = players[i].laps+1;
 						tab[scorelines].num = i;
 						tab[scorelines].color = players[i].skincolor;
 						tab[scorelines].name = player_names[i];
-						tab[scorelines].emeralds = players[i].powers[pw_emeralds];
 					}
 				}
 				else
 				{
-					if (players[i].score >= tab[scorelines].count && completed[i] == false)
+					if (players[i].realtime <= tab[scorelines].count && completed[i] == false)
 					{
-						tab[scorelines].count = players[i].score;
+						tab[scorelines].count = players[i].realtime;
 						tab[scorelines].num = i;
 						tab[scorelines].color = players[i].skincolor;
 						tab[scorelines].name = player_names[i];
-						tab[scorelines].emeralds = players[i].powers[pw_emeralds];
 					}
 				}
 			}
+			else if (gametype == GT_COMPETITION)
+			{
+				// todo put something more fitting for the gametype here, such as current
+				// number of categories led
+				if (players[i].score >= tab[scorelines].count && completed[i] == false)
+				{
+					tab[scorelines].count = players[i].score;
+					tab[scorelines].num = i;
+					tab[scorelines].color = players[i].skincolor;
+					tab[scorelines].name = player_names[i];
+					tab[scorelines].emeralds = players[i].powers[pw_emeralds];
+				}
+			}
+			else
+			{
+				if (players[i].score >= tab[scorelines].count && completed[i] == false)
+				{
+					tab[scorelines].count = players[i].score;
+					tab[scorelines].num = i;
+					tab[scorelines].color = players[i].skincolor;
+					tab[scorelines].name = player_names[i];
+					tab[scorelines].emeralds = players[i].powers[pw_emeralds];
+				}
+			}
 		}
 		completed[tab[scorelines].num] = true;
 		scorelines++;
diff --git a/src/hu_stuff.h b/src/hu_stuff.h
index 7b22f33f189b5a602ca043862b21ca2e8cc0d678..e757db85a97e43c6607b104d89a6c5c35fb4bb72 100644
--- a/src/hu_stuff.h
+++ b/src/hu_stuff.h
@@ -21,7 +21,7 @@
 //------------------------------------
 //           heads up font
 //------------------------------------
-#define HU_FONTSTART '\x1F' // the first font character
+#define HU_FONTSTART '\x16' // the first font character
 #define HU_FONTEND '~'
 
 #define HU_FONTSIZE (HU_FONTEND - HU_FONTSTART + 1)
@@ -71,6 +71,7 @@ extern patch_t *rmatcico;
 extern patch_t *bmatcico;
 extern patch_t *tagico;
 extern patch_t *tallminus;
+extern patch_t *tokenicon;
 
 // set true when entering a chat message
 extern boolean chat_on;
@@ -78,9 +79,6 @@ extern boolean chat_on;
 // set true whenever the tab rankings are being shown for any reason
 extern boolean hu_showscores;
 
-// P_DeathThink sets this true to show scores while dead, in multiplayer
-extern boolean playerdeadview;
-
 // init heads up data at game startup.
 void HU_Init(void);
 
diff --git a/src/info.c b/src/info.c
index 7d6e1fededd5797f7c8f6043c038f65088e86389..2d6c9a3d12bb9ad518506531b6ad8acd89bfae61 100644
--- a/src/info.c
+++ b/src/info.c
@@ -114,7 +114,6 @@ char sprnames[NUMSPRITES + 1][5] =
 	// Collectible Items
 	"RING",
 	"TRNG", // Team Rings
-	"EMMY", // emerald test
 	"TOKE", // Special Stage Token
 	"RFLG", // Red CTF Flag
 	"BFLG", // Blue CTF Flag
@@ -131,6 +130,8 @@ char sprnames[NUMSPRITES + 1][5] =
 	"SPIK", // Spike Ball
 	"SFLM", // Spin fire
 	"USPK", // Floor spike
+	"WSPK", // Wall spike
+	"WSPB", // Wall spike base
 	"STPT", // Starpost
 	"BMNE", // Big floating mine
 
@@ -181,6 +182,12 @@ char sprnames[NUMSPRITES + 1][5] =
 	"FWR4",
 	"BUS1", // GFZ Bush w/ berries
 	"BUS2", // GFZ Bush w/o berries
+	// Trees (both GFZ and misc)
+	"TRE1", // GFZ
+	"TRE2", // Checker
+	"TRE3", // Frozen Hillside
+	"TRE4", // Polygon
+	"TRE5", // Bush tree
 
 	// Techno Hill Scenery
 	"THZP", // Techno Hill Zone Plant
@@ -204,6 +211,10 @@ char sprnames[NUMSPRITES + 1][5] =
 	"BMCH", // Big Mace Chain
 	"SMCE", // Small Mace
 	"BMCE", // Big Mace
+	"YSPB", // Yellow spring on a ball
+	"RSPB", // Red spring on a ball
+	"SFBR", // Small Firebar
+	"BFBR", // Big Firebar
 
 	// Arid Canyon Scenery
 	"BTBL", // Big tumbleweed
@@ -219,9 +230,11 @@ char sprnames[NUMSPRITES + 1][5] =
 	// Egg Rock Scenery
 
 	// Christmas Scenery
-	"XMS1",
-	"XMS2",
-	"XMS3",
+	"XMS1", // Christmas Pole
+	"XMS2", // Candy Cane
+	"XMS3", // Snowman
+	"XMS4", // Lamppost
+	"XMS5", // Hanging Star
 
 	// Botanic Serenity Scenery
 	"BSZ1", // Tall flowers
@@ -924,13 +937,13 @@ state_t states[NUMSTATES] =
 	{SPR_EGGM, 20,   2, {NULL},                    0, 0, S_EGGMOBILE_STND},   // S_EGGMOBILE_RATK10
 	{SPR_EGGM,  3,  12, {NULL},                    0, 0, S_EGGMOBILE_PANIC2}, // S_EGGMOBILE_PANIC1
 	{SPR_EGGM,  4,   4, {A_Boss1Spikeballs},       0, 4, S_EGGMOBILE_PANIC3}, // S_EGGMOBILE_PANIC2
-	{SPR_EGGM,  3,   5, {NULL},                    0, 0, S_EGGMOBILE_PANIC4}, // S_EGGMOBILE_PANIC3
+	{SPR_EGGM,  3,   8, {NULL},                    0, 0, S_EGGMOBILE_PANIC4}, // S_EGGMOBILE_PANIC3
 	{SPR_EGGM,  4,   4, {A_Boss1Spikeballs},       1, 4, S_EGGMOBILE_PANIC5}, // S_EGGMOBILE_PANIC4
-	{SPR_EGGM,  3,   5, {NULL},                    0, 0, S_EGGMOBILE_PANIC6}, // S_EGGMOBILE_PANIC5
+	{SPR_EGGM,  3,   8, {NULL},                    0, 0, S_EGGMOBILE_PANIC6}, // S_EGGMOBILE_PANIC5
 	{SPR_EGGM,  4,   4, {A_Boss1Spikeballs},       2, 4, S_EGGMOBILE_PANIC7}, // S_EGGMOBILE_PANIC6
-	{SPR_EGGM,  3,   5, {NULL},                    0, 0, S_EGGMOBILE_PANIC8}, // S_EGGMOBILE_PANIC7
+	{SPR_EGGM,  3,   8, {NULL},                    0, 0, S_EGGMOBILE_PANIC8}, // S_EGGMOBILE_PANIC7
 	{SPR_EGGM,  4,   4, {A_Boss1Spikeballs},       3, 4, S_EGGMOBILE_PANIC9}, // S_EGGMOBILE_PANIC8
-	{SPR_EGGM,  3,   5, {NULL},                    0, 0, S_EGGMOBILE_PANIC10},// S_EGGMOBILE_PANIC9
+	{SPR_EGGM,  3,   8, {NULL},                    0, 0, S_EGGMOBILE_PANIC10},// S_EGGMOBILE_PANIC9
 	{SPR_EGGM,  0,  35, {A_SkullAttack},           0, 0, S_EGGMOBILE_STND},   // S_EGGMOBILE_PANIC10
 	{SPR_EGGM, 21,  24, {A_Pain},                  0, 0, S_EGGMOBILE_PAIN2},  // S_EGGMOBILE_PAIN
 	{SPR_EGGM, 21,  16, {A_SkullAttack},           1, 1, S_EGGMOBILE_STND},   // S_EGGMOBILE_PAIN2
@@ -1407,14 +1420,10 @@ state_t states[NUMSTATES] =
 	{SPR_GWLR, 2, 1, {NULL}, 0, 0, S_GRAVWELLRED},    // S_GRAVWELLRED3
 
 	// Individual Team Rings (now with shield attracting action! =P)
-	{SPR_TRNG, FF_ANIMATE|FF_GLOBALANIM, -1, {NULL}, 23, 1, S_TEAMRING},  // S_TEAMRING1
+	{SPR_TRNG, FF_ANIMATE|FF_GLOBALANIM, -1, {NULL}, 23, 1, S_TEAMRING},  // S_TEAMRING
 
 	// Special Stage Token
-	{SPR_EMMY, FF_ANIMATE|FF_FULLBRIGHT, -1, {NULL}, 6, 2, S_EMMY}, // S_EMMY
-
-	// Special Stage Token
-	{SPR_TOKE, FF_TRANS50|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_TOKEN
-	{SPR_TOKE, FF_TRANS50|FF_FULLBRIGHT,  1, {A_CapeChase}, 0, 0, S_MOVINGTOKEN}, // S_MOVINGTOKEN
+	{SPR_TOKE, FF_ANIMATE|FF_FULLBRIGHT, -1, {NULL}, 19, 1, S_TOKEN}, // S_TOKEN
 
 	// CTF Flags
 	{SPR_RFLG, 0, -1, {NULL}, 0, 0, S_NULL}, // S_REDFLAG
@@ -1558,13 +1567,24 @@ state_t states[NUMSTATES] =
 
 	// Floor Spike
 	{SPR_USPK, 0,-1, {A_SpikeRetract}, 1, 0, S_SPIKE2}, // S_SPIKE1 -- Fully extended
-	{SPR_USPK, 5, 2, {A_Pain},         0, 0, S_SPIKE3}, // S_SPIKE2
-	{SPR_USPK, 4, 2, {NULL},           0, 0, S_SPIKE4}, // S_SPIKE3
+	{SPR_USPK, 1, 2, {A_Pain},         0, 0, S_SPIKE3}, // S_SPIKE2
+	{SPR_USPK, 2, 2, {NULL},           0, 0, S_SPIKE4}, // S_SPIKE3
 	{SPR_USPK, 3,-1, {A_SpikeRetract}, 0, 0, S_SPIKE5}, // S_SPIKE4 -- Fully retracted
-	{SPR_USPK, 4, 2, {A_Pain},         0, 0, S_SPIKE6}, // S_SPIKE5
-	{SPR_USPK, 5, 2, {NULL},           0, 0, S_SPIKE1}, // S_SPIKE6
-	{SPR_USPK, 1,-1, {NULL}, 0, 0, S_NULL}, // S_SPIKED1 -- Busted spike particles
-	{SPR_USPK, 2,-1, {NULL}, 0, 0, S_NULL}, // S_SPIKED2
+	{SPR_USPK, 2, 2, {A_Pain},         0, 0, S_SPIKE6}, // S_SPIKE5
+	{SPR_USPK, 1, 2, {NULL},           0, 0, S_SPIKE1}, // S_SPIKE6
+	{SPR_USPK, 4,-1, {NULL}, 0, 0, S_NULL}, // S_SPIKED1 -- Busted spike particles
+	{SPR_USPK, 5,-1, {NULL}, 0, 0, S_NULL}, // S_SPIKED2
+
+	// Wall Spike
+	{SPR_WSPK, 0|FF_PAPERSPRITE,-1, {A_SpikeRetract}, 1, 0, S_WALLSPIKE2}, // S_WALLSPIKE1 -- Fully extended
+	{SPR_WSPK, 1|FF_PAPERSPRITE, 2, {A_Pain},         0, 0, S_WALLSPIKE3}, // S_WALLSPIKE2
+	{SPR_WSPK, 2|FF_PAPERSPRITE, 2, {NULL},           0, 0, S_WALLSPIKE4}, // S_WALLSPIKE3
+	{SPR_WSPK, 3|FF_PAPERSPRITE,-1, {A_SpikeRetract}, 0, 0, S_WALLSPIKE5}, // S_WALLSPIKE4 -- Fully retracted
+	{SPR_WSPK, 2|FF_PAPERSPRITE, 2, {A_Pain},         0, 0, S_WALLSPIKE6}, // S_WALLSPIKE5
+	{SPR_WSPK, 1|FF_PAPERSPRITE, 2, {NULL},           0, 0, S_WALLSPIKE1}, // S_WALLSPIKE6
+	{SPR_WSPB, 0|FF_PAPERSPRITE,-1, {NULL}, 0, 0, S_NULL}, // S_WALLSPIKEBASE -- Base
+	{SPR_WSPK, 4,-1, {NULL}, 0, 0, S_NULL}, // S_WALLSPIKED1 -- Busted spike particles
+	{SPR_WSPK, 5,-1, {NULL}, 0, 0, S_NULL}, // S_WALLSPIKED2
 
 	// Starpost
 	{SPR_STPT, 0            , -1, {NULL},  0, 0, S_NULL},           // S_STARPOST_IDLE
@@ -1659,22 +1679,22 @@ state_t states[NUMSTATES] =
 	{SPR_TVRI, 2, 18, {A_RingBox}, 0, 0, S_NULL}, // S_RING_ICON2
 
 	{SPR_TVPI, FF_ANIMATE|2, 18, {NULL}, 3, 4, S_PITY_ICON2}, // S_PITY_ICON1
-	{SPR_TVPI, 2, 18, {A_PityShield}, 0, 0, S_NULL},  // S_PITY_ICON2
+	{SPR_TVPI, 2, 18, {A_GiveShield}, SH_PITY, 0, S_NULL},  // S_PITY_ICON2
 
 	{SPR_TVAT, FF_ANIMATE|2, 18, {NULL}, 3, 4, S_ATTRACT_ICON2}, // S_ATTRACT_ICON1
-	{SPR_TVAT, 2, 18, {A_RingShield},0, 0, S_NULL}, // S_ATTRACT_ICON2
+	{SPR_TVAT, 2, 18, {A_GiveShield}, SH_ATTRACT, 0, S_NULL}, // S_ATTRACT_ICON2
 
 	{SPR_TVFO, FF_ANIMATE|2, 18, {NULL}, 3, 4, S_FORCE_ICON2}, // S_FORCE_ICON1
-	{SPR_TVFO, 2, 18, {A_ForceShield}, 1, 0, S_NULL}, // S_FORCE_ICON2
+	{SPR_TVFO, 2, 18, {A_GiveShield}, SH_FORCE|1, 0, S_NULL}, // S_FORCE_ICON2
 
 	{SPR_TVAR, FF_ANIMATE|2, 18, {NULL}, 3, 4, S_ARMAGEDDON_ICON2}, // S_ARMAGEDDON_ICON1
-	{SPR_TVAR, 2, 18, {A_BombShield}, 0, 0, S_NULL}, // S_ARMAGEDDON_ICON2
+	{SPR_TVAR, 2, 18, {A_GiveShield}, SH_ARMAGEDDON, 0, S_NULL}, // S_ARMAGEDDON_ICON2
 
 	{SPR_TVWW, FF_ANIMATE|2, 18, {NULL}, 3, 4, S_WHIRLWIND_ICON2}, // S_WHIRLWIND_ICON1
-	{SPR_TVWW, 2, 18, {A_JumpShield}, 0, 0, S_NULL}, // S_WHIRLWIND_ICON2
+	{SPR_TVWW, 2, 18, {A_GiveShield}, SH_WHIRLWIND, 0, S_NULL}, // S_WHIRLWIND_ICON2
 
 	{SPR_TVEL, FF_ANIMATE|2, 18, {NULL}, 3, 4, S_ELEMENTAL_ICON2}, // S_ELEMENTAL_ICON1
-	{SPR_TVEL, 2, 18, {A_WaterShield}, 0, 0, S_NULL}, // S_ELEMENTAL_ICON2
+	{SPR_TVEL, 2, 18, {A_GiveShield}, SH_ELEMENTAL, 0, S_NULL}, // S_ELEMENTAL_ICON2
 
 	{SPR_TVSS, FF_ANIMATE|2, 18, {NULL}, 3, 4, S_SNEAKERS_ICON2}, // S_SNEAKERS_ICON1
 	{SPR_TVSS, 2, 18, {A_SuperSneakers}, 0, 0, S_NULL}, // S_SNEAKERS_ICON2
@@ -1704,13 +1724,13 @@ state_t states[NUMSTATES] =
 	{SPR_TVTK, 2, 18, {A_AwardScore}, 0, 0, S_NULL}, // S_SCORE10K_ICON2
 
 	{SPR_TVFL, FF_ANIMATE|2, 18, {NULL}, 3, 4, S_FLAMEAURA_ICON2}, // S_FLAMEAURA_ICON1
-	{SPR_TVFL, 2, 18, {A_FlameShield}, 0, 0, S_NULL}, // S_FLAMEAURA_ICON2
+	{SPR_TVFL, 2, 18, {A_GiveShield}, SH_FLAMEAURA, 0, S_NULL}, // S_FLAMEAURA_ICON2
 
 	{SPR_TVBB, FF_ANIMATE|2, 18, {NULL}, 3, 4, S_BUBBLEWRAP_ICON2}, // S_BUBBLEWRAP_ICON1
-	{SPR_TVBB, 2, 18, {A_BubbleShield}, 0, 0, S_NULL}, // S_BUBBLERWAP_ICON2
+	{SPR_TVBB, 2, 18, {A_GiveShield}, SH_BUBBLEWRAP, 0, S_NULL}, // S_BUBBLERWAP_ICON2
 
 	{SPR_TVZP, FF_ANIMATE|2, 18, {NULL}, 3, 4, S_THUNDERCOIN_ICON2}, // S_THUNDERCOIN_ICON1
-	{SPR_TVZP, 2, 18, {A_ThunderShield}, 0, 0, S_NULL}, // S_THUNDERCOIN_ICON2
+	{SPR_TVZP, 2, 18, {A_GiveShield}, SH_THUNDERCOIN, 0, S_NULL}, // S_THUNDERCOIN_ICON2
 
 	// ---
 
@@ -1759,6 +1779,18 @@ state_t states[NUMSTATES] =
 	{SPR_BUS1, 0, -1, {NULL}, 0, 0, S_NULL},       // S_BERRYBUSH
 	{SPR_BUS2, 0, -1, {NULL}, 0, 0, S_NULL},       // S_BUSH
 
+	// Trees
+	{SPR_TRE1, 0, -1, {NULL}, 0, 0, S_NULL}, // S_GFZTREE
+	{SPR_TRE1, 1, -1, {NULL}, 0, 0, S_NULL}, // S_GFZBERRYTREE
+	{SPR_TRE1, 2, -1, {NULL}, 0, 0, S_NULL}, // S_GFZCHERRYTREE
+	{SPR_TRE2, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CHECKERTREE
+	{SPR_TRE2, 1, -1, {NULL}, 0, 0, S_NULL}, // S_CHECKERSUNSETTREE
+	{SPR_TRE3, 0, -1, {NULL}, 0, 0, S_NULL}, // S_FHZTREE
+	{SPR_TRE3, 1, -1, {NULL}, 0, 0, S_NULL}, // S_FHZPINKTREE
+	{SPR_TRE4, 0, -1, {NULL}, 0, 0, S_NULL}, // S_POLYGONTREE
+	{SPR_TRE5, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BUSHTREE
+	{SPR_TRE5, 1, -1, {NULL}, 0, 0, S_NULL}, // S_BUSHREDTREE
+
 	{SPR_THZP, FF_ANIMATE, -1, {NULL},  7, 4, S_NULL}, // S_THZFLOWERA
 	{SPR_FWR5, FF_ANIMATE, -1, {NULL}, 19, 2, S_NULL}, // S_THZFLOWERB
 
@@ -1767,6 +1799,7 @@ state_t states[NUMSTATES] =
 
 	// Deep Sea Gargoyle
 	{SPR_GARG, 0, -1, {NULL}, 0, 0, S_NULL},  // S_GARGOYLE
+	{SPR_GARG, 1, -1, {NULL}, 0, 0, S_NULL},  // S_BIGGARGOYLE
 
 	// DSZ Seaweed
 	{SPR_SEWE, 0, -1, {NULL}, 0, 0, S_SEAWEED2}, // S_SEAWEED1
@@ -1801,13 +1834,13 @@ state_t states[NUMSTATES] =
 	{SPR_CHAN, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CEZCHAIN
 
 	// Flame
-	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS20,    3, {A_FlameParticle}, 3, 0, S_FLAME2}, // S_FLAME1
-	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS20|1,  3,            {NULL}, 0, 0, S_FLAME3}, // S_FLAME2
-	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS20|2,  3, {A_FlameParticle}, 3, 0, S_FLAME4}, // S_FLAME3
-	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS20|3,  3,            {NULL}, 0, 0, S_FLAME5}, // S_FLAME4
-	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS20|4,  3, {A_FlameParticle}, 3, 0, S_FLAME6}, // S_FLAME5
-	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS20|5,  3,            {NULL}, 0, 0, S_FLAME1}, // S_FLAME6
-	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS10|6, 24,            {NULL}, 0, 0, S_NULL},   // S_FLAMEPARTICLE
+	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS20,    3, {A_FlameParticle}, 3, FRACUNIT/2, S_FLAME2}, // S_FLAME1
+	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS20|1,  3,            {NULL}, 0, 0         , S_FLAME3}, // S_FLAME2
+	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS20|2,  3, {A_FlameParticle}, 3, FRACUNIT/2, S_FLAME4}, // S_FLAME3
+	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS20|3,  3,            {NULL}, 0, 0         , S_FLAME5}, // S_FLAME4
+	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS20|4,  3, {A_FlameParticle}, 3, FRACUNIT/2, S_FLAME6}, // S_FLAME5
+	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS20|5,  3,            {NULL}, 0, 0         , S_FLAME1}, // S_FLAME6
+	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS10|6, 24,            {NULL}, 0, 0         , S_NULL},   // S_FLAMEPARTICLE
 
 	{SPR_FLAM, FF_FULLBRIGHT|FF_TRANS20|FF_ANIMATE, -1, {NULL}, 5, 3, S_FLAME2}, // S_FLAMEREST
 
@@ -1818,31 +1851,75 @@ state_t states[NUMSTATES] =
 	{SPR_NULL, 0, -1, {NULL},          0, 0, S_SLING2}, // S_SLING1
 	{SPR_NULL, 0, -1, {A_SlingAppear}, 0, 0, S_NULL},   // S_SLING2
 
-	// Small Mace Chain
-	{SPR_SMCH, 0, 1, {A_MaceRotate}, 0, 0, S_SMALLMACECHAIN}, // S_SMALLMACECHAIN
-
-	// Big Mace Chain
-	{SPR_BMCH, 0, 1, {A_MaceRotate}, 0, 0, S_BIGMACECHAIN}, // S_BIGMACECHAIN
-
-	// Small Mace
-	{SPR_SMCE, 0, 1, {A_MaceRotate}, 0, 0, S_SMALLMACE}, // S_SMALLMACE
-
-	// Big Mace
-	{SPR_BMCE, 0, 1, {A_MaceRotate}, 0, 0, S_BIGMACE}, // S_BIGMACE
+	// CEZ maces and chains
+	{SPR_SMCH, 0, -1, {NULL}, 0, 0, S_SMALLMACECHAIN}, // S_SMALLMACECHAIN
+	{SPR_BMCH, 0, -1, {NULL}, 0, 0, S_BIGMACECHAIN},   // S_BIGMACECHAIN
+	{SPR_SMCE, 0, -1, {NULL}, 0, 0, S_SMALLMACE},      // S_SMALLMACE
+	{SPR_BMCE, 0, -1, {NULL}, 0, 0, S_BIGMACE},        // S_BIGMACE
+
+	// Yellow spring on a ball
+	{SPR_YSPB, 0, -1, {NULL},   0, 0, S_NULL},              // S_YELLOWSPRINGBALL
+	{SPR_YSPB, 4,  4, {A_Pain}, 0, 0, S_YELLOWSPRINGBALL3}, // S_YELLOWSPRINGBALL2
+	{SPR_YSPB, 3,  1, {NULL},   0, 0, S_YELLOWSPRINGBALL4}, // S_YELLOWSPRINGBALL3
+	{SPR_YSPB, 2,  1, {NULL},   0, 0, S_YELLOWSPRINGBALL5}, // S_YELLOWSPRINGBALL4
+	{SPR_YSPB, 1,  1, {NULL},   0, 0, S_YELLOWSPRINGBALL},  // S_YELLOWSPRINGBALL5
+
+	// Red spring on a ball
+	{SPR_RSPB, 0, -1, {NULL},   0, 0, S_NULL},           // S_REDSPRINGBALL
+	{SPR_RSPB, 4,  4, {A_Pain}, 0, 0, S_REDSPRINGBALL3}, // S_REDSPRINGBALL2
+	{SPR_RSPB, 3,  1, {NULL},   0, 0, S_REDSPRINGBALL4}, // S_REDSPRINGBALL3
+	{SPR_RSPB, 2,  1, {NULL},   0, 0, S_REDSPRINGBALL5}, // S_REDSPRINGBALL4
+	{SPR_RSPB, 1,  1, {NULL},   0, 0, S_REDSPRINGBALL},  // S_REDSPRINGBALL5
+
+	// Small Firebar
+	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20,     1, {A_FlameParticle}, 3, FRACUNIT/3, S_SMALLFIREBAR2},  // S_SMALLFIREBAR1
+	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20| 1,  1, {NULL},            0, 0,          S_SMALLFIREBAR3},  // S_SMALLFIREBAR2
+	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20| 2,  1, {A_FlameParticle}, 3, FRACUNIT/3, S_SMALLFIREBAR4},  // S_SMALLFIREBAR3
+	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20| 3,  1, {NULL},            0, 0,          S_SMALLFIREBAR5},  // S_SMALLFIREBAR4
+	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20| 4,  1, {A_FlameParticle}, 3, FRACUNIT/3, S_SMALLFIREBAR6},  // S_SMALLFIREBAR5
+	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20| 5,  1, {NULL},            0, 0,          S_SMALLFIREBAR7},  // S_SMALLFIREBAR6
+	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20| 6,  1, {A_FlameParticle}, 3, FRACUNIT/3, S_SMALLFIREBAR8},  // S_SMALLFIREBAR7
+	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20| 7,  1, {NULL},            0, 0,          S_SMALLFIREBAR9},  // S_SMALLFIREBAR8
+	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20| 8,  1, {A_FlameParticle}, 3, FRACUNIT/3, S_SMALLFIREBAR10}, // S_SMALLFIREBAR9
+	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20| 9,  1, {NULL},            0, 0,          S_SMALLFIREBAR11}, // S_SMALLFIREBAR10
+	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20|10,  1, {A_FlameParticle}, 3, FRACUNIT/3, S_SMALLFIREBAR12}, // S_SMALLFIREBAR11
+	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20|11,  1, {NULL},            0, 0,          S_SMALLFIREBAR13}, // S_SMALLFIREBAR12
+	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20|12,  1, {A_FlameParticle}, 3, FRACUNIT/3, S_SMALLFIREBAR14}, // S_SMALLFIREBAR13
+	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20|13,  1, {NULL},            0, 0,          S_SMALLFIREBAR15}, // S_SMALLFIREBAR14
+	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20|14,  1, {A_FlameParticle}, 3, FRACUNIT/3, S_SMALLFIREBAR16}, // S_SMALLFIREBAR15
+	{SPR_SFBR, FF_FULLBRIGHT|FF_TRANS20|15,  1, {NULL},            0, 0,          S_SMALLFIREBAR1},  // S_SMALLFIREBAR16
+
+	// Big Firebar
+	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20,     1, {A_FlameParticle}, 3, FRACUNIT/2, S_BIGFIREBAR2},  // S_BIGFIREBAR1
+	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20| 1,  1, {NULL},            0, 0,          S_BIGFIREBAR3},  // S_BIGFIREBAR2
+	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20| 2,  1, {A_FlameParticle}, 3, FRACUNIT/2, S_BIGFIREBAR4},  // S_BIGFIREBAR3
+	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20| 3,  1, {NULL},            0, 0,          S_BIGFIREBAR5},  // S_BIGFIREBAR4
+	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20| 4,  1, {A_FlameParticle}, 3, FRACUNIT/2, S_BIGFIREBAR6},  // S_BIGFIREBAR5
+	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20| 5,  1, {NULL},            0, 0,          S_BIGFIREBAR7},  // S_BIGFIREBAR6
+	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20| 6,  1, {A_FlameParticle}, 3, FRACUNIT/2, S_BIGFIREBAR8},  // S_BIGFIREBAR7
+	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20| 7,  1, {NULL},            0, 0,          S_BIGFIREBAR9},  // S_BIGFIREBAR8
+	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20| 8,  1, {A_FlameParticle}, 3, FRACUNIT/2, S_BIGFIREBAR10}, // S_BIGFIREBAR9
+	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20| 9,  1, {NULL},            0, 0,          S_BIGFIREBAR11}, // S_BIGFIREBAR10
+	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20|10,  1, {A_FlameParticle}, 3, FRACUNIT/2, S_BIGFIREBAR12}, // S_BIGFIREBAR11
+	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20|11,  1, {NULL},            0, 0,          S_BIGFIREBAR13}, // S_BIGFIREBAR12
+	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20|12,  1, {A_FlameParticle}, 3, FRACUNIT/2, S_BIGFIREBAR14}, // S_BIGFIREBAR13
+	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20|13,  1, {NULL},            0, 0,          S_BIGFIREBAR15}, // S_BIGFIREBAR14
+	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20|14,  1, {A_FlameParticle}, 3, FRACUNIT/2, S_BIGFIREBAR16}, // S_BIGFIREBAR15
+	{SPR_BFBR, FF_FULLBRIGHT|FF_TRANS20|15,  1, {NULL},            0, 0,          S_BIGFIREBAR1},  // S_BIGFIREBAR16
 
 	// CEZ Flower
 	{SPR_FWR4, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CEZFLOWER1
 
 	// Big Tumbleweed
-	{SPR_BTBL, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BIGTUMBLEWEED
-	{SPR_BTBL, 0, 5, {NULL}, 0, 0, S_BIGTUMBLEWEED_ROLL2}, // S_BIGTUMBLEWEED_ROLL1
-	{SPR_BTBL, 1, 5, {NULL}, 0, 0, S_BIGTUMBLEWEED_ROLL3}, // S_BIGTUMBLEWEED_ROLL2
-	{SPR_BTBL, 2, 5, {NULL}, 0, 0, S_BIGTUMBLEWEED_ROLL4}, // S_BIGTUMBLEWEED_ROLL3
-	{SPR_BTBL, 3, 5, {NULL}, 0, 0, S_BIGTUMBLEWEED_ROLL5}, // S_BIGTUMBLEWEED_ROLL4
-	{SPR_BTBL, 4, 5, {NULL}, 0, 0, S_BIGTUMBLEWEED_ROLL6}, // S_BIGTUMBLEWEED_ROLL5
-	{SPR_BTBL, 5, 5, {NULL}, 0, 0, S_BIGTUMBLEWEED_ROLL7}, // S_BIGTUMBLEWEED_ROLL6
-	{SPR_BTBL, 6, 5, {NULL}, 0, 0, S_BIGTUMBLEWEED_ROLL8}, // S_BIGTUMBLEWEED_ROLL7
-	{SPR_BTBL, 7, 5, {NULL}, 0, 0, S_BIGTUMBLEWEED_ROLL1}, // S_BIGTUMBLEWEED_ROLL8
+	{SPR_BTBL, 0, -1, {NULL}, 0, 0, S_NULL},                // S_BIGTUMBLEWEED
+	{SPR_BTBL, 0,  5, {NULL}, 0, 0, S_BIGTUMBLEWEED_ROLL2}, // S_BIGTUMBLEWEED_ROLL1
+	{SPR_BTBL, 1,  5, {NULL}, 0, 0, S_BIGTUMBLEWEED_ROLL3}, // S_BIGTUMBLEWEED_ROLL2
+	{SPR_BTBL, 2,  5, {NULL}, 0, 0, S_BIGTUMBLEWEED_ROLL4}, // S_BIGTUMBLEWEED_ROLL3
+	{SPR_BTBL, 3,  5, {NULL}, 0, 0, S_BIGTUMBLEWEED_ROLL5}, // S_BIGTUMBLEWEED_ROLL4
+	{SPR_BTBL, 4,  5, {NULL}, 0, 0, S_BIGTUMBLEWEED_ROLL6}, // S_BIGTUMBLEWEED_ROLL5
+	{SPR_BTBL, 5,  5, {NULL}, 0, 0, S_BIGTUMBLEWEED_ROLL7}, // S_BIGTUMBLEWEED_ROLL6
+	{SPR_BTBL, 6,  5, {NULL}, 0, 0, S_BIGTUMBLEWEED_ROLL8}, // S_BIGTUMBLEWEED_ROLL7
+	{SPR_BTBL, 7,  5, {NULL}, 0, 0, S_BIGTUMBLEWEED_ROLL1}, // S_BIGTUMBLEWEED_ROLL8
 
 	// Little Tumbleweed
 	{SPR_STBL, 0, -1, {NULL}, 0, 0, S_NULL}, // S_LITTLETUMBLEWEED
@@ -1933,6 +2010,13 @@ state_t states[NUMSTATES] =
 	{SPR_XMS1, 0, -1, {NULL}, 0, 0, S_NULL}, // S_XMASPOLE
 	{SPR_XMS2, 0, -1, {NULL}, 0, 0, S_NULL}, // S_CANDYCANE
 	{SPR_XMS3, 0, -1, {NULL}, 0, 0, S_NULL}, // S_SNOWMAN
+	{SPR_XMS3, 1, -1, {NULL}, 0, 0, S_NULL}, // S_SNOWMANHAT
+	{SPR_XMS4, 0, -1, {NULL}, 0, 0, S_NULL}, // S_LAMPPOST1
+	{SPR_XMS4, 1, -1, {NULL}, 0, 0, S_NULL}, // S_LAMPPOST2
+	{SPR_XMS5, 0, -1, {NULL}, 0, 0, S_NULL}, // S_HANGSTAR
+	// Xmas GFZ bushes
+	{SPR_BUS1, 1, -1, {NULL}, 0, 0, S_NULL}, // S_BERRYBUSH
+	{SPR_BUS2, 1, -1, {NULL}, 0, 0, S_NULL}, // S_BUSH
 
 	// Loads of Botanic Serenity bullshit
 	{SPR_BSZ1, 0, -1, {NULL}, 0, 0, S_NULL}, // S_BSZTALLFLOWER_RED
@@ -2122,16 +2206,12 @@ state_t states[NUMSTATES] =
 	{SPR_ELEM, FF_FULLBRIGHT|20, 1, {NULL}, 0, 0, S_ELEMF10}, // S_ELEMF9
 	{SPR_NULL, 0,                1, {NULL}, 0, 0, S_ELEMF1 }, // S_ELEMF10
 
-	{SPR_PITY, FF_TRANS20  , 1, {NULL}, 0, 0, S_PITY2 }, // S_PITY1
-	{SPR_PITY, FF_TRANS20|1, 1, {NULL}, 0, 0, S_PITY3 }, // S_PITY2
-	{SPR_PITY, FF_TRANS20  , 1, {NULL}, 0, 0, S_PITY4 }, // S_PITY3
-	{SPR_PITY, FF_TRANS20|2, 1, {NULL}, 0, 0, S_PITY5 }, // S_PITY4
-	{SPR_PITY, FF_TRANS20  , 1, {NULL}, 0, 0, S_PITY6 }, // S_PITY5
-	{SPR_PITY, FF_TRANS20|3, 1, {NULL}, 0, 0, S_PITY7 }, // S_PITY6
-	{SPR_PITY, FF_TRANS20  , 1, {NULL}, 0, 0, S_PITY8 }, // S_PITY7
-	{SPR_PITY, FF_TRANS20|4, 1, {NULL}, 0, 0, S_PITY9 }, // S_PITY8
-	{SPR_PITY, FF_TRANS20  , 1, {NULL}, 0, 0, S_PITY10}, // S_PITY9
-	{SPR_PITY, FF_TRANS20|5, 1, {NULL}, 0, 0, S_PITY1 }, // S_PITY10
+	{SPR_PITY, FF_TRANS30  , 2, {NULL}, 0, 0, S_PITY2}, // S_PITY1
+	{SPR_PITY, FF_TRANS30|1, 2, {NULL}, 0, 0, S_PITY3}, // S_PITY2
+	{SPR_PITY, FF_TRANS30|2, 2, {NULL}, 0, 0, S_PITY4}, // S_PITY3
+	{SPR_PITY, FF_TRANS20|3, 2, {NULL}, 0, 0, S_PITY5}, // S_PITY4
+	{SPR_PITY, FF_TRANS30|4, 2, {NULL}, 0, 0, S_PITY6}, // S_PITY5
+	{SPR_PITY, FF_TRANS20|5, 2, {NULL}, 0, 0, S_PITY1}, // S_PITY6
 
 	{SPR_FIRS, FF_FULLBRIGHT|FF_TRANS40  , 2, {NULL}, 0, 0, S_FIRS2}, // S_FIRS1
 	{SPR_FIRS, FF_FULLBRIGHT|FF_TRANS40|1, 2, {NULL}, 0, 0, S_FIRS3}, // S_FIRS2
@@ -2501,7 +2581,7 @@ state_t states[NUMSTATES] =
 
 	// Particle sprite
 	{SPR_PRTL, FF_FULLBRIGHT|FF_TRANS70, 2*TICRATE, {NULL}, 0, 0, S_NULL}, // S_PARTICLE
-	{SPR_NULL,     0,         1, {A_ParticleSpawn}, 0, 0, S_PARTICLEGEN}, // S_PARTICLEGEN
+	{SPR_NULL,     0,          3, {A_ParticleSpawn}, 0, 0, S_PARTICLEGEN}, // S_PARTICLEGEN
 
 	{SPR_SCOR, 0, 32, {A_ScoreRise}, 0, 0, S_NULL}, // S_SCRA  - 100
 	{SPR_SCOR, 1, 32, {A_ScoreRise}, 0, 0, S_NULL}, // S_SCRB  - 200
@@ -2864,16 +2944,11 @@ state_t states[NUMSTATES] =
 	{SPR_NWNG, 1, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSWING_XMAS
 
 	// NiGHTS Paraloop Powerups
-	{SPR_NULL, 0, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSPOWERUP1
-	{SPR_NPRU, 0, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSPOWERUP2
-	{SPR_NULL, 0, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSPOWERUP3
-	{SPR_NPRU, 1, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSPOWERUP4
-	{SPR_NULL, 0, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSPOWERUP5
-	{SPR_NPRU, 2, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSPOWERUP6
-	{SPR_NULL, 0, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSPOWERUP7
-	{SPR_NPRU, 3, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSPOWERUP8
-	{SPR_NULL, 0, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSPOWERUP9
-	{SPR_NPRU, 4, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSPOWERUP10
+	{SPR_NPRU, 0, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSSUPERLOOP
+	{SPR_NPRU, 1, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSDRILLREFILL
+	{SPR_NPRU, 2, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSHELPER
+	{SPR_NPRU, 3, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSEXTRATIME
+	{SPR_NPRU, 4, -1, {NULL}, 0, 0, S_NULL}, // S_NIGHTSLINKFREEZE
 
 	{SPR_CAPS, 0, -1, {NULL}, 0, 0, S_NULL}, // S_EGGCAPSULE
 
@@ -4091,7 +4166,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		2*FRACUNIT,     // speed
+		4*FRACUNIT,     // speed
 		13*FRACUNIT,    // radius
 		26*FRACUNIT,    // height
 		0,              // display offset
@@ -5156,9 +5231,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_EMMY
+	{           // MT_TOKEN
 		312,            // doomednum
-		S_EMMY,         // spawnstate
+		S_TOKEN,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -5183,33 +5258,6 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_TOKEN
-		-1,             // doomednum
-		S_TOKEN,        // spawnstate
-		1000,           // spawnhealth
-		S_MOVINGTOKEN,  // seestate
-		sfx_None,       // seesound
-		8,              // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		0,              // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_NULL,         // missilestate
-		S_NULL,         // deathstate
-		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		16*FRACUNIT,    // height
-		0,              // display offset
-		100,            // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP, // flags
-		S_NULL          // raisestate
-	},
-
 	{           // MT_REDFLAG
 		310,            // doomednum
 		S_REDFLAG,      // spawnstate
@@ -5987,6 +6035,60 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_WALLSPIKE
+		522,            // doomednum
+		S_WALLSPIKE1,   // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_s3k64,      // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_WALLSPIKED1,  // deathstate
+		S_WALLSPIKED2,  // xdeathstate
+		sfx_mspogo,     // deathsound
+		2*TICRATE,      // speed
+		16*FRACUNIT,    // radius
+		14*FRACUNIT,    // height
+		0,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_SCENERY|MF_NOCLIPHEIGHT|MF_PAPERCOLLISION,  // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_WALLSPIKEBASE
+		-1,            // doomednum
+		S_WALLSPIKEBASE, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		7*FRACUNIT,     // radius
+		14*FRACUNIT,    // height
+		0,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPTHING,  // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_STARPOST
 		502,            // doomednum
 		S_STARPOST_IDLE, // spawnstate
@@ -6447,7 +6549,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 	},
 
 	{           // MT_MYSTERY_BOX
-		412,            // doomednum
+		-1, //412,      // doomednum
 		S_MYSTERY_BOX,  // spawnstate
 		1,              // spawnhealth
 		S_NULL,         // seestate
@@ -7099,7 +7201,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_PITY_ICON1,   // spawnstate
 		1,              // spawnhealth
 		S_NULL,         // seestate
-		sfx_s3k3a,      // seesound
+		sfx_shield,     // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -7450,7 +7552,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_SCORE1K_ICON1, // spawnstate
 		1,              // spawnhealth
 		S_NULL,         // seestate
-		sfx_token,      // seesound
+		sfx_chchng,     // seesound
 		1000,           // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -7477,7 +7579,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_SCORE10K_ICON1, // spawnstate
 		1,              // spawnhealth
 		S_NULL,         // seestate
-		sfx_token,      // seesound
+		sfx_chchng,     // seesound
 		10000,          // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -8039,9 +8141,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_THZFLOWER1
-		900,            // doomednum
-		S_THZFLOWERA,    // spawnstate
+	{           // MT_GFZTREE
+		806,            // doomednum
+		S_GFZTREE,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8055,20 +8157,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		32*FRACUNIT,    // height
+		0,              // speed
+		20*FRACUNIT,    // radius
+		128*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SOLID|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_THZFLOWER2
-		902,            // doomednum
-		S_THZFLOWERB,    // spawnstate
+	{           // MT_GFZBERRYTREE
+		807,            // doomednum
+		S_GFZBERRYTREE,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8082,20 +8184,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		8,              // speed
-		8*FRACUNIT,     // radius
-		32*FRACUNIT,    // height
+		0,              // speed
+		20*FRACUNIT,    // radius
+		128*FRACUNIT,    // height
 		0,              // display offset
-		16,             // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_SOLID|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_ALARM
-		901,            // doomednum
-		S_ALARM1,       // spawnstate
+	{           // MT_GFZCHERRYTREE
+		808,            // doomednum
+		S_GFZCHERRYTREE,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8108,21 +8210,21 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_alarm,      // deathsound
-		1,              // speed
-		8*FRACUNIT,     // radius
-		16*FRACUNIT,    // height
+		sfx_None,       // deathsound
+		0,              // speed
+		20*FRACUNIT,    // radius
+		128*FRACUNIT,    // height
 		0,              // display offset
-		4,              // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags
+		MF_SOLID|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_GARGOYLE
-		1000,           // doomednum
-		S_GARGOYLE,     // spawnstate
+	{           // MT_CHECKERTREE
+		810,            // doomednum
+		S_CHECKERTREE,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8136,20 +8238,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		21*FRACUNIT,    // speed
-		16*FRACUNIT,    // radius
-		40*FRACUNIT,    // height
+		0,              // speed
+		20*FRACUNIT,    // radius
+		200*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		1,              // damage
-		sfx_statu2,     // activesound
-		MF_SLIDEME|MF_SOLID|MF_PUSHABLE, // flags
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_SEAWEED
-		1001,           // doomednum
-		S_SEAWEED1,     // spawnstate
+	{           // MT_CHECKERSUNSETTREE
+		811,            // doomednum
+		S_CHECKERSUNSETTREE,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8164,19 +8266,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		24*FRACUNIT,    // radius
-		56*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		200*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_WATERDRIP
-		1002,           // doomednum
-		S_DRIPA1,       // spawnstate
+	{           // MT_FHZTREE
+		812,            // doomednum
+		S_FHZTREE,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8191,19 +8293,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		1*FRACUNIT,     // radius
-		15*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		200*FRACUNIT,    // height
 		0,              // display offset
-		4,              // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_SPAWNCEILING|MF_NOGRAVITY,  // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_WATERDROP
-		-1,             // doomednum
-		S_DRIPB1,       // spawnstate
+	{           // MT_FHZPINKTREE
+		813,            // doomednum
+		S_FHZPINKTREE,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8214,23 +8316,23 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
-		S_DRIPC1,       // deathstate
+		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_wdrip1,     // deathsound
+		sfx_None,       // deathsound
 		0,              // speed
-		2*FRACUNIT,     // radius
-		8*FRACUNIT,     // height
+		20*FRACUNIT,    // radius
+		200*FRACUNIT,    // height
 		0,              // display offset
-		8,              // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_SPECIAL|MF_SCENERY,     // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_CORAL1
-		1003,           // doomednum
-		S_CORAL1,       // spawnstate
+	{           // MT_POLYGONTREE
+		814,            // doomednum
+		S_POLYGONTREE,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8245,19 +8347,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		8*FRACUNIT,     // radius
-		16*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		200*FRACUNIT,    // height
 		0,              // display offset
-		4,              // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY,     // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_CORAL2
-		1004,           // doomednum
-		S_CORAL2,       // spawnstate
+	{           // MT_BUSHTREE
+		815,            // doomednum
+		S_BUSHTREE,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8272,19 +8374,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		8*FRACUNIT,     // radius
-		16*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		200*FRACUNIT,    // height
 		0,              // display offset
-		4,              // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY,     // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_CORAL3
-		1005,           // doomednum
-		S_CORAL3,       // spawnstate
+	{           // MT_BUSHREDTREE
+		816,            // doomednum
+		S_BUSHREDTREE,         // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8299,19 +8401,19 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		8*FRACUNIT,     // radius
-		16*FRACUNIT,    // height
+		20*FRACUNIT,    // radius
+		200*FRACUNIT,    // height
 		0,              // display offset
-		4,              // mass
+		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY,     // flags
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_BLUECRYSTAL
-		1006,           // doomednum
-		S_BLUECRYSTAL1, // spawnstate
+	{           // MT_THZFLOWER1
+		900,            // doomednum
+		S_THZFLOWERA,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8325,20 +8427,20 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
+		8,              // speed
 		8*FRACUNIT,     // radius
-		16*FRACUNIT,    // height
+		32*FRACUNIT,    // height
 		0,              // display offset
-		4,              // mass
+		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY,     // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_CHAIN
-		1100,           // doomednum
-		S_CEZCHAIN,     // spawnstate
+	{           // MT_THZFLOWER2
+		902,            // doomednum
+		S_THZFLOWERB,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8353,46 +8455,46 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		8,              // speed
-		4*FRACUNIT,     // radius
-		128*FRACUNIT,   // height
+		8*FRACUNIT,     // radius
+		32*FRACUNIT,    // height
 		0,              // display offset
 		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_SPAWNCEILING|MF_NOGRAVITY|MF_SCENERY, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_FLAME
-		1101,           // doomednum
-		S_FLAME1,       // spawnstate
+	{           // MT_ALARM
+		901,            // doomednum
+		S_ALARM1,       // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		MT_FLAMEPARTICLE, // painchance
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		0,              // speed
+		sfx_alarm,      // deathsound
+		1,              // speed
 		8*FRACUNIT,     // radius
-		32*FRACUNIT,    // height
+		16*FRACUNIT,    // height
 		0,              // display offset
-		100,            // mass
+		4,              // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOGRAVITY|MF_PAIN|MF_FIRE, // flags
+		MF_SOLID|MF_SPAWNCEILING|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_FLAMEPARTICLE
-		-1,             // doomednum
-		S_FLAMEPARTICLE, // spawnstate
+	{           // MT_GARGOYLE
+		1000,           // doomednum
+		S_GARGOYLE,     // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8406,20 +8508,317 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
-		FRACUNIT,       // radius
-		FRACUNIT,       // height
+		21*FRACUNIT,    // speed
+		16*FRACUNIT,    // radius
+		40*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
-		0,              // damage
-		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_SCENERY, // flags
+		1,              // damage
+		sfx_statu2,     // activesound
+		MF_SLIDEME|MF_SOLID|MF_PUSHABLE, // flags
 		S_NULL          // raisestate
 	},
 
-	{           // MT_EGGSTATUE
-		1102,           // doomednum
-		S_EGGSTATUE1,   // spawnstate
+	{           // MT_BIGGARGOYLE
+		1009,           // doomednum
+		S_BIGGARGOYLE,  // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		12*FRACUNIT,    // speed
+		32*FRACUNIT,    // radius
+		80*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		1,              // damage
+		sfx_statu2,     // activesound
+		MF_SLIDEME|MF_SOLID|MF_PUSHABLE, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_SEAWEED
+		1001,           // doomednum
+		S_SEAWEED1,     // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		24*FRACUNIT,    // radius
+		56*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_WATERDRIP
+		1002,           // doomednum
+		S_DRIPA1,       // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		1*FRACUNIT,     // radius
+		15*FRACUNIT,    // height
+		0,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SPAWNCEILING|MF_NOGRAVITY,  // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_WATERDROP
+		-1,             // doomednum
+		S_DRIPB1,       // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_DRIPC1,       // deathstate
+		S_NULL,         // xdeathstate
+		sfx_wdrip1,     // deathsound
+		0,              // speed
+		2*FRACUNIT,     // radius
+		8*FRACUNIT,     // height
+		0,              // display offset
+		8,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_SPECIAL|MF_SCENERY,     // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_CORAL1
+		1003,           // doomednum
+		S_CORAL1,       // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY,     // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_CORAL2
+		1004,           // doomednum
+		S_CORAL2,       // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY,     // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_CORAL3
+		1005,           // doomednum
+		S_CORAL3,       // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY,     // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_BLUECRYSTAL
+		1006,           // doomednum
+		S_BLUECRYSTAL1, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		4,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY,     // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_CHAIN
+		1100,           // doomednum
+		S_CEZCHAIN,     // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		8,              // speed
+		4*FRACUNIT,     // radius
+		128*FRACUNIT,   // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SPAWNCEILING|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_FLAME
+		1101,           // doomednum
+		S_FLAME1,       // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		MT_FLAMEPARTICLE, // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOGRAVITY|MF_PAIN|MF_FIRE, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_FLAMEPARTICLE
+		-1,             // doomednum
+		S_FLAMEPARTICLE, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		FRACUNIT,       // radius
+		FRACUNIT,       // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_EGGSTATUE
+		1102,           // doomednum
+		S_EGGSTATUE1,   // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -8471,7 +8870,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_SWINGMACEPOINT
+	{           // MT_CHAINMACEPOINT
 		1105,           // doomednum
 		S_INVISIBLE,    // spawnstate
 		1000,           // spawnhealth
@@ -8498,7 +8897,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_HANGMACEPOINT
+	{           // MT_SPRINGBALLPOINT
 		1106,           // doomednum
 		S_INVISIBLE,    // spawnstate
 		1000,           // spawnhealth
@@ -8525,7 +8924,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_SPINMACEPOINT
+	{           // MT_CHAINPOINT
 		1107,           // doomednum
 		S_INVISIBLE,    // spawnstate
 		1000,           // spawnhealth
@@ -8545,7 +8944,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		128*FRACUNIT,   // radius
 		1*FRACUNIT,     // height
 		0,              // display offset
-		200,            // mass
+		10000,          // mass
 		0,              // damage
 		sfx_None,       // activesound
 		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
@@ -8572,10 +8971,64 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		8*FRACUNIT,     // radius
 		8*FRACUNIT,     // height
 		0,              // display offset
-		0,              // mass
+		0,              // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_FIREBARPOINT
+		1109,           // doomednum
+		S_INVISIBLE,    // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		10*FRACUNIT,    // speed
+		128*FRACUNIT,   // radius
+		1*FRACUNIT,     // height
+		0,              // display offset
+		200,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_CUSTOMMACEPOINT
+		1111,           // doomednum
+		S_INVISIBLE,    // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		10*FRACUNIT,    // speed
+		128*FRACUNIT,   // radius
+		1*FRACUNIT,     // height
+		0,              // display offset
+		200,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
 		S_NULL          // raisestate
 	},
 
@@ -8602,7 +9055,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		100,              // mass
 		1,                // damage
 		sfx_None,         // activesound
-		MF_NOGRAVITY|MF_SPECIAL|MF_NOCLIPHEIGHT, // flags
+		MF_SCENERY|MF_SPECIAL|MF_NOGRAVITY, // flags
 		S_NULL            // raisestate
 	},
 
@@ -8629,7 +9082,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		100,            // mass
 		1,              // damage
 		sfx_None,       // activesound
-		MF_NOGRAVITY|MF_SPECIAL|MF_NOCLIPHEIGHT, // flags
+		MF_SCENERY|MF_SPECIAL|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -8652,11 +9105,11 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		24*FRACUNIT,    // speed
 		17*FRACUNIT,    // radius
 		34*FRACUNIT,    // height
-		0,              // display offset
+		1,              // display offset
 		100,            // mass
 		1,              // damage
 		sfx_mswing,     // activesound
-		MF_PAIN|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_SCENERY|MF_PAIN|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -8679,11 +9132,119 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		48*FRACUNIT,    // speed
 		34*FRACUNIT,    // radius
 		68*FRACUNIT,    // height
-		0,              // display offset
+		1,              // display offset
 		100,            // mass
 		1,              // damage
 		sfx_mswing,     // activesound
-		MF_PAIN|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_SCENERY|MF_PAIN|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
+	{            // MT_YELLOWSPRINGBALL
+		-1,             // doomednum
+		S_YELLOWSPRINGBALL, // spawnstate
+		1000,           // spawnhealth
+		S_YELLOWSPRINGBALL2, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_spring,     // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		24*FRACUNIT,    // speed
+		17*FRACUNIT,    // radius
+		34*FRACUNIT,    // height
+		1,              // display offset
+		20*FRACUNIT,    // mass
+		0,              // damage
+		sfx_mswing,     // activesound
+		MF_SCENERY|MF_SOLID|MF_SPRING|MF_NOGRAVITY, // flags
+		S_YELLOWSPRINGBALL2 // raisestate
+	},
+
+	{            // MT_REDSPRINGBALL
+		-1,             // doomednum
+		S_REDSPRINGBALL, // spawnstate
+		1000,           // spawnhealth
+		S_REDSPRINGBALL2, // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_spring,     // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		24*FRACUNIT,    // speed
+		17*FRACUNIT,    // radius
+		34*FRACUNIT,    // height
+		1,              // display offset
+		32*FRACUNIT,    // mass
+		0,              // damage
+		sfx_mswing,     // activesound
+		MF_SCENERY|MF_SOLID|MF_SPRING|MF_NOGRAVITY, // flags
+		S_REDSPRINGBALL2 // raisestate
+	},
+
+	{            // MT_SMALLFIREBAR
+		-1,             // doomednum
+		S_SMALLFIREBAR1,     // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		MT_FLAMEPARTICLE, // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		24*FRACUNIT,    // speed
+		17*FRACUNIT,    // radius
+		34*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_SCENERY|MF_PAIN|MF_FIRE|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
+	{            // MT_BIGFIREBAR
+		-1,             // doomednum
+		S_BIGFIREBAR1,  // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		MT_FLAMEPARTICLE, // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		48*FRACUNIT,    // speed
+		34*FRACUNIT,    // radius
+		68*FRACUNIT,    // height
+		1,              // display offset
+		100,            // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_SCENERY|MF_PAIN|MF_FIRE|MF_NOGRAVITY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -9515,7 +10076,34 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // deathsound
 		25*FRACUNIT,    // speed
 		16*FRACUNIT,    // radius
-		40*FRACUNIT,    // height
+		64*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_SLIDEME|MF_SOLID|MF_PUSHABLE, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_SNOWMANHAT
+		1853,           // doomednum
+		S_SNOWMANHAT,   // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		25*FRACUNIT,    // speed
+		16*FRACUNIT,    // radius
+		80*FRACUNIT,    // height
 		0,              // display offset
 		100,            // mass
 		1,              // damage
@@ -9524,6 +10112,141 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_LAMPPOST1
+		1854,           // doomednum
+		S_LAMPPOST1,    // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		120*FRACUNIT,   // height
+		0,              // display offset
+		100,            // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_SOLID,       // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_LAMPPOST2
+		1855,           // doomednum
+		S_LAMPPOST2,    // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		120*FRACUNIT,   // height
+		0,              // display offset
+		100,            // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_SOLID,       // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_HANGSTAR
+		1856,           // doomednum
+		S_HANGSTAR,     // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		4*FRACUNIT,     // radius
+		80*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		1,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_SPAWNCEILING|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_XMASBERRYBUSH
+		1857,           // doomednum
+		S_XMASBERRYBUSH, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_XMASBUSH
+		1858,           // doomednum
+		S_XMASBUSH,     // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		32*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOCLIP|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
 	// No, I did not do all of this by hand.
 	// I made a script to make all of these for me.
 	// Ha HA. ~Inuyasha
@@ -12457,7 +13180,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_RRNG1,        // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_thok,       // seesound
+		sfx_wepfir,     // seesound
 		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -12864,7 +13587,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_THROWNINFINITY1, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_thok,       // seesound
+		sfx_wepfir,     // seesound
 		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -12891,7 +13614,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_THROWNAUTOMATIC1, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_thok,       // seesound
+		sfx_wepfir,     // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
@@ -12972,7 +13695,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_THROWNGRENADE1, // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
-		sfx_thok,       // seesound
+		sfx_wepfir,     // seesound
 		8,              // reactiontime
 		sfx_gbeep,      // attacksound
 		S_NULL,         // painstate
@@ -13759,7 +14482,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		0,              // painchance
-		sfx_itemup,     // painsound
+		sfx_s3k33,      // painsound
 		S_RING,         // meleestate
 		S_NULL,         // missilestate
 		S_SPRK1,        // deathstate
@@ -13778,9 +14501,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_NIGHTSSUPERLOOP
 		1707,           // doomednum
-		S_NIGHTSPOWERUP1, // spawnstate
+		S_NIGHTSSUPERLOOP, // spawnstate
 		1000,           // spawnhealth
-		S_NIGHTSPOWERUP2, // seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
 		0,              // reactiontime
 		sfx_None,       // attacksound
@@ -13805,9 +14528,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_NIGHTSDRILLREFILL
 		1708,           // doomednum
-		S_NIGHTSPOWERUP3, // spawnstate
+		S_NIGHTSDRILLREFILL, // spawnstate
 		1000,           // spawnhealth
-		S_NIGHTSPOWERUP4, // seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
 		0,              // reactiontime
 		sfx_None,       // attacksound
@@ -13832,9 +14555,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_NIGHTSHELPER
 		1709,           // doomednum
-		S_NIGHTSPOWERUP5, // spawnstate
+		S_NIGHTSHELPER, // spawnstate
 		1000,           // spawnhealth
-		S_NIGHTSPOWERUP6, // seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
 		0,              // reactiontime
 		sfx_None,       // attacksound
@@ -13859,9 +14582,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_NIGHTSEXTRATIME
 		1711,           // doomednum
-		S_NIGHTSPOWERUP7, // spawnstate
+		S_NIGHTSEXTRATIME, // spawnstate
 		1000,           // spawnhealth
-		S_NIGHTSPOWERUP8, // seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
 		0,              // reactiontime
 		sfx_None,       // attacksound
@@ -13886,9 +14609,9 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_NIGHTSLINKFREEZE
 		1712,           // doomednum
-		S_NIGHTSPOWERUP9, // spawnstate
+		S_NIGHTSLINKFREEZE, // spawnstate
 		1000,           // spawnhealth
-		S_NIGHTSPOWERUP10, // seestate
+		S_NULL,         // seestate
 		sfx_None,       // seesound
 		0,              // reactiontime
 		sfx_None,       // attacksound
diff --git a/src/info.h b/src/info.h
index 4b21e98ec43e234283dfbe441d399257063df869..cd79b12a98951a6116cbe9a0d7d88d6b9e8263d9 100644
--- a/src/info.h
+++ b/src/info.h
@@ -40,8 +40,6 @@ void A_Scream();
 void A_BossDeath();
 void A_CustomPower(); // Use this for a custom power
 void A_GiveWeapon(); // Gives the player weapon(s)
-void A_JumpShield(); // Obtained Jump Shield
-void A_RingShield(); // Obtained Ring Shield
 void A_RingBox(); // Obtained Ring Box Tails
 void A_Invincibility(); // Obtained Invincibility Box
 void A_SuperSneakers(); // Obtained Super Sneakers Box
@@ -52,13 +50,7 @@ void A_BubbleRise(); // Bubbles float to surface
 void A_BubbleCheck(); // Don't draw if not underwater
 void A_AwardScore();
 void A_ExtraLife(); // Extra Life
-void A_BombShield(); // Obtained Bomb Shield
-void A_WaterShield(); // Obtained Water Shield
-void A_ForceShield(); // Obtained Force Shield
-void A_PityShield(); // Obtained Pity Shield. We're... sorry.
-void A_FlameShield(); // Obtained Flame Shield
-void A_BubbleShield(); // Obtained Bubble Shield
-void A_ThunderShield(); // Obtained Thunder Shield
+void A_GiveShield(); // Obtained Shield
 void A_GravityBox();
 void A_ScoreRise(); // Rise the score logo
 void A_ParticleSpawn();
@@ -84,7 +76,6 @@ void A_DetonChase(); // Deton Chaser
 void A_CapeChase(); // Fake little Super Sonic cape
 void A_RotateSpikeBall(); // Spike ball rotation
 void A_SlingAppear();
-void A_MaceRotate();
 void A_UnidusBall();
 void A_RockSpawn();
 void A_SetFuse();
@@ -320,7 +311,6 @@ typedef enum sprite
 	// Collectible Items
 	SPR_RING,
 	SPR_TRNG, // Team Rings
-	SPR_EMMY, // emerald test
 	SPR_TOKE, // Special Stage Token
 	SPR_RFLG, // Red CTF Flag
 	SPR_BFLG, // Blue CTF Flag
@@ -337,6 +327,8 @@ typedef enum sprite
 	SPR_SPIK, // Spike Ball
 	SPR_SFLM, // Spin fire
 	SPR_USPK, // Floor spike
+	SPR_WSPK, // Wall spike
+	SPR_WSPB, // Wall spike base
 	SPR_STPT, // Starpost
 	SPR_BMNE, // Big floating mine
 
@@ -387,6 +379,12 @@ typedef enum sprite
 	SPR_FWR4,
 	SPR_BUS1, // GFZ Bush w/ berries
 	SPR_BUS2, // GFZ Bush w/o berries
+	// Trees (both GFZ and misc)
+	SPR_TRE1, // GFZ
+	SPR_TRE2, // Checker
+	SPR_TRE3, // Frozen Hillside
+	SPR_TRE4, // Polygon
+	SPR_TRE5, // Bush tree
 
 	// Techno Hill Scenery
 	SPR_THZP, // THZ1 Flower
@@ -410,6 +408,10 @@ typedef enum sprite
 	SPR_BMCH, // Big Mace Chain
 	SPR_SMCE, // Small Mace
 	SPR_BMCE, // Big Mace
+	SPR_YSPB, // Yellow spring on a ball
+	SPR_RSPB, // Red spring on a ball
+	SPR_SFBR, // Small Firebar
+	SPR_BFBR, // Big Firebar
 
 	// Arid Canyon Scenery
 	SPR_BTBL, // Big tumbleweed
@@ -425,9 +427,11 @@ typedef enum sprite
 	// Egg Rock Scenery
 
 	// Christmas Scenery
-	SPR_XMS1,
-	SPR_XMS2,
-	SPR_XMS3,
+	SPR_XMS1, // Christmas Pole
+	SPR_XMS2, // Candy Cane
+	SPR_XMS3, // Snowman
+	SPR_XMS4, // Lamppost
+	SPR_XMS5, // Hanging Star
 
 	// Botanic Serenity Scenery
 	SPR_BSZ1, // Tall flowers
@@ -1615,12 +1619,8 @@ typedef enum state
 	// Individual Team Rings
 	S_TEAMRING,
 
-	// Special Stage Token
-	S_EMMY,
-
 	// Special Stage Token
 	S_TOKEN,
-	S_MOVINGTOKEN,
 
 	// CTF Flags
 	S_REDFLAG,
@@ -1771,6 +1771,17 @@ typedef enum state
 	S_SPIKED1,
 	S_SPIKED2,
 
+	// Wall spikes
+	S_WALLSPIKE1,
+	S_WALLSPIKE2,
+	S_WALLSPIKE3,
+	S_WALLSPIKE4,
+	S_WALLSPIKE5,
+	S_WALLSPIKE6,
+	S_WALLSPIKEBASE,
+	S_WALLSPIKED1,
+	S_WALLSPIKED2,
+
 	// Starpost
 	S_STARPOST_IDLE,
 	S_STARPOST_FLASH,
@@ -1959,6 +1970,7 @@ typedef enum state
 	S_DEMONFIRE5,
 	S_DEMONFIRE6,
 
+	// GFZ flowers
 	S_GFZFLOWERA,
 	S_GFZFLOWERB,
 	S_GFZFLOWERC,
@@ -1966,6 +1978,18 @@ typedef enum state
 	S_BERRYBUSH,
 	S_BUSH,
 
+	// Trees (both GFZ and misc)
+	S_GFZTREE,
+	S_GFZBERRYTREE,
+	S_GFZCHERRYTREE,
+	S_CHECKERTREE,
+	S_CHECKERSUNSETTREE,
+	S_FHZTREE, // Frozen Hillside
+	S_FHZPINKTREE,
+	S_POLYGONTREE,
+	S_BUSHTREE,
+	S_BUSHREDTREE,
+
 	// THZ Plant
 	S_THZFLOWERA,
 	S_THZFLOWERB,
@@ -1975,6 +1999,7 @@ typedef enum state
 
 	// Deep Sea Gargoyle
 	S_GARGOYLE,
+	S_BIGGARGOYLE,
 
 	// DSZ Seaweed
 	S_SEAWEED1,
@@ -2026,18 +2051,62 @@ typedef enum state
 	S_SLING1,
 	S_SLING2,
 
-	// CEZ Small Mace Chain
+	// CEZ maces and chains
 	S_SMALLMACECHAIN,
-
-	// CEZ Big Mace Chain
 	S_BIGMACECHAIN,
-
-	// CEZ Small Mace
 	S_SMALLMACE,
-
-	// CEZ Big Mace
 	S_BIGMACE,
 
+	// Yellow spring on a ball
+	S_YELLOWSPRINGBALL,
+	S_YELLOWSPRINGBALL2,
+	S_YELLOWSPRINGBALL3,
+	S_YELLOWSPRINGBALL4,
+	S_YELLOWSPRINGBALL5,
+
+	// Red spring on a ball
+	S_REDSPRINGBALL,
+	S_REDSPRINGBALL2,
+	S_REDSPRINGBALL3,
+	S_REDSPRINGBALL4,
+	S_REDSPRINGBALL5,
+
+	// Small Firebar
+	S_SMALLFIREBAR1,
+	S_SMALLFIREBAR2,
+	S_SMALLFIREBAR3,
+	S_SMALLFIREBAR4,
+	S_SMALLFIREBAR5,
+	S_SMALLFIREBAR6,
+	S_SMALLFIREBAR7,
+	S_SMALLFIREBAR8,
+	S_SMALLFIREBAR9,
+	S_SMALLFIREBAR10,
+	S_SMALLFIREBAR11,
+	S_SMALLFIREBAR12,
+	S_SMALLFIREBAR13,
+	S_SMALLFIREBAR14,
+	S_SMALLFIREBAR15,
+	S_SMALLFIREBAR16,
+
+	// Big Firebar
+	S_BIGFIREBAR1,
+	S_BIGFIREBAR2,
+	S_BIGFIREBAR3,
+	S_BIGFIREBAR4,
+	S_BIGFIREBAR5,
+	S_BIGFIREBAR6,
+	S_BIGFIREBAR7,
+	S_BIGFIREBAR8,
+	S_BIGFIREBAR9,
+	S_BIGFIREBAR10,
+	S_BIGFIREBAR11,
+	S_BIGFIREBAR12,
+	S_BIGFIREBAR13,
+	S_BIGFIREBAR14,
+	S_BIGFIREBAR15,
+	S_BIGFIREBAR16,
+
 	S_CEZFLOWER1,
 
 	// Big Tumbleweed
@@ -2133,7 +2202,14 @@ typedef enum state
 	// Xmas-specific stuff
 	S_XMASPOLE,
 	S_CANDYCANE,
-	S_SNOWMAN,
+	S_SNOWMAN,    // normal
+	S_SNOWMANHAT, // with hat + scarf
+	S_LAMPPOST1,  // normal
+	S_LAMPPOST2,  // with snow
+	S_HANGSTAR,
+	// Xmas GFZ bushes
+	S_XMASBERRYBUSH,
+	S_XMASBUSH,
 
 	// Botanic Serenity's loads of scenery states
 	S_BSZTALLFLOWER_RED,
@@ -2325,10 +2401,6 @@ typedef enum state
 	S_PITY4,
 	S_PITY5,
 	S_PITY6,
-	S_PITY7,
-	S_PITY8,
-	S_PITY9,
-	S_PITY10,
 
 	S_FIRS1,
 	S_FIRS2,
@@ -3008,16 +3080,11 @@ typedef enum state
 	S_NIGHTSWING_XMAS,
 
 	// NiGHTS Paraloop Powerups
-	S_NIGHTSPOWERUP1,
-	S_NIGHTSPOWERUP2,
-	S_NIGHTSPOWERUP3,
-	S_NIGHTSPOWERUP4,
-	S_NIGHTSPOWERUP5,
-	S_NIGHTSPOWERUP6,
-	S_NIGHTSPOWERUP7,
-	S_NIGHTSPOWERUP8,
-	S_NIGHTSPOWERUP9,
-	S_NIGHTSPOWERUP10,
+	S_NIGHTSSUPERLOOP,
+	S_NIGHTSDRILLREFILL,
+	S_NIGHTSHELPER,
+	S_NIGHTSEXTRATIME,
+	S_NIGHTSLINKFREEZE,
 	S_EGGCAPSULE,
 
 	// Orbiting Chaos Emeralds
@@ -3233,8 +3300,7 @@ typedef enum mobj_type
 	MT_BLUEBALL,  // Blue sphere replacement for special stages
 	MT_REDTEAMRING,  //Rings collectable by red team.
 	MT_BLUETEAMRING, //Rings collectable by blue team.
-	MT_EMMY, // emerald token for special stage
-	MT_TOKEN, // Special Stage Token (uncollectible part)
+	MT_TOKEN, // Special Stage token for special stage
 	MT_REDFLAG, // Red CTF Flag
 	MT_BLUEFLAG, // Blue CTF Flag
 	MT_EMBLEM,
@@ -3268,6 +3334,8 @@ typedef enum mobj_type
 	MT_SPECIALSPIKEBALL,
 	MT_SPINFIRE,
 	MT_SPIKE,
+	MT_WALLSPIKE,
+	MT_WALLSPIKEBASE,
 	MT_STARPOST,
 	MT_BIGMINE,
 	MT_BIGAIRMINE,
@@ -3358,6 +3426,17 @@ typedef enum mobj_type
 	MT_GFZFLOWER3,
 	MT_BERRYBUSH,
 	MT_BUSH,
+	// Trees (both GFZ and misc)
+	MT_GFZTREE,
+	MT_GFZBERRYTREE,
+	MT_GFZCHERRYTREE,
+	MT_CHECKERTREE,
+	MT_CHECKERSUNSETTREE,
+	MT_FHZTREE, // Frozen Hillside
+	MT_FHZPINKTREE,
+	MT_POLYGONTREE,
+	MT_BUSHTREE,
+	MT_BUSHREDTREE,
 
 	// Techno Hill Scenery
 	MT_THZFLOWER1,
@@ -3366,6 +3445,7 @@ typedef enum mobj_type
 
 	// Deep Sea Scenery
 	MT_GARGOYLE, // Deep Sea Gargoyle
+	MT_BIGGARGOYLE, // Deep Sea Gargoyle (Big)
 	MT_SEAWEED, // DSZ Seaweed
 	MT_WATERDRIP, // Dripping Water source
 	MT_WATERDROP, // Water drop from dripping water
@@ -3380,14 +3460,20 @@ typedef enum mobj_type
 	MT_FLAMEPARTICLE,
 	MT_EGGSTATUE, // Eggman Statue
 	MT_MACEPOINT, // Mace rotation point
-	MT_SWINGMACEPOINT, // Mace swinging point
-	MT_HANGMACEPOINT, // Hangable mace chain
-	MT_SPINMACEPOINT, // Spin/Controllable mace chain
+	MT_CHAINMACEPOINT, // Combination of chains and maces point
+	MT_SPRINGBALLPOINT, // Spring ball point
+	MT_CHAINPOINT, // Mace chain
 	MT_HIDDEN_SLING, // Spin mace chain (activatable)
+	MT_FIREBARPOINT, // Firebar
+	MT_CUSTOMMACEPOINT, // Custom mace
 	MT_SMALLMACECHAIN, // Small Mace Chain
 	MT_BIGMACECHAIN, // Big Mace Chain
 	MT_SMALLMACE, // Small Mace
 	MT_BIGMACE, // Big Mace
+	MT_YELLOWSPRINGBALL, // Yellow spring on a ball
+	MT_REDSPRINGBALL, // Red spring on a ball
+	MT_SMALLFIREBAR, // Small Firebar
+	MT_BIGFIREBAR, // Big Firebar
 	MT_CEZFLOWER,
 
 	// Arid Canyon Scenery
@@ -3434,7 +3520,14 @@ typedef enum mobj_type
 	// Christmas Scenery
 	MT_XMASPOLE,
 	MT_CANDYCANE,
-	MT_SNOWMAN,
+	MT_SNOWMAN,    // normal
+	MT_SNOWMANHAT, // with hat + scarf
+	MT_LAMPPOST1,  // normal
+	MT_LAMPPOST2,  // with snow
+	MT_HANGSTAR,
+	// Xmas GFZ bushes
+	MT_XMASBERRYBUSH,
+	MT_XMASBUSH,
 
 	// Botanic Serenity scenery
 	MT_BSZTALLFLOWER_RED,
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 71f6a7e655d9aaa0f96f54fa72cbe51f1ef02e80..be14554153962e68d0aeec8c84043b2d2e156b39 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -462,7 +462,7 @@ static int lib_pSpawnLockOn(lua_State *L)
 		return LUA_ErrInvalid(L, "mobj_t");
 	if (!player)
 		return LUA_ErrInvalid(L, "player_t");
-	if (player == &players[consoleplayer] || player == &players[secondarydisplayplayer] || player == &players[displayplayer]) // Only display it on your own view.
+	if (P_IsLocalPlayer(player)) // Only display it on your own view.
 	{
 		mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
 		visual->target = lockon;
@@ -978,6 +978,19 @@ static int lib_pGivePlayerLives(lua_State *L)
 	return 0;
 }
 
+static int lib_pGiveCoopLives(lua_State *L)
+{
+	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+	INT32 numlives = (INT32)luaL_checkinteger(L, 2);
+	boolean sound = (boolean)lua_opttrueboolean(L, 3);
+	NOHUD
+	INLEVEL
+	if (!player)
+		return LUA_ErrInvalid(L, "player_t");
+	P_GiveCoopLives(player, numlives, sound);
+	return 0;
+}
+
 static int lib_pResetScore(lua_State *L)
 {
 	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
@@ -1183,6 +1196,18 @@ static int lib_pTelekinesis(lua_State *L)
 	return 0;
 }
 
+static int lib_pSwitchShield(lua_State *L)
+{
+	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+	UINT16 shield = luaL_checkinteger(L, 2);
+	NOHUD
+	INLEVEL
+	if (!player)
+		return LUA_ErrInvalid(L, "player_t");
+	P_SwitchShield(player, shield);
+	return 0;
+}
+
 // P_MAP
 ///////////
 
@@ -2181,6 +2206,31 @@ static int lib_sSoundPlaying(lua_State *L)
 	return 1;
 }
 
+// This doesn't really exist, but we're providing it as a handy netgame-safe wrapper for stuff that should be locally handled.
+
+static int lib_sStartMusicCaption(lua_State *L)
+{
+	player_t *player = NULL;
+	const char *caption = luaL_checkstring(L, 1);
+	UINT16 lifespan = (UINT16)luaL_checkinteger(L, 2);
+	//HUDSAFE
+	INLEVEL
+
+	if (!lua_isnone(L, 3) && lua_isuserdata(L, 3))
+	{
+		player = *((player_t **)luaL_checkudata(L, 3, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+	}
+
+	if (lifespan && (!player || P_IsLocalPlayer(player)))
+	{
+		strlcpy(S_sfx[sfx_None].caption, caption, sizeof(S_sfx[sfx_None].caption));
+		S_StartCaption(sfx_None, -1, lifespan);
+	}
+	return 0;
+}
+
 // G_GAME
 ////////////
 
@@ -2403,6 +2453,7 @@ static luaL_Reg lib[] = {
 	{"P_SpawnGhostMobj",lib_pSpawnGhostMobj},
 	{"P_GivePlayerRings",lib_pGivePlayerRings},
 	{"P_GivePlayerLives",lib_pGivePlayerLives},
+	{"P_GiveCoopLives",lib_pGiveCoopLives},
 	{"P_ResetScore",lib_pResetScore},
 	{"P_DoJumpShield",lib_pDoJumpShield},
 	{"P_DoBubbleBounce",lib_pDoBubbleBounce},
@@ -2420,6 +2471,7 @@ static luaL_Reg lib[] = {
 	{"P_SpawnThokMobj",lib_pSpawnThokMobj},
 	{"P_SpawnSpinMobj",lib_pSpawnSpinMobj},
 	{"P_Telekinesis",lib_pTelekinesis},
+	{"P_SwitchShield",lib_pSwitchShield},
 
 	// p_map
 	{"P_CheckPosition",lib_pCheckPosition},
@@ -2493,6 +2545,7 @@ static luaL_Reg lib[] = {
 	{"S_OriginPlaying",lib_sOriginPlaying},
 	{"S_IdPlaying",lib_sIdPlaying},
 	{"S_SoundPlaying",lib_sSoundPlaying},
+	{"S_StartMusicCaption", lib_sStartMusicCaption},
 
 	// g_game
 	{"G_BuildMapName",lib_gBuildMapName},
diff --git a/src/lua_blockmaplib.c b/src/lua_blockmaplib.c
index d90ef4d67bd6c8d5149da6175f297245675ddc1a..ccd90f99363aa2645f9220ef757c509ac1e3944b 100644
--- a/src/lua_blockmaplib.c
+++ b/src/lua_blockmaplib.c
@@ -266,4 +266,4 @@ int LUA_BlockmapLib(lua_State *L)
 	return 0;
 }
 
-#endif
\ No newline at end of file
+#endif
diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c
index 322fecb64f1739b3d9749e731f1eef59b9f8570c..559c576f0513a14ffa7273b59243b537bc4af971 100644
--- a/src/lua_consolelib.c
+++ b/src/lua_consolelib.c
@@ -22,8 +22,13 @@
 #include "lua_libs.h"
 #include "lua_hud.h" // hud_running errors
 
+// for functions not allowed in hud.add hooks
 #define NOHUD if (hud_running)\
 return luaL_error(L, "HUD rendering code should not call this function!");
+// for functions not allowed in hooks or coroutines (supercedes above)
+#define NOHOOK if (!lua_lumploading)\
+		return luaL_error(L, "This function cannot be called from within a hook or coroutine!");
+// for functions only allowed within a level
 #define INLEVEL if (gamestate != GS_LEVEL)\
 return luaL_error(L, "This function can only be used in a level!");
 
@@ -184,7 +189,7 @@ static int lib_comAddCommand(lua_State *L)
 	strlwr(name);
 
 	luaL_checktype(L, 2, LUA_TFUNCTION);
-	NOHUD
+	NOHOOK
 	if (lua_gettop(L) >= 3)
 	{ // For the third argument, only take a boolean or a number.
 		lua_settop(L, 3);
@@ -296,7 +301,7 @@ static int lib_cvRegisterVar(lua_State *L)
 	consvar_t *cvar;
 	luaL_checktype(L, 1, LUA_TTABLE);
 	lua_settop(L, 1); // Clear out all other possible arguments, leaving only the first one.
-	NOHUD
+	NOHOOK
 	cvar = lua_newuserdata(L, sizeof(consvar_t));
 	luaL_getmetatable(L, META_CVAR);
 	lua_setmetatable(L, -2);
@@ -394,12 +399,21 @@ static int lib_cvRegisterVar(lua_State *L)
 	// stack: cvar table, cvar userdata
 	lua_getfield(L, LUA_REGISTRYINDEX, "CV_Vars");
 	I_Assert(lua_istable(L, 3));
+
+	lua_getfield(L, 3, cvar->name);
+	if (lua_type(L, -1) != LUA_TNIL)
+		return luaL_error(L, M_GetText("Variable %s is already defined\n"), cvar->name);
+	lua_pop(L, 1);
+
 	lua_pushvalue(L, 2);
 	lua_setfield(L, 3, cvar->name);
 	lua_pop(L, 1);
 
 	// actually time to register it to the console now! Finally!
+	cvar->flags |= CV_MODIFIED;
 	CV_RegisterVar(cvar);
+	if (cvar->flags & CV_MODIFIED)
+		return luaL_error(L, "failed to register cvar (probable conflict with internal variable/command names)");
 
 	// return cvar userdata
 	return 1;
diff --git a/src/lua_hook.h b/src/lua_hook.h
index 88867db2b8b051891518f51566489b39aa909dda..fe5706f56a6b3c90051ba2bf84f49d5da274b122 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -46,6 +46,7 @@ enum hook {
 	hook_ShieldSpawn,
 	hook_ShieldSpecial,
 	hook_MobjMoveBlocked,
+	hook_MapThingSpawn,
 
 	hook_MAX // last hook
 };
@@ -83,5 +84,6 @@ boolean LUAh_HurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8
 #define LUAh_ShieldSpawn(player) LUAh_PlayerHook(player, hook_ShieldSpawn) // Hook for P_SpawnShieldOrb
 #define LUAh_ShieldSpecial(player) LUAh_PlayerHook(player, hook_ShieldSpecial) // Hook for shield abilities
 #define LUAh_MobjMoveBlocked(mo) LUAh_MobjHook(mo, hook_MobjMoveBlocked) // Hook for P_XYMovement (when movement is blocked)
+boolean LUAh_MapThingSpawn(mobj_t *mo, mapthing_t *mthing); // Hook for P_SpawnMapThing by mobj type
 
 #endif
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index dadc1861ac879ce3ffd9dba3db3bee65b9af11d2..3dd3f932f81a41bb82c046a7eb4fe8a564de796f 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -57,6 +57,7 @@ const char *const hookNames[hook_MAX+1] = {
 	"ShieldSpawn",
 	"ShieldSpecial",
 	"MobjMoveBlocked",
+	"MapThingSpawn",
 	NULL
 };
 
@@ -108,8 +109,8 @@ static int lib_addHook(lua_State *L)
 
 	luaL_checktype(L, 1, LUA_TFUNCTION);
 
-	if (hud_running)
-		return luaL_error(L, "HUD rendering code should not call this function!");
+	if (!lua_lumploading)
+		return luaL_error(L, "This function cannot be called from within a hook or coroutine!");
 
 	switch(hook.type)
 	{
@@ -128,6 +129,7 @@ static int lib_addHook(lua_State *L)
 	case hook_MobjRemoved:
 	case hook_HurtMsg:
 	case hook_MobjMoveBlocked:
+	case hook_MapThingSpawn:
 		hook.s.mt = MT_NULL;
 		if (lua_isnumber(L, 2))
 			hook.s.mt = lua_tonumber(L, 2);
@@ -187,6 +189,7 @@ static int lib_addHook(lua_State *L)
 	case hook_BossDeath:
 	case hook_MobjRemoved:
 	case hook_MobjMoveBlocked:
+	case hook_MapThingSpawn:
 		lastp = &mobjhooks[hook.s.mt];
 		break;
 	case hook_JumpSpecial:
@@ -1073,4 +1076,66 @@ void LUAh_NetArchiveHook(lua_CFunction archFunc)
 	// stack: tables
 }
 
+boolean LUAh_MapThingSpawn(mobj_t *mo, mapthing_t *mthing)
+{
+	hook_p hookp;
+	boolean hooked = false;
+	if (!gL || !(hooksAvailable[hook_MapThingSpawn/8] & (1<<(hook_MapThingSpawn%8))))
+		return false;
+
+	lua_settop(gL, 0);
+
+	// Look for all generic mobj map thing spawn hooks
+	for (hookp = mobjhooks[MT_NULL]; hookp; hookp = hookp->next)
+		if (hookp->type == hook_MapThingSpawn)
+		{
+			if (lua_gettop(gL) == 0)
+			{
+				LUA_PushUserdata(gL, mo, META_MOBJ);
+				LUA_PushUserdata(gL, mthing, META_MAPTHING);
+			}
+			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+			lua_gettable(gL, LUA_REGISTRYINDEX);
+			lua_pushvalue(gL, -3);
+			lua_pushvalue(gL, -3);
+			if (lua_pcall(gL, 2, 1, 0)) {
+				if (!hookp->error || cv_debug & DBG_LUA)
+					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+				lua_pop(gL, 1);
+				hookp->error = true;
+				continue;
+			}
+			if (lua_toboolean(gL, -1))
+				hooked = true;
+			lua_pop(gL, 1);
+		}
+
+	for (hookp = mobjhooks[mo->type]; hookp; hookp = hookp->next)
+		if (hookp->type == hook_MapThingSpawn)
+		{
+			if (lua_gettop(gL) == 0)
+			{
+				LUA_PushUserdata(gL, mo, META_MOBJ);
+				LUA_PushUserdata(gL, mthing, META_MAPTHING);
+			}
+			lua_pushfstring(gL, FMT_HOOKID, hookp->id);
+			lua_gettable(gL, LUA_REGISTRYINDEX);
+			lua_pushvalue(gL, -3);
+			lua_pushvalue(gL, -3);
+			if (lua_pcall(gL, 2, 1, 0)) {
+				if (!hookp->error || cv_debug & DBG_LUA)
+					CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL, -1));
+				lua_pop(gL, 1);
+				hookp->error = true;
+				continue;
+			}
+			if (lua_toboolean(gL, -1))
+				hooked = true;
+			lua_pop(gL, 1);
+		}
+
+	lua_settop(gL, 0);
+	return hooked;
+}
+
 #endif
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 5b3cd46ce8f86cf3f987dc716d2b4f0e0c86d7f3..29e4970c1d3153536315e2d7151d0bf885854ad6 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -612,6 +612,9 @@ static int lib_hudadd(lua_State *L)
 	luaL_checktype(L, 1, LUA_TFUNCTION);
 	field = luaL_checkoption(L, 2, "game", hudhook_opt);
 
+	if (!lua_lumploading)
+		return luaL_error(L, "This function cannot be called from within a hook or coroutine!");
+
 	lua_getfield(L, LUA_REGISTRYINDEX, "HUD");
 	I_Assert(lua_istable(L, -1));
 	lua_rawgeti(L, -1, field+2); // HUD[2+]
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 4f7fdaa2647d62706869d68afd23a96ff63219c6..9361abe94e9ec06cf622a7999820e169c2270316 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -31,6 +31,7 @@ enum sfxinfo_read {
 	sfxinfor_singular,
 	sfxinfor_priority,
 	sfxinfor_flags, // "pitch"
+	sfxinfor_caption,
 	sfxinfor_skinsound
 };
 const char *const sfxinfo_ropt[] = {
@@ -38,18 +39,21 @@ const char *const sfxinfo_ropt[] = {
 	"singular",
 	"priority",
 	"flags",
+	"caption",
 	"skinsound",
 	NULL};
 
 enum sfxinfo_write {
 	sfxinfow_singular = 0,
 	sfxinfow_priority,
-	sfxinfow_flags // "pitch"
+	sfxinfow_flags, // "pitch"
+	sfxinfow_caption
 };
 const char *const sfxinfo_wopt[] = {
 	"singular",
 	"priority",
 	"flags",
+	"caption",
 	NULL};
 
 //
@@ -769,8 +773,8 @@ static int lib_getSfxInfo(lua_State *L)
 	lua_remove(L, 1);
 
 	i = luaL_checkinteger(L, 1);
-	if (i >= NUMSFX)
-		return luaL_error(L, "sfxinfo[] index %d out of range (0 - %d)", i, NUMSFX-1);
+	if (i == 0 || i >= NUMSFX)
+		return luaL_error(L, "sfxinfo[] index %d out of range (1 - %d)", i, NUMSFX-1);
 	LUA_PushUserdata(L, &S_sfx[i], META_SFXINFO);
 	return 1;
 }
@@ -783,9 +787,9 @@ static int lib_setSfxInfo(lua_State *L)
 	lua_remove(L, 1);
 	{
 		UINT32 i = luaL_checkinteger(L, 1);
-		if (i >= NUMSFX)
-			return luaL_error(L, "sfxinfo[] index %d out of range (0 - %d)", i, NUMSFX-1);
-		info = &S_sfx[i]; // get the mobjinfo to assign to.
+		if (i == 0 || i >= NUMSFX)
+			return luaL_error(L, "sfxinfo[] index %d out of range (1 - %d)", i, NUMSFX-1);
+		info = &S_sfx[i]; // get the sfxinfo to assign to.
 	}
 	luaL_checktype(L, 2, LUA_TTABLE); // check that we've been passed a table.
 	lua_remove(L, 1); // pop mobjtype num, don't need it any more.
@@ -814,6 +818,9 @@ static int lib_setSfxInfo(lua_State *L)
 		case sfxinfow_flags:
 			info->pitch = (INT32)luaL_checkinteger(L, 3);
 			break;
+		case sfxinfow_caption:
+			strlcpy(info->caption, luaL_checkstring(L, 3), sizeof(info->caption));
+			break;
 		default:
 			break;
 		}
@@ -851,11 +858,14 @@ static int sfxinfo_get(lua_State *L)
 	case sfxinfor_flags:
 		lua_pushinteger(L, sfx->pitch);
 		return 1;
+	case sfxinfor_caption:
+		lua_pushstring(L, sfx->caption);
+		return 1;
 	case sfxinfor_skinsound:
 		lua_pushinteger(L, sfx->skinsound);
 		return 1;
 	default:
-		return luaL_error(L, "impossible error");
+		return luaL_error(L, "Field does not exist in sfxinfo_t");
 	}
 	return 0;
 }
@@ -886,8 +896,11 @@ static int sfxinfo_set(lua_State *L)
 	case sfxinfow_flags:
 		sfx->pitch = luaL_checkinteger(L, 1);
 		break;
+	case sfxinfow_caption:
+		strlcpy(sfx->caption, luaL_checkstring(L, 1), sizeof(sfx->caption));
+		break;
 	default:
-		return luaL_error(L, "impossible error");
+		return luaL_error(L, "Field does not exist in sfxinfo_t");
 	}
 	return 0;
 }
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index 2fcccab6683052b927dcbaed7d3284e8515ba49c..d384b75d1b5163e7f24ffb83a3177f0e84585fd4 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -417,7 +417,7 @@ static int mobj_set(lua_State *L)
 		mo->frame = (UINT32)luaL_checkinteger(L, 3);
 		break;
 	case mobj_sprite2:
-		mo->sprite2 = P_GetMobjSprite2(mo, (UINT8)luaL_checkinteger(L, 3));
+		mo->sprite2 = P_GetSkinSprite2(((skin_t *)mo->skin), (UINT8)luaL_checkinteger(L, 3), mo->player);
 		break;
 	case mobj_anim_duration:
 		mo->anim_duration = (UINT16)luaL_checkinteger(L, 3);
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index 19ede443bff14c9d9227d4f0f7b1c7a858d52ee5..7c55012d22c1e8bfe2472eb808482c39617906ef 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -322,6 +322,8 @@ static int player_get(lua_State *L)
 		lua_pushangle(L, plr->awayviewaiming);
 	else if (fastcmp(field,"spectator"))
 		lua_pushboolean(L, plr->spectator);
+	else if (fastcmp(field,"outofcoop"))
+		lua_pushboolean(L, plr->outofcoop);
 	else if (fastcmp(field,"bot"))
 		lua_pushinteger(L, plr->bot);
 	else if (fastcmp(field,"jointime"))
@@ -601,6 +603,8 @@ static int player_set(lua_State *L)
 		plr->awayviewaiming = luaL_checkangle(L, 3);
 	else if (fastcmp(field,"spectator"))
 		plr->spectator = lua_toboolean(L, 3);
+	else if (fastcmp(field,"outofcoop"))
+		plr->outofcoop = lua_toboolean(L, 3);
 	else if (fastcmp(field,"bot"))
 		return NOSET;
 	else if (fastcmp(field,"jointime"))
diff --git a/src/lua_script.c b/src/lua_script.c
index d30790be1bc70359b7e13b566d4791b2271c0780..994f81a40bb54880fffcefd061bb909a43e6fead 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -161,6 +161,11 @@ void LUA_ClearExtVars(void)
 }
 #endif
 
+// Use this variable to prevent certain functions from running
+// if they were not called on lump load
+// (i.e. they were called in hooks or coroutines etc)
+boolean lua_lumploading = false;
+
 // Load a script from a MYFILE
 static inline void LUA_LoadFile(MYFILE *f, char *name)
 {
@@ -198,7 +203,9 @@ void LUA_LoadLump(UINT16 wad, UINT16 lump)
 		name[strlen(wadfiles[wad]->filename)+9] = '\0';
 	}
 
-	LUA_LoadFile(&f, name);
+	lua_lumploading = true; // turn on loading flag
+	LUA_LoadFile(&f, name); // actually load file!
+	lua_lumploading = false; // turn off again
 
 	free(name);
 	Z_Free(f.data);
@@ -596,14 +603,14 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
 		{
 			mobjinfo_t *info = *((mobjinfo_t **)lua_touserdata(gL, myindex));
 			WRITEUINT8(save_p, ARCH_MOBJINFO);
-			WRITEUINT8(save_p, info - mobjinfo);
+			WRITEUINT16(save_p, info - mobjinfo);
 			break;
 		}
 		case ARCH_STATE:
 		{
 			state_t *state = *((state_t **)lua_touserdata(gL, myindex));
 			WRITEUINT8(save_p, ARCH_STATE);
-			WRITEUINT8(save_p, state - states);
+			WRITEUINT16(save_p, state - states);
 			break;
 		}
 		case ARCH_MOBJ:
diff --git a/src/lua_script.h b/src/lua_script.h
index d143ed879a25944778f6105c6b123b0190184ab0..51f1eaeaaeb4d00b4c55721aa4ae8fad165cd185 100644
--- a/src/lua_script.h
+++ b/src/lua_script.h
@@ -38,6 +38,8 @@
 void LUA_ClearExtVars(void);
 #endif
 
+extern boolean lua_lumploading; // is LUA_LoadLump being called?
+
 void LUA_LoadLump(UINT16 wad, UINT16 lump);
 #ifdef LUA_ALLOW_BYTECODE
 void LUA_DumpFile(const char *filename);
diff --git a/src/lua_thinkerlib.c b/src/lua_thinkerlib.c
index b8cf1baec64f09b3065679409be925abdb23e5b1..a661aaf04f780ef95774e0606426efc03ffc4844 100644
--- a/src/lua_thinkerlib.c
+++ b/src/lua_thinkerlib.c
@@ -16,89 +16,127 @@
 #include "lua_script.h"
 #include "lua_libs.h"
 
+#define META_ITERATIONSTATE "iteration state"
+
 static const char *const iter_opt[] = {
 	"all",
 	"mobj",
 	NULL};
 
-static int lib_iterateThinkers(lua_State *L)
+static const actionf_p1 iter_funcs[] = {
+	NULL,
+	(actionf_p1)P_MobjThinker
+};
+
+struct iterationState {
+	actionf_p1 filter;
+	int next;
+};
+
+static int iterationState_gc(lua_State *L)
 {
-	int state = luaL_checkoption(L, 1, "mobj", iter_opt);
+	struct iterationState *it = luaL_checkudata(L, -1, META_ITERATIONSTATE);
+	if (it->next != LUA_REFNIL)
+	{
+		luaL_unref(L, LUA_REGISTRYINDEX, it->next);
+		it->next = LUA_REFNIL;
+	}
+	return 0;
+}
 
-	thinker_t *th = NULL;
-	actionf_p1 searchFunc;
-	const char *searchMeta;
+#define push_thinker(th) {\
+	if ((th)->function.acp1 == (actionf_p1)P_MobjThinker) \
+		LUA_PushUserdata(L, (th), META_MOBJ); \
+	else \
+		lua_pushlightuserdata(L, (th)); \
+}
+
+static int lib_iterateThinkers(lua_State *L)
+{
+	thinker_t *th = NULL, *next = NULL;
+	struct iterationState *it;
 
 	if (gamestate != GS_LEVEL)
 		return luaL_error(L, "This function can only be used in a level!");
 
+	it = luaL_checkudata(L, 1, META_ITERATIONSTATE);
+
 	lua_settop(L, 2);
-	lua_remove(L, 1); // remove state now.
 
-	switch(state)
+	if (lua_isnil(L, 2))
+		th = &thinkercap;
+	else if (lua_isuserdata(L, 2))
 	{
-		case 0:
-			searchFunc = NULL;
-			searchMeta = NULL;
-			break;
-		case 1:
-		default:
-			searchFunc = (actionf_p1)P_MobjThinker;
-			searchMeta = META_MOBJ;
-			break;
+		if (lua_islightuserdata(L, 2))
+			th = lua_touserdata(L, 2);
+		else
+		{
+			th = *(thinker_t **)lua_touserdata(L, -1);
+			if (!th)
+			{
+				if (it->next == LUA_REFNIL)
+					return 0;
+
+				lua_rawgeti(L, LUA_REGISTRYINDEX, it->next);
+				if (lua_islightuserdata(L, -1))
+					next = lua_touserdata(L, -1);
+				else
+					next = *(thinker_t **)lua_touserdata(L, -1);
+			}
+		}
 	}
 
-	if (!lua_isnil(L, 1)) {
-		if (lua_islightuserdata(L, 1))
-			th = (thinker_t *)lua_touserdata(L, 1);
-		else if (searchMeta)
-			th = *((thinker_t **)luaL_checkudata(L, 1, searchMeta));
-		else
-			th = *((thinker_t **)lua_touserdata(L, 1));
-	} else
-		th = &thinkercap;
+	luaL_unref(L, LUA_REGISTRYINDEX, it->next);
+	it->next = LUA_REFNIL;
 
-	if (!th) // something got our userdata invalidated!
-		return 0;
+	if (th && !next)
+		next = th->next;
+	if (!next)
+		return luaL_error(L, "next thinker invalidated during iteration");
 
-	if (searchFunc == NULL)
-	{
-		if ((th = th->next) != &thinkercap)
+	for (; next != &thinkercap; next = next->next)
+		if (!it->filter || next->function.acp1 == it->filter)
 		{
-			if (th->function.acp1 == (actionf_p1)P_MobjThinker)
-				LUA_PushUserdata(L, th, META_MOBJ);
-			else
-				lua_pushlightuserdata(L, th);
+			push_thinker(next);
+			if (next->next != &thinkercap)
+			{
+				push_thinker(next->next);
+				it->next = luaL_ref(L, LUA_REGISTRYINDEX);
+			}
 			return 1;
 		}
-		return 0;
-	}
-
-	for (th = th->next; th != &thinkercap; th = th->next)
-	{
-		if (th->function.acp1 != searchFunc)
-			continue;
-
-		LUA_PushUserdata(L, th, searchMeta);
-		return 1;
-	}
 	return 0;
 }
 
 static int lib_startIterate(lua_State *L)
 {
+	struct iterationState *it;
+
 	if (gamestate != GS_LEVEL)
 		return luaL_error(L, "This function can only be used in a level!");
-	luaL_checkoption(L, 1, iter_opt[0], iter_opt);
-	lua_pushcfunction(L, lib_iterateThinkers);
-	lua_pushvalue(L, 1);
+
+	lua_pushvalue(L, lua_upvalueindex(1));
+	it = lua_newuserdata(L, sizeof(struct iterationState));
+	luaL_getmetatable(L, META_ITERATIONSTATE);
+	lua_setmetatable(L, -2);
+
+	it->filter = iter_funcs[luaL_checkoption(L, 1, "mobj", iter_opt)];
+	it->next = LUA_REFNIL;
 	return 2;
 }
 
+#undef push_thinker
+
 int LUA_ThinkerLib(lua_State *L)
 {
+	luaL_newmetatable(L, META_ITERATIONSTATE);
+	lua_pushcfunction(L, iterationState_gc);
+	lua_setfield(L, -2, "__gc");
+	lua_pop(L, 1);
+
 	lua_createtable(L, 0, 1);
-		lua_pushcfunction(L, lib_startIterate);
+		lua_pushcfunction(L, lib_iterateThinkers);
+		lua_pushcclosure(L, lib_startIterate, 1);
 		lua_setfield(L, -2, "iterate");
 	lua_setglobal(L, "thinkers");
 	return 0;
diff --git a/src/m_anigif.c b/src/m_anigif.c
index 2540665ad57e8f901f315238ad74d9c352e01878..6ae112ea8ee86af4dcf57574f0988ecb9abc97d1 100644
--- a/src/m_anigif.c
+++ b/src/m_anigif.c
@@ -18,6 +18,7 @@
 #include "z_zone.h"
 #include "v_video.h"
 #include "i_video.h"
+#include "m_misc.h"
 
 // GIFs are always little-endian
 #include "byteptr.h"
@@ -396,7 +397,6 @@ static void GIF_headwrite(void)
 {
 	UINT8 *gifhead = Z_Malloc(800, PU_STATIC, NULL);
 	UINT8 *p = gifhead;
-	RGBA_t *c;
 	INT32 i;
 	UINT16 rwidth, rheight;
 
@@ -427,12 +427,17 @@ static void GIF_headwrite(void)
 	WRITEUINT8(p, 0x00);
 
 	// write color table
-	for (i = 0; i < 256; ++i)
 	{
-		c = &pLocalPalette[i];
-		WRITEUINT8(p, c->s.red);
-		WRITEUINT8(p, c->s.green);
-		WRITEUINT8(p, c->s.blue);
+		RGBA_t *pal = ((cv_screenshot_colorprofile.value)
+		? pLocalPalette
+		: pMasterPalette);
+
+		for (i = 0; i < 256; i++)
+		{
+			WRITEUINT8(p, pal[i].s.red);
+			WRITEUINT8(p, pal[i].s.green);
+			WRITEUINT8(p, pal[i].s.blue);
+		}
 	}
 
 	// write extension block
diff --git a/src/m_cheat.c b/src/m_cheat.c
index 8eb9551a808140eb8c80ce177e07c460133d241a..f0472b11ac3d662ff27deaa3831afd634ce32f2f 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -968,7 +968,7 @@ void OP_NightsObjectplace(player_t *player)
 	if (player->pflags & PF_ATTACKDOWN)
 	{
 		// Are ANY objectplace buttons pressed?  If no, remove flag.
-		if (!(cmd->buttons & (BT_ATTACK|BT_TOSSFLAG|BT_USE|BT_CAMRIGHT|BT_CAMLEFT)))
+		if (!(cmd->buttons & (BT_ATTACK|BT_TOSSFLAG|BT_USE|BT_WEAPONNEXT|BT_WEAPONPREV)))
 			player->pflags &= ~PF_ATTACKDOWN;
 
 		// Do nothing.
@@ -1019,7 +1019,7 @@ void OP_NightsObjectplace(player_t *player)
 	}
 
 	// This places a ring!
-	if (cmd->buttons & BT_CAMRIGHT)
+	if (cmd->buttons & BT_WEAPONNEXT)
 	{
 		player->pflags |= PF_ATTACKDOWN;
 		if (!OP_HeightOkay(player, false))
@@ -1030,7 +1030,7 @@ void OP_NightsObjectplace(player_t *player)
 	}
 
 	// This places a wing item!
-	if (cmd->buttons & BT_CAMLEFT)
+	if (cmd->buttons & BT_WEAPONPREV)
 	{
 		player->pflags |= PF_ATTACKDOWN;
 		if (!OP_HeightOkay(player, false))
diff --git a/src/m_cond.c b/src/m_cond.c
index 7f977c15d815b73b972094b805f4a28a6f0128d7..9339c498991ccb9993fd044b42b984eb5b5e35c6 100644
--- a/src/m_cond.c
+++ b/src/m_cond.c
@@ -499,63 +499,63 @@ emblem_t emblemlocations[MAXEMBLEMS] =
 	// FLORAL FIELD
 	// ---
 	{0, 5394, -996, 160, 50, 'N', SKINCOLOR_RUST, 0, "", 0},
-	{ET_NGRADE, 0,0,0,   50, 'Q', SKINCOLOR_TEAL,     GRADE_A, "", 0},
+	{ET_NGRADE, 0,0,0,   50, 'Q', SKINCOLOR_CYAN,     GRADE_A, "", 0},
 	{ET_NTIME,  0,0,0,   50, 'T', SKINCOLOR_GREY,  40*TICRATE, "", 0},
 
 
 	// TOXIC PLATEAU
 	// ---
 	{0, 780, -1664, 32, 51, 'N', SKINCOLOR_RUST, 0, "", 0},
-	{ET_NGRADE, 0,0,0,  51, 'Q', SKINCOLOR_TEAL,     GRADE_A, "", 0},
+	{ET_NGRADE, 0,0,0,  51, 'Q', SKINCOLOR_CYAN,     GRADE_A, "", 0},
 	{ET_NTIME,  0,0,0,  51, 'T', SKINCOLOR_GREY,  50*TICRATE, "", 0},
 
 
 	// FLOODED COVE
 	// ---
 	{0, 1824, -1888, 2448, 52, 'N', SKINCOLOR_RUST, 0, "", 0},
-	{ET_NGRADE, 0,0,0,     52, 'Q', SKINCOLOR_TEAL,     GRADE_A, "", 0},
+	{ET_NGRADE, 0,0,0,     52, 'Q', SKINCOLOR_CYAN,     GRADE_A, "", 0},
 	{ET_NTIME,  0,0,0,     52, 'T', SKINCOLOR_GREY,  90*TICRATE, "", 0},
 
 
 	// CAVERN FORTRESS
 	// ---
 	{0, -3089, -431, 1328, 53, 'N', SKINCOLOR_RUST, 0, "", 0},
-	{ET_NGRADE, 0,0,0,     53, 'Q', SKINCOLOR_TEAL,     GRADE_A, "", 0},
+	{ET_NGRADE, 0,0,0,     53, 'Q', SKINCOLOR_CYAN,     GRADE_A, "", 0},
 	{ET_NTIME,  0,0,0,     53, 'T', SKINCOLOR_GREY,  75*TICRATE, "", 0},
 
 
 	// DUSTY WASTELAND
 	// ---
 	{0, 957, 924, 2956, 54, 'N', SKINCOLOR_RUST, 0, "", 0},
-	{ET_NGRADE, 0,0,0,  54, 'Q', SKINCOLOR_TEAL,     GRADE_A, "", 0},
+	{ET_NGRADE, 0,0,0,  54, 'Q', SKINCOLOR_CYAN,     GRADE_A, "", 0},
 	{ET_NTIME,  0,0,0,  54, 'T', SKINCOLOR_GREY,  65*TICRATE, "", 0},
 
 
 	// MAGMA CAVES
 	// ---
 	{0, -2752, 3104, 1800, 55, 'N', SKINCOLOR_RUST, 0, "", 0},
-	{ET_NGRADE, 0,0,0,     55, 'Q', SKINCOLOR_TEAL,     GRADE_A, "", 0},
+	{ET_NGRADE, 0,0,0,     55, 'Q', SKINCOLOR_CYAN,     GRADE_A, "", 0},
 	{ET_NTIME,  0,0,0,     55, 'T', SKINCOLOR_GREY,  80*TICRATE, "", 0},
 
 
 	// EGG SATELLITE
 	// ---
 	{0, 5334, -609, 3426, 56, 'N', SKINCOLOR_RUST, 0, "", 0},
-	{ET_NGRADE, 0,0,0,    56, 'Q', SKINCOLOR_TEAL,     GRADE_A, "", 0},
+	{ET_NGRADE, 0,0,0,    56, 'Q', SKINCOLOR_CYAN,     GRADE_A, "", 0},
 	{ET_NTIME,  0,0,0,    56, 'T', SKINCOLOR_GREY, 120*TICRATE, "", 0},
 
 
 	// BLACK HOLE
 	// ---
 	{0, 2108, 3776, 32, 57, 'N', SKINCOLOR_RUST, 0, "", 0},
-	{ET_NGRADE, 0,0,0,  57, 'Q', SKINCOLOR_TEAL,     GRADE_A, "", 0},
+	{ET_NGRADE, 0,0,0,  57, 'Q', SKINCOLOR_CYAN,     GRADE_A, "", 0},
 	{ET_NTIME,  0,0,0,  57, 'T', SKINCOLOR_GREY, 150*TICRATE, "", 0},
 
 
 	// SPRING HILL
 	// ---
 	{0, -1840, -1024, 1644, 58, 'N', SKINCOLOR_RUST, 0, "", 0},
-	{ET_NGRADE, 0,0,0,      58, 'Q', SKINCOLOR_TEAL,     GRADE_A, "", 0},
+	{ET_NGRADE, 0,0,0,      58, 'Q', SKINCOLOR_CYAN,     GRADE_A, "", 0},
 	{ET_NTIME,  0,0,0,      58, 'T', SKINCOLOR_GREY,  60*TICRATE, "", 0},
 };
 
@@ -566,38 +566,38 @@ extraemblem_t extraemblems[MAXEXTRAEMBLEMS] =
 	{"All Emeralds",   "Complete 1P Mode with all Emeralds",  11, 'V', SKINCOLOR_GREY, 0},
 	{"Perfect Bonus",  "Perfect Bonus on a non-secret stage", 30, 'P', SKINCOLOR_GOLD, 0},
 	{"PLACEHOLDER", "PLACEHOLDER", 0, 'O', SKINCOLOR_RUST, 0},
-	{"NiGHTS Mastery", "Show your mastery of NiGHTS!",        22, 'W', SKINCOLOR_TEAL, 0},
+	{"NiGHTS Mastery", "Show your mastery of NiGHTS!",        22, 'W', SKINCOLOR_CYAN, 0},
 };
 
 // Default Unlockables
 unlockable_t unlockables[MAXUNLOCKABLES] =
 {
 	// Name, Objective, Menu Height, ConditionSet, Unlock Type, Variable, NoCecho, NoChecklist
-	/* 01 */ {"Record Attack",     "Complete Greenflower Zone, Act 1", 0, 1, SECRET_RECORDATTACK,  0,  true,  true, 0},
-	/* 02 */ {"NiGHTS Mode",       "Complete Floral Field",            0, 2, SECRET_NIGHTSMODE,    0,  true,  true, 0},
+	/* 01 */ {"Record Attack",     "/", 0, 1, SECRET_RECORDATTACK,  0,  true,  true, 0},
+	/* 02 */ {"NiGHTS Mode",       "/",            0, 2, SECRET_NIGHTSMODE,    0,  true,  true, 0},
 
-	/* 03 */ {"Play Credits",      "Complete 1P Mode", 30, 10, SECRET_CREDITS,   0,  true,  true, 0},
-	/* 04 */ {"Sound Test",        "Complete 1P Mode", 40, 10, SECRET_SOUNDTEST, 0, false, false, 0},
+	/* 03 */ {"Play Credits",      "/", 30, 10, SECRET_CREDITS,   0,  true,  true, 0},
+	/* 04 */ {"Sound Test",        "/", 40, 10, SECRET_SOUNDTEST, 0, false, false, 0},
 
-	/* 05 */ {"EXTRA LEVELS", "", 60, 0, SECRET_HEADER, 0, true, true, 0},
+	/* 05 */ {"EXTRA LEVELS", "/", 58, 0, SECRET_HEADER, 0, true, true, 0},
 
-	/* 06 */ {"Aerial Garden Zone", "Complete 1P Mode w/ all emeralds", 70, 11, SECRET_WARP, 40, false, false, 0},
-	/* 07 */ {"Azure Temple Zone",  "Complete Aerial Garden Zone",      80, 20, SECRET_WARP, 41, false, false, 0},
+	/* 06 */ {"Aerial Garden Zone", "/", 70, 11, SECRET_WARP, 40, false, false, 0},
+	/* 07 */ {"Azure Temple Zone",  "/",      80, 20, SECRET_WARP, 41, false, false, 0},
 
-	/* 08 */ {"BONUS LEVELS", "", 100, 0, SECRET_HEADER, 0, true, true, 0},
+	/* 08 */ {"BONUS LEVELS", "/", 98, 0, SECRET_HEADER, 0, true, true, 0},
 
-	/* 09 */ {"PLACEHOLDER", "PLACEHOLDER", 0, 0, SECRET_NONE, 0, true, true, 0},
-	/* 10 */ {"Mario Koopa Blast", "Collect 60 Emblems",   110, 42, SECRET_WARP,         30, false, false, 0},
-	/* 11 */ {"PLACEHOLDER", "PLACEHOLDER", 0, 0, SECRET_NONE, 0, true, true, 0},
+	/* 09 */ {"PLACEHOLDER", "/", 0, 0, SECRET_NONE, 0, true, true, 0},
+	/* 10 */ {"Mario Koopa Blast", "/",   110, 42, SECRET_WARP,         30, false, false, 0},
+	/* 11 */ {"PLACEHOLDER", "/", 0, 0, SECRET_NONE, 0, true, true, 0},
 
-	/* 12 */ {"Spring Hill Zone", "Collect 100 Emblems",          0, 44, SECRET_NONE, 0, false, false, 0},
-	/* 13 */ {"Black Hole",       "A Rank in all Special Stages", 0, 50, SECRET_NONE, 0, false, true, 0},
+	/* 12 */ {"Spring Hill Zone", "/",          0, 44, SECRET_NONE, 0, false, false, 0},
+	/* 13 */ {"Black Hole",       "Get grade A in all Special Stages", 0, 50, SECRET_NONE, 0, false, true, 0},
 
-	/* 14 */ {"Emblem Hints", "Collect 40 Emblems", 0, 41, SECRET_EMBLEMHINTS, 0, false,  true, 0},
-	/* 15 */ {"Emblem Radar", "Collect 80 Emblems", 0, 43, SECRET_ITEMFINDER,  0, false,  true, 0},
+	/* 14 */ {"Emblem Hints", "/", 0, 41, SECRET_EMBLEMHINTS, 0, false,  true, 0},
+	/* 15 */ {"Emblem Radar", "/", 0, 43, SECRET_ITEMFINDER,  0, false,  true, 0},
 
-	/* 16 */ {"Pandora's Box", "Collect All Emblems",  0, 45, SECRET_PANDORA,     0, false, false, 0},
-	/* 17 */ {"Level Select",  "Collect All Emblems", 20, 45, SECRET_LEVELSELECT, 1, false,  true, 0},
+	/* 16 */ {"Pandora's Box", "/",  0, 45, SECRET_PANDORA,     0, false, false, 0},
+	/* 17 */ {"Level Select",  "/", 20, 45, SECRET_LEVELSELECT, 1, false,  true, 0},
 };
 
 // Default number of emblems and extra emblems
diff --git a/src/m_cond.h b/src/m_cond.h
index 94802f66594bfd3f2f3e7162a87b4387d66c0ace..00f633a8d079e1c79a6e57b2189fc349f23a2c0b 100644
--- a/src/m_cond.h
+++ b/src/m_cond.h
@@ -66,14 +66,18 @@ typedef struct
 } conditionset_t;
 
 // Emblem information
-#define ET_GLOBAL 0 // Global map emblem, var == color
-#define ET_SKIN   1 // Skin specific emblem, var == skin
-#define ET_SCORE  2
-#define ET_TIME   3
-#define ET_RINGS  4
-#define ET_NGRADE 5
-#define ET_NTIME  6
-#define ET_MAP    7
+#define ET_GLOBAL 0 // Emblem with a position in space
+#define ET_SKIN   1 // Skin specific emblem with a position in space, var == skin
+#define ET_MAP    2 // Beat the map
+#define ET_SCORE  3 // Get the score
+#define ET_TIME   4 // Get the time
+#define ET_RINGS  5 // Get the rings
+#define ET_NGRADE 6 // Get the grade
+#define ET_NTIME  7 // Get the time (NiGHTS mode)
+
+// Global emblem flags
+#define GE_NIGHTSPULL 1 // sun off the nights track - loop it
+#define GE_NIGHTSITEM 2 // moon on the nights track - find it
 
 // Map emblem flags
 #define ME_ALLEMERALDS 1
diff --git a/src/m_menu.c b/src/m_menu.c
index fb8aeedad1252f9c733b5e94ba5cfd7ed030e18f..64255e71a82d6630a962878cde7c634a8262af9c 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -33,6 +33,9 @@
 #include "s_sound.h"
 #include "i_system.h"
 
+// Addfile
+#include "filesrch.h"
+
 #include "v_video.h"
 #include "i_video.h"
 #include "keys.h"
@@ -72,9 +75,8 @@ int	snprintf(char *str, size_t n, const char *fmt, ...);
 #define STRINGHEIGHT 8
 #define FONTBHEIGHT 20
 #define SMALLLINEHEIGHT 8
-#define SLIDER_RANGE 10
-#define SLIDER_WIDTH (8*SLIDER_RANGE+6)
-#define MAXSTRINGLENGTH 32
+#define SLIDER_RANGE 9
+#define SLIDER_WIDTH 78
 #define SERVERS_PER_PAGE 11
 
 typedef enum
@@ -211,14 +213,13 @@ menu_t SPauseDef;
 
 // Level Select
 static levelselect_t levelselect = {0, NULL};
-static UINT8 levelselectselect[4];
+static UINT8 levelselectselect[3];
 static patch_t *levselp[2][3];
 static INT32 lsoffs[2];
 
 #define lsrow levelselectselect[0]
 #define lscol levelselectselect[1]
-#define lstic levelselectselect[2]
-#define lshli levelselectselect[3]
+#define lshli levelselectselect[2]
 
 #define lshseperation 101
 #define lsbasevseperation 62
@@ -241,8 +242,11 @@ static void M_LevelSelectWarp(INT32 choice);
 static void M_Credits(INT32 choice);
 static void M_PandorasBox(INT32 choice);
 static void M_EmblemHints(INT32 choice);
+static void M_HandleChecklist(INT32 choice);
 menu_t SR_MainDef, SR_UnlockChecklistDef;
 
+static UINT8 check_on;
+
 // Misc. Main Menu
 static void M_SinglePlayerMenu(INT32 choice);
 static void M_Options(INT32 choice);
@@ -259,7 +263,7 @@ static void M_ConfirmTeamChange(INT32 choice);
 static void M_SecretsMenu(INT32 choice);
 static void M_SetupChoosePlayer(INT32 choice);
 static void M_QuitSRB2(INT32 choice);
-menu_t SP_MainDef, MP_MainDef, OP_MainDef;
+menu_t SP_MainDef, OP_MainDef;
 menu_t MISC_ScrambleTeamDef, MISC_ChangeTeamDef;
 
 // Single Player
@@ -277,35 +281,34 @@ static void M_ModeAttackEndGame(INT32 choice);
 static void M_SetGuestReplay(INT32 choice);
 static void M_HandleChoosePlayerMenu(INT32 choice);
 static void M_ChoosePlayer(INT32 choice);
-menu_t SP_GameStatsDef, SP_LevelStatsDef;
+menu_t SP_LevelStatsDef;
 static menu_t SP_TimeAttackDef, SP_ReplayDef, SP_GuestReplayDef, SP_GhostDef;
 static menu_t SP_NightsAttackDef, SP_NightsReplayDef, SP_NightsGuestReplayDef, SP_NightsGhostDef;
 
 // Multiplayer
-#ifndef NONET
-static void M_StartServerMenu(INT32 choice);
-static void M_ConnectMenu(INT32 choice);
-static void M_ConnectIPMenu(INT32 choice);
-#endif
+static void M_SetupMultiPlayer(INT32 choice);
+static void M_SetupMultiPlayer2(INT32 choice);
 static void M_StartSplitServerMenu(INT32 choice);
 static void M_StartServer(INT32 choice);
 static void M_ServerOptions(INT32 choice);
 #ifndef NONET
+static void M_StartServerMenu(INT32 choice);
+static void M_ConnectMenu(INT32 choice);
 static void M_Refresh(INT32 choice);
 static void M_Connect(INT32 choice);
 static void M_ChooseRoom(INT32 choice);
+menu_t MP_MainDef;
 #endif
-static void M_SetupMultiPlayer(INT32 choice);
-static void M_SetupMultiPlayer2(INT32 choice);
 
 // Options
 // Split into multiple parts due to size
 // Controls
-menu_t OP_ControlsDef, OP_ControlListDef, OP_MoveControlsDef;
+menu_t OP_ChangeControlsDef;
 menu_t OP_MPControlsDef, OP_CameraControlsDef, OP_MiscControlsDef;
 menu_t OP_P1ControlsDef, OP_P2ControlsDef, OP_MouseOptionsDef;
 menu_t OP_Mouse2OptionsDef, OP_Joystick1Def, OP_Joystick2Def;
 static void M_VideoModeMenu(INT32 choice);
+static void M_SoundMenu(INT32 choice);
 static void M_Setup1PControlsMenu(INT32 choice);
 static void M_Setup2PControlsMenu(INT32 choice);
 static void M_Setup1PJoystickMenu(INT32 choice);
@@ -314,26 +317,36 @@ static void M_AssignJoystick(INT32 choice);
 static void M_ChangeControl(INT32 choice);
 
 // Video & Sound
-menu_t OP_VideoOptionsDef, OP_VideoModeDef;
+menu_t OP_VideoOptionsDef, OP_VideoModeDef, OP_ColorOptionsDef;
 #ifdef HWRENDER
 menu_t OP_OpenGLOptionsDef, OP_OpenGLFogDef, OP_OpenGLColorDef;
 #endif
 menu_t OP_SoundOptionsDef;
-static void M_ToggleSFX(void);
-static void M_ToggleDigital(void);
-static void M_ToggleMIDI(void);
+static void M_ToggleSFX(INT32 choice);
+static void M_ToggleDigital(INT32 choice);
+static void M_ToggleMIDI(INT32 choice);
 
 //Misc
 menu_t OP_DataOptionsDef, OP_ScreenshotOptionsDef, OP_EraseDataDef;
-menu_t OP_GameOptionsDef, OP_ServerOptionsDef;
-menu_t OP_NetgameOptionsDef, OP_GametypeOptionsDef;
+menu_t OP_ServerOptionsDef;
 menu_t OP_MonitorToggleDef;
 static void M_ScreenshotOptions(INT32 choice);
 static void M_EraseData(INT32 choice);
 
+static void M_Addons(INT32 choice);
+static void M_AddonsOptions(INT32 choice);
+static patch_t *addonsp[NUM_EXT+6];
+static UINT8 addonsresponselimit = 0;
+
+#define numaddonsshown 4
+
+static void M_DrawLevelPlatterHeader(INT32 y, const char *header, boolean headerhighlight, boolean allowlowercase);
+
 // Drawing functions
 static void M_DrawGenericMenu(void);
+static void M_DrawGenericScrollMenu(void);
 static void M_DrawCenteredMenu(void);
+static void M_DrawAddons(void);
 static void M_DrawSkyRoom(void);
 static void M_DrawChecklist(void);
 static void M_DrawEmblemHints(void);
@@ -344,38 +357,40 @@ static void M_DrawLevelPlatterMenu(void);
 static void M_DrawImageDef(void);
 static void M_DrawLoad(void);
 static void M_DrawLevelStats(void);
-static void M_DrawGameStats(void);
 static void M_DrawTimeAttackMenu(void);
 static void M_DrawNightsAttackMenu(void);
 static void M_DrawSetupChoosePlayerMenu(void);
 static void M_DrawControl(void);
+static void M_DrawMainVideoMenu(void);
 static void M_DrawVideoMode(void);
+static void M_DrawColorMenu(void);
+static void M_DrawSoundMenu(void);
+static void M_DrawScreenshotMenu(void);
 static void M_DrawMonitorToggles(void);
 #ifdef HWRENDER
 static void M_OGL_DrawFogMenu(void);
 static void M_OGL_DrawColorMenu(void);
 #endif
 #ifndef NONET
+static void M_DrawScreenshotMenu(void);
 static void M_DrawConnectMenu(void);
-static void M_DrawConnectIPMenu(void);
+static void M_DrawMPMainMenu(void);
 static void M_DrawRoomMenu(void);
 #endif
 static void M_DrawJoystick(void);
 static void M_DrawSetupMultiPlayerMenu(void);
 
 // Handling functions
-#ifndef NONET
-static boolean M_CancelConnect(void);
-#endif
 static boolean M_ExitPandorasBox(void);
 static boolean M_QuitMultiPlayerMenu(void);
+static void M_HandleAddons(INT32 choice);
 static void M_HandleLevelPlatter(INT32 choice);
 static void M_HandleSoundTest(INT32 choice);
 static void M_HandleImageDef(INT32 choice);
 static void M_HandleLoadSave(INT32 choice);
-static void M_HandleGameStats(INT32 choice);
 static void M_HandleLevelStats(INT32 choice);
 #ifndef NONET
+static boolean M_CancelConnect(void);
 static void M_HandleConnectIP(INT32 choice);
 #endif
 static void M_HandleSetupMultiPlayer(INT32 choice);
@@ -384,6 +399,8 @@ static void M_HandleFogColor(INT32 choice);
 #endif
 static void M_HandleVideoMode(INT32 choice);
 
+static void M_ResetCvars(void);
+
 // Consvar onchange functions
 static void Newgametype_OnChange(void);
 static void Dummymares_OnChange(void);
@@ -476,11 +493,16 @@ static consvar_t cv_dummymares = {"dummymares", "Overall", CV_HIDEN|CV_CALL, dum
 // ---------
 static menuitem_t MainMenu[] =
 {
-	{IT_CALL   |IT_STRING, NULL, "Secrets",     M_SecretsMenu,      84},
-	{IT_CALL   |IT_STRING, NULL, "1  player",   M_SinglePlayerMenu, 92},
-	{IT_SUBMENU|IT_STRING, NULL, "multiplayer", &MP_MainDef,       100},
-	{IT_CALL   |IT_STRING, NULL, "options",     M_Options,         108},
-	{IT_CALL   |IT_STRING, NULL, "quit  game",  M_QuitSRB2,        116},
+	{IT_STRING|IT_CALL,    NULL, "Secrets",     M_SecretsMenu,           76},
+	{IT_STRING|IT_CALL,    NULL, "1  player",   M_SinglePlayerMenu,      84},
+#ifndef NONET
+	{IT_STRING|IT_SUBMENU, NULL, "multiplayer", &MP_MainDef,             92},
+#else
+	{IT_STRING|IT_CALL,    NULL, "multiplayer", M_StartSplitServerMenu,  92},
+#endif
+	{IT_STRING|IT_CALL,    NULL, "options",     M_Options,              100},
+	{IT_CALL   |IT_STRING, NULL, "addons",      M_Addons,               108},
+	{IT_STRING|IT_CALL,    NULL, "quit  game",  M_QuitSRB2,             116},
 };
 
 typedef enum
@@ -489,9 +511,15 @@ typedef enum
 	singleplr,
 	multiplr,
 	options,
+	addons,
 	quitdoom
 } main_e;
 
+static menuitem_t MISC_AddonsMenu[] =
+{
+	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleAddons, 0},     // dummy menuitem for the control func
+};
+
 // ---------------------------------
 // Pause Menu Mode Attacking Edition
 // ---------------------------------
@@ -514,26 +542,28 @@ typedef enum
 // ---------------------
 static menuitem_t MPauseMenu[] =
 {
-	{IT_STRING  | IT_SUBMENU, NULL, "Scramble Teams...",        &MISC_ScrambleTeamDef, 16},
-	{IT_STRING  | IT_CALL,    NULL, "Switch Gametype/Level...", M_GameTypeChange,      24},
+	{IT_STRING | IT_CALL,    NULL, "Add-ons...",                M_Addons,               8},
+	{IT_STRING | IT_SUBMENU, NULL, "Scramble Teams...",         &MISC_ScrambleTeamDef, 16},
+	{IT_STRING | IT_CALL,    NULL, "Switch Gametype/Level...",  M_GameTypeChange,      24},
 
-	{IT_CALL | IT_STRING,    NULL, "Continue",                  M_SelectableClearMenus,40},
-	{IT_CALL | IT_STRING,    NULL, "Player 1 Setup",            M_SetupMultiPlayer,    48}, // splitscreen
-	{IT_CALL | IT_STRING,    NULL, "Player 2 Setup",            M_SetupMultiPlayer2,   56}, // splitscreen
+	{IT_STRING | IT_CALL,    NULL, "Continue",                  M_SelectableClearMenus,40},
+	{IT_STRING | IT_CALL,    NULL, "Player 1 Setup",            M_SetupMultiPlayer,    48}, // splitscreen
+	{IT_STRING | IT_CALL,    NULL, "Player 2 Setup",            M_SetupMultiPlayer2,   56}, // splitscreen
 
 	{IT_STRING | IT_CALL,    NULL, "Spectate",                  M_ConfirmSpectate,     48},
 	{IT_STRING | IT_CALL,    NULL, "Enter Game",                M_ConfirmEnterGame,    48},
 	{IT_STRING | IT_SUBMENU, NULL, "Switch Team...",            &MISC_ChangeTeamDef,   48},
-	{IT_CALL | IT_STRING,    NULL, "Player Setup",              M_SetupMultiPlayer,    56}, // alone
-	{IT_CALL | IT_STRING,    NULL, "Options",                   M_Options,             64},
+	{IT_STRING | IT_CALL,    NULL, "Player Setup",              M_SetupMultiPlayer,    56}, // alone
+	{IT_STRING | IT_CALL,    NULL, "Options",                   M_Options,             64},
 
-	{IT_CALL | IT_STRING,    NULL, "Return to Title",           M_EndGame,             80},
-	{IT_CALL | IT_STRING,    NULL, "Quit Game",                 M_QuitSRB2,            88},
+	{IT_STRING | IT_CALL,    NULL, "Return to Title",           M_EndGame,             80},
+	{IT_STRING | IT_CALL,    NULL, "Quit Game",                 M_QuitSRB2,            88},
 };
 
 typedef enum
 {
-	mpause_scramble = 0,
+	mpause_addons = 0,
+	mpause_scramble,
 	mpause_switchmap,
 
 	mpause_continue,
@@ -576,6 +606,7 @@ typedef enum
 	spause_continue,
 	spause_retry,
 	spause_options,
+
 	spause_title,
 	spause_quit
 } spause_e;
@@ -705,7 +736,7 @@ static menuitem_t SR_LevelSelectMenu[] =
 
 static menuitem_t SR_UnlockChecklistMenu[] =
 {
-	{IT_SUBMENU | IT_STRING,         NULL, "NEXT", &SR_MainDef, 192},
+	{IT_KEYHANDLER | IT_STRING, NULL, "", M_HandleChecklist, 0},
 };
 
 static menuitem_t SR_EmblemHintMenu[] =
@@ -876,11 +907,6 @@ enum
 };
 
 // Statistics
-static menuitem_t SP_GameStatsMenu[] =
-{
-	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleGameStats, 0},     // dummy menuitem for the control func
-};
-
 static menuitem_t SP_LevelStatsMenu[] =
 {
 	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleLevelStats, 0},     // dummy menuitem for the control func
@@ -896,56 +922,56 @@ static menuitem_t SP_PlayerMenu[] =
 // Multiplayer and all of its submenus
 // -----------------------------------
 // Prefix: MP_
-static menuitem_t MP_MainMenu[] =
+
+// Separated splitscreen and normal servers.
+static menuitem_t MP_SplitServerMenu[] =
 {
-#ifndef NONET
-	{IT_CALL | IT_STRING, NULL, "HOST GAME",              M_StartServerMenu,      10},
-	{IT_CALL | IT_STRING, NULL, "JOIN GAME (Search)",	  M_ConnectMenu,		  30},
-	{IT_CALL | IT_STRING, NULL, "JOIN GAME (Specify IP)", M_ConnectIPMenu,        40},
+	{IT_STRING|IT_CALL,              NULL, "Select Gametype/Level...", M_GameTypeChange,    100},
+#ifdef NONET // In order to keep player setup accessible.
+	{IT_STRING|IT_CALL,              NULL, "Player 1 setup...",        M_SetupMultiPlayer,  110},
+	{IT_STRING|IT_CALL,              NULL, "Player 2 setup...",        M_SetupMultiPlayer2, 120},
 #endif
-	{IT_CALL | IT_STRING, NULL, "TWO PLAYER GAME",        M_StartSplitServerMenu, 60},
+	{IT_STRING|IT_CALL,              NULL, "More Options...",          M_ServerOptions,     130},
+	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                    M_StartServer,       140},
+};
 
-	{IT_CALL | IT_STRING, NULL, "SETUP PLAYER 1",         M_SetupMultiPlayer,     80},
-	{IT_CALL | IT_STRING, NULL, "SETUP PLAYER 2",         M_SetupMultiPlayer2,    90},
+#ifndef NONET
+
+static menuitem_t MP_MainMenu[] =
+{
+	{IT_HEADER, NULL, "Host a game", NULL, 0},
+	{IT_STRING|IT_CALL,       NULL, "Internet/LAN...",       M_StartServerMenu,      12},
+	{IT_STRING|IT_CALL,       NULL, "Splitscreen...",        M_StartSplitServerMenu, 22},
+	{IT_HEADER, NULL, "Join a game", NULL, 40},
+	{IT_STRING|IT_CALL,       NULL, "Server browser...",     M_ConnectMenu,          52},
+	{IT_STRING|IT_KEYHANDLER, NULL, "Specify IPv4 address:", M_HandleConnectIP,      62},
+	{IT_HEADER, NULL, "Player setup", NULL, 94},
+	{IT_STRING|IT_CALL,       NULL, "Player 1...",           M_SetupMultiPlayer,    106},
+	{IT_STRING|IT_CALL,       NULL, "Player 2... ",          M_SetupMultiPlayer2,   116},
 };
 
 static menuitem_t MP_ServerMenu[] =
 {
-	{IT_DISABLED|IT_NOTHING, NULL, "", NULL, 0},
-#ifndef NONET
-	{IT_STRING|IT_CALL,              NULL, "Room...",                  M_RoomMenu,        10},
-	{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Server Name",              &cv_servername,    20},
-	{IT_STRING|IT_CVAR,              NULL, "Max Players",              &cv_maxplayers,    46},
-	{IT_STRING|IT_CVAR,              NULL, "Allow WAD Downloading",    &cv_downloading,   56},
-#endif
-	{IT_STRING|IT_CALL,              NULL, "Select Gametype/Level...", M_GameTypeChange, 100},
-	{IT_STRING|IT_CALL,              NULL, "More Options...",          M_ServerOptions,  130},
-	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                    M_StartServer,    140},
+	{IT_STRING|IT_CALL,              NULL, "Room...",                  M_RoomMenu,          10},
+	{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Server Name",              &cv_servername,      20},
+	{IT_STRING|IT_CVAR,              NULL, "Max Players",              &cv_maxplayers,      46},
+	{IT_STRING|IT_CVAR,              NULL, "Allow Add-on Downloading", &cv_downloading,     56},
+	{IT_STRING|IT_CALL,              NULL, "Select Gametype/Level...", M_GameTypeChange,   100},
+	{IT_STRING|IT_CALL,              NULL, "More Options...",          M_ServerOptions,    130},
+	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                    M_StartServer,      140},
 };
 
 enum
 {
-	mp_server_dummy = 0, // exists solely so zero-indexed in both NONET and not NONET
-#ifndef NONET
-	mp_server_room,
+	mp_server_room = 0,
 	mp_server_name,
 	mp_server_maxpl,
 	mp_server_waddl,
-#endif
 	mp_server_levelgt,
 	mp_server_options,
 	mp_server_start
 };
 
-// Separated splitscreen and normal servers.
-static menuitem_t MP_SplitServerMenu[] =
-{
-	{IT_STRING|IT_CALL,              NULL, "Select Gametype/Level...", M_GameTypeChange, 100},
-	{IT_STRING|IT_CALL,              NULL, "More Options...",          M_ServerOptions,  130},
-	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                    M_StartServer,    140},
-};
-
-#ifndef NONET
 static menuitem_t MP_ConnectMenu[] =
 {
 	{IT_STRING | IT_CALL,       NULL, "Room...",  M_RoomMenu,         4},
@@ -997,17 +1023,14 @@ static menuitem_t MP_RoomMenu[] =
 	{IT_DISABLED,         NULL, "",               M_ChooseRoom, 162},
 };
 
-static menuitem_t MP_ConnectIPMenu[] =
-{
-	{IT_KEYHANDLER | IT_STRING, NULL, "  IP Address:", M_HandleConnectIP, 0},
-};
 #endif
 
 static menuitem_t MP_PlayerSetupMenu[] =
 {
-	{IT_KEYHANDLER | IT_STRING,   NULL, "Your name",   M_HandleSetupMultiPlayer,   0},
-	{IT_KEYHANDLER | IT_STRING,   NULL, "Your color",  M_HandleSetupMultiPlayer,  16},
-	{IT_KEYHANDLER | IT_STRING,   NULL, "Your player", M_HandleSetupMultiPlayer,  96}, // Tails 01-18-2001
+	{IT_KEYHANDLER, NULL, "", M_HandleSetupMultiPlayer, 0}, // name
+	{IT_KEYHANDLER, NULL, "", M_HandleSetupMultiPlayer, 0}, // skin
+	{IT_KEYHANDLER, NULL, "", M_HandleSetupMultiPlayer, 0}, // colour
+	{IT_KEYHANDLER, NULL, "", M_HandleSetupMultiPlayer, 0}, // default
 };
 
 // ------------------------------------
@@ -1016,22 +1039,16 @@ static menuitem_t MP_PlayerSetupMenu[] =
 // Prefix: OP_
 static menuitem_t OP_MainMenu[] =
 {
-	{IT_SUBMENU | IT_STRING, NULL, "Setup Controls...", &OP_ControlsDef,      10},
+	{IT_SUBMENU | IT_STRING, NULL, "Player 1 Controls...", &OP_P1ControlsDef,   10},
+	{IT_SUBMENU | IT_STRING, NULL, "Player 2 Controls...", &OP_P2ControlsDef,   20},
+	{IT_CVAR    | IT_STRING, NULL, "Controls per key",     &cv_controlperkey,   30},
 
-	{IT_SUBMENU | IT_STRING, NULL, "Video Options...",  &OP_VideoOptionsDef,  30},
-	{IT_SUBMENU | IT_STRING, NULL, "Sound Options...",  &OP_SoundOptionsDef,  40},
-	{IT_SUBMENU | IT_STRING, NULL, "Data Options...",   &OP_DataOptionsDef,   50},
+	{IT_SUBMENU | IT_STRING, NULL, "Video Options...",     &OP_VideoOptionsDef, 50},
+	{IT_CALL    | IT_STRING, NULL, "Sound Options...",     M_SoundMenu,         60},
 
-	{IT_SUBMENU | IT_STRING, NULL, "Game Options...",   &OP_GameOptionsDef,   70},
-	{IT_CALL | IT_STRING,    NULL, "Server Options...", M_ServerOptions,      80},
-};
-
-static menuitem_t OP_ControlsMenu[] =
-{
-	{IT_SUBMENU | IT_STRING, NULL, "Player 1 Controls...", &OP_P1ControlsDef,  10},
-	{IT_SUBMENU | IT_STRING, NULL, "Player 2 Controls...", &OP_P2ControlsDef,  20},
+	{IT_CALL    | IT_STRING, NULL, "Server Options...",    M_ServerOptions,     80},
 
-	{IT_STRING  | IT_CVAR, NULL, "Controls per key", &cv_controlperkey, 40},
+	{IT_SUBMENU | IT_STRING, NULL, "Data Options...",      &OP_DataOptionsDef, 100},
 };
 
 static menuitem_t OP_P1ControlsMenu[] =
@@ -1040,10 +1057,11 @@ static menuitem_t OP_P1ControlsMenu[] =
 	{IT_SUBMENU | IT_STRING, NULL, "Mouse Options...", &OP_MouseOptionsDef, 20},
 	{IT_SUBMENU | IT_STRING, NULL, "Joystick Options...", &OP_Joystick1Def  ,  30},
 
-	{IT_STRING  | IT_CVAR, NULL, "Camera"  , &cv_chasecam  ,  50},
-	{IT_STRING  | IT_CVAR, NULL, "Crosshair", &cv_crosshair , 60},
+	{IT_STRING  | IT_CVAR, NULL, "Third-person Camera"  , &cv_chasecam , 50},
+	{IT_STRING  | IT_CVAR, NULL, "Flip Camera with Gravity"  , &cv_flipcam , 60},
+	{IT_STRING  | IT_CVAR, NULL, "Crosshair", &cv_crosshair, 70},
 
-	{IT_STRING  | IT_CVAR, NULL, "Analog Control", &cv_useranalog,  80},
+	{IT_STRING  | IT_CVAR, NULL, "Analog Control", &cv_useranalog,  90},
 };
 
 static menuitem_t OP_P2ControlsMenu[] =
@@ -1052,93 +1070,84 @@ static menuitem_t OP_P2ControlsMenu[] =
 	{IT_SUBMENU | IT_STRING, NULL, "Second Mouse Options...", &OP_Mouse2OptionsDef, 20},
 	{IT_SUBMENU | IT_STRING, NULL, "Second Joystick Options...", &OP_Joystick2Def  ,  30},
 
-	{IT_STRING  | IT_CVAR, NULL, "Camera"  , &cv_chasecam2 , 50},
-	{IT_STRING  | IT_CVAR, NULL, "Crosshair", &cv_crosshair2, 60},
-
-	{IT_STRING  | IT_CVAR, NULL, "Analog Control", &cv_useranalog2,  80},
-};
-
-static menuitem_t OP_ControlListMenu[] =
-{
-	{IT_SUBMENU | IT_STRING, NULL, "Movement Controls...",      &OP_MoveControlsDef,   10},
-	{IT_SUBMENU | IT_STRING, NULL, "Multiplayer Controls...",   &OP_MPControlsDef,     20},
-	{IT_SUBMENU | IT_STRING, NULL, "Camera Controls...",        &OP_CameraControlsDef, 30},
-	{IT_SUBMENU | IT_STRING, NULL, "Miscellaneous Controls...", &OP_MiscControlsDef,   40},
-};
-
-static menuitem_t OP_MoveControlsMenu[] =
-{
-	{IT_CALL | IT_STRING2, NULL, "Forward",      M_ChangeControl, gc_forward    },
-	{IT_CALL | IT_STRING2, NULL, "Reverse",      M_ChangeControl, gc_backward   },
-	{IT_CALL | IT_STRING2, NULL, "Turn Left",    M_ChangeControl, gc_turnleft   },
-	{IT_CALL | IT_STRING2, NULL, "Turn Right",   M_ChangeControl, gc_turnright  },
-	{IT_CALL | IT_STRING2, NULL, "Jump",         M_ChangeControl, gc_jump       },
-	{IT_CALL | IT_STRING2, NULL, "Spin",         M_ChangeControl, gc_use        },
-	{IT_CALL | IT_STRING2, NULL, "Strafe Left",  M_ChangeControl, gc_strafeleft },
-	{IT_CALL | IT_STRING2, NULL, "Strafe Right", M_ChangeControl, gc_straferight},
-};
-
-static menuitem_t OP_MPControlsMenu[] =
-{
-	{IT_CALL | IT_STRING2, NULL, "Talk key",         M_ChangeControl, gc_talkkey      },
-	{IT_CALL | IT_STRING2, NULL, "Team-Talk key",    M_ChangeControl, gc_teamkey      },
-	{IT_CALL | IT_STRING2, NULL, "Rankings/Scores",  M_ChangeControl, gc_scores       },
-	{IT_CALL | IT_STRING2, NULL, "Toss Flag",        M_ChangeControl, gc_tossflag     },
-	{IT_CALL | IT_STRING2, NULL, "Next Weapon",      M_ChangeControl, gc_weaponnext   },
-	{IT_CALL | IT_STRING2, NULL, "Prev Weapon",      M_ChangeControl, gc_weaponprev   },
-	{IT_CALL | IT_STRING2, NULL, "Weapon Slot 1",    M_ChangeControl, gc_wepslot1     },
-	{IT_CALL | IT_STRING2, NULL, "Weapon Slot 2",    M_ChangeControl, gc_wepslot2     },
-	{IT_CALL | IT_STRING2, NULL, "Weapon Slot 3",    M_ChangeControl, gc_wepslot3     },
-	{IT_CALL | IT_STRING2, NULL, "Weapon Slot 4",    M_ChangeControl, gc_wepslot4     },
-	{IT_CALL | IT_STRING2, NULL, "Weapon Slot 5",    M_ChangeControl, gc_wepslot5     },
-	{IT_CALL | IT_STRING2, NULL, "Weapon Slot 6",    M_ChangeControl, gc_wepslot6     },
-	{IT_CALL | IT_STRING2, NULL, "Weapon Slot 7",    M_ChangeControl, gc_wepslot7     },
-	{IT_CALL | IT_STRING2, NULL, "Ring Toss",        M_ChangeControl, gc_fire         },
-	{IT_CALL | IT_STRING2, NULL, "Ring Toss Normal", M_ChangeControl, gc_firenormal   },
-};
+	{IT_STRING  | IT_CVAR, NULL, "Third-person Camera"  , &cv_chasecam2 , 50},
+	{IT_STRING  | IT_CVAR, NULL, "Flip Camera with Gravity"  , &cv_flipcam2 , 60},
+	{IT_STRING  | IT_CVAR, NULL, "Crosshair", &cv_crosshair2, 70},
 
-static menuitem_t OP_CameraControlsMenu[] =
-{
-	{IT_CALL | IT_STRING2, NULL, "Look Up",          M_ChangeControl, gc_lookup       },
-	{IT_CALL | IT_STRING2, NULL, "Look Down",        M_ChangeControl, gc_lookdown     },
-	{IT_CALL | IT_STRING2, NULL, "Rotate Camera L",  M_ChangeControl, gc_camleft      },
-	{IT_CALL | IT_STRING2, NULL, "Rotate Camera R",  M_ChangeControl, gc_camright     },
-	{IT_CALL | IT_STRING2, NULL, "Center View",      M_ChangeControl, gc_centerview   },
-	{IT_CALL | IT_STRING2, NULL, "Mouselook",        M_ChangeControl, gc_mouseaiming  },
-	{IT_CALL | IT_STRING2, NULL, "Reset Camera",     M_ChangeControl, gc_camreset     },
-	{IT_CALL | IT_STRING2, NULL, "Toggle Chasecam",  M_ChangeControl, gc_camtoggle    },
+	{IT_STRING  | IT_CVAR, NULL, "Analog Control", &cv_useranalog2,  90},
 };
 
-static menuitem_t OP_MiscControlsMenu[] =
-{
-	{IT_CALL | IT_STRING2, NULL, "Custom Action 1",  M_ChangeControl, gc_custom1      },
-	{IT_CALL | IT_STRING2, NULL, "Custom Action 2",  M_ChangeControl, gc_custom2      },
-	{IT_CALL | IT_STRING2, NULL, "Custom Action 3",  M_ChangeControl, gc_custom3      },
-
-	{IT_CALL | IT_STRING2, NULL, "Pause",            M_ChangeControl, gc_pause        },
-	{IT_CALL | IT_STRING2, NULL, "Console",          M_ChangeControl, gc_console      },
+static menuitem_t OP_ChangeControlsMenu[] =
+{
+	{IT_HEADER, NULL, "Movement", NULL, 0},
+	{IT_SPACE, NULL, NULL, NULL, 0}, // padding
+	{IT_CALL | IT_STRING2, NULL, "Move Forward",     M_ChangeControl, gc_forward     },
+	{IT_CALL | IT_STRING2, NULL, "Move Backward",    M_ChangeControl, gc_backward    },
+	{IT_CALL | IT_STRING2, NULL, "Move Left",        M_ChangeControl, gc_strafeleft  },
+	{IT_CALL | IT_STRING2, NULL, "Move Right",       M_ChangeControl, gc_straferight },
+	{IT_CALL | IT_STRING2, NULL, "Jump / Main Action", M_ChangeControl, gc_jump      },
+	{IT_CALL | IT_STRING2, NULL, "Spin / Shield Action", M_ChangeControl, gc_use     },
+	{IT_HEADER, NULL, "Camera", NULL, 0},
+	{IT_SPACE, NULL, NULL, NULL, 0}, // padding
+	{IT_CALL | IT_STRING2, NULL, "Camera Up",        M_ChangeControl, gc_lookup      },
+	{IT_CALL | IT_STRING2, NULL, "Camera Down",      M_ChangeControl, gc_lookdown    },
+	{IT_CALL | IT_STRING2, NULL, "Camera Left",      M_ChangeControl, gc_turnleft    },
+	{IT_CALL | IT_STRING2, NULL, "Camera Right",     M_ChangeControl, gc_turnright   },
+	{IT_CALL | IT_STRING2, NULL, "Center View",      M_ChangeControl, gc_centerview  },
+	{IT_CALL | IT_STRING2, NULL, "Toggle Mouselook", M_ChangeControl, gc_mouseaiming },
+	{IT_CALL | IT_STRING2, NULL, "Toggle Third-Person", M_ChangeControl, gc_camtoggle},
+	{IT_CALL | IT_STRING2, NULL, "Reset Camera",     M_ChangeControl, gc_camreset    },
+	{IT_HEADER, NULL, "Meta", NULL, 0},
+	{IT_SPACE, NULL, NULL, NULL, 0}, // padding
+	{IT_CALL | IT_STRING2, NULL, "Game Status",
+    M_ChangeControl, gc_scores      },
+	{IT_CALL | IT_STRING2, NULL, "Pause",            M_ChangeControl, gc_pause       },
+	{IT_CALL | IT_STRING2, NULL, "Console",          M_ChangeControl, gc_console     },
+	{IT_HEADER, NULL, "Multiplayer", NULL, 0},
+	{IT_SPACE, NULL, NULL, NULL, 0}, // padding
+	{IT_CALL | IT_STRING2, NULL, "Talk",             M_ChangeControl, gc_talkkey     },
+	{IT_CALL | IT_STRING2, NULL, "Talk (Team only)", M_ChangeControl, gc_teamkey     },
+	{IT_HEADER, NULL, "Ringslinger (Match, CTF, Tag, H&S)", NULL, 0},
+	{IT_SPACE, NULL, NULL, NULL, 0}, // padding
+	{IT_CALL | IT_STRING2, NULL, "Fire",             M_ChangeControl, gc_fire        },
+	{IT_CALL | IT_STRING2, NULL, "Fire Normal",      M_ChangeControl, gc_firenormal  },
+	{IT_CALL | IT_STRING2, NULL, "Toss Flag",        M_ChangeControl, gc_tossflag    },
+	{IT_CALL | IT_STRING2, NULL, "Next Weapon",      M_ChangeControl, gc_weaponnext  },
+	{IT_CALL | IT_STRING2, NULL, "Prev Weapon",      M_ChangeControl, gc_weaponprev  },
+	{IT_CALL | IT_STRING2, NULL, "Normal / Infinity",   M_ChangeControl, gc_wepslot1    },
+	{IT_CALL | IT_STRING2, NULL, "Automatic",        M_ChangeControl, gc_wepslot2    },
+	{IT_CALL | IT_STRING2, NULL, "Bounce",           M_ChangeControl, gc_wepslot3    },
+	{IT_CALL | IT_STRING2, NULL, "Scatter",          M_ChangeControl, gc_wepslot4    },
+	{IT_CALL | IT_STRING2, NULL, "Grenade",          M_ChangeControl, gc_wepslot5    },
+	{IT_CALL | IT_STRING2, NULL, "Explosion",        M_ChangeControl, gc_wepslot6    },
+	{IT_CALL | IT_STRING2, NULL, "Rail",             M_ChangeControl, gc_wepslot7    },
+	{IT_HEADER, NULL, "Add-ons", NULL, 0},
+	{IT_SPACE, NULL, NULL, NULL, 0}, // padding
+	{IT_CALL | IT_STRING2, NULL, "Custom Action 1",  M_ChangeControl, gc_custom1     },
+	{IT_CALL | IT_STRING2, NULL, "Custom Action 2",  M_ChangeControl, gc_custom2     },
+	{IT_CALL | IT_STRING2, NULL, "Custom Action 3",  M_ChangeControl, gc_custom3     },
 };
 
 static menuitem_t OP_Joystick1Menu[] =
 {
 	{IT_STRING | IT_CALL,  NULL, "Select Joystick...", M_Setup1PJoystickMenu,  10},
-	{IT_STRING | IT_CVAR,  NULL, "Axis For Turning"  , &cv_turnaxis         ,  30},
-	{IT_STRING | IT_CVAR,  NULL, "Axis For Moving"   , &cv_moveaxis         ,  40},
-	{IT_STRING | IT_CVAR,  NULL, "Axis For Strafe"   , &cv_sideaxis         ,  50},
-	{IT_STRING | IT_CVAR,  NULL, "Axis For Looking"  , &cv_lookaxis         ,  60},
-	{IT_STRING | IT_CVAR,  NULL, "Axis For Firing"   , &cv_fireaxis         ,  70},
-	{IT_STRING | IT_CVAR,  NULL, "Axis For NFiring"  , &cv_firenaxis        ,  80},
+	{IT_STRING | IT_CVAR,  NULL, "Move \x17 Axis"    , &cv_moveaxis         , 30},
+	{IT_STRING | IT_CVAR,  NULL, "Move \x18 Axis"    , &cv_sideaxis         , 40},
+	{IT_STRING | IT_CVAR,  NULL, "Camera \x17 Axis"  , &cv_lookaxis         , 50},
+	{IT_STRING | IT_CVAR,  NULL, "Camera \x18 Axis"  , &cv_turnaxis         , 60},
+	{IT_STRING | IT_CVAR,  NULL, "Fire Axis"         , &cv_fireaxis         , 70},
+	{IT_STRING | IT_CVAR,  NULL, "Fire Normal Axis"  , &cv_firenaxis        , 80},
 };
 
 static menuitem_t OP_Joystick2Menu[] =
 {
 	{IT_STRING | IT_CALL,  NULL, "Select Joystick...", M_Setup2PJoystickMenu, 10},
-	{IT_STRING | IT_CVAR,  NULL, "Axis For Turning"  , &cv_turnaxis2        , 30},
-	{IT_STRING | IT_CVAR,  NULL, "Axis For Moving"   , &cv_moveaxis2        , 40},
-	{IT_STRING | IT_CVAR,  NULL, "Axis For Strafe"   , &cv_sideaxis2        , 50},
-	{IT_STRING | IT_CVAR,  NULL, "Axis For Looking"  , &cv_lookaxis2        , 60},
-	{IT_STRING | IT_CVAR,  NULL, "Axis For Firing"   , &cv_fireaxis2        , 70},
-	{IT_STRING | IT_CVAR,  NULL, "Axis For NFiring"  , &cv_firenaxis2       , 80},
+	{IT_STRING | IT_CVAR,  NULL, "Move \x17 Axis"    , &cv_moveaxis2        , 30},
+	{IT_STRING | IT_CVAR,  NULL, "Move \x18 Axis"    , &cv_sideaxis2        , 40},
+	{IT_STRING | IT_CVAR,  NULL, "Camera \x17 Axis"  , &cv_lookaxis2        , 50},
+	{IT_STRING | IT_CVAR,  NULL, "Camera \x18 Axis"  , &cv_turnaxis2        , 60},
+	{IT_STRING | IT_CVAR,  NULL, "Fire Axis"         , &cv_fireaxis2        , 70},
+	{IT_STRING | IT_CVAR,  NULL, "Fire Normal Axis"  , &cv_firenaxis2       , 80},
 };
 
 static menuitem_t OP_JoystickSetMenu[] =
@@ -1148,8 +1157,6 @@ static menuitem_t OP_JoystickSetMenu[] =
 	{IT_CALL | IT_NOTHING, "", NULL, M_AssignJoystick, '2'},
 	{IT_CALL | IT_NOTHING, "", NULL, M_AssignJoystick, '3'},
 	{IT_CALL | IT_NOTHING, "", NULL, M_AssignJoystick, '4'},
-	{IT_CALL | IT_NOTHING, "", NULL, M_AssignJoystick, '5'},
-	{IT_CALL | IT_NOTHING, "", NULL, M_AssignJoystick, '6'},
 };
 
 static menuitem_t OP_MouseOptionsMenu[] =
@@ -1157,13 +1164,13 @@ static menuitem_t OP_MouseOptionsMenu[] =
 	{IT_STRING | IT_CVAR, NULL, "Use Mouse",        &cv_usemouse,         10},
 
 
-	{IT_STRING | IT_CVAR, NULL, "Always MouseLook", &cv_alwaysfreelook,   30},
+	{IT_STRING | IT_CVAR, NULL, "Always Mouselook", &cv_alwaysfreelook,   30},
 	{IT_STRING | IT_CVAR, NULL, "Mouse Move",       &cv_mousemove,        40},
-	{IT_STRING | IT_CVAR, NULL, "Invert Mouse",     &cv_invertmouse,      50},
+	{IT_STRING | IT_CVAR, NULL, "Invert Y Axis",     &cv_invertmouse,      50},
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
-	                      NULL, "Mouse X Speed",    &cv_mousesens,        60},
+	                      NULL, "Mouse X Sensitivity",    &cv_mousesens,        60},
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
-	                      NULL, "Mouse Y Speed",    &cv_mouseysens,        70},
+	                      NULL, "Mouse Y Sensitivity",    &cv_mouseysens,        70},
 };
 
 static menuitem_t OP_Mouse2OptionsMenu[] =
@@ -1171,37 +1178,56 @@ static menuitem_t OP_Mouse2OptionsMenu[] =
 	{IT_STRING | IT_CVAR, NULL, "Use Mouse 2",      &cv_usemouse2,        10},
 	{IT_STRING | IT_CVAR, NULL, "Second Mouse Serial Port",
 	                                                &cv_mouse2port,       20},
-	{IT_STRING | IT_CVAR, NULL, "Always MouseLook", &cv_alwaysfreelook2,  30},
+	{IT_STRING | IT_CVAR, NULL, "Always Mouselook", &cv_alwaysfreelook2,  30},
 	{IT_STRING | IT_CVAR, NULL, "Mouse Move",       &cv_mousemove2,       40},
-	{IT_STRING | IT_CVAR, NULL, "Invert Mouse",     &cv_invertmouse2,     50},
+	{IT_STRING | IT_CVAR, NULL, "Invert Y Axis",     &cv_invertmouse2,     50},
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
-	                      NULL, "Mouse X Speed",    &cv_mousesens2,       60},
+	                      NULL, "Mouse X Sensitivity",    &cv_mousesens2,       60},
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
-	                      NULL, "Mouse Y Speed",    &cv_mouseysens2,      70},
+	                      NULL, "Mouse Y Sensitivity",    &cv_mouseysens2,      70},
 };
 
 static menuitem_t OP_VideoOptionsMenu[] =
 {
-	{IT_STRING | IT_CALL,  NULL,   "Video Modes...",      M_VideoModeMenu,     10},
+	{IT_HEADER, NULL, "Screen", NULL, 0},
+	{IT_STRING | IT_CALL,  NULL, "Set Resolution...",       M_VideoModeMenu,          6},
 
-#ifdef HWRENDER
-	{IT_SUBMENU|IT_STRING, NULL,   "3D Card Options...",  &OP_OpenGLOptionsDef,    20},
+#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)
+	{IT_STRING|IT_CVAR,      NULL, "Fullscreen",             &cv_fullscreen,         11},
 #endif
+	{IT_STRING | IT_CVAR, NULL, "Vertical Sync",                &cv_vidwait,         16},
 
-#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)
-	{IT_STRING|IT_CVAR,      NULL, "Fullscreen",          &cv_fullscreen,    30},
+#ifdef HWRENDER
+	{IT_SUBMENU|IT_STRING, NULL, "OpenGL Options...", &OP_OpenGLOptionsDef,          21},
 #endif
 
+	{IT_HEADER, NULL, "Color Profile", NULL, 30},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Brightness (F11)", &cv_globalgamma,      36},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Saturation", &cv_globalsaturation, 41},
+	{IT_SUBMENU|IT_STRING, NULL, "Advanced Settings...",     &OP_ColorOptionsDef,  46},
+
+	{IT_HEADER, NULL, "Heads-Up Display", NULL, 55},
+	{IT_STRING | IT_CVAR, NULL, "Show HUD",                  &cv_showhud,          61},
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
-	                         NULL, "Brightness",          &cv_usegamma,      50},
-	{IT_STRING | IT_CVAR,    NULL, "Draw Distance",       &cv_drawdist, 60},
-	{IT_STRING | IT_CVAR,    NULL, "NiGHTS Draw Dist",    &cv_drawdist_nights, 70},
-	{IT_STRING | IT_CVAR,    NULL, "Precip Draw Dist",    &cv_drawdist_precip, 80},
-	{IT_STRING | IT_CVAR,    NULL, "Precip Density",      &cv_precipdensity, 90},
-
-	{IT_STRING | IT_CVAR,    NULL, "Show FPS",            &cv_ticrate,    110},
-	{IT_STRING | IT_CVAR,    NULL, "Clear Before Redraw", &cv_homremoval, 120},
-	{IT_STRING | IT_CVAR,    NULL, "Vertical Sync",       &cv_vidwait,    130},
+	                      NULL, "HUD Transparency",          &cv_translucenthud,   66},
+	{IT_STRING | IT_CVAR, NULL, "Time Display",              &cv_timetic,          71},
+#ifdef SEENAMES
+	{IT_STRING | IT_CVAR, NULL, "Show player names",         &cv_seenames,         76},
+#endif
+
+	{IT_HEADER, NULL, "Console", NULL, 85},
+	{IT_STRING | IT_CVAR, NULL, "Background color",          &cons_backcolor,      91},
+	{IT_STRING | IT_CVAR, NULL, "Text Size",                 &cv_constextsize,     96},
+
+	{IT_HEADER, NULL, "Level", NULL, 105},
+	{IT_STRING | IT_CVAR, NULL, "Draw Distance",             &cv_drawdist,        111},
+	{IT_STRING | IT_CVAR, NULL, "NiGHTS Draw Dist.",         &cv_drawdist_nights, 116},
+	{IT_STRING | IT_CVAR, NULL, "Weather Draw Dist.",        &cv_drawdist_precip, 121},
+	{IT_STRING | IT_CVAR, NULL, "Weather Density",           &cv_precipdensity,   126},
+
+	{IT_HEADER, NULL, "Diagnostic", NULL, 135},
+	{IT_STRING | IT_CVAR, NULL, "Show FPS",                  &cv_ticrate,         141},
+	{IT_STRING | IT_CVAR, NULL, "Clear Before Redraw",       &cv_homremoval,      146},
 };
 
 static menuitem_t OP_VideoModeMenu[] =
@@ -1209,6 +1235,47 @@ static menuitem_t OP_VideoModeMenu[] =
 	{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandleVideoMode, 0},     // dummy menuitem for the control func
 };
 
+static menuitem_t OP_ColorOptionsMenu[] =
+{
+	{IT_STRING | IT_CALL, NULL, "Reset to defaults", M_ResetCvars, 0},
+
+	{IT_HEADER, NULL, "Red", NULL, 9},
+	{IT_DISABLED, NULL, NULL, NULL, 35},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Hue",          &cv_rhue,         15},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Saturation",   &cv_rsaturation,  20},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Brightness",   &cv_rgamma,       25},
+
+	{IT_HEADER, NULL, "Yellow", NULL, 34},
+	{IT_DISABLED, NULL, NULL, NULL, 73},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Hue",          &cv_yhue,         40},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Saturation",   &cv_ysaturation,  45},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Brightness",   &cv_ygamma,       50},
+
+	{IT_HEADER, NULL, "Green", NULL, 59},
+	{IT_DISABLED, NULL, NULL, NULL, 112},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Hue",          &cv_ghue,         65},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Saturation",   &cv_gsaturation,  70},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Brightness",   &cv_ggamma,       75},
+
+	{IT_HEADER, NULL, "Cyan", NULL, 84},
+	{IT_DISABLED, NULL, NULL, NULL, 255},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Hue",          &cv_chue,         90},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Saturation",   &cv_csaturation,  95},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Brightness",   &cv_cgamma,      100},
+
+	{IT_HEADER, NULL, "Blue", NULL, 109},
+	{IT_DISABLED, NULL, NULL, NULL, 152},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Hue",          &cv_bhue,        115},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Saturation",   &cv_bsaturation, 120},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Brightness",   &cv_bgamma,      125},
+
+	{IT_HEADER, NULL, "Magenta", NULL, 134},
+	{IT_DISABLED, NULL, NULL, NULL, 181},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Hue",          &cv_mhue,        140},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Saturation",   &cv_msaturation, 145},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Brightness",   &cv_mgamma,      150},
+};
+
 #ifdef HWRENDER
 static menuitem_t OP_OpenGLOptionsMenu[] =
 {
@@ -1216,7 +1283,7 @@ static menuitem_t OP_OpenGLOptionsMenu[] =
 	{IT_STRING|IT_CVAR,         NULL, "Quality",         &cv_scr_depth,        20},
 	{IT_STRING|IT_CVAR,         NULL, "Texture Filter",  &cv_grfiltermode,     30},
 	{IT_STRING|IT_CVAR,         NULL, "Anisotropic",     &cv_granisotropicmode,40},
-#ifdef _WINDOWS
+#if defined (_WINDOWS) && (!((defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)))
 	{IT_STRING|IT_CVAR,         NULL, "Fullscreen",      &cv_fullscreen,       50},
 #endif
 #ifdef ALAM_LIGHTING
@@ -1254,60 +1321,60 @@ static menuitem_t OP_OpenGLColorMenu[] =
 
 static menuitem_t OP_SoundOptionsMenu[] =
 {
-	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
-                              NULL, "Sound Volume" , &cv_soundvolume,     10},
-	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
-                              NULL, "Music Volume" , &cv_digmusicvolume,  20},
-	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
-                              NULL, "MIDI Volume"  , &cv_midimusicvolume, 30},
-#ifdef PC_DOS
-	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
-                              NULL, "CD Volume"    , &cd_volume,          40},
-#endif
+	{IT_STRING | IT_KEYHANDLER,  NULL,  "Sound Effects", M_ToggleSFX, 10},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Sound Volume", &cv_soundvolume, 20},
+
+	{IT_STRING | IT_KEYHANDLER,  NULL,  "Digital Music", M_ToggleDigital, 40},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Digital Music Volume", &cv_digmusicvolume,  50},
 
-	{IT_STRING    | IT_CALL,  NULL,  "Toggle SFX"   , M_ToggleSFX,        50},
-	{IT_STRING    | IT_CALL,  NULL,  "Toggle Digital Music", M_ToggleDigital,     60},
-	{IT_STRING    | IT_CALL,  NULL,  "Toggle MIDI Music", M_ToggleMIDI,        70},
+	{IT_STRING | IT_KEYHANDLER,  NULL,  "MIDI Music", M_ToggleMIDI, 70},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "MIDI Music Volume", &cv_midimusicvolume, 80},
+
+	{IT_STRING | IT_CVAR, NULL, "Closed Captioning", &cv_closedcaptioning, 100},
 };
 
 static menuitem_t OP_DataOptionsMenu[] =
 {
-	{IT_STRING | IT_CALL, NULL, "Screenshot Options...", M_ScreenshotOptions, 10},
+	{IT_STRING | IT_CALL,    NULL, "Add-on Options...",     M_AddonsOptions,     10},
+	{IT_STRING | IT_CALL,    NULL, "Screenshot Options...", M_ScreenshotOptions, 20},
 
-	{IT_STRING | IT_SUBMENU, NULL, "Erase Data...", &OP_EraseDataDef, 30},
+	{IT_STRING | IT_SUBMENU, NULL, "\x85" "Erase Data...",  &OP_EraseDataDef,    40},
 };
 
 static menuitem_t OP_ScreenshotOptionsMenu[] =
 {
-	{IT_STRING|IT_CVAR, NULL, "Storage Location", &cv_screenshot_option, 10},
-	{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Custom Folder", &cv_screenshot_folder, 20},
+	{IT_HEADER, NULL, "General", NULL, 0},
+	{IT_STRING|IT_CVAR, NULL, "Use color profile", &cv_screenshot_colorprofile,     6},
+	{IT_STRING|IT_CVAR, NULL, "Storage Location",  &cv_screenshot_option,          11},
+	{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Custom Folder", &cv_screenshot_folder, 16},
 
-	{IT_HEADER, NULL, "Screenshots (F8)", NULL, 50},
-	{IT_STRING|IT_CVAR, NULL, "Memory Level",      &cv_zlib_memory,      60},
-	{IT_STRING|IT_CVAR, NULL, "Compression Level", &cv_zlib_level,       70},
-	{IT_STRING|IT_CVAR, NULL, "Strategy",          &cv_zlib_strategy,    80},
-	{IT_STRING|IT_CVAR, NULL, "Window Size",       &cv_zlib_window_bits, 90},
+	{IT_HEADER, NULL, "Screenshots (F8)", NULL, 30},
+	{IT_STRING|IT_CVAR, NULL, "Memory Level",      &cv_zlib_memory,                36},
+	{IT_STRING|IT_CVAR, NULL, "Compression Level", &cv_zlib_level,                 41},
+	{IT_STRING|IT_CVAR, NULL, "Strategy",          &cv_zlib_strategy,              46},
+	{IT_STRING|IT_CVAR, NULL, "Window Size",       &cv_zlib_window_bits,           51},
 
-	{IT_HEADER, NULL, "Movie Mode (F9)", NULL, 105},
-	{IT_STRING|IT_CVAR, NULL, "Capture Mode", &cv_moviemode, 115},
+	{IT_HEADER, NULL, "Movie Mode (F9)", NULL, 60},
+	{IT_STRING|IT_CVAR, NULL, "Capture Mode",      &cv_moviemode,                  66},
 
-	{IT_STRING|IT_CVAR, NULL, "Region Optimizing", &cv_gif_optimize,  125},
-	{IT_STRING|IT_CVAR, NULL, "Downscaling",       &cv_gif_downscale, 135},
+	{IT_STRING|IT_CVAR, NULL, "Region Optimizing", &cv_gif_optimize,               71},
+	{IT_STRING|IT_CVAR, NULL, "Downscaling",       &cv_gif_downscale,              76},
 
-	{IT_STRING|IT_CVAR, NULL, "Memory Level",      &cv_zlib_memorya,      125},
-	{IT_STRING|IT_CVAR, NULL, "Compression Level", &cv_zlib_levela,       135},
-	{IT_STRING|IT_CVAR, NULL, "Strategy",          &cv_zlib_strategya,    145},
-	{IT_STRING|IT_CVAR, NULL, "Window Size",       &cv_zlib_window_bitsa, 155},
+	{IT_STRING|IT_CVAR, NULL, "Memory Level",      &cv_zlib_memorya,               71},
+	{IT_STRING|IT_CVAR, NULL, "Compression Level", &cv_zlib_levela,                76},
+	{IT_STRING|IT_CVAR, NULL, "Strategy",          &cv_zlib_strategya,             81},
+	{IT_STRING|IT_CVAR, NULL, "Window Size",       &cv_zlib_window_bitsa,          86},
 };
 
 enum
 {
-	op_screenshot_folder = 1,
-	op_screenshot_capture = 8,
-	op_screenshot_gif_start = 9,
-	op_screenshot_gif_end = 10,
-	op_screenshot_apng_start = 11,
-	op_screenshot_apng_end = 14,
+	op_screenshot_colorprofile = 1,
+	op_screenshot_folder = 3,
+	op_screenshot_capture = 10,
+	op_screenshot_gif_start = 11,
+	op_screenshot_gif_end = 12,
+	op_screenshot_apng_start = 13,
+	op_screenshot_apng_end = 16,
 };
 
 static menuitem_t OP_EraseDataMenu[] =
@@ -1318,111 +1385,94 @@ static menuitem_t OP_EraseDataMenu[] =
 	{IT_STRING | IT_CALL, NULL, "\x85" "Erase ALL Data", M_EraseData, 40},
 };
 
-static menuitem_t OP_GameOptionsMenu[] =
+static menuitem_t OP_AddonsOptionsMenu[] =
 {
-#ifndef NONET
-	{IT_STRING | IT_CVAR | IT_CV_STRING,
-	                      NULL, "Master server",          &cv_masterserver,     10},
-#endif
-	{IT_STRING | IT_CVAR, NULL, "Show HUD",               &cv_showhud,     40},
-	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
-	                      NULL, "HUD Visibility",         &cv_translucenthud, 50},
-	{IT_STRING | IT_CVAR, NULL, "Timer Display",          &cv_timetic,     60},
-#ifdef SEENAMES
-	{IT_STRING | IT_CVAR, NULL, "HUD Player Names",       &cv_seenames,    80},
-#endif
-	{IT_STRING | IT_CVAR, NULL, "Log Hazard Damage",      &cv_hazardlog,   90},
+	{IT_HEADER,                      NULL, "Menu",                        NULL,                     0},
+	{IT_STRING|IT_CVAR,              NULL, "Location",                    &cv_addons_option,       12},
+	{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Custom Folder",               &cv_addons_folder,       22},
+	{IT_STRING|IT_CVAR,              NULL, "Identify add-ons via",        &cv_addons_md5,          50},
+	{IT_STRING|IT_CVAR,              NULL, "Show unsupported file types", &cv_addons_showall,      60},
 
-	{IT_STRING | IT_CVAR, NULL, "Console Back Color",     &cons_backcolor, 100},
-	{IT_STRING | IT_CVAR, NULL, "Console Text Size",      &cv_constextsize,110},
-	{IT_STRING | IT_CVAR, NULL, "Uppercase Console",      &cv_allcaps,     120},
+	{IT_HEADER,                      NULL, "Search",                      NULL,                    78},
+	{IT_STRING|IT_CVAR,              NULL, "Matching",                    &cv_addons_search_type,  90},
+	{IT_STRING|IT_CVAR,              NULL, "Case-sensitive",              &cv_addons_search_case, 100},
+};
 
-	{IT_STRING | IT_CVAR, NULL, "Title Screen Demos",     &cv_rollingdemos, 140},
+enum
+{
+	op_addons_folder = 2,
 };
 
 static menuitem_t OP_ServerOptionsMenu[] =
 {
-	{IT_STRING | IT_SUBMENU, NULL, "General netgame options...",  &OP_NetgameOptionsDef,  10},
-	{IT_STRING | IT_SUBMENU, NULL, "Gametype options...",         &OP_GametypeOptionsDef, 20},
-	{IT_STRING | IT_SUBMENU, NULL, "Random Monitor Toggles...",   &OP_MonitorToggleDef,   30},
-
+	{IT_HEADER, NULL, "General", NULL, 0},
 #ifndef NONET
 	{IT_STRING | IT_CVAR | IT_CV_STRING,
-	                         NULL, "Server name",                 &cv_servername,         50},
-#endif
-
-	{IT_STRING | IT_CVAR,    NULL, "Intermission Timer",          &cv_inttime,            80},
-	{IT_STRING | IT_CVAR,    NULL, "Advance to next map",         &cv_advancemap,         90},
-
-#ifndef NONET
-	{IT_STRING | IT_CVAR,    NULL, "Max Players",                 &cv_maxplayers,        110},
-	{IT_STRING | IT_CVAR,    NULL, "Allow players to join",       &cv_allownewplayer,    120},
-	{IT_STRING | IT_CVAR,    NULL, "Allow WAD Downloading",       &cv_downloading,       130},
-	{IT_STRING | IT_CVAR,    NULL, "Attempts to Resynch",         &cv_resynchattempts,   140},
+	                         NULL, "Server name",                      &cv_servername,          7},
+	{IT_STRING | IT_CVAR,    NULL, "Max Players",                      &cv_maxplayers,          21},
+	{IT_STRING | IT_CVAR,    NULL, "Allow Add-on Downloading",         &cv_downloading,         26},
+	{IT_STRING | IT_CVAR,    NULL, "Allow players to join",            &cv_allownewplayer,      31},
 #endif
-};
+	{IT_STRING | IT_CVAR,    NULL, "Map progression",                  &cv_advancemap,          36},
+	{IT_STRING | IT_CVAR,    NULL, "Intermission Timer",               &cv_inttime,             41},
 
-static menuitem_t OP_NetgameOptionsMenu[] =
-{
-	{IT_STRING | IT_CVAR, NULL, "Time Limit",            &cv_timelimit,        10},
-	{IT_STRING | IT_CVAR, NULL, "Point Limit",           &cv_pointlimit,       18},
-	{IT_STRING | IT_CVAR, NULL, "Overtime Tie-Breaker",  &cv_overtime,         26},
-
-	{IT_STRING | IT_CVAR, NULL, "Special Ring Weapons",  &cv_specialrings,     42},
-	{IT_STRING | IT_CVAR, NULL, "Emeralds",              &cv_powerstones,      50},
-	{IT_STRING | IT_CVAR, NULL, "Item Boxes",            &cv_matchboxes,       58},
-	{IT_STRING | IT_CVAR, NULL, "Item Respawn",          &cv_itemrespawn,      66},
-	{IT_STRING | IT_CVAR, NULL, "Item Respawn time",     &cv_itemrespawntime,  74},
+	{IT_HEADER, NULL, "Characters", NULL, 50},
+	{IT_STRING | IT_CVAR,    NULL, "Force a character",                &cv_forceskin,           56},
+	{IT_STRING | IT_CVAR,    NULL, "Restrict character changes",       &cv_restrictskinchange,  61},
 
-	{IT_STRING | IT_CVAR, NULL, "Sudden Death",          &cv_suddendeath,      90},
-	{IT_STRING | IT_CVAR, NULL, "Player respawn delay",  &cv_respawntime,      98},
+	{IT_HEADER, NULL, "Items", NULL, 70},
+	{IT_STRING | IT_CVAR,    NULL, "Item respawn delay",               &cv_itemrespawntime,     76},
+	{IT_STRING | IT_SUBMENU, NULL, "Mystery Item Monitor Toggles...",  &OP_MonitorToggleDef,    81},
 
-	{IT_STRING | IT_CVAR, NULL, "Force Skin",            &cv_forceskin,          114},
-	{IT_STRING | IT_CVAR, NULL, "Restrict skin changes", &cv_restrictskinchange, 122},
-
-	{IT_STRING | IT_CVAR, NULL, "Autobalance Teams",            &cv_autobalance,      138},
-	{IT_STRING | IT_CVAR, NULL, "Scramble Teams on Map Change", &cv_scrambleonchange, 146},
-};
+	{IT_HEADER, NULL, "Cooperative", NULL, 90},
+	{IT_STRING | IT_CVAR,    NULL, "Players required for exit",        &cv_playersforexit,      96},
+	{IT_STRING | IT_CVAR,    NULL, "Starposts",                        &cv_coopstarposts,      101},
+	{IT_STRING | IT_CVAR,    NULL, "Life sharing",                     &cv_cooplives,          106},
 
-static menuitem_t OP_GametypeOptionsMenu[] =
-{
-	{IT_HEADER,           NULL, "CO-OP",                 NULL,                  2},
-	{IT_STRING | IT_CVAR, NULL, "Players for exit",      &cv_playersforexit,   10},
-	{IT_STRING | IT_CVAR, NULL, "Starting Lives",        &cv_startinglives,    18},
+	{IT_HEADER, NULL, "Race, Competition", NULL, 115},
+	{IT_STRING | IT_CVAR,    NULL, "Level completion countdown",       &cv_countdowntime,      121},
+	{IT_STRING | IT_CVAR,    NULL, "Item Monitors",                    &cv_competitionboxes,   126},
 
-	{IT_HEADER,           NULL, "COMPETITION",           NULL,                 34},
-	{IT_STRING | IT_CVAR, NULL, "Item Boxes",            &cv_competitionboxes, 42},
-	{IT_STRING | IT_CVAR, NULL, "Countdown Time",        &cv_countdowntime,    50},
+	{IT_HEADER, NULL, "Ringslinger (Match, CTF, Tag, H&S)", NULL, 135},
+	{IT_STRING | IT_CVAR,    NULL, "Time Limit",                       &cv_timelimit,          141},
+	{IT_STRING | IT_CVAR,    NULL, "Score Limit",                      &cv_pointlimit,         146},
+	{IT_STRING | IT_CVAR,    NULL, "Overtime on Tie",                  &cv_overtime,           151},
+	{IT_STRING | IT_CVAR,    NULL, "Player respawn delay",             &cv_respawntime,        156},
 
-	{IT_HEADER,           NULL, "RACE",                  NULL,                 66},
-	{IT_STRING | IT_CVAR, NULL, "Number of Laps",        &cv_numlaps,          74},
-	{IT_STRING | IT_CVAR, NULL, "Use Map Lap Counts",    &cv_usemapnumlaps,    82},
+	{IT_STRING | IT_CVAR,    NULL, "Item Monitors",                    &cv_matchboxes,         166},
+	{IT_STRING | IT_CVAR,    NULL, "Weapon Rings",                     &cv_specialrings,       171},
+	{IT_STRING | IT_CVAR,    NULL, "Power Stones",                     &cv_powerstones,        176},
 
-	{IT_HEADER,           NULL, "MATCH",                 NULL,                 98},
-	{IT_STRING | IT_CVAR, NULL, "Scoring Type",          &cv_match_scoring,   106},
+	{IT_STRING | IT_CVAR,    NULL, "Flag respawn delay",               &cv_flagtime,           186},
+	{IT_STRING | IT_CVAR,    NULL, "Hiding time",                      &cv_hidetime,           191},
 
-	{IT_HEADER,           NULL, "TAG",                   NULL,                122},
-	{IT_STRING | IT_CVAR, NULL, "Hide Time",             &cv_hidetime,        130},
+	{IT_HEADER, NULL, "Teams", NULL, 200},
+	{IT_STRING | IT_CVAR,    NULL, "Autobalance sizes",                &cv_autobalance,        206},
+	{IT_STRING | IT_CVAR,    NULL, "Scramble on Map Change",           &cv_scrambleonchange,   211},
 
-	{IT_HEADER,           NULL, "CTF",                   NULL,                146},
-	{IT_STRING | IT_CVAR, NULL, "Flag Respawn Time",     &cv_flagtime,        154},
+#ifndef NONET
+	{IT_HEADER, NULL, "Advanced", NULL, 220},
+	{IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "Master server",        &cv_masterserver,        226},
+	{IT_STRING | IT_CVAR,    NULL, "Attempts to resynchronise",        &cv_resynchattempts,     240},
+#endif
 };
 
 static menuitem_t OP_MonitorToggleMenu[] =
 {
 	// Printing handled by drawing function
-	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Recycler",          &cv_recycler,      20},
-	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Teleporters",       &cv_teleporters,   30},
-	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Super Ring",        &cv_superring,     40},
-	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Super Sneakers",    &cv_supersneakers, 50},
-	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Invincibility",     &cv_invincibility, 60},
-	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Jump Shield",       &cv_jumpshield,    70},
-	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Elemental Shield",  &cv_watershield,   80},
-	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Attraction Shield", &cv_ringshield,    90},
-	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Force Shield",      &cv_forceshield,  100},
-	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Armageddon Shield", &cv_bombshield,   110},
-	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "1 Up",              &cv_1up,          120},
-	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Eggman Box",        &cv_eggmanbox,    130},
+	{IT_STRING|IT_CALL, NULL, "Reset to defaults", M_ResetCvars, 15},
+	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Recycler",          &cv_recycler,      30},
+	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Teleport",          &cv_teleporters,   40},
+	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Super Ring",        &cv_superring,     50},
+	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Super Sneakers",    &cv_supersneakers, 60},
+	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Invincibility",     &cv_invincibility, 70},
+	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Whirlwind Shield",  &cv_jumpshield,    80},
+	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Elemental Shield",  &cv_watershield,   90},
+	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Attraction Shield", &cv_ringshield,   100},
+	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Force Shield",      &cv_forceshield,  110},
+	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Armageddon Shield", &cv_bombshield,   120},
+	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "1 Up",              &cv_1up,          130},
+	{IT_STRING|IT_CVAR|IT_CV_INVISSLIDER, NULL, "Eggman Box",        &cv_eggmanbox,    140},
 };
 
 // ==========================================================================
@@ -1432,6 +1482,18 @@ static menuitem_t OP_MonitorToggleMenu[] =
 // Main Menu and related
 menu_t MainDef = CENTERMENUSTYLE(NULL, MainMenu, NULL, 72);
 
+menu_t MISC_AddonsDef =
+{
+	NULL,
+	sizeof (MISC_AddonsMenu)/sizeof (menuitem_t),
+	&MainDef,
+	MISC_AddonsMenu,
+	M_DrawAddons,
+	50, 28,
+	0,
+	NULL
+};
+
 menu_t MAPauseDef = PAUSEMENUSTYLE(MAPauseMenu, 40, 72);
 menu_t SPauseDef = PAUSEMENUSTYLE(SPauseMenu, 40, 72);
 menu_t MPauseDef = PAUSEMENUSTYLE(MPauseMenu, 40, 72);
@@ -1494,12 +1556,12 @@ menu_t SR_LevelSelectDef = MAPPLATTERMENUSTYLE(NULL, SR_LevelSelectMenu);
 
 menu_t SR_UnlockChecklistDef =
 {
-	NULL,
+	"M_SECRET",
 	1,
 	&SR_MainDef,
 	SR_UnlockChecklistMenu,
 	M_DrawChecklist,
-	280, 185,
+	30, 30,
 	0,
 	NULL
 };
@@ -1531,17 +1593,6 @@ menu_t SP_LoadDef =
 
 menu_t SP_LevelSelectDef = MAPPLATTERMENUSTYLE(NULL, SP_LevelSelectMenu);
 
-menu_t SP_GameStatsDef =
-{
-	"M_STATS",
-	1,
-	&SP_MainDef,
-	SP_GameStatsMenu,
-	M_DrawGameStats,
-	280, 185,
-	0,
-	NULL
-};
 menu_t SP_LevelStatsDef =
 {
 	"M_STATS",
@@ -1662,37 +1713,49 @@ menu_t SP_PlayerDef =
 };
 
 // Multiplayer
-menu_t MP_MainDef = DEFAULTMENUSTYLE("M_MULTI", MP_MainMenu, &MainDef, 60, 40);
 
-menu_t MP_ServerDef =
+menu_t MP_SplitServerDef =
 {
 	"M_MULTI",
-	sizeof (MP_ServerMenu)/sizeof (menuitem_t),
+	sizeof (MP_SplitServerMenu)/sizeof (menuitem_t),
+#ifndef NONET
 	&MP_MainDef,
-	MP_ServerMenu,
-	M_DrawServerMenu,
-	27, 30
-#ifdef NONET
-	- 50
+#else
+	&MainDef,
 #endif
-	,
+	MP_SplitServerMenu,
+	M_DrawServerMenu,
+	27, 30 - 50,
 	0,
 	NULL
 };
 
-menu_t MP_SplitServerDef =
+#ifndef NONET
+
+menu_t MP_MainDef =
 {
 	"M_MULTI",
-	sizeof (MP_SplitServerMenu)/sizeof (menuitem_t),
+	sizeof (MP_MainMenu)/sizeof (menuitem_t),
+	&MainDef,
+	MP_MainMenu,
+	M_DrawMPMainMenu,
+	27, 40,
+	0,
+	M_CancelConnect
+};
+
+menu_t MP_ServerDef =
+{
+	"M_MULTI",
+	sizeof (MP_ServerMenu)/sizeof (menuitem_t),
 	&MP_MainDef,
-	MP_SplitServerMenu,
+	MP_ServerMenu,
 	M_DrawServerMenu,
-	27, 30 - 50,
+	27, 30,
 	0,
 	NULL
 };
 
-#ifndef NONET
 menu_t MP_ConnectDef =
 {
 	"M_MULTI",
@@ -1704,17 +1767,7 @@ menu_t MP_ConnectDef =
 	0,
 	M_CancelConnect
 };
-menu_t MP_ConnectIPDef =
-{
-	"M_MULTI",
-	sizeof (MP_ConnectIPMenu)/sizeof (menuitem_t),
-	&MP_MainDef,
-	MP_ConnectIPMenu,
-	M_DrawConnectIPMenu,
-	27,40,
-	0,
-	M_CancelConnect
-};
+
 menu_t MP_RoomDef =
 {
 	"M_MULTI",
@@ -1732,28 +1785,23 @@ menu_t MP_PlayerSetupDef =
 {
 	"M_SPLAYR",
 	sizeof (MP_PlayerSetupMenu)/sizeof (menuitem_t),
-	&MP_MainDef,
+	&MainDef, // doesn't matter
 	MP_PlayerSetupMenu,
 	M_DrawSetupMultiPlayerMenu,
-	27, 40,
+	19, 22,
 	0,
 	M_QuitMultiPlayerMenu
 };
 
 // Options
-menu_t OP_MainDef = DEFAULTMENUSTYLE("M_OPTTTL", OP_MainMenu, &MainDef, 60, 30);
-menu_t OP_ControlsDef = DEFAULTMENUSTYLE("M_CONTRO", OP_ControlsMenu, &OP_MainDef, 60, 30);
-menu_t OP_ControlListDef = DEFAULTMENUSTYLE("M_CONTRO", OP_ControlListMenu, &OP_ControlsDef, 60, 30);
-menu_t OP_MoveControlsDef = CONTROLMENUSTYLE(OP_MoveControlsMenu, &OP_ControlListDef);
-menu_t OP_MPControlsDef = CONTROLMENUSTYLE(OP_MPControlsMenu, &OP_ControlListDef);
-menu_t OP_CameraControlsDef = CONTROLMENUSTYLE(OP_CameraControlsMenu, &OP_ControlListDef);
-menu_t OP_MiscControlsDef = CONTROLMENUSTYLE(OP_MiscControlsMenu, &OP_ControlListDef);
-menu_t OP_P1ControlsDef = DEFAULTMENUSTYLE("M_CONTRO", OP_P1ControlsMenu, &OP_ControlsDef, 60, 30);
-menu_t OP_P2ControlsDef = DEFAULTMENUSTYLE("M_CONTRO", OP_P2ControlsMenu, &OP_ControlsDef, 60, 30);
-menu_t OP_MouseOptionsDef = DEFAULTMENUSTYLE("M_CONTRO", OP_MouseOptionsMenu, &OP_P1ControlsDef, 60, 30);
-menu_t OP_Mouse2OptionsDef = DEFAULTMENUSTYLE("M_CONTRO", OP_Mouse2OptionsMenu, &OP_P2ControlsDef, 60, 30);
-menu_t OP_Joystick1Def = DEFAULTMENUSTYLE("M_CONTRO", OP_Joystick1Menu, &OP_P1ControlsDef, 60, 30);
-menu_t OP_Joystick2Def = DEFAULTMENUSTYLE("M_CONTRO", OP_Joystick2Menu, &OP_P2ControlsDef, 60, 30);
+menu_t OP_MainDef = DEFAULTMENUSTYLE("M_OPTTTL", OP_MainMenu, &MainDef, 50, 30);
+menu_t OP_ChangeControlsDef = CONTROLMENUSTYLE(OP_ChangeControlsMenu, &OP_MainDef);
+menu_t OP_P1ControlsDef = DEFAULTMENUSTYLE("M_CONTRO", OP_P1ControlsMenu, &OP_MainDef, 50, 30);
+menu_t OP_P2ControlsDef = DEFAULTMENUSTYLE("M_CONTRO", OP_P2ControlsMenu, &OP_MainDef, 50, 30);
+menu_t OP_MouseOptionsDef = DEFAULTMENUSTYLE("M_CONTRO", OP_MouseOptionsMenu, &OP_P1ControlsDef, 35, 30);
+menu_t OP_Mouse2OptionsDef = DEFAULTMENUSTYLE("M_CONTRO", OP_Mouse2OptionsMenu, &OP_P2ControlsDef, 35, 30);
+menu_t OP_Joystick1Def = DEFAULTMENUSTYLE("M_CONTRO", OP_Joystick1Menu, &OP_P1ControlsDef, 50, 30);
+menu_t OP_Joystick2Def = DEFAULTMENUSTYLE("M_CONTRO", OP_Joystick2Menu, &OP_P2ControlsDef, 50, 30);
 menu_t OP_JoystickSetDef =
 {
 	"M_CONTRO",
@@ -1761,12 +1809,22 @@ menu_t OP_JoystickSetDef =
 	&OP_Joystick1Def,
 	OP_JoystickSetMenu,
 	M_DrawJoystick,
-	50, 40,
+	60, 40,
 	0,
 	NULL
 };
 
-menu_t OP_VideoOptionsDef = DEFAULTMENUSTYLE("M_VIDEO", OP_VideoOptionsMenu, &OP_MainDef, 60, 30);
+menu_t OP_VideoOptionsDef =
+{
+	"M_VIDEO",
+	sizeof (OP_VideoOptionsMenu)/sizeof (menuitem_t),
+	&OP_MainDef,
+	OP_VideoOptionsMenu,
+	M_DrawMainVideoMenu,
+	30, 30,
+	0,
+	NULL
+};
 menu_t OP_VideoModeDef =
 {
 	"M_VIDEO",
@@ -1778,12 +1836,31 @@ menu_t OP_VideoModeDef =
 	0,
 	NULL
 };
-menu_t OP_SoundOptionsDef = DEFAULTMENUSTYLE("M_SOUND", OP_SoundOptionsMenu, &OP_MainDef, 60, 30);
-menu_t OP_GameOptionsDef = DEFAULTMENUSTYLE("M_GAME", OP_GameOptionsMenu, &OP_MainDef, 30, 30);
-menu_t OP_ServerOptionsDef = DEFAULTMENUSTYLE("M_SERVER", OP_ServerOptionsMenu, &OP_MainDef, 30, 30);
+menu_t OP_ColorOptionsDef =
+{
+	"M_VIDEO",
+	sizeof (OP_ColorOptionsMenu)/sizeof (menuitem_t),
+	&OP_VideoOptionsDef,
+	OP_ColorOptionsMenu,
+	M_DrawColorMenu,
+	30, 30,
+	0,
+	NULL
+};
+menu_t OP_SoundOptionsDef =
+{
+	"M_SOUND",
+	sizeof (OP_SoundOptionsMenu)/sizeof (menuitem_t),
+	&OP_MainDef,
+	OP_SoundOptionsMenu,
+	M_DrawSoundMenu,
+	30, 30,
+	0,
+	NULL
+};
+
+menu_t OP_ServerOptionsDef = DEFAULTSCROLLMENUSTYLE("M_SERVER", OP_ServerOptionsMenu, &OP_MainDef, 30, 30);
 
-menu_t OP_NetgameOptionsDef = DEFAULTMENUSTYLE("M_SERVER", OP_NetgameOptionsMenu, &OP_ServerOptionsDef, 30, 30);
-menu_t OP_GametypeOptionsDef = DEFAULTMENUSTYLE("M_SERVER", OP_GametypeOptionsMenu, &OP_ServerOptionsDef, 30, 30);
 menu_t OP_MonitorToggleDef =
 {
 	"M_SERVER",
@@ -1825,7 +1902,21 @@ menu_t OP_OpenGLColorDef =
 };
 #endif
 menu_t OP_DataOptionsDef = DEFAULTMENUSTYLE("M_DATA", OP_DataOptionsMenu, &OP_MainDef, 60, 30);
-menu_t OP_ScreenshotOptionsDef = DEFAULTMENUSTYLE("M_DATA", OP_ScreenshotOptionsMenu, &OP_DataOptionsDef, 30, 30);
+
+menu_t OP_ScreenshotOptionsDef =
+{
+	"M_DATA",
+	sizeof (OP_ScreenshotOptionsMenu)/sizeof (menuitem_t),
+	&OP_DataOptionsDef,
+	OP_ScreenshotOptionsMenu,
+	M_DrawScreenshotMenu,
+	30, 30,
+	0,
+	NULL
+};
+
+menu_t OP_AddonsOptionsDef = DEFAULTMENUSTYLE("M_ADDONS", OP_AddonsOptionsMenu, &OP_DataOptionsDef, 30, 30);
+
 menu_t OP_EraseDataDef = DEFAULTMENUSTYLE("M_DATA", OP_EraseDataMenu, &OP_DataOptionsDef, 60, 30);
 
 // ==========================================================================
@@ -2044,6 +2135,12 @@ void Moviemode_mode_Onchange(void)
 		OP_ScreenshotOptionsMenu[i].status = IT_STRING|IT_CVAR;
 }
 
+void Addons_option_Onchange(void)
+{
+	OP_AddonsOptionsMenu[op_addons_folder].status =
+		(cv_addons_option.value == 3 ? IT_CVAR|IT_STRING|IT_CV_STRING : IT_DISABLED);
+}
+
 // ==========================================================================
 // END ORGANIZATION STUFF.
 // ==========================================================================
@@ -2106,9 +2203,12 @@ static void M_ChangeCvar(INT32 choice)
 static boolean M_ChangeStringCvar(INT32 choice)
 {
 	consvar_t *cv = (consvar_t *)currentMenu->menuitems[itemOn].itemaction;
-	char buf[255];
+	char buf[MAXSTRINGLENGTH];
 	size_t len;
 
+	if (shiftdown && choice >= 32 && choice <= 127)
+		choice = shiftxform[choice];
+
 	switch (choice)
 	{
 		case KEY_BACKSPACE:
@@ -2138,6 +2238,19 @@ static boolean M_ChangeStringCvar(INT32 choice)
 	return false;
 }
 
+// resets all cvars on a menu - assumes that all that have itemactions are cvars
+static void M_ResetCvars(void)
+{
+	INT32 i;
+	consvar_t *cv;
+	for (i = 0; i < currentMenu->numitems; i++)
+	{
+		if (!(currentMenu->menuitems[i].status & IT_CVAR) || !(cv = (consvar_t *)currentMenu->menuitems[i].itemaction))
+			continue;
+		CV_SetValue(cv, atoi(cv->defaultvalue));
+	}
+}
+
 static void M_NextOpt(void)
 {
 	INT16 oldItemOn = itemOn; // prevent infinite loop
@@ -2342,7 +2455,7 @@ boolean M_Responder(event_t *ev)
 				return true;
 
 			case KEY_F11: // Gamma Level
-				CV_AddValue(&cv_usegamma, 1);
+				CV_AddValue(&cv_globalgamma, 1);
 				return true;
 
 			// Spymode on F12 handled in game logic
@@ -2406,8 +2519,6 @@ boolean M_Responder(event_t *ev)
 	{
 		if ((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_STRING)
 		{
-			if (shiftdown && ch >= 32 && ch <= 127)
-				ch = shiftxform[ch];
 			if (M_ChangeStringCvar(ch))
 				return true;
 			else
@@ -2444,8 +2555,7 @@ boolean M_Responder(event_t *ev)
 			if (routine && ((currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_ARROWS
 				|| (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR))
 			{
-				if (currentMenu != &OP_SoundOptionsDef)
-					S_StartSound(NULL, sfx_menu1);
+				S_StartSound(NULL, sfx_menu1);
 				routine(0);
 			}
 			return true;
@@ -2454,8 +2564,7 @@ boolean M_Responder(event_t *ev)
 			if (routine && ((currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_ARROWS
 				|| (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR))
 			{
-				if (currentMenu != &OP_SoundOptionsDef)
-					S_StartSound(NULL, sfx_menu1);
+				S_StartSound(NULL, sfx_menu1);
 				routine(1);
 			}
 			return true;
@@ -2507,6 +2616,7 @@ boolean M_Responder(event_t *ev)
 			{
 				// detach any keys associated with the game control
 				G_ClearControlKeys(setupcontrols, currentMenu->menuitems[itemOn].alphaKey);
+				S_StartSound(NULL, sfx_shldls);
 				return true;
 			}
 			// Why _does_ backspace go back anyway?
@@ -2654,6 +2764,7 @@ void M_StartControlPanel(void)
 	else // multiplayer
 	{
 		MPauseMenu[mpause_switchmap].status = IT_DISABLED;
+		MPauseMenu[mpause_addons].status = IT_DISABLED;
 		MPauseMenu[mpause_scramble].status = IT_DISABLED;
 		MPauseMenu[mpause_psetupsplit].status = IT_DISABLED;
 		MPauseMenu[mpause_psetupsplit2].status = IT_DISABLED;
@@ -2665,15 +2776,20 @@ void M_StartControlPanel(void)
 		if ((server || adminplayer == consoleplayer))
 		{
 			MPauseMenu[mpause_switchmap].status = IT_STRING | IT_CALL;
+			MPauseMenu[mpause_addons].status = IT_STRING | IT_CALL;
 			if (G_GametypeHasTeams())
 				MPauseMenu[mpause_scramble].status = IT_STRING | IT_SUBMENU;
 		}
 
 		if (splitscreen)
+		{
 			MPauseMenu[mpause_psetupsplit].status = MPauseMenu[mpause_psetupsplit2].status = IT_STRING | IT_CALL;
+			MPauseMenu[mpause_psetup].text = "Player 1 Setup";
+		}
 		else
 		{
 			MPauseMenu[mpause_psetup].status = IT_STRING | IT_CALL;
+			MPauseMenu[mpause_psetup].text = "Player Setup";
 
 			if (G_GametypeHasTeams())
 				MPauseMenu[mpause_switchteam].status = IT_STRING | IT_SUBMENU;
@@ -2822,15 +2938,14 @@ void M_Init(void)
 #ifdef HWRENDER
 	// Permanently hide some options based on render mode
 	if (rendermode == render_soft)
-		OP_VideoOptionsMenu[1].status = IT_DISABLED;
+		OP_VideoOptionsMenu[4].status = IT_DISABLED;
+	else if (rendermode == render_opengl)
+		OP_ScreenshotOptionsMenu[op_screenshot_colorprofile].status = IT_GRAYEDOUT;
 #endif
 
 #ifndef NONET
 	CV_RegisterVar(&cv_serversort);
 #endif
-
-	//todo put this somewhere better...
-	CV_RegisterVar(&cv_allcaps);
 }
 
 // ==========================================================================
@@ -2889,36 +3004,58 @@ static void M_DrawThermo(INT32 x, INT32 y, consvar_t *cv)
 }
 
 //  A smaller 'Thermo', with range given as percents (0-100)
-static void M_DrawSlider(INT32 x, INT32 y, const consvar_t *cv)
+static void M_DrawSlider(INT32 x, INT32 y, const consvar_t *cv, boolean ontop)
 {
 	INT32 i;
 	INT32 range;
 	patch_t *p;
 
-	for (i = 0; cv->PossibleValue[i+1].strvalue; i++);
-
-	range = ((cv->value - cv->PossibleValue[0].value) * 100 /
-	 (cv->PossibleValue[i].value - cv->PossibleValue[0].value));
-
-	if (range < 0)
-		range = 0;
-	if (range > 100)
-		range = 100;
-
 	x = BASEVIDWIDTH - x - SLIDER_WIDTH;
 
-	V_DrawScaledPatch(x - 8, y, 0, W_CachePatchName("M_SLIDEL", PU_CACHE));
+	V_DrawScaledPatch(x, y, 0, W_CachePatchName("M_SLIDEL", PU_CACHE));
 
 	p =  W_CachePatchName("M_SLIDEM", PU_CACHE);
-	for (i = 0; i < SLIDER_RANGE; i++)
+	for (i = 1; i < SLIDER_RANGE; i++)
 		V_DrawScaledPatch (x+i*8, y, 0,p);
 
+	if (ontop)
+	{
+		V_DrawCharacter(x - 6 - (skullAnimCounter/5), y,
+			'\x1C' | V_YELLOWMAP, false);
+		V_DrawCharacter(x+i*8 + 8 + (skullAnimCounter/5), y,
+			'\x1D' | V_YELLOWMAP, false);
+	}
+
 	p = W_CachePatchName("M_SLIDER", PU_CACHE);
-	V_DrawScaledPatch(x+SLIDER_RANGE*8, y, 0, p);
+	V_DrawScaledPatch(x+i*8, y, 0, p);
 
 	// draw the slider cursor
 	p = W_CachePatchName("M_SLIDEC", PU_CACHE);
-	V_DrawMappedPatch(x + ((SLIDER_RANGE-1)*8*range)/100, y, 0, p, yellowmap);
+
+	for (i = 0; cv->PossibleValue[i+1].strvalue; i++);
+
+	if ((range = atoi(cv->defaultvalue)) != cv->value)
+	{
+		range = ((range - cv->PossibleValue[0].value) * 100 /
+		 (cv->PossibleValue[i].value - cv->PossibleValue[0].value));
+
+		if (range < 0)
+			range = 0;
+		else if (range > 100)
+			range = 100;
+
+		V_DrawMappedPatch(x + 2 + (SLIDER_RANGE*8*range)/100, y, V_TRANSLUCENT, p, yellowmap);
+	}
+
+	range = ((cv->value - cv->PossibleValue[0].value) * 100 /
+	 (cv->PossibleValue[i].value - cv->PossibleValue[0].value));
+
+	if (range < 0)
+		range = 0;
+	else if (range > 100)
+		range = 100;
+
+	V_DrawMappedPatch(x + 2 + (SLIDER_RANGE*8*range)/100, y, 0, p, yellowmap);
 }
 
 //
@@ -3138,7 +3275,7 @@ static void M_DrawGenericMenu(void)
 						switch (currentMenu->menuitems[i].status & IT_CVARTYPE)
 						{
 							case IT_CV_SLIDER:
-								M_DrawSlider(x, y, cv);
+								M_DrawSlider(x, y, cv, (i == itemOn));
 							case IT_CV_NOPRINT: // color use this
 							case IT_CV_INVISSLIDER: // monitor toggles use this
 								break;
@@ -3151,8 +3288,15 @@ static void M_DrawGenericMenu(void)
 								y += 16;
 								break;
 							default:
-								V_DrawString(BASEVIDWIDTH - x - V_StringWidth(cv->string, 0), y,
+								V_DrawRightAlignedString(BASEVIDWIDTH - x, y,
 									((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? V_REDMAP : V_YELLOWMAP), cv->string);
+								if (i == itemOn)
+								{
+									V_DrawCharacter(BASEVIDWIDTH - x - 10 - V_StringWidth(cv->string, 0) - (skullAnimCounter/5), y,
+											'\x1C' | V_YELLOWMAP, false);
+									V_DrawCharacter(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y,
+											'\x1D' | V_YELLOWMAP, false);
+								}
 								break;
 						}
 						break;
@@ -3188,7 +3332,8 @@ static void M_DrawGenericMenu(void)
 				if (currentMenu->menuitems[i].alphaKey)
 					y = currentMenu->y+currentMenu->menuitems[i].alphaKey;
 
-				V_DrawString(x-16, y, V_YELLOWMAP, currentMenu->menuitems[i].text);
+				//V_DrawString(x-16, y, V_YELLOWMAP, currentMenu->menuitems[i].text);
+				M_DrawLevelPlatterHeader(y - (lsheadingheight - 12), currentMenu->menuitems[i].text, true, false);
 				y += SMALLLINEHEIGHT;
 				break;
 		}
@@ -3209,18 +3354,154 @@ static void M_DrawGenericMenu(void)
 	}
 }
 
-static void M_DrawPauseMenu(void)
+#define scrollareaheight 72
+
+// note that alphakey is multiplied by 2 for scrolling menus to allow greater usage in UINT8 range.
+static void M_DrawGenericScrollMenu(void)
 {
-	if (!netgame && !multiplayer && (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION))
-	{
-		emblem_t *emblem_detail[3] = {NULL, NULL, NULL};
-		char emblem_text[3][20];
-		INT32 i;
+	INT32 x, y, i, max, bottom, tempcentery, cursory = 0;
 
-		M_DrawTextBox(27, 16, 32, 6);
+	// DRAW MENU
+	x = currentMenu->x;
+	y = currentMenu->y;
 
-		// Draw any and all emblems at the top.
-		M_DrawMapEmblems(gamemap, 272, 28);
+	if ((currentMenu->menuitems[itemOn].alphaKey*2 - currentMenu->menuitems[0].alphaKey*2) <= scrollareaheight)
+		tempcentery = currentMenu->y - currentMenu->menuitems[0].alphaKey*2;
+	else if ((currentMenu->menuitems[currentMenu->numitems-1].alphaKey*2 - currentMenu->menuitems[itemOn].alphaKey*2) <= scrollareaheight)
+		tempcentery = currentMenu->y - currentMenu->menuitems[currentMenu->numitems-1].alphaKey*2 + 2*scrollareaheight;
+	else
+		tempcentery = currentMenu->y - currentMenu->menuitems[itemOn].alphaKey*2 + scrollareaheight;
+
+	for (i = 0; i < currentMenu->numitems; i++)
+	{
+		if (currentMenu->menuitems[i].status != IT_DISABLED && currentMenu->menuitems[i].alphaKey*2 + tempcentery >= currentMenu->y)
+			break;
+	}
+
+	for (bottom = currentMenu->numitems; bottom > 0; bottom--)
+	{
+		if (currentMenu->menuitems[bottom-1].status != IT_DISABLED)
+			break;
+	}
+
+	for (max = bottom; max > 0; max--)
+	{
+		if (currentMenu->menuitems[max-1].status != IT_DISABLED && currentMenu->menuitems[max-1].alphaKey*2 + tempcentery <= (currentMenu->y + 2*scrollareaheight))
+			break;
+	}
+
+	if (i)
+		V_DrawString(currentMenu->x - 20, currentMenu->y - (skullAnimCounter/5), V_YELLOWMAP, "\x1A"); // up arrow
+	if (max != bottom)
+		V_DrawString(currentMenu->x - 20, currentMenu->y + 2*scrollareaheight + (skullAnimCounter/5), V_YELLOWMAP, "\x1B"); // down arrow
+
+	// draw title (or big pic)
+	M_DrawMenuTitle();
+
+	for (; i < max; i++)
+	{
+		y = currentMenu->menuitems[i].alphaKey*2 + tempcentery;
+		if (i == itemOn)
+			cursory = y;
+		switch (currentMenu->menuitems[i].status & IT_DISPLAY)
+		{
+			case IT_PATCH:
+			case IT_DYBIGSPACE:
+			case IT_BIGSLIDER:
+			case IT_STRING2:
+			case IT_DYLITLSPACE:
+			case IT_GRAYPATCH:
+			case IT_TRANSTEXT2:
+				// unsupported
+				break;
+			case IT_NOTHING:
+				break;
+			case IT_STRING:
+			case IT_WHITESTRING:
+				if (i != itemOn && (currentMenu->menuitems[i].status & IT_DISPLAY)==IT_STRING)
+					V_DrawString(x, y, 0, currentMenu->menuitems[i].text);
+				else
+					V_DrawString(x, y, V_YELLOWMAP, currentMenu->menuitems[i].text);
+
+				// Cvar specific handling
+				switch (currentMenu->menuitems[i].status & IT_TYPE)
+					case IT_CVAR:
+					{
+						consvar_t *cv = (consvar_t *)currentMenu->menuitems[i].itemaction;
+						switch (currentMenu->menuitems[i].status & IT_CVARTYPE)
+						{
+							case IT_CV_SLIDER:
+								M_DrawSlider(x, y, cv, (i == itemOn));
+							case IT_CV_NOPRINT: // color use this
+							case IT_CV_INVISSLIDER: // monitor toggles use this
+								break;
+							case IT_CV_STRING:
+#if 1
+								if (y + 12 > (currentMenu->y + 2*scrollareaheight))
+									break;
+								M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1);
+								V_DrawString(x + 8, y + 12, V_ALLOWLOWERCASE, cv->string);
+								if (skullAnimCounter < 4 && i == itemOn)
+									V_DrawCharacter(x + 8 + V_StringWidth(cv->string, 0), y + 12,
+										'_' | 0x80, false);
+#else // cool new string type stuff, not ready for limelight
+								if (i == itemOn)
+								{
+									V_DrawFill(x-2, y-1, MAXSTRINGLENGTH*8 + 4, 8+3, 159);
+									V_DrawString(x, y, V_ALLOWLOWERCASE, cv->string);
+									if (skullAnimCounter < 4)
+										V_DrawCharacter(x + V_StringWidth(cv->string, 0), y, '_' | 0x80, false);
+								}
+								else
+									V_DrawRightAlignedString(BASEVIDWIDTH - x, y,
+									V_YELLOWMAP|V_ALLOWLOWERCASE, cv->string);
+#endif
+								break;
+							default:
+								V_DrawRightAlignedString(BASEVIDWIDTH - x, y,
+									((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? V_REDMAP : V_YELLOWMAP), cv->string);
+								if (i == itemOn)
+								{
+									V_DrawCharacter(BASEVIDWIDTH - x - 10 - V_StringWidth(cv->string, 0) - (skullAnimCounter/5), y,
+											'\x1C' | V_YELLOWMAP, false);
+									V_DrawCharacter(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y,
+											'\x1D' | V_YELLOWMAP, false);
+								}
+								break;
+						}
+						break;
+					}
+					break;
+			case IT_TRANSTEXT:
+				V_DrawString(x, y, V_TRANSLUCENT, currentMenu->menuitems[i].text);
+				break;
+			case IT_QUESTIONMARKS:
+				V_DrawString(x, y, V_TRANSLUCENT|V_OLDSPACING, M_CreateSecretMenuOption(currentMenu->menuitems[i].text));
+				break;
+			case IT_HEADERTEXT:
+				//V_DrawString(x-16, y, V_YELLOWMAP, currentMenu->menuitems[i].text);
+				M_DrawLevelPlatterHeader(y - (lsheadingheight - 12), currentMenu->menuitems[i].text, true, false);
+				break;
+		}
+	}
+
+	// DRAW THE SKULL CURSOR
+	V_DrawScaledPatch(currentMenu->x - 24, cursory, 0,
+		W_CachePatchName("M_CURSOR", PU_CACHE));
+}
+
+static void M_DrawPauseMenu(void)
+{
+	if (!netgame && !multiplayer && (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION))
+	{
+		emblem_t *emblem_detail[3] = {NULL, NULL, NULL};
+		char emblem_text[3][20];
+		INT32 i;
+
+		M_DrawTextBox(27, 16, 32, 6);
+
+		// Draw any and all emblems at the top.
+		M_DrawMapEmblems(gamemap, 272, 28);
 
 		if (mapheaderinfo[gamemap-1]->actnum != 0)
 			V_DrawString(40, 28, V_YELLOWMAP, va("%s %d", mapheaderinfo[gamemap-1]->lvlttl, mapheaderinfo[gamemap-1]->actnum));
@@ -3413,7 +3694,7 @@ static void M_DrawCenteredMenu(void)
 						switch(currentMenu->menuitems[i].status & IT_CVARTYPE)
 						{
 							case IT_CV_SLIDER:
-								M_DrawSlider(x, y, cv);
+								M_DrawSlider(x, y, cv, (i == itemOn));
 							case IT_CV_NOPRINT: // color use this
 								break;
 							case IT_CV_STRING:
@@ -3427,6 +3708,13 @@ static void M_DrawCenteredMenu(void)
 							default:
 								V_DrawString(BASEVIDWIDTH - x - V_StringWidth(cv->string, 0), y,
 									((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? V_REDMAP : V_YELLOWMAP), cv->string);
+								if (i == itemOn)
+								{
+									V_DrawCharacter(BASEVIDWIDTH - x - 10 - V_StringWidth(cv->string, 0) - (skullAnimCounter/5), y,
+											'\x1C' | V_YELLOWMAP, false);
+									V_DrawCharacter(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y,
+											'\x1D' | V_YELLOWMAP, false);
+								}
 								break;
 						}
 						break;
@@ -3709,7 +3997,7 @@ static boolean M_PrepareLevelPlatter(INT32 gt)
 		I_Error("Insufficient memory to prepare level platter");
 
 	// done here so lsrow and lscol can be set if cv_nextmap is on the platter
-	lsrow = lscol = lstic = lshli = lsoffs[0] = lsoffs[1] = 0;
+	lsrow = lscol = lshli = lsoffs[0] = lsoffs[1] = 0;
 
 	while (mapnum < NUMMAPS)
 	{
@@ -3932,6 +4220,8 @@ static void M_HandleLevelPlatter(INT32 choice)
 						M_SetupNextMenu(currentMenu->prevMenu->prevMenu);
 					else
 						M_ChangeLevel(0);
+					Z_Free(levelselect.rows);
+					levelselect.rows = NULL;
 				}
 				else
 					M_LevelSelectWarp(0);
@@ -3961,13 +4251,15 @@ static void M_HandleLevelPlatter(INT32 choice)
 		}
 		else
 			M_ClearMenus(true);
+		Z_Free(levelselect.rows);
+		levelselect.rows = NULL;
 	}
 }
 
-static void M_DrawLevelPlatterHeader(INT32 y, const char *header, boolean headerhighlight)
+void M_DrawLevelPlatterHeader(INT32 y, const char *header, boolean headerhighlight, boolean allowlowercase)
 {
 	y += lsheadingheight - 12;
-	V_DrawString(19, y, (headerhighlight ? V_YELLOWMAP : 0), header);
+	V_DrawString(19, y, (headerhighlight ? V_YELLOWMAP : 0)|(allowlowercase ? V_ALLOWLOWERCASE : 0), header);
 	y += 9;
 	if ((y >= 0) && (y < 200))
 	{
@@ -3976,10 +4268,7 @@ static void M_DrawLevelPlatterHeader(INT32 y, const char *header, boolean header
 	}
 	y++;
 	if ((y >= 0) && (y < 200))
-	{
 		V_DrawFill(19, y, 282, 1, 26);
-	}
-	y += 2;
 }
 
 static void M_DrawLevelPlatterWideMap(UINT8 row, UINT8 col, INT32 x, INT32 y, boolean highlight)
@@ -4073,7 +4362,7 @@ static void M_DrawLevelPlatterRow(UINT8 row, INT32 y)
 	const boolean rowhighlight = (row == lsrow);
 	if (levelselect.rows[row].header[0])
 	{
-		M_DrawLevelPlatterHeader(y, levelselect.rows[row].header, (rowhighlight || (row == lshli)));
+		M_DrawLevelPlatterHeader(y, levelselect.rows[row].header, (rowhighlight || (row == lshli)), false);
 		y += lsheadingheight;
 	}
 
@@ -4092,9 +4381,6 @@ static void M_DrawLevelPlatterMenu(void)
 	INT32 y = lsbasey + lsoffs[0] - getheadingoffset(lsrow);
 	const INT32 cursorx = (sizeselect ? 0 : (lscol*lshseperation));
 
-	if (++lstic == 32)
-		lstic = 0;
-
 	if (gamestate == GS_TIMEATTACK)
 		V_DrawPatchFill(W_CachePatchName("SRB2BACK", PU_CACHE));
 
@@ -4114,7 +4400,7 @@ static void M_DrawLevelPlatterMenu(void)
 	}
 
 	// draw cursor box
-	V_DrawSmallScaledPatch(lsbasex + cursorx + lsoffs[1], lsbasey, 0, ((lstic & 8) ? levselp[sizeselect][0] : levselp[sizeselect][1]));
+	V_DrawSmallScaledPatch(lsbasex + cursorx + lsoffs[1], lsbasey, 0, (levselp[sizeselect][((skullAnimCounter/4) ? 1 : 0)]));
 
 	if (levelselect.rows[lsrow].maplist[lscol])
 		V_DrawScaledPatch(lsbasex + cursorx-17, lsbasey+50+lsoffs[0], 0, W_CachePatchName("M_CURSOR", PU_CACHE));
@@ -4414,185 +4700,696 @@ static void M_HandleImageDef(INT32 choice)
 // MISC MAIN MENU OPTIONS
 // ======================
 
-static void M_PandorasBox(INT32 choice)
+static void M_AddonsOptions(INT32 choice)
 {
 	(void)choice;
-	CV_StealthSetValue(&cv_dummyrings, max(players[consoleplayer].rings, 0));
-	CV_StealthSetValue(&cv_dummylives, players[consoleplayer].lives);
-	CV_StealthSetValue(&cv_dummycontinues, players[consoleplayer].continues);
-	M_SetupNextMenu(&SR_PandoraDef);
-}
+	Addons_option_Onchange();
 
-static boolean M_ExitPandorasBox(void)
-{
-	if (cv_dummyrings.value != max(players[consoleplayer].rings, 0))
-		COM_ImmedExecute(va("setrings %d", cv_dummyrings.value));
-	if (cv_dummylives.value != players[consoleplayer].lives)
-		COM_ImmedExecute(va("setlives %d", cv_dummylives.value));
-	if (cv_dummycontinues.value != players[consoleplayer].continues)
-		COM_ImmedExecute(va("setcontinues %d", cv_dummycontinues.value));
-	return true;
+	M_SetupNextMenu(&OP_AddonsOptionsDef);
 }
 
-static void M_ChangeLevel(INT32 choice)
-{
-	char mapname[6];
-	(void)choice;
-
-	strlcpy(mapname, G_BuildMapName(cv_nextmap.value), sizeof (mapname));
-	strlwr(mapname);
-	mapname[5] = '\0';
-
-	M_ClearMenus(true);
-	COM_BufAddText(va("map %s -gametype \"%s\"\n", mapname, cv_newgametype.string));
-}
+#define LOCATIONSTRING "Visit \x83SRB2.ORG/MODS\x80 to get & make add-ons!"
 
-static void M_ConfirmSpectate(INT32 choice)
+static void M_Addons(INT32 choice)
 {
-	(void)choice;
-	M_ClearMenus(true);
-	COM_ImmedExecute("changeteam spectator");
-}
+	const char *pathname = ".";
 
-static void M_ConfirmEnterGame(INT32 choice)
-{
 	(void)choice;
-	M_ClearMenus(true);
-	COM_ImmedExecute("changeteam playing");
-}
 
-static void M_ConfirmTeamScramble(INT32 choice)
-{
-	(void)choice;
-	M_ClearMenus(true);
+	/*if (cv_addons_option.value == 0)
+		pathname = srb2home; usehome ? srb2home : srb2path;
+	else if (cv_addons_option.value == 1)
+		pathname = srb2home;
+	else if (cv_addons_option.value == 2)
+		pathname = srb2path;
+	else*/
+	if (cv_addons_option.value == 3 && *cv_addons_folder.string != '\0')
+		pathname = cv_addons_folder.string;
 
-	switch (cv_dummyscramble.value)
+	strlcpy(menupath, pathname, 1024);
+	menupathindex[(menudepthleft = menudepth-1)] = strlen(menupath) + 1;
+
+	if (menupath[menupathindex[menudepthleft]-2] != '/')
 	{
-		case 0:
-			COM_ImmedExecute("teamscramble 1");
-			break;
-		case 1:
-			COM_ImmedExecute("teamscramble 2");
-			break;
+		menupath[menupathindex[menudepthleft]-1] = '/';
+		menupath[menupathindex[menudepthleft]] = 0;
 	}
-}
+	else
+		--menupathindex[menudepthleft];
 
-static void M_ConfirmTeamChange(INT32 choice)
-{
-	(void)choice;
-	if (!cv_allowteamchange.value && cv_dummyteam.value)
+	if (!preparefilemenu(false))
 	{
-		M_StartMessage(M_GetText("The server is not allowing\nteam changes at this time.\nPress a key.\n"), NULL, MM_NOTHING);
+		M_StartMessage(M_GetText("No files/folders found.\n\n"LOCATIONSTRING"\n\n(Press a key)\n"),NULL,MM_NOTHING);
 		return;
 	}
+	else
+		dir_on[menudepthleft] = 0;
+
+	if (addonsp[0]) // never going to have some provided but not all, saves individually checking
+	{
+		size_t i;
+		for (i = 0; i < NUM_EXT+6; i++)
+			W_UnlockCachedPatch(addonsp[i]);
+	}
+
+	addonsp[EXT_FOLDER] = W_CachePatchName("M_FFLDR", PU_STATIC);
+	addonsp[EXT_UP] = W_CachePatchName("M_FBACK", PU_STATIC);
+	addonsp[EXT_NORESULTS] = W_CachePatchName("M_FNOPE", PU_STATIC);
+	addonsp[EXT_TXT] = W_CachePatchName("M_FTXT", PU_STATIC);
+	addonsp[EXT_CFG] = W_CachePatchName("M_FCFG", PU_STATIC);
+	addonsp[EXT_WAD] = W_CachePatchName("M_FWAD", PU_STATIC);
+	addonsp[EXT_SOC] = W_CachePatchName("M_FSOC", PU_STATIC);
+	addonsp[EXT_LUA] = W_CachePatchName("M_FLUA", PU_STATIC);
+	addonsp[NUM_EXT] = W_CachePatchName("M_FUNKN", PU_STATIC);
+	addonsp[NUM_EXT+1] = W_CachePatchName("M_FSEL1", PU_STATIC);
+	addonsp[NUM_EXT+2] = W_CachePatchName("M_FSEL2", PU_STATIC);
+	addonsp[NUM_EXT+3] = W_CachePatchName("M_FLOAD", PU_STATIC);
+	addonsp[NUM_EXT+4] = W_CachePatchName("M_FSRCH", PU_STATIC);
+	addonsp[NUM_EXT+5] = W_CachePatchName("M_FSAVE", PU_STATIC);
+
+	MISC_AddonsDef.prevMenu = currentMenu;
+	M_SetupNextMenu(&MISC_AddonsDef);
+}
+
+#define width 4
+#define vpadding 27
+#define h (BASEVIDHEIGHT-(2*vpadding))
+#define NUMCOLOURS 8 // when toast's coding it's british english hacker fucker
+static void M_DrawTemperature(INT32 x, fixed_t t)
+{
+	INT32 y;
+
+	// bounds check
+	if (t > FRACUNIT)
+		t = FRACUNIT;
+	/*else if (t < 0) -- not needed
+		t = 0;*/
+
+	// scale
+	if (t > 1)
+		t = (FixedMul(h<<FRACBITS, t)>>FRACBITS);
+
+	// border
+	V_DrawFill(x - 1, vpadding, 1, h, 3);
+	V_DrawFill(x + width, vpadding, 1, h, 3);
+	V_DrawFill(x - 1, vpadding-1, width+2, 1, 3);
+	V_DrawFill(x - 1, vpadding+h, width+2, 1, 3);
+
+	// bar itself
+	y = h;
+	if (t)
+		for (t = h - t; y > 0; y--)
+		{
+			UINT8 colours[NUMCOLOURS] = {42, 40, 58, 222, 65, 90, 97, 98};
+			UINT8 c;
+			if (y <= t) break;
+			if (y+vpadding >= BASEVIDHEIGHT/2)
+				c = 113;
+			else
+				c = colours[(NUMCOLOURS*(y-1))/(h/2)];
+			V_DrawFill(x, y-1 + vpadding, width, 1, c);
+		}
 
-	M_ClearMenus(true);
-
-	switch (cv_dummyteam.value)
-	{
-		case 0:
-			COM_ImmedExecute("changeteam spectator");
-			break;
-		case 1:
-			COM_ImmedExecute("changeteam red");
-			break;
-		case 2:
-			COM_ImmedExecute("changeteam blue");
-			break;
-	}
+	// fill the rest of the backing
+	if (y)
+		V_DrawFill(x, vpadding, width, y, 27);
 }
+#undef width
+#undef vpadding
+#undef h
+#undef NUMCOLOURS
 
-static void M_Options(INT32 choice)
+static char *M_AddonsHeaderPath(void)
 {
-	(void)choice;
+	UINT32 len;
+	static char header[1024];
 
-	// if the player is not admin or server, disable server options
-	OP_MainMenu[5].status = (Playing() && !(server || adminplayer == consoleplayer)) ? (IT_GRAYEDOUT) : (IT_STRING|IT_CALL);
+	if (menupath[0] == '.')
+		strlcpy(header, va("SRB2 folder%s", menupath+1), 1024);
+	else
+		strcpy(header, menupath);
 
-	// if the player is playing _at all_, disable the erase data options
-	OP_DataOptionsMenu[1].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU);
+	len = strlen(header);
+	if (len > 34)
+	{
+		len = len-34;
+		header[len] = header[len+1] = header[len+2] = '.';
+	}
+	else
+		len = 0;
 
-	OP_MainDef.prevMenu = currentMenu;
-	M_SetupNextMenu(&OP_MainDef);
+	return header+len;
 }
 
-static void M_RetryResponse(INT32 ch)
+#define UNEXIST S_StartSound(NULL, sfx_lose);\
+		M_SetupNextMenu(MISC_AddonsDef.prevMenu);\
+		M_StartMessage(va("\x82%s\x80\nThis folder no longer exists!\nAborting to main menu.\n\n(Press a key)\n", M_AddonsHeaderPath()),NULL,MM_NOTHING)
+
+// returns whether to do message draw
+static boolean M_AddonsRefresh(void)
 {
-	if (ch != 'y' && ch != KEY_ENTER)
-		return;
+	if ((refreshdirmenu & REFRESHDIR_NORMAL) && !preparefilemenu(true))
+	{
+		UNEXIST;
+		return true;
+	}
 
-	if (!&players[consoleplayer] || netgame || multiplayer) // Should never happen!
-		return;
+	if (refreshdirmenu & REFRESHDIR_ADDFILE)
+	{
+		addonsresponselimit = 0;
 
-	M_ClearMenus(true);
-	G_SetRetryFlag();
-}
+		if (refreshdirmenu & REFRESHDIR_NOTLOADED)
+		{
+			char *message = NULL;
+			S_StartSound(NULL, sfx_lose);
+			if (refreshdirmenu & REFRESHDIR_MAX)
+				message = va("\x82%s\x80\nMaximum number of add-ons reached.\nThis file could not be loaded.\nIf you want to play with this add-on, restart the game to clear existing ones.\n\n(Press a key)\n", dirmenu[dir_on[menudepthleft]]+DIR_STRING);
+			else
+				message = va("\x82%s\x80\nThe file was not loaded.\nCheck the console log for more information.\n\n(Press a key)\n", dirmenu[dir_on[menudepthleft]]+DIR_STRING);
+			M_StartMessage(message,NULL,MM_NOTHING);
+			return true;
+		}
 
-static void M_Retry(INT32 choice)
-{
-	(void)choice;
-	M_StartMessage(M_GetText("Retry this act from the last starpost?\n\n(Press 'Y' to confirm)\n"),M_RetryResponse,MM_YESNO);
-}
+		if (refreshdirmenu & (REFRESHDIR_WARNING|REFRESHDIR_ERROR))
+		{
+			S_StartSound(NULL, sfx_skid);
+			M_StartMessage(va("\x82%s\x80\nThe file was loaded with %s.\nCheck the console log for more information.\n\n(Press a key)\n", dirmenu[dir_on[menudepthleft]]+DIR_STRING, ((refreshdirmenu & REFRESHDIR_ERROR) ? "errors" : "warnings")),NULL,MM_NOTHING);
+			return true;
+		}
 
-static void M_SelectableClearMenus(INT32 choice)
-{
-	(void)choice;
-	M_ClearMenus(true);
+		S_StartSound(NULL, sfx_strpst);
+	}
+
+	return false;
 }
 
-// ======
-// CHEATS
-// ======
+#define offs 1
 
-static void M_UltimateCheat(INT32 choice)
+static void M_DrawAddons(void)
 {
-	(void)choice;
-	I_Quit();
-}
+	INT32 x, y;
+	ssize_t i, max;
 
-static void M_GetAllEmeralds(INT32 choice)
-{
-	(void)choice;
+	// hack - need to refresh at end of frame to handle addfile...
+	if (refreshdirmenu & M_AddonsRefresh())
+		return M_DrawMessageMenu();
 
-	emeralds = ((EMERALD7)*2)-1;
-	M_StartMessage(M_GetText("You now have all 7 emeralds.\nUse them wisely.\nWith great power comes great ring drain.\n"),NULL,MM_NOTHING);
+	if (addonsresponselimit)
+		addonsresponselimit--;
 
-	G_SetGameModified(multiplayer);
-}
+	V_DrawCenteredString(BASEVIDWIDTH/2, 4+offs, 0, (Playing()
+	? "\x85""Adding files mid-game may cause problems."
+	: LOCATIONSTRING));
 
-static void M_DestroyRobotsResponse(INT32 ch)
-{
-	if (ch != 'y' && ch != KEY_ENTER)
-		return;
+	if (numwadfiles <= mainwads+1)
+		y = 0;
+	else if (numwadfiles >= MAX_WADFILES)
+		y = FRACUNIT;
+	else
+	{
+		x = FixedDiv((numwadfiles - mainwads+1)<<FRACBITS, (MAX_WADFILES - mainwads+1)<<FRACBITS);
+		y = FixedDiv(((packetsizetally-mainwadstally)<<FRACBITS), (((MAXFILENEEDED*sizeof(UINT8)-mainwadstally)-(5+22))<<FRACBITS)); // 5+22 = (a.ext + checksum length) is minimum addition to packet size tally
+		if (x > y)
+			y = x;
+		if (y > FRACUNIT) // happens because of how we're shrinkin' it a little
+			y = FRACUNIT;
+	}
 
-	// Destroy all robots
-	P_DestroyRobots();
+	M_DrawTemperature(BASEVIDWIDTH - 19 - 5, y);
 
-	G_SetGameModified(multiplayer);
-}
+	// DRAW MENU
+	x = currentMenu->x;
+	y = currentMenu->y + offs;
 
-static void M_DestroyRobots(INT32 choice)
-{
-	(void)choice;
+	//M_DrawLevelPlatterHeader(y - 16, M_AddonsHeaderPath(), true, true); -- wanted different width
+	V_DrawString(x-21, (y - 16) + (lsheadingheight - 12), V_YELLOWMAP|V_ALLOWLOWERCASE, M_AddonsHeaderPath());
+	V_DrawFill(x-21, (y - 16) + (lsheadingheight - 3), (MAXSTRINGLENGTH*8+6 - 1), 1, yellowmap[3]);
+	V_DrawFill(x-21 + (MAXSTRINGLENGTH*8+6 - 1), (y - 16) + (lsheadingheight - 3), 1, 1, 26);
+	V_DrawFill(x-21, (y - 16) + (lsheadingheight - 2), MAXSTRINGLENGTH*8+6, 1, 26);
 
-	M_StartMessage(M_GetText("Do you want to destroy all\nrobots in the current level?\n\n(Press 'Y' to confirm)\n"),M_DestroyRobotsResponse,MM_YESNO);
-}
+	V_DrawFill(x - 21, y - 1, MAXSTRINGLENGTH*8+6, (BASEVIDHEIGHT - currentMenu->y + 1 + offs) - (y - 1), 159);
 
-static void M_LevelSelectWarp(INT32 choice)
-{
-	boolean fromloadgame = (currentMenu == &SP_LevelSelectDef);
+	// get bottom...
+	max = dir_on[menudepthleft] + numaddonsshown + 1;
+	if (max > (ssize_t)sizedirmenu)
+		max = sizedirmenu;
 
-	(void)choice;
+	// then top...
+	i = max - (2*numaddonsshown + 1);
 
-	if (W_CheckNumForName(G_BuildMapName(cv_nextmap.value)) == LUMPERROR)
+	// then adjust!
+	if (i < 0)
 	{
-//		CONS_Alert(CONS_WARNING, "Internal game map '%s' not found\n", G_BuildMapName(cv_nextmap.value));
-		return;
+		if ((max -= i) > (ssize_t)sizedirmenu)
+			max = sizedirmenu;
+		i = 0;
 	}
 
-	startmap = (INT16)(cv_nextmap.value);
+	if (i != 0)
+		V_DrawString(19, y+4 - (skullAnimCounter/5), V_YELLOWMAP, "\x1A");
 
-	fromlevelselect = true;
+	for (; i < max; i++)
+	{
+		UINT32 flags = V_ALLOWLOWERCASE;
+		if (y > BASEVIDHEIGHT) break;
+		if (dirmenu[i])
+#define type (UINT8)(dirmenu[i][DIR_TYPE])
+		{
+			if (type & EXT_LOADED)
+			flags |= V_TRANSLUCENT;
+
+			V_DrawSmallScaledPatch(x-(16+4), y, (flags & V_TRANSLUCENT), addonsp[((UINT8)(dirmenu[i][DIR_TYPE]) & ~EXT_LOADED)]);
+
+			if (type & EXT_LOADED)
+				V_DrawSmallScaledPatch(x-(16+4), y, 0, addonsp[NUM_EXT+3]);
+
+			if ((size_t)i == dir_on[menudepthleft])
+			{
+				V_DrawSmallScaledPatch(x-(16+4), y, 0, addonsp[NUM_EXT+1+((skullAnimCounter/4) ? 1 : 0)]);
+				flags = V_ALLOWLOWERCASE|V_YELLOWMAP;
+			}
+
+#define charsonside 14
+			if (dirmenu[i][DIR_LEN] > (charsonside*2 + 3))
+				V_DrawString(x, y+4, flags, va("%.*s...%s", charsonside, dirmenu[i]+DIR_STRING, dirmenu[i]+DIR_STRING+dirmenu[i][DIR_LEN]-(charsonside+1)));
+#undef charsonside
+			else
+				V_DrawString(x, y+4, flags, dirmenu[i]+DIR_STRING);
+		}
+#undef type
+		y += 16;
+	}
+
+	if (max != (ssize_t)sizedirmenu)
+		V_DrawString(19, y-12 + (skullAnimCounter/5), V_YELLOWMAP, "\x1B");
+
+	y = BASEVIDHEIGHT - currentMenu->y + offs;
+
+	M_DrawTextBox(x - (21 + 5), y, MAXSTRINGLENGTH, 1);
+	if (menusearch[0])
+		V_DrawString(x - 18, y + 8, V_ALLOWLOWERCASE, menusearch+1);
+	else
+		V_DrawString(x - 18, y + 8, V_ALLOWLOWERCASE|V_TRANSLUCENT, "Type to search...");
+	if (skullAnimCounter < 4)
+		V_DrawCharacter(x - 18 + V_StringWidth(menusearch+1, 0), y + 8,
+			'_' | 0x80, false);
+
+	x -= (21 + 5 + 16);
+	V_DrawSmallScaledPatch(x, y + 4, (menusearch[0] ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+4]);
+
+#define CANSAVE (!modifiedgame || savemoddata)
+	x = BASEVIDWIDTH - x - 16;
+	V_DrawSmallScaledPatch(x, y + 4, (CANSAVE ? 0 : V_TRANSLUCENT), addonsp[NUM_EXT+5]);
+
+	if CANSAVE
+		V_DrawSmallScaledPatch(x, y + 4, 0, addonsp[NUM_EXT+3]);
+#undef CANSAVE
+}
+
+#undef offs
+
+static void M_AddonExec(INT32 ch)
+{
+	if (ch != 'y' && ch != KEY_ENTER)
+		return;
+
+	S_StartSound(NULL, sfx_zoom);
+	COM_BufAddText(va("exec %s%s", menupath, dirmenu[dir_on[menudepthleft]]+DIR_STRING));
+}
+
+#define len menusearch[0]
+static boolean M_ChangeStringAddons(INT32 choice)
+{
+	if (shiftdown && choice >= 32 && choice <= 127)
+		choice = shiftxform[choice];
+
+	switch (choice)
+	{
+		case KEY_DEL:
+			if (len)
+			{
+				len = menusearch[1] = 0;
+				return true;
+			}
+			break;
+		case KEY_BACKSPACE:
+			if (len)
+			{
+				menusearch[1+--len] = 0;
+				return true;
+			}
+			break;
+		default:
+			if (choice >= 32 && choice <= 127)
+			{
+				if (len < MAXSTRINGLENGTH - 1)
+				{
+					menusearch[1+len++] = (char)choice;
+					menusearch[1+len] = 0;
+					return true;
+				}
+			}
+			break;
+	}
+	return false;
+}
+#undef len
+
+static void M_HandleAddons(INT32 choice)
+{
+	boolean exitmenu = false; // exit to previous menu
+
+	if (addonsresponselimit)
+		return;
+
+	if (M_ChangeStringAddons(choice))
+	{
+		if (!preparefilemenu(true))
+		{
+			UNEXIST;
+			return;
+		}
+	}
+
+	switch (choice)
+	{
+		case KEY_DOWNARROW:
+			if (dir_on[menudepthleft] < sizedirmenu-1)
+				dir_on[menudepthleft]++;
+			S_StartSound(NULL, sfx_menu1);
+			break;
+		case KEY_UPARROW:
+			if (dir_on[menudepthleft])
+				dir_on[menudepthleft]--;
+			S_StartSound(NULL, sfx_menu1);
+			break;
+		case KEY_PGDN:
+			{
+				UINT8 i;
+				for (i = numaddonsshown; i && (dir_on[menudepthleft] < sizedirmenu-1); i--)
+					dir_on[menudepthleft]++;
+			}
+			S_StartSound(NULL, sfx_menu1);
+			break;
+		case KEY_PGUP:
+			{
+				UINT8 i;
+				for (i = numaddonsshown; i && (dir_on[menudepthleft]); i--)
+					dir_on[menudepthleft]--;
+			}
+			S_StartSound(NULL, sfx_menu1);
+			break;
+		case KEY_ENTER:
+			{
+				boolean refresh = true;
+				if (!dirmenu[dir_on[menudepthleft]])
+					S_StartSound(NULL, sfx_lose);
+				else
+				{
+					switch (dirmenu[dir_on[menudepthleft]][DIR_TYPE])
+					{
+						case EXT_FOLDER:
+							strcpy(&menupath[menupathindex[menudepthleft]],dirmenu[dir_on[menudepthleft]]+DIR_STRING);
+							if (menudepthleft)
+							{
+								menupathindex[--menudepthleft] = strlen(menupath);
+								menupath[menupathindex[menudepthleft]] = 0;
+
+								if (!preparefilemenu(false))
+								{
+									S_StartSound(NULL, sfx_skid);
+									M_StartMessage(va("\x82%s\x80\nThis folder is empty.\n\n(Press a key)\n", M_AddonsHeaderPath()),NULL,MM_NOTHING);
+									menupath[menupathindex[++menudepthleft]] = 0;
+
+									if (!preparefilemenu(true))
+									{
+										UNEXIST;
+										return;
+									}
+								}
+								else
+								{
+									S_StartSound(NULL, sfx_menu1);
+									dir_on[menudepthleft] = 1;
+								}
+								refresh = false;
+							}
+							else
+							{
+								S_StartSound(NULL, sfx_lose);
+								M_StartMessage(va("\x82%s\x80\nThis folder is too deep to navigate to!\n\n(Press a key)\n", M_AddonsHeaderPath()),NULL,MM_NOTHING);
+								menupath[menupathindex[menudepthleft]] = 0;
+							}
+							break;
+						case EXT_UP:
+							S_StartSound(NULL, sfx_menu1);
+							menupath[menupathindex[++menudepthleft]] = 0;
+							if (!preparefilemenu(false))
+							{
+								UNEXIST;
+								return;
+							}
+							break;
+						case EXT_TXT:
+							M_StartMessage(va("\x82%s\x80\nThis file may not be a console script.\nAttempt to run anyways? \n\n(Press 'Y' to confirm)\n", dirmenu[dir_on[menudepthleft]]+DIR_STRING),M_AddonExec,MM_YESNO);
+							break;
+						case EXT_CFG:
+							M_AddonExec(KEY_ENTER);
+							break;
+						case EXT_LUA:
+#ifndef HAVE_BLUA
+							S_StartSound(NULL, sfx_lose);
+							M_StartMessage(va("\x82%s\x80\nThis copy of SRB2 was compiled\nwithout support for .lua files.\n\n(Press a key)\n", dirmenu[dir_on[menudepthleft]]+DIR_STRING),NULL,MM_NOTHING);
+							break;
+#endif
+						// else intentional fallthrough
+						case EXT_SOC:
+						case EXT_WAD:
+							COM_BufAddText(va("addfile %s%s", menupath, dirmenu[dir_on[menudepthleft]]+DIR_STRING));
+							addonsresponselimit = 5;
+							break;
+						default:
+							S_StartSound(NULL, sfx_lose);
+					}
+				}
+				if (refresh)
+					refreshdirmenu |= REFRESHDIR_NORMAL;
+			}
+			break;
+
+		case KEY_ESCAPE:
+			exitmenu = true;
+			break;
+
+		default:
+			break;
+	}
+	if (exitmenu)
+	{
+		for (; sizedirmenu > 0; sizedirmenu--)
+		{
+			Z_Free(dirmenu[sizedirmenu-1]);
+			dirmenu[sizedirmenu-1] = NULL;
+		}
+
+		Z_Free(dirmenu);
+		dirmenu = NULL;
+
+		// secrets disabled by addfile...
+		MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
+
+		if (currentMenu->prevMenu)
+			M_SetupNextMenu(currentMenu->prevMenu);
+		else
+			M_ClearMenus(true);
+	}
+}
+
+static void M_PandorasBox(INT32 choice)
+{
+	(void)choice;
+	CV_StealthSetValue(&cv_dummyrings, max(players[consoleplayer].rings, 0));
+	CV_StealthSetValue(&cv_dummylives, players[consoleplayer].lives);
+	CV_StealthSetValue(&cv_dummycontinues, players[consoleplayer].continues);
+	M_SetupNextMenu(&SR_PandoraDef);
+}
+
+static boolean M_ExitPandorasBox(void)
+{
+	if (cv_dummyrings.value != max(players[consoleplayer].rings, 0))
+		COM_ImmedExecute(va("setrings %d", cv_dummyrings.value));
+	if (cv_dummylives.value != players[consoleplayer].lives)
+		COM_ImmedExecute(va("setlives %d", cv_dummylives.value));
+	if (cv_dummycontinues.value != players[consoleplayer].continues)
+		COM_ImmedExecute(va("setcontinues %d", cv_dummycontinues.value));
+	return true;
+}
+
+static void M_ChangeLevel(INT32 choice)
+{
+	char mapname[6];
+	(void)choice;
+
+	strlcpy(mapname, G_BuildMapName(cv_nextmap.value), sizeof (mapname));
+	strlwr(mapname);
+	mapname[5] = '\0';
+
+	M_ClearMenus(true);
+	COM_BufAddText(va("map %s -gametype \"%s\"\n", mapname, cv_newgametype.string));
+}
+
+static void M_ConfirmSpectate(INT32 choice)
+{
+	(void)choice;
+	// We allow switching to spectator even if team changing is not allowed
+	M_ClearMenus(true);
+	COM_ImmedExecute("changeteam spectator");
+}
+
+static void M_ConfirmEnterGame(INT32 choice)
+{
+	(void)choice;
+	if (!cv_allowteamchange.value)
+	{
+		M_StartMessage(M_GetText("The server is not allowing\nteam changes at this time.\nPress a key.\n"), NULL, MM_NOTHING);
+		return;
+	}
+	M_ClearMenus(true);
+	COM_ImmedExecute("changeteam playing");
+}
+
+static void M_ConfirmTeamScramble(INT32 choice)
+{
+	(void)choice;
+	M_ClearMenus(true);
+
+	switch (cv_dummyscramble.value)
+	{
+		case 0:
+			COM_ImmedExecute("teamscramble 1");
+			break;
+		case 1:
+			COM_ImmedExecute("teamscramble 2");
+			break;
+	}
+}
+
+static void M_ConfirmTeamChange(INT32 choice)
+{
+	(void)choice;
+	if (!cv_allowteamchange.value && cv_dummyteam.value)
+	{
+		M_StartMessage(M_GetText("The server is not allowing\nteam changes at this time.\nPress a key.\n"), NULL, MM_NOTHING);
+		return;
+	}
+
+	M_ClearMenus(true);
+
+	switch (cv_dummyteam.value)
+	{
+		case 0:
+			COM_ImmedExecute("changeteam spectator");
+			break;
+		case 1:
+			COM_ImmedExecute("changeteam red");
+			break;
+		case 2:
+			COM_ImmedExecute("changeteam blue");
+			break;
+	}
+}
+
+static void M_Options(INT32 choice)
+{
+	(void)choice;
+
+	// if the player is not admin or server, disable server options
+	OP_MainMenu[5].status = (Playing() && !(server || adminplayer == consoleplayer)) ? (IT_GRAYEDOUT) : (IT_STRING|IT_CALL);
+
+	// if the player is playing _at all_, disable the erase data options
+	OP_DataOptionsMenu[2].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU);
+
+	OP_MainDef.prevMenu = currentMenu;
+	M_SetupNextMenu(&OP_MainDef);
+}
+
+static void M_RetryResponse(INT32 ch)
+{
+	if (ch != 'y' && ch != KEY_ENTER)
+		return;
+
+	if (!&players[consoleplayer] || netgame || multiplayer) // Should never happen!
+		return;
+
+	M_ClearMenus(true);
+	G_SetRetryFlag();
+}
+
+static void M_Retry(INT32 choice)
+{
+	(void)choice;
+	M_StartMessage(M_GetText("Retry this act from the last starpost?\n\n(Press 'Y' to confirm)\n"),M_RetryResponse,MM_YESNO);
+}
+
+static void M_SelectableClearMenus(INT32 choice)
+{
+	(void)choice;
+	M_ClearMenus(true);
+}
+
+// ======
+// CHEATS
+// ======
+
+static void M_UltimateCheat(INT32 choice)
+{
+	(void)choice;
+	I_Quit();
+}
+
+static void M_GetAllEmeralds(INT32 choice)
+{
+	(void)choice;
+
+	emeralds = ((EMERALD7)*2)-1;
+	M_StartMessage(M_GetText("You now have all 7 emeralds.\nUse them wisely.\nWith great power comes great ring drain.\n"),NULL,MM_NOTHING);
+
+	G_SetGameModified(multiplayer);
+}
+
+static void M_DestroyRobotsResponse(INT32 ch)
+{
+	if (ch != 'y' && ch != KEY_ENTER)
+		return;
+
+	// Destroy all robots
+	P_DestroyRobots();
+
+	G_SetGameModified(multiplayer);
+}
+
+static void M_DestroyRobots(INT32 choice)
+{
+	(void)choice;
+
+	M_StartMessage(M_GetText("Do you want to destroy all\nrobots in the current level?\n\n(Press 'Y' to confirm)\n"),M_DestroyRobotsResponse,MM_YESNO);
+}
+
+static void M_LevelSelectWarp(INT32 choice)
+{
+	boolean fromloadgame = (currentMenu == &SP_LevelSelectDef);
+
+	(void)choice;
+
+	if (W_CheckNumForName(G_BuildMapName(cv_nextmap.value)) == LUMPERROR)
+	{
+//		CONS_Alert(CONS_WARNING, "Internal game map '%s' not found\n", G_BuildMapName(cv_nextmap.value));
+		return;
+	}
+
+	startmap = (INT16)(cv_nextmap.value);
+
+	fromlevelselect = true;
 
 	if (fromloadgame)
 		G_LoadGame((UINT32)cursaveslot, startmap);
@@ -4603,34 +5400,347 @@ static void M_LevelSelectWarp(INT32 choice)
 	}
 }
 
-// ========
-// SKY ROOM
-// ========
-
-UINT8 skyRoomMenuTranslations[MAXUNLOCKABLES];
+// ========
+// SKY ROOM
+// ========
+
+UINT8 skyRoomMenuTranslations[MAXUNLOCKABLES];
+
+static boolean checklist_cangodown; // uuuueeerggghhhh HACK
+
+static void M_HandleChecklist(INT32 choice)
+{
+	INT32 j;
+	switch (choice)
+	{
+		case KEY_DOWNARROW:
+			S_StartSound(NULL, sfx_menu1);
+			if ((check_on != MAXUNLOCKABLES) && checklist_cangodown)
+			{
+				for (j = check_on+1; j < MAXUNLOCKABLES; j++)
+				{
+					if (!unlockables[j].name[0])
+						continue;
+					// if (unlockables[j].nochecklist)
+					//	continue;
+					if (!unlockables[j].conditionset)
+						continue;
+					if (unlockables[j].conditionset > MAXCONDITIONSETS)
+						continue;
+					if (unlockables[j].conditionset == unlockables[check_on].conditionset)
+						continue;
+					break;
+				}
+				if (j != MAXUNLOCKABLES)
+					check_on = j;
+			}
+			return;
+
+		case KEY_UPARROW:
+			S_StartSound(NULL, sfx_menu1);
+			if (check_on)
+			{
+				for (j = check_on-1; j > -1; j--)
+				{
+					if (!unlockables[j].name[0])
+						continue;
+					// if (unlockables[j].nochecklist)
+					//	continue;
+					if (!unlockables[j].conditionset)
+						continue;
+					if (unlockables[j].conditionset > MAXCONDITIONSETS)
+						continue;
+					if (j && unlockables[j].conditionset == unlockables[j-1].conditionset)
+						continue;
+					break;
+				}
+				if (j != -1)
+					check_on = j;
+			}
+			return;
+
+		case KEY_ESCAPE:
+			if (currentMenu->prevMenu)
+				M_SetupNextMenu(currentMenu->prevMenu);
+			else
+				M_ClearMenus(true);
+			return;
+		default:
+			break;
+	}
+}
+
+#define addy(add) { y += add; if ((y - currentMenu->y) > (scrollareaheight*2)) goto finishchecklist; }
 
-#define NUMCHECKLIST 8
 static void M_DrawChecklist(void)
 {
-	INT32 i, j = 0;
+	INT32 i = check_on, j = 0, y = currentMenu->y;
+	UINT32 condnum, previd, maxcond;
+	condition_t *cond;
+
+	// draw title (or big pic)
+	M_DrawMenuTitle();
 
-	for (i = 0; i < MAXUNLOCKABLES; i++)
+	if (check_on)
+		V_DrawString(10, y-(skullAnimCounter/5), V_YELLOWMAP, "\x1A");
+
+	while (i < MAXUNLOCKABLES)
 	{
-		if (unlockables[i].name[0] == 0 || unlockables[i].nochecklist
+		if (unlockables[i].name[0] == 0 //|| unlockables[i].nochecklist
 		|| !unlockables[i].conditionset || unlockables[i].conditionset > MAXCONDITIONSETS)
 			continue;
 
-		V_DrawString(8, 8+(24*j), V_RETURN8, unlockables[i].name);
-		V_DrawString(160, 8+(24*j), V_RETURN8, V_WordWrap(160, 292, 0, unlockables[i].objective));
+		V_DrawString(currentMenu->x, y, ((unlockables[i].unlocked) ? V_GREENMAP : V_TRANSLUCENT), ((unlockables[i].unlocked || !unlockables[i].nochecklist) ? unlockables[i].name : M_CreateSecretMenuOption(unlockables[i].name)));
+
+		for (j = i+1; j < MAXUNLOCKABLES; j++)
+		{
+			if (!(unlockables[j].name[0] == 0 //|| unlockables[j].nochecklist
+			|| !unlockables[j].conditionset || unlockables[j].conditionset > MAXCONDITIONSETS))
+				break;
+		}
+		if ((j != MAXUNLOCKABLES) && (unlockables[i].conditionset == unlockables[j].conditionset))
+			addy(8)
+		else
+		{
+			if ((maxcond = conditionSets[unlockables[i].conditionset-1].numconditions))
+			{
+				cond = conditionSets[unlockables[i].conditionset-1].condition;
+				previd = cond[0].id;
+				addy(2);
+
+				if (unlockables[i].objective[0] != '/')
+				{
+					addy(8);
+					V_DrawString(currentMenu->x, y,
+						V_ALLOWLOWERCASE,
+						va("\x1E %s", unlockables[i].objective));
+				}
+				else
+				{
+					for (condnum = 0; condnum < maxcond; condnum++)
+					{
+						const char *beat = "!";
+
+						if (cond[condnum].id != previd)
+						{
+							addy(8);
+							V_DrawString(currentMenu->x + 4, y, V_YELLOWMAP, "OR");
+						}
+
+						addy(8);
+
+						switch (cond[condnum].type)
+						{
+							case UC_PLAYTIME:
+								{
+									UINT32 hours = G_TicsToHours(cond[condnum].requirement);
+									UINT32 minutes = G_TicsToMinutes(cond[condnum].requirement, false);
+									UINT32 seconds = G_TicsToSeconds(cond[condnum].requirement);
+
+#define getplural(field) ((field == 1) ? "" : "s")
+									if (hours)
+									{
+										if (minutes)
+											beat = va("Play the game for %d hour%s %d minute%s", hours, getplural(hours), minutes, getplural(minutes));
+										else
+											beat = va("Play the game for %d hour%s", hours, getplural(hours));
+									}
+									else
+									{
+										if (minutes && seconds)
+											beat = va("Play the game for %d minute%s %d second%s", minutes, getplural(minutes), seconds, getplural(seconds));
+										else if (minutes)
+											beat = va("Play the game for %d minute%s", minutes, getplural(minutes));
+										else
+											beat = va("Play the game for %d second%s", seconds, getplural(seconds));
+									}
+#undef getplural
+								}
+								break;
+							case UC_MAPVISITED:
+							case UC_MAPBEATEN:
+							case UC_MAPALLEMERALDS:
+							case UC_MAPULTIMATE:
+							case UC_MAPPERFECT:
+								{
+									char *title = G_BuildMapTitle(cond[condnum].requirement);
+
+									if (title)
+									{
+										const char *level = ((M_MapLocked(cond[condnum].requirement) || !((mapheaderinfo[cond[condnum].requirement-1]->menuflags & LF2_NOVISITNEEDED) || mapvisited[cond[condnum].requirement-1])) ? M_CreateSecretMenuOption(title) : title);
+
+										switch (cond[condnum].type)
+										{
+											case UC_MAPVISITED:
+												beat = va("Visit %s", level);
+												break;
+											case UC_MAPALLEMERALDS:
+												beat = va("Beat %s with all emeralds", level);
+												break;
+											case UC_MAPULTIMATE:
+												beat = va("Beat %s in Ultimate mode", level);
+												break;
+											case UC_MAPPERFECT:
+												beat = va("Get all rings in %s", level);
+												break;
+											case UC_MAPBEATEN:
+											default:
+												beat = va("Beat %s", level);
+												break;
+										}
+										Z_Free(title);
+									}
+								}
+								break;
+							case UC_MAPSCORE:
+							case UC_MAPTIME:
+							case UC_MAPRINGS:
+								{
+									char *title = G_BuildMapTitle(cond[condnum].extrainfo1);
+
+									if (title)
+									{
+										const char *level = ((M_MapLocked(cond[condnum].extrainfo1) || !((mapheaderinfo[cond[condnum].extrainfo1-1]->menuflags & LF2_NOVISITNEEDED) || mapvisited[cond[condnum].extrainfo1-1])) ? M_CreateSecretMenuOption(title) : title);
+
+										switch (cond[condnum].type)
+										{
+											case UC_MAPSCORE:
+												beat = va("Get %d points in %s", cond[condnum].requirement, level);
+												break;
+											case UC_MAPTIME:
+												beat = va("Beat %s in %d:%d.%d", level,
+												G_TicsToMinutes(cond[condnum].requirement, true),
+												G_TicsToSeconds(cond[condnum].requirement),
+												G_TicsToCentiseconds(cond[condnum].requirement));
+												break;
+											case UC_MAPRINGS:
+												beat = va("Get %d rings in %s", cond[condnum].requirement, level);
+												break;
+											default:
+												break;
+										}
+										Z_Free(title);
+									}
+								}
+								break;
+							case UC_OVERALLSCORE:
+							case UC_OVERALLTIME:
+							case UC_OVERALLRINGS:
+								{
+									switch (cond[condnum].type)
+									{
+										case UC_OVERALLSCORE:
+											beat = va("Get %d points over all maps", cond[condnum].requirement);
+											break;
+										case UC_OVERALLTIME:
+											beat = va("Get a total time of less than %d:%d.%d",
+											G_TicsToMinutes(cond[condnum].requirement, true),
+											G_TicsToSeconds(cond[condnum].requirement),
+											G_TicsToCentiseconds(cond[condnum].requirement));
+											break;
+										case UC_OVERALLRINGS:
+											beat = va("Get %d rings over all maps", cond[condnum].requirement);
+											break;
+										default:
+											break;
+									}
+								}
+								break;
+							case UC_GAMECLEAR:
+							case UC_ALLEMERALDS:
+								{
+									const char *emeraldtext = ((cond[condnum].type == UC_ALLEMERALDS) ? " with all emeralds" : "");
+									if (cond[condnum].requirement != 1)
+										beat = va("Beat the game %d times%s",
+										cond[condnum].requirement, emeraldtext);
+									else
+										beat = va("Beat the game%s",
+										emeraldtext);
+								}
+								break;
+							case UC_TOTALEMBLEMS:
+								beat = va("Collect %s%d emblems", ((numemblems+numextraemblems == cond[condnum].requirement) ? "all " : ""), cond[condnum].requirement);
+								break;
+							case UC_NIGHTSTIME:
+							case UC_NIGHTSSCORE:
+							case UC_NIGHTSGRADE:
+								{
+									char *title = G_BuildMapTitle(cond[condnum].extrainfo1);
+
+									if (title)
+									{
+										const char *level = ((M_MapLocked(cond[condnum].extrainfo1) || !((mapheaderinfo[cond[condnum].extrainfo1-1]->menuflags & LF2_NOVISITNEEDED) || mapvisited[cond[condnum].extrainfo1-1])) ? M_CreateSecretMenuOption(title) : title);
+
+										switch (cond[condnum].type)
+										{
+											case UC_NIGHTSSCORE:
+												if (cond[condnum].extrainfo2)
+													beat = va("Get %d points in %s, mare %d", cond[condnum].requirement, level, cond[condnum].extrainfo2);
+												else
+													beat = va("Get %d points in %s", cond[condnum].requirement, level);
+												break;
+											case UC_NIGHTSTIME:
+												if (cond[condnum].extrainfo2)
+													beat = va("Beat %s, mare %d in %d:%d.%d", level, cond[condnum].extrainfo2,
+													G_TicsToMinutes(cond[condnum].requirement, true),
+													G_TicsToSeconds(cond[condnum].requirement),
+													G_TicsToCentiseconds(cond[condnum].requirement));
+												else
+													beat = va("Beat %s in %d:%d.%d",
+													level,
+													G_TicsToMinutes(cond[condnum].requirement, true),
+													G_TicsToSeconds(cond[condnum].requirement),
+													G_TicsToCentiseconds(cond[condnum].requirement));
+												break;
+											case UC_NIGHTSGRADE:
+												{
+													char grade = ('F' - (char)cond[condnum].requirement);
+													if (grade < 'A')
+														grade = 'A';
+													if (cond[condnum].extrainfo2)
+														beat = va("Get grade %c in %s, mare %d", grade, level, cond[condnum].extrainfo2);
+													else
+														beat = va("Get grade %c in %s", grade, level);
+												}
+											break;
+											default:
+												break;
+										}
+										Z_Free(title);
+									}
+								}
+								break;
+							case UC_TRIGGER:
+							case UC_EMBLEM:
+							case UC_CONDITIONSET:
+							default:
+								y -= 8; // Nope, not showing this.
+								break;
+						}
+						if (beat[0] != '!')
+						{
+							V_DrawString(currentMenu->x, y, 0, "\x1E");
+							V_DrawString(currentMenu->x+12, y, V_ALLOWLOWERCASE, beat);
+						}
+						previd = cond[condnum].id;
+					}
+				}
+			}
+			addy(12);
+		}
+		i = j;
+
+		/*V_DrawString(160, 8+(24*j), V_RETURN8, V_WordWrap(160, 292, 0, unlockables[i].objective));
 
 		if (unlockables[i].unlocked)
 			V_DrawString(308, 8+(24*j), V_YELLOWMAP, "Y");
 		else
-			V_DrawString(308, 8+(24*j), V_YELLOWMAP, "N");
-
-		if (++j >= NUMCHECKLIST)
-			break;
+			V_DrawString(308, 8+(24*j), V_YELLOWMAP, "N");*/
 	}
+
+finishchecklist:
+	if ((checklist_cangodown = ((y - currentMenu->y) > (scrollareaheight*2)))) // haaaaaaacks.
+		V_DrawString(10, currentMenu->y+(scrollareaheight*2)+(skullAnimCounter/5), V_YELLOWMAP, "\x1B");
 }
 
 #define NUMHINTS 5
@@ -4701,6 +5811,13 @@ static void M_DrawSkyRoom(void)
 		return;
 
 	V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + y, V_YELLOWMAP, cv_soundtest.string);
+	if (i == itemOn)
+	{
+		V_DrawCharacter(BASEVIDWIDTH - currentMenu->x - 10 - V_StringWidth(cv_soundtest.string, 0) - (skullAnimCounter/5), currentMenu->y + y,
+			'\x1C' | V_YELLOWMAP, false);
+		V_DrawCharacter(BASEVIDWIDTH - currentMenu->x + 2 + (skullAnimCounter/5), currentMenu->y + y,
+			'\x1D' | V_YELLOWMAP, false);
+	}
 	if (cv_soundtest.value)
 		V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + y + 8, V_YELLOWMAP, S_sfx[cv_soundtest.value].name);
 }
@@ -5603,11 +6720,15 @@ static void M_ChoosePlayer(INT32 choice)
 	if (startmap != spstage_start)
 		cursaveslot = -1;
 
-	lastmapsaved = 0;
+	//lastmapsaved = 0;
 	gamecomplete = false;
 
 	G_DeferedInitNew(ultmode, G_BuildMapName(startmap), (UINT8)skinnum, false, fromlevelselect);
 	COM_BufAddText("dummyconsvar 1\n"); // G_DeferedInitNew doesn't do this
+
+	if (levelselect.rows)
+		Z_Free(levelselect.rows);
+	levelselect.rows = NULL;
 }
 
 // ===============
@@ -5640,23 +6761,24 @@ static void M_Statistics(INT32 choice)
 		statsMapList[j++] = i;
 	}
 	statsMapList[j] = -1;
-	statsMax = j - 13 + numextraemblems;
+	statsMax = j - 11 + numextraemblems;
 	statsLocation = 0;
 
 	if (statsMax < 0)
 		statsMax = 0;
 
-	M_SetupNextMenu(&SP_GameStatsDef);
+	M_SetupNextMenu(&SP_LevelStatsDef);
 }
 
 static void M_DrawStatsMaps(int location)
 {
-	INT32 y = 76, i = -1;
+	INT32 y = 80, i = -1;
 	INT16 mnum;
 	extraemblem_t *exemblem;
+	boolean dotopname = true, dobottomarrow = (location < statsMax);
 
-	V_DrawString(20,  y-12, 0, "LEVEL NAME");
-	V_DrawString(248, y-12, 0, "EMBLEMS");
+	if (location)
+		V_DrawString(10, y-(skullAnimCounter/5), V_YELLOWMAP, "\x1A");
 
 	while (statsMapList[++i] != -1)
 	{
@@ -5665,6 +6787,13 @@ static void M_DrawStatsMaps(int location)
 			--location;
 			continue;
 		}
+		else if (dotopname)
+		{
+			V_DrawString(20,  y, V_GREENMAP, "LEVEL NAME");
+			V_DrawString(248, y, V_GREENMAP, "EMBLEMS");
+			y += 8;
+			dotopname = false;
+		}
 
 		mnum = statsMapList[i];
 		M_DrawMapEmblems(mnum+1, 292, y);
@@ -5677,21 +6806,36 @@ static void M_DrawStatsMaps(int location)
 		y += 8;
 
 		if (y >= BASEVIDHEIGHT-8)
-			return;
+			goto bottomarrow;
+	}
+	if (dotopname && !location)
+	{
+		V_DrawString(20,  y, V_GREENMAP, "LEVEL NAME");
+		V_DrawString(248, y, V_GREENMAP, "EMBLEMS");
+		y += 8;
 	}
+	else if (location)
+		--location;
 
 	// Extra Emblems
 	for (i = -2; i < numextraemblems; ++i)
 	{
+		if (i == -1)
+		{
+			V_DrawString(20, y, V_GREENMAP, "EXTRA EMBLEMS");
+			if (location)
+			{
+				y += 8;
+				location++;
+			}
+		}
 		if (location)
 		{
 			--location;
 			continue;
 		}
 
-		if (i == -1)
-			V_DrawString(20, y, V_GREENMAP, "EXTRA EMBLEMS");
-		else if (i >= 0)
+		if (i >= 0)
 		{
 			exemblem = &extraemblems[i];
 
@@ -5707,70 +6851,14 @@ static void M_DrawStatsMaps(int location)
 		y += 8;
 
 		if (y >= BASEVIDHEIGHT-8)
-			return;
+			goto bottomarrow;
 	}
+bottomarrow:
+	if (dobottomarrow)
+		V_DrawString(10, y-8 + (skullAnimCounter/5), V_YELLOWMAP, "\x1B");
 }
 
 static void M_DrawLevelStats(void)
-{
-	M_DrawMenuTitle();
-	V_DrawCenteredString(BASEVIDWIDTH/2, 24, V_YELLOWMAP, "PAGE 2 OF 2");
-
-	V_DrawString(72, 48, 0, va("x %d/%d", M_CountEmblems(), numemblems+numextraemblems));
-	V_DrawScaledPatch(40, 48-4, 0, W_CachePatchName("EMBLICON", PU_STATIC));
-
-	M_DrawStatsMaps(statsLocation);
-}
-
-// Handle statistics.
-static void M_HandleLevelStats(INT32 choice)
-{
-	boolean exitmenu = false; // exit to previous menu
-
-	switch (choice)
-	{
-		case KEY_DOWNARROW:
-			S_StartSound(NULL, sfx_menu1);
-			if (statsLocation < statsMax)
-				++statsLocation;
-			break;
-
-		case KEY_UPARROW:
-			S_StartSound(NULL, sfx_menu1);
-			if (statsLocation)
-				--statsLocation;
-			break;
-
-		case KEY_RIGHTARROW:
-			S_StartSound(NULL, sfx_menu1);
-			statsLocation += (statsLocation+15 >= statsMax) ? statsMax-statsLocation : 15;
-			break;
-
-		case KEY_LEFTARROW:
-			S_StartSound(NULL, sfx_menu1);
-			statsLocation -= (statsLocation < 15) ? statsLocation : 15;
-			break;
-
-		case KEY_ESCAPE:
-			exitmenu = true;
-			break;
-
-		case KEY_ENTER:
-			S_StartSound(NULL, sfx_menu1);
-			M_SetupNextMenu(&SP_GameStatsDef);
-			break;
-	}
-	if (exitmenu)
-	{
-		if (currentMenu->prevMenu)
-			M_SetupNextMenu(currentMenu->prevMenu);
-		else
-			M_ClearMenus(true);
-	}
-}
-
-// Handle GAME statistics.
-static void M_DrawGameStats(void)
 {
 	char beststr[40];
 
@@ -5779,81 +6867,106 @@ static void M_DrawGameStats(void)
 	UINT32 bestrings = 0;
 
 	INT32 i;
-	INT32 mapsunfinished[3] = {0, 0, 0};
+	INT32 mapsunfinished = 0;
+	boolean bestunfinished[3] = {false, false, false};
 
 	M_DrawMenuTitle();
-	V_DrawCenteredString(BASEVIDWIDTH/2, 24, V_YELLOWMAP, "PAGE 1 OF 2");
 
-	V_DrawString(32, 60, V_YELLOWMAP, "Total Play Time:");
-	V_DrawRightAlignedString(BASEVIDWIDTH-32, 70, 0, va("%i hours, %i minutes, %i seconds",
+	V_DrawString(20, 24, V_YELLOWMAP, "Total Play Time:");
+	V_DrawCenteredString(BASEVIDWIDTH/2, 32, 0, va("%i hours, %i minutes, %i seconds",
 	                         G_TicsToHours(totalplaytime),
 	                         G_TicsToMinutes(totalplaytime, false),
 	                         G_TicsToSeconds(totalplaytime)));
 
 	for (i = 0; i < NUMMAPS; i++)
 	{
+		boolean mapunfinished = false;
+
 		if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK))
 			continue;
 
 		if (!mainrecords[i])
 		{
-			mapsunfinished[0]++;
-			mapsunfinished[1]++;
-			mapsunfinished[2]++;
+			mapsunfinished++;
+			bestunfinished[0] = bestunfinished[1] = bestunfinished[2] = true;
 			continue;
 		}
 
 		if (mainrecords[i]->score > 0)
 			bestscore += mainrecords[i]->score;
 		else
-			mapsunfinished[0]++;
+			mapunfinished = bestunfinished[0] = true;
 
 		if (mainrecords[i]->time > 0)
 			besttime += mainrecords[i]->time;
 		else
-			mapsunfinished[1]++;
+			mapunfinished = bestunfinished[1] = true;
 
 		if (mainrecords[i]->rings > 0)
 			bestrings += mainrecords[i]->rings;
 		else
-			mapsunfinished[2]++;
+			mapunfinished = bestunfinished[2] = true;
 
+		if (mapunfinished)
+			mapsunfinished++;
 	}
 
-	V_DrawCenteredString(BASEVIDWIDTH/2, 90, 0, "* COMBINED RECORDS *");
+	V_DrawString(20, 48, 0, "Combined records:");
+
+	if (mapsunfinished)
+		V_DrawString(20, 56, V_REDMAP, va("(%d unfinished)", mapsunfinished));
+	else
+		V_DrawString(20, 56, V_GREENMAP, "(complete)");
+
+	V_DrawString(36, 64, 0, va("x %d/%d", M_CountEmblems(), numemblems+numextraemblems));
+	V_DrawSmallScaledPatch(20, 64, 0, W_CachePatchName("EMBLICON", PU_STATIC));
 
 	sprintf(beststr, "%u", bestscore);
-	V_DrawString(32, 100, V_YELLOWMAP, "SCORE:");
-	V_DrawRightAlignedString(BASEVIDWIDTH-32, 100, 0, beststr);
-	if (mapsunfinished[0])
-		V_DrawRightAlignedString(BASEVIDWIDTH-32, 108, V_REDMAP, va("(%d unfinished)", mapsunfinished[0]));
+	V_DrawString(BASEVIDWIDTH/2, 48, V_YELLOWMAP, "SCORE:");
+	V_DrawRightAlignedString(BASEVIDWIDTH-16, 48, (bestunfinished[0] ? V_REDMAP : 0), beststr);
 
 	sprintf(beststr, "%i:%02i:%02i.%02i", G_TicsToHours(besttime), G_TicsToMinutes(besttime, false), G_TicsToSeconds(besttime), G_TicsToCentiseconds(besttime));
-	V_DrawString(32, 120, V_YELLOWMAP, "TIME:");
-	V_DrawRightAlignedString(BASEVIDWIDTH-32, 120, 0, beststr);
-	if (mapsunfinished[1])
-		V_DrawRightAlignedString(BASEVIDWIDTH-32, 128, V_REDMAP, va("(%d unfinished)", mapsunfinished[1]));
+	V_DrawString(BASEVIDWIDTH/2, 56, V_YELLOWMAP, "TIME:");
+	V_DrawRightAlignedString(BASEVIDWIDTH-16, 56, (bestunfinished[1] ? V_REDMAP : 0), beststr);
 
 	sprintf(beststr, "%u", bestrings);
-	V_DrawString(32, 140, V_YELLOWMAP, "RINGS:");
-	V_DrawRightAlignedString(BASEVIDWIDTH-32, 140, 0, beststr);
-	if (mapsunfinished[2])
-		V_DrawRightAlignedString(BASEVIDWIDTH-32, 148, V_REDMAP, va("(%d unfinished)", mapsunfinished[2]));
+	V_DrawString(BASEVIDWIDTH/2, 64, V_YELLOWMAP, "RINGS:");
+	V_DrawRightAlignedString(BASEVIDWIDTH-16, 64, (bestunfinished[2] ? V_REDMAP : 0), beststr);
+
+	M_DrawStatsMaps(statsLocation);
 }
 
-static void M_HandleGameStats(INT32 choice)
+// Handle statistics.
+static void M_HandleLevelStats(INT32 choice)
 {
 	boolean exitmenu = false; // exit to previous menu
 
 	switch (choice)
 	{
-		case KEY_ESCAPE:
-			exitmenu = true;
+		case KEY_DOWNARROW:
+			S_StartSound(NULL, sfx_menu1);
+			if (statsLocation < statsMax)
+				++statsLocation;
 			break;
 
-		case KEY_ENTER:
+		case KEY_UPARROW:
 			S_StartSound(NULL, sfx_menu1);
-			M_SetupNextMenu(&SP_LevelStatsDef);
+			if (statsLocation)
+				--statsLocation;
+			break;
+
+		case KEY_PGDN:
+			S_StartSound(NULL, sfx_menu1);
+			statsLocation += (statsLocation+13 >= statsMax) ? statsMax-statsLocation : 13;
+			break;
+
+		case KEY_PGUP:
+			S_StartSound(NULL, sfx_menu1);
+			statsLocation -= (statsLocation < 13) ? statsLocation : 13;
+			break;
+
+		case KEY_ESCAPE:
+			exitmenu = true;
 			break;
 	}
 	if (exitmenu)
@@ -5911,6 +7024,13 @@ void M_DrawTimeAttackMenu(void)
 
 			// Should see nothing but strings
 			V_DrawString(BASEVIDWIDTH - x - soffset - V_StringWidth(cv->string, 0), y, V_YELLOWMAP, cv->string);
+			if (i == itemOn)
+			{
+				V_DrawCharacter(BASEVIDWIDTH - x - soffset - 10 - V_StringWidth(cv->string, 0) - (skullAnimCounter/5), y,
+					'\x1C' | V_YELLOWMAP, false);
+				V_DrawCharacter(BASEVIDWIDTH - x - soffset + 2 + (skullAnimCounter/5), y,
+					'\x1D' | V_YELLOWMAP, false);
+			}
 		}
 	}
 
@@ -5937,7 +7057,7 @@ void M_DrawTimeAttackMenu(void)
 		lumpnum_t lumpnum;
 		char beststr[40];
 
-		M_DrawLevelPlatterHeader(32-lsheadingheight/2, cv_nextmap.string, true);
+		M_DrawLevelPlatterHeader(32-lsheadingheight/2, cv_nextmap.string, true, false);
 
 		//  A 160x100 image of the level as entry MAPxxP
 		lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
@@ -6093,6 +7213,13 @@ void M_DrawNightsAttackMenu(void)
 
 			// Should see nothing but strings
 			V_DrawString(BASEVIDWIDTH - x - soffset - V_StringWidth(cv->string, 0), y, V_YELLOWMAP, cv->string);
+			if (i == itemOn)
+			{
+				V_DrawCharacter(BASEVIDWIDTH - x - soffset - 10 - V_StringWidth(cv->string, 0) - (skullAnimCounter/5), y,
+					'\x1C' | V_YELLOWMAP, false);
+				V_DrawCharacter(BASEVIDWIDTH - x - soffset + 2 + (skullAnimCounter/5), y,
+					'\x1D' | V_YELLOWMAP, false);
+			}
 		}
 	}
 
@@ -6114,7 +7241,7 @@ void M_DrawNightsAttackMenu(void)
 		UINT32 bestscore	= G_GetBestNightsScore(cv_nextmap.value, cv_dummymares.value);
 		tic_t besttime		= G_GetBestNightsTime(cv_nextmap.value, cv_dummymares.value);
 
-		M_DrawLevelPlatterHeader(32-lsheadingheight/2, cv_nextmap.string, true);
+		M_DrawLevelPlatterHeader(32-lsheadingheight/2, cv_nextmap.string, true, false);
 
 		//  A 160x100 image of the level as entry MAPxxP
 		lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
@@ -6870,7 +7997,7 @@ static void M_DrawServerMenu(void)
 	// Room name
 	if (currentMenu == &MP_ServerDef)
 	{
-		M_DrawLevelPlatterHeader(currentMenu->y - lsheadingheight/2, "Server settings", true);
+		M_DrawLevelPlatterHeader(currentMenu->y - lsheadingheight/2, "Server settings", true, false);
 		if (ms_RoomId < 0)
 			V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ServerMenu[mp_server_room].alphaKey,
 			                         V_YELLOWMAP, (itemOn == mp_server_room) ? "<Select to change>" : "<Offline Mode>");
@@ -6882,13 +8009,18 @@ static void M_DrawServerMenu(void)
 
 	if (cv_nextmap.value)
 	{
+#ifndef NONET
+#define imgheight MP_ServerMenu[mp_server_levelgt].alphaKey
+#else
+#define imgheight 100
+#endif
 		patch_t *PictureOfLevel;
 		lumpnum_t lumpnum;
 		char headerstr[40];
 
 		sprintf(headerstr, "%s - %s", cv_newgametype.string, cv_nextmap.string);
 
-		M_DrawLevelPlatterHeader(currentMenu->y + MP_ServerMenu[mp_server_levelgt].alphaKey - 10 - lsheadingheight/2, (const char *)headerstr, true);
+		M_DrawLevelPlatterHeader(currentMenu->y + imgheight - 10 - lsheadingheight/2, (const char *)headerstr, true, false);
 
 		//  A 160x100 image of the level as entry MAPxxP
 		lumpnum = W_CheckNumForName(va("%sP", G_BuildMapName(cv_nextmap.value)));
@@ -6898,7 +8030,7 @@ static void M_DrawServerMenu(void)
 		else
 			PictureOfLevel = W_CachePatchName("BLANKLVL", PU_CACHE);
 
-		V_DrawSmallScaledPatch(319 - (currentMenu->x + (SHORT(PictureOfLevel->width)/2)), currentMenu->y + MP_ServerMenu[mp_server_levelgt].alphaKey, 0, PictureOfLevel);
+		V_DrawSmallScaledPatch(319 - (currentMenu->x + (SHORT(PictureOfLevel->width)/2)), currentMenu->y + imgheight, 0, PictureOfLevel);
 	}
 }
 
@@ -6918,7 +8050,7 @@ static void M_GameTypeChange(INT32 choice)
 void M_DrawGameTypeMenu(void)
 {
 	M_DrawGenericMenu();
-	M_DrawLevelPlatterHeader(currentMenu->y - lsheadingheight, "Select Gametype", true);
+	M_DrawLevelPlatterHeader(currentMenu->y - lsheadingheight, "Select Gametype", true, false);
 
 	if (!char_notes)
 		char_notes = V_WordWrap(0, (160 - 30) - 8, V_ALLOWLOWERCASE, gametypedesc[itemOn].notes);
@@ -6958,6 +8090,29 @@ static void M_ServerOptions(INT32 choice)
 {
 	(void)choice;
 
+#ifndef NONET
+	if ((splitscreen && !netgame) || currentMenu == &MP_SplitServerDef)
+	{
+		OP_ServerOptionsMenu[ 1].status = IT_GRAYEDOUT; // Server name
+		OP_ServerOptionsMenu[ 2].status = IT_GRAYEDOUT; // Max players
+		OP_ServerOptionsMenu[ 3].status = IT_GRAYEDOUT; // Allow add-on downloading
+		OP_ServerOptionsMenu[ 4].status = IT_GRAYEDOUT; // Allow players to join
+		OP_ServerOptionsMenu[34].status = IT_GRAYEDOUT; // Master server
+		OP_ServerOptionsMenu[35].status = IT_GRAYEDOUT; // Attempts to resynchronise
+	}
+	else
+	{
+		OP_ServerOptionsMenu[ 1].status = IT_STRING | IT_CVAR | IT_CV_STRING;
+		OP_ServerOptionsMenu[ 2].status = IT_STRING | IT_CVAR;
+		OP_ServerOptionsMenu[ 3].status = IT_STRING | IT_CVAR;
+		OP_ServerOptionsMenu[ 4].status = IT_STRING | IT_CVAR;
+		OP_ServerOptionsMenu[34].status = (netgame
+			? IT_GRAYEDOUT
+			: (IT_STRING | IT_CVAR | IT_CV_STRING));
+		OP_ServerOptionsMenu[35].status = IT_STRING | IT_CVAR;
+	}
+#endif
+
 	OP_ServerOptionsDef.prevMenu = currentMenu;
 	M_SetupNextMenu(&OP_ServerOptionsDef);
 }
@@ -6979,30 +8134,40 @@ static void M_StartServerMenu(INT32 choice)
 
 static char setupm_ip[16];
 
-// Connect using IP address Tails 11-19-2002
-static void M_ConnectIPMenu(INT32 choice)
-{
-	(void)choice;
-	// modified game check: no longer handled
-	// we don't request a restart unless the filelist differs
-
-	M_SetupNextMenu(&MP_ConnectIPDef);
-}
-
 // Draw the funky Connect IP menu. Tails 11-19-2002
 // So much work for such a little thing!
-static void M_DrawConnectIPMenu(void)
+static void M_DrawMPMainMenu(void)
 {
+	INT32 x = currentMenu->x;
+	INT32 y = currentMenu->y;
+
 	// use generic drawer for cursor, items and title
 	M_DrawGenericMenu();
 
+#if MAXPLAYERS == 32
+	V_DrawRightAlignedString(BASEVIDWIDTH-x, y+12,
+		((itemOn == 1) ? V_YELLOWMAP : 0), "(2-32 players)");
+#else
+Update the maxplayers label...
+#endif
+
+	V_DrawRightAlignedString(BASEVIDWIDTH-x, y+22,
+		((itemOn == 2) ? V_YELLOWMAP : 0), "(2 players)");
+
+	V_DrawRightAlignedString(BASEVIDWIDTH-x, y+116,
+		((itemOn == 8) ? V_YELLOWMAP : 0), "(splitscreen)");
+
+	y += 62;
+
+	V_DrawFill(x+5, y+4+5, /*16*8 + 6,*/ BASEVIDWIDTH - 2*(x+5), 8+6, 159);
+
 	// draw name string
-	V_DrawString(128,40, V_MONOSPACE, setupm_ip);
+	V_DrawString(x+8,y+12, V_MONOSPACE, setupm_ip);
 
 	// draw text cursor for name
-	if (itemOn == 0 &&
-	    skullAnimCounter < 4)   //blink cursor
-		V_DrawCharacter(128+V_StringWidth(setupm_ip, V_MONOSPACE),40,'_',false);
+	if (itemOn == 5 //0
+	    && skullAnimCounter < 4)   //blink cursor
+		V_DrawCharacter(x+8+V_StringWidth(setupm_ip, V_MONOSPACE),y+12,'_',false);
 }
 
 // Tails 11-19-2002
@@ -7023,11 +8188,21 @@ static void M_ConnectIP(INT32 choice)
 // Tails 11-19-2002
 static void M_HandleConnectIP(INT32 choice)
 {
-	size_t   l;
-	boolean  exitmenu = false;  // exit to previous menu and send name change
+	size_t l;
+	boolean exitmenu = false;  // exit to previous menu and send name change
 
 	switch (choice)
 	{
+		case KEY_DOWNARROW:
+			M_NextOpt();
+			S_StartSound(NULL,sfx_menu1); // Tails
+			break;
+
+		case KEY_UPARROW:
+			M_PrevOpt();
+			S_StartSound(NULL,sfx_menu1); // Tails
+			break;
+
 		case KEY_ENTER:
 			S_StartSound(NULL,sfx_menu1); // Tails
 			M_ClearMenus(true);
@@ -7039,29 +8214,41 @@ static void M_HandleConnectIP(INT32 choice)
 			break;
 
 		case KEY_BACKSPACE:
-			if ((l = strlen(setupm_ip))!=0 && itemOn == 0)
+			if ((l = strlen(setupm_ip)) != 0)
 			{
 				S_StartSound(NULL,sfx_menu1); // Tails
-				setupm_ip[l-1] =0;
+				setupm_ip[l-1] = 0;
+			}
+			break;
+
+		case KEY_DEL:
+			if (setupm_ip[0])
+			{
+				S_StartSound(NULL,sfx_menu1); // Tails
+				setupm_ip[0] = 0;
 			}
 			break;
 
 		default:
 			l = strlen(setupm_ip);
-			if (l < 16-1 && (choice == 46 || (choice >= 48 && choice <= 57))) // Rudimentary number and period enforcing
+			if (l >= 16-1)
+				break;
+
+			if (choice == 46 || (choice >= 48 && choice <= 57)) // Rudimentary number and period enforcing
 			{
 				S_StartSound(NULL,sfx_menu1); // Tails
-				setupm_ip[l] =(char)choice;
-				setupm_ip[l+1] =0;
+				setupm_ip[l] = (char)choice;
+				setupm_ip[l+1] = 0;
 			}
-			else if (l < 16-1 && choice >= 199 && choice <= 211 && choice != 202 && choice != 206) //numpad too!
+			else if (choice >= 199 && choice <= 211 && choice != 202 && choice != 206) //numpad too!
 			{
 				XBOXSTATIC char keypad_translation[] = {'7','8','9','-','4','5','6','+','1','2','3','0','.'};
 				choice = keypad_translation[choice - 199];
 				S_StartSound(NULL,sfx_menu1); // Tails
-				setupm_ip[l] =(char)choice;
-				setupm_ip[l+1] =0;
+				setupm_ip[l] = (char)choice;
+				setupm_ip[l+1] = 0;
 			}
+
 			break;
 	}
 
@@ -7080,11 +8267,9 @@ static void M_HandleConnectIP(INT32 choice)
 // ========================
 // Tails 03-02-2002
 
-#define PLBOXW    8
-#define PLBOXH    9
-
 static UINT8      multi_tics;
 static UINT8      multi_frame;
+static UINT8      multi_spr2;
 
 // this is set before entering the MultiPlayer setup menu,
 // for either player 1 or 2
@@ -7093,38 +8278,62 @@ static player_t  *setupm_player;
 static consvar_t *setupm_cvskin;
 static consvar_t *setupm_cvcolor;
 static consvar_t *setupm_cvname;
+static consvar_t *setupm_cvdefaultskin;
+static consvar_t *setupm_cvdefaultcolor;
+static consvar_t *setupm_cvdefaultname;
 static INT32      setupm_fakeskin;
 static INT32      setupm_fakecolor;
 
 static void M_DrawSetupMultiPlayerMenu(void)
 {
-	INT32 mx, my, flags = 0;
+	INT32 x, y, cursory = 0, flags = 0;
 	spritedef_t *sprdef;
 	spriteframe_t *sprframe;
 	patch_t *patch;
+	UINT8 *colormap;
 
-	mx = MP_PlayerSetupDef.x;
-	my = MP_PlayerSetupDef.y;
+	x = MP_PlayerSetupDef.x;
+	y = MP_PlayerSetupDef.y;
 
 	// use generic drawer for cursor, items and title
-	M_DrawGenericMenu();
+	//M_DrawGenericMenu();
+
+	// draw title (or big pic)
+	M_DrawMenuTitle();
+
+	M_DrawLevelPlatterHeader(y - (lsheadingheight - 12), "Name", true, false);
+	if (itemOn == 0)
+		cursory = y;
+	y += 11;
 
 	// draw name string
-	M_DrawTextBox(mx + 90, my - 8, MAXPLAYERNAME, 1);
-	V_DrawString(mx + 98, my, V_ALLOWLOWERCASE, setupm_name);
+	V_DrawFill(x, y, 282/*(MAXPLAYERNAME+1)*8+6*/, 14, 159);
+	V_DrawString(x + 8, y + 3, V_ALLOWLOWERCASE, setupm_name);
+	if (skullAnimCounter < 4 && itemOn == 0)
+		V_DrawCharacter(x + 8 + V_StringWidth(setupm_name, V_ALLOWLOWERCASE), y + 3,
+			'_' | 0x80, false);
+
+	y += 20;
+
+	M_DrawLevelPlatterHeader(y - (lsheadingheight - 12), "Character", true, false);
+	if (itemOn == 1)
+		cursory = y;
 
 	// draw skin string
-	V_DrawString(mx + 90, my + 96,
-	             ((MP_PlayerSetupMenu[2].status & IT_TYPE) == IT_SPACE ? V_TRANSLUCENT : 0)|V_YELLOWMAP|V_ALLOWLOWERCASE,
+	V_DrawRightAlignedString(BASEVIDWIDTH - x, y,
+	             ((MP_PlayerSetupMenu[2].status & IT_TYPE) == IT_SPACE ? V_TRANSLUCENT : 0)|(itemOn == 1 ? V_YELLOWMAP : 0)|V_ALLOWLOWERCASE,
 	             skins[setupm_fakeskin].realname);
 
-	// draw the name of the color you have chosen
-	// Just so people don't go thinking that "Default" is Green.
-	V_DrawString(208, 72, V_YELLOWMAP|V_ALLOWLOWERCASE, Color_Names[setupm_fakecolor]);
+	if (itemOn == 1 && (MP_PlayerSetupMenu[2].status & IT_TYPE) != IT_SPACE)
+	{
+		V_DrawCharacter(BASEVIDWIDTH - x - 10 - V_StringWidth(skins[setupm_fakeskin].realname, V_ALLOWLOWERCASE) - (skullAnimCounter/5), y,
+			'\x1C' | V_YELLOWMAP, false);
+		V_DrawCharacter(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y,
+			'\x1D' | V_YELLOWMAP, false);
+	}
 
-	// draw text cursor for name
-	if (!itemOn && skullAnimCounter < 4) // blink cursor
-		V_DrawCharacter(mx + 98 + V_StringWidth(setupm_name, 0), my, '_',false);
+	x = BASEVIDWIDTH/2;
+	y += 11;
 
 	// anim the player in the box
 	if (--multi_tics <= 0)
@@ -7133,14 +8342,18 @@ static void M_DrawSetupMultiPlayerMenu(void)
 		multi_tics = 4;
 	}
 
-	// skin 0 is default player sprite
-	if (R_SkinAvailable(skins[setupm_fakeskin].name) != -1)
-		sprdef = &skins[R_SkinAvailable(skins[setupm_fakeskin].name)].sprites[SPR2_WALK];
-	else
-		sprdef = &skins[0].sprites[SPR2_WALK];
+#define charw 74
 
-	if (!sprdef->numframes) // No frames ??
-		return; // Can't render!
+	// draw box around character
+	V_DrawFill(x-(charw/2), y, charw, 84, 159);
+
+	sprdef = &skins[setupm_fakeskin].sprites[multi_spr2];
+
+	if (!setupm_fakecolor || !sprdef->numframes) // should never happen but hey, who knows
+		goto faildraw;
+
+	// ok, draw player sprite for sure now
+	colormap = R_GetTranslationColormap(setupm_fakeskin, setupm_fakecolor, 0);
 
 	if (multi_frame >= sprdef->numframes)
 		multi_frame = 0;
@@ -7150,38 +8363,96 @@ static void M_DrawSetupMultiPlayerMenu(void)
 	if (sprframe->flip & 1) // Only for first sprite
 		flags |= V_FLIP; // This sprite is left/right flipped!
 
-	// draw box around guy
-	M_DrawTextBox(mx + 90, my + 8, PLBOXW, PLBOXH);
+#define chary (y+64)
+
+	V_DrawFixedPatch(
+		x<<FRACBITS,
+		chary<<FRACBITS,
+		FixedDiv(skins[setupm_fakeskin].highresscale, skins[setupm_fakeskin].shieldscale),
+		flags, patch, colormap);
+
+	Z_Free(colormap);
+	goto colordraw;
+
+faildraw:
+	sprdef = &sprites[SPR_UNKN];
+	if (!sprdef->numframes) // No frames ??
+		return; // Can't render!
+
+	sprframe = &sprdef->spriteframes[0];
+	patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+	if (sprframe->flip & 1) // Only for first sprite
+		flags |= V_FLIP; // This sprite is left/right flipped!
+
+	V_DrawScaledPatch(x, chary, flags, patch);
+
+#undef chary
+
+colordraw:
+	x = MP_PlayerSetupDef.x;
+	y += 75;
+
+	M_DrawLevelPlatterHeader(y - (lsheadingheight - 12), "Color", true, false);
+	if (itemOn == 2)
+		cursory = y;
+
+	// draw color string
+	V_DrawRightAlignedString(BASEVIDWIDTH - x, y,
+	             (itemOn == 2 ? V_YELLOWMAP : 0)|V_ALLOWLOWERCASE,
+	             Color_Names[setupm_fakecolor]);
 
-	// draw player sprite
-	if (!setupm_fakecolor) // should never happen but hey, who knows
+	if (itemOn == 2 && (MP_PlayerSetupMenu[2].status & IT_TYPE) != IT_SPACE)
 	{
-		if (skins[setupm_fakeskin].flags & SF_HIRES)
-		{
-			V_DrawSciencePatch((mx+98+(PLBOXW*8/2))<<FRACBITS,
-						(my+16+(PLBOXH*8)-12)<<FRACBITS,
-						flags, patch,
-						skins[setupm_fakeskin].highresscale);
-		}
-		else
-			V_DrawScaledPatch(mx + 98 + (PLBOXW*8/2), my + 16 + (PLBOXH*8) - 12, flags, patch);
+		V_DrawCharacter(BASEVIDWIDTH - x - 10 - V_StringWidth(Color_Names[setupm_fakecolor], V_ALLOWLOWERCASE) - (skullAnimCounter/5), y,
+			'\x1C' | V_YELLOWMAP, false);
+		V_DrawCharacter(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y,
+			'\x1D' | V_YELLOWMAP, false);
 	}
-	else
+
+	y += 11;
+
+#define indexwidth 8
 	{
-		UINT8 *colormap = R_GetTranslationColormap(setupm_fakeskin, setupm_fakecolor, 0);
+		const INT32 colwidth = (282-charw)/(2*indexwidth);
+		INT32 i = -colwidth;
+		INT16 col = setupm_fakecolor - colwidth;
+		INT32 w = indexwidth;
+		UINT8 h;
 
-		if (skins[setupm_fakeskin].flags & SF_HIRES)
+		while (col < 1)
+			col += MAXSKINCOLORS-1;
+		while (i <= colwidth)
 		{
-			V_DrawFixedPatch((mx+98+(PLBOXW*8/2))<<FRACBITS,
-						(my+16+(PLBOXH*8)-12)<<FRACBITS,
-						skins[setupm_fakeskin].highresscale,
-						flags, patch, colormap);
+			if (!(i++))
+				w = charw;
+			else
+				w = indexwidth;
+			for (h = 0; h < 16; h++)
+				V_DrawFill(x, y+h, w, 1, Color_Index[col-1][h]);
+			if (++col >= MAXSKINCOLORS)
+				col -= MAXSKINCOLORS-1;
+			x += w;
 		}
-		else
-			V_DrawMappedPatch(mx + 98 + (PLBOXW*8/2), my + 16 + (PLBOXH*8) - 12, flags, patch, colormap);
-
-		Z_Free(colormap);
 	}
+#undef charw
+#undef indexwidth
+
+	x = MP_PlayerSetupDef.x;
+	y += 20;
+
+	V_DrawString(x, y,
+		((R_SkinAvailable(setupm_cvdefaultskin->string) != setupm_fakeskin
+		|| setupm_cvdefaultcolor->value != setupm_fakecolor
+		|| strcmp(setupm_name, setupm_cvdefaultname->string))
+			? 0
+			: V_TRANSLUCENT)
+		| ((itemOn == 3) ? V_YELLOWMAP : 0),
+		"Save as default");
+	if (itemOn == 3)
+		cursory = y;
+
+	V_DrawScaledPatch(x - 17, cursory, 0,
+		W_CachePatchName("M_CURSOR", PU_CACHE));
 }
 
 // Handle 1P/2P MP Setup
@@ -7204,7 +8475,7 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 			break;
 
 		case KEY_LEFTARROW:
-			if (itemOn == 2)       //player skin
+			if (itemOn == 1)       //player skin
 			{
 				S_StartSound(NULL,sfx_menu1); // Tails
 				prev_setupm_fakeskin = setupm_fakeskin;
@@ -7215,16 +8486,30 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 						setupm_fakeskin = numskins-1;
 				}
 				while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUsable(-1, setupm_fakeskin)));
+				multi_spr2 = P_GetSkinSprite2(&skins[setupm_fakeskin], SPR2_WALK, NULL);
 			}
-			else if (itemOn == 1) // player color
+			else if (itemOn == 2) // player color
 			{
 				S_StartSound(NULL,sfx_menu1); // Tails
 				setupm_fakecolor--;
 			}
 			break;
 
+		case KEY_ENTER:
+			if (itemOn == 3
+			&& (R_SkinAvailable(setupm_cvdefaultskin->string) != setupm_fakeskin
+			|| setupm_cvdefaultcolor->value != setupm_fakecolor
+			|| strcmp(setupm_name, setupm_cvdefaultname->string)))
+			{
+				S_StartSound(NULL,sfx_strpst);
+				// you know what? always putting these in the buffer won't hurt anything.
+				COM_BufAddText (va("%s \"%s\"\n",setupm_cvdefaultskin->name,skins[setupm_fakeskin].name));
+				COM_BufAddText (va("%s %d\n",setupm_cvdefaultcolor->name,setupm_fakecolor));
+				COM_BufAddText (va("%s %s\n",setupm_cvdefaultname->name,setupm_name));
+				break;
+			}
 		case KEY_RIGHTARROW:
-			if (itemOn == 2)       //player skin
+			if (itemOn == 1)       //player skin
 			{
 				S_StartSound(NULL,sfx_menu1); // Tails
 				prev_setupm_fakeskin = setupm_fakeskin;
@@ -7235,8 +8520,9 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 						setupm_fakeskin = 0;
 				}
 				while ((prev_setupm_fakeskin != setupm_fakeskin) && !(R_SkinUsable(-1, setupm_fakeskin)));
+				multi_spr2 = P_GetSkinSprite2(&skins[setupm_fakeskin], SPR2_WALK, NULL);
 			}
-			else if (itemOn == 1) // player color
+			else if (itemOn == 2) // player color
 			{
 				S_StartSound(NULL,sfx_menu1); // Tails
 				setupm_fakecolor++;
@@ -7248,22 +8534,30 @@ static void M_HandleSetupMultiPlayer(INT32 choice)
 			break;
 
 		case KEY_BACKSPACE:
-			if ((l = strlen(setupm_name))!=0 && itemOn == 0)
+			if (itemOn == 0 && (l = strlen(setupm_name))!=0)
+			{
+				S_StartSound(NULL,sfx_menu1); // Tails
+				setupm_name[l-1] = 0;
+			}
+			break;
+
+		case KEY_DEL:
+			if (itemOn == 0 && (l = strlen(setupm_name))!=0)
 			{
 				S_StartSound(NULL,sfx_menu1); // Tails
-				setupm_name[l-1] =0;
+				setupm_name[0] = 0;
 			}
 			break;
 
 		default:
-			if (choice < 32 || choice > 127 || itemOn != 0)
+			if (itemOn != 0 || choice < 32 || choice > 127)
 				break;
+			S_StartSound(NULL,sfx_menu1); // Tails
 			l = strlen(setupm_name);
 			if (l < MAXPLAYERNAME-1)
 			{
-				S_StartSound(NULL,sfx_menu1); // Tails
-				setupm_name[l] =(char)choice;
-				setupm_name[l+1] =0;
+				setupm_name[l] = (char)choice;
+				setupm_name[l+1] = 0;
 			}
 			break;
 	}
@@ -7297,6 +8591,9 @@ static void M_SetupMultiPlayer(INT32 choice)
 	setupm_cvskin = &cv_skin;
 	setupm_cvcolor = &cv_playercolor;
 	setupm_cvname = &cv_playername;
+	setupm_cvdefaultskin = &cv_defaultskin;
+	setupm_cvdefaultcolor = &cv_defaultplayercolor;
+	setupm_cvdefaultname = &cv_defaultplayername;
 
 	// For whatever reason this doesn't work right if you just use ->value
 	setupm_fakeskin = R_SkinAvailable(setupm_cvskin->string);
@@ -7306,9 +8603,11 @@ static void M_SetupMultiPlayer(INT32 choice)
 
 	// disable skin changes if we can't actually change skins
 	if (!CanChangeSkin(consoleplayer))
-		MP_PlayerSetupMenu[2].status = (IT_GRAYEDOUT);
+		MP_PlayerSetupMenu[1].status = (IT_GRAYEDOUT);
 	else
-		MP_PlayerSetupMenu[2].status = (IT_KEYHANDLER|IT_STRING);
+		MP_PlayerSetupMenu[1].status = (IT_KEYHANDLER|IT_STRING);
+
+	multi_spr2 = P_GetSkinSprite2(&skins[setupm_fakeskin], SPR2_WALK, NULL);
 
 	MP_PlayerSetupDef.prevMenu = currentMenu;
 	M_SetupNextMenu(&MP_PlayerSetupDef);
@@ -7328,6 +8627,9 @@ static void M_SetupMultiPlayer2(INT32 choice)
 	setupm_cvskin = &cv_skin2;
 	setupm_cvcolor = &cv_playercolor2;
 	setupm_cvname = &cv_playername2;
+	setupm_cvdefaultskin = &cv_defaultskin2;
+	setupm_cvdefaultcolor = &cv_defaultplayercolor2;
+	setupm_cvdefaultname = &cv_defaultplayername2;
 
 	// For whatever reason this doesn't work right if you just use ->value
 	setupm_fakeskin = R_SkinAvailable(setupm_cvskin->string);
@@ -7337,9 +8639,11 @@ static void M_SetupMultiPlayer2(INT32 choice)
 
 	// disable skin changes if we can't actually change skins
 	if (splitscreen && !CanChangeSkin(secondarydisplayplayer))
-		MP_PlayerSetupMenu[2].status = (IT_GRAYEDOUT);
+		MP_PlayerSetupMenu[1].status = (IT_GRAYEDOUT);
 	else
-		MP_PlayerSetupMenu[2].status = (IT_KEYHANDLER | IT_STRING);
+		MP_PlayerSetupMenu[1].status = (IT_KEYHANDLER | IT_STRING);
+
+	multi_spr2 = P_GetSkinSprite2(&skins[setupm_fakeskin], SPR2_WALK, NULL);
 
 	MP_PlayerSetupDef.prevMenu = currentMenu;
 	M_SetupNextMenu(&MP_PlayerSetupDef);
@@ -7383,6 +8687,7 @@ static void M_EraseDataResponse(INT32 ch)
 		totalplaytime = 0;
 		F_StartIntro();
 	}
+	S_StartSound(NULL, sfx_bewar1+M_RandomKey(4)); // Bweh heh he
 	M_ClearMenus(true);
 }
 
@@ -7422,17 +8727,24 @@ static void M_DrawJoystick(void)
 {
 	INT32 i;
 
-	M_DrawGenericMenu();
+	// draw title (or big pic)
+	M_DrawMenuTitle();
 
-	for (i = 0;i < 8; i++)
+	for (i = 0; i <= 4; i++) // See MAX_JOYSTICKS
 	{
-		M_DrawSaveLoadBorder(OP_JoystickSetDef.x, OP_JoystickSetDef.y+LINEHEIGHT*i);
+		M_DrawSaveLoadBorder(OP_JoystickSetDef.x+4, OP_JoystickSetDef.y+1+LINEHEIGHT*i);
 
 		if ((setupcontrols_secondaryplayer && (i == cv_usejoystick2.value))
 			|| (!setupcontrols_secondaryplayer && (i == cv_usejoystick.value)))
 			V_DrawString(OP_JoystickSetDef.x, OP_JoystickSetDef.y+LINEHEIGHT*i,V_GREENMAP,joystickInfo[i]);
 		else
 			V_DrawString(OP_JoystickSetDef.x, OP_JoystickSetDef.y+LINEHEIGHT*i,0,joystickInfo[i]);
+
+		if (i == itemOn)
+		{
+			V_DrawScaledPatch(currentMenu->x - 24, OP_JoystickSetDef.y+LINEHEIGHT*i, 0,
+				W_CachePatchName("M_CURSOR", PU_CACHE));
+		}
 	}
 }
 
@@ -7493,16 +8805,22 @@ static void M_Setup1PControlsMenu(INT32 choice)
 	setupcontrols = gamecontrol;        // was called from main Options (for console player, then)
 	currentMenu->lastOn = itemOn;
 
-	// Unhide the three non-P2 controls
-	OP_MPControlsMenu[0].status = IT_CALL|IT_STRING2;
-	OP_MPControlsMenu[1].status = IT_CALL|IT_STRING2;
-	OP_MPControlsMenu[2].status = IT_CALL|IT_STRING2;
-	// Unide the pause/console controls too
-	OP_MiscControlsMenu[3].status = IT_CALL|IT_STRING2;
-	OP_MiscControlsMenu[4].status = IT_CALL|IT_STRING2;
+	// Unhide the five non-P2 controls and their headers
+	OP_ChangeControlsMenu[18+0].status = IT_HEADER;
+	OP_ChangeControlsMenu[18+1].status = IT_SPACE;
+	// ...
+	OP_ChangeControlsMenu[18+2].status = IT_CALL|IT_STRING2;
+	OP_ChangeControlsMenu[18+3].status = IT_CALL|IT_STRING2;
+	OP_ChangeControlsMenu[18+4].status = IT_CALL|IT_STRING2;
+	// ...
+	OP_ChangeControlsMenu[23+0].status = IT_HEADER;
+	OP_ChangeControlsMenu[23+1].status = IT_SPACE;
+	// ...
+	OP_ChangeControlsMenu[23+2].status = IT_CALL|IT_STRING2;
+	OP_ChangeControlsMenu[23+3].status = IT_CALL|IT_STRING2;
 
-	OP_ControlListDef.prevMenu = &OP_P1ControlsDef;
-	M_SetupNextMenu(&OP_ControlListDef);
+	OP_ChangeControlsDef.prevMenu = &OP_P1ControlsDef;
+	M_SetupNextMenu(&OP_ChangeControlsDef);
 }
 
 static void M_Setup2PControlsMenu(INT32 choice)
@@ -7512,62 +8830,137 @@ static void M_Setup2PControlsMenu(INT32 choice)
 	setupcontrols = gamecontrolbis;
 	currentMenu->lastOn = itemOn;
 
-	// Hide the three non-P2 controls
-	OP_MPControlsMenu[0].status = IT_GRAYEDOUT2;
-	OP_MPControlsMenu[1].status = IT_GRAYEDOUT2;
-	OP_MPControlsMenu[2].status = IT_GRAYEDOUT2;
-	// Hide the pause/console controls too
-	OP_MiscControlsMenu[3].status = IT_GRAYEDOUT2;
-	OP_MiscControlsMenu[4].status = IT_GRAYEDOUT2;
+	// Hide the five non-P2 controls and their headers
+	OP_ChangeControlsMenu[18+0].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[18+1].status = IT_GRAYEDOUT2;
+	// ...
+	OP_ChangeControlsMenu[18+2].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[18+3].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[18+4].status = IT_GRAYEDOUT2;
+	// ...
+	OP_ChangeControlsMenu[23+0].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[23+1].status = IT_GRAYEDOUT2;
+	// ...
+	OP_ChangeControlsMenu[23+2].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[23+3].status = IT_GRAYEDOUT2;
 
-	OP_ControlListDef.prevMenu = &OP_P2ControlsDef;
-	M_SetupNextMenu(&OP_ControlListDef);
+	OP_ChangeControlsDef.prevMenu = &OP_P2ControlsDef;
+	M_SetupNextMenu(&OP_ChangeControlsDef);
 }
 
+#define controlheight 18
+
 // Draws the Customise Controls menu
 static void M_DrawControl(void)
 {
 	char     tmp[50];
-	INT32    i;
+	INT32    x, y, i, max, cursory = 0, iter;
 	INT32    keys[2];
 
-	// draw title, strings and submenu
-	M_DrawGenericMenu();
+	x = currentMenu->x;
+	y = currentMenu->y;
+
+	/*i = itemOn - (controlheight/2);
+	if (i < 0)
+		i = 0;
+	*/
+
+	iter = (controlheight/2);
+	for (i = itemOn; ((iter || currentMenu->menuitems[i].status == IT_GRAYEDOUT2) && i > 0); i--)
+	{
+		if (currentMenu->menuitems[i].status != IT_GRAYEDOUT2)
+			iter--;
+	}
+	if (currentMenu->menuitems[i].status == IT_GRAYEDOUT2)
+		i--;
+
+	iter += (controlheight/2);
+	for (max = itemOn; (iter && max < currentMenu->numitems); max++)
+	{
+		if (currentMenu->menuitems[max].status != IT_GRAYEDOUT2)
+			iter--;
+	}
+
+	if (iter)
+	{
+		iter += (controlheight/2);
+		for (i = itemOn; ((iter || currentMenu->menuitems[i].status == IT_GRAYEDOUT2) && i > 0); i--)
+		{
+			if (currentMenu->menuitems[i].status != IT_GRAYEDOUT2)
+				iter--;
+		}
+	}
+
+	/*max = i + controlheight;
+	if (max > currentMenu->numitems)
+	{
+		max = currentMenu->numitems;
+		if (max < controlheight)
+			i = 0;
+		else
+			i = max - controlheight;
+	}*/
+
+	// draw title (or big pic)
+	M_DrawMenuTitle();
 
 	M_CentreText(30,
 		 (setupcontrols_secondaryplayer ? "SET CONTROLS FOR SECONDARY PLAYER" :
 		                                  "PRESS ENTER TO CHANGE, BACKSPACE TO CLEAR"));
 
-	for (i = 0;i < currentMenu->numitems;i++)
+	if (i)
+		V_DrawString(currentMenu->x - 16, y-(skullAnimCounter/5), V_YELLOWMAP, "\x1A"); // up arrow
+	if (max != currentMenu->numitems)
+		V_DrawString(currentMenu->x - 16, y+(SMALLLINEHEIGHT*(controlheight-1))+(skullAnimCounter/5), V_YELLOWMAP, "\x1B"); // down arrow
+
+	for (; i < max; i++)
 	{
-		if (currentMenu->menuitems[i].status != IT_CONTROL)
+		if (currentMenu->menuitems[i].status == IT_GRAYEDOUT2)
 			continue;
 
-		keys[0] = setupcontrols[currentMenu->menuitems[i].alphaKey][0];
-		keys[1] = setupcontrols[currentMenu->menuitems[i].alphaKey][1];
+		if (i == itemOn)
+			cursory = y;
 
-		tmp[0] ='\0';
-		if (keys[0] == KEY_NULL && keys[1] == KEY_NULL)
-		{
-			strcpy(tmp, "---");
-		}
-		else
+		if (currentMenu->menuitems[i].status == IT_CONTROL)
 		{
-			if (keys[0] != KEY_NULL)
-				strcat (tmp, G_KeynumToString (keys[0]));
+			V_DrawString(x, y, ((i == itemOn) ? V_YELLOWMAP : 0), currentMenu->menuitems[i].text);
+			keys[0] = setupcontrols[currentMenu->menuitems[i].alphaKey][0];
+			keys[1] = setupcontrols[currentMenu->menuitems[i].alphaKey][1];
+
+			tmp[0] ='\0';
+			if (keys[0] == KEY_NULL && keys[1] == KEY_NULL)
+			{
+				strcpy(tmp, "---");
+			}
+			else
+			{
+				if (keys[0] != KEY_NULL)
+					strcat (tmp, G_KeynumToString (keys[0]));
 
-			if (keys[0] != KEY_NULL && keys[1] != KEY_NULL)
-				strcat(tmp," or ");
+				if (keys[0] != KEY_NULL && keys[1] != KEY_NULL)
+					strcat(tmp," or ");
 
-			if (keys[1] != KEY_NULL)
-				strcat (tmp, G_KeynumToString (keys[1]));
+				if (keys[1] != KEY_NULL)
+					strcat (tmp, G_KeynumToString (keys[1]));
 
 
+			}
+			V_DrawRightAlignedString(BASEVIDWIDTH-currentMenu->x, y, V_YELLOWMAP, tmp);
 		}
-		V_DrawRightAlignedString(BASEVIDWIDTH-currentMenu->x, currentMenu->y + i*8, V_YELLOWMAP, tmp);
+		/*else if (currentMenu->menuitems[i].status == IT_GRAYEDOUT2)
+			V_DrawString(x, y, V_TRANSLUCENT, currentMenu->menuitems[i].text);*/
+		else if ((currentMenu->menuitems[i].status == IT_HEADER) && (i != max-1))
+			M_DrawLevelPlatterHeader(y, currentMenu->menuitems[i].text, true, false);
+
+		y += SMALLLINEHEIGHT;
 	}
+	
+	V_DrawScaledPatch(currentMenu->x - 20, cursory, 0,
+		W_CachePatchName("M_CURSOR", PU_CACHE));
 }
 
+#undef controlbuffer
+
 static INT32 controltochange;
 
 static void M_ChangecontrolResponse(event_t *ev)
@@ -7636,8 +9029,10 @@ static void M_ChangecontrolResponse(event_t *ev)
 			G_CheckDoubleUsage(ch);
 			setupcontrols[control][found] = ch;
 		}
-
+		S_StartSound(NULL, sfx_strpst);
 	}
+	else
+		S_StartSound(NULL, sfx_skid);
 
 	M_StopMessage(0);
 }
@@ -7657,35 +9052,129 @@ static void M_ChangeControl(INT32 choice)
 // SOUND
 // =====
 
+static void M_SoundMenu(INT32 choice)
+{
+	(void)choice;
+
+	OP_SoundOptionsMenu[6].status = ((nosound || sound_disabled) ? IT_GRAYEDOUT : (IT_STRING | IT_CVAR));
+	M_SetupNextMenu(&OP_SoundOptionsDef);
+}
+
+void M_DrawSoundMenu(void)
+{
+	const char* onstring = "ON";
+	const char* offstring = "OFF";
+	INT32 lengthstring;
+	M_DrawGenericMenu();
+
+	V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x,
+		currentMenu->y+currentMenu->menuitems[0].alphaKey,
+		(nosound ? V_REDMAP : V_YELLOWMAP),
+		((nosound || sound_disabled) ? offstring : onstring));
+
+	V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x,
+		currentMenu->y+currentMenu->menuitems[2].alphaKey,
+		(nodigimusic ? V_REDMAP : V_YELLOWMAP),
+		((nodigimusic || digital_disabled) ? offstring : onstring));
+
+	V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x,
+		currentMenu->y+currentMenu->menuitems[4].alphaKey,
+		(nomidimusic ? V_REDMAP : V_YELLOWMAP),
+		((nomidimusic || music_disabled) ? offstring : onstring));
+
+	if (itemOn == 0)
+		lengthstring = ((nosound || sound_disabled) ? 3 : 2);
+	else if (itemOn == 2)
+		lengthstring = ((nodigimusic || digital_disabled) ? 3 : 2);
+	else if (itemOn == 4)
+		lengthstring = ((nomidimusic || music_disabled) ? 3 : 2);
+	else
+		return;
+
+	V_DrawCharacter(BASEVIDWIDTH - currentMenu->x - 10 - (lengthstring*8) - (skullAnimCounter/5), currentMenu->y+currentMenu->menuitems[itemOn].alphaKey,
+		'\x1C' | V_YELLOWMAP, false);
+	V_DrawCharacter(BASEVIDWIDTH - currentMenu->x + 2 + (skullAnimCounter/5), currentMenu->y+currentMenu->menuitems[itemOn].alphaKey,
+		'\x1D' | V_YELLOWMAP, false);
+}
+
 // Toggles sound systems in-game.
-static void M_ToggleSFX(void)
+static void M_ToggleSFX(INT32 choice)
 {
+	switch (choice)
+	{
+		case KEY_DOWNARROW:
+			S_StartSound(NULL, sfx_menu1);
+			itemOn++;
+			return;
+
+		case KEY_UPARROW:
+			S_StartSound(NULL, sfx_menu1);
+			itemOn = currentMenu->numitems-1;
+			return;
+
+		case KEY_ESCAPE:
+			if (currentMenu->prevMenu)
+				M_SetupNextMenu(currentMenu->prevMenu);
+			else
+				M_ClearMenus(true);
+			return;
+		default:
+			break;
+	}
+
 	if (nosound)
 	{
 		nosound = false;
 		I_StartupSound();
 		if (nosound) return;
 		S_Init(cv_soundvolume.value, cv_digmusicvolume.value, cv_midimusicvolume.value);
-		M_StartMessage(M_GetText("SFX Enabled\n"), NULL, MM_NOTHING);
+		S_StartSound(NULL, sfx_strpst);
+		OP_SoundOptionsMenu[6].status = IT_STRING | IT_CVAR;
+		//M_StartMessage(M_GetText("SFX Enabled\n"), NULL, MM_NOTHING);
 	}
 	else
 	{
 		if (sound_disabled)
 		{
 			sound_disabled = false;
-			M_StartMessage(M_GetText("SFX Enabled\n"), NULL, MM_NOTHING);
+			S_StartSound(NULL, sfx_strpst);
+			OP_SoundOptionsMenu[6].status = IT_STRING | IT_CVAR;
+			//M_StartMessage(M_GetText("SFX Enabled\n"), NULL, MM_NOTHING);
 		}
 		else
 		{
 			sound_disabled = true;
 			S_StopSounds();
-			M_StartMessage(M_GetText("SFX Disabled\n"), NULL, MM_NOTHING);
+			OP_SoundOptionsMenu[6].status = IT_GRAYEDOUT;
+			//M_StartMessage(M_GetText("SFX Disabled\n"), NULL, MM_NOTHING);
 		}
 	}
 }
 
-static void M_ToggleDigital(void)
+static void M_ToggleDigital(INT32 choice)
 {
+	switch (choice)
+	{
+		case KEY_DOWNARROW:
+			S_StartSound(NULL, sfx_menu1);
+			itemOn++;
+			return;
+
+		case KEY_UPARROW:
+			S_StartSound(NULL, sfx_menu1);
+			itemOn--;
+			return;
+
+		case KEY_ESCAPE:
+			if (currentMenu->prevMenu)
+				M_SetupNextMenu(currentMenu->prevMenu);
+			else
+				M_ClearMenus(true);
+			return;
+		default:
+			break;
+	}
+
 	if (nodigimusic)
 	{
 		nodigimusic = false;
@@ -7694,26 +9183,49 @@ static void M_ToggleDigital(void)
 		S_Init(cv_soundvolume.value, cv_digmusicvolume.value, cv_midimusicvolume.value);
 		S_StopMusic();
 		S_ChangeMusicInternal("_clear", false);
-		M_StartMessage(M_GetText("Digital Music Enabled\n"), NULL, MM_NOTHING);
+		//M_StartMessage(M_GetText("Digital Music Enabled\n"), NULL, MM_NOTHING);
 	}
 	else
 	{
 		if (digital_disabled)
 		{
 			digital_disabled = false;
-			M_StartMessage(M_GetText("Digital Music Enabled\n"), NULL, MM_NOTHING);
+			S_ChangeMusicInternal("_clear", false);
+			//M_StartMessage(M_GetText("Digital Music Enabled\n"), NULL, MM_NOTHING);
 		}
 		else
 		{
 			digital_disabled = true;
 			S_StopMusic();
-			M_StartMessage(M_GetText("Digital Music Disabled\n"), NULL, MM_NOTHING);
+			//M_StartMessage(M_GetText("Digital Music Disabled\n"), NULL, MM_NOTHING);
 		}
 	}
 }
 
-static void M_ToggleMIDI(void)
+static void M_ToggleMIDI(INT32 choice)
 {
+	switch (choice)
+	{
+		case KEY_DOWNARROW:
+			S_StartSound(NULL, sfx_menu1);
+			itemOn++;
+			return;
+
+		case KEY_UPARROW:
+			S_StartSound(NULL, sfx_menu1);
+			itemOn--;
+			return;
+
+		case KEY_ESCAPE:
+			if (currentMenu->prevMenu)
+				M_SetupNextMenu(currentMenu->prevMenu);
+			else
+				M_ClearMenus(true);
+			return;
+		default:
+			break;
+	}
+
 	if (nomidimusic)
 	{
 		nomidimusic = false;
@@ -7721,20 +9233,21 @@ static void M_ToggleMIDI(void)
 		if (nomidimusic) return;
 		S_Init(cv_soundvolume.value, cv_digmusicvolume.value, cv_midimusicvolume.value);
 		S_ChangeMusicInternal("_clear", false);
-		M_StartMessage(M_GetText("MIDI Music Enabled\n"), NULL, MM_NOTHING);
+		//M_StartMessage(M_GetText("MIDI Music Enabled\n"), NULL, MM_NOTHING);
 	}
 	else
 	{
 		if (music_disabled)
 		{
 			music_disabled = false;
-			M_StartMessage(M_GetText("MIDI Music Enabled\n"), NULL, MM_NOTHING);
+			S_ChangeMusicInternal("_clear", false);
+			//M_StartMessage(M_GetText("MIDI Music Enabled\n"), NULL, MM_NOTHING);
 		}
 		else
 		{
 			music_disabled = true;
 			S_StopMusic();
-			M_StartMessage(M_GetText("MIDI Music Disabled\n"), NULL, MM_NOTHING);
+			//M_StartMessage(M_GetText("MIDI Music Disabled\n"), NULL, MM_NOTHING);
 		}
 	}
 }
@@ -7829,6 +9342,21 @@ static void M_VideoModeMenu(INT32 choice)
 	M_SetupNextMenu(&OP_VideoModeDef);
 }
 
+static void M_DrawMainVideoMenu(void)
+{
+
+	M_DrawGenericScrollMenu();
+	if (itemOn < 8) // where it starts to go offscreen; change this number if you change the layout of the video menu
+	{
+		INT32 y = currentMenu->y+currentMenu->menuitems[1].alphaKey*2;
+		if (itemOn == 7)
+			y -= 10;
+		V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, y,
+		(SCR_IsAspectCorrect(vid.width, vid.height) ? V_GREENMAP : V_YELLOWMAP),
+			va("%dx%d", vid.width, vid.height));
+	}
+}
+
 // Draw the video modes list, a-la-Quake
 static void M_DrawVideoMode(void)
 {
@@ -7887,10 +9415,10 @@ static void M_DrawVideoMode(void)
 
 		V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y + 138,
 			V_GREENMAP, "Green modes are recommended.");
-		V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y + 150,
+		V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y + 146,
 			V_YELLOWMAP, "Other modes may have visual errors.");
 		V_DrawCenteredString(BASEVIDWIDTH/2, OP_VideoModeDef.y + 158,
-			V_YELLOWMAP, "Use at own risk.");
+			V_YELLOWMAP, "Larger modes may have performance issues.");
 	}
 
 	// Draw the cursor for the VidMode menu
@@ -7901,6 +9429,134 @@ static void M_DrawVideoMode(void)
 		W_CachePatchName("M_CURSOR", PU_CACHE));
 }
 
+// Just M_DrawGenericScrollMenu but showing a backing behind the headers.
+static void M_DrawColorMenu(void)
+{
+	INT32 x, y, i, max, tempcentery, cursory = 0;
+
+	// DRAW MENU
+	x = currentMenu->x;
+	y = currentMenu->y;
+
+	V_DrawFill(19       , y-4, 47, 1,  35);
+	V_DrawFill(19+(  47), y-4, 47, 1,  73);
+	V_DrawFill(19+(2*47), y-4, 47, 1, 112);
+	V_DrawFill(19+(3*47), y-4, 47, 1, 255);
+	V_DrawFill(19+(4*47), y-4, 47, 1, 152);
+	V_DrawFill(19+(5*47), y-4, 46, 1, 181);
+
+	V_DrawFill(300, y-4, 1, 1, 26);
+	V_DrawFill( 19, y-3, 282, 1, 26);
+
+	if ((currentMenu->menuitems[itemOn].alphaKey*2 - currentMenu->menuitems[0].alphaKey*2) <= scrollareaheight)
+		tempcentery = currentMenu->y - currentMenu->menuitems[0].alphaKey*2;
+	else if ((currentMenu->menuitems[currentMenu->numitems-1].alphaKey*2 - currentMenu->menuitems[itemOn].alphaKey*2) <= scrollareaheight)
+		tempcentery = currentMenu->y - currentMenu->menuitems[currentMenu->numitems-1].alphaKey*2 + 2*scrollareaheight;
+	else
+		tempcentery = currentMenu->y - currentMenu->menuitems[itemOn].alphaKey*2 + scrollareaheight;
+
+	for (i = 0; i < currentMenu->numitems; i++)
+	{
+		if (currentMenu->menuitems[i].status != IT_DISABLED && currentMenu->menuitems[i].alphaKey*2 + tempcentery >= currentMenu->y)
+			break;
+	}
+
+	for (max = currentMenu->numitems; max > 0; max--)
+	{
+		if (currentMenu->menuitems[max].status != IT_DISABLED && currentMenu->menuitems[max-1].alphaKey*2 + tempcentery <= (currentMenu->y + 2*scrollareaheight))
+			break;
+	}
+
+	if (i)
+		V_DrawString(currentMenu->x - 20, currentMenu->y - (skullAnimCounter/5), V_YELLOWMAP, "\x1A"); // up arrow
+	if (max != currentMenu->numitems)
+		V_DrawString(currentMenu->x - 20, currentMenu->y + 2*scrollareaheight + (skullAnimCounter/5), V_YELLOWMAP, "\x1B"); // down arrow
+
+	// draw title (or big pic)
+	M_DrawMenuTitle();
+
+	for (; i < max; i++)
+	{
+		y = currentMenu->menuitems[i].alphaKey*2 + tempcentery;
+		if (i == itemOn)
+			cursory = y;
+		switch (currentMenu->menuitems[i].status & IT_DISPLAY)
+		{
+			case IT_PATCH:
+			case IT_DYBIGSPACE:
+			case IT_BIGSLIDER:
+			case IT_STRING2:
+			case IT_DYLITLSPACE:
+			case IT_GRAYPATCH:
+			case IT_TRANSTEXT2:
+				// unsupported
+				break;
+			case IT_NOTHING:
+				break;
+			case IT_STRING:
+			case IT_WHITESTRING:
+				if (i != itemOn && (currentMenu->menuitems[i].status & IT_DISPLAY)==IT_STRING)
+					V_DrawString(x, y, 0, currentMenu->menuitems[i].text);
+				else
+					V_DrawString(x, y, V_YELLOWMAP, currentMenu->menuitems[i].text);
+
+				// Cvar specific handling
+				switch (currentMenu->menuitems[i].status & IT_TYPE)
+					case IT_CVAR:
+					{
+						consvar_t *cv = (consvar_t *)currentMenu->menuitems[i].itemaction;
+						switch (currentMenu->menuitems[i].status & IT_CVARTYPE)
+						{
+							case IT_CV_SLIDER:
+								M_DrawSlider(x, y, cv, (i == itemOn));
+							case IT_CV_NOPRINT: // color use this
+							case IT_CV_INVISSLIDER: // monitor toggles use this
+								break;
+							case IT_CV_STRING:
+								if (y + 12 > (currentMenu->y + 2*scrollareaheight))
+									break;
+								M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1);
+								V_DrawString(x + 8, y + 12, V_ALLOWLOWERCASE, cv->string);
+								if (skullAnimCounter < 4 && i == itemOn)
+									V_DrawCharacter(x + 8 + V_StringWidth(cv->string, 0), y + 12,
+										'_' | 0x80, false);
+								y += 16;
+								break;
+							default:
+								V_DrawRightAlignedString(BASEVIDWIDTH - x, y,
+									((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? V_REDMAP : V_YELLOWMAP), cv->string);
+								if (i == itemOn)
+								{
+									V_DrawCharacter(BASEVIDWIDTH - x - 10 - V_StringWidth(cv->string, 0) - (skullAnimCounter/5), y,
+											'\x1C' | V_YELLOWMAP, false);
+									V_DrawCharacter(BASEVIDWIDTH - x + 2 + (skullAnimCounter/5), y,
+											'\x1D' | V_YELLOWMAP, false);
+								}
+								break;
+						}
+						break;
+					}
+					break;
+			case IT_TRANSTEXT:
+				V_DrawString(x, y, V_TRANSLUCENT, currentMenu->menuitems[i].text);
+				break;
+			case IT_QUESTIONMARKS:
+				V_DrawString(x, y, V_TRANSLUCENT|V_OLDSPACING, M_CreateSecretMenuOption(currentMenu->menuitems[i].text));
+				break;
+			case IT_HEADERTEXT:
+				//V_DrawString(x-16, y, V_YELLOWMAP, currentMenu->menuitems[i].text);
+				V_DrawFill(19, y, 281, 9, currentMenu->menuitems[i+1].alphaKey);
+				V_DrawFill(300, y, 1, 9, 26);
+				M_DrawLevelPlatterHeader(y - (lsheadingheight - 12), currentMenu->menuitems[i].text, false, false);
+				break;
+		}
+	}
+
+	// DRAW THE SKULL CURSOR
+	V_DrawScaledPatch(currentMenu->x - 24, cursory, 0,
+		W_CachePatchName("M_CURSOR", PU_CACHE));
+}
+
 // special menuitem key handler for video mode list
 static void M_HandleVideoMode(INT32 ch)
 {
@@ -7974,6 +9630,21 @@ static void M_HandleVideoMode(INT32 ch)
 	}
 }
 
+static void M_DrawScreenshotMenu(void)
+{
+
+	M_DrawGenericScrollMenu();
+#ifdef HWRENDER
+	if ((rendermode == render_opengl) && (itemOn < 7)) // where it starts to go offscreen; change this number if you change the layout of the screenshot menu
+	{
+		INT32 y = currentMenu->y+currentMenu->menuitems[op_screenshot_colorprofile].alphaKey*2;
+		if (itemOn == 6)
+			y -= 10;
+		V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, y, V_REDMAP, "ON");
+	}
+#endif
+}
+
 // ===============
 // Monitor Toggles
 // ===============
@@ -7989,7 +9660,8 @@ static void M_DrawMonitorToggles(void)
 	// Assumes all are cvar type.
 	for (i = 0; i < currentMenu->numitems; ++i)
 	{
-		cv = (consvar_t *)currentMenu->menuitems[i].itemaction;
+		if (!(currentMenu->menuitems[i].status & IT_CVAR) || !(cv = (consvar_t *)currentMenu->menuitems[i].itemaction))
+			continue;
 		sum += cv->value;
 
 		if (!CV_IsSetToDefault(cv))
@@ -7998,10 +9670,11 @@ static void M_DrawMonitorToggles(void)
 
 	for (i = 0; i < currentMenu->numitems; ++i)
 	{
-		cv = (consvar_t *)currentMenu->menuitems[i].itemaction;
+		if (!(currentMenu->menuitems[i].status & IT_CVAR) || !(cv = (consvar_t *)currentMenu->menuitems[i].itemaction))
+			continue;
 		y = currentMenu->y + currentMenu->menuitems[i].alphaKey;
 
-		M_DrawSlider(currentMenu->x + 20, y, cv);
+		M_DrawSlider(currentMenu->x + 20, y, cv, (i == itemOn));
 
 		if (!cv->value)
 			V_DrawRightAlignedString(312, y, V_OLDSPACING|((i == itemOn) ? V_YELLOWMAP : 0), "None");
@@ -8045,6 +9718,8 @@ void M_QuitResponse(INT32 ch)
 		return;
 	if (!(netgame || cv_debug))
 	{
+		S_ResetCaptions();
+
 		mrand = M_RandomKey(sizeof(quitsounds)/sizeof(INT32));
 		if (quitsounds[mrand]) S_StartSound(NULL, quitsounds[mrand]);
 
@@ -8128,7 +9803,6 @@ static void M_HandleFogColor(INT32 choice)
 			break;
 
 		case KEY_ESCAPE:
-			S_StartSound(NULL, sfx_menu1);
 			exitmenu = true;
 			break;
 
diff --git a/src/m_menu.h b/src/m_menu.h
index 2e20789efc09792618ac9650b1c7d74edcd8835e..53dc266d1d075c2195618846914dcd5fb57ccf6f 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -124,6 +124,8 @@ boolean M_CanShowLevelInList(INT32 mapnum, INT32 gt);
 #define IT_HEADER      (IT_SPACE  +IT_HEADERTEXT)
 #define IT_SECRET      (IT_SPACE  +IT_QUESTIONMARKS)
 
+#define MAXSTRINGLENGTH 32
+
 typedef union
 {
 	struct menu_s *submenu;      // IT_SUBMENU
@@ -249,6 +251,9 @@ void Nextmap_OnChange(void);
 void Moviemode_mode_Onchange(void);
 void Screenshot_option_Onchange(void);
 
+// Addons menu updating
+void Addons_option_Onchange(void);
+
 // These defines make it a little easier to make menus
 #define DEFAULTMENUSTYLE(header, source, prev, x, y)\
 {\
@@ -262,6 +267,18 @@ void Screenshot_option_Onchange(void);
 	NULL\
 }
 
+#define DEFAULTSCROLLMENUSTYLE(header, source, prev, x, y)\
+{\
+	header,\
+	sizeof(source)/sizeof(menuitem_t),\
+	prev,\
+	source,\
+	M_DrawGenericScrollMenu,\
+	x, y,\
+	0,\
+	NULL\
+}
+
 #define PAUSEMENUSTYLE(source, x, y)\
 {\
 	NULL,\
diff --git a/src/m_misc.c b/src/m_misc.c
index f8d3213c2d8b8ba7ced535c343603b37e75381e4..d271558fb6c056c076873190b0aa824bfcc2330f 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -100,6 +100,8 @@ static CV_PossibleValue_t screenshot_cons_t[] = {{0, "Default"}, {1, "HOME"}, {2
 consvar_t cv_screenshot_option = {"screenshot_option", "Default", CV_SAVE|CV_CALL, screenshot_cons_t, Screenshot_option_Onchange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_screenshot_folder = {"screenshot_folder", "", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
 
+consvar_t cv_screenshot_colorprofile = {"screenshot_colorprofile", "Yes", CV_SAVE, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
+
 static CV_PossibleValue_t moviemode_cons_t[] = {{MM_GIF, "GIF"}, {MM_APNG, "aPNG"}, {MM_SCREENSHOT, "Screenshots"}, {0, NULL}};
 consvar_t cv_moviemode = {"moviemode_mode", "GIF", CV_SAVE|CV_CALL, moviemode_cons_t, Moviemode_mode_Onchange, 0, NULL, NULL, 0, 0, NULL};
 
@@ -610,20 +612,25 @@ static void PNG_warn(png_structp PNG, png_const_charp pngtext)
 	CONS_Debug(DBG_RENDER, "libpng warning at %p: %s", PNG, pngtext);
 }
 
-static void M_PNGhdr(png_structp png_ptr, png_infop png_info_ptr, PNG_CONST png_uint_32 width, PNG_CONST png_uint_32 height, PNG_CONST png_byte *palette)
+static void M_PNGhdr(png_structp png_ptr, png_infop png_info_ptr, PNG_CONST png_uint_32 width, PNG_CONST png_uint_32 height, const boolean palette)
 {
 	const png_byte png_interlace = PNG_INTERLACE_NONE; //PNG_INTERLACE_ADAM7
 	if (palette)
 	{
 		png_colorp png_PLTE = png_malloc(png_ptr, sizeof(png_color)*256); //palette
-		const png_byte *pal = palette;
 		png_uint_16 i;
+
+		RGBA_t *pal = ((cv_screenshot_colorprofile.value)
+		? pLocalPalette
+		: pMasterPalette);
+
 		for (i = 0; i < 256; i++)
 		{
-			png_PLTE[i].red   = *pal; pal++;
-			png_PLTE[i].green = *pal; pal++;
-			png_PLTE[i].blue  = *pal; pal++;
+			png_PLTE[i].red   = pal[i].s.red;
+			png_PLTE[i].green = pal[i].s.green;
+			png_PLTE[i].blue  = pal[i].s.blue;
 		}
+
 		png_set_IHDR(png_ptr, png_info_ptr, width, height, 8, PNG_COLOR_TYPE_PALETTE,
 		 png_interlace, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
 		png_write_info_before_PLTE(png_ptr, png_info_ptr);
@@ -924,7 +931,7 @@ static void M_PNGfix_acTL(png_structp png_ptr, png_infop png_info_ptr)
 	fseek(apng_FILE, oldpos, SEEK_SET);
 }
 
-static boolean M_SetupaPNG(png_const_charp filename, png_bytep pal)
+static boolean M_SetupaPNG(png_const_charp filename, boolean palette)
 {
 	apng_FILE = fopen(filename,"wb+"); // + mode for reading
 	if (!apng_FILE)
@@ -966,7 +973,7 @@ static boolean M_SetupaPNG(png_const_charp filename, png_bytep pal)
 	png_set_compression_strategy(apng_ptr, cv_zlib_strategya.value);
 	png_set_compression_window_bits(apng_ptr, cv_zlib_window_bitsa.value);
 
-	M_PNGhdr(apng_ptr, apng_info_ptr, vid.width, vid.height, pal);
+	M_PNGhdr(apng_ptr, apng_info_ptr, vid.width, vid.height, palette);
 
 	M_PNGText(apng_ptr, apng_info_ptr, true);
 
@@ -1007,9 +1014,9 @@ static inline moviemode_t M_StartMovieAPNG(const char *pathname)
 	}
 
 	if (rendermode == render_soft)
-		ret = M_SetupaPNG(va(pandf,pathname,freename), W_CacheLumpName(GetPalette(), PU_CACHE));
+		ret = M_SetupaPNG(va(pandf,pathname,freename), true);
 	else
-		ret = M_SetupaPNG(va(pandf,pathname,freename), NULL);
+		ret = M_SetupaPNG(va(pandf,pathname,freename), false);
 
 	if (!ret)
 	{
@@ -1215,11 +1222,10 @@ void M_StopMovie(void)
   * \param palette  Palette of image data
   *  \note if palette is NULL, BGR888 format
   */
-boolean M_SavePNG(const char *filename, void *data, int width, int height, const UINT8 *palette)
+boolean M_SavePNG(const char *filename, void *data, int width, int height, const boolean palette)
 {
 	png_structp png_ptr;
 	png_infop png_info_ptr;
-	PNG_CONST png_byte *PLTE = (const png_byte *)palette;
 #ifdef PNG_SETJMP_SUPPORTED
 #ifdef USE_FAR_KEYWORD
 	jmp_buf jmpbuf;
@@ -1282,7 +1288,7 @@ boolean M_SavePNG(const char *filename, void *data, int width, int height, const
 	png_set_compression_strategy(png_ptr, cv_zlib_strategy.value);
 	png_set_compression_window_bits(png_ptr, cv_zlib_window_bits.value);
 
-	M_PNGhdr(png_ptr, png_info_ptr, width, height, PLTE);
+	M_PNGhdr(png_ptr, png_info_ptr, width, height, palette);
 
 	M_PNGText(png_ptr, png_info_ptr, false);
 
@@ -1329,7 +1335,7 @@ typedef struct
   * \param palette  Palette of image data
   */
 #if NUMSCREENS > 2
-static boolean WritePCXfile(const char *filename, const UINT8 *data, int width, int height, const UINT8 *palette)
+static boolean WritePCXfile(const char *filename, const UINT8 *data, int width, int height)
 {
 	int i;
 	size_t length;
@@ -1370,8 +1376,20 @@ static boolean WritePCXfile(const char *filename, const UINT8 *data, int width,
 
 	// write the palette
 	*pack++ = 0x0c; // palette ID byte
-	for (i = 0; i < 768; i++)
-		*pack++ = *palette++;
+
+	// write color table
+	{
+		RGBA_t *pal = ((cv_screenshot_colorprofile.value)
+		? pLocalPalette
+		: pMasterPalette);
+
+		for (i = 0; i < 256; i++)
+		{
+			*pack++ = pal[i].s.red;
+			*pack++ = pal[i].s.green;
+			*pack++ = pal[i].s.blue;
+		}
+	}
 
 	// write output file
 	length = pack - (UINT8 *)pcx;
@@ -1445,11 +1463,9 @@ void M_DoScreenShot(void)
 	if (rendermode != render_none)
 	{
 #ifdef USE_PNG
-		ret = M_SavePNG(va(pandf,pathname,freename), linear, vid.width, vid.height,
-			W_CacheLumpName(GetPalette(), PU_CACHE));
+		ret = M_SavePNG(va(pandf,pathname,freename), linear, vid.width, vid.height, true);
 #else
-		ret = WritePCXfile(va(pandf,pathname,freename), linear, vid.width, vid.height,
-			W_CacheLumpName(GetPalette(), PU_CACHE));
+		ret = WritePCXfile(va(pandf,pathname,freename), linear, vid.width, vid.height);
 #endif
 	}
 
diff --git a/src/m_misc.h b/src/m_misc.h
index dc540dc16325ffca245ba194393463067823c083..85d819a3cf703ed697b9941c69a53575210b3579 100644
--- a/src/m_misc.h
+++ b/src/m_misc.h
@@ -19,6 +19,7 @@
 #include "tables.h"
 
 #include "d_event.h" // Screenshot responder
+#include "command.h"
 
 typedef enum {
 	MM_OFF = 0,
@@ -28,6 +29,12 @@ typedef enum {
 } moviemode_t;
 extern moviemode_t moviemode;
 
+extern consvar_t cv_screenshot_option, cv_screenshot_folder, cv_screenshot_colorprofile;
+extern consvar_t cv_moviemode;
+extern consvar_t cv_zlib_memory, cv_zlib_level, cv_zlib_strategy, cv_zlib_window_bits;
+extern consvar_t cv_zlib_memorya, cv_zlib_levela, cv_zlib_strategya, cv_zlib_window_bitsa;
+extern consvar_t cv_apng_delay;
+
 void M_StartMovie(void);
 void M_SaveFrame(void);
 void M_StopMovie(void);
@@ -57,7 +64,7 @@ void FIL_ForceExtension(char *path, const char *extension);
 boolean FIL_CheckExtension(const char *in);
 
 #ifdef HAVE_PNG
-boolean M_SavePNG(const char *filename, void *data, int width, int height, const UINT8 *palette);
+boolean M_SavePNG(const char *filename, void *data, int width, int height, const boolean palette);
 #endif
 
 extern boolean takescreenshot;
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 808581274478b875d6e332af8633f5c681b353f5..1f5b902d42796827cc5160a8b8fadfe7f2ff8fd4 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -93,20 +93,12 @@ void A_Explode(mobj_t *actor);
 void A_BossDeath(mobj_t *actor);
 void A_CustomPower(mobj_t *actor);
 void A_GiveWeapon(mobj_t *actor);
-void A_JumpShield(mobj_t *actor);
-void A_RingShield(mobj_t *actor);
 void A_RingBox(mobj_t *actor);
 void A_Invincibility(mobj_t *actor);
 void A_SuperSneakers(mobj_t *actor);
 void A_AwardScore(mobj_t *actor);
 void A_ExtraLife(mobj_t *actor);
-void A_BombShield(mobj_t *actor);
-void A_WaterShield(mobj_t *actor);
-void A_ForceShield(mobj_t *actor);
-void A_PityShield(mobj_t *actor);
-void A_FlameShield(mobj_t *actor);
-void A_BubbleShield(mobj_t *actor);
-void A_ThunderShield(mobj_t *actor);
+void A_GiveShield(mobj_t *actor);
 void A_GravityBox(mobj_t *actor);
 void A_ScoreRise(mobj_t *actor);
 void A_ParticleSpawn(mobj_t *actor);
@@ -137,7 +129,6 @@ void A_DetonChase(mobj_t *actor);
 void A_CapeChase(mobj_t *actor);
 void A_RotateSpikeBall(mobj_t *actor);
 void A_SlingAppear(mobj_t *actor);
-void A_MaceRotate(mobj_t *actor);
 void A_UnidusBall(mobj_t *actor);
 void A_RockSpawn(mobj_t *actor);
 void A_SetFuse(mobj_t *actor);
@@ -841,6 +832,34 @@ static mobjtype_t P_DoRandomBoxChances(void)
 	mobjtype_t spawnchance[256];
 	INT32 numchoices = 0, i = 0;
 
+	if (!(netgame || multiplayer))
+	{
+		switch (P_RandomKey(10))
+		{
+			case 0:
+				return MT_RING_ICON;
+			case 1:
+				return MT_SNEAKERS_ICON;
+			case 2:
+				return MT_INVULN_ICON;
+			case 3:
+				return MT_WHIRLWIND_ICON;
+			case 4:
+				return MT_ELEMENTAL_ICON;
+			case 5:
+				return MT_ATTRACT_ICON;
+			case 6:
+				return MT_FORCE_ICON;
+			case 7:
+				return MT_ARMAGEDDON_ICON;
+			case 8:
+				return MT_1UP_ICON;
+			case 9:
+				return MT_EGGMAN_ICON;
+		}
+		return MT_NULL;
+	}
+
 #define QUESTIONBOXCHANCES(type, cvar) \
 for (i = cvar.value; i; --i) spawnchance[numchoices++] = type
 	QUESTIONBOXCHANCES(MT_RING_ICON,       cv_superring);
@@ -1145,7 +1164,7 @@ void A_JetJawChomp(mobj_t *actor)
 	if (!actor->target || !(actor->target->flags & MF_SHOOTABLE)
 		|| actor->target->health <= 0 || !P_CheckSight(actor, actor->target))
 	{
-		P_SetMobjState(actor, actor->info->spawnstate);
+		P_SetMobjStateNF(actor, actor->info->spawnstate);
 		return;
 	}
 
@@ -2835,8 +2854,8 @@ void A_BossDeath(mobj_t *mo)
 
 	// make sure there is a player alive for victory
 	for (i = 0; i < MAXPLAYERS; i++)
-		if (playeringame[i] && ((players[i].mo && players[i].mo->health > 0)
-			|| ((netgame || multiplayer) && (players[i].lives > 0 || players[i].continues > 0))))
+		if (playeringame[i] && ((players[i].mo && players[i].mo->health)
+			|| ((netgame || multiplayer) && (players[i].lives || players[i].continues))))
 			break;
 
 	if (i == MAXPLAYERS)
@@ -3057,62 +3076,6 @@ void A_GiveWeapon(mobj_t *actor)
 		S_StartSound(player->mo, actor->info->seesound);
 }
 
-// Function: A_JumpShield
-//
-// Description: Awards the player a jump shield.
-//
-// var1 = unused
-// var2 = unused
-//
-void A_JumpShield(mobj_t *actor)
-{
-	player_t *player;
-
-#ifdef HAVE_BLUA
-	if (LUA_CallAction("A_JumpShield", actor))
-		return;
-#endif
-	if (!actor->target || !actor->target->player)
-	{
-		CONS_Debug(DBG_GAMELOGIC, "Powerup has no target.\n");
-		return;
-	}
-
-	player = actor->target->player;
-
-	P_SwitchShield(player, SH_WHIRLWIND);
-
-	S_StartSound(player->mo, actor->info->seesound);
-}
-
-// Function: A_RingShield
-//
-// Description: Awards the player a ring shield.
-//
-// var1 = unused
-// var2 = unused
-//
-void A_RingShield(mobj_t *actor)
-{
-	player_t *player;
-
-#ifdef HAVE_BLUA
-	if (LUA_CallAction("A_RingShield", actor))
-		return;
-#endif
-	if (!actor->target || !actor->target->player)
-	{
-		CONS_Debug(DBG_GAMELOGIC, "Powerup has no target.\n");
-		return;
-	}
-
-	player = actor->target->player;
-
-	P_SwitchShield(player, SH_ATTRACT);
-
-	S_StartSound(player->mo, actor->info->seesound);
-}
-
 // Function: A_RingBox
 //
 // Description: Awards the player 10 rings.
@@ -3170,6 +3133,8 @@ void A_Invincibility(mobj_t *actor)
 		S_StopMusic();
 		if (mariomode)
 			G_GhostAddColor(GHC_INVINCIBLE);
+		strlcpy(S_sfx[sfx_None].caption, "Invincibility", 14);
+		S_StartCaption(sfx_None, -1, player->powers[pw_invulnerability]);
 		S_ChangeMusicInternal((mariomode) ? "_minv" : "_inv", false);
 	}
 }
@@ -3208,6 +3173,8 @@ void A_SuperSneakers(mobj_t *actor)
 			S_StopMusic();
 			S_ChangeMusicInternal("_shoes", false);
 		}
+		strlcpy(S_sfx[sfx_None].caption, "Speed shoes", 12);
+		S_StartCaption(sfx_None, -1, player->powers[pw_sneakers]);
 	}
 }
 
@@ -3276,209 +3243,28 @@ void A_ExtraLife(mobj_t *actor)
 
 	// In shooter gametypes, give the player 100 rings instead of an extra life.
 	if (gametype != GT_COOP && gametype != GT_COMPETITION)
-		P_GivePlayerRings(player, 100);
-	else
-		P_GivePlayerLives(player, 1);
-	P_PlayLivesJingle(player);
-}
-
-// Function: A_BombShield
-//
-// Description: Awards the player a bomb shield.
-//
-// var1 = unused
-// var2 = unused
-//
-void A_BombShield(mobj_t *actor)
-{
-	player_t *player;
-
-#ifdef HAVE_BLUA
-	if (LUA_CallAction("A_BombShield", actor))
-		return;
-#endif
-	if (!actor->target || !actor->target->player)
-	{
-		CONS_Debug(DBG_GAMELOGIC, "Powerup has no target.\n");
-		return;
-	}
-
-	player = actor->target->player;
-
-	// If you already have a bomb shield, use it!
-	if ((player->powers[pw_shield] & SH_NOSTACK) == SH_ARMAGEDDON)
-		P_BlackOw(player);
-
-	// Now we know for certain that we don't have a bomb shield, so add one. :3
-	P_SwitchShield(player, SH_ARMAGEDDON);
-
-	S_StartSound(player->mo, actor->info->seesound);
-}
-
-// Function: A_WaterShield
-//
-// Description: Awards the player a water shield.
-//
-// var1 = unused
-// var2 = unused
-//
-void A_WaterShield(mobj_t *actor)
-{
-	player_t *player;
-
-#ifdef HAVE_BLUA
-	if (LUA_CallAction("A_WaterShield", actor))
-		return;
-#endif
-	if (!actor->target || !actor->target->player)
 	{
-		CONS_Debug(DBG_GAMELOGIC, "Powerup has no target.\n");
-		return;
-	}
-
-	player = actor->target->player;
-
-	P_SwitchShield(player, SH_ELEMENTAL);
-
-	S_StartSound(player->mo, actor->info->seesound);
-}
-
-// Function: A_ForceShield
-//
-// Description: Awards the player a force shield.
-//
-// var1 = Number of additional hitpoints to give
-// var2 = unused
-//
-void A_ForceShield(mobj_t *actor)
-{
-	player_t *player;
-	INT32 locvar1 = var1;
-
-#ifdef HAVE_BLUA
-	if (LUA_CallAction("A_ForceShield", actor))
-		return;
-#endif
-	if (!actor->target || !actor->target->player)
-	{
-		CONS_Debug(DBG_GAMELOGIC, "Powerup has no target.\n");
-		return;
-	}
-
-	if (locvar1 & ~SH_FORCEHP)
-	{
-		CONS_Debug(DBG_GAMELOGIC, "Invalid number of additional hitpoints.\n");
-		return;
-	}
-
-	player = actor->target->player;
-
-	P_SwitchShield(player, SH_FORCE|locvar1);
-
-	S_StartSound(player->mo, actor->info->seesound);
-}
-
-// Function: A_PityShield
-//
-// Description: Awards the player a pity shield.
-// Because you fail it.
-// Your skill is not enough.
-// See you next time.
-// Bye-bye.
-//
-// var1 = unused
-// var2 = unused
-//
-void A_PityShield(mobj_t *actor)
-{
-	player_t *player;
-
-#ifdef HAVE_BLUA
-	if (LUA_CallAction("A_PityShield", actor))
-		return;
-#endif
-	if (!actor->target || !actor->target->player)
-	{
-		CONS_Debug(DBG_GAMELOGIC, "Powerup has no target.\n");
-		return;
-	}
-
-	player = actor->target->player;
-
-	P_SwitchShield(player, SH_PITY);
-
-	S_StartSound(player->mo, actor->info->seesound);
-}
-
-// Function: A_FlameShield
-//
-// Description: Awards the player a flame shield.
-//
-// var1 = unused
-// var2 = unused
-//
-void A_FlameShield(mobj_t *actor)
-{
-	player_t *player;
-
-#ifdef HAVE_BLUA
-	if (LUA_CallAction("A_FlameShield", actor))
-		return;
-#endif
-	if (!actor->target || !actor->target->player)
-	{
-		CONS_Debug(DBG_GAMELOGIC, "Powerup has no target.\n");
-		return;
-	}
-
-	player = actor->target->player;
-
-	P_SwitchShield(player, SH_FLAMEAURA);
-
-	S_StartSound(player->mo, actor->info->seesound);
-}
-
-// Function: A_BubbleShield
-//
-// Description: Awards the player a bubble shield.
-//
-// var1 = unused
-// var2 = unused
-//
-void A_BubbleShield(mobj_t *actor)
-{
-	player_t *player;
-
-#ifdef HAVE_BLUA
-	if (LUA_CallAction("A_BubbleShield", actor))
-		return;
-#endif
-	if (!actor->target || !actor->target->player)
-	{
-		CONS_Debug(DBG_GAMELOGIC, "Powerup has no target.\n");
-		return;
+		P_GivePlayerRings(player, 100);
+		P_PlayLivesJingle(player);
 	}
-
-	player = actor->target->player;
-
-	P_SwitchShield(player, SH_BUBBLEWRAP);
-
-	S_StartSound(player->mo, actor->info->seesound);
+	else
+		P_GiveCoopLives(player, 1, true);
 }
 
-// Function: A_ThunderShield
+// Function: A_GiveShield
 //
-// Description: Awards the player a thunder shield.
+// Description: Awards the player a specified shield.
 //
-// var1 = unused
+// var1 = Shield type (make with SH_ constants)
 // var2 = unused
 //
-void A_ThunderShield(mobj_t *actor)
+void A_GiveShield(mobj_t *actor)
 {
 	player_t *player;
+	UINT16 locvar1 = var1;
 
 #ifdef HAVE_BLUA
-	if (LUA_CallAction("A_ThunderShield", actor))
+	if (LUA_CallAction("A_GiveShield", actor))
 		return;
 #endif
 	if (!actor->target || !actor->target->player)
@@ -3489,12 +3275,10 @@ void A_ThunderShield(mobj_t *actor)
 
 	player = actor->target->player;
 
-	P_SwitchShield(player, SH_THUNDERCOIN);
-
+	P_SwitchShield(player, locvar1);
 	S_StartSound(player->mo, actor->info->seesound);
 }
 
-
 // Function: A_GravityBox
 //
 // Description: Awards the player gravity boots.
@@ -3579,12 +3363,12 @@ void A_ParticleSpawn(mobj_t *actor)
 		spawn->tics = (tic_t)actor->health;
 		spawn->flags2 |= (actor->flags2 & MF2_OBJECTFLIP);
 		spawn->angle += P_RandomKey(36)*ANG10; // irrelevant for default objects but might make sense for some custom ones
-		if (spawn->frame & FF_ANIMATE)
-			spawn->frame += P_RandomKey(spawn->state->var1);
 
 		actor->angle += actor->movedir;
 	}
+
 	actor->angle += (angle_t)actor->movecount;
+	actor->tics = (tic_t)actor->reactiontime;
 }
 
 // Function: A_BunnyHop
@@ -4106,15 +3890,18 @@ void A_SetSolidSteam(mobj_t *actor)
 #endif
 	actor->flags &= ~MF_NOCLIP;
 	actor->flags |= MF_SOLID;
-	if (P_RandomChance(FRACUNIT/8))
-	{
-		if (actor->info->deathsound)
-			S_StartSound(actor, actor->info->deathsound); // Hiss!
-	}
-	else
+	if (!(actor->flags2 & MF2_AMBUSH))
 	{
-		if (actor->info->painsound)
-			S_StartSound(actor, actor->info->painsound);
+		if (P_RandomChance(FRACUNIT/8))
+		{
+			if (actor->info->deathsound)
+				S_StartSound(actor, actor->info->deathsound); // Hiss!
+		}
+		else
+		{
+			if (actor->info->painsound)
+				S_StartSound(actor, actor->info->painsound);
+		}
 	}
 
 	P_SetObjectMomZ (actor, 1, true);
@@ -4174,12 +3961,12 @@ void A_SignPlayer(mobj_t *actor)
 		of in the name. If you have a better idea, feel free
 		to let me know. ~toast 2016/07/20
 		*/
-		actor->frame += Color_Opposite[Color_Opposite[skin->prefoppositecolor*2]*2+1];
+		actor->frame += (15 - Color_Opposite[(Color_Opposite[(skin->prefoppositecolor - 1)*2] - 1)*2 + 1]);
 	}
-	else // Set the sign to be an appropriate background color for this player's skincolor.
+	else if (actor->target->player->skincolor) // Set the sign to be an appropriate background color for this player's skincolor.
 	{
-		actor->color = Color_Opposite[actor->target->player->skincolor*2];
-		actor->frame += Color_Opposite[actor->target->player->skincolor*2+1];
+		actor->color = Color_Opposite[(actor->target->player->skincolor - 1)*2];
+		actor->frame += (15 - Color_Opposite[(actor->target->player->skincolor - 1)*2 + 1]);
 	}
 
 	if (skin->sprites[SPR2_SIGN].numframes)
@@ -5141,15 +4928,12 @@ void A_SlingAppear(mobj_t *actor)
 	actor->movefactor = actor->threshold;
 	actor->friction = 128;
 
-	actor->flags |= MF_SLIDEME;
-
 	while (mlength > 0)
 	{
 		spawnee = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SMALLMACECHAIN);
 
 		P_SetTarget(&spawnee->target, actor);
 
-		spawnee->movecount = 0;
 		spawnee->threshold = 0;
 		spawnee->reactiontime = mlength;
 
@@ -5164,129 +4948,6 @@ void A_SlingAppear(mobj_t *actor)
 	}
 }
 
-//
-// Function: A_MaceRotate
-//
-// Spins an object around its target, or, swings it from side to side.
-//
-// var1 = unused
-// var2 = unused
-//
-// So NOBODY forgets:
-// actor->
-// threshold - X tilt
-// movecount - Z tilt
-// reactiontime - link # in the chain (1 is closest)
-// lastlook - speed
-// friction - top speed
-// movedir - current angle holder
-// extravalue1 - smoothly move link into place
-//
-void A_MaceRotate(mobj_t *actor)
-{
-	TVector v;
-	TVector *res;
-	fixed_t radius;
-#ifdef HAVE_BLUA
-	if (LUA_CallAction("A_MaceRotate", actor))
-		return;
-#endif
-
-	// Target was removed.
-	if (!actor->target)
-	{
-		P_RemoveMobj(actor);
-		return;
-	}
-
-	P_UnsetThingPosition(actor);
-
-	// Radius of the link's rotation.
-	radius = FixedMul(actor->info->speed * actor->reactiontime, actor->target->scale);
-
-	// Double the radius if the chain links are made up of maces.
-	if (actor->target->type == MT_AXIS && (actor->type == MT_SMALLMACE || actor->type == MT_BIGMACE))
-		radius *= 2;
-
-	// Axis offset for the axis.
-	radius += actor->target->extravalue1;
-
-	// Smoothly move the link into position.
-	if (actor->extravalue1)
-	{
-		radius = FixedMul(radius, FixedDiv(actor->extravalue1, 100));
-		actor->extravalue1 += 1;
-		if (actor->extravalue1 >= 100)
-			actor->extravalue1 = 0;
-	}
-
-	actor->x = actor->target->x;
-	actor->y = actor->target->y;
-	actor->z = actor->target->z;
-
-	// Cut the height to align the link with the axis.
-	if (actor->type == MT_SMALLMACECHAIN || actor->type == MT_BIGMACECHAIN)
-		actor->z -= actor->height/4;
-	else
-		actor->z -= actor->height/2;
-
-	// Set the top speed for the link if it happens to be over that speed.
-	if (actor->target->lastlook > actor->target->friction)
-		actor->target->lastlook = actor->target->friction;
-
-	// Swinging Chain.
-	if (actor->target->type == MT_HANGMACEPOINT || actor->target->type == MT_SWINGMACEPOINT)
-	{
-		actor->movecount += actor->target->lastlook;
-		actor->movecount &= FINEMASK;
-
-		actor->threshold = FixedMul(FINECOSINE(actor->movecount), actor->target->lastlook << FRACBITS);
-
-		v[0] = FRACUNIT;
-		v[1] = 0;
-		v[2] = -radius;
-		v[3] = FRACUNIT;
-
-		// Calculate the angle matrixes for the link.
-		res = VectorMatrixMultiply(v, *RotateXMatrix(FixedAngle(actor->threshold)));
-		M_Memcpy(&v, res, sizeof(v));
-		res = VectorMatrixMultiply(v, *RotateZMatrix(actor->target->health << ANGLETOFINESHIFT));
-		M_Memcpy(&v, res, sizeof(v));
-	}
-	// Rotating Chain.
-	else
-	{
-		angle_t fa;
-
-		actor->threshold += actor->target->lastlook;
-		actor->threshold &= FINEMASK;
-		actor->target->health &= FINEMASK;
-
-		fa = actor->threshold;
-		v[0] = FixedMul(FINECOSINE(fa), radius);
-		v[1] = 0;
-		v[2] = FixedMul(FINESINE(fa), radius);
-		v[3] = FRACUNIT;
-
-		// Calculate the angle matrixes for the link.
-		res = VectorMatrixMultiply(v, *RotateXMatrix(actor->target->threshold << ANGLETOFINESHIFT));
-		M_Memcpy(&v, res, sizeof(v));
-		res = VectorMatrixMultiply(v, *RotateZMatrix(actor->target->health << ANGLETOFINESHIFT));
-		M_Memcpy(&v, res, sizeof(v));
-	}
-
-	// Add on the appropriate distances to the actor's co-ordinates.
-	actor->x += v[0];
-	actor->y += v[1];
-	actor->z += v[2];
-
-	P_SetThingPosition(actor);
-
-	if (!(actor->target->flags2 & MF2_BOSSNOTRAP) // flag that makes maces shut up on request
-	&& !(leveltime & 63) && (actor->type == MT_BIGMACE || actor->type == MT_SMALLMACE) && actor->target->type == MT_MACEPOINT)
-		S_StartSound(actor, actor->info->activesound);
-}
-
 // Function: A_SetFuse
 //
 // Description: Sets the actor's fuse timer if not set already. May also change state when fuse reaches the last tic, otherwise by default the actor will die or disappear. (Replaces A_SnowBall)
@@ -5605,7 +5266,10 @@ void A_MixUp(mobj_t *actor)
 	// No mix-up monitors in hide and seek or time only race.
 	// The random factor is okay for other game modes, but in these, it is cripplingly unfair.
 	if (gametype == GT_HIDEANDSEEK || gametype == GT_RACE)
+	{
+		S_StartSound(actor, sfx_lose);
 		return;
+	}
 
 	numplayers = 0;
 	memset(teleported, 0, sizeof (teleported));
@@ -5623,7 +5287,10 @@ void A_MixUp(mobj_t *actor)
 		}
 
 	if (numplayers <= 1) // Not enough players to mix up.
+	{
+		S_StartSound(actor, sfx_lose);
 		return;
+	}
 	else if (numplayers == 2) // Special case -- simple swap
 	{
 		fixed_t x, y, z;
@@ -5869,7 +5536,10 @@ void A_RecyclePowers(mobj_t *actor)
 #endif
 
 	if (!multiplayer)
+	{
+		S_StartSound(actor, sfx_lose);
 		return;
+	}
 
 	numplayers = 0;
 
@@ -5905,7 +5575,10 @@ void A_RecyclePowers(mobj_t *actor)
 	}
 
 	if (numplayers <= 1)
+	{
+		S_StartSound(actor, sfx_lose);
 		return; //nobody to touch!
+	}
 
 	//shuffle the post scramble list, whee!
 	// hardcoded 0-1 to 1-0 for two players
@@ -9385,8 +9058,8 @@ void A_ForceWin(mobj_t *actor)
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
-		if (playeringame[i] && ((players[i].mo && players[i].mo->health > 0)
-		    || ((netgame || multiplayer) && (players[i].lives > 0 || players[i].continues > 0))))
+		if (playeringame[i] && ((players[i].mo && players[i].mo->health)
+		    || ((netgame || multiplayer) && (players[i].lives || players[i].continues))))
 			break;
 	}
 
@@ -10787,26 +10460,33 @@ void A_FlickyFlutter(mobj_t *actor)
 // Description: Creates the mobj's painchance at a random position around the object's radius.
 //
 // var1 = momz of particle.
+// var2 = chance of particle spawn
 //
 void A_FlameParticle(mobj_t *actor)
 {
 	mobjtype_t type = (mobjtype_t)(mobjinfo[actor->type].painchance);
+	fixed_t rad, hei;
+	mobj_t *particle;
 	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
 
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_FlameParticle", actor))
 		return;
 #endif
 
-	if (type)
-	{
-		fixed_t rad = 2*actor->radius>>FRACBITS;
-		fixed_t hei = actor->height>>FRACBITS;
-		mobj_t *particle = P_SpawnMobjFromMobj(actor,
-			P_RandomRange(rad, -rad)<<FRACBITS,
-			P_RandomRange(rad, -rad)<<FRACBITS,
-			P_RandomRange(hei/2, hei)<<FRACBITS,
-			type);
-		P_SetObjectMomZ(particle, locvar1<<FRACBITS, false);
-	}
+	if (!P_RandomChance(locvar2))
+		return;
+
+	if (!type)
+		return;
+
+	rad = 2*actor->radius>>FRACBITS;
+	hei = actor->height>>FRACBITS;
+	particle = P_SpawnMobjFromMobj(actor,
+		P_RandomRange(rad, -rad)<<FRACBITS,
+		P_RandomRange(rad, -rad)<<FRACBITS,
+		P_RandomRange(hei/2, hei)<<FRACBITS,
+		type);
+	P_SetObjectMomZ(particle, locvar1<<FRACBITS, false);
 }
diff --git a/src/p_floor.c b/src/p_floor.c
index d16c8b9ffe76bdf55d625a8fc956651f9c7d7aad..ef94bb95d8a648480a4ab47176a1f7c03fba41ff 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -2061,6 +2061,33 @@ void T_NoEnemiesSector(levelspecthink_t *nobaddies)
 	P_RemoveThinker(&nobaddies->thinker);
 }
 
+//
+// P_IsObjectOnRealGround
+//
+// Helper function for T_EachTimeThinker
+// Like P_IsObjectOnGroundIn, except ONLY THE REAL GROUND IS CONSIDERED, NOT FOFS
+// I'll consider whether to make this a more globally accessible function or whatever in future
+// -- Monster Iestyn
+//
+static boolean P_IsObjectOnRealGround(mobj_t *mo, sector_t *sec)
+{
+	// Is the object in reverse gravity?
+	if (mo->eflags & MFE_VERTICALFLIP)
+	{
+		// Detect if the player is on the ceiling.
+		if (mo->z+mo->height >= P_GetSpecialTopZ(mo, sec, sec))
+			return true;
+	}
+	// Nope!
+	else
+	{
+		// Detect if the player is on the floor.
+		if (mo->z <= P_GetSpecialBottomZ(mo, sec, sec))
+			return true;
+	}
+	return false;
+}
+
 //
 // P_HavePlayersEnteredArea
 //
@@ -2113,6 +2140,7 @@ void T_EachTimeThinker(levelspecthink_t *eachtime)
 	boolean inAndOut = false;
 	boolean floortouch = false;
 	fixed_t bottomheight, topheight;
+	msecnode_t *node;
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
@@ -2174,7 +2202,23 @@ void T_EachTimeThinker(levelspecthink_t *eachtime)
 					if ((netgame || multiplayer) && players[j].spectator)
 						continue;
 
-					if (players[j].mo->subsector->sector != targetsec)
+					if (players[j].mo->subsector->sector == targetsec)
+						;
+					else if (sec->flags & SF_TRIGGERSPECIAL_TOUCH)
+					{
+						boolean insector = false;
+						for (node = players[j].mo->touching_sectorlist; node; node = node->m_sectorlist_next)
+						{
+							if (node->m_sector == targetsec)
+							{
+								insector = true;
+								break;
+							}
+						}
+						if (!insector)
+							continue;
+					}
+					else
 						continue;
 
 					topheight = P_GetSpecialTopZ(players[j].mo, sec, targetsec);
@@ -2224,10 +2268,30 @@ void T_EachTimeThinker(levelspecthink_t *eachtime)
 				if ((netgame || multiplayer) && players[i].spectator)
 					continue;
 
-				if (players[i].mo->subsector->sector != sec)
+				if (players[i].mo->subsector->sector == sec)
+					;
+				else if (sec->flags & SF_TRIGGERSPECIAL_TOUCH)
+				{
+					boolean insector = false;
+					for (node = players[i].mo->touching_sectorlist; node; node = node->m_sectorlist_next)
+					{
+						if (node->m_sector == sec)
+						{
+							insector = true;
+							break;
+						}
+					}
+					if (!insector)
+						continue;
+				}
+				else
+					continue;
+
+				if (!(players[i].mo->subsector->sector == sec
+					|| P_PlayerTouchingSectorSpecial(&players[i], 2, (GETSECSPECIAL(sec->special, 2))) == sec))
 					continue;
 
-				if (floortouch == true && P_IsObjectOnGroundIn(players[i].mo, sec))
+				if (floortouch == true && P_IsObjectOnRealGround(players[i].mo, sec))
 				{
 					if (i & 1)
 						eachtime->var2s[i/2] |= 1;
@@ -2557,6 +2621,12 @@ void T_PlaneDisplace(planedisplace_t *pd)
 	direction = (control->floorheight > pd->last_height) ? 1 : -1;
 	diff = FixedMul(control->floorheight-pd->last_height, pd->speed);
 
+	if (pd->reverse) // reverse direction?
+	{
+		direction *= -1;
+		diff *= -1;
+	}
+
 	if (pd->type == pd_floor || pd->type == pd_both)
 		T_MovePlane(target, INT32_MAX/2, target->floorheight+diff, 0, 0, direction); // move floor
 	if (pd->type == pd_ceiling || pd->type == pd_both)
@@ -2923,7 +2993,7 @@ void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
 	fixed_t leftx, rightx;
 	fixed_t topy, bottomy;
 	fixed_t topz, bottomz;
-	fixed_t widthfactor, heightfactor;
+	fixed_t widthfactor = FRACUNIT, heightfactor = FRACUNIT;
 	fixed_t a, b, c;
 	mobjtype_t type = MT_ROCKCRUMBLE1;
 	fixed_t spacing = (32<<FRACBITS);
@@ -3240,14 +3310,6 @@ INT32 EV_MarioBlock(ffloor_t *rover, sector_t *sector, mobj_t *puncher)
 		}
 		else
 		{
-			if (thing->type == MT_EMMY && thing->spawnpoint && (thing->spawnpoint->options & MTF_OBJECTSPECIAL))
-			{
-				mobj_t *tokenobj = P_SpawnMobj(sector->soundorg.x, sector->soundorg.y, topheight, MT_TOKEN);
-				P_SetTarget(&thing->tracer, tokenobj);
-				P_SetTarget(&tokenobj->target, thing);
-				P_SetMobjState(tokenobj, mobjinfo[MT_TOKEN].seestate);
-			}
-
 			// "Powerup rise" sound
 			S_StartSound(puncher, sfx_mario9); // Puncher is "close enough"
 		}
diff --git a/src/p_inter.c b/src/p_inter.c
index f5255a2f7d1d74ebbdead3d6100dc4474a9bcffd..d2101ca575ae358648a1ed863f642dce57b2d0d2 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -257,6 +257,8 @@ void P_DoMatchSuper(player_t *player)
 		S_StopMusic();
 		if (mariomode)
 			G_GhostAddColor(GHC_INVINCIBLE);
+		strlcpy(S_sfx[sfx_None].caption, "Invincibility", 14);
+		S_StartCaption(sfx_None, -1, player->powers[pw_invulnerability]);
 		S_ChangeMusicInternal((mariomode) ? "_minv" : "_inv", false);
 	}
 
@@ -278,6 +280,8 @@ void P_DoMatchSuper(player_t *player)
 					S_StopMusic();
 					if (mariomode)
 						G_GhostAddColor(GHC_INVINCIBLE);
+					strlcpy(S_sfx[sfx_None].caption, "Invincibility", 14);
+					S_StartCaption(sfx_None, -1, player->powers[pw_invulnerability]);
 					S_ChangeMusicInternal((mariomode) ? "_minv" : "_inv", false);
 				}
 			}
@@ -410,13 +414,15 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 		////////////////////////////////////////////////////////
 		if (special->type == MT_GSNAPPER && !(((player->powers[pw_carry] == CR_NIGHTSMODE) && (player->pflags & PF_DRILLING))
 		|| player->powers[pw_invulnerability] || player->powers[pw_super] || elementalpierce)
-		&& toucher->z < special->z + special->height && toucher->z + toucher->height > special->z)
+		&& toucher->z < special->z + special->height && toucher->z + toucher->height > special->z
+		&& !(player->powers[pw_shield] & SH_PROTECTSPIKE))
 		{
 			// Can only hit snapper from above
-			P_DamageMobj(toucher, special, special, 1, 0);
+			P_DamageMobj(toucher, special, special, 1, DMG_SPIKE);
 		}
 		else if (special->type == MT_SHARP
-		&& ((special->state == &states[special->info->xdeathstate]) || (toucher->z > special->z + special->height/2)))
+		&& ((special->state == &states[special->info->xdeathstate]) || (toucher->z > special->z + special->height/2))
+		&& !(player->powers[pw_shield] & SH_PROTECTSPIKE))
 		{
 			if (player->pflags & PF_BOUNCING)
 			{
@@ -424,7 +430,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				P_DoAbilityBounce(player, false);
 			}
 			else // Cannot hit sharp from above or when red and angry
-				P_DamageMobj(toucher, special, special, 1, 0);
+				P_DamageMobj(toucher, special, special, 1, DMG_SPIKE);
 		}
 		else if (((player->powers[pw_carry] == CR_NIGHTSMODE) && (player->pflags & PF_DRILLING))
 		|| ((player->pflags & PF_JUMPED) && (!(player->pflags & PF_NOJUMPDAMAGE) || (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)))
@@ -566,21 +572,25 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 // Gameplay related collectibles //
 // ***************************** //
 		// Special Stage Token
-		case MT_EMMY:
+		case MT_TOKEN:
 			if (player->bot)
 				return;
 			tokenlist += special->health;
 
+			P_AddPlayerScore(player, 1000);
+
 			if (ALL7EMERALDS(emeralds)) // Got all 7
 			{
-				P_GivePlayerRings(player, 50);
-				nummaprings += 50; // no cheating towards Perfect!
+				if (!(netgame || multiplayer))
+				{
+					player->continues += 1;
+					players->gotcontinue = true;
+					if (P_IsLocalPlayer(player))
+						S_StartSound(NULL, sfx_s3kac);
+				}
 			}
 			else
 				token++;
-
-			if (special->tracer) // token BG
-				P_RemoveMobj(special->tracer);
 			break;
 
 		// Emerald Hunt
@@ -870,7 +880,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 
 						if (!(mo2->flags & MF_SPECIAL) && mo2->health)
 						{
-							P_SetMobjState(mo2, mo2->info->seestate);
+							mo2->flags2 &= ~MF2_DONTDRAW;
 							mo2->flags |= MF_SPECIAL;
 							mo2->flags &= ~MF_NIGHTSITEM;
 							S_StartSound(toucher, sfx_hidden);
@@ -879,7 +889,8 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 					}
 
 					if (!(mo2->type == MT_NIGHTSWING || mo2->type == MT_RING || mo2->type == MT_COIN
-					   || mo2->type == MT_BLUEBALL))
+					   || mo2->type == MT_BLUEBALL
+					   || ((mo2->type == MT_EMBLEM) && (mo2->reactiontime & GE_NIGHTSPULL))))
 						continue;
 
 					// Yay! The thing's in reach! Pull it in!
@@ -1282,13 +1293,40 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			if (player->starpostnum >= special->health)
 				return; // Already hit this post
 
-			// Save the player's time and position.
-			player->starposttime = leveltime;
-			player->starpostx = toucher->x>>FRACBITS;
-			player->starposty = toucher->y>>FRACBITS;
-			player->starpostz = special->z>>FRACBITS;
-			player->starpostangle = special->angle;
-			player->starpostnum = special->health;
+			if (cv_coopstarposts.value && gametype == GT_COOP && (netgame || multiplayer))
+			{
+				for (i = 0; i < MAXPLAYERS; i++)
+				{
+					if (playeringame[i])
+					{
+						if (players[i].bot) // ignore dumb, stupid tails
+							continue;
+
+						players[i].starposttime = leveltime;
+						players[i].starpostx = player->mo->x>>FRACBITS;
+						players[i].starposty = player->mo->y>>FRACBITS;
+						players[i].starpostz = special->z>>FRACBITS;
+						players[i].starpostangle = special->angle;
+						players[i].starpostnum = special->health;
+
+						if (cv_coopstarposts.value == 2 && (players[i].playerstate == PST_DEAD || players[i].spectator) && P_GetLives(&players[i]))
+							P_SpectatorJoinGame(&players[i]); //players[i].playerstate = PST_REBORN;
+					}
+				}
+				S_StartSound(NULL, special->info->painsound);
+			}
+			else
+			{
+				// Save the player's time and position.
+				player->starposttime = leveltime;
+				player->starpostx = toucher->x>>FRACBITS;
+				player->starposty = toucher->y>>FRACBITS;
+				player->starpostz = special->z>>FRACBITS;
+				player->starpostangle = special->angle;
+				player->starpostnum = special->health;
+				S_StartSound(toucher, special->info->painsound);
+			}
+
 			P_ClearStarPost(special->health);
 
 			// Find all starposts in the level with this value.
@@ -1460,10 +1498,19 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			if (player->powers[pw_flashing])
 				return;
 
+			if (special->movefactor && special->tracer && (angle_t)special->tracer->health != ANGLE_90 && (angle_t)special->tracer->health != ANGLE_270)
+			{ // I don't expect you to understand this, Mr Bond...
+				angle_t ang = R_PointToAngle2(special->x, special->y, toucher->x, toucher->y) - special->tracer->threshold;
+				if ((special->movefactor > 0) == ((angle_t)special->tracer->health > ANGLE_90 && (angle_t)special->tracer->health < ANGLE_270))
+					ang += ANGLE_180;
+				if (ang < ANGLE_180)
+					return; // I expect you to die.
+			}
+
 			P_ResetPlayer(player);
 			P_SetTarget(&toucher->tracer, special);
 
-			if (special->target && (special->target->type == MT_SPINMACEPOINT || special->target->type == MT_HIDDEN_SLING))
+			if (special->tracer && !(special->tracer->flags2 & MF2_STRONGBOX))
 			{
 				player->powers[pw_carry] = CR_MACESPIN;
 				S_StartSound(toucher, sfx_spin);
@@ -1474,6 +1521,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 
 			// Can't jump first frame
 			player->pflags |= PF_JUMPSTASIS;
+
 			return;
 		case MT_BIGMINE:
 		case MT_BIGAIRMINE:
@@ -1657,7 +1705,7 @@ static void P_HitDeathMessages(player_t *player, mobj_t *inflictor, mobj_t *sour
 					if (damagetype == DMG_NUKE) // SH_ARMAGEDDON, armageddon shield
 						str = M_GetText("%s%s's armageddon blast %s %s.\n");
 					else if ((inflictor->player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL && (inflictor->player->pflags & PF_SHIELDABILITY))
-						str = M_GetText("%s%s's flame stomp %s %s.\n");
+						str = M_GetText("%s%s's elemental stomp %s %s.\n");
 					else if (inflictor->player->powers[pw_invulnerability])
 						str = M_GetText("%s%s's invincibility aura %s %s.\n");
 					else if (inflictor->player->powers[pw_super])
@@ -1711,6 +1759,7 @@ static void P_HitDeathMessages(player_t *player, mobj_t *inflictor, mobj_t *sour
 				str = M_GetText("%s was %s by Eggman's nefarious TV magic.\n");
 				break;
 			case MT_SPIKE:
+			case MT_WALLSPIKE:
 				str = M_GetText("%s was %s by spikes.\n");
 				break;
 			default:
@@ -2097,7 +2146,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 	{
 		if (metalrecording) // Ack! Metal Sonic shouldn't die! Cut the tape, end recording!
 			G_StopMetalRecording();
-		if (gametype == GT_MATCH && cv_match_scoring.value == 0 // note, no team match suicide penalty
+		if (gametype == GT_MATCH // note, no team match suicide penalty
 			&& ((target == source) || (source == NULL && inflictor == NULL) || (source && !source->player)))
 		{ // Suicide penalty
 			if (target->player->score >= 50)
@@ -2208,14 +2257,34 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 		target->flags |= MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY;
 		P_SetThingPosition(target);
 
-		if (!target->player->bot && !G_IsSpecialStage(gamemap)
+		if ((target->player->lives <= 1) && (netgame || multiplayer) && (gametype == GT_COOP) && (cv_cooplives.value == 0))
+			;
+		else if (!target->player->bot && !target->player->spectator && !G_IsSpecialStage(gamemap)
 		 && G_GametypeUsesLives())
 		{
 			target->player->lives -= 1; // Lose a life Tails 03-11-2000
 
 			if (target->player->lives <= 0) // Tails 03-14-2000
 			{
-				if (P_IsLocalPlayer(target->player)/* && target->player == &players[consoleplayer] */)
+				boolean gameovermus = false;
+				if ((netgame || multiplayer) && (gametype == GT_COOP) && (cv_cooplives.value != 1))
+				{
+					INT32 i;
+					for (i = 0; i < MAXPLAYERS; i++)
+					{
+						if (!playeringame[i])
+							continue;
+
+						if (players[i].lives > 0)
+							break;
+					}
+					if (i == MAXPLAYERS)
+						gameovermus = true;
+				}
+				else if (P_IsLocalPlayer(target->player))
+					gameovermus = true;
+
+				if (gameovermus)
 				{
 					S_StopMusic(); // Stop the Music! Tails 03-14-2000
 					S_ChangeMusicInternal("_gover", false); // Yousa dead now, Okieday? Tails 03-14-2000
@@ -2387,7 +2456,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 				else
 				{
 					P_SetObjectMomZ(target, 14*FRACUNIT, false);
-					if ((source && source->type == MT_SPIKE) || damagetype == DMG_SPIKE) // Spikes
+					if (damagetype == DMG_SPIKE) // Spikes
 						S_StartSound(target, sfx_spkdth);
 					else
 						P_PlayDeathSound(target);
@@ -2449,90 +2518,159 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 		}
 	}
 
-	if (target->type == MT_SPIKE && inflictor && target->info->deathstate != S_NULL)
+	if (target->type == MT_SPIKE && target->info->deathstate != S_NULL)
 	{
-		const fixed_t x=target->x,y=target->y,z=target->z;
-		const fixed_t scale=target->scale;
-		const boolean flip=(target->eflags & MFE_VERTICALFLIP) == MFE_VERTICALFLIP;
-		S_StartSound(target,target->info->deathsound);
+		const angle_t ang = ((inflictor) ? inflictor->angle : 0) + ANGLE_90;
+		const fixed_t scale = target->scale;
+		const fixed_t xoffs = P_ReturnThrustX(target, ang, 8*scale), yoffs = P_ReturnThrustY(target, ang, 8*scale);
+		const UINT16 flip = (target->eflags & MFE_VERTICALFLIP);
+		mobj_t *chunk;
+		fixed_t momz;
+
+		S_StartSound(target, target->info->deathsound);
+
+		if (target->info->xdeathstate != S_NULL)
+		{
+			momz = 6*scale;
+			if (flip)
+				momz *= -1;
+#define makechunk(angtweak, xmov, ymov) \
+			chunk = P_SpawnMobj(target->x, target->y, target->z, MT_SPIKE);\
+			chunk->eflags |= flip;\
+			P_SetMobjState(chunk, target->info->xdeathstate);\
+			chunk->health = 0;\
+			chunk->angle = angtweak;\
+			chunk->destscale = scale;\
+			P_SetScale(chunk, scale);\
+			P_UnsetThingPosition(chunk);\
+			chunk->flags = MF_NOCLIP;\
+			chunk->x += xmov;\
+			chunk->y += ymov;\
+			P_SetThingPosition(chunk);\
+			P_InstaThrust(chunk,chunk->angle, 4*scale);\
+			chunk->momz = momz
+
+			makechunk(ang + ANGLE_180, -xoffs, -yoffs);
+			makechunk(ang, xoffs, yoffs);
+
+#undef makechunk
+		}
+
+		momz = 7*scale;
+		if (flip)
+			momz *= -1;
+
+		chunk = P_SpawnMobj(target->x, target->y, target->z, MT_SPIKE);
+		chunk->eflags |= flip;
+
+		P_SetMobjState(chunk, target->info->deathstate);
+		chunk->health = 0;
+		chunk->angle = ang + ANGLE_180;
+		chunk->destscale = scale;
+		P_SetScale(chunk, scale);
+		P_UnsetThingPosition(chunk);
+		chunk->flags = MF_NOCLIP;
+		chunk->x -= xoffs;
+		chunk->y -= yoffs;
+		if (flip)
+			chunk->z -= 12*scale;
+		else
+			chunk->z += 12*scale;
+		P_SetThingPosition(chunk);
+		P_InstaThrust(chunk, chunk->angle, 2*scale);
+		chunk->momz = momz;
 
 		P_SetMobjState(target, target->info->deathstate);
 		target->health = 0;
-		target->angle = inflictor->angle + ANGLE_90;
+		target->angle = ang;
 		P_UnsetThingPosition(target);
 		target->flags = MF_NOCLIP;
-		target->x += P_ReturnThrustX(target, target->angle, FixedMul(8*FRACUNIT, target->scale));
-		target->y += P_ReturnThrustY(target, target->angle, FixedMul(8*FRACUNIT, target->scale));
-		if (flip)
-			target->z -= FixedMul(12*FRACUNIT, target->scale);
-		else
-			target->z += FixedMul(12*FRACUNIT, target->scale);
+		target->x += xoffs;
+		target->y += yoffs;
+		target->z = chunk->z;
 		P_SetThingPosition(target);
-		P_InstaThrust(target,target->angle,FixedMul(2*FRACUNIT, target->scale));
-		target->momz = FixedMul(7*FRACUNIT, target->scale);
-		if (flip)
-			target->momz = -target->momz;
+		P_InstaThrust(target, target->angle, 2*scale);
+		target->momz = momz;
+	}
+	else if (target->type == MT_WALLSPIKE && target->info->deathstate != S_NULL)
+	{
+		const angle_t ang = (/*(inflictor) ? inflictor->angle : */target->angle) + ANGLE_90;
+		const fixed_t scale = target->scale;
+		const fixed_t xoffs = P_ReturnThrustX(target, ang, 8*scale), yoffs = P_ReturnThrustY(target, ang, 8*scale), forwardxoffs = P_ReturnThrustX(target, target->angle, 7*scale), forwardyoffs = P_ReturnThrustY(target, target->angle, 7*scale);
+		const UINT16 flip = (target->eflags & MFE_VERTICALFLIP);
+		mobj_t *chunk;
+		boolean sprflip;
 
-		if (flip)
+		S_StartSound(target, target->info->deathsound);
+		if (!P_MobjWasRemoved(target->tracer))
+			P_RemoveMobj(target->tracer);
+
+		if (target->info->xdeathstate != S_NULL)
 		{
-			target = P_SpawnMobj(x,y,z-FixedMul(12*FRACUNIT, target->scale),MT_SPIKE);
-			target->eflags |= MFE_VERTICALFLIP;
-		}
-		else
-			target = P_SpawnMobj(x,y,z+FixedMul(12*FRACUNIT, target->scale),MT_SPIKE);
+			sprflip = P_RandomChance(FRACUNIT/2);
+
+#define makechunk(angtweak, xmov, ymov) \
+			chunk = P_SpawnMobj(target->x, target->y, target->z, MT_WALLSPIKE);\
+			chunk->eflags |= flip;\
+			P_SetMobjState(chunk, target->info->xdeathstate);\
+			chunk->health = 0;\
+			chunk->angle = target->angle;\
+			chunk->destscale = scale;\
+			P_SetScale(chunk, scale);\
+			P_UnsetThingPosition(chunk);\
+			chunk->flags = MF_NOCLIP;\
+			chunk->x += xmov - forwardxoffs;\
+			chunk->y += ymov - forwardyoffs;\
+			P_SetThingPosition(chunk);\
+			P_InstaThrust(chunk, angtweak, 4*scale);\
+			chunk->momz = P_RandomRange(5, 7)*scale;\
+			if (flip)\
+				chunk->momz *= -1;\
+			if (sprflip)\
+				chunk->frame |= FF_VERTICALFLIP
+
+			makechunk(ang + ANGLE_180, -xoffs, -yoffs);
+			sprflip = !sprflip;
+			makechunk(ang, xoffs, yoffs);
+
+#undef makechunk
+		}
+
+		sprflip = P_RandomChance(FRACUNIT/2);
+
+		chunk = P_SpawnMobj(target->x, target->y, target->z, MT_WALLSPIKE);
+		chunk->eflags |= flip;
+
+		P_SetMobjState(chunk, target->info->deathstate);
+		chunk->health = 0;
+		chunk->angle = target->angle;
+		chunk->destscale = scale;
+		P_SetScale(chunk, scale);
+		P_UnsetThingPosition(chunk);
+		chunk->flags = MF_NOCLIP;
+		chunk->x += forwardxoffs - xoffs;
+		chunk->y += forwardyoffs - yoffs;
+		P_SetThingPosition(chunk);
+		P_InstaThrust(chunk, ang + ANGLE_180, 2*scale);
+		chunk->momz = P_RandomRange(5, 7)*scale;
+		if (flip)
+			chunk->momz *= -1;
+		if (sprflip)
+			chunk->frame |= FF_VERTICALFLIP;
+
 		P_SetMobjState(target, target->info->deathstate);
 		target->health = 0;
-		target->angle = inflictor->angle - ANGLE_90;
-		target->destscale = scale;
-		P_SetScale(target, scale);
 		P_UnsetThingPosition(target);
 		target->flags = MF_NOCLIP;
-		target->x += P_ReturnThrustX(target, target->angle, FixedMul(8*FRACUNIT, target->scale));
-		target->y += P_ReturnThrustY(target, target->angle, FixedMul(8*FRACUNIT, target->scale));
+		target->x += forwardxoffs + xoffs;
+		target->y += forwardyoffs + yoffs;
 		P_SetThingPosition(target);
-		P_InstaThrust(target,target->angle,FixedMul(2*FRACUNIT, target->scale));
-		target->momz = FixedMul(7*FRACUNIT, target->scale);
+		P_InstaThrust(target, ang, 2*scale);
+		target->momz = P_RandomRange(5, 7)*scale;
 		if (flip)
-			target->momz = -target->momz;
-
-		if (target->info->xdeathstate != S_NULL)
-		{
-			target = P_SpawnMobj(x,y,z,MT_SPIKE);
-			if (flip)
-				target->eflags |= MFE_VERTICALFLIP;
-			P_SetMobjState(target, target->info->xdeathstate);
-			target->health = 0;
-			target->angle = inflictor->angle + ANGLE_90;
-			target->destscale = scale;
-			P_SetScale(target, scale);
-			P_UnsetThingPosition(target);
-			target->flags = MF_NOCLIP;
-			target->x += P_ReturnThrustX(target, target->angle, FixedMul(8*FRACUNIT, target->scale));
-			target->y += P_ReturnThrustY(target, target->angle, FixedMul(8*FRACUNIT, target->scale));
-			P_SetThingPosition(target);
-			P_InstaThrust(target,target->angle,FixedMul(4*FRACUNIT, target->scale));
-			target->momz = FixedMul(6*FRACUNIT, target->scale);
-			if (flip)
-				target->momz = -target->momz;
-
-			target = P_SpawnMobj(x,y,z,MT_SPIKE);
-			if (flip)
-				target->eflags |= MFE_VERTICALFLIP;
-			P_SetMobjState(target, target->info->xdeathstate);
-			target->health = 0;
-			target->angle = inflictor->angle - ANGLE_90;
-			target->destscale = scale;
-			P_SetScale(target, scale);
-			P_UnsetThingPosition(target);
-			target->flags = MF_NOCLIP;
-			target->x += P_ReturnThrustX(target, target->angle, FixedMul(8*FRACUNIT, target->scale));
-			target->y += P_ReturnThrustY(target, target->angle, FixedMul(8*FRACUNIT, target->scale));
-			P_SetThingPosition(target);
-			P_InstaThrust(target,target->angle,FixedMul(4*FRACUNIT, target->scale));
-			target->momz = FixedMul(6*FRACUNIT, target->scale);
-			if (flip)
-				target->momz = -target->momz;
-		}
+			target->momz *= -1;
+		if (!sprflip)
+			target->frame |= FF_VERTICALFLIP;
 	}
 	else if (target->player)
 	{
@@ -2868,7 +3006,7 @@ static void P_ShieldDamage(player_t *player, mobj_t *inflictor, mobj_t *source,
 
 	P_ForceFeed(player, 40, 10, TICRATE, 40 + min(damage, 100)*2);
 
-	if ((source && source->type == MT_SPIKE) || damagetype == DMG_SPIKE) // spikes
+	if (damagetype == DMG_SPIKE) // spikes
 		S_StartSound(player->mo, sfx_spkdth);
 	else
 		S_StartSound (player->mo, sfx_shldls); // Ba-Dum! Shield loss.
@@ -2887,7 +3025,7 @@ static void P_ShieldDamage(player_t *player, mobj_t *inflictor, mobj_t *source,
 	{
 		// Award no points when players shoot each other when cv_friendlyfire is on.
 		if (!G_GametypeHasTeams() || !(source->player->ctfteam == player->ctfteam && source != player->mo))
-			P_AddPlayerScore(source->player, cv_match_scoring.value == 1 ? 25 : 50);
+			P_AddPlayerScore(source->player, 50);
 	}
 }
 
@@ -2897,7 +3035,7 @@ static void P_RingDamage(player_t *player, mobj_t *inflictor, mobj_t *source, IN
 
 	P_ForceFeed(player, 40, 10, TICRATE, 40 + min(damage, 100)*2);
 
-	if ((source && source->type == MT_SPIKE) || damagetype == DMG_SPIKE) // spikes
+	if (damagetype == DMG_SPIKE) // spikes
 		S_StartSound(player->mo, sfx_spkdth);
 
 	if (source && source->player && !player->powers[pw_super]) //don't score points against super players
@@ -3075,18 +3213,16 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 
 			switch (damagetype)
 			{
-				case DMG_WATER:
-					if (player->powers[pw_shield] & SH_PROTECTWATER)
-						return false; // Invincible to water damage
-					break;
-				case DMG_FIRE:
-					if (player->powers[pw_shield] & SH_PROTECTFIRE)
-						return false; // Invincible to fire damage
-					break;
-				case DMG_ELECTRIC:
-					if (player->powers[pw_shield] & SH_PROTECTELECTRIC)
-						return false; // Invincible to electric damage
-					break;
+#define DAMAGECASE(type)\
+				case DMG_##type:\
+					if (player->powers[pw_shield] & SH_PROTECT##type)\
+						return false;\
+					break
+				DAMAGECASE(WATER);
+				DAMAGECASE(FIRE);
+				DAMAGECASE(ELECTRIC);
+				DAMAGECASE(SPIKE);
+#undef DAMAGECASE
 				default:
 					break;
 			}
@@ -3120,14 +3256,6 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
 				return false; // Don't get hurt by fire generated from friends.
 		}
 
-		// Sudden-Death mode
-		if (source && source->type == MT_PLAYER)
-		{
-			if ((gametype == GT_MATCH || gametype == GT_TEAMMATCH || gametype == GT_CTF) && cv_suddendeath.value
-				&& !player->powers[pw_flashing] && !player->powers[pw_invulnerability])
-				damagetype = DMG_INSTAKILL;
-		}
-
 		// Player hits another player
 		if (!force && source && source->player)
 		{
diff --git a/src/p_local.h b/src/p_local.h
index a1b07e952d4d531aae8d8d0f850a77c1a07d5b7d..b1bfc645641b39c04728d2bc45828a01214a6228 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -72,7 +72,6 @@
 
 // both the head and tail of the thinker list
 extern thinker_t thinkercap;
-extern INT32 runcount;
 
 void P_InitThinkers(void);
 void P_AddThinker(thinker_t *thinker);
@@ -149,6 +148,7 @@ void P_SwitchShield(player_t *player, UINT16 shieldtype);
 mobj_t *P_SpawnGhostMobj(mobj_t *mobj);
 void P_GivePlayerRings(player_t *player, INT32 num_rings);
 void P_GivePlayerLives(player_t *player, INT32 numlives);
+void P_GiveCoopLives(player_t *player, INT32 numlives, boolean sound);
 UINT8 P_GetNextEmerald(void);
 void P_GiveEmerald(boolean spawnObj);
 #if 0
@@ -199,6 +199,9 @@ void P_PlayLivesJingle(player_t *player);
 #define P_PlayDeathSound(s)		S_StartSound(s, sfx_altdi1 + P_RandomKey(4));
 #define P_PlayVictorySound(s)	S_StartSound(s, sfx_victr1 + P_RandomKey(4));
 
+boolean P_GetLives(player_t *player);
+boolean P_SpectatorJoinGame(player_t *player);
+void P_RestoreMultiMusic(player_t *player);
 
 //
 // P_MOBJ
@@ -225,7 +228,6 @@ void P_PrecipitationEffects(void);
 void P_RemoveMobj(mobj_t *th);
 boolean P_MobjWasRemoved(mobj_t *th);
 void P_RemoveSavegameMobj(mobj_t *th);
-UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2);
 boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state);
 boolean P_SetMobjState(mobj_t *mobj, statenum_t state);
 void P_RunShields(void);
diff --git a/src/p_map.c b/src/p_map.c
index 87effd09dc487c25fbf1c6a521997f543f673ccb..0339ca4a5a0700be14d14978b6b997b841721fd7 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -443,7 +443,9 @@ static boolean PIT_CheckThing(mobj_t *thing)
 
 	// Metal Sonic destroys tiny baby objects.
 	if (tmthing->type == MT_METALSONIC_RACE
-	&& (thing->flags & (MF_MISSILE|MF_ENEMY|MF_BOSS) || thing->type == MT_SPIKE))
+	&& (thing->flags & (MF_MISSILE|MF_ENEMY|MF_BOSS)
+	|| (thing->type == MT_SPIKE
+	|| thing->type == MT_WALLSPIKE)))
 	{
 		if ((thing->flags & (MF_ENEMY|MF_BOSS)) && (thing->health <= 0 || !(thing->flags & MF_SHOOTABLE)))
 			return true;
@@ -455,12 +457,14 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			return true; // overhead
 		if (tmthing->z + tmthing->height < thing->z)
 			return true; // underneath
-		if (thing->type == MT_SPIKE)
+		if (thing->type == MT_SPIKE
+		|| thing->type == MT_WALLSPIKE)
 		{
+			mobjtype_t type = thing->type;
 			if (thing->flags & MF_SOLID)
 				S_StartSound(tmthing, thing->info->deathsound);
 			for (thing = thing->subsector->sector->thinglist; thing; thing = thing->snext)
-				if (thing->type == MT_SPIKE && thing->health > 0 && thing->flags & MF_SOLID && P_AproxDistance(thing->x - tmthing->x, thing->y - tmthing->y) < FixedMul(56*FRACUNIT, thing->scale))
+				if (thing->type == type && thing->health > 0 && thing->flags & MF_SOLID && P_AproxDistance(P_AproxDistance(thing->x - tmthing->x, thing->y - tmthing->y), thing->z - tmthing->z) < 56*thing->scale)//FixedMul(56*FRACUNIT, thing->scale))
 					P_KillMobj(thing, tmthing, tmthing, 0);
 		}
 		else
@@ -474,10 +478,13 @@ static boolean PIT_CheckThing(mobj_t *thing)
 	// SF_DASHMODE users destroy spikes and monitors, CA_TWINSPIN users and CA2_MELEE users destroy spikes.
 	if ((tmthing->player)
 		&& (((tmthing->player->charflags & SF_DASHMODE) && (tmthing->player->dashmode >= 3*TICRATE)
-		&& (thing->flags & (MF_MONITOR) || thing->type == MT_SPIKE))
+		&& (thing->flags & (MF_MONITOR)
+		|| (thing->type == MT_SPIKE
+		|| thing->type == MT_WALLSPIKE)))
 	|| ((((tmthing->player->charability == CA_TWINSPIN) && (tmthing->player->panim == PA_ABILITY))
 	|| (tmthing->player->charability2 == CA2_MELEE && tmthing->player->panim == PA_ABILITY2))
-		&& (thing->type == MT_SPIKE))))
+		&& (thing->type == MT_SPIKE
+		|| thing->type == MT_WALLSPIKE))))
 	{
 		if ((thing->flags & (MF_MONITOR)) && (thing->health <= 0 || !(thing->flags & MF_SHOOTABLE)))
 			return true;
@@ -489,12 +496,14 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			return true; // overhead
 		if (tmthing->z + tmthing->height < thing->z)
 			return true; // underneath
-		if (thing->type == MT_SPIKE)
+		if (thing->type == MT_SPIKE
+		|| thing->type == MT_WALLSPIKE)
 		{
+			mobjtype_t type = thing->type;
 			if (thing->flags & MF_SOLID)
 				S_StartSound(tmthing, thing->info->deathsound);
 			for (thing = thing->subsector->sector->thinglist; thing; thing = thing->snext)
-				if (thing->type == MT_SPIKE && thing->health > 0 && thing->flags & MF_SOLID && P_AproxDistance(thing->x - tmthing->x, thing->y - tmthing->y) < FixedMul(56*FRACUNIT, thing->scale))
+				if (thing->type == type && thing->health > 0 && thing->flags & MF_SOLID && P_AproxDistance(P_AproxDistance(thing->x - tmthing->x, thing->y - tmthing->y), thing->z - tmthing->z) < 56*thing->scale)//FixedMul(56*FRACUNIT, thing->scale))
 					P_KillMobj(thing, tmthing, tmthing, 0);
 		}
 		else
@@ -941,12 +950,12 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			if (thing->z + thing->height <= tmthing->z + FixedMul(FRACUNIT, tmthing->scale)
 			&& thing->z + thing->height + thing->momz  >= tmthing->z + FixedMul(FRACUNIT, tmthing->scale) + tmthing->momz
 			&& !(thing->player->charability == CA_BOUNCE && thing->player->panim == PA_ABILITY && thing->eflags & MFE_VERTICALFLIP))
-				P_DamageMobj(thing, tmthing, tmthing, 1, 0);
+				P_DamageMobj(thing, tmthing, tmthing, 1, DMG_SPIKE);
 		}
 		else if (thing->z >= tmthing->z + tmthing->height - FixedMul(FRACUNIT, tmthing->scale)
 		&& thing->z + thing->momz <= tmthing->z + tmthing->height - FixedMul(FRACUNIT, tmthing->scale) + tmthing->momz
 		&& !(thing->player->charability == CA_BOUNCE && thing->player->panim == PA_ABILITY && !(thing->eflags & MFE_VERTICALFLIP)))
-			P_DamageMobj(thing, tmthing, tmthing, 1, 0);
+			P_DamageMobj(thing, tmthing, tmthing, 1, DMG_SPIKE);
 	}
 	else if (thing->type == MT_SPIKE && thing->flags & MF_SOLID && tmthing->player) // unfortunate player falls into spike?!
 	{
@@ -955,12 +964,61 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			if (tmthing->z + tmthing->height <= thing->z - FixedMul(FRACUNIT, thing->scale)
 			&& tmthing->z + tmthing->height + tmthing->momz >= thing->z - FixedMul(FRACUNIT, thing->scale)
 			&& !(tmthing->player->charability == CA_BOUNCE && tmthing->player->panim == PA_ABILITY && tmthing->eflags & MFE_VERTICALFLIP))
-				P_DamageMobj(tmthing, thing, thing, 1, 0);
+				P_DamageMobj(tmthing, thing, thing, 1, DMG_SPIKE);
 		}
 		else if (tmthing->z >= thing->z + thing->height + FixedMul(FRACUNIT, thing->scale)
 		&& tmthing->z + tmthing->momz <= thing->z + thing->height + FixedMul(FRACUNIT, thing->scale)
 		&& !(tmthing->player->charability == CA_BOUNCE && tmthing->player->panim == PA_ABILITY && !(tmthing->eflags & MFE_VERTICALFLIP)))
-			P_DamageMobj(tmthing, thing, thing, 1, 0);
+			P_DamageMobj(tmthing, thing, thing, 1, DMG_SPIKE);
+	}
+
+	if (tmthing->type == MT_WALLSPIKE && tmthing->flags & MF_SOLID && thing->player) // wall spike impales player
+	{
+		fixed_t bottomz, topz;
+		bottomz = tmthing->z;
+		topz = tmthing->z + tmthing->height;
+		if (tmthing->eflags & MFE_VERTICALFLIP)
+			bottomz -= FixedMul(FRACUNIT, tmthing->scale);
+		else
+			topz += FixedMul(FRACUNIT, tmthing->scale);
+
+		if (thing->z + thing->height > bottomz // above bottom
+		&&  thing->z < topz) // below top
+		// don't check angle, the player was clearly in the way in this case
+			P_DamageMobj(thing, tmthing, tmthing, 1, DMG_SPIKE);
+	}
+	else if (thing->type == MT_WALLSPIKE && thing->flags & MF_SOLID && tmthing->player)
+	{
+		fixed_t bottomz, topz;
+		angle_t touchangle = R_PointToAngle2(thing->tracer->x, thing->tracer->y, tmthing->x, tmthing->y);
+
+		if (P_PlayerInPain(tmthing->player) && (tmthing->momx || tmthing->momy))
+		{
+			angle_t playerangle = R_PointToAngle2(0, 0, tmthing->momx, tmthing->momy) - touchangle;
+			if (playerangle > ANGLE_180)
+				playerangle = InvAngle(playerangle);
+			if (playerangle < ANGLE_90)
+				return true; // Yes, this is intentionally outside the z-height check. No standing on spikes whilst moving away from them.
+		}
+
+		bottomz = thing->z;
+		topz = thing->z + thing->height;
+
+		if (thing->eflags & MFE_VERTICALFLIP)
+			bottomz -= FixedMul(FRACUNIT, thing->scale);
+		else
+			topz += FixedMul(FRACUNIT, thing->scale);
+
+		if (tmthing->z + tmthing->height > bottomz // above bottom
+		&&  tmthing->z < topz // below top
+		&& !P_MobjWasRemoved(thing->tracer)) // this probably wouldn't work if we didn't have a tracer
+		{ // use base as a reference point to determine what angle you touched the spike at
+			touchangle = thing->angle - touchangle;
+			if (touchangle > ANGLE_180)
+				touchangle = InvAngle(touchangle);
+			if (touchangle <= ANGLE_22h) // if you touched it at this close an angle, you get poked!
+				P_DamageMobj(tmthing, thing, thing, 1, DMG_SPIKE);
+		}
 	}
 
 	if (thing->flags & MF_PUSHABLE)
@@ -1116,11 +1174,15 @@ static boolean PIT_CheckThing(mobj_t *thing)
 		}
 	}
 
-	if (thing->flags & MF_SPRING && (tmthing->player || tmthing->flags & MF_PUSHABLE))
+	if (!(tmthing->player) && (thing->player))
+		; // no solid thing should ever be able to step up onto a player
+	else if (thing->flags & MF_SPRING && (tmthing->player || tmthing->flags & MF_PUSHABLE))
 	{
 		if (iwassprung) // this spring caused you to gain MFE_SPRUNG just now...
 			return false; // "cancel" P_TryMove via blocking so you keep your current position
 	}
+	else if (tmthing->flags & MF_SPRING && (thing->flags & MF_PUSHABLE))
+		; // Fix a few nasty spring-jumping bugs that happen sometimes.
 	// Monitors are not treated as solid to players who are jumping, spinning or gliding,
 	// unless it's a CTF team monitor and you're on the wrong team
 	else if (thing->flags & MF_MONITOR && tmthing->player
@@ -1159,11 +1221,13 @@ static boolean PIT_CheckThing(mobj_t *thing)
 
 			topz = thing->z - thing->scale; // FixedMul(FRACUNIT, thing->scale), but thing->scale == FRACUNIT in base scale anyways
 
+			if (thing->flags & MF_SPRING)
+				;
 			// block only when jumping not high enough,
 			// (dont climb max. 24units while already in air)
 			// since return false doesn't handle momentum properly,
 			// we lie to P_TryMove() so it's always too high
-			if (tmthing->player && tmthing->z + tmthing->height > topz
+			else if (tmthing->player && tmthing->z + tmthing->height > topz
 				&& tmthing->z + tmthing->height < tmthing->ceilingz)
 			{
 				if (thing->flags & MF_GRENADEBOUNCE && (thing->flags & MF_MONITOR || thing->flags2 & MF2_STANDONME)) // Gold monitor hack...
@@ -1175,8 +1239,6 @@ static boolean PIT_CheckThing(mobj_t *thing)
 #endif
 				tmfloorthing = thing; // needed for side collision
 			}
-			else if (thing->flags & MF_SPRING)
-				;
 			else if (topz < tmceilingz && tmthing->z <= thing->z+thing->height)
 			{
 				tmceilingz = topz;
@@ -1205,11 +1267,13 @@ static boolean PIT_CheckThing(mobj_t *thing)
 
 			topz = thing->z + thing->height + thing->scale; // FixedMul(FRACUNIT, thing->scale), but thing->scale == FRACUNIT in base scale anyways
 
+			if (thing->flags & MF_SPRING)
+				;
 			// block only when jumping not high enough,
 			// (dont climb max. 24units while already in air)
 			// since return false doesn't handle momentum properly,
 			// we lie to P_TryMove() so it's always too high
-			if (tmthing->player && tmthing->z < topz
+			else if (tmthing->player && tmthing->z < topz
 				&& tmthing->z > tmthing->floorz)
 			{
 				if (thing->flags & MF_GRENADEBOUNCE && (thing->flags & MF_MONITOR || thing->flags2 & MF2_STANDONME)) // Gold monitor hack...
@@ -1221,8 +1285,6 @@ static boolean PIT_CheckThing(mobj_t *thing)
 #endif
 				tmfloorthing = thing; // needed for side collision
 			}
-			else if (thing->flags & MF_SPRING)
-				;
 			else if (topz > tmfloorz && tmthing->z+tmthing->height >= thing->z)
 			{
 				tmfloorz = topz;
@@ -3056,12 +3118,86 @@ void P_SlideMove(mobj_t *mo)
 	INT16 hitcount = 0;
 	boolean success = false;
 
+	boolean papercol = false;
+	vertex_t v1, v2; // fake vertexes
+	line_t junk; // fake linedef
+
 	if (tmhitthing && mo->z + mo->height > tmhitthing->z && mo->z < tmhitthing->z + tmhitthing->height)
 	{
 		// Don't mess with your momentum if it's a pushable object. Pushables do their own crazy things already.
 		if (tmhitthing->flags & MF_PUSHABLE)
 			return;
 
+		if (tmhitthing->flags & MF_PAPERCOLLISION)
+		{
+			fixed_t cosradius, sinradius, num, den;
+
+			// trace along the three leading corners
+			if (mo->momx > 0)
+			{
+				leadx = mo->x + mo->radius;
+				trailx = mo->x - mo->radius;
+			}
+			else
+			{
+				leadx = mo->x - mo->radius;
+				trailx = mo->x + mo->radius;
+			}
+
+			if (mo->momy > 0)
+			{
+				leady = mo->y + mo->radius;
+				traily = mo->y - mo->radius;
+			}
+			else
+			{
+				leady = mo->y - mo->radius;
+				traily = mo->y + mo->radius;
+			}
+
+			papercol = true;
+			slidemo = mo;
+			bestslideline = &junk;
+
+			cosradius = FixedMul(tmhitthing->radius, FINECOSINE(tmhitthing->angle>>ANGLETOFINESHIFT));
+			sinradius = FixedMul(tmhitthing->radius, FINESINE(tmhitthing->angle>>ANGLETOFINESHIFT));
+
+			v1.x = tmhitthing->x - cosradius;
+			v1.y = tmhitthing->y - sinradius;
+			v2.x = tmhitthing->x + cosradius;
+			v2.y = tmhitthing->y + sinradius;
+
+			junk.v1 = &v1;
+			junk.v2 = &v2;
+			junk.dx = 2*cosradius; // v2.x - v1.x;
+			junk.dy = 2*sinradius; // v2.y - v1.y;
+
+			junk.slopetype = !cosradius ? ST_VERTICAL : !sinradius ? ST_HORIZONTAL :
+			((sinradius > 0) == (cosradius > 0)) ? ST_POSITIVE : ST_NEGATIVE;
+
+			bestslidefrac = FRACUNIT+1;
+
+			den = FixedMul(junk.dy>>8, mo->momx) - FixedMul(junk.dx>>8, mo->momy);
+
+			if (!den)
+				bestslidefrac = 0;
+			else
+			{
+				fixed_t frac;
+#define P_PaperTraverse(startx, starty) \
+				num = FixedMul((v1.x - leadx)>>8, junk.dy) + FixedMul((leady - v1.y)>>8, junk.dx); \
+				frac = FixedDiv(num, den); \
+				if (frac < bestslidefrac) \
+					bestslidefrac = frac
+				P_PaperTraverse(leadx, leady);
+				P_PaperTraverse(trailx, leady);
+				P_PaperTraverse(leadx, traily);
+#undef dowork
+			}
+
+			goto papercollision;
+		}
+
 		// Thankfully box collisions are a lot simpler than arbitrary lines. There's only four possible cases.
 		if (mo->y + mo->radius <= tmhitthing->y - tmhitthing->radius)
 		{
@@ -3092,7 +3228,7 @@ void P_SlideMove(mobj_t *mo)
 	bestslideline = NULL;
 
 retry:
-	if (++hitcount == 3)
+	if ((++hitcount == 3) || papercol)
 		goto stairstep; // don't loop forever
 
 	// trace along the three leading corners
@@ -3134,6 +3270,7 @@ retry:
 		return;
 	}
 
+papercollision:
 	// move up to the wall
 	if (bestslidefrac == FRACUNIT+1)
 	{
diff --git a/src/p_mobj.c b/src/p_mobj.c
index d4e96f023e7644609fd48d07b5a0d67cac887f7f..31262ff1600ee1dfcf4903297f02e5ab3b88f04c 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -86,7 +86,7 @@ void P_AddCachedAction(mobj_t *mobj, INT32 statenum)
 //
 FUNCINLINE static ATTRINLINE void P_SetupStateAnimation(mobj_t *mobj, state_t *st)
 {
-	INT32 animlength = (mobj->skin && mobj->sprite == SPR_PLAY)
+	INT32 animlength = (mobj->sprite == SPR_PLAY && mobj->skin)
 		? (INT32)(((skin_t *)mobj->skin)->sprites[mobj->sprite2].numframes) - 1
 		: st->var1;
 
@@ -185,195 +185,6 @@ static void P_CyclePlayerMobjState(mobj_t *mobj)
 	}
 }
 
-//
-// P_GetMobjSprite2
-// For non-super players, tries each sprite2's immediate predecessor until it finds one with a number of frames or ends up at standing.
-// For super players, does the same as above - but tries the super equivalent for each sprite2 before the non-super version.
-//
-
-UINT8 P_GetMobjSprite2(mobj_t *mobj, UINT8 spr2)
-{
-	player_t *player = mobj->player;
-	skin_t *skin = ((skin_t *)mobj->skin);
-	UINT8 super = (spr2 & FF_SPR2SUPER);
-
-	if (!skin)
-		return 0;
-
-	while (!(skin->sprites[spr2].numframes)
-		&& spr2 != SPR2_STND)
-	{
-		if (spr2 & FF_SPR2SUPER)
-		{
-			spr2 &= ~FF_SPR2SUPER;
-			continue;
-		}
-
-		switch(spr2)
-		{
-		case SPR2_RUN:
-			spr2 = SPR2_WALK;
-			break;
-		case SPR2_STUN:
-			spr2 = SPR2_PAIN;
-			break;
-		case SPR2_DRWN:
-			spr2 = SPR2_DEAD;
-			break;
-		case SPR2_SPIN:
-			spr2 = SPR2_ROLL;
-			break;
-		case SPR2_GASP:
-			spr2 = SPR2_SPNG;
-			break;
-		case SPR2_JUMP:
-			spr2 = ((player
-					? player->charflags
-					: skin->flags)
-					& SF_NOJUMPSPIN) ? SPR2_SPNG : SPR2_ROLL;
-			break;
-		case SPR2_SPNG: // spring
-			spr2 = SPR2_FALL;
-			break;
-		case SPR2_FALL:
-			spr2 = SPR2_WALK;
-			break;
-		case SPR2_RIDE:
-			spr2 = SPR2_FALL;
-			break;
-
-		case SPR2_FLY :
-			spr2 = SPR2_SPNG;
-			break;
-		case SPR2_SWIM:
-			spr2 = SPR2_FLY ;
-			break;
-		case SPR2_TIRE:
-			spr2 = (player && player->charability == CA_SWIM) ? SPR2_SWIM : SPR2_FLY;
-			break;
-
-		case SPR2_GLID:
-			spr2 = SPR2_FLY;
-			break;
-		case SPR2_CLMB:
-			spr2 = SPR2_ROLL;
-			break;
-		case SPR2_CLNG:
-			spr2 = SPR2_CLMB;
-			break;
-
-		case SPR2_FLT :
-			spr2 = SPR2_WALK;
-			break;
-		case SPR2_FRUN:
-			spr2 = SPR2_RUN ;
-			break;
-
-		case SPR2_DASH:
-			spr2 = SPR2_FRUN;
-			break;
-
-		case SPR2_BNCE:
-			spr2 = SPR2_FALL;
-			break;
-		case SPR2_BLND:
-			spr2 = SPR2_ROLL;
-			break;
-
-		case SPR2_TWIN:
-			spr2 = SPR2_ROLL;
-			break;
-
-		case SPR2_MLEE:
-			spr2 = SPR2_TWIN;
-			break;
-
-		// NiGHTS sprites.
-		case SPR2_NSTD:
-			spr2 = SPR2_STND;
-			super = FF_SPR2SUPER;
-			break;
-		case SPR2_NFLT:
-			spr2 = SPR2_FLT ;
-			super = FF_SPR2SUPER;
-			break;
-		case SPR2_NSTN:
-			spr2 = SPR2_STUN;
-			break;
-		case SPR2_NPUL:
-			spr2 = SPR2_NSTN;
-			break;
-		case SPR2_NATK:
-			spr2 = SPR2_ROLL;
-			super = FF_SPR2SUPER;
-			break;
-		/*case SPR2_NGT0:
-			spr2 = SPR2_NFLT;
-			break;*/
-		case SPR2_NGT1:
-		case SPR2_NGT7:
-		case SPR2_DRL0:
-			spr2 = SPR2_NGT0;
-			break;
-		case SPR2_NGT2:
-		case SPR2_DRL1:
-			spr2 = SPR2_NGT1;
-			break;
-		case SPR2_NGT3:
-		case SPR2_DRL2:
-			spr2 = SPR2_NGT2;
-			break;
-		case SPR2_NGT4:
-		case SPR2_DRL3:
-			spr2 = SPR2_NGT3;
-			break;
-		case SPR2_NGT5:
-		case SPR2_DRL4:
-			spr2 = SPR2_NGT4;
-			break;
-		case SPR2_NGT6:
-		case SPR2_DRL5:
-			spr2 = SPR2_NGT5;
-			break;
-		case SPR2_DRL6:
-			spr2 = SPR2_NGT6;
-			break;
-		case SPR2_NGT8:
-		case SPR2_DRL7:
-			spr2 = SPR2_NGT7;
-			break;
-		case SPR2_NGT9:
-		case SPR2_DRL8:
-			spr2 = SPR2_NGT8;
-			break;
-		case SPR2_NGTA:
-		case SPR2_DRL9:
-			spr2 = SPR2_NGT9;
-			break;
-		case SPR2_NGTB:
-		case SPR2_DRLA:
-			spr2 = SPR2_NGTA;
-			break;
-		case SPR2_NGTC:
-		case SPR2_DRLB:
-			spr2 = SPR2_NGTB;
-			break;
-		case SPR2_DRLC:
-			spr2 = SPR2_NGTC;
-			break;
-
-		// Dunno? Just go to standing then.
-		default:
-			spr2 = SPR2_STND;
-			break;
-		}
-
-		spr2 |= super;
-	}
-
-	return spr2;
-}
-
 //
 // P_SetPlayerMobjState
 // Returns true if the mobj is still present.
@@ -574,14 +385,16 @@ boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 		{
 			skin_t *skin = ((skin_t *)mobj->skin);
 			UINT16 frame = (mobj->frame & FF_FRAMEMASK)+1;
-			UINT8 numframes;
-
-			UINT8 spr2 = P_GetMobjSprite2(mobj, (((player->powers[pw_super]) ? FF_SPR2SUPER : 0)|st->frame) & FF_FRAMEMASK);
+			UINT8 numframes, spr2;
 
 			if (skin)
+			{
+				spr2 = P_GetSkinSprite2(skin, (((player->powers[pw_super]) ? FF_SPR2SUPER : 0)|st->frame) & FF_FRAMEMASK, mobj->player);
 				numframes = skin->sprites[spr2].numframes;
+			}
 			else
 			{
+				spr2 = 0;
 				frame = 0;
 				numframes = 0;
 			}
@@ -700,14 +513,16 @@ boolean P_SetMobjState(mobj_t *mobj, statenum_t state)
 		{
 			skin_t *skin = ((skin_t *)mobj->skin);
 			UINT16 frame = (mobj->frame & FF_FRAMEMASK)+1;
-			UINT8 numframes;
-
-			UINT8 spr2 = P_GetMobjSprite2(mobj, st->frame & FF_FRAMEMASK);
+			UINT8 numframes, spr2;
 
 			if (skin)
+			{
+				spr2 = P_GetSkinSprite2(skin, st->frame & FF_FRAMEMASK, mobj->player);
 				numframes = skin->sprites[spr2].numframes;
+			}
 			else
 			{
+				spr2 = 0;
 				frame = 0;
 				numframes = 0;
 			}
@@ -2562,10 +2377,18 @@ boolean P_CheckSolidLava(mobj_t *mo, ffloor_t *rover)
 	I_Assert(mo != NULL);
 	I_Assert(!P_MobjWasRemoved(mo));
 
-	if (rover->flags & FF_SWIMMABLE && GETSECSPECIAL(rover->master->frontsector->special, 1) == 3
-		&& !(rover->master->flags & ML_BLOCKMONSTERS)
-		&& ((rover->master->flags & ML_EFFECT3) || mo->z-mo->momz > *rover->topheight - FixedMul(16*FRACUNIT, mo->scale)))
-			return true;
+	{
+		fixed_t topheight =
+	#ifdef ESLOPE
+			*rover->t_slope ? P_GetZAt(*rover->t_slope, mo->x, mo->y) :
+	#endif
+			*rover->topheight;
+
+		if (rover->flags & FF_SWIMMABLE && GETSECSPECIAL(rover->master->frontsector->special, 1) == 3
+			&& !(rover->master->flags & ML_BLOCKMONSTERS)
+			&& ((rover->master->flags & ML_EFFECT3) || mo->z-mo->momz > topheight - FixedMul(16*FRACUNIT, mo->scale)))
+				return true;
+	}
 
 	return false;
 }
@@ -2733,8 +2556,9 @@ static boolean P_ZMovement(mobj_t *mo)
 				return true;
 			break;
 		case MT_SPIKE:
+		case MT_WALLSPIKE:
 			// Dead spike particles disappear upon ground contact
-			if ((mo->z <= mo->floorz || mo->z + mo->height >= mo->ceilingz) && mo->health <= 0)
+			if (!mo->health && (mo->z <= mo->floorz || mo->z + mo->height >= mo->ceilingz))
 			{
 				P_RemoveMobj(mo);
 				return false;
@@ -2804,7 +2628,7 @@ static boolean P_ZMovement(mobj_t *mo)
 			mo->z = mo->floorz;
 
 #ifdef ESLOPE
-		if (mo->standingslope) // You're still on the ground; why are we here?
+		if (!(mo->flags & MF_MISSILE) && mo->standingslope) // You're still on the ground; why are we here?
 		{
 			mo->momz = 0;
 			return true;
@@ -3218,8 +3042,17 @@ static void P_PlayerZMovement(mobj_t *mo)
 					}
 				}
 
-				if (mo->health && !P_CheckDeathPitCollide(mo))
+				if (mo->health && !mo->player->spectator && !P_CheckDeathPitCollide(mo))
 				{
+					if ((mo->player->charability2 == CA2_SPINDASH) && !(mo->player->pflags & PF_THOKKED) && (mo->player->cmd.buttons & BT_USE) && (FixedHypot(mo->momx, mo->momy) > (5*mo->scale)))
+					{
+						mo->player->pflags |= PF_SPINNING;
+						P_SetPlayerMobjState(mo, S_PLAY_ROLL);
+						S_StartSound(mo, sfx_spin);
+					}
+					else
+						mo->player->pflags &= ~PF_SPINNING;
+
 					if (mo->player->pflags & PF_GLIDING) // ground gliding
 					{
 						mo->player->skidtime = TICRATE;
@@ -3232,7 +3065,7 @@ static void P_PlayerZMovement(mobj_t *mo)
 						S_StartSound(mo, sfx_s3k8b);
 						mo->player->pflags |= PF_FULLSTASIS;
 					}
-					else if (mo->player->pflags & PF_JUMPED || (mo->player->pflags & (PF_SPINNING|PF_USEDOWN)) != (PF_SPINNING|PF_USEDOWN)
+					else if (mo->player->pflags & PF_JUMPED || !(mo->player->pflags & PF_SPINNING)
 					|| mo->player->powers[pw_tailsfly] || mo->state-states == S_PLAY_FLY_TIRED)
 					{
 						if (mo->player->cmomx || mo->player->cmomy)
@@ -3263,15 +3096,6 @@ static void P_PlayerZMovement(mobj_t *mo)
 						}
 					}
 
-					if ((mo->player->charability2 == CA2_SPINDASH) && !(mo->player->pflags & PF_THOKKED) && (mo->player->cmd.buttons & BT_USE) && (FixedHypot(mo->momx, mo->momy) > (5*mo->scale)))
-					{
-						mo->player->pflags |= PF_SPINNING;
-						P_SetPlayerMobjState(mo, S_PLAY_ROLL);
-						S_StartSound(mo, sfx_spin);
-					}
-					else
-						mo->player->pflags &= ~PF_SPINNING;
-
 					if (!(mo->player->pflags & PF_GLIDING))
 						mo->player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE);
 
@@ -3580,11 +3404,17 @@ static boolean P_SceneryZMovement(mobj_t *mo)
 //
 boolean P_CanRunOnWater(player_t *player, ffloor_t *rover)
 {
+	fixed_t topheight =
+#ifdef ESLOPE
+		*rover->t_slope ? P_GetZAt(*rover->t_slope, player->mo->x, player->mo->y) :
+#endif
+		*rover->topheight;
+
 	if (!player->powers[pw_carry] && !player->homing
-		&& ((player->powers[pw_super] || player->charflags & SF_RUNONWATER || player->dashmode >= 3*TICRATE) && player->mo->ceilingz-*rover->topheight >= player->mo->height)
+		&& ((player->powers[pw_super] || player->charflags & SF_RUNONWATER || player->dashmode >= 3*TICRATE) && player->mo->ceilingz-topheight >= player->mo->height)
 		&& (rover->flags & FF_SWIMMABLE) && !(player->pflags & PF_SPINNING) && player->speed > FixedMul(player->runspeed, player->mo->scale)
 		&& !(player->pflags & PF_SLIDING)
-		&& abs(player->mo->z - *rover->topheight) < FixedMul(30*FRACUNIT, player->mo->scale))
+		&& abs(player->mo->z - topheight) < FixedMul(30*FRACUNIT, player->mo->scale))
 		return true;
 
 	return false;
@@ -6441,6 +6271,128 @@ static void P_NightsItemChase(mobj_t *thing)
 	P_Attract(thing, thing->tracer, true);
 }
 
+//
+// P_MaceRotate
+// Spins an object around its target, or, swings it from side to side.
+//
+static void P_MaceRotate(mobj_t *mobj)
+{
+	TVector v;
+	TVector *res;
+	fixed_t radius, dist;
+	angle_t fa;
+	INT32 prevswing;
+	boolean donetwice = false;
+
+	// Tracer was removed.
+	if (!mobj->health)
+		return;
+	else if (!mobj->tracer)
+	{
+		P_KillMobj(mobj, NULL, NULL, 0);
+		return;
+	}
+
+	mobj->momx = mobj->momy = mobj->momz = 0;
+
+	prevswing = mobj->threshold;
+	mobj->threshold += mobj->tracer->lastlook;
+	mobj->threshold &= FINEMASK;
+
+	dist = ((mobj->info->speed) ? mobj->info->speed : mobjinfo[MT_SMALLMACECHAIN].speed);
+
+	// Radius of the link's rotation.
+	radius = FixedMul(dist * mobj->movecount, mobj->tracer->scale) + mobj->tracer->extravalue1;
+
+maceretry:
+
+	fa = (FixedAngle(mobj->tracer->movefactor*FRACUNIT) >> ANGLETOFINESHIFT);
+	radius = FixedMul(FINECOSINE(fa), radius);
+	v[1] = -FixedMul(FINESINE(fa), radius)
+	+ FixedMul(dist * mobj->movefactor, mobj->tracer->scale);
+	v[3] = FRACUNIT;
+
+	// Swinging Chain.
+	if (mobj->tracer->flags2 & MF2_STRONGBOX)
+	{
+		fixed_t swingmagnitude = FixedMul(FINECOSINE(mobj->threshold), mobj->tracer->lastlook << FRACBITS);
+		prevswing = FINECOSINE(prevswing);
+
+		if (!donetwice
+		&& (mobj->flags2 & MF2_BOSSNOTRAP) // at the end of the chain and can play a sound
+		&& ((prevswing > 0) != (swingmagnitude > 0))) // just passed its lowest point
+			S_StartSound(mobj, mobj->info->activesound);
+
+		fa = ((FixedAngle(swingmagnitude) >> ANGLETOFINESHIFT) + mobj->friction) & FINEMASK;
+
+		v[0] = FixedMul(FINESINE(fa), -radius);
+		v[2] = FixedMul(FINECOSINE(fa), -radius);
+	}
+	// Rotating Chain.
+	else
+	{
+		prevswing = (prevswing + mobj->friction) & FINEMASK;
+		fa = (mobj->threshold + mobj->friction) & FINEMASK;
+
+		if (!donetwice
+		&& (mobj->flags2 & MF2_BOSSNOTRAP) // at the end of the chain and can play a sound
+		&& (!(prevswing > (FINEMASK/2)) && (fa > (FINEMASK/2)))) // completed a full swing
+			S_StartSound(mobj, mobj->info->activesound);
+
+		v[0] = FixedMul(FINECOSINE(fa), radius);
+		v[2] = FixedMul(FINESINE(fa), radius);
+	}
+
+	// Calculate the angle matrixes for the link.
+	res = VectorMatrixMultiply(v, *RotateXMatrix(mobj->tracer->threshold << ANGLETOFINESHIFT));
+	M_Memcpy(&v, res, sizeof(v));
+	res = VectorMatrixMultiply(v, *RotateZMatrix(mobj->tracer->health << ANGLETOFINESHIFT));
+	M_Memcpy(&v, res, sizeof(v));
+
+	// Cut the height to align the link with the axis.
+	if (mobj->type == MT_SMALLMACECHAIN || mobj->type == MT_BIGMACECHAIN)
+		v[2] -= P_MobjFlip(mobj)*mobj->height/4;
+	else
+		v[2] -= P_MobjFlip(mobj)*mobj->height/2;
+
+	P_UnsetThingPosition(mobj);
+
+	// Add on the appropriate distances to the center's co-ordinates.
+	mobj->x = mobj->tracer->x + v[0];
+	mobj->y = mobj->tracer->y + v[1];
+	mobj->z = mobj->tracer->z + v[2];
+
+	P_SetThingPosition(mobj);
+
+	if (donetwice || P_MobjWasRemoved(mobj))
+		return;
+
+	if (mobj->flags & (MF_NOCLIP|MF_NOCLIPHEIGHT))
+		return;
+
+	if ((fa = ((mobj->tracer->threshold & (FINEMASK/2)) << ANGLETOFINESHIFT)) > ANGLE_45 && fa < ANGLE_135) // only move towards center when the motion is towards/away from the ground, rather than alongside it
+		return;
+
+	if (mobj->subsector->sector->ffloors)
+		P_AdjustMobjFloorZ_FFloors(mobj, mobj->subsector->sector, 2);
+
+	// Variable reuse
+	if (mobj->floorz > mobj->z)
+		dist = (mobj->floorz - mobj->tracer->z);
+	else if (mobj->ceilingz < mobj->z)
+		dist = (mobj->ceilingz - mobj->tracer->z);
+	else
+		return;
+
+	if ((dist = FixedDiv(dist, v[2])) > FRACUNIT)
+		return;
+
+	radius = FixedMul(radius, dist);
+	donetwice = true;
+	dist = ((mobj->info->speed) ? mobj->info->speed : mobjinfo[MT_SMALLMACECHAIN].speed);
+	goto maceretry;
+}
+
 static boolean P_ShieldLook(mobj_t *thing, shieldtype_t shield)
 {
 	if (!thing->target || thing->target->health <= 0 || !thing->target->player
@@ -6798,6 +6750,13 @@ void P_MobjThinker(mobj_t *mobj)
 		// fade out when nearing the end of fuse...
 		mobj->frame = (mobj->frame & ~FF_TRANSMASK) | (((NUMTRANSMAPS-1) - mobj->fuse / 2) << FF_TRANSSHIFT);
 
+	if (mobj->flags2 & MF2_MACEROTATE)
+	{
+		P_MaceRotate(mobj);
+		if (P_MobjWasRemoved(mobj))
+			return;
+	}
+
 	// Special thinker for scenery objects
 	if (mobj->flags & MF_SCENERY)
 	{
@@ -7361,6 +7320,37 @@ void P_MobjThinker(mobj_t *mobj)
 		}
 	else switch (mobj->type)
 	{
+		case MT_WALLSPIKEBASE:
+			if (!mobj->target) {
+				P_RemoveMobj(mobj);
+				return;
+			}
+			mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|(mobj->target->frame & FF_FRAMEMASK);
+#if 0
+			if (mobj->angle != mobj->target->angle + ANGLE_90) // reposition if not the correct angle
+			{
+				mobj_t *target = mobj->target; // shortcut
+				const fixed_t baseradius = target->radius - (target->scale/2); //FixedMul(FRACUNIT/2, target->scale);
+				P_UnsetThingPosition(mobj);
+				mobj->x = target->x - P_ReturnThrustX(target, target->angle, baseradius);
+				mobj->y = target->y - P_ReturnThrustY(target, target->angle, baseradius);
+				P_SetThingPosition(mobj);
+				mobj->angle = target->angle + ANGLE_90;
+			}
+#endif
+			break;
+		case MT_FALLINGROCK:
+			// Despawn rocks here in case zmovement code can't do so (blame slopes)
+			if (!mobj->momx && !mobj->momy && !mobj->momz
+			&& ((mobj->eflags & MFE_VERTICALFLIP) ?
+				  mobj->z + mobj->height >= mobj->ceilingz
+				: mobj->z <= mobj->floorz))
+			{
+				P_RemoveMobj(mobj);
+				return;
+			}
+			P_MobjCheckWater(mobj);
+			break;
 		case MT_EMERALDSPAWN:
 			if (mobj->threshold)
 			{
@@ -7432,7 +7422,8 @@ void P_MobjThinker(mobj_t *mobj)
 				}
 			}
 			break;
-		case MT_SPINMACEPOINT:
+		case MT_CHAINPOINT:
+		case MT_CHAINMACEPOINT:
 			if (leveltime & 1)
 			{
 				if (mobj->lastlook > mobj->movecount)
@@ -7768,6 +7759,10 @@ void P_MobjThinker(mobj_t *mobj)
 			if (mobj->flags2 & MF2_NIGHTSPULL)
 				P_NightsItemChase(mobj);
 			break;
+		case MT_EMBLEM:
+			if (mobj->flags2 & MF2_NIGHTSPULL)
+				P_NightsItemChase(mobj);
+			break;
 		case MT_SHELL:
 			if (mobj->threshold && mobj->threshold != TICRATE)
 				mobj->threshold--;
@@ -8042,6 +8037,10 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s
 					if (mobj->spawnpoint)
 						mobj->fuse += mobj->spawnpoint->angle;
 					break;
+				case MT_WALLSPIKE:
+					P_SetMobjState(mobj, mobj->state->nextstate);
+					mobj->fuse = mobj->info->speed;
+					break;
 				case MT_NIGHTSCORE:
 					P_RemoveMobj(mobj);
 					return;
@@ -8433,9 +8432,17 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 			// Collision helper can be stood on but not pushed
 			mobj->flags2 |= MF2_STANDONME;
 			break;
+		case MT_WALLSPIKE:
 		case MT_SPIKE:
 			mobj->flags2 |= MF2_STANDONME;
 			break;
+		case MT_GFZTREE:
+		case MT_GFZBERRYTREE:
+		case MT_GFZCHERRYTREE:
+		case MT_LAMPPOST1:
+		case MT_LAMPPOST2:
+			mobj->flags2 |= MF2_STANDONME;
+			break;
 		case MT_DETON:
 			mobj->movedir = 0;
 			break;
@@ -8501,6 +8508,7 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 			break;
 		case MT_EGGCAPSULE:
 			mobj->extravalue1 = -1; // timer for how long a player has been at the capsule
+			break;
 		case MT_REDTEAMRING:
 			mobj->color = skincolor_redteam;
 			break;
@@ -8765,7 +8773,6 @@ consvar_t cv_itemrespawntime = {"respawnitemtime", "30", CV_NETVAR|CV_CHEAT, res
 consvar_t cv_itemrespawn = {"respawnitem", "On", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 static CV_PossibleValue_t flagtime_cons_t[] = {{0, "MIN"}, {300, "MAX"}, {0, NULL}};
 consvar_t cv_flagtime = {"flagtime", "30", CV_NETVAR|CV_CHEAT, flagtime_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_suddendeath = {"suddendeath", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 void P_SpawnPrecipitation(void)
 {
@@ -9071,40 +9078,44 @@ void P_SpawnPlayer(INT32 playernum)
 	// spawn as spectator determination
 	if (!G_GametypeHasSpectators())
 	{
-		// Special case for (NiGHTS) special stages!
-		// if stage has already started, force players to become spectators until the next stage
-		if (multiplayer && netgame && G_IsSpecialStage(gamemap) && useNightsSS && leveltime > 0)
-			p->spectator = true;
-		else
-			p->spectator = false;
+		p->spectator = p->outofcoop =
+		(((multiplayer || netgame) && gametype == GT_COOP) // only question status in coop
+		&& ((leveltime > 0
+		&& ((G_IsSpecialStage(gamemap) && useNightsSS) // late join special stage
+		|| (cv_coopstarposts.value == 2 && (p->jointime < 1 || p->outofcoop)))) // late join or die in new coop
+		|| (((cv_cooplives.value == 1) || !P_GetLives(p)) && p->lives <= 0))); // game over and can't redistribute lives
 	}
-	else if (netgame && p->jointime < 1)
-		p->spectator = true;
-	else if (multiplayer && !netgame)
+	else
 	{
-		// If you're in a team game and you don't have a team assigned yet...
-		if (G_GametypeHasTeams() && p->ctfteam == 0)
+		p->outofcoop = false;
+		if (netgame && p->jointime < 1)
+			p->spectator = true;
+		else if (multiplayer && !netgame)
 		{
-			changeteam_union NetPacket;
-			UINT16 usvalue;
-			NetPacket.value.l = NetPacket.value.b = 0;
+			// If you're in a team game and you don't have a team assigned yet...
+			if (G_GametypeHasTeams() && p->ctfteam == 0)
+			{
+				changeteam_union NetPacket;
+				UINT16 usvalue;
+				NetPacket.value.l = NetPacket.value.b = 0;
 
-			// Spawn as a spectator,
-			// yes even in splitscreen mode
-			p->spectator = true;
-			if (playernum&1) p->skincolor = skincolor_redteam;
-			else             p->skincolor = skincolor_blueteam;
+				// Spawn as a spectator,
+				// yes even in splitscreen mode
+				p->spectator = true;
+				if (playernum&1) p->skincolor = skincolor_redteam;
+				else             p->skincolor = skincolor_blueteam;
 
-			// but immediately send a team change packet.
-			NetPacket.packet.playernum = playernum;
-			NetPacket.packet.verification = true;
-			NetPacket.packet.newteam = !(playernum&1) + 1;
+				// but immediately send a team change packet.
+				NetPacket.packet.playernum = playernum;
+				NetPacket.packet.verification = true;
+				NetPacket.packet.newteam = !(playernum&1) + 1;
 
-			usvalue = SHORT(NetPacket.value.l|NetPacket.value.b);
-			SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue));
+				usvalue = SHORT(NetPacket.value.l|NetPacket.value.b);
+				SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue));
+			}
+			else // Otherwise, never spectator.
+				p->spectator = false;
 		}
-		else // Otherwise, never spectator.
-			p->spectator = false;
 	}
 
 	if (G_GametypeHasTeams())
@@ -9121,6 +9132,9 @@ void P_SpawnPlayer(INT32 playernum)
 			p->skincolor = skincolor_blueteam;
 	}
 
+	if ((netgame || multiplayer) && (gametype != GT_COOP || leveltime) && !p->spectator && !(maptol & TOL_NIGHTS))
+		p->powers[pw_flashing] = flashingtics-1; // Babysitting deterrent
+
 	mobj = P_SpawnMobj(0, 0, 0, MT_PLAYER);
 	(mobj->player = p)->mo = mobj;
 
@@ -9451,7 +9465,7 @@ void P_SpawnMapThing(mapthing_t *mthing)
 	}
 
 	if (metalrecording) // Metal Sonic can't use these things.
-		if (mobjinfo[i].flags & (MF_ENEMY|MF_BOSS) || i == MT_EMMY || i == MT_STARPOST)
+		if (mobjinfo[i].flags & (MF_ENEMY|MF_BOSS) || i == MT_TOKEN || i == MT_STARPOST)
 			return;
 
 	if (i >= MT_EMERALD1 && i <= MT_EMERALD7) // Pickupable Emeralds
@@ -9509,12 +9523,12 @@ void P_SpawnMapThing(mapthing_t *mthing)
 	// Yeah, this is a dirty hack.
 	if ((mobjinfo[i].flags & (MF_MONITOR|MF_GRENADEBOUNCE)) == MF_MONITOR)
 	{
-		if (gametype == GT_COMPETITION)
+		if (gametype == GT_COMPETITION || gametype == GT_RACE)
 		{
 			// Set powerup boxes to user settings for competition.
-			if (cv_competitionboxes.value == 1) // Random
+			if (cv_competitionboxes.value == 1) // Mystery
 				i = MT_MYSTERY_BOX;
-			else if (cv_competitionboxes.value == 2) // Teleports
+			else if (cv_competitionboxes.value == 2) // Teleport
 				i = MT_MIXUP_BOX;
 			else if (cv_competitionboxes.value == 3) // None
 				return; // Don't spawn!
@@ -9523,12 +9537,12 @@ void P_SpawnMapThing(mapthing_t *mthing)
 		// Set powerup boxes to user settings for other netplay modes
 		else if (gametype != GT_COOP)
 		{
-			if (cv_matchboxes.value == 1) // Random
+			if (cv_matchboxes.value == 1) // Mystery
 				i = MT_MYSTERY_BOX;
-			else if (cv_matchboxes.value == 2) // Non-Random
+			else if (cv_matchboxes.value == 2) // Unchanging
 			{
 				if (i == MT_MYSTERY_BOX)
-					return; // don't spawn in Non-Random
+					return; // don't spawn
 				mthing->options &= ~(MTF_AMBUSH|MTF_OBJECTSPECIAL); // no random respawning!
 			}
 			else if (cv_matchboxes.value == 3) // Don't spawn
@@ -9565,7 +9579,7 @@ void P_SpawnMapThing(mapthing_t *mthing)
 			return;
 
 		// Emerald Tokens -->> Score Tokens
-		else if (i == MT_EMMY)
+		else if (i == MT_TOKEN)
 			return; /// \todo
 
 		// 1UPs -->> Score TVs
@@ -9582,7 +9596,8 @@ void P_SpawnMapThing(mapthing_t *mthing)
 	if (ultimatemode)
 	{
 		if (i == MT_PITY_BOX || i == MT_ELEMENTAL_BOX || i == MT_ATTRACT_BOX
-		 || i == MT_FORCE_BOX || i == MT_ARMAGEDDON_BOX || i == MT_WHIRLWIND_BOX)
+		 || i == MT_FORCE_BOX || i == MT_ARMAGEDDON_BOX || i == MT_WHIRLWIND_BOX
+		 || i == MT_FLAMEAURA_BOX || i == MT_BUBBLEWRAP_BOX || i == MT_THUNDERCOIN_BOX)
 			return; // No shields in Ultimate mode
 
 		if (i == MT_RING_BOX && !G_IsSpecialStage(gamemap))
@@ -9592,7 +9607,7 @@ void P_SpawnMapThing(mapthing_t *mthing)
 		// They're likely facets of the level's design and therefore required to progress.
 	}
 
-	if (i == MT_EMMY && (gametype != GT_COOP || ultimatemode || tokenbits == 30 || tokenlist & (1 << tokenbits++)))
+	if (i == MT_TOKEN && (gametype != GT_COOP || ultimatemode || tokenbits == 30 || tokenlist & (1 << tokenbits++)))
 		return; // you already got this token, or there are too many, or the gametype's not right
 
 	// Objectplace landing point
@@ -9611,7 +9626,7 @@ void P_SpawnMapThing(mapthing_t *mthing)
 			ss->sector->floorheight) + ((mthing->options >> ZSHIFT) << FRACBITS);
 	else if (i == MT_AXIS || i == MT_AXISTRANSFER || i == MT_AXISTRANSFERLINE)
 		z = ONFLOORZ;
-	else if (i == MT_SPECIALSPIKEBALL || P_WeaponOrPanel(i) || i == MT_EMERALDSPAWN || i == MT_EMMY)
+	else if (i == MT_SPECIALSPIKEBALL || P_WeaponOrPanel(i) || i == MT_EMERALDSPAWN || i == MT_TOKEN)
 	{
 		if (mthing->options & MTF_OBJECTFLIP)
 		{
@@ -9701,14 +9716,23 @@ void P_SpawnMapThing(mapthing_t *mthing)
 	mobj = P_SpawnMobj(x, y, z, i);
 	mobj->spawnpoint = mthing;
 
+#ifdef HAVE_BLUA
+	if (LUAh_MapThingSpawn(mobj, mthing))
+	{
+		if (P_MobjWasRemoved(mobj))
+			return;
+	}
+	else if (P_MobjWasRemoved(mobj))
+		return;
+	else
+#endif
 	switch(mobj->type)
 	{
 	case MT_SKYBOX:
-		mobj->angle = 0;
 		if (mthing->options & MTF_OBJECTSPECIAL)
-			skyboxmo[1] = mobj;
+			skyboxcenterpnts[mthing->extrainfo] = mobj;
 		else
-			skyboxmo[0] = mobj;
+			skyboxviewpnts[mthing->extrainfo] = mobj;
 		break;
 	case MT_FAN:
 		if (mthing->options & MTF_OBJECTSPECIAL)
@@ -9744,138 +9768,283 @@ void P_SpawnMapThing(mapthing_t *mthing)
 		mobj->movedir = mthing->extrainfo;
 		break;
 	case MT_MACEPOINT:
-	case MT_SWINGMACEPOINT:
-	case MT_HANGMACEPOINT:
-	case MT_SPINMACEPOINT:
-	{
-		fixed_t mlength, mspeed, mxspeed, mzspeed, mstartangle, mmaxspeed;
-		mobjtype_t chainlink = MT_SMALLMACECHAIN;
-		mobjtype_t macetype = MT_SMALLMACE;
-		boolean firsttime;
+	case MT_CHAINMACEPOINT:
+	case MT_SPRINGBALLPOINT:
+	case MT_CHAINPOINT:
+	case MT_FIREBARPOINT:
+	case MT_CUSTOMMACEPOINT:
+	{
+		fixed_t mlength, mlengthset, mspeed, mphase, myaw, mpitch, mmaxspeed, mnumspokes, mnumspokesset, mpinch, mroll, mnumnospokes, mwidth, mmin, msound, radiusfactor;
+		angle_t mspokeangle;
+		mobjtype_t chainlink, macetype, firsttype, linktype;
+		boolean mdoall = true;
 		mobj_t *spawnee;
-		size_t line;
+		mobjflag_t mflagsapply;
+		mobjflag2_t mflags2apply;
+		mobjeflag_t meflagsapply;
+		INT32 line;
 		const size_t mthingi = (size_t)(mthing - mapthings);
 
-		// Why does P_FindSpecialLineFromTag not work here?!?
-		// Monster Iestyn: tag lists haven't been initialised yet for the map, that's why
-		for (line = 0; line < numlines; line++)
-		{
-			if (lines[line].special == 9 && lines[line].tag == mthing->angle)
-				break;
-		}
+		// Find the corresponding linedef special, using angle as tag
+		// P_FindSpecialLineFromTag works here now =D
+		line = P_FindSpecialLineFromTag(9, mthing->angle, -1);
 
-		if (line == numlines)
+		if (line == -1)
 		{
-			CONS_Debug(DBG_GAMELOGIC, "Mace chain (mapthing #%s) needs tagged to a #9 parameter line (trying to find tag %d).\n", sizeu1(mthingi), mthing->angle);
+			CONS_Debug(DBG_GAMELOGIC, "Mace chain (mapthing #%s) needs to be tagged to a #9 parameter line (trying to find tag %d).\n", sizeu1(mthingi), mthing->angle);
 			return;
 		}
 /*
-No deaf - small mace
-Deaf - big mace
-
-ML_NOCLIMB : Direction not controllable
+mapthing -
+MTF_AMBUSH :
+	MT_SPRINGBALLPOINT - upgrade from yellow to red spring
+	anything else - bigger mace/chain theory
+MTF_OBJECTSPECIAL - force silent
+MTF_GRAVFLIP - flips objects, doesn't affect chain arrangements
+Parameter value : number of "spokes"
+
+linedef -
+ML_NOCLIMB :
+	MT_CHAINPOINT/MT_CHAINMACEPOINT with ML_EFFECT1 applied - Direction not controllable
+	anything else - no functionality
+ML_EFFECT1 : Swings instead of spins
+ML_EFFECT2 : Linktype is replaced with macetype for all spokes not ending in chains (inverted for MT_FIREBARPOINT)
+ML_EFFECT3 : Spawn a bonus macetype at the hinge point
+ML_EFFECT4 : Don't clip inside the ground
 */
 		mlength = abs(lines[line].dx >> FRACBITS);
-		mspeed = abs(lines[line].dy >> FRACBITS);
-		mxspeed = sides[lines[line].sidenum[0]].textureoffset >> FRACBITS;
-		mzspeed = sides[lines[line].sidenum[0]].rowoffset >> FRACBITS;
-		mstartangle = lines[line].frontsector->floorheight >> FRACBITS;
-		mmaxspeed = lines[line].frontsector->ceilingheight >> FRACBITS;
+		mspeed = abs(lines[line].dy >> (FRACBITS - 4));
+		mphase = (sides[lines[line].sidenum[0]].textureoffset >> FRACBITS) % 360;
+		if ((mmaxspeed = sides[lines[line].sidenum[0]].rowoffset >> (FRACBITS - 4)) < mspeed)
+			mmaxspeed = mspeed << 1;
+		mpitch = (lines[line].frontsector->floorheight >> FRACBITS) % 360;
+		myaw = (lines[line].frontsector->ceilingheight >> FRACBITS) % 360;
 
-		mstartangle %= 360;
-		mxspeed %= 360;
-		mzspeed %= 360;
+		mnumspokes = mthing->extrainfo + 1;
+		mspokeangle = FixedAngle((360*FRACUNIT)/mnumspokes)>>ANGLETOFINESHIFT;
 
-		CONS_Debug(DBG_GAMELOGIC, "Mace Chain (mapthing #%s):\n"
+		if (lines[line].backsector)
+		{
+			mpinch = (lines[line].backsector->floorheight >> FRACBITS) % 360;
+			mroll = (lines[line].backsector->ceilingheight >> FRACBITS) % 360;
+			mnumnospokes = (sides[lines[line].sidenum[1]].textureoffset >> FRACBITS);
+			if ((mwidth = sides[lines[line].sidenum[1]].rowoffset >> FRACBITS) < 0)
+				mwidth = 0;
+		}
+		else
+			mpinch = mroll = mnumnospokes = mwidth = 0;
+
+		CONS_Debug(DBG_GAMELOGIC, "Mace/Chain (mapthing #%s):\n"
 				"Length is %d\n"
 				"Speed is %d\n"
-				"Xspeed is %d\n"
-				"Zspeed is %d\n"
-				"startangle is %d\n"
-				"maxspeed is %d\n",
-				sizeu1(mthingi), mlength, mspeed, mxspeed, mzspeed, mstartangle, mmaxspeed);
+				"Phase is %d\n"
+				"Yaw is %d\n"
+				"Pitch is %d\n"
+				"Max. speed is %d\n"
+				"No. of spokes is %d\n"
+				"Pinch is %d\n"
+				"Roll is %d\n"
+				"No. of antispokes is %d\n"
+				"Width is %d\n",
+				sizeu1(mthingi), mlength, mspeed, mphase, myaw, mpitch, mmaxspeed, mnumspokes, mpinch, mroll, mnumnospokes, mwidth);
+
+		if (mnumnospokes > 0 && (mnumnospokes < mnumspokes))
+			mnumnospokes = mnumspokes/mnumnospokes;
+		else
+			mnumnospokes = ((mobj->type == MT_CHAINMACEPOINT) ? (mnumspokes - 1) : 0);
 
-		mobj->lastlook = mspeed << 4;
+		mobj->lastlook = mspeed;
 		mobj->movecount = mobj->lastlook;
-		mobj->health = (FixedAngle(mzspeed*FRACUNIT)>>ANGLETOFINESHIFT) + (FixedAngle(mstartangle*FRACUNIT)>>ANGLETOFINESHIFT);
-		mobj->threshold = (FixedAngle(mxspeed*FRACUNIT)>>ANGLETOFINESHIFT) + (FixedAngle(mstartangle*FRACUNIT)>>ANGLETOFINESHIFT);
-		mobj->movefactor = mobj->threshold;
+		mobj->health = (FixedAngle(myaw*FRACUNIT)>>ANGLETOFINESHIFT);
+		mobj->threshold = (FixedAngle(mpitch*FRACUNIT)>>ANGLETOFINESHIFT);
 		mobj->friction = mmaxspeed;
+		mobj->movefactor = mpinch;
 
-		if (lines[line].flags & ML_NOCLIMB)
-			mobj->flags |= MF_SLIDEME;
+		// Mobjtype selection
+		switch(mobj->type)
+		{
+			case MT_SPRINGBALLPOINT:
+				macetype = ((mthing->options & MTF_AMBUSH)
+					? MT_REDSPRINGBALL
+					: MT_YELLOWSPRINGBALL);
+				chainlink = MT_SMALLMACECHAIN;
+				break;
+			case MT_FIREBARPOINT:
+				macetype = ((mthing->options & MTF_AMBUSH)
+						? MT_BIGFIREBAR
+						: MT_SMALLFIREBAR);
+				chainlink = MT_NULL;
+				break;
+			case MT_CUSTOMMACEPOINT:
+				macetype = (mobjtype_t)sides[lines[line].sidenum[0]].toptexture;
+				if (lines[line].backsector)
+					chainlink = (mobjtype_t)sides[lines[line].sidenum[1]].toptexture;
+				else
+					chainlink = MT_NULL;
+				break;
+			default:
+				if (mthing->options & MTF_AMBUSH)
+				{
+					macetype = MT_BIGMACE;
+					chainlink = MT_BIGMACECHAIN;
+				}
+				else
+				{
+					macetype = MT_SMALLMACE;
+					chainlink = MT_SMALLMACECHAIN;
+				}
+				break;
+		}
 
-		mobj->reactiontime = 0;
+		if (!macetype)
+			break;
 
-		if (mthing->options & MTF_AMBUSH)
+		if (mobj->type != MT_CHAINPOINT)
+		{
+			firsttype = macetype;
+			mlength++;
+		}
+		else
 		{
-			chainlink = MT_BIGMACECHAIN;
-			macetype = MT_BIGMACE;
+			if (!mlength)
+				break;
+			firsttype = chainlink;
 		}
 
-		if (mthing->options & MTF_OBJECTSPECIAL)
-			mobj->flags2 |= MF2_BOSSNOTRAP; // shut up maces.
+		// Adjustable direction
+		if (lines[line].flags & ML_NOCLIMB)
+			mobj->flags |= MF_SLIDEME;
 
-		if (mobj->type == MT_HANGMACEPOINT || mobj->type == MT_SPINMACEPOINT)
-			firsttime = true;
+		// Swinging
+		if (lines[line].flags & ML_EFFECT1)
+		{
+			mobj->flags2 |= MF2_STRONGBOX;
+			mmin = ((mnumnospokes > 1) ? 1 : 0);
+		}
 		else
+			mmin = mnumspokes;
+
+		// Make the links the same type as the end - repeated below
+		if ((mobj->type != MT_CHAINPOINT) && (!(lines[line].flags & ML_EFFECT2) == (mobj->type == MT_FIREBARPOINT))) // exclusive or
 		{
-			firsttime = false;
+			linktype = macetype;
+			radiusfactor = 2; // Double the radius.
+		}
+		else
+			radiusfactor = (((linktype = chainlink) == MT_NULL) ? 2 : 1);
 
-			spawnee = P_SpawnMobj(mobj->x, mobj->y, mobj->z, macetype);
-			P_SetTarget(&spawnee->target, mobj);
+		mflagsapply = ((lines[line].flags & ML_EFFECT4) ? 0 : (MF_NOCLIP|MF_NOCLIPHEIGHT));
+		mflags2apply = (MF2_MACEROTATE|((mthing->options & MTF_OBJECTFLIP) ? MF2_OBJECTFLIP : 0));
+		meflagsapply = ((mthing->options & MTF_OBJECTFLIP) ? MFE_VERTICALFLIP : 0);
 
-			if (mobj->type == MT_SWINGMACEPOINT)
-				spawnee->movecount = FixedAngle(mstartangle*FRACUNIT)>>ANGLETOFINESHIFT;
-			else
-				spawnee->movecount = 0;
+		msound = ((firsttype == chainlink) ? 0 : (mwidth & 1));
 
-			spawnee->threshold = FixedAngle(mstartangle*FRACUNIT)>>ANGLETOFINESHIFT;
-			spawnee->reactiontime = mlength+1;
-		}
+		// Quick and easy preparatory variable setting
+		mphase = (FixedAngle(mphase*FRACUNIT)>>ANGLETOFINESHIFT);
+		mroll = (FixedAngle(mroll*FRACUNIT)>>ANGLETOFINESHIFT);
+
+#define makemace(mobjtype, dist, moreflags2) P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobjtype);\
+				P_SetTarget(&spawnee->tracer, mobj);\
+				spawnee->threshold = mphase;\
+				spawnee->friction = mroll;\
+				spawnee->movefactor = mwidth;\
+				spawnee->movecount = dist;\
+				spawnee->angle = myaw;\
+				spawnee->flags |= (MF_NOGRAVITY|mflagsapply);\
+				spawnee->flags2 |= (mflags2apply|moreflags2);\
+				spawnee->eflags |= meflagsapply
 
-		while (mlength > 0)
+domaceagain:
+		mnumspokesset = mnumspokes;
+
+		if (mdoall && lines[line].flags & ML_EFFECT3) // Innermost mace/link
+			{ spawnee = makemace(macetype, 0, MF2_AMBUSH); }
+
+		// The actual spawning of spokes
+		while (mnumspokesset-- > 0)
 		{
-			spawnee = P_SpawnMobj(mobj->x, mobj->y, mobj->z, chainlink);
+			// Offsets
+			if (lines[line].flags & ML_EFFECT1) // Swinging
+				mroll = (mroll - mspokeangle) & FINEMASK;
+			else // Spinning
+				mphase = (mphase - mspokeangle) & FINEMASK;
 
-			P_SetTarget(&spawnee->target, mobj);
+			if (mnumnospokes && !(mnumspokesset % mnumnospokes)) // Skipping a "missing" spoke
+			{
+				if (mobj->type != MT_CHAINMACEPOINT)
+					continue;
 
-			if (mobj->type == MT_HANGMACEPOINT || mobj->type == MT_SWINGMACEPOINT)
-				spawnee->movecount = FixedAngle(mstartangle*FRACUNIT)>>ANGLETOFINESHIFT;
+				firsttype = linktype = chainlink;
+				mlengthset = 1 + (mlength - 1)*radiusfactor;
+				radiusfactor = 1;
+			}
 			else
-				spawnee->movecount = 0;
+			{
+				if (mobj->type == MT_CHAINMACEPOINT)
+				{
+					// Make the links the same type as the end - repeated above
+					if (lines[line].flags & ML_EFFECT2)
+					{
+						linktype = macetype;
+						radiusfactor = 2;
+					}
+					else
+					{
+						linktype = chainlink;
+						radiusfactor = (((linktype = chainlink) == MT_NULL) ? 2 : 1);
+					}
 
-			spawnee->threshold = FixedAngle(mstartangle*FRACUNIT)>>ANGLETOFINESHIFT;
-			spawnee->reactiontime = mlength;
+					firsttype = macetype;
+				}
 
-			if (firsttime)
-			{
-				// This is the outermost link in the chain
-				spawnee->flags2 |= MF2_AMBUSH;
-				firsttime = false;
+				mlengthset = mlength;
 			}
 
-			mlength--;
+			// Outermost mace/link
+			spawnee = makemace(firsttype, radiusfactor*(mlengthset--), MF2_AMBUSH);
+
+			if (mspeed && (mwidth == msound) && !(mthing->options & MTF_OBJECTSPECIAL) && mnumspokesset <= mmin) // Can it make a sound?
+				spawnee->flags2 |= MF2_BOSSNOTRAP;
+
+			if (!mdoall || !linktype)
+				continue;
+
+			// The rest of the links
+			while (mlengthset > 0)
+				{ spawnee = makemace(linktype, radiusfactor*(mlengthset--), 0); }
 		}
+
+		if (mwidth > 0)
+		{
+			mwidth *= -1;
+			goto domaceagain;
+		}
+		else if (mwidth != 0)
+		{
+			if ((mwidth = -(mwidth + ((firsttype == chainlink) ? 1 : 2))) < 0)
+				break;
+			mdoall = false;
+			goto domaceagain;
+		}
+
+#undef makemace
+
 		break;
 	}
 	case MT_PARTICLEGEN:
 	{
 		fixed_t radius, speed, bottomheight, topheight;
-		INT32 type, numdivisions, time, anglespeed;
+		INT32 type, numdivisions, time, anglespeed, ticcount;
 		angle_t angledivision;
-		size_t line;
+		INT32 line;
 		const size_t mthingi = (size_t)(mthing - mapthings);
 
-		for (line = 0; line < numlines; line++)
-		{
-			if (lines[line].special == 15 && lines[line].tag == mthing->angle)
-				break;
-		}
+		// Find the corresponding linedef special, using angle as tag
+		line = P_FindSpecialLineFromTag(15, mthing->angle, -1);
 
-		if (line == numlines)
+		if (line == -1)
 		{
-			CONS_Debug(DBG_GAMELOGIC, "Particle generator (mapthing #%s) needs tagged to a #15 parameter line (trying to find tag %d).\n", sizeu1(mthingi), mthing->angle);
+			CONS_Debug(DBG_GAMELOGIC, "Particle generator (mapthing #%s) needs to be tagged to a #15 parameter line (trying to find tag %d).\n", sizeu1(mthingi), mthing->angle);
 			return;
 		}
 
@@ -9888,6 +10057,10 @@ ML_NOCLIMB : Direction not controllable
 		bottomheight = lines[line].frontsector->floorheight;
 		topheight = lines[line].frontsector->ceilingheight - mobjinfo[(mobjtype_t)type].height;
 
+		if (!lines[line].backsector
+		|| (ticcount = (sides[lines[line].sidenum[1]].textureoffset >> FRACBITS)) < 1)
+			ticcount = states[S_PARTICLEGEN].tics;
+
 		numdivisions = (mthing->options >> ZSHIFT);
 
 		if (numdivisions)
@@ -9924,8 +10097,9 @@ ML_NOCLIMB : Direction not controllable
 				"Numdivisions is %d\n"
 				"Angledivision is %d\n"
 				"Time is %d\n"
-				"Type is %d\n",
-				sizeu1(mthingi), radius, speed, anglespeed, numdivisions, angledivision, time, type);
+				"Type is %d\n"
+				"Tic seperation is %d\n",
+				sizeu1(mthingi), radius, speed, anglespeed, numdivisions, angledivision, time, type, ticcount);
 
 		mobj->angle = 0;
 		mobj->movefactor = speed;
@@ -9935,6 +10109,7 @@ ML_NOCLIMB : Direction not controllable
 		mobj->health = time;
 		mobj->friction = radius;
 		mobj->threshold = type;
+		mobj->reactiontime = ticcount;
 
 		break;
 	}
@@ -10008,28 +10183,10 @@ ML_NOCLIMB : Direction not controllable
 				mobj->radius = (mthing->angle & 16383)*FRACUNIT;
 		}
 	}
-	else if (i == MT_EMMY)
+	else if (i == MT_TOKEN)
 	{
 		if (mthing->options & MTF_OBJECTSPECIAL) // Mario Block version
 			mobj->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT);
-		else
-		{
-			fixed_t zheight = mobj->z;
-			mobj_t *tokenobj;
-
-			if (mthing->options & MTF_OBJECTFLIP)
-				zheight += mobj->height-FixedMul(mobjinfo[MT_TOKEN].height, mobj->scale); // align with emmy properly!
-
-			tokenobj = P_SpawnMobj(x, y, zheight, MT_TOKEN);
-			P_SetTarget(&mobj->tracer, tokenobj);
-			tokenobj->destscale = mobj->scale;
-			P_SetScale(tokenobj, mobj->scale);
-			if (mthing->options & MTF_OBJECTFLIP) // flip token to match emmy
-			{
-				tokenobj->eflags |= MFE_VERTICALFLIP;
-				tokenobj->flags2 |= MF2_OBJECTFLIP;
-			}
-		}
 
 		// We advanced tokenbits earlier due to the return check.
 		// Subtract 1 here for the correct value.
@@ -10080,8 +10237,8 @@ ML_NOCLIMB : Direction not controllable
 			mobj->flags &= ~MF_SCENERY;
 			mobj->fuse = mthing->angle + mobj->info->speed;
 		}
-		// Use per-thing collision for spikes if the deaf flag is checked.
-		if (mthing->options & MTF_AMBUSH && !metalrecording)
+		// Use per-thing collision for spikes if the deaf flag isn't checked.
+		if (!(mthing->options & MTF_AMBUSH) && !metalrecording)
 		{
 			P_UnsetThingPosition(mobj);
 			mobj->flags &= ~(MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT);
@@ -10089,6 +10246,38 @@ ML_NOCLIMB : Direction not controllable
 			P_SetThingPosition(mobj);
 		}
 	}
+	else if (i == MT_WALLSPIKE)
+	{
+		// Pop up spikes!
+		if (mthing->options & MTF_OBJECTSPECIAL)
+		{
+			mobj->flags &= ~MF_SCENERY;
+			mobj->fuse = mobj->info->speed;
+		}
+		// Use per-thing collision for spikes if the deaf flag isn't checked.
+		if (!(mthing->options & MTF_AMBUSH) && !metalrecording)
+		{
+			P_UnsetThingPosition(mobj);
+			mobj->flags &= ~(MF_NOBLOCKMAP|MF_NOCLIPHEIGHT);
+			mobj->flags |= MF_SOLID;
+			P_SetThingPosition(mobj);
+		}
+
+		// spawn base
+		{
+			const angle_t mobjangle = FixedAngle(mthing->angle*FRACUNIT); // the mobj's own angle hasn't been set quite yet so...
+			const fixed_t baseradius = mobj->radius - mobj->scale;
+			mobj_t *base = P_SpawnMobj(
+					mobj->x - P_ReturnThrustX(mobj, mobjangle, baseradius),
+					mobj->y - P_ReturnThrustY(mobj, mobjangle, baseradius),
+					mobj->z, MT_WALLSPIKEBASE);
+			base->angle = mobjangle + ANGLE_90;
+			base->destscale = mobj->destscale;
+			P_SetScale(base, mobj->scale);
+			P_SetTarget(&base->target, mobj);
+			P_SetTarget(&mobj->tracer, base);
+		}
+	}
 
 	//count 10 ring boxes into the number of rings equation too.
 	if (i == MT_RING_BOX)
@@ -10161,7 +10350,6 @@ ML_NOCLIMB : Direction not controllable
 				// Spawn already displayed
 				mobj->flags |= MF_SPECIAL;
 				mobj->flags &= ~MF_NIGHTSITEM;
-				P_SetMobjState(mobj, mobj->info->seestate);
 			}
 
 			if (mobj->flags & MF_PUSHABLE)
@@ -10214,6 +10402,10 @@ ML_NOCLIMB : Direction not controllable
 		mobj->flags2 |= MF2_OBJECTFLIP;
 	}
 
+	// Final set of not being able to draw nightsitems.
+	if (mobj->flags & MF_NIGHTSITEM)
+		mobj->flags2 |= MF2_DONTDRAW;
+
 	mthing->mobj = mobj;
 }
 
@@ -11215,4 +11407,4 @@ mobj_t *P_SpawnMobjFromMobj(mobj_t *mobj, fixed_t xofs, fixed_t yofs, fixed_t zo
 	newmobj->destscale = mobj->destscale;
 	P_SetScale(newmobj, mobj->scale);
 	return newmobj;
-}
+}
\ No newline at end of file
diff --git a/src/p_mobj.h b/src/p_mobj.h
index f6ebd3cadc497465312a139860ee2975b74619cc..c6e1bfbf2317b3f6a4f2f83f42fe04f9e63102fb 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -194,6 +194,7 @@ typedef enum
 	MF2_AMBUSH         = 1<<27, // Alternate behaviour typically set by MTF_AMBUSH
 	MF2_LINKDRAW       = 1<<28, // Draw vissprite of mobj immediately before/after tracer's vissprite (dependent on dispoffset and position)
 	MF2_SHIELD         = 1<<29, // Thinker calls P_AddShield/P_ShieldLook (must be partnered with MF_SCENERY to use)
+	MF2_MACEROTATE     = 1<<30, // Thinker calls P_MaceRotate around tracer
 	// free: to and including 1<<31
 } mobjflag2_t;
 
diff --git a/src/p_polyobj.c b/src/p_polyobj.c
index 7776ab19a601fd807278ad40dd22c8e8d8a607ac..fd3237c9da8f71f53686270f6ff7ee5daa9fb465 100644
--- a/src/p_polyobj.c
+++ b/src/p_polyobj.c
@@ -1237,7 +1237,7 @@ static void Polyobj_rotateLine(line_t *ld)
 
 	// determine slopetype
 	ld->slopetype = !ld->dx ? ST_VERTICAL : !ld->dy ? ST_HORIZONTAL :
-			FixedDiv(ld->dy, ld->dx) > 0 ? ST_POSITIVE : ST_NEGATIVE;
+			((ld->dy > 0) == (ld->dx > 0)) ? ST_POSITIVE : ST_NEGATIVE;
 
 	// update bounding box
 	if (v1->x < v2->x)
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 7697ce546b804b25d3568d128ba8b59cd4b54222..43425d6eb3300df84a503a5f88eed4f4699f803b 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -3161,7 +3161,8 @@ static inline void P_ArchiveMisc(void)
 	else
 		WRITEINT16(save_p, gamemap);
 
-	lastmapsaved = gamemap;
+	//lastmapsaved = gamemap;
+	lastmaploaded = gamemap;
 
 	WRITEUINT16(save_p, (botskin ? (emeralds|(1<<10)) : emeralds)+357);
 	WRITESTRINGN(save_p, timeattackfolder, sizeof(timeattackfolder));
@@ -3186,7 +3187,8 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride)
 	if(!mapheaderinfo[gamemap-1])
 		P_AllocMapHeader(gamemap-1);
 
-	lastmapsaved = gamemap;
+	//lastmapsaved = gamemap;
+	lastmaploaded = gamemap;
 
 	tokenlist = 0;
 	token = 0;
diff --git a/src/p_setup.c b/src/p_setup.c
index a0c745e6085f3b6ec00278943b5edd1ab54a62a9..9c4bede7474571667eff241d2736e683418ea46b 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -54,6 +54,8 @@
 
 #include "v_video.h"
 
+#include "filesrch.h" // refreshdirmenu
+
 // wipes
 #include "f_finale.h"
 
@@ -197,74 +199,42 @@ void P_DeleteFlickies(INT16 i)
 static void P_ClearSingleMapHeaderInfo(INT16 i)
 {
 	const INT16 num = (INT16)(i-1);
-	DEH_WriteUndoline("LEVELNAME", mapheaderinfo[num]->lvlttl, UNDO_NONE);
 	mapheaderinfo[num]->lvlttl[0] = '\0';
-	DEH_WriteUndoline("SELECTHEADING", mapheaderinfo[num]->selectheading, UNDO_NONE);
 	mapheaderinfo[num]->selectheading[0] = '\0';
-	DEH_WriteUndoline("SUBTITLE", mapheaderinfo[num]->subttl, UNDO_NONE);
 	mapheaderinfo[num]->subttl[0] = '\0';
-	DEH_WriteUndoline("ACT", va("%d", mapheaderinfo[num]->actnum), UNDO_NONE);
 	mapheaderinfo[num]->actnum = 0;
-	DEH_WriteUndoline("TYPEOFLEVEL", va("%d", mapheaderinfo[num]->typeoflevel), UNDO_NONE);
 	mapheaderinfo[num]->typeoflevel = 0;
-	DEH_WriteUndoline("NEXTLEVEL", va("%d", mapheaderinfo[num]->nextlevel), UNDO_NONE);
 	mapheaderinfo[num]->nextlevel = (INT16)(i + 1);
-	DEH_WriteUndoline("MUSIC", mapheaderinfo[num]->musname, UNDO_NONE);
 	snprintf(mapheaderinfo[num]->musname, 7, "%sM", G_BuildMapName(i));
 	mapheaderinfo[num]->musname[6] = 0;
-	DEH_WriteUndoline("MUSICTRACK", va("%d", mapheaderinfo[num]->mustrack), UNDO_NONE);
 	mapheaderinfo[num]->mustrack = 0;
-	DEH_WriteUndoline("FORCECHARACTER", va("%d", mapheaderinfo[num]->forcecharacter), UNDO_NONE);
 	mapheaderinfo[num]->forcecharacter[0] = '\0';
-	DEH_WriteUndoline("WEATHER", va("%d", mapheaderinfo[num]->weather), UNDO_NONE);
 	mapheaderinfo[num]->weather = 0;
-	DEH_WriteUndoline("SKYNUM", va("%d", mapheaderinfo[num]->skynum), UNDO_NONE);
 	mapheaderinfo[num]->skynum = 1;
-	DEH_WriteUndoline("SKYBOXSCALEX", va("%d", mapheaderinfo[num]->skybox_scalex), UNDO_NONE);
 	mapheaderinfo[num]->skybox_scalex = 16;
-	DEH_WriteUndoline("SKYBOXSCALEY", va("%d", mapheaderinfo[num]->skybox_scaley), UNDO_NONE);
 	mapheaderinfo[num]->skybox_scaley = 16;
-	DEH_WriteUndoline("SKYBOXSCALEZ", va("%d", mapheaderinfo[num]->skybox_scalez), UNDO_NONE);
 	mapheaderinfo[num]->skybox_scalez = 16;
-	DEH_WriteUndoline("INTERSCREEN", mapheaderinfo[num]->interscreen, UNDO_NONE);
 	mapheaderinfo[num]->interscreen[0] = '#';
-	DEH_WriteUndoline("RUNSOC", mapheaderinfo[num]->runsoc, UNDO_NONE);
 	mapheaderinfo[num]->runsoc[0] = '#';
-	DEH_WriteUndoline("SCRIPTNAME", mapheaderinfo[num]->scriptname, UNDO_NONE);
 	mapheaderinfo[num]->scriptname[0] = '#';
-	DEH_WriteUndoline("PRECUTSCENENUM", va("%d", mapheaderinfo[num]->precutscenenum), UNDO_NONE);
 	mapheaderinfo[num]->precutscenenum = 0;
-	DEH_WriteUndoline("CUTSCENENUM", va("%d", mapheaderinfo[num]->cutscenenum), UNDO_NONE);
 	mapheaderinfo[num]->cutscenenum = 0;
-	DEH_WriteUndoline("COUNTDOWN", va("%d", mapheaderinfo[num]->countdown), UNDO_NONE);
 	mapheaderinfo[num]->countdown = 0;
-	DEH_WriteUndoline("PALLETE", va("%u", mapheaderinfo[num]->palette), UNDO_NONE);
 	mapheaderinfo[num]->palette = UINT16_MAX;
-	DEH_WriteUndoline("NUMLAPS", va("%u", mapheaderinfo[num]->numlaps), UNDO_NONE);
 	mapheaderinfo[num]->numlaps = NUMLAPS_DEFAULT;
-	DEH_WriteUndoline("UNLOCKABLE", va("%s", mapheaderinfo[num]->unlockrequired), UNDO_NONE);
 	mapheaderinfo[num]->unlockrequired = -1;
-	DEH_WriteUndoline("LEVELSELECT", va("%d", mapheaderinfo[num]->levelselect), UNDO_NONE);
 	mapheaderinfo[num]->levelselect = 0;
-	DEH_WriteUndoline("BONUSTYPE", va("%d", mapheaderinfo[num]->bonustype), UNDO_NONE);
 	mapheaderinfo[num]->bonustype = 0;
-	DEH_WriteUndoline("LEVELFLAGS", va("%d", mapheaderinfo[num]->levelflags), UNDO_NONE);
 	mapheaderinfo[num]->levelflags = 0;
-	DEH_WriteUndoline("MENUFLAGS", va("%d", mapheaderinfo[num]->menuflags), UNDO_NONE);
 	mapheaderinfo[num]->menuflags = 0;
-	// Flickies. Nope, no delfile support here either
 #if 1 // equivalent to "FlickyList = DEMO"
 	P_SetDemoFlickies(num);
 #else // equivalent to "FlickyList = NONE"
 	P_DeleteFlickies(num);
 #endif
-	// TODO grades support for delfile (pfft yeah right)
 	P_DeleteGrades(num);
-	// an even further impossibility, delfile custom opts support
 	mapheaderinfo[num]->customopts = NULL;
 	mapheaderinfo[num]->numCustomOptions = 0;
-
-	DEH_WriteUndoline(va("# uload for map %d", i), NULL, UNDO_DONE);
 }
 
 /** Allocates a new map-header structure.
@@ -487,6 +457,7 @@ static void P_LoadSegs(lumpnum_t lumpnum)
 			//Hurdler: 04/12/2000: for now, only used in hardware mode
 			li->lightmaps = NULL; // list of static lightmap for this seg
 		}
+		li->pv1 = li->pv2 = NULL;
 #endif
 
 		li->angle = (SHORT(ml->angle))<<FRACBITS;
@@ -1118,7 +1089,20 @@ static inline void P_SpawnEmblems(void)
 			P_SetThingPosition(emblemmobj);
 		}
 		else
+		{
 			emblemmobj->frame &= ~FF_TRANSMASK;
+
+			if (emblemlocations[i].type == ET_GLOBAL)
+			{
+				emblemmobj->reactiontime = emblemlocations[i].var;
+				if (emblemlocations[i].var & GE_NIGHTSITEM)
+				{
+					emblemmobj->flags |= MF_NIGHTSITEM;
+					emblemmobj->flags &= ~MF_SPECIAL;
+					emblemmobj->flags2 |= MF2_DONTDRAW;
+				}
+			}
+		}
 	}
 }
 
@@ -1212,7 +1196,7 @@ static void P_LoadLineDefs(lumpnum_t lumpnum)
 			ld->slopetype = ST_VERTICAL;
 		else if (!ld->dy)
 			ld->slopetype = ST_HORIZONTAL;
-		else if (FixedDiv(ld->dy, ld->dx) > 0)
+		else if ((ld->dy > 0) == (ld->dx > 0))
 			ld->slopetype = ST_POSITIVE;
 		else
 			ld->slopetype = ST_NEGATIVE;
@@ -1578,6 +1562,7 @@ static void P_LoadSideDefs2(lumpnum_t lumpnum)
 				break;
 			}
 
+			case 9: // Mace parameters
 			case 14: // Bustable block parameters
 			case 15: // Fan particle spawner parameters
 			case 425: // Calls P_SetMobjState on calling mobj
@@ -2181,6 +2166,7 @@ lumpnum_t lastloadedmaplumpnum; // for comparative savegame
 static void P_LevelInitStuff(void)
 {
 	INT32 i;
+	boolean canresetlives = true;
 
 	leveltime = 0;
 
@@ -2198,7 +2184,18 @@ static void P_LevelInitStuff(void)
 
 	// map time limit
 	if (mapheaderinfo[gamemap-1]->countdown)
+	{
+		tic_t maxtime = 0;
 		countdowntimer = mapheaderinfo[gamemap-1]->countdown * TICRATE;
+		for (i = 0; i < MAXPLAYERS; i++)
+		{
+			if (!playeringame[i])
+				continue;
+			if (players[i].starposttime > maxtime)
+				maxtime = players[i].starposttime;
+		}
+		countdowntimer -= maxtime;
+	}
 	else
 		countdowntimer = 0;
 	countdowntimeup = false;
@@ -2220,9 +2217,21 @@ static void P_LevelInitStuff(void)
 	// earthquake camera
 	memset(&quake,0,sizeof(struct quake));
 
+	if ((netgame || multiplayer) && gametype == GT_COOP && cv_coopstarposts.value == 2)
+	{
+		for (i = 0; i < MAXPLAYERS; i++)
+		{
+			if (playeringame[i] && players[i].lives > 0)
+			{
+				canresetlives = false;
+				break;
+			}
+		}
+	}
+
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
-		if ((netgame || multiplayer) && (gametype == GT_COMPETITION || players[i].lives <= 0))
+		if (canresetlives && (netgame || multiplayer) && playeringame[i] && (gametype == GT_COMPETITION || players[i].lives <= 0))
 		{
 			// In Co-Op, replenish a user's lives if they are depleted.
 			players[i].lives = cv_startinglives.value;
@@ -2276,6 +2285,17 @@ void P_LoadThingsOnly(void)
 	// Search through all the thinkers.
 	mobj_t *mo;
 	thinker_t *think;
+	INT32 i, viewid = -1, centerid = -1; // for skyboxes
+
+	// check if these are any of the normal viewpoint/centerpoint mobjs in the level or not
+	if (skyboxmo[0] || skyboxmo[1])
+		for (i = 0; i < 16; i++)
+		{
+			if (skyboxmo[0] && skyboxmo[0] == skyboxviewpnts[i])
+				viewid = i; // save id just in case
+			if (skyboxmo[1] && skyboxmo[1] == skyboxcenterpnts[i])
+				centerid = i; // save id just in case
+		}
 
 	for (think = thinkercap.next; think != &thinkercap; think = think->next)
 	{
@@ -2293,6 +2313,10 @@ void P_LoadThingsOnly(void)
 	P_PrepareThings(lastloadedmaplumpnum + ML_THINGS);
 	P_LoadThings();
 
+	// restore skybox viewpoint/centerpoint if necessary, set them to defaults if we can't do that
+	skyboxmo[0] = skyboxviewpnts[(viewid >= 0) ? viewid : 0];
+	skyboxmo[1] = skyboxcenterpnts[(centerid >= 0) ? centerid : 0];
+
 	P_SpawnSecretItems(true);
 }
 
@@ -2517,6 +2541,21 @@ static void P_LoadNightsGhosts(void)
 	free(gpath);
 }
 
+static boolean CanSaveLevel(INT32 mapnum)
+{
+	if (ultimatemode) // never save in ultimate (probably redundant with cursaveslot also being checked)
+		return false;
+
+	if (G_IsSpecialStage(mapnum) // don't save in special stages
+		|| mapnum == lastmaploaded) // don't save if the last map loaded was this one
+		return false;
+	
+	// Any levels that have the savegame flag can save normally.
+	// If the game is complete for this save slot, then any level can save!
+	// On the other side of the spectrum, if lastmaploaded is 0, then the save file has only just been created and needs to save ASAP!
+	return (mapheaderinfo[mapnum-1]->levelflags & LF_SAVEGAME || gamecomplete || !lastmaploaded);
+}
+
 /** Loads a level from a lump or external wad.
   *
   * \param skipprecip If true, don't spawn precipitation.
@@ -2574,8 +2613,7 @@ boolean P_SetupLevel(boolean skipprecip)
 
 	postimgtype = postimgtype2 = postimg_none;
 
-	if (mapheaderinfo[gamemap-1]->forcecharacter[0] != '\0'
-	&& atoi(mapheaderinfo[gamemap-1]->forcecharacter) != 255)
+	if (mapheaderinfo[gamemap-1]->forcecharacter[0] != '\0')
 		P_ForceCharacter(mapheaderinfo[gamemap-1]->forcecharacter);
 
 	// chasecam on in chaos, race, coop
@@ -2728,15 +2766,25 @@ boolean P_SetupLevel(boolean skipprecip)
 	for (i = 0; i < 2; i++)
 		skyboxmo[i] = NULL;
 
+	for (i = 0; i < 16; i++)
+		skyboxviewpnts[i] = skyboxcenterpnts[i] = NULL;
+
 	P_MapStart();
 
 	P_PrepareThings(lastloadedmaplumpnum + ML_THINGS);
 
+	// init gravity, tag lists,
+	// anything that P_ResetDynamicSlopes/P_LoadThings needs to know
+	P_InitSpecials();
+
 #ifdef ESLOPE
 	P_ResetDynamicSlopes();
 #endif
 
 	P_LoadThings();
+	// skybox mobj defaults
+	skyboxmo[0] = skyboxviewpnts[0];
+	skyboxmo[1] = skyboxcenterpnts[0];
 
 	P_SpawnSecretItems(loademblems);
 
@@ -2750,8 +2798,6 @@ boolean P_SetupLevel(boolean skipprecip)
 	if (loadprecip) //  ugly hack for P_NetUnArchiveMisc (and P_LoadNetGame)
 		P_SpawnPrecipitation();
 
-	globalweather = mapheaderinfo[gamemap-1]->weather;
-
 #ifdef HWRENDER // not win32 only 19990829 by Kin
 	if (rendermode != render_soft && rendermode != render_none)
 	{
@@ -2799,6 +2845,19 @@ boolean P_SetupLevel(boolean skipprecip)
 			}
 		}
 
+	// restore time in netgame (see also g_game.c)
+	if ((netgame || multiplayer) && gametype == GT_COOP && cv_coopstarposts.value == 2)
+	{
+		// is this a hack? maybe
+		tic_t maxstarposttime = 0;
+		for (i = 0; i < MAXPLAYERS; i++)
+		{
+			if (playeringame[i] && players[i].starposttime > maxstarposttime)
+				maxstarposttime = players[i].starposttime;
+		}
+		leveltime = maxstarposttime;
+	}
+
 	if (modeattacking == ATTACKING_RECORD && !demoplayback)
 		P_LoadRecordGhosts();
 	else if (modeattacking == ATTACKING_NIGHTS && !demoplayback)
@@ -2960,11 +3019,11 @@ boolean P_SetupLevel(boolean skipprecip)
 	P_RunCachedActions();
 
 	if (!(netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking || players[consoleplayer].lives <= 0)
-		&& (!modifiedgame || savemoddata) && cursaveslot >= 0 && !ultimatemode
-		&& !(mapheaderinfo[gamemap-1]->menuflags & LF2_HIDEINMENU)
-		&& (!G_IsSpecialStage(gamemap)) && gamemap != lastmapsaved && (mapheaderinfo[gamemap-1]->actnum < 2 || gamecomplete))
+		&& (!modifiedgame || savemoddata) && cursaveslot >= 0 && CanSaveLevel(gamemap))
 		G_SaveGame((UINT32)cursaveslot);
 
+	lastmaploaded = gamemap; // HAS to be set after saving!!
+
 	if (savedata.lives > 0)
 	{
 		players[consoleplayer].continues = savedata.continues;
@@ -3028,11 +3087,11 @@ boolean P_AddWadFile(const char *wadfilename, char **firstmapname)
 	INT16 firstmapreplaced = 0, num;
 	char *name;
 	lumpinfo_t *lumpinfo;
-	boolean texturechange = false;
 	boolean replacedcurrentmap = false;
 
 	if ((numlumps = W_LoadWadFile(wadfilename)) == INT16_MAX)
 	{
+		refreshdirmenu |= REFRESHDIR_NOTLOADED;
 		CONS_Printf(M_GetText("Errors occured while loading %s; not added.\n"), wadfilename);
 		return false;
 	}
@@ -3071,14 +3130,6 @@ boolean P_AddWadFile(const char *wadfilename, char **firstmapname)
 			CONS_Debug(DBG_SETUP, "Music %.8s replaced\n", name);
 			digmreplaces++;
 		}
-#if 0
-		//
-		// search for texturechange replacements
-		//
-		else if (!memcmp(name, "TEXTURE1", 8) || !memcmp(name, "TEXTURE2", 8)
-			|| !memcmp(name, "PNAMES", 6))
-#endif
-			texturechange = true;
 	}
 	if (!devparm && sreplaces)
 		CONS_Printf(M_GetText("%s sounds replaced\n"), sizeu1(sreplaces));
@@ -3094,13 +3145,10 @@ boolean P_AddWadFile(const char *wadfilename, char **firstmapname)
 
 	// Reload it all anyway, just in case they
 	// added some textures but didn't insert a
-	// TEXTURE1/PNAMES/etc. list.
-	if (texturechange) // initialized in the sound check
-		R_LoadTextures(); // numtexture changes
-	else
-		R_FlushTextureCache(); // just reload it from file
+	// TEXTURES/etc. list.
+	R_LoadTextures(); // numtexture changes
 
-	// Reload ANIMATED / ANIMDEFS
+	// Reload ANIMDEFS
 	P_InitPicAnims();
 
 	// Flush and reload HUD graphics
@@ -3113,6 +3161,7 @@ boolean P_AddWadFile(const char *wadfilename, char **firstmapname)
 	// look for skins
 	//
 	R_AddSkins(wadnum); // faB: wadfile index in wadfiles[]
+	R_PatchSkins(wadnum); // toast: PATCH PATCH
 
 	//
 	// search for maps
@@ -3163,31 +3212,3 @@ boolean P_AddWadFile(const char *wadfilename, char **firstmapname)
 
 	return true;
 }
-
-#ifdef DELFILE
-boolean P_DelWadFile(void)
-{
-	sfxenum_t i;
-	const UINT16 wadnum = (UINT16)(numwadfiles - 1);
-	const lumpnum_t lumpnum = numwadfiles<<16;
-	//lumpinfo_t *lumpinfo = wadfiles[wadnum]->lumpinfo;
-	R_DelSkins(wadnum); // only used by DELFILE
-	R_DelSpriteDefs(wadnum); // only used by DELFILE
-	for (i = 0; i < NUMSFX; i++)
-	{
-		if (S_sfx[i].lumpnum != LUMPERROR && S_sfx[i].lumpnum >= lumpnum)
-		{
-			S_StopSoundByNum(i);
-			S_RemoveSoundFx(i);
-			if (S_sfx[i].lumpnum != LUMPERROR)
-			{
-				I_FreeSfx(&S_sfx[i]);
-				S_sfx[i].lumpnum = LUMPERROR;
-			}
-		}
-	}
-	W_UnloadWadFile(wadnum); // only used by DELFILE
-	R_LoadTextures();
-	return false;
-}
-#endif
diff --git a/src/p_setup.h b/src/p_setup.h
index 95976d2761587504315cdde6a5dfae3b8da727f9..3443ffdb7766fa375acbc87874a15d4038da1f8f 100644
--- a/src/p_setup.h
+++ b/src/p_setup.h
@@ -60,9 +60,6 @@ void P_ScanThings(INT16 mapnum, INT16 wadnum, INT16 lumpnum);
 void P_LoadThingsOnly(void);
 boolean P_SetupLevel(boolean skipprecip);
 boolean P_AddWadFile(const char *wadfilename, char **firstmapname);
-#ifdef DELFILE
-boolean P_DelWadFile(void);
-#endif
 boolean P_RunSOC(const char *socfilename);
 void P_WriteThings(lumpnum_t lump);
 size_t P_PrecacheLevelFlats(void);
diff --git a/src/p_sight.c b/src/p_sight.c
index bd6ab4d730f308973b011af421f147384ecbd648..132f993cfe1a1a95e4f083438fcd79eec2a4533b 100644
--- a/src/p_sight.c
+++ b/src/p_sight.c
@@ -103,12 +103,20 @@ static fixed_t P_InterceptVector2(divline_t *v2, divline_t *v1)
 static boolean P_CrossSubsecPolyObj(polyobj_t *po, register los_t *los)
 {
 	size_t i;
+	sector_t *polysec;
+
+	if (!(po->flags & POF_RENDERALL))
+		return true; // the polyobject isn't visible, so we can ignore it
+
+	polysec = po->lines[0]->backsector;
 
 	for (i = 0; i < po->numLines; ++i)
 	{
 		line_t *line = po->lines[i];
 		divline_t divl;
 		const vertex_t *v1,*v2;
+		fixed_t frac;
+		fixed_t topslope, bottomslope;
 
 		// already checked other side?
 		if (line->validcount == validcount)
@@ -140,7 +148,22 @@ static boolean P_CrossSubsecPolyObj(polyobj_t *po, register los_t *los)
 			continue;
 
 		// stop because it is not two sided
-		return false;
+		//if (!(po->flags & POF_TESTHEIGHT))
+			//return false;
+
+		frac = P_InterceptVector2(&los->strace, &divl);
+
+		// get slopes of top and bottom of this polyobject line
+		topslope = FixedDiv(polysec->ceilingheight - los->sightzstart , frac);
+		bottomslope = FixedDiv(polysec->floorheight - los->sightzstart , frac);
+
+		if (topslope >= los->topslope && bottomslope <= los->bottomslope)
+			return false; // view completely blocked
+
+		// TODO: figure out if it's worth considering partially blocked cases or not?
+		// maybe to adjust los's top/bottom slopes if needed
+		//if (los->topslope <= los->bottomslope)
+			//return false;
 	}
 
 	return true;
diff --git a/src/p_spec.c b/src/p_spec.c
index c327f0d1333398c911ae7e23c659fa46d32bc1f3..2cad4fc904dd8a8cae32edbabd8f2e08c957656d 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -46,7 +46,9 @@
 #include <errno.h>
 #endif
 
-mobj_t *skyboxmo[2];
+mobj_t *skyboxmo[2]; // current skybox mobjs: 0 = viewpoint, 1 = centerpoint
+mobj_t *skyboxviewpnts[16]; // array of MT_SKYBOX viewpoint mobjs
+mobj_t *skyboxcenterpnts[16]; // array of MT_SKYBOX centerpoint mobjs
 
 // Amount (dx, dy) vector linedef is shifted right to get scroll amount
 #define SCROLL_SHIFT 5
@@ -72,7 +74,7 @@ typedef struct
 #endif
 
 /** Animated texture definition.
-  * Used for ::harddefs and for loading an ANIMATED lump from a wad.
+  * Used for loading an ANIMDEFS lump from a wad.
   *
   * Animations are defined by the first and last frame (i.e., flat or texture).
   * The animation sequence uses all flats between the start and end entry, in
@@ -111,7 +113,7 @@ static void P_AddFakeFloorsByLine(size_t line, ffloortype_e ffloorflags, thinker
 static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec);
 static void Add_Friction(INT32 friction, INT32 movefactor, INT32 affectee, INT32 referrer);
 static void P_AddSpikeThinker(sector_t *sec, INT32 referrer);
-static void P_AddPlaneDisplaceThinker(INT32 type, fixed_t speed, INT32 control, INT32 affectee);
+static void P_AddPlaneDisplaceThinker(INT32 type, fixed_t speed, INT32 control, INT32 affectee, UINT8 reverse);
 
 
 //SoM: 3/7/2000: New sturcture without limits.
@@ -119,104 +121,6 @@ static anim_t *lastanim;
 static anim_t *anims = NULL; /// \todo free leak
 static size_t maxanims;
 
-//
-// P_InitPicAnims
-//
-/** Hardcoded animation sequences.
-  * Used if no ANIMATED lump is found in a loaded wad.
-  */
-static animdef_t harddefs[] =
-{
-	// flat animations.
-	{false,     "LITEY3",       "LITEY1",       4},
-	{false,     "FWATER16",     "FWATER1",      4},
-	{false,     "BWATER16",     "BWATER01",     4},
-	{false,     "LWATER16",     "LWATER1",      4},
-	{false,     "WATER7",       "WATER0",       4},
-	{false,     "LAVA4",        "LAVA1",        8},
-	{false,     "DLAVA4",       "DLAVA1",       8},
-	{false,     "RLAVA8",       "RLAVA1",       8},
-	{false,     "LITER3",       "LITER1",       8},
-	{false,     "SURF08",       "SURF01",       4},
-
-	{false,     "CHEMG16",      "CHEMG01",      4}, // THZ Chemical gunk
-	{false,     "GOOP16",       "GOOP01",       4}, // Green chemical gunk
-	{false,     "OIL16",        "OIL01",        4}, // Oil
-	{false,     "THZBOXF4",     "THZBOXF1",     2}, // Moved up with the flats
-	{false,     "ALTBOXF4",     "ALTBOXF1",     2},
-
-	{false,     "LITEB3",       "LITEB1",       4},
-	{false,     "LITEN3",       "LITEN1",       4},
-	{false,     "ACZRFL1H",     "ACZRFL1A",     4},
-	{false,     "ACZRFL2H",     "ACZRFL2A",     4},
-	{false,     "EGRIDF3",      "EGRIDF1",      4},
-	{false,     "ERZFAN4",      "ERZFAN1",      1},
-	{false,     "ERZFANR4",     "ERZFANR1",     1},
-	{false,     "DISCO4",       "DISCO1",      15},
-
-	// animated textures
-	{true,      "GFALL4",       "GFALL1",       2}, // Short waterfall
-	{true,      "CFALL4",       "CFALL1",       2}, // Long waterfall
-	{true,      "TFALL4",       "TFALL1",       2}, // THZ Chemical fall
-	{true,      "AFALL4",       "AFALL1",       2}, // Green Chemical fall
-	{true,      "QFALL4",       "QFALL1",       2}, // Quicksand fall
-	{true,      "Q2FALL4",      "Q2FALL1",      2},
-	{true,      "Q3FALL4",      "Q3FALL1",      2},
-	{true,      "Q4FALL4",      "Q4FALL1",      2},
-	{true,      "Q5FALL4",      "Q5FALL1",      2},
-	{true,      "Q6FALL4",      "Q6FALL1",      2},
-	{true,      "Q7FALL4",      "Q7FALL1",      2},
-	{true,      "LFALL4",       "LFALL1",       2},
-	{true,      "MFALL4",       "MFALL1",       2},
-	{true,      "OFALL4",       "OFALL1",       2},
-	{true,      "DLAVA4",       "DLAVA1",       8},
-	{true,      "ERZLASA2",     "ERZLASA1",     1},
-	{true,      "ERZLASB4",     "ERZLASB1",     1},
-	{true,      "ERZLASC4",     "ERZLASC1",     1},
-	{true,      "THZBOX04",     "THZBOX01",     2},
-	{true,      "ALTBOX04",     "ALTBOX01",     2},
-	{true,      "SFALL4",       "SFALL1",       4}, // Lava fall
-	{true,      "RVZFALL8",     "RVZFALL1",     4},
-	{true,      "BFALL4",       "BFALL1",       2}, // HPZ waterfall
-	{true,      "GREYW3",       "GREYW1",       4},
-	{true,      "BLUEW3",       "BLUEW1",       4},
-	{true,      "COMP6",        "COMP4",        4},
-	{true,      "RED3",         "RED1",         4},
-	{true,      "YEL3",         "YEL1",         4},
-	{true,      "ACWRFL1D",     "ACWRFL1A",     1},
-	{true,      "ACWRFL2D",     "ACWRFL2A",     1},
-	{true,      "ACWRFL3D",     "ACWRFL3A",     1},
-	{true,      "ACWRFL4D",     "ACWRFL4A",     1},
-	{true,      "ACWRP1D",      "ACWRP1A",      1},
-	{true,      "ACWRP2D",      "ACWRP2A",      1},
-	{true,      "ACZRP1D",      "ACZRP1A",      1},
-	{true,      "ACZRP2D",      "ACZRP2A",      1},
-	{true,      "OILFALL4",     "OILFALL1",     2},
-	{true,      "SOLFALL4",     "SOLFALL1",     2},
-	{true,      "DOWN1C",       "DOWN1A",       4},
-	{true,      "DOWN2C",       "DOWN2A",       4},
-	{true,      "DOWN3D",       "DOWN3A",       4},
-	{true,      "DOWN4C",       "DOWN4A",       4},
-	{true,      "DOWN5C",       "DOWN5A",       4},
-	{true,      "UP1C",         "UP1A",         4},
-	{true,      "UP2C",         "UP2A",         4},
-	{true,      "UP3D",         "UP3A",         4},
-	{true,      "UP4C",         "UP4A",         4},
-	{true,      "UP5C",         "UP5A",         4},
-	{true,      "EGRID3",       "EGRID1",       4},
-	{true,      "ERFANW4",      "ERFANW1",      1},
-	{true,      "ERFANX4",      "ERFANX1",      1},
-	{true,      "DISCOD4",      "DISCOD1",     15},
-	{true,      "DANCE4",       "DANCE1",       8},
-	{true,      "SKY135",       "SKY132",       2},
-	{true,      "APPLMS4",      "APPLMS1",      2},
-	{true,      "APBOXW3",      "APBOXW1",      2},
-	{true,      "ERZLAZC4",     "ERZLAZC1",     4},
-
-	// End of line
-	{   -1,             "",            "",    0},
-};
-
 // Animating line specials
 
 // Init animated textures
@@ -230,7 +134,7 @@ void P_ParseAnimationDefintion(SINT8 istexture);
 
 /** Sets up texture and flat animations.
   *
-  * Converts an ::animdef_t array loaded from ::harddefs or a lump into
+  * Converts an ::animdef_t array loaded from a lump into
   * ::anim_t format.
   *
   * Issues an error if any animation cycles are invalid.
@@ -242,70 +146,37 @@ void P_InitPicAnims(void)
 {
 	// Init animation
 	INT32 w; // WAD
-	UINT8 *animatedLump;
-	UINT8 *currentPos;
 	size_t i;
 
 	I_Assert(animdefs == NULL);
 
-	if (W_CheckNumForName("ANIMATED") != LUMPERROR || W_CheckNumForName("ANIMDEFS") != LUMPERROR)
+	maxanims = 0;
+
+	if (W_CheckNumForName("ANIMDEFS") != LUMPERROR)
 	{
-		for (w = numwadfiles-1, maxanims = 0; w >= 0; w--)
+		for (w = numwadfiles-1; w >= 0; w--)
 		{
-			UINT16 animatedLumpNum;
 			UINT16 animdefsLumpNum;
 
-			// Find ANIMATED lump in the WAD
-			animatedLumpNum = W_CheckNumForNamePwad("ANIMATED", w, 0);
-			if (animatedLumpNum != INT16_MAX)
-			{
-				animatedLump = (UINT8 *)W_CacheLumpNumPwad(w, animatedLumpNum, PU_STATIC);
-
-				// Get the number of animations in the file
-				i = maxanims;
-				for (currentPos = animatedLump; *currentPos != UINT8_MAX; maxanims++, currentPos+=23);
-
-				// Resize animdefs (or if it hasn't been created, create it)
-				animdefs = (animdef_t *)Z_Realloc(animdefs, sizeof(animdef_t)*(maxanims + 1), PU_STATIC, NULL);
-				// Sanity check it
-				if (!animdefs)
-					I_Error("Not enough free memory for ANIMATED data");
-
-				// Populate the new array
-				for (currentPos = animatedLump; *currentPos != UINT8_MAX; i++, currentPos+=23)
-				{
-					M_Memcpy(&(animdefs[i].istexture), currentPos, 1); // istexture, 1 byte
-					M_Memcpy(animdefs[i].endname, (currentPos + 1), 9); // endname, 9 bytes
-					M_Memcpy(animdefs[i].startname, (currentPos + 10), 9); // startname, 9 bytes
-					M_Memcpy(&(animdefs[i].speed), (currentPos + 19), 4); // speed, 4 bytes
-				}
-
-				Z_Free(animatedLump);
-			}
-
-			// Now find ANIMDEFS
+			// Find ANIMDEFS lump in the WAD
 			animdefsLumpNum = W_CheckNumForNamePwad("ANIMDEFS", w, 0);
 			if (animdefsLumpNum != INT16_MAX)
 				P_ParseANIMDEFSLump(w, animdefsLumpNum);
 		}
-		// Define the last one
-		animdefs[maxanims].istexture = -1;
-		strncpy(animdefs[maxanims].endname, "", 9);
-		strncpy(animdefs[maxanims].startname, "", 9);
-		animdefs[maxanims].speed = 0;
-	}
-	else
-	{
-		animdefs = harddefs;
-		for (maxanims = 0; animdefs[maxanims].istexture != -1; maxanims++);
 	}
 
+	// Define the last one
+	animdefs[maxanims].istexture = -1;
+	strncpy(animdefs[maxanims].endname, "", 9);
+	strncpy(animdefs[maxanims].startname, "", 9);
+	animdefs[maxanims].speed = 0;
+
 	if (anims)
 		free(anims);
 
 	anims = (anim_t *)malloc(sizeof (*anims)*(maxanims + 1));
 	if (!anims)
-		I_Error("Not enough free memory for ANIMATED data");
+		I_Error("Not enough free memory for ANIMDEFS data");
 
 	lastanim = anims;
 	for (i = 0; animdefs[i].istexture != -1; i++)
@@ -337,10 +208,7 @@ void P_InitPicAnims(void)
 				animdefs[i].startname, animdefs[i].endname);
 		}
 
-		if (animdefs == harddefs)
-			lastanim->speed = animdefs[i].speed;
-		else
-			lastanim->speed = LONG(animdefs[i].speed);
+		lastanim->speed = LONG(animdefs[i].speed);
 		lastanim++;
 	}
 	lastanim->istexture = -1;
@@ -348,8 +216,7 @@ void P_InitPicAnims(void)
 
 	// Clear animdefs now that we're done with it.
 	// We'll only be using anims from now on.
-	if (animdefs != harddefs)
-		Z_Free(animdefs);
+	Z_Free(animdefs);
 	animdefs = NULL;
 }
 
@@ -454,7 +321,8 @@ void P_ParseAnimationDefintion(SINT8 istexture)
 
 	// Search for existing animdef
 	for (i = 0; i < maxanims; i++)
-		if (stricmp(animdefsToken, animdefs[i].startname) == 0)
+		if (animdefs[i].istexture == istexture // Check if it's the same type!
+		&& stricmp(animdefsToken, animdefs[i].startname) == 0)
 		{
 			//CONS_Alert(CONS_NOTICE, "Duplicate animation: %s\n", animdefsToken);
 
@@ -1746,7 +1614,7 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 		case 305: // continuous
 		case 306: // each time
 		case 307: // once
-			if (!(actor && actor->player && actor->player->charability != dist/10))
+			if (!(actor && actor->player && actor->player->charability == dist/10))
 				return false;
 			break;
 		case 309: // continuous
@@ -2442,73 +2310,81 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 		case 414: // Play SFX
 			{
-				fixed_t sfxnum;
+				INT32 sfxnum;
 
 				sfxnum = sides[line->sidenum[0]].toptexture;
 
-				if (line->tag != 0 && line->flags & ML_EFFECT5)
+				if (sfxnum == sfx_None)
+					return; // Do nothing!
+				if (sfxnum < sfx_None || sfxnum >= NUMSFX)
 				{
-					sector_t *sec;
-
-					while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
-					{
-						sec = &sectors[secnum];
-						S_StartSound(&sec->soundorg, sfxnum);
-					}
+					CONS_Debug(DBG_GAMELOGIC, "Line type 414 Executor: sfx number %d is invalid!\n", sfxnum);
+					return;
 				}
-				else if (line->tag != 0 && mo)
+				if (line->tag != 0) // Do special stuff only if a non-zero linedef tag is set
 				{
-					// Only trigger if mobj is touching the tag
-					ffloor_t *rover;
-					boolean foundit = false;
+					if (line->flags & ML_EFFECT5) // Repeat Midtexture
+					{
+						// Additionally play the sound from tagged sectors' soundorgs
+						sector_t *sec;
 
-					for(rover = mo->subsector->sector->ffloors; rover; rover = rover->next)
+						while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+						{
+							sec = &sectors[secnum];
+							S_StartSound(&sec->soundorg, sfxnum);
+						}
+					}
+					else if (mo) // A mobj must have triggered the executor
 					{
-						if (rover->master->frontsector->tag != line->tag)
-							continue;
+						// Only trigger if mobj is touching the tag
+						ffloor_t *rover;
+						boolean foundit = false;
 
-						if (mo->z > P_GetSpecialTopZ(mo, sectors + rover->secnum, mo->subsector->sector))
-							continue;
+						for(rover = mo->subsector->sector->ffloors; rover; rover = rover->next)
+						{
+							if (rover->master->frontsector->tag != line->tag)
+								continue;
 
-						if (mo->z + mo->height < P_GetSpecialBottomZ(mo, sectors + rover->secnum, mo->subsector->sector))
-							continue;
+							if (mo->z > P_GetSpecialTopZ(mo, sectors + rover->secnum, mo->subsector->sector))
+								continue;
 
-						foundit = true;
-					}
+							if (mo->z + mo->height < P_GetSpecialBottomZ(mo, sectors + rover->secnum, mo->subsector->sector))
+								continue;
 
-					if (mo->subsector->sector->tag == line->tag)
-						foundit = true;
+							foundit = true;
+						}
 
-					if (!foundit)
-						return;
+						if (mo->subsector->sector->tag == line->tag)
+							foundit = true;
+
+						if (!foundit)
+							return;
+					}
 				}
 
-				if (sfxnum < NUMSFX && sfxnum > sfx_None)
+				if (line->flags & ML_NOCLIMB)
 				{
-					if (line->flags & ML_NOCLIMB)
-					{
-						// play the sound from nowhere, but only if display player triggered it
-						if (mo && mo->player && (mo->player == &players[displayplayer] || mo->player == &players[secondarydisplayplayer]))
-							S_StartSound(NULL, sfxnum);
-					}
-					else if (line->flags & ML_EFFECT4)
-					{
-						// play the sound from nowhere
+					// play the sound from nowhere, but only if display player triggered it
+					if (mo && mo->player && (mo->player == &players[displayplayer] || mo->player == &players[secondarydisplayplayer]))
 						S_StartSound(NULL, sfxnum);
-					}
-					else if (line->flags & ML_BLOCKMONSTERS)
-					{
-						// play the sound from calling sector's soundorg
-						if (callsec)
-							S_StartSound(&callsec->soundorg, sfxnum);
-						else if (mo)
-							S_StartSound(&mo->subsector->sector->soundorg, sfxnum);
-					}
+				}
+				else if (line->flags & ML_EFFECT4)
+				{
+					// play the sound from nowhere
+					S_StartSound(NULL, sfxnum);
+				}
+				else if (line->flags & ML_BLOCKMONSTERS)
+				{
+					// play the sound from calling sector's soundorg
+					if (callsec)
+						S_StartSound(&callsec->soundorg, sfxnum);
 					else if (mo)
-					{
-						// play the sound from mobj that triggered it
-						S_StartSound(mo, sfxnum);
-					}
+						S_StartSound(&mo->subsector->sector->soundorg, sfxnum);
+				}
+				else if (mo)
+				{
+					// play the sound from mobj that triggered it
+					S_StartSound(mo, sfxnum);
 				}
 			}
 			break;
@@ -3159,6 +3035,47 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			}
 			break;
 
+		case 448: // Change skybox viewpoint/centerpoint
+			if ((mo && mo->player && P_IsLocalPlayer(mo->player)) || (line->flags & ML_NOCLIMB))
+			{
+				INT32 viewid = sides[line->sidenum[0]].textureoffset>>FRACBITS;
+				INT32 centerid = sides[line->sidenum[0]].rowoffset>>FRACBITS;
+
+				if ((line->flags & (ML_EFFECT4|ML_BLOCKMONSTERS)) == ML_EFFECT4) // Solid Midtexture is on but Block Enemies is off?
+				{
+					CONS_Alert(CONS_WARNING,
+					M_GetText("Skybox switch linedef (tag %d) doesn't have anything to do.\nConsider changing the linedef's flag configuration or removing it entirely.\n"),
+					line->tag);
+				}
+				else
+				{
+					// set viewpoint mobj
+					if (!(line->flags & ML_EFFECT4)) // Solid Midtexture turns off viewpoint setting
+					{
+						if (viewid >= 0 && viewid < 16)
+							skyboxmo[0] = skyboxviewpnts[viewid];
+						else
+							skyboxmo[0] = NULL;
+					}
+
+					// set centerpoint mobj
+					if (line->flags & ML_BLOCKMONSTERS) // Block Enemies turns ON centerpoint setting
+					{
+						if (centerid >= 0 && centerid < 16)
+							skyboxmo[1] = skyboxcenterpnts[centerid];
+						else
+							skyboxmo[1] = NULL;
+					}
+				}
+
+				CONS_Debug(DBG_GAMELOGIC, "Line type 448 Executor: viewid = %d, centerid = %d, viewpoint? = %s, centerpoint? = %s\n",
+						viewid,
+						centerid,
+						((line->flags & ML_EFFECT4) ? "no" : "yes"),
+						((line->flags & ML_BLOCKMONSTERS) ? "yes" : "no"));
+			}
+			break;
+
 		case 450: // Execute Linedef Executor - for recursion
 			P_LinedefExecute(line->tag, mo, NULL);
 			break;
@@ -3526,7 +3443,7 @@ static boolean P_ThingIsOnThe3DFloor(mobj_t *mo, sector_t *sector, sector_t *tar
 //
 // Is player standing on the sector's "ground"?
 //
-static inline boolean P_MobjReadyToTrigger(mobj_t *mo, sector_t *sec)
+static boolean P_MobjReadyToTrigger(mobj_t *mo, sector_t *sec)
 {
 	if (mo->eflags & MFE_VERTICALFLIP)
 		return (mo->z+mo->height == P_GetSpecialTopZ(mo, sec, sec) && sec->flags & SF_FLIPSPECIAL_CEILING);
@@ -3659,14 +3576,49 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 				{
 					if (roversector)
 					{
-						if (players[i].mo->subsector->sector != roversector)
+						if (players[i].mo->subsector->sector == roversector)
+							;
+						else if (sector->flags & SF_TRIGGERSPECIAL_TOUCH)
+						{
+							boolean insector = false;
+							msecnode_t *node;
+							for (node = players[i].mo->touching_sectorlist; node; node = node->m_sectorlist_next)
+							{
+								if (node->m_sector == roversector)
+								{
+									insector = true;
+									break;
+								}
+							}
+							if (!insector)
+								goto DoneSection2;
+						}
+						else
 							goto DoneSection2;
+
 						if (!P_ThingIsOnThe3DFloor(players[i].mo, sector, roversector))
 							goto DoneSection2;
 					}
 					else
 					{
-						if (players[i].mo->subsector->sector != sector)
+						if (players[i].mo->subsector->sector == sector)
+							;
+						else if (sector->flags & SF_TRIGGERSPECIAL_TOUCH)
+						{
+							boolean insector = false;
+							msecnode_t *node;
+							for (node = players[i].mo->touching_sectorlist; node; node = node->m_sectorlist_next)
+							{
+								if (node->m_sector == sector)
+								{
+									insector = true;
+									break;
+								}
+							}
+							if (!insector)
+								goto DoneSection2;
+						}
+						else
 							goto DoneSection2;
 
 						if (special == 3 && !P_MobjReadyToTrigger(players[i].mo, sector))
@@ -4447,6 +4399,7 @@ sector_t *P_ThingOnSpecial3DFloor(mobj_t *mo)
 {
 	sector_t *sector;
 	ffloor_t *rover;
+	fixed_t topheight, bottomheight;
 
 	sector = mo->subsector->sector;
 	if (!sector->ffloors)
@@ -4454,8 +4407,6 @@ sector_t *P_ThingOnSpecial3DFloor(mobj_t *mo)
 
 	for (rover = sector->ffloors; rover; rover = rover->next)
 	{
-		fixed_t topheight, bottomheight;
-
 		if (!rover->master->frontsector->special)
 			continue;
 
@@ -4503,6 +4454,8 @@ sector_t *P_ThingOnSpecial3DFloor(mobj_t *mo)
 	return NULL;
 }
 
+#define TELEPORTED (player->mo->subsector->sector != originalsector)
+
 /** Checks if a player is standing on or is inside a 3D floor (e.g. water) and
   * applies any specials.
   *
@@ -4511,12 +4464,12 @@ sector_t *P_ThingOnSpecial3DFloor(mobj_t *mo)
   */
 static void P_PlayerOnSpecial3DFloor(player_t *player, sector_t *sector)
 {
+	sector_t *originalsector = player->mo->subsector->sector;
 	ffloor_t *rover;
+	fixed_t topheight, bottomheight;
 
 	for (rover = sector->ffloors; rover; rover = rover->next)
 	{
-		fixed_t topheight, bottomheight;
-
 		if (!rover->master->frontsector->special)
 			continue;
 
@@ -4560,7 +4513,10 @@ static void P_PlayerOnSpecial3DFloor(player_t *player, sector_t *sector)
 		// This FOF has the special we're looking for, but are we allowed to touch it?
 		if (sector == player->mo->subsector->sector
 			|| (rover->master->frontsector->flags & SF_TRIGGERSPECIAL_TOUCH))
+		{
 			P_ProcessSpecialSector(player, rover->master->frontsector, sector);
+			if TELEPORTED return;
+		}
 	}
 
 	// Allow sector specials to be applied to polyobjects!
@@ -4571,7 +4527,7 @@ static void P_PlayerOnSpecial3DFloor(player_t *player, sector_t *sector)
 		boolean touching = false;
 		boolean inside = false;
 
-		while(po)
+		while (po)
 		{
 			if (po->flags & POF_NOSPECIALS)
 			{
@@ -4647,6 +4603,7 @@ static void P_PlayerOnSpecial3DFloor(player_t *player, sector_t *sector)
 			}
 
 			P_ProcessSpecialSector(player, polysec, sector);
+			if TELEPORTED return;
 
 			po = (polyobj_t *)(po->link.next);
 		}
@@ -4751,40 +4708,43 @@ static void P_RunSpecialSectorCheck(player_t *player, sector_t *sector)
   */
 void P_PlayerInSpecialSector(player_t *player)
 {
-	sector_t *sector;
+	sector_t *originalsector;
+	sector_t *loopsector;
 	msecnode_t *node;
 
 	if (!player->mo)
 		return;
 
-	// Do your ->subsector->sector first
-	sector = player->mo->subsector->sector;
-	P_PlayerOnSpecial3DFloor(player, sector);
-	// After P_PlayerOnSpecial3DFloor, recheck if the player is in that sector,
-	// because the player can be teleported in between these times.
-	if (sector == player->mo->subsector->sector)
-		P_RunSpecialSectorCheck(player, sector);
+	originalsector = player->mo->subsector->sector;
+
+	P_PlayerOnSpecial3DFloor(player, originalsector); // Handle FOFs first.
+	if TELEPORTED return;
+
+	P_RunSpecialSectorCheck(player, originalsector);
+	if TELEPORTED return;
 
-	// Iterate through touching_sectorlist
+	// Iterate through touching_sectorlist for SF_TRIGGERSPECIAL_TOUCH
 	for (node = player->mo->touching_sectorlist; node; node = node->m_sectorlist_next)
 	{
-		sector = node->m_sector;
+		loopsector = node->m_sector;
 
-		if (sector == player->mo->subsector->sector) // Don't duplicate
+		if (loopsector == originalsector) // Don't duplicate
 			continue;
 
 		// Check 3D floors...
-		P_PlayerOnSpecial3DFloor(player, sector);
+		P_PlayerOnSpecial3DFloor(player, loopsector);
+		if TELEPORTED return;
 
-		if (!(sector->flags & SF_TRIGGERSPECIAL_TOUCH))
-			return;
-		// After P_PlayerOnSpecial3DFloor, recheck if the player is in that sector,
-		// because the player can be teleported in between these times.
-		if (sector == player->mo->subsector->sector)
-			P_RunSpecialSectorCheck(player, sector);
+		if (!(loopsector->flags & SF_TRIGGERSPECIAL_TOUCH))
+			continue;
+
+		P_RunSpecialSectorCheck(player, loopsector);
+		if TELEPORTED return;
 	}
 }
 
+#undef TELEPORTED
+
 /** Animate planes, scroll walls, etc. and keeps track of level timelimit and exits if time is up.
   *
   * \sa P_CheckTimeLimit, P_CheckPointLimit
@@ -5176,10 +5136,11 @@ static inline void P_AddBridgeThinker(line_t *sourceline, sector_t *sec)
   * \param speed            Rate of movement relative to control sector
   * \param control          Control sector.
   * \param affectee         Target sector.
+  * \param reverse          Reverse direction?
   * \sa P_SpawnSpecials, T_PlaneDisplace
   * \author Monster Iestyn
   */
-static void P_AddPlaneDisplaceThinker(INT32 type, fixed_t speed, INT32 control, INT32 affectee)
+static void P_AddPlaneDisplaceThinker(INT32 type, fixed_t speed, INT32 control, INT32 affectee, UINT8 reverse)
 {
 	planedisplace_t *displace;
 
@@ -5193,6 +5154,7 @@ static void P_AddPlaneDisplaceThinker(INT32 type, fixed_t speed, INT32 control,
 	displace->last_height = sectors[control].floorheight;
 	displace->speed = speed;
 	displace->type = type;
+	displace->reverse = reverse;
 }
 
 /** Adds a Mario block thinker, which changes the block's texture between blank
@@ -5544,6 +5506,45 @@ static void P_RunLevelLoadExecutors(void)
 	}
 }
 
+/** Before things are loaded, initialises certain stuff in case they're needed
+  * by P_ResetDynamicSlopes or P_LoadThings. This was split off from
+  * P_SpawnSpecials, in case you couldn't tell.
+  *
+  * \sa P_SpawnSpecials, P_InitTagLists
+  * \author Monster Iestyn
+  */
+void P_InitSpecials(void)
+{
+	// Set the default gravity. Custom gravity overrides this setting.
+	gravity = FRACUNIT/2;
+
+	// Defaults in case levels don't have them set.
+	sstimer = 90*TICRATE + 6;
+	totalrings = 1;
+
+	CheckForBustableBlocks = CheckForBouncySector = CheckForQuicksand = CheckForMarioBlocks = CheckForFloatBob = CheckForReverseGravity = false;
+
+	// Set curWeather
+	switch (mapheaderinfo[gamemap-1]->weather)
+	{
+		case PRECIP_SNOW: // snow
+		case PRECIP_RAIN: // rain
+		case PRECIP_STORM: // storm
+		case PRECIP_STORM_NORAIN: // storm w/o rain
+		case PRECIP_STORM_NOSTRIKES: // storm w/o lightning
+			curWeather = mapheaderinfo[gamemap-1]->weather;
+			break;
+		default: // blank/none
+			curWeather = PRECIP_NONE;
+			break;
+	}
+
+	// Set globalweather
+	globalweather = mapheaderinfo[gamemap-1]->weather;
+
+	P_InitTagLists();   // Create xref tables for tags
+}
+
 /** After the map has loaded, scans for specials that spawn 3Dfloors and
   * thinkers.
   *
@@ -5565,15 +5566,6 @@ void P_SpawnSpecials(INT32 fromnetsave)
 	// but currently isn't.
 	(void)fromnetsave;
 
-	// Set the default gravity. Custom gravity overrides this setting.
-	gravity = FRACUNIT/2;
-
-	// Defaults in case levels don't have them set.
-	sstimer = 90*TICRATE + 6;
-	totalrings = 1;
-
-	CheckForBustableBlocks = CheckForBouncySector = CheckForQuicksand = CheckForMarioBlocks = CheckForFloatBob = CheckForReverseGravity = false;
-
 	// Init special SECTORs.
 	sector = sectors;
 	for (i = 0; i < numsectors; i++, sector++)
@@ -5622,20 +5614,6 @@ void P_SpawnSpecials(INT32 fromnetsave)
 		}
 	}
 
-	if (mapheaderinfo[gamemap-1]->weather == 2) // snow
-		curWeather = PRECIP_SNOW;
-	else if (mapheaderinfo[gamemap-1]->weather == 3) // rain
-		curWeather = PRECIP_RAIN;
-	else if (mapheaderinfo[gamemap-1]->weather == 1) // storm
-		curWeather = PRECIP_STORM;
-	else if (mapheaderinfo[gamemap-1]->weather == 5) // storm w/o rain
-		curWeather = PRECIP_STORM_NORAIN;
-	else if (mapheaderinfo[gamemap-1]->weather == 6) // storm w/o lightning
-		curWeather = PRECIP_STORM_NOSTRIKES;
-	else
-		curWeather = PRECIP_NONE;
-
-	P_InitTagLists();   // Create xref tables for tags
 	P_SearchForDisableLinedefs(); // Disable linedefs are now allowed to disable *any* line
 
 	P_SpawnScrollers(); // Add generalized scrollers
@@ -5766,10 +5744,8 @@ void P_SpawnSpecials(INT32 fromnetsave)
 					}
 					else // Otherwise, set calculated offsets such that line's v1 is the apparent origin
 					{
-						fixed_t cosinecomponent = FINECOSINE(flatangle>>ANGLETOFINESHIFT);
-						fixed_t sinecomponent = FINESINE(flatangle>>ANGLETOFINESHIFT);
-						xoffs = (-FixedMul(lines[i].v1->x, cosinecomponent) % MAXFLATSIZE) + (FixedMul(lines[i].v1->y, sinecomponent) % MAXFLATSIZE); // No danger of overflow thanks to the strategically placed modulo operations.
-						yoffs = (FixedMul(lines[i].v1->x, sinecomponent) % MAXFLATSIZE) + (FixedMul(lines[i].v1->y, cosinecomponent) % MAXFLATSIZE); // Ditto.
+						xoffs = -lines[i].v1->x;
+						yoffs = lines[i].v1->y;
 					}
 
 					for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
@@ -5912,15 +5888,15 @@ void P_SpawnSpecials(INT32 fromnetsave)
 
 			case 66: // Displace floor by front sector
 				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
-					P_AddPlaneDisplaceThinker(pd_floor, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s);
+					P_AddPlaneDisplaceThinker(pd_floor, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
 				break;
 			case 67: // Displace ceiling by front sector
 				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
-					P_AddPlaneDisplaceThinker(pd_ceiling, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s);
+					P_AddPlaneDisplaceThinker(pd_ceiling, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
 				break;
 			case 68: // Displace both floor AND ceiling by front sector
 				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
-					P_AddPlaneDisplaceThinker(pd_both, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s);
+					P_AddPlaneDisplaceThinker(pd_both, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
 				break;
 
 			case 100: // FOF (solid, opaque, shadows)
diff --git a/src/p_spec.h b/src/p_spec.h
index 0c77eb19f4a4c6a3de5fd1b18c994ee6517a3029..c4e05e0723485c4fd226227a463234eeab5c02f1 100644
--- a/src/p_spec.h
+++ b/src/p_spec.h
@@ -17,7 +17,9 @@
 #ifndef __P_SPEC__
 #define __P_SPEC__
 
-extern mobj_t *skyboxmo[2];
+extern mobj_t *skyboxmo[2]; // current skybox mobjs: 0 = viewpoint, 1 = centerpoint
+extern mobj_t *skyboxviewpnts[16]; // array of MT_SKYBOX viewpoint mobjs
+extern mobj_t *skyboxcenterpnts[16]; // array of MT_SKYBOX centerpoint mobjs
 
 // GETSECSPECIAL (specialval, section)
 //
@@ -32,6 +34,7 @@ void P_InitPicAnims(void);
 void P_SetupLevelFlatAnims(void);
 
 // at map load
+void P_InitSpecials(void);
 void P_SpawnSpecials(INT32 fromnetsave);
 
 // every tic
@@ -458,6 +461,7 @@ typedef struct
 	INT32 control;       ///< Control sector used to control plane positions.
 	fixed_t last_height; ///< Last known height of control sector.
 	fixed_t speed;       ///< Plane movement speed.
+	UINT8 reverse;       ///< Move in reverse direction to control sector?
 	/** Types of plane displacement effects.
 	*/
 	enum
diff --git a/src/p_tick.c b/src/p_tick.c
index 5235a1a034d4b60e2885b0feef202e9a0cc45b01..a79d71ef445333ae6585087d1336657fc1a9c9d3 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -359,7 +359,7 @@ static void P_DoAutobalanceTeams(void)
 	totalred = red + redflagcarrier;
 	totalblue = blue + blueflagcarrier;
 
-	if ((abs(totalred - totalblue) > cv_autobalance.value))
+	if ((abs(totalred - totalblue) > max(1, (totalred + totalblue) / 8)))
 	{
 		if (totalred > totalblue)
 		{
@@ -372,8 +372,7 @@ static void P_DoAutobalanceTeams(void)
 			usvalue  = SHORT(NetPacket.value.l|NetPacket.value.b);
 			SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue));
 		}
-
-		if (totalblue > totalred)
+		else //if (totalblue > totalred)
 		{
 			i = M_RandomKey(blue);
 			NetPacket.packet.newteam = 1;
@@ -651,7 +650,7 @@ void P_Ticker(boolean run)
 
 	if (run)
 	{
-		if (countdowntimer && --countdowntimer <= 0)
+		if (countdowntimer && G_PlatformGametype() && (gametype == GT_COOP || leveltime >= 4*TICRATE) && --countdowntimer <= 0)
 		{
 			countdowntimer = 0;
 			countdowntimeup = true;
@@ -663,6 +662,8 @@ void P_Ticker(boolean run)
 				if (!players[i].mo)
 					continue;
 
+				if (multiplayer || netgame)
+					players[i].exiting = 0;
 				P_DamageMobj(players[i].mo, NULL, NULL, 1, DMG_INSTAKILL);
 			}
 		}
diff --git a/src/p_user.c b/src/p_user.c
index 5846d0b68b04562370b203a6a01b9e5fbf554806..9bd38c1cb08c3998126c98f879a689ab2e66f9d3 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -666,7 +666,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 	if (skins[player->skin].sprites[SPR2_NGT0].numframes == 0) // If you don't have a sprite for flying horizontally, use the default NiGHTS skin.
 	{
 		player->mo->skin = &skins[DEFAULTNIGHTSSKIN];
-		player->mo->color = ((skin_t *)(player->mo->skin))->prefcolor;
+		player->mo->color = skins[DEFAULTNIGHTSSKIN].prefcolor;
 	}
 
 	player->nightstime = player->startedtime = nighttime*TICRATE;
@@ -821,7 +821,10 @@ void P_DoPlayerPain(player_t *player, mobj_t *source, mobj_t *inflictor)
 
 		if (inflictor)
 		{
-			ang = R_PointToAngle2(inflictor->x-inflictor->momx, inflictor->y - inflictor->momy, player->mo->x - player->mo->momx, player->mo->y - player->mo->momy);
+			if (inflictor->type == MT_WALLSPIKE)
+				ang = inflictor->angle;
+			else
+				ang = R_PointToAngle2(inflictor->x-inflictor->momx, inflictor->y - inflictor->momy, player->mo->x - player->mo->momx, player->mo->y - player->mo->momy);
 
 			// explosion and rail rings send you farther back, making it more difficult
 			// to recover
@@ -956,6 +959,29 @@ void P_GivePlayerLives(player_t *player, INT32 numlives)
 		player->lives = 1;
 }
 
+void P_GiveCoopLives(player_t *player, INT32 numlives, boolean sound)
+{
+	if (!((netgame || multiplayer) && gametype == GT_COOP))
+	{
+		P_GivePlayerLives(player, numlives);
+		if (sound)
+			P_PlayLivesJingle(player);
+	}
+	else
+	{
+		INT32 i;
+		for (i = 0; i < MAXPLAYERS; i++)
+		{
+			if (!playeringame[i])
+				continue;
+
+			P_GivePlayerLives(&players[i], numlives);
+			if (sound)
+				P_PlayLivesJingle(&players[i]);
+		}
+	}
+}
+
 //
 // P_DoSuperTransformation
 //
@@ -1031,7 +1057,7 @@ void P_AddPlayerScore(player_t *player, UINT32 amount)
 						players[i].continues += 1;
 						players[i].gotcontinue = true;
 						if (P_IsLocalPlayer(player))
-							S_StartSound(NULL, sfx_flgcap);
+							S_StartSound(NULL, sfx_s3kac);
 					} */
 				}
 		}
@@ -1051,7 +1077,7 @@ void P_AddPlayerScore(player_t *player, UINT32 amount)
 				player->continues += 1;
 				player->gotcontinue = true;
 				if (P_IsLocalPlayer(player))
-					S_StartSound(NULL, sfx_flgcap);
+					S_StartSound(NULL, sfx_s3kac);
 			}
 		}
 
@@ -1128,15 +1154,19 @@ void P_PlayLivesJingle(player_t *player)
 	if (player && !P_IsLocalPlayer(player))
 		return;
 
-	if (use1upSound)
+	if (gametype == GT_COOP && (netgame || multiplayer) && cv_cooplives.value == 0)
+		S_StartSound(NULL, sfx_lose);
+	else if (use1upSound)
 		S_StartSound(NULL, sfx_oneup);
 	else if (mariomode)
 		S_StartSound(NULL, sfx_marioa);
 	else
 	{
 		if (player)
-			player->powers[pw_extralife] = extralifetics + 1;
+			player->powers[pw_extralife] = extralifetics+1;
 		S_StopMusic(); // otherwise it won't restart if this is done twice in a row
+		strlcpy(S_sfx[sfx_None].caption, "One-up", 7);
+		S_StartCaption(sfx_None, -1, extralifetics+1);
 		S_ChangeMusicInternal("_1up", false);
 	}
 }
@@ -1157,9 +1187,15 @@ void P_RestoreMusic(player_t *player)
 	if (player->powers[pw_super] && !(mapheaderinfo[gamemap-1]->levelflags & LF_NOSSMUSIC))
 		S_ChangeMusicInternal("_super", true);
 	else if (player->powers[pw_invulnerability] > 1)
+	{
+		strlcpy(S_sfx[sfx_None].caption, "Invincibility", 14);
+		S_StartCaption(sfx_None, -1, player->powers[pw_invulnerability]);
 		S_ChangeMusicInternal((mariomode) ? "_minv" : "_inv", false);
+	}
 	else if (player->powers[pw_sneakers] > 1 && !player->powers[pw_super])
 	{
+		strlcpy(S_sfx[sfx_None].caption, "Speed shoes", 12);
+		S_StartCaption(sfx_None, -1, player->powers[pw_sneakers]);
 		if (mapheaderinfo[gamemap-1]->levelflags & LF_SPEEDMUSIC)
 		{
 			S_SpeedMusic(1.4f);
@@ -1260,11 +1296,12 @@ boolean P_IsObjectOnGroundIn(mobj_t *mo, sector_t *sec)
 				if (!(rover->flags & FF_EXISTS))
 					continue;
 
-				// If the FOF is configured to let players through, continue.
-				if (!(rover->flags & FF_BLOCKPLAYER) && (rover->flags & FF_BLOCKOTHERS))
+				// If the FOF is configured to let the object through, continue.
+				if (!((rover->flags & FF_BLOCKPLAYER && mo->player)
+					|| (rover->flags & FF_BLOCKOTHERS && !mo->player)))
 					continue;
 
-				// If the the platform is intangile from below, continue.
+				// If the the platform is intangible from below, continue.
 				if (rover->flags & FF_PLATFORM)
 					continue;
 
@@ -1293,11 +1330,12 @@ boolean P_IsObjectOnGroundIn(mobj_t *mo, sector_t *sec)
 				if (!(rover->flags & FF_EXISTS))
 					continue;
 
-				// If the FOF is configured to let players through, continue.
-				if (!(rover->flags & FF_BLOCKPLAYER) && (rover->flags & FF_BLOCKOTHERS))
+				// If the FOF is configured to let the object through, continue.
+				if (!((rover->flags & FF_BLOCKPLAYER && mo->player)
+					|| (rover->flags & FF_BLOCKOTHERS && !mo->player)))
 					continue;
 
-				// If the the platform is intangile from above, continue.
+				// If the the platform is intangible from above, continue.
 				if (rover->flags & FF_REVERSEPLATFORM)
 					continue;
 
@@ -1461,9 +1499,15 @@ void P_SpawnShieldOrb(player_t *player)
 //
 void P_SwitchShield(player_t *player, UINT16 shieldtype)
 {
-	boolean donthavealready = (shieldtype & SH_FORCE)
-	? (!(player->powers[pw_shield] & SH_FORCE) || (player->powers[pw_shield] & SH_FORCEHP) < (shieldtype & ~SH_FORCE))
-	: ((player->powers[pw_shield] & SH_NOSTACK) != shieldtype);
+	boolean donthavealready;
+
+	// If you already have a bomb shield, use it!
+	if ((shieldtype == SH_ARMAGEDDON) && (player->powers[pw_shield] & SH_NOSTACK) == SH_ARMAGEDDON)
+		P_BlackOw(player);
+
+	donthavealready = (shieldtype & SH_FORCE)
+		? (!(player->powers[pw_shield] & SH_FORCE) || (player->powers[pw_shield] & SH_FORCEHP) < (shieldtype & ~SH_FORCE))
+		: ((player->powers[pw_shield] & SH_NOSTACK) != shieldtype);
 
 	if (donthavealready)
 	{
@@ -1699,9 +1743,8 @@ void P_DoPlayerExit(player_t *player)
 #define SPACESPECIAL 12
 boolean P_InSpaceSector(mobj_t *mo) // Returns true if you are in space
 {
-	sector_t *sector;
-
-	sector = mo->subsector->sector;
+	sector_t *sector = mo->subsector->sector;
+	fixed_t topheight, bottomheight;
 
 	if (GETSECSPECIAL(sector->special, 1) == SPACESPECIAL)
 		return true;
@@ -1714,11 +1757,18 @@ boolean P_InSpaceSector(mobj_t *mo) // Returns true if you are in space
 		{
 			if (GETSECSPECIAL(rover->master->frontsector->special, 1) != SPACESPECIAL)
 				continue;
+#ifdef ESLOPE
+			topheight = *rover->t_slope ? P_GetZAt(*rover->t_slope, mo->x, mo->y) : *rover->topheight;
+			bottomheight = *rover->b_slope ? P_GetZAt(*rover->b_slope, mo->x, mo->y) : *rover->bottomheight;
+#else
+			topheight = *rover->topheight;
+			bottomheight = *rover->bottomheight;
+#endif
 
-			if (mo->z > *rover->topheight)
+			if (mo->z + (mo->height/2) > topheight)
 				continue;
 
-			if (mo->z + (mo->height/2) < *rover->bottomheight)
+			if (mo->z + (mo->height/2) < bottomheight)
 				continue;
 
 			return true;
@@ -1730,9 +1780,10 @@ boolean P_InSpaceSector(mobj_t *mo) // Returns true if you are in space
 
 boolean P_InQuicksand(mobj_t *mo) // Returns true if you are in quicksand
 {
-	sector_t *sector;
+	sector_t *sector = mo->subsector->sector;
+	fixed_t topheight, bottomheight;
 
-	sector = mo->subsector->sector;
+	fixed_t flipoffset = ((mo->eflags & MFE_VERTICALFLIP) ? (mo->height/2) : 0);
 
 	if (sector->ffloors)
 	{
@@ -1746,10 +1797,18 @@ boolean P_InQuicksand(mobj_t *mo) // Returns true if you are in quicksand
 			if (!(rover->flags & FF_QUICKSAND))
 				continue;
 
-			if (mo->z > *rover->topheight)
+#ifdef ESLOPE
+			topheight = *rover->t_slope ? P_GetZAt(*rover->t_slope, mo->x, mo->y) : *rover->topheight;
+			bottomheight = *rover->b_slope ? P_GetZAt(*rover->b_slope, mo->x, mo->y) : *rover->bottomheight;
+#else
+			topheight = *rover->topheight;
+			bottomheight = *rover->bottomheight;
+#endif
+
+			if (mo->z + flipoffset > topheight)
 				continue;
 
-			if (mo->z + (mo->height/2) < *rover->bottomheight)
+			if (mo->z + (mo->height/2) + flipoffset < bottomheight)
 				continue;
 
 			return true;
@@ -2060,6 +2119,7 @@ static void P_CheckQuicksand(player_t *player)
 {
 	ffloor_t *rover;
 	fixed_t sinkspeed, friction;
+	fixed_t topheight, bottomheight;
 
 	if (!(player->mo->subsector->sector->ffloors && player->mo->momz <= 0))
 		return;
@@ -2071,16 +2131,38 @@ static void P_CheckQuicksand(player_t *player)
 		if (!(rover->flags & FF_QUICKSAND))
 			continue;
 
-		if (*rover->topheight >= player->mo->z && *rover->bottomheight < player->mo->z + player->mo->height)
+#ifdef ESLOPE
+		topheight = *rover->t_slope ? P_GetZAt(*rover->t_slope, player->mo->x, player->mo->y) : *rover->topheight;
+		bottomheight = *rover->b_slope ? P_GetZAt(*rover->b_slope, player->mo->x, player->mo->y) : *rover->bottomheight;
+#else
+		topheight = *rover->topheight;
+		bottomheight = *rover->bottomheight;
+#endif
+
+		if (topheight >= player->mo->z && bottomheight < player->mo->z + player->mo->height)
 		{
 			sinkspeed = abs(rover->master->v1->x - rover->master->v2->x)>>1;
 
 			sinkspeed = FixedDiv(sinkspeed,TICRATE*FRACUNIT);
 
-			player->mo->z -= sinkspeed;
+			if (player->mo->eflags & MFE_VERTICALFLIP)
+			{
+				fixed_t ceilingheight = P_GetCeilingZ(player->mo, player->mo->subsector->sector, player->mo->x, player->mo->y, NULL);
+
+				player->mo->z += sinkspeed;
 
-			if (player->mo->z <= player->mo->subsector->sector->floorheight)
-				player->mo->z = player->mo->subsector->sector->floorheight;
+				if (player->mo->z + player->mo->height >= ceilingheight)
+					player->mo->z = ceilingheight - player->mo->height;
+			}
+			else
+			{
+				fixed_t floorheight = P_GetFloorZ(player->mo, player->mo->subsector->sector, player->mo->x, player->mo->y, NULL);
+
+				player->mo->z -= sinkspeed;
+
+				if (player->mo->z <= floorheight)
+					player->mo->z = floorheight;
+			}
 
 			friction = abs(rover->master->v1->y - rover->master->v2->y)>>6;
 
@@ -2210,7 +2292,7 @@ static void P_CheckInvincibilityTimer(player_t *player)
 		return;
 
 	if (mariomode && !player->powers[pw_super])
-		player->mo->color = (UINT8)(SKINCOLOR_RED + (leveltime % (MAXSKINCOLORS - SKINCOLOR_RED))); // Passes through all saturated colours
+		player->mo->color = (UINT8)(SKINCOLOR_RUBY + (leveltime % (MAXSKINCOLORS - SKINCOLOR_RUBY))); // Passes through all saturated colours
 	else if (leveltime % (TICRATE/7) == 0)
 	{
 		mobj_t *sparkle = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_IVSP);
@@ -2267,7 +2349,7 @@ static void P_DoBubbleBreath(player_t *player)
 
 	if (player->charflags & SF_MACHINE)
 	{
-		if (P_RandomChance((128-(player->powers[pw_underwater]/4))*FRACUNIT/256))
+		if (player->powers[pw_underwater] && P_RandomChance((128-(player->powers[pw_underwater]/4))*FRACUNIT/256))
 		{
 			fixed_t r = player->mo->radius>>FRACBITS;
 			x += (P_RandomRange(r, -r)<<FRACBITS);
@@ -2342,7 +2424,7 @@ static void P_DoPlayerHeadSigns(player_t *player)
 		// If you're "IT", show a big "IT" over your head for others to see.
 		if (player->pflags & PF_TAGIT)
 		{
-			if (!(player == &players[consoleplayer] || player == &players[secondarydisplayplayer] || player == &players[displayplayer])) // Don't display it on your own view.
+			if (!P_IsLocalPlayer(player)) // Don't display it on your own view.
 			{
 				if (!(player->mo->eflags & MFE_VERTICALFLIP))
 					P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height, MT_TAG);
@@ -2430,11 +2512,11 @@ static void P_DoClimbing(player_t *player)
 					floorclimb = true;
 
 #ifdef ESLOPE
-					bottomheight = *rover->b_slope ? P_GetZAt(*rover->b_slope, player->mo->x, player->mo->y) : *rover->bottomheight;
 					topheight = *rover->t_slope ? P_GetZAt(*rover->t_slope, player->mo->x, player->mo->y) : *rover->topheight;
+					bottomheight = *rover->b_slope ? P_GetZAt(*rover->b_slope, player->mo->x, player->mo->y) : *rover->bottomheight;
 #else
-					bottomheight = *rover->bottomheight;
 					topheight = *rover->topheight;
+					bottomheight = *rover->bottomheight;
 #endif
 
 					// Only supports rovers that are moving like an 'elevator', not just the top or bottom.
@@ -2623,13 +2705,20 @@ static void P_DoClimbing(player_t *player)
 					// Is there a FOF directly below that we can move onto?
 					if (glidesector->sector->ffloors)
 					{
+						fixed_t topheight;
 						ffloor_t *rover;
 						for (rover = glidesector->sector->ffloors; rover; rover = rover->next)
 						{
 							if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_BLOCKPLAYER) || (rover->flags & FF_BUSTUP))
 								continue;
 
-							if (*rover->topheight > ceilingheight - FixedMul(16*FRACUNIT, player->mo->scale))
+#ifdef ESLOPE
+							topheight = *rover->t_slope ? P_GetZAt(*rover->t_slope, player->mo->x, player->mo->y) : *rover->topheight;
+#else
+							topheight = *rover->topheight;
+#endif
+
+							if (topheight > ceilingheight - FixedMul(16*FRACUNIT, player->mo->scale))
 							{
 								foundfof = true;
 								break;
@@ -3007,14 +3096,12 @@ static void P_DoTeeter(player_t *player)
 			{
 				if (!(rover->flags & FF_EXISTS)) continue;
 
+#ifdef ESLOPE
+				topheight = *rover->t_slope ? P_GetZAt(*rover->t_slope, player->mo->x, player->mo->y) : *rover->topheight;
+				bottomheight = *rover->b_slope ? P_GetZAt(*rover->b_slope, player->mo->x, player->mo->y) : *rover->bottomheight;
+#else
 				topheight = *rover->topheight;
 				bottomheight = *rover->bottomheight;
-
-#ifdef ESLOPE
-				if (*rover->t_slope)
-					topheight = P_GetZAt(*rover->t_slope, player->mo->x, player->mo->y);
-				if (*rover->b_slope)
-					bottomheight = P_GetZAt(*rover->b_slope, player->mo->x, player->mo->y);
 #endif
 
 				if (P_CheckSolidLava(player->mo, rover))
@@ -3859,7 +3946,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 						mobj_t *lockon = P_LookForEnemies(player, false, true);
 						if (lockon)
 						{
-							if (player == &players[consoleplayer] || player == &players[secondarydisplayplayer] || player == &players[displayplayer]) // Only display it on your own view.
+							if (P_IsLocalPlayer(player)) // Only display it on your own view.
 							{
 								mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
 								visual->target = lockon;
@@ -4131,7 +4218,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 
 	if ((player->charability == CA_HOMINGTHOK) && !player->homing && (player->pflags & PF_JUMPED) && (!(player->pflags & PF_THOKKED) || (player->charflags & SF_MULTIABILITY)) && (lockon = P_LookForEnemies(player, true, false)))
 	{
-		if (player == &players[consoleplayer] || player == &players[secondarydisplayplayer] || player == &players[displayplayer]) // Only display it on your own view.
+		if (P_IsLocalPlayer(player)) // Only display it on your own view.
 		{
 			mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
 			visual->target = lockon;
@@ -4286,11 +4373,6 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 							player->mo->momy /= 3;
 						}
 
-						if (player->mo->info->attacksound && !player->spectator)
-							S_StartSound(player->mo, player->mo->info->attacksound); // Play the THOK sound
-
-						P_SpawnThokMobj(player);
-
 						if (player->charability == CA_HOMINGTHOK)
 						{
 							P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, lockon));
@@ -4303,10 +4385,16 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 							{
 								P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
 								player->pflags &= ~PF_JUMPED;
+								player->mo->height = P_GetPlayerHeight(player);
 							}
 							player->pflags &= ~PF_NOJUMPDAMAGE;
 						}
 
+						if (player->mo->info->attacksound && !player->spectator)
+							S_StartSound(player->mo, player->mo->info->attacksound); // Play the THOK sound
+
+						P_SpawnThokMobj(player);
+
 						player->pflags &= ~(PF_SPINNING|PF_STARTDASH);
 						player->pflags |= PF_THOKKED;
 					}
@@ -4768,7 +4856,7 @@ static void P_3dMovement(player_t *player)
 	angle_t dangle; // replaces old quadrants bits
 	fixed_t normalspd = FixedMul(player->normalspeed, player->mo->scale);
 	boolean analogmove = false;
-	boolean spin = (player->pflags & PF_SPINNING && (player->rmomx || player->rmomy) && !(player->pflags & PF_STARTDASH));
+	boolean spin = ((onground = P_IsObjectOnGround(player->mo)) && player->pflags & PF_SPINNING && (player->rmomx || player->rmomy) && !(player->pflags & PF_STARTDASH));
 	fixed_t oldMagnitude, newMagnitude;
 #ifdef ESLOPE
 	vector3_t totalthrust;
@@ -4858,9 +4946,6 @@ static void P_3dMovement(player_t *player)
 	if (player->pflags & PF_SLIDING)
 		cmd->forwardmove = 0;
 
-	// Do not let the player control movement if not onground.
-	onground = P_IsObjectOnGround(player->mo);
-
 	player->aiming = cmd->aiming<<FRACBITS;
 
 	// Set the player speeds.
@@ -4921,10 +5006,7 @@ static void P_3dMovement(player_t *player)
 	if (spin) // Prevent gaining speed whilst rolling!
 	{
 		const fixed_t ns = FixedDiv(549*ORIG_FRICTION,500*FRACUNIT); // P_XYFriction
-		if (onground)
-			topspeed = FixedMul(oldMagnitude, ns);
-		else
-			topspeed = oldMagnitude;
+		topspeed = FixedMul(oldMagnitude, ns);
 	}
 
 	// Better maneuverability while flying
@@ -4965,19 +5047,20 @@ static void P_3dMovement(player_t *player)
 	{
 		movepushforward = cmd->forwardmove * (thrustfactor * acceleration);
 
-		// allow very small movement while in air for gameplay
-		if (!onground)
-			movepushforward >>= 2; // proper air movement
-
 		// Allow a bit of movement while spinning
 		if (player->pflags & PF_SPINNING)
 		{
 			if ((mforward && cmd->forwardmove > 0) || (mbackward && cmd->forwardmove < 0)
 			|| (player->pflags & PF_STARTDASH))
 				movepushforward = 0;
+			else if (onground)
+				movepushforward >>= 4;
 			else
-				movepushforward = FixedDiv(movepushforward, 16*FRACUNIT);
+				movepushforward >>= 3;
 		}
+		// allow very small movement while in air for gameplay
+		else if (!onground)
+			movepushforward >>= 2; // proper air movement
 
 		movepushforward = FixedMul(movepushforward, player->mo->scale);
 
@@ -5005,21 +5088,20 @@ static void P_3dMovement(player_t *player)
 
 			movepushforward = max(abs(cmd->sidemove), abs(cmd->forwardmove)) * (thrustfactor * acceleration);
 
-			// allow very small movement while in air for gameplay
-			if (!onground)
-				movepushforward >>= 2; // proper air movement
-
 			// Allow a bit of movement while spinning
 			if (player->pflags & PF_SPINNING)
 			{
-				// Stupid little movement prohibitor hack
-				// that REALLY shouldn't belong in analog code.
 				if ((mforward && cmd->forwardmove > 0) || (mbackward && cmd->forwardmove < 0)
 				|| (player->pflags & PF_STARTDASH))
 					movepushforward = 0;
+				else if (onground)
+					movepushforward >>= 4;
 				else
-					movepushforward = FixedDiv(movepushforward, 16*FRACUNIT);
+					movepushforward >>= 3;
 			}
+			// allow very small movement while in air for gameplay
+			else if (!onground)
+				movepushforward >>= 2; // proper air movement
 
 			movepushsideangle = controldirection;
 
@@ -5037,25 +5119,26 @@ static void P_3dMovement(player_t *player)
 	{
 		movepushside = cmd->sidemove * (thrustfactor * acceleration);
 
+		// allow very small movement while in air for gameplay
 		if (!onground)
 		{
-			movepushside >>= 2;
-
+			movepushside >>= 2; // proper air movement
 			// Reduce movepushslide even more if over "max" flight speed
-			if (player->powers[pw_tailsfly] && player->speed > topspeed)
+			if ((player->pflags & PF_SPINNING) || (player->powers[pw_tailsfly] && player->speed > topspeed))
 				movepushside >>= 2;
 		}
-
 		// Allow a bit of movement while spinning
-		if (player->pflags & PF_SPINNING)
+		else if (player->pflags & PF_SPINNING)
 		{
-			if ((player->pflags & PF_STARTDASH))
+			if (player->pflags & PF_STARTDASH)
 				movepushside = 0;
+			else if (onground)
+				movepushside >>= 4;
 			else
-				movepushside = FixedDiv(movepushside,16*FRACUNIT);
+				movepushside >>= 3;
 		}
 
-		// Finally move the player now that his speed/direction has been decided.
+		// Finally move the player now that their speed/direction has been decided.
 		movepushside = FixedMul(movepushside, player->mo->scale);
 
 #ifdef ESLOPE
@@ -5138,16 +5221,16 @@ static void P_SpectatorMovement(player_t *player)
 	if (!(cmd->angleturn & TICCMD_RECEIVED))
 		ticmiss++;
 
-	if (player->mo->z > player->mo->ceilingz - player->mo->height)
-		player->mo->z = player->mo->ceilingz - player->mo->height;
-	if (player->mo->z < player->mo->floorz)
-		player->mo->z = player->mo->floorz;
-
 	if (cmd->buttons & BT_JUMP)
 		player->mo->z += FRACUNIT*16;
 	else if (cmd->buttons & BT_USE)
 		player->mo->z -= FRACUNIT*16;
 
+	if (player->mo->z > player->mo->ceilingz - player->mo->height)
+		player->mo->z = player->mo->ceilingz - player->mo->height;
+	if (player->mo->z < player->mo->floorz)
+		player->mo->z = player->mo->floorz;
+
 	// Aiming needed for SEENAMES, etc.
 	// We may not need to fire as a spectator, but this is still handy!
 	player->aiming = cmd->aiming<<FRACBITS;
@@ -6623,9 +6706,6 @@ static void P_MovePlayer(player_t *player)
 
 	fixed_t runspd;
 
-	if (countdowntimeup)
-		return;
-
 	if (player->mo->state >= &states[S_PLAY_SUPER_TRANS] && player->mo->state <= &states[S_PLAY_SUPER_TRANS9])
 	{
 		player->mo->momx = player->mo->momy = player->mo->momz = 0;
@@ -6686,6 +6766,7 @@ static void P_MovePlayer(player_t *player)
 
 	if (player->spectator)
 	{
+		player->mo->eflags &= ~MFE_VERTICALFLIP; // deflip...
 		P_SpectatorMovement(player);
 		return;
 	}
@@ -7200,7 +7281,7 @@ static void P_MovePlayer(player_t *player)
 		{
 			if ((lockon = P_LookForEnemies(player, false, false)))
 			{
-				if (player == &players[consoleplayer] || player == &players[secondarydisplayplayer] || player == &players[displayplayer]) // Only display it on your own view.
+				if (P_IsLocalPlayer(player)) // Only display it on your own view.
 				{
 					mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
 					visual->target = lockon;
@@ -8080,6 +8161,111 @@ void P_FindEmerald(void)
 	return;
 }
 
+//
+// P_GetLives
+// Get extra lives in new co-op if you're allowed to.
+//
+
+boolean P_GetLives(player_t *player)
+{
+	INT32 i, maxlivesplayer = -1, livescheck = 1;
+	if (!(netgame || multiplayer)
+	|| (gametype != GT_COOP)
+	|| (cv_cooplives.value == 1))
+		return true;
+
+	if ((cv_cooplives.value == 2 || cv_cooplives.value == 0) && player->lives > 0)
+		return true;
+
+	if (cv_cooplives.value == 0) // infinite lives
+	{
+		player->lives++;
+		return true;
+	}
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!playeringame[i])
+			continue;
+
+		if (players[i].lives > livescheck)
+		{
+			maxlivesplayer = i;
+			livescheck = players[i].lives;
+		}
+	}
+	if (maxlivesplayer != -1 && &players[maxlivesplayer] != player)
+	{
+		if (cv_cooplives.value == 2 && (P_IsLocalPlayer(player) || P_IsLocalPlayer(&players[maxlivesplayer])))
+			S_StartSound(NULL, sfx_jshard); // placeholder
+		players[maxlivesplayer].lives--;
+		player->lives++;
+		if (player->lives < 1)
+			player->lives = 1;
+		return true;
+	}
+	return (player->lives > 0);
+}
+
+//
+// P_ConsiderAllGone
+// Shamelessly lifted from TD. Thanks, Sryder!
+//
+
+static void P_ConsiderAllGone(void)
+{
+	INT32 i, lastdeadplayer = -1, deadtimercheck = INT32_MAX;
+
+	if (countdown2)
+		return;
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!playeringame[i])
+			continue;
+
+		if (players[i].playerstate != PST_DEAD && !players[i].spectator && players[i].mo && players[i].mo->health)
+			break;
+
+		if (players[i].spectator)
+		{
+			if (lastdeadplayer == -1)
+				lastdeadplayer = i;
+		}
+		else if (players[i].lives > 0)
+		{
+			lastdeadplayer = i;
+			if (players[i].deadtimer < deadtimercheck)
+				deadtimercheck = players[i].deadtimer;
+		}
+	}
+
+	if (i == MAXPLAYERS && lastdeadplayer != -1 && deadtimercheck > 2*TICRATE) // the last killed player will reset the level in G_DoReborn
+	{
+		//players[lastdeadplayer].spectator = true;
+		players[lastdeadplayer].outofcoop = true;
+		players[lastdeadplayer].playerstate = PST_REBORN;
+	}
+}
+
+void P_RestoreMultiMusic(player_t *player)
+{
+	if (netgame)
+	{
+		if (P_IsLocalPlayer(player))
+			S_ChangeMusic(mapmusname, mapmusflags, true);
+	}
+	else if (multiplayer) // local multiplayer only
+	{
+		// Restore the other player's music once we're dead for long enough
+		// -- that is, as long as they aren't dead too
+		if (player == &players[displayplayer] && players[secondarydisplayplayer].lives > 0)
+			P_RestoreMusic(&players[secondarydisplayplayer]);
+		else if (player == &players[secondarydisplayplayer] && players[displayplayer].lives > 0)
+			P_RestoreMusic(&players[displayplayer]);
+	}
+}
+
 //
 // P_DeathThink
 // Fall on your face when dying.
@@ -8088,6 +8274,8 @@ void P_FindEmerald(void)
 
 static void P_DeathThink(player_t *player)
 {
+	INT32 j = MAXPLAYERS;
+
 	ticcmd_t *cmd = &player->cmd;
 	player->deltaviewheight = 0;
 
@@ -8103,76 +8291,100 @@ static void P_DeathThink(player_t *player)
 			G_UseContinue(); // Even if we don't have one this handles ending the game
 	}
 
+	if ((cv_cooplives.value != 1)
+	&& (gametype == GT_COOP)
+	&& (netgame || multiplayer)
+	&& (player->lives <= 0))
+	{
+		for (j = 0; j < MAXPLAYERS; j++)
+		{
+			if (!playeringame[j])
+				continue;
+
+			if (players[j].lives > 1)
+				break;
+		}
+	}
+
 	// Force respawn if idle for more than 30 seconds in shooter modes.
 	if (player->deadtimer > 30*TICRATE && !G_PlatformGametype())
 		player->playerstate = PST_REBORN;
-	else if (player->lives > 0 && !G_IsSpecialStage(gamemap)) // Don't allow "click to respawn" in special stages!
+	else if ((player->lives > 0 || j != MAXPLAYERS) && !G_IsSpecialStage(gamemap)) // Don't allow "click to respawn" in special stages!
 	{
-		// Respawn with jump button, force respawn time (3 second default, cheat protected) in shooter modes.
-		if ((cmd->buttons & BT_JUMP) && player->deadtimer > cv_respawntime.value*TICRATE
-			&& gametype != GT_RACE && gametype != GT_COOP)
-			player->playerstate = PST_REBORN;
-
-		// Instant respawn in race or if you're spectating.
-		if ((cmd->buttons & BT_JUMP) && (gametype == GT_RACE || player->spectator))
-			player->playerstate = PST_REBORN;
-
-		// One second respawn in coop.
-		if ((cmd->buttons & BT_JUMP) && player->deadtimer > TICRATE && (gametype == GT_COOP || gametype == GT_COMPETITION))
-			player->playerstate = PST_REBORN;
+		if (gametype == GT_COOP && (netgame || multiplayer) && cv_coopstarposts.value == 2)
+		{
+			P_ConsiderAllGone();
+			if ((player->deadtimer > 5*TICRATE) || ((cmd->buttons & BT_JUMP) && (player->deadtimer > TICRATE)))
+			{
+				//player->spectator = true;
+				player->outofcoop = true;
+				player->playerstate = PST_REBORN;
+			}
+		}
+		else
+		{
+			// Respawn with jump button, force respawn time (3 second default, cheat protected) in shooter modes.
+			if (cmd->buttons & BT_JUMP)
+			{
+				if (gametype != GT_COOP && player->spectator)
+					player->playerstate = PST_REBORN;
+				else switch(gametype) {
+					case GT_COOP:
+						if (player->deadtimer > TICRATE)
+							player->playerstate = PST_REBORN;
+						break;
+					case GT_COMPETITION:
+						if (player->deadtimer > TICRATE)
+							player->playerstate = PST_REBORN;
+						break;
+					case GT_RACE:
+						player->playerstate = PST_REBORN;
+						break;
+					default:
+						if (player->deadtimer > cv_respawntime.value*TICRATE)
+							player->playerstate = PST_REBORN;
+						break;
+				}
+			}
 
-		// Single player auto respawn
-		if (!(netgame || multiplayer) && player->deadtimer > 5*TICRATE)
-			player->playerstate = PST_REBORN;
+			// Single player auto respawn
+			if (!(netgame || multiplayer) && player->deadtimer > 5*TICRATE)
+				player->playerstate = PST_REBORN;
+		}
 	}
-	else if ((netgame || multiplayer) && player->deadtimer == 8*TICRATE)
+	else if ((netgame || multiplayer) && player->deadtimer >= 8*TICRATE)
 	{
+
+		INT32 i, deadtimercheck = INT32_MAX;
+
 		// In a net/multiplayer game, and out of lives
 		if (gametype == GT_COMPETITION)
 		{
-			INT32 i;
-
 			for (i = 0; i < MAXPLAYERS; i++)
-				if (playeringame[i] && !players[i].exiting && players[i].lives > 0)
+			{
+				if (!playeringame[i])
+					continue;
+				if (!players[i].exiting && players[i].lives)
 					break;
+				if (players[i].deadtimer < deadtimercheck)
+					deadtimercheck = players[i].deadtimer;
+			}
 
-			if (i == MAXPLAYERS)
+			if (i == MAXPLAYERS && deadtimercheck == 8*TICRATE)
 			{
 				// Everyone's either done with the race, or dead.
 				if (!countdown2 || countdown2 > 1*TICRATE)
 					countdown2 = 1*TICRATE;
 			}
 		}
+		//else if (gametype == GT_COOP) -- moved to G_DoReborn
+	}
 
-		// In a coop game, and out of lives
-		if (gametype == GT_COOP)
-		{
-			INT32 i;
-
-			for (i = 0; i < MAXPLAYERS; i++)
-				if (playeringame[i] && (players[i].exiting || players[i].lives > 0))
-					break;
-
-			if (i == MAXPLAYERS)
-			{
-				// They're dead, Jim.
-				//nextmapoverride = spstage_start;
-				nextmapoverride = gamemap;
-				countdown2 = 1*TICRATE;
-				skipstats = true;
-
-				for (i = 0; i < MAXPLAYERS; i++)
-				{
-					if (playeringame[i])
-						players[i].score = 0;
-				}
-
-				//emeralds = 0;
-				tokenbits = 0;
-				tokenlist = 0;
-				token = 0;
-			}
-		}
+	if (gametype == GT_COOP && (multiplayer || netgame) && (player->lives <= 0) && (player->deadtimer >= 8*TICRATE || ((cmd->buttons & BT_JUMP) && (player->deadtimer > TICRATE))))
+	{
+		//player->spectator = true;
+		player->outofcoop = true;
+		player->playerstate = PST_REBORN;
 	}
 
 	if (gametype == GT_RACE || gametype == GT_COMPETITION || (gametype == GT_COOP && (multiplayer || netgame)))
@@ -8192,25 +8404,8 @@ static void P_DeathThink(player_t *player)
 		}
 
 		// Return to level music
-		if (player->lives <= 0)
-		{
-			if (netgame)
-			{
-				if (player->deadtimer == gameovertics && P_IsLocalPlayer(player))
-					S_ChangeMusic(mapmusname, mapmusflags, true);
-			}
-			else if (multiplayer) // local multiplayer only
-			{
-				if (player->deadtimer != gameovertics)
-					;
-				// Restore the other player's music once we're dead for long enough
-				// -- that is, as long as they aren't dead too
-				else if (player == &players[displayplayer] && players[secondarydisplayplayer].lives > 0)
-					P_RestoreMusic(&players[secondarydisplayplayer]);
-				else if (player == &players[secondarydisplayplayer] && players[displayplayer].lives > 0)
-					P_RestoreMusic(&players[displayplayer]);
-			}
-		}
+		if (gametype != GT_COOP && player->lives <= 0 && player->deadtimer == gameovertics)
+			P_RestoreMultiMusic(player);
 	}
 
 	if (!player->mo)
@@ -8462,46 +8657,24 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 		thiscam->angle = angle;
 	}
 
-	if (!objectplacing && !(twodlevel || (mo->flags2 & MF2_TWOD)) && (player->powers[pw_carry] != CR_NIGHTSMODE) && displayplayer == consoleplayer)
+	if ((((thiscam == &camera) && cv_analog.value) || ((thiscam != &camera) && cv_analog2.value) || demoplayback) && !objectplacing && !(twodlevel || (mo->flags2 & MF2_TWOD)) && (player->powers[pw_carry] != CR_NIGHTSMODE) && displayplayer == consoleplayer)
 	{
 #ifdef REDSANALOG
 		if ((player->cmd.buttons & (BT_CAMLEFT|BT_CAMRIGHT)) == (BT_CAMLEFT|BT_CAMRIGHT)); else
 #endif
-		if (player->cmd.buttons & BT_CAMLEFT)
+		if (player->cmd.buttons & BT_CAMRIGHT)
 		{
 			if (thiscam == &camera)
-			{
-				if (cv_analog.value || demoplayback)
-					angle -= FixedAngle(cv_cam_rotspeed.value*FRACUNIT);
-				else
-					CV_SetValue(&cv_cam_rotate, camrotate == 0 ? 358
-						: camrotate - 2);
-			}
+				angle -= FixedAngle(cv_cam_rotspeed.value*FRACUNIT);
 			else
-			{
-				if (cv_analog2.value)
-					angle -= FixedAngle(cv_cam2_rotspeed.value*FRACUNIT);
-				else
-					CV_SetValue(&cv_cam2_rotate, camrotate == 0 ? 358
-						: camrotate - 2);
-			}
+				angle -= FixedAngle(cv_cam2_rotspeed.value*FRACUNIT);
 		}
-		else if (player->cmd.buttons & BT_CAMRIGHT)
+		else if (player->cmd.buttons & BT_CAMLEFT)
 		{
 			if (thiscam == &camera)
-			{
-				if (cv_analog.value || demoplayback)
-					angle += FixedAngle(cv_cam_rotspeed.value*FRACUNIT);
-				else
-					CV_SetValue(&cv_cam_rotate, camrotate + 2);
-			}
+				angle += FixedAngle(cv_cam_rotspeed.value*FRACUNIT);
 			else
-			{
-				if (cv_analog2.value)
-					angle += FixedAngle(cv_cam2_rotspeed.value*FRACUNIT);
-				else
-					CV_SetValue(&cv_cam2_rotate, camrotate + 2);
-			}
+				angle += FixedAngle(cv_cam2_rotspeed.value*FRACUNIT);
 		}
 	}
 
@@ -8886,16 +9059,9 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	return (x == thiscam->x && y == thiscam->y && z == thiscam->z && angle == thiscam->aiming);
 }
 
-static boolean P_SpectatorJoinGame(player_t *player)
+boolean P_SpectatorJoinGame(player_t *player)
 {
-	if (!G_GametypeHasSpectators() && G_IsSpecialStage(gamemap) && useNightsSS) // Special Stage spectators should NEVER be allowed to rejoin the game
-	{
-		if (P_IsLocalPlayer(player))
-			CONS_Printf(M_GetText("You cannot enter the game while a special stage is in progress.\n"));
-		player->powers[pw_flashing] += 2*TICRATE; //to prevent message spam.
-	}
-
-	else if (!cv_allowteamchange.value)
+	if (gametype != GT_COOP && !cv_allowteamchange.value)
 	{
 		if (P_IsLocalPlayer(player))
 			CONS_Printf(M_GetText("Server does not allow team change.\n"));
@@ -8964,7 +9130,7 @@ static boolean P_SpectatorJoinGame(player_t *player)
 				P_RemoveMobj(player->mo);
 				player->mo = NULL;
 			}
-			player->spectator = false;
+			player->spectator = player->outofcoop = false;
 			player->playerstate = PST_REBORN;
 
 			if (gametype == GT_TAG)
@@ -8983,7 +9149,8 @@ static boolean P_SpectatorJoinGame(player_t *player)
 			if (P_IsLocalPlayer(player) && displayplayer != consoleplayer)
 				displayplayer = consoleplayer;
 
-			CONS_Printf(M_GetText("%s entered the game.\n"), player_names[player-players]);
+			if (gametype != GT_COOP)
+				CONS_Printf(M_GetText("%s entered the game.\n"), player_names[player-players]);
 			return true; // no more player->mo, cannot continue.
 		}
 		else
@@ -9032,13 +9199,23 @@ static void P_CalcPostImg(player_t *player)
 	else if (sector->ffloors)
 	{
 		ffloor_t *rover;
+		fixed_t topheight;
+		fixed_t bottomheight;
 
 		for (rover = sector->ffloors; rover; rover = rover->next)
 		{
 			if (!(rover->flags & FF_EXISTS))
 				continue;
 
-			if (pviewheight >= *rover->topheight || pviewheight <= *rover->bottomheight)
+#ifdef ESLOPE
+			topheight = *rover->t_slope ? P_GetZAt(*rover->t_slope, player->mo->x, player->mo->y) : *rover->topheight;
+			bottomheight = *rover->b_slope ? P_GetZAt(*rover->b_slope, player->mo->x, player->mo->y) : *rover->bottomheight;
+#else
+			topheight = *rover->topheight;
+			bottomheight = *rover->bottomheight;
+#endif
+
+			if (pviewheight >= topheight || pviewheight <= bottomheight)
 				continue;
 
 			if (P_FindSpecialLineFromTag(13, rover->master->frontsector->tag, -1) != -1)
@@ -9050,13 +9227,23 @@ static void P_CalcPostImg(player_t *player)
 	if (sector->ffloors)
 	{
 		ffloor_t *rover;
+		fixed_t topheight;
+		fixed_t bottomheight;
 
 		for (rover = sector->ffloors; rover; rover = rover->next)
 		{
 			if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_SWIMMABLE) || rover->flags & FF_BLOCKPLAYER)
 				continue;
 
-			if (pviewheight >= *rover->topheight || pviewheight <= *rover->bottomheight)
+#ifdef ESLOPE
+			topheight = *rover->t_slope ? P_GetZAt(*rover->t_slope, player->mo->x, player->mo->y) : *rover->topheight;
+			bottomheight = *rover->b_slope ? P_GetZAt(*rover->b_slope, player->mo->x, player->mo->y) : *rover->bottomheight;
+#else
+			topheight = *rover->topheight;
+			bottomheight = *rover->bottomheight;
+#endif
+
+			if (pviewheight >= topheight || pviewheight <= bottomheight)
 				continue;
 
 			*type = postimg_water;
@@ -9104,8 +9291,6 @@ void P_DoPityCheck(player_t *player)
 // P_PlayerThink
 //
 
-boolean playerdeadview; // show match/chaos/tag/capture the flag rankings while in death view
-
 void P_PlayerThink(player_t *player)
 {
 	ticcmd_t *cmd;
@@ -9164,8 +9349,9 @@ void P_PlayerThink(player_t *player)
 		if (player->panim != PA_ABILITY)
 			P_SetPlayerMobjState(player->mo, S_PLAY_GLIDE);
 	}
-	else if ((player->pflags & PF_JUMPED && !(player->pflags & PF_NOJUMPDAMAGE))
-	&& ((player->charflags & SF_NOJUMPSPIN && player->panim != PA_ROLL)
+	else if ((player->pflags & PF_JUMPED && !(player->pflags & PF_NOJUMPDAMAGE)
+	&& (player->mo->state-states != S_PLAY_FLOAT && player->mo->state-states != S_PLAY_FLOAT_RUN))
+	&& ((((player->charflags & (SF_NOJUMPSPIN|SF_NOJUMPDAMAGE)) == (SF_NOJUMPSPIN|SF_NOJUMPDAMAGE)) && player->panim != PA_ROLL)
 	|| (!(player->charflags & SF_NOJUMPSPIN) && player->panim != PA_JUMP)))
 	{
 		if (!(player->charflags & SF_NOJUMPSPIN))
@@ -9258,7 +9444,7 @@ void P_PlayerThink(player_t *player)
 	{
 		if (cv_playersforexit.value) // Count to be sure everyone's exited
 		{
-			INT32 i;
+			INT32 i, total = 0, exiting = 0;
 
 			for (i = 0; i < MAXPLAYERS; i++)
 			{
@@ -9267,11 +9453,12 @@ void P_PlayerThink(player_t *player)
 				if (players[i].lives <= 0)
 					continue;
 
-				if (!players[i].exiting || players[i].exiting > 3)
-					break;
+				total++;
+				if (players[i].exiting && players[i].exiting < 4)
+					exiting++;
 			}
 
-			if (i == MAXPLAYERS)
+			if (!total || ((4*exiting)/total) >= cv_playersforexit.value)
 			{
 				if (server)
 					SendNetXCmd(XD_EXITLEVEL, NULL, 0);
@@ -9289,6 +9476,7 @@ void P_PlayerThink(player_t *player)
 	// check water content, set stuff in mobj
 	P_MobjCheckWater(player->mo);
 
+#ifndef SECTORSPECIALSAFTERTHINK
 #ifdef POLYOBJECTS
 	if (player->onconveyor != 1 || !P_IsObjectOnGround(player->mo))
 #endif
@@ -9297,14 +9485,16 @@ void P_PlayerThink(player_t *player)
 
 	if (!player->spectator)
 		P_PlayerInSpecialSector(player);
+	else if (
+#else
+	if (player->spectator &&
+#endif
+	gametype == GT_COOP && (netgame || multiplayer) && cv_coopstarposts.value == 2)
+		P_ConsiderAllGone();
 
 	if (player->playerstate == PST_DEAD)
 	{
 		player->mo->flags2 &= ~MF2_SHADOW;
-		// show the multiplayer rankings while dead
-		if (player == &players[displayplayer])
-			playerdeadview = true;
-
 		P_DeathThink(player);
 
 		return;
@@ -9313,21 +9503,18 @@ void P_PlayerThink(player_t *player)
 	// Make sure spectators always have a score and ring count of 0.
 	if (player->spectator)
 	{
-		player->score = 0;
+		if (gametype != GT_COOP)
+			player->score = 0;
 		player->mo->health = 1;
 		player->rings = 0;
 	}
-
-	if ((netgame || multiplayer) && player->lives <= 0)
+	else if ((netgame || multiplayer) && player->lives <= 0 && gametype != GT_COOP)
 	{
-		// In Co-Op, replenish a user's lives if they are depleted.
+		// Outside of Co-Op, replenish a user's lives if they are depleted.
 		// of course, this is just a cheap hack, meh...
 		player->lives = cv_startinglives.value;
 	}
 
-	if (player == &players[displayplayer])
-		playerdeadview = false;
-
 	if ((gametype == GT_RACE || gametype == GT_COMPETITION) && leveltime < 4*TICRATE)
 	{
 		cmd->buttons &= BT_USE; // Remove all buttons except BT_USE
@@ -9349,7 +9536,7 @@ void P_PlayerThink(player_t *player)
 			player->realtime = leveltime;
 	}
 
-	if ((netgame || splitscreen) && player->spectator && cmd->buttons & BT_ATTACK && !player->powers[pw_flashing])
+	if (player->spectator && cmd->buttons & BT_ATTACK && !player->powers[pw_flashing] && G_GametypeHasSpectators())
 	{
 		if (P_SpectatorJoinGame(player))
 			return; // player->mo was removed.
@@ -9749,6 +9936,17 @@ void P_PlayerAfterThink(player_t *player)
 
 	cmd = &player->cmd;
 
+#ifdef SECTORSPECIALSAFTERTHINK
+#ifdef POLYOBJECTS
+	if (player->onconveyor != 1 || !P_IsObjectOnGround(player->mo))
+#endif
+	player->onconveyor = 0;
+	// check special sectors : damage & secrets
+
+	if (!player->spectator)
+		P_PlayerInSpecialSector(player);
+#endif
+
 	if (splitscreen && player == &players[secondarydisplayplayer])
 		thiscam = &camera2;
 	else if (player == &players[displayplayer])
@@ -9980,7 +10178,7 @@ void P_PlayerAfterThink(player_t *player)
 			}
 		}
 	}
-	else if (player->powers[pw_carry] == CR_MACESPIN && player->mo->tracer && player->mo->tracer->target)
+	else if (player->powers[pw_carry] == CR_MACESPIN && player->mo->tracer && player->mo->tracer->tracer)
 	{
 		player->mo->height = P_GetPlayerSpinHeight(player);
 		// tracer is what you're hanging onto....
@@ -9996,14 +10194,20 @@ void P_PlayerAfterThink(player_t *player)
 			player->pflags &= ~PF_THOKKED;
 
 			if (cmd->forwardmove > 0)
-				player->mo->tracer->target->lastlook += 2;
-			else if (cmd->forwardmove < 0 && player->mo->tracer->target->lastlook > player->mo->tracer->target->movecount)
-				player->mo->tracer->target->lastlook -= 2;
+			{
+				if ((player->mo->tracer->tracer->lastlook += 2) > player->mo->tracer->tracer->friction)
+					player->mo->tracer->tracer->lastlook = player->mo->tracer->tracer->friction;
+			}
+			else if (cmd->forwardmove < 0)
+			{
+				if ((player->mo->tracer->tracer->lastlook -= 2) < player->mo->tracer->tracer->movecount)
+					player->mo->tracer->tracer->lastlook = player->mo->tracer->tracer->movecount;
+			}
 
-			if (!(player->mo->tracer->target->flags & MF_SLIDEME) // Noclimb on chain parameters gives this
+			if ((player->mo->tracer->tracer->flags & MF_SLIDEME) // Noclimb on chain parameters gives this
 			&& !(twodlevel || player->mo->flags2 & MF2_TWOD)) // why on earth would you want to turn them in 2D mode?
 			{
-				player->mo->tracer->target->health += cmd->sidemove;
+				player->mo->tracer->tracer->health += cmd->sidemove;
 				player->mo->angle += cmd->sidemove<<ANGLETOFINESHIFT; // 2048 --> ANGLE_MAX
 
 				if (!demoplayback || P_AnalogMove(player))
diff --git a/src/r_bsp.c b/src/r_bsp.c
index 44cb991a7000fbea276aa64b1e8f07d5d1314254..ad4975cde33b6c86e3ab26c0e10f4deca01afed2 100644
--- a/src/r_bsp.c
+++ b/src/r_bsp.c
@@ -365,6 +365,36 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 	return sec;
 }
 
+boolean R_IsEmptyLine(seg_t *line, sector_t *front, sector_t *back)
+{
+	return (
+#ifdef POLYOBJECTS
+		!line->polyseg &&
+#endif
+		back->ceilingpic == front->ceilingpic
+		&& back->floorpic == front->floorpic
+#ifdef ESLOPE
+		&& back->f_slope == front->f_slope
+		&& back->c_slope == front->c_slope
+#endif
+		&& back->lightlevel == front->lightlevel
+		&& !line->sidedef->midtexture
+		// Check offsets too!
+		&& back->floor_xoffs == front->floor_xoffs
+		&& back->floor_yoffs == front->floor_yoffs
+		&& back->floorpic_angle == front->floorpic_angle
+		&& back->ceiling_xoffs == front->ceiling_xoffs
+		&& back->ceiling_yoffs == front->ceiling_yoffs
+		&& back->ceilingpic_angle == front->ceilingpic_angle
+		// Consider altered lighting.
+		&& back->floorlightsec == front->floorlightsec
+		&& back->ceilinglightsec == front->ceilinglightsec
+		// Consider colormaps
+		&& back->extra_colormap == front->extra_colormap
+		&& ((!front->ffloors && !back->ffloors)
+		|| front->tag == back->tag));
+}
+
 //
 // R_AddLine
 // Clips the given segment and adds any visible pieces to the line list.
@@ -526,36 +556,8 @@ static void R_AddLine(seg_t *line)
 	// Identical floor and ceiling on both sides, identical light levels on both sides,
 	// and no middle texture.
 
-	if (
-#ifdef POLYOBJECTS
-		!line->polyseg &&
-#endif
-		backsector->ceilingpic == frontsector->ceilingpic
-		&& backsector->floorpic == frontsector->floorpic
-#ifdef ESLOPE
-		&& backsector->f_slope == frontsector->f_slope
-		&& backsector->c_slope == frontsector->c_slope
-#endif
-		&& backsector->lightlevel == frontsector->lightlevel
-		&& !curline->sidedef->midtexture
-		// Check offsets too!
-		&& backsector->floor_xoffs == frontsector->floor_xoffs
-		&& backsector->floor_yoffs == frontsector->floor_yoffs
-		&& backsector->floorpic_angle == frontsector->floorpic_angle
-		&& backsector->ceiling_xoffs == frontsector->ceiling_xoffs
-		&& backsector->ceiling_yoffs == frontsector->ceiling_yoffs
-		&& backsector->ceilingpic_angle == frontsector->ceilingpic_angle
-		// Consider altered lighting.
-		&& backsector->floorlightsec == frontsector->floorlightsec
-		&& backsector->ceilinglightsec == frontsector->ceilinglightsec
-		// Consider colormaps
-		&& backsector->extra_colormap == frontsector->extra_colormap
-		&& ((!frontsector->ffloors && !backsector->ffloors)
-		|| frontsector->tag == backsector->tag))
-	{
+	if (R_IsEmptyLine(line, frontsector, backsector))
 		return;
-	}
-
 
 clippass:
 	R_ClipPassWallSegment(x1, x2 - 1);
@@ -1102,30 +1104,12 @@ static void R_Subsector(size_t num)
 				&& polysec->floorheight >= floorcenterz
 				&& (viewz < polysec->floorheight))
 			{
-				fixed_t xoff, yoff;
-				xoff = polysec->floor_xoffs;
-				yoff = polysec->floor_yoffs;
-
-				if (po->angle != 0) {
-					angle_t fineshift = po->angle >> ANGLETOFINESHIFT;
-
-					xoff -= FixedMul(FINECOSINE(fineshift), po->centerPt.x)+FixedMul(FINESINE(fineshift), po->centerPt.y);
-					yoff -= FixedMul(FINESINE(fineshift), po->centerPt.x)-FixedMul(FINECOSINE(fineshift), po->centerPt.y);
-				} else {
-					xoff -= po->centerPt.x;
-					yoff += po->centerPt.y;
-				}
-
 				light = R_GetPlaneLight(frontsector, polysec->floorheight, viewz < polysec->floorheight);
 				light = 0;
 				ffloor[numffloors].plane = R_FindPlane(polysec->floorheight, polysec->floorpic,
-						polysec->lightlevel, xoff, yoff,
-						polysec->floorpic_angle-po->angle,
-						NULL,
-						NULL
-#ifdef POLYOBJECTS_PLANES
-					, po
-#endif
+					polysec->lightlevel, polysec->floor_xoffs, polysec->floor_yoffs,
+					polysec->floorpic_angle-po->angle,
+					NULL, NULL, po
 #ifdef ESLOPE
 					, NULL // will ffloors be slopable eventually?
 #endif
@@ -1150,28 +1134,11 @@ static void R_Subsector(size_t num)
 				&& polysec->ceilingheight <= ceilingcenterz
 				&& (viewz > polysec->ceilingheight))
 			{
-				fixed_t xoff, yoff;
-				xoff = polysec->ceiling_xoffs;
-				yoff = polysec->ceiling_yoffs;
-
-				if (po->angle != 0) {
-					angle_t fineshift = po->angle >> ANGLETOFINESHIFT;
-
-					xoff -= FixedMul(FINECOSINE(fineshift), po->centerPt.x)+FixedMul(FINESINE(fineshift), po->centerPt.y);
-					yoff -= FixedMul(FINESINE(fineshift), po->centerPt.x)-FixedMul(FINECOSINE(fineshift), po->centerPt.y);
-				} else {
-					xoff -= po->centerPt.x;
-					yoff += po->centerPt.y;
-				}
-
 				light = R_GetPlaneLight(frontsector, polysec->ceilingheight, viewz < polysec->ceilingheight);
 				light = 0;
 				ffloor[numffloors].plane = R_FindPlane(polysec->ceilingheight, polysec->ceilingpic,
-					polysec->lightlevel, xoff, yoff, polysec->ceilingpic_angle-po->angle,
-					NULL, NULL
-#ifdef POLYOBJECTS_PLANES
-					, po
-#endif
+					polysec->lightlevel, polysec->ceiling_xoffs, polysec->ceiling_yoffs, polysec->ceilingpic_angle-po->angle,
+					NULL, NULL, po
 #ifdef ESLOPE
 					, NULL // will ffloors be slopable eventually?
 #endif
@@ -1222,6 +1189,9 @@ static void R_Subsector(size_t num)
 	while (count--)
 	{
 //		CONS_Debug(DBG_GAMELOGIC, "Adding normal line %d...(%d)\n", line->linedef-lines, leveltime);
+#ifdef POLYOBJECTS
+		if (!line->polyseg) // ignore segs that belong to polyobjects
+#endif
 		R_AddLine(line);
 		line++;
 		curline = NULL; /* cph 2001/11/18 - must clear curline now we're done with it, so stuff doesn't try using it for other things */
diff --git a/src/r_bsp.h b/src/r_bsp.h
index e871b5dde5c6ae704b5d772d06371f94e62ad361..80824831b8511fe1f6b02b22b66213bb82e26b31 100644
--- a/src/r_bsp.h
+++ b/src/r_bsp.h
@@ -50,6 +50,7 @@ extern polyobj_t **po_ptrs; // temp ptr array to sort polyobject pointers
 
 sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 	INT32 *ceilinglightlevel, boolean back);
+boolean R_IsEmptyLine(seg_t *line, sector_t *front, sector_t *back);
 
 INT32 R_GetPlaneLight(sector_t *sector, fixed_t planeheight, boolean underside);
 void R_Prep3DFloors(sector_t *sector);
diff --git a/src/r_data.c b/src/r_data.c
index 4df5209a5a90b9fa4dd234083e00a5040000c602..791d90d9411446f256bb43475b1a7fbdc9ecbb00 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -22,7 +22,7 @@
 #include "w_wad.h"
 #include "z_zone.h"
 #include "p_setup.h" // levelflats
-#include "v_video.h" // pLocalPalette
+#include "v_video.h" // pMasterPalette
 #include "dehacked.h"
 
 #if defined (_WIN32) || defined (_WIN32_WCE)
@@ -625,62 +625,33 @@ void R_LoadTextures(void)
 		{
 			patchlump = W_CacheLumpNumPwad((UINT16)w, texstart + j, PU_CACHE);
 
-			// Then, check the lump directly to see if it's a texture SOC,
-			// and if it is, load it using dehacked instead.
-			if (strstr((const char *)patchlump, "TEXTURE"))
-			{
-				CONS_Alert(CONS_WARNING, "%s is a Texture SOC.\n", W_CheckNameForNumPwad((UINT16)w,texstart+j));
-				Z_Unlock(patchlump);
-				DEH_LoadDehackedLumpPwad((UINT16)w, texstart + j);
-			}
-			else
-			{
-				UINT16 patchcount = 1;
-				//CONS_Printf("\n\"%s\" is a single patch, dimensions %d x %d",W_CheckNameForNumPwad((UINT16)w,texstart+j),patchlump->width, patchlump->height);
-				if (SHORT(patchlump->width) == 64
-				&& SHORT(patchlump->height) == 64)
-				{ // 64x64 patch
-					const column_t *column;
-					for (k = 0; k < SHORT(patchlump->width); k++)
-					{ // Find use of transparency.
-						column = (const column_t *)((const UINT8 *)patchlump + LONG(patchlump->columnofs[k]));
-						if (column->length != SHORT(patchlump->height))
-							break;
-					}
-					if (k == SHORT(patchlump->width))
-						patchcount = 2; // No transparency? 64x128 texture.
-				}
-				texture = textures[i] = Z_Calloc(sizeof(texture_t) + (sizeof(texpatch_t) * patchcount), PU_STATIC, NULL);
-
-				// Set texture properties.
-				M_Memcpy(texture->name, W_CheckNameForNumPwad((UINT16)w, texstart + j), sizeof(texture->name));
-				texture->width = SHORT(patchlump->width);
-				texture->height = SHORT(patchlump->height)*patchcount;
-				texture->patchcount = patchcount;
-				texture->holes = false;
-				texture->flip = 0;
-
-				// Allocate information for the texture's patches.
-				for (k = 0; k < patchcount; k++)
-				{
-					patch = &texture->patches[k];
+			//CONS_Printf("\n\"%s\" is a single patch, dimensions %d x %d",W_CheckNameForNumPwad((UINT16)w,texstart+j),patchlump->width, patchlump->height);
+			texture = textures[i] = Z_Calloc(sizeof(texture_t) + sizeof(texpatch_t), PU_STATIC, NULL);
 
-					patch->originx = 0;
-					patch->originy = (INT16)(k*patchlump->height);
-					patch->wad = (UINT16)w;
-					patch->lump = texstart + j;
-					patch->flip = 0;
-				}
+			// Set texture properties.
+			M_Memcpy(texture->name, W_CheckNameForNumPwad((UINT16)w, texstart + j), sizeof(texture->name));
+			texture->width = SHORT(patchlump->width);
+			texture->height = SHORT(patchlump->height);
+			texture->patchcount = 1;
+			texture->holes = false;
+			texture->flip = 0;
 
-				Z_Unlock(patchlump);
+			// Allocate information for the texture's patches.
+			patch = &texture->patches[0];
 
-				k = 1;
-				while (k << 1 <= texture->width)
-					k <<= 1;
+			patch->originx = patch->originy = 0;
+			patch->wad = (UINT16)w;
+			patch->lump = texstart + j;
+			patch->flip = 0;
 
-				texturewidthmask[i] = k - 1;
-				textureheight[i] = texture->height << FRACBITS;
-			}
+			Z_Unlock(patchlump);
+
+			k = 1;
+			while (k << 1 <= texture->width)
+				k <<= 1;
+
+			texturewidthmask[i] = k - 1;
+			textureheight[i] = texture->height << FRACBITS;
 		}
 	}
 }
@@ -1169,23 +1140,24 @@ static void R_InitExtraColormaps(void)
 	for (cfile = clump = 0; cfile < numwadfiles; cfile++, clump = 0)
 	{
 		startnum = W_CheckNumForNamePwad("C_START", cfile, clump);
-		if (startnum == LUMPERROR)
+		if (startnum == INT16_MAX)
 			continue;
 
 		endnum = W_CheckNumForNamePwad("C_END", cfile, clump);
 
-		if (endnum == LUMPERROR)
+		if (endnum == INT16_MAX)
 			I_Error("R_InitExtraColormaps: C_START without C_END\n");
 
-		if (WADFILENUM(startnum) != WADFILENUM(endnum))
-			I_Error("R_InitExtraColormaps: C_START and C_END in different wad files!\n");
+		// This shouldn't be possible when you use the Pwad function, silly
+		//if (WADFILENUM(startnum) != WADFILENUM(endnum))
+			//I_Error("R_InitExtraColormaps: C_START and C_END in different wad files!\n");
 
 		if (numcolormaplumps >= maxcolormaplumps)
 			maxcolormaplumps *= 2;
 		colormaplumps = Z_Realloc(colormaplumps,
 			sizeof (*colormaplumps) * maxcolormaplumps, PU_STATIC, NULL);
-		colormaplumps[numcolormaplumps].wadfile = WADFILENUM(startnum);
-		colormaplumps[numcolormaplumps].firstlump = LUMPNUM(startnum+1);
+		colormaplumps[numcolormaplumps].wadfile = cfile;
+		colormaplumps[numcolormaplumps].firstlump = startnum+1;
 		colormaplumps[numcolormaplumps].numlumps = endnum - (startnum + 1);
 		numcolormaplumps++;
 	}
@@ -1428,9 +1400,9 @@ INT32 R_CreateColormap(char *p1, char *p2, char *p3)
 	{
 		for (i = 0; i < 256; i++)
 		{
-			r = pLocalPalette[i].s.red;
-			g = pLocalPalette[i].s.green;
-			b = pLocalPalette[i].s.blue;
+			r = pMasterPalette[i].s.red;
+			g = pMasterPalette[i].s.green;
+			b = pMasterPalette[i].s.blue;
 			cbrightness = sqrt((r*r) + (g*g) + (b*b));
 
 			map[i][0] = (cbrightness * cmaskr) + (r * othermask);
@@ -1571,9 +1543,9 @@ void R_CreateColormap2(char *p1, char *p2, char *p3)
 	{
 		for (i = 0; i < 256; i++)
 		{
-			r = pLocalPalette[i].s.red;
-			g = pLocalPalette[i].s.green;
-			b = pLocalPalette[i].s.blue;
+			r = pMasterPalette[i].s.red;
+			g = pMasterPalette[i].s.green;
+			b = pMasterPalette[i].s.blue;
 			cbrightness = sqrt((r*r) + (g*g) + (b*b));
 
 			map[i][0] = (cbrightness * cmaskr) + (r * othermask);
@@ -1653,9 +1625,9 @@ static UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b)
 
 	for (i = 0; i < 256; i++)
 	{
-		dr = r - pLocalPalette[i].s.red;
-		dg = g - pLocalPalette[i].s.green;
-		db = b - pLocalPalette[i].s.blue;
+		dr = r - pMasterPalette[i].s.red;
+		dg = g - pMasterPalette[i].s.green;
+		db = b - pMasterPalette[i].s.blue;
 		distortion = dr*dr + dg*dg + db*db;
 		if (distortion < bestdistortion)
 		{
diff --git a/src/r_data.h b/src/r_data.h
index 2d984c1c8da80d0d254b1ca8fc8c1c0994b217f6..53bf27835863e52dde02098321fc115863ebad3b 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -68,7 +68,7 @@ extern INT16 *hicolormaps; // remap high colors to high colors..
 
 extern CV_PossibleValue_t Color_cons_t[];
 
-// Load TEXTURE1/TEXTURE2/PNAMES definitions, create lookup tables
+// Load TEXTURES definitions, create lookup tables
 void R_LoadTextures(void);
 void R_FlushTextureCache(void);
 
diff --git a/src/r_defs.h b/src/r_defs.h
index 32be7c4c8d360a6de6bbef0dbbff78fdf9175861..7c8f2a73fc29511268fc8728a6d9700fcfa55447 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -574,6 +574,9 @@ typedef struct seg_s
 	sector_t *backsector;
 
 #ifdef HWRENDER
+	// new pointers so that AdjustSegs doesn't mess with v1/v2
+	void *pv1; // polyvertex_t
+	void *pv2; // polyvertex_t
 	float flength; // length of the seg, used by hardware renderer
 
 	lightmap_t *lightmaps; // for static lightmap
@@ -685,7 +688,7 @@ typedef enum
 // Patches.
 // A patch holds one or more columns.
 // Patches are used for sprites and all masked pictures, and we compose
-// textures from the TEXTURE1 list of patches.
+// textures from the TEXTURES list of patches.
 //
 // WARNING: this structure is cloned in GLPatch_t
 typedef struct
diff --git a/src/r_draw.c b/src/r_draw.c
index 30b60a0e0a40dad34266e620047d65ac6ce9046f..4479cf02ca5565b3e561420223202237be6b51e6 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -126,95 +126,301 @@ UINT32 nflatxshift, nflatyshift, nflatshiftup, nflatmask;
 #define BOSS_TT_CACHE_INDEX (MAXSKINS + 1)
 #define METALSONIC_TT_CACHE_INDEX (MAXSKINS + 2)
 #define ALLWHITE_TT_CACHE_INDEX (MAXSKINS + 3)
-#define SKIN_RAMP_LENGTH 16
 #define DEFAULT_STARTTRANSCOLOR 96
 #define NUM_PALETTE_ENTRIES 256
 
 static UINT8** translationtablecache[MAXSKINS + 4] = {NULL};
 
+const UINT8 Color_Index[MAXTRANSLATIONS-1][16] = {
+	// {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, // SKINCOLOR_NONE
+
+	// Greyscale ranges
+	{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x02, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x11}, // SKINCOLOR_WHITE
+	{0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x05, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x11, 0x12}, // SKINCOLOR_BONE
+	{0x02, 0x03, 0x04, 0x05, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14}, // SKINCOLOR_CLOUDY
+	{0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}, // SKINCOLOR_GREY
+	{0x02, 0x03, 0x05, 0x07, 0x09, 0x0b, 0x0d, 0x0f, 0x11, 0x13, 0x15, 0x17, 0x19, 0x1b, 0x1d, 0x1f}, // SKINCOLOR_SILVER
+	{0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x16, 0x17, 0x17, 0x19, 0x19, 0x1a, 0x1a, 0x1b, 0x1c, 0x1d}, // SKINCOLOR_CARBON
+	{0x00, 0x05, 0x0a, 0x0f, 0x14, 0x19, 0x1a, 0x1b, 0x1c, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x1f}, // SKINCOLOR_JET
+	{0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1b, 0x1b, 0x1c, 0x1d, 0x1d, 0x1e, 0x1e, 0x1f, 0x1f}, // SKINCOLOR_BLACK
+
+	// Desaturated
+	{0x00, 0x00, 0x01, 0x02, 0x02, 0x03, 0x91, 0x91, 0x91, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xaf}, // SKINCOLOR_AETHER
+	{0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0xaa, 0xaa, 0xaa, 0xab, 0xac, 0xac, 0xad, 0xad, 0xae, 0xaf}, // SKINCOLOR_SLATE
+	{0xd0, 0xd0, 0xd1, 0xd1, 0xd2, 0xd2, 0xd3, 0xd3, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0x2b, 0x2c, 0x2e}, // SKINCOLOR_PINK
+	{0xd0, 0x30, 0xd8, 0xd9, 0xda, 0xdb, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe3, 0xe6, 0xe8, 0xe9}, // SKINCOLOR_YOGURT
+	{0xdf, 0xe0, 0xe1, 0xe2, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef}, // SKINCOLOR_BROWN
+	{0x51, 0x51, 0x54, 0x54, 0x55, 0x55, 0x56, 0x56, 0x56, 0x57, 0xf5, 0xf5, 0xf9, 0xf9, 0xed, 0xed}, // SKINCOLOR_TAN
+	{0x54, 0x55, 0x56, 0x56, 0xf2, 0xf3, 0xf3, 0xf4, 0xf5, 0xf6, 0xf8, 0xf9, 0xfa, 0xfb, 0xed, 0xed}, // SKINCOLOR_BEIGE
+	{0x58, 0x58, 0x59, 0x59, 0x5a, 0x5a, 0x5b, 0x5b, 0x5b, 0x5c, 0x5d, 0x5d, 0x5e, 0x5e, 0x5f, 0x5f}, // SKINCOLOR_MOSS
+	{0x90, 0x90, 0x91, 0x91, 0xaa, 0xaa, 0xab, 0xab, 0xab, 0xac, 0xad, 0xad, 0xae, 0xae, 0xaf, 0xaf}, // SKINCOLOR_AZURE
+	{0xc0, 0xc0, 0xc1, 0xc1, 0xc2, 0xc2, 0xc3, 0xc3, 0xc3, 0xc4, 0xc5, 0xc5, 0xc6, 0xc6, 0xc7, 0xc7}, // SKINCOLOR_LAVENDER
+
+	// Viv's vivid colours (toast 21/07/17)
+	{0xb0, 0xb0, 0xc9, 0xca, 0xcc, 0x26, 0x27, 0x28, 0x29, 0x2a, 0xb9, 0xb9, 0xba, 0xba, 0xbb, 0xfd}, // SKINCOLOR_RUBY
+	{0xd0, 0xd0, 0xd1, 0xd2, 0x20, 0x21, 0x24, 0x25, 0x26, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e}, // SKINCOLOR_SALMON
+	{0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x47, 0x2e, 0x2f}, // SKINCOLOR_RED
+	{0x27, 0x27, 0x28, 0x28, 0x29, 0x2a, 0x2b, 0x2b, 0x2c, 0x2d, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, 0x1f}, // SKINCOLOR_CRIMSON
+	{0x31, 0x32, 0x33, 0x36, 0x22, 0x22, 0x25, 0x25, 0x25, 0xcd, 0xcf, 0xcf, 0xc5, 0xc5, 0xc7, 0xc7}, // SKINCOLOR_FLAME
+	{0xd0, 0x30, 0x31, 0x31, 0x32, 0x32, 0xdc, 0xdc, 0xdc, 0xd3, 0xd4, 0xd4, 0xcc, 0xcd, 0xce, 0xcf}, // SKINCOLOR_PEACHY
+	{0xd8, 0xd9, 0xdb, 0xdc, 0xde, 0xdf, 0xd5, 0xd5, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0x1d, 0x1f}, // SKINCOLOR_QUAIL
+	{0x51, 0x52, 0x40, 0x40, 0x34, 0x36, 0xd5, 0xd5, 0xd6, 0xd7, 0xcf, 0xcf, 0xc6, 0xc6, 0xc7, 0xfe}, // SKINCOLOR_SUNSET
+	{0x00, 0xd8, 0xd9, 0xda, 0xdb, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e}, // SKINCOLOR_APRICOT
+	{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x2c}, // SKINCOLOR_ORANGE
+	{0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3c, 0x3d, 0x3d, 0x3d, 0x3f, 0x2c, 0x2d, 0x47, 0x2e, 0x2f, 0x2f}, // SKINCOLOR_RUST
+	{0x51, 0x51, 0x54, 0x54, 0x41, 0x42, 0x43, 0x43, 0x44, 0x45, 0x46, 0x3f, 0x2d, 0x2e, 0x2f, 0x2f}, // SKINCOLOR_GOLD
+	{0x53, 0x40, 0x41, 0x42, 0x43, 0xe6, 0xe9, 0xe9, 0xea, 0xec, 0xec, 0xc6, 0xc6, 0xc7, 0xc7, 0xfe}, // SKINCOLOR_SANDY
+	{0x52, 0x53, 0x49, 0x49, 0x4a, 0x4a, 0x4b, 0x4b, 0x4b, 0x4c, 0x4d, 0x4d, 0x4e, 0x4e, 0x4f, 0xed}, // SKINCOLOR_YELLOW
+	{0x4b, 0x4b, 0x4c, 0x4c, 0x4d, 0x4e, 0xe7, 0xe7, 0xe9, 0xc5, 0xc5, 0xc6, 0xc6, 0xc7, 0xc7, 0xfd}, // SKINCOLOR_OLIVE
+	{0x50, 0x51, 0x52, 0x53, 0x48, 0xbc, 0xbd, 0xbe, 0xbe, 0xbf, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f}, // SKINCOLOR_LIME
+	{0x58, 0x58, 0xbc, 0xbc, 0xbd, 0xbd, 0xbe, 0xbe, 0xbe, 0xbf, 0x5e, 0x5e, 0x5f, 0x5f, 0x77, 0x77}, // SKINCOLOR_PERIDOT
+	{0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f}, // SKINCOLOR_GREEN
+	{0x65, 0x66, 0x67, 0x68, 0x69, 0x69, 0x6a, 0x6b, 0x6b, 0x6c, 0x6d, 0x6d, 0x6e, 0x6e, 0x6e, 0x6f}, // SKINCOLOR_FOREST
+	{0x70, 0x70, 0x71, 0x71, 0x72, 0x72, 0x73, 0x73, 0x73, 0x74, 0x75, 0x75, 0x76, 0x76, 0x77, 0x77}, // SKINCOLOR_EMERALD
+	{0x00, 0x00, 0x58, 0x58, 0x59, 0x62, 0x62, 0x62, 0x64, 0x67, 0x7e, 0x7e, 0x8f, 0x8f, 0x8a, 0x8a}, // SKINCOLOR_MINT
+	{0x01, 0x58, 0x59, 0x5a, 0x7d, 0x7d, 0x7e, 0x7e, 0x7e, 0x8f, 0x8f, 0x8a, 0x8a, 0x8a, 0xfd, 0xfd}, // SKINCOLOR_SEAFOAM
+	{0x78, 0x79, 0x7a, 0x7a, 0x7b, 0x7b, 0x7c, 0x7c, 0x7c, 0x7d, 0x7e, 0x7e, 0x7f, 0x7f, 0x76, 0x77}, // SKINCOLOR_AQUA
+	{0x78, 0x78, 0x8c, 0x8c, 0x8d, 0x8d, 0x8d, 0x8e, 0x8e, 0x8f, 0x8f, 0x8f, 0x8a, 0x8a, 0x8a, 0x8a}, // SKINCOLOR_TEAL
+	{0x00, 0x78, 0x78, 0x79, 0x8d, 0x87, 0x88, 0x89, 0x89, 0xae, 0xa8, 0xa8, 0xa9, 0xa9, 0xfd, 0xfd}, // SKINCOLOR_WAVE
+	{0x80, 0x81, 0xff, 0xff, 0x83, 0x83, 0x8d, 0x8d, 0x8d, 0x8e, 0x7e, 0x7f, 0x76, 0x76, 0x77, 0x6e}, // SKINCOLOR_CYAN
+	{0x80, 0x80, 0x81, 0x82, 0x83, 0x83, 0x84, 0x85, 0x85, 0x86, 0x87, 0x88, 0x89, 0x89, 0x8a, 0x8b}, // SKINCOLOR_SKY
+	{0x85, 0x86, 0x87, 0x88, 0x88, 0x89, 0x89, 0x89, 0x8a, 0x8a, 0xfd, 0xfd, 0xfd, 0x1f, 0x1f, 0x1f}, // SKINCOLOR_CERULEAN
+	{0x00, 0x00, 0x00, 0x00, 0x80, 0x81, 0x83, 0x83, 0x86, 0x87, 0x95, 0x95, 0xad, 0xad, 0xae, 0xaf}, // SKINCOLOR_ICY
+	{0x80, 0x83, 0x86, 0x87, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xfd, 0xfe}, // SKINCOLOR_SAPPHIRE
+	{0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x9a, 0x9c, 0x9d, 0x9d, 0x9e, 0x9e, 0x9e}, // SKINCOLOR_CORNFLOWER
+	{0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xfd, 0xfe}, // SKINCOLOR_BLUE
+	{0x93, 0x94, 0x95, 0x96, 0x98, 0x9a, 0x9b, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xfd, 0xfd, 0xfe, 0xfe}, // SKINCOLOR_COBALT
+	{0x80, 0x81, 0x83, 0x86, 0x94, 0x94, 0xa3, 0xa3, 0xa4, 0xa6, 0xa6, 0xa6, 0xa8, 0xa8, 0xa9, 0xa9}, // SKINCOLOR_VAPOR
+	{0x92, 0x93, 0x94, 0x94, 0xac, 0xad, 0xad, 0xad, 0xae, 0xae, 0xaf, 0xaf, 0xa9, 0xa9, 0xfd, 0xfd}, // SKINCOLOR_DUSK
+	{0x90, 0x90, 0xa0, 0xa0, 0xa1, 0xa1, 0xa2, 0xa2, 0xa2, 0xa3, 0xa4, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8}, // SKINCOLOR_PASTEL
+	{0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa4, 0xa5, 0xa5, 0xa5, 0xa6, 0xa7, 0xa7, 0xa8, 0xa8, 0xa9, 0xa9}, // SKINCOLOR_PURPLE
+	{0x00, 0xd0, 0xd0, 0xc8, 0xc8, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8}, // SKINCOLOR_BUBBLEGUM
+	{0xb3, 0xb3, 0xb4, 0xb5, 0xb6, 0xb6, 0xb7, 0xb7, 0xb7, 0xb8, 0xb9, 0xb9, 0xba, 0xba, 0xbb, 0xbb}, // SKINCOLOR_MAGENTA
+	{0xb3, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xb9, 0xba, 0xba, 0xbb, 0xbb, 0xc7, 0xc7, 0x1d, 0x1d, 0x1e}, // SKINCOLOR_NEON
+	{0xd0, 0xd1, 0xd2, 0xca, 0xcc, 0xb8, 0xb9, 0xb9, 0xba, 0xa8, 0xa8, 0xa9, 0xa9, 0xfd, 0xfe, 0xfe}, // SKINCOLOR_VIOLET
+	{0x00, 0xd0, 0xd1, 0xd2, 0xd3, 0xc1, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc5, 0xc6, 0xc6, 0xfe, 0x1f}, // SKINCOLOR_LILAC
+	{0xc8, 0xd3, 0xd5, 0xd6, 0xd7, 0xce, 0xcf, 0xb9, 0xb9, 0xba, 0xba, 0xa9, 0xa9, 0xa9, 0xfd, 0xfe}, // SKINCOLOR_PLUM
+	{0xfc, 0xc8, 0xc8, 0xc9, 0xc9, 0xca, 0xca, 0xcb, 0xcb, 0xcc, 0xcc, 0xcd, 0xcd, 0xce, 0xce, 0xcf}, // SKINCOLOR_ROSY
+
+	// {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, // SKINCOLOR_?
+
+	// super
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x03}, // SKINCOLOR_SUPERSILVER1
+	{0x00, 0x01, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x07}, // SKINCOLOR_SUPERSILVER2
+	{0x01, 0x02, 0x02, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x07, 0x09, 0x0b}, // SKINCOLOR_SUPERSILVER3
+	{0x02, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x07, 0x09, 0x0b, 0x0d, 0x0f, 0x11}, // SKINCOLOR_SUPERSILVER4
+	{0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x07, 0x09, 0x0b, 0x0d, 0x0f, 0x11, 0x13}, // SKINCOLOR_SUPERSILVER5
+
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0xd0, 0xd1, 0xd1, 0xd2, 0xd2}, // SKINCOLOR_SUPERRED1
+	{0x00, 0x00, 0x00, 0xd0, 0xd0, 0xd0, 0xd1, 0xd1, 0xd1, 0xd2, 0xd2, 0xd2, 0x20, 0x20, 0x21, 0x21}, // SKINCOLOR_SUPERRED2
+	{0x00, 0x00, 0xd0, 0xd0, 0xd1, 0xd1, 0xd2, 0xd2, 0x20, 0x20, 0x21, 0x21, 0x22, 0x22, 0x23, 0x23}, // SKINCOLOR_SUPERRED3
+	{0x00, 0xd0, 0xd1, 0xd1, 0xd2, 0xd2, 0x20, 0x20, 0x21, 0x21, 0x22, 0x22, 0x23, 0x23, 0x24, 0x24}, // SKINCOLOR_SUPERRED4
+	{0xd0, 0xd1, 0xd2, 0xd2, 0x20, 0x20, 0x21, 0x21, 0x22, 0x22, 0x23, 0x23, 0x24, 0x24, 0x25, 0x25}, // SKINCOLOR_SUPERRED5
+
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x30, 0x31, 0x32, 0x33, 0x34}, // SKINCOLOR_SUPERORANGE1
+	{0x00, 0x00, 0x00, 0x00, 0xd0, 0xd0, 0x30, 0x30, 0x31, 0x31, 0x32, 0x32, 0x33, 0x33, 0x34, 0x34}, // SKINCOLOR_SUPERORANGE2
+	{0x00, 0x00, 0xd0, 0xd0, 0x30, 0x30, 0x31, 0x31, 0x32, 0x32, 0x33, 0x33, 0x34, 0x34, 0x35, 0x35}, // SKINCOLOR_SUPERORANGE3
+	{0x00, 0xd0, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x44, 0x45, 0x46}, // SKINCOLOR_SUPERORANGE4
+	{0xd0, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x44, 0x45, 0x46, 0x47}, // SKINCOLOR_SUPERORANGE5
+
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x50, 0x51, 0x52, 0x53, 0x48}, // SKINCOLOR_SUPERGOLD1
+	{0x00, 0x50, 0x51, 0x52, 0x53, 0x53, 0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x40, 0x41}, // SKINCOLOR_SUPERGOLD2
+	{0x51, 0x52, 0x53, 0x53, 0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x40, 0x41, 0x42, 0x43}, // SKINCOLOR_SUPERGOLD3
+	{0x53, 0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46}, // SKINCOLOR_SUPERGOLD4
+	{0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47}, // SKINCOLOR_SUPERGOLD5
+
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x58, 0x58, 0xbc, 0xbc, 0xbc}, // SKINCOLOR_SUPERPERIDOT1
+	{0x00, 0x58, 0x58, 0x58, 0xbc, 0xbc, 0xbc, 0xbc, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbe, 0xbe}, // SKINCOLOR_SUPERPERIDOT2
+	{0x58, 0x58, 0xbc, 0xbc, 0xbc, 0xbc, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbe, 0xbe, 0xbf, 0xbf}, // SKINCOLOR_SUPERPERIDOT3
+	{0x58, 0xbc, 0xbc, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbe, 0xbe, 0xbf, 0xbf, 0x5e, 0x5e, 0x5f}, // SKINCOLOR_SUPERPERIDOT4
+	{0xbc, 0xbc, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbe, 0xbe, 0xbf, 0xbf, 0x5e, 0x5e, 0x5f, 0x77}, // SKINCOLOR_SUPERPERIDOT5
+
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x81, 0x82, 0x83, 0x84}, // SKINCOLOR_SUPERSKY1
+	{0x00, 0x80, 0x81, 0x82, 0x83, 0x83, 0x84, 0x84, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x86, 0x86}, // SKINCOLOR_SUPERSKY2
+	{0x81, 0x82, 0x83, 0x83, 0x84, 0x84, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x86, 0x86, 0x87, 0x87}, // SKINCOLOR_SUPERSKY3
+	{0x83, 0x84, 0x84, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x86, 0x86, 0x87, 0x87, 0x88, 0x89, 0x8a}, // SKINCOLOR_SUPERSKY4
+	{0x84, 0x84, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x86, 0x86, 0x87, 0x87, 0x88, 0x89, 0x8a, 0x8b}, // SKINCOLOR_SUPERSKY5
+
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x90, 0xa0, 0xa0, 0xa1, 0xa2}, // SKINCOLOR_SUPERPURPLE1
+	{0x00, 0x90, 0xa0, 0xa0, 0xa1, 0xa1, 0xa2, 0xa2, 0xa3, 0xa3, 0xa3, 0xa3, 0xa4, 0xa4, 0xa5, 0xa5}, // SKINCOLOR_SUPERPURPLE2
+	{0xa0, 0xa0, 0xa1, 0xa1, 0xa2, 0xa2, 0xa3, 0xa3, 0xa3, 0xa3, 0xa4, 0xa4, 0xa5, 0xa5, 0xa6, 0xa6}, // SKINCOLOR_SUPERPURPLE3
+	{0xa1, 0xa2, 0xa2, 0xa3, 0xa3, 0xa3, 0xa3, 0xa4, 0xa4, 0xa5, 0xa5, 0xa6, 0xa6, 0xa7, 0xa8, 0xa9}, // SKINCOLOR_SUPERPURPLE4
+	{0xa2, 0xa2, 0xa3, 0xa3, 0xa3, 0xa3, 0xa4, 0xa4, 0xa5, 0xa5, 0xa6, 0xa6, 0xa7, 0xa8, 0xa9, 0xfd}, // SKINCOLOR_SUPERPURPLE5
+
+	{0x00, 0xd0, 0xd0, 0xd0, 0x30, 0x30, 0x31, 0x32, 0x33, 0x37, 0x3a, 0x44, 0x45, 0x46, 0x47, 0x2e}, // SKINCOLOR_SUPERRUST1
+	{0x30, 0x31, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x38, 0x3a, 0x44, 0x45, 0x46, 0x47, 0x47, 0x2e}, // SKINCOLOR_SUPERRUST2
+	{0x31, 0x32, 0x33, 0x34, 0x36, 0x37, 0x38, 0x3a, 0x44, 0x45, 0x45, 0x46, 0x46, 0x47, 0x2e, 0x2e}, // SKINCOLOR_SUPERRUST3
+	{0x48, 0x40, 0x41, 0x42, 0x43, 0x44, 0x44, 0x45, 0x45, 0x46, 0x46, 0x47, 0x47, 0x2e, 0x2e, 0x2e}, // SKINCOLOR_SUPERRUST4
+	{0x41, 0x42, 0x43, 0x43, 0x44, 0x44, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xed, 0xee, 0xee, 0xef, 0xef}, // SKINCOLOR_SUPERRUST5
+
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x50, 0x51, 0x51, 0x52, 0x52}, // SKINCOLOR_SUPERTAN1
+	{0x00, 0x50, 0x50, 0x51, 0x51, 0x52, 0x52, 0x52, 0x54, 0x54, 0x54, 0x54, 0x55, 0x56, 0x57, 0xf5}, // SKINCOLOR_SUPERTAN2
+	{0x50, 0x51, 0x51, 0x52, 0x52, 0x52, 0x54, 0x54, 0x54, 0x54, 0x55, 0x56, 0x57, 0xf5, 0xf7, 0xf9}, // SKINCOLOR_SUPERTAN3
+	{0x51, 0x52, 0x52, 0x52, 0x52, 0x54, 0x54, 0x54, 0x55, 0x56, 0x57, 0xf5, 0xf7, 0xf9, 0xfb, 0xed}, // SKINCOLOR_SUPERTAN4
+	{0x52, 0x52, 0x54, 0x54, 0x54, 0x55, 0x56, 0x57, 0xf5, 0xf7, 0xf9, 0xfb, 0xed, 0xee, 0xef, 0xef} // SKINCOLOR_SUPERTAN5
+};
 
 // See also the enum skincolors_t
 // TODO Callum: Can this be translated?
 const char *Color_Names[MAXSKINCOLORS + NUMSUPERCOLORS] =
 {
-	"None",     	// SKINCOLOR_NONE
-	"White",    	// SKINCOLOR_WHITE
-	"Silver",   	// SKINCOLOR_SILVER
-	"Grey",	    	// SKINCOLOR_GREY
-	"Black",    	// SKINCOLOR_BLACK
-	"Beige",    	// SKINCOLOR_BEIGE
-	"Peach",    	// SKINCOLOR_PEACH
-	"Brown",    	// SKINCOLOR_BROWN
-	"Red",      	// SKINCOLOR_RED
-	"Crimson",     	// SKINCOLOR_CRIMSON
-	"Orange",   	// SKINCOLOR_ORANGE
-	"Rust",     	// SKINCOLOR_RUST
-	"Gold",      	// SKINCOLOR_GOLD
-	"Yellow",   	// SKINCOLOR_YELLOW
-	"Tan",      	// SKINCOLOR_TAN
-	"Moss",      	// SKINCOLOR_MOSS
-	"Peridot",    	// SKINCOLOR_PERIDOT
-	"Green",    	// SKINCOLOR_GREEN
-	"Emerald",  	// SKINCOLOR_EMERALD
-	"Aqua",     	// SKINCOLOR_AQUA
-	"Teal",     	// SKINCOLOR_TEAL
-	"Cyan",     	// SKINCOLOR_CYAN
-	"Blue",     	// SKINCOLOR_BLUE
-	"Azure",    	// SKINCOLOR_AZURE
-	"Pastel",		// SKINCOLOR_PASTEL
-	"Purple",   	// SKINCOLOR_PURPLE
-	"Lavender", 	// SKINCOLOR_LAVENDER
-	"Magenta",   	// SKINCOLOR_MAGENTA
-	"Pink",     	// SKINCOLOR_PINK
-	"Rosy",			// SKINCOLOR_ROSY
+	"None",     	// SKINCOLOR_NONE,
+
+	// Greyscale ranges
+	"White",     	// SKINCOLOR_WHITE,
+	"Bone",     	// SKINCOLOR_BONE,
+	"Cloudy",     	// SKINCOLOR_CLOUDY,
+	"Grey",     	// SKINCOLOR_GREY,
+	"Silver",     	// SKINCOLOR_SILVER,
+	"Carbon",     	// SKINCOLOR_CARBON,
+	"Jet",     		// SKINCOLOR_JET,
+	"Black",     	// SKINCOLOR_BLACK,
+
+	// Desaturated
+	"Aether",     	// SKINCOLOR_AETHER,
+	"Slate",     	// SKINCOLOR_SLATE,
+	"Pink",     	// SKINCOLOR_PINK,
+	"Yogurt",     	// SKINCOLOR_YOGURT,
+	"Brown",     	// SKINCOLOR_BROWN,
+	"Tan",     		// SKINCOLOR_TAN,
+	"Beige",     	// SKINCOLOR_BEIGE,
+	"Moss",     	// SKINCOLOR_MOSS,
+	"Azure",     	// SKINCOLOR_AZURE,
+	"Lavender",     // SKINCOLOR_LAVENDER,
+
+	// Viv's vivid colours (toast 21/07/17)
+	"Ruby",     	// SKINCOLOR_RUBY,
+	"Salmon",     	// SKINCOLOR_SALMON,
+	"Red",     		// SKINCOLOR_RED,
+	"Crimson",     	// SKINCOLOR_CRIMSON,
+	"Flame",     	// SKINCOLOR_FLAME,
+	"Peachy",     	// SKINCOLOR_PEACHY,
+	"Quail",     	// SKINCOLOR_QUAIL,
+	"Sunset",     	// SKINCOLOR_SUNSET,
+	"Apricot",     	// SKINCOLOR_APRICOT,
+	"Orange",     	// SKINCOLOR_ORANGE,
+	"Rust",     	// SKINCOLOR_RUST,
+	"Gold",     	// SKINCOLOR_GOLD,
+	"Sandy",     	// SKINCOLOR_SANDY,
+	"Yellow",     	// SKINCOLOR_YELLOW,
+	"Olive",     	// SKINCOLOR_OLIVE,
+	"Lime",     	// SKINCOLOR_LIME,
+	"Peridot",     	// SKINCOLOR_PERIDOT,
+	"Green",     	// SKINCOLOR_GREEN,
+	"Forest",     	// SKINCOLOR_FOREST,
+	"Emerald",     	// SKINCOLOR_EMERALD,
+	"Mint",     	// SKINCOLOR_MINT,
+	"Seafoam",     	// SKINCOLOR_SEAFOAM,
+	"Aqua",     	// SKINCOLOR_AQUA,
+	"Teal",     	// SKINCOLOR_TEAL,
+	"Wave",     	// SKINCOLOR_WAVE,
+	"Cyan",     	// SKINCOLOR_CYAN,
+	"Sky",     		// SKINCOLOR_SKY,
+	"Cerulean",     // SKINCOLOR_CERULEAN,
+	"Icy",     		// SKINCOLOR_ICY,
+	"Sapphire",     // SKINCOLOR_SAPPHIRE,
+	"Cornflower",   // SKINCOLOR_CORNFLOWER,
+	"Blue",     	// SKINCOLOR_BLUE,
+	"Cobalt",     	// SKINCOLOR_COBALT,
+	"Vapor",     	// SKINCOLOR_VAPOR,
+	"Dusk",     	// SKINCOLOR_DUSK,
+	"Pastel",     	// SKINCOLOR_PASTEL,
+	"Purple",     	// SKINCOLOR_PURPLE,
+	"Bubblegum",    // SKINCOLOR_BUBBLEGUM,
+	"Magenta",     	// SKINCOLOR_MAGENTA,
+	"Neon",     	// SKINCOLOR_NEON,
+	"Violet",     	// SKINCOLOR_VIOLET,
+	"Lilac",     	// SKINCOLOR_LILAC,
+	"Plum",     	// SKINCOLOR_PLUM,
+	"Rosy",     	// SKINCOLOR_ROSY,
+
 	// Super behaves by different rules (one name per 5 colours), and will be accessed exclusively via R_GetSuperColorByName instead of R_GetColorByName.
-	"Silver",		// SKINCOLOR_SUPERSILVER1
-	"Red",			// SKINCOLOR_SUPERRED1
-	"Orange",		// SKINCOLOR_SUPERORANGE1
-	"Gold",			// SKINCOLOR_SUPERGOLD1
-	"Peridot",		// SKINCOLOR_SUPERPERIDOT1
-	"Cyan",			// SKINCOLOR_SUPERCYAN1
-	"Purple",		// SKINCOLOR_SUPERPURPLE1
-	"Rust",			// SKINCOLOR_SUPERRUST1
-	"Tan"			// SKINCOLOR_SUPERTAN1
+	"Silver",		// SKINCOLOR_SUPERSILVER1,
+	"Red",			// SKINCOLOR_SUPERRED1,
+	"Orange",		// SKINCOLOR_SUPERORANGE1,
+	"Gold",			// SKINCOLOR_SUPERGOLD1,
+	"Peridot",		// SKINCOLOR_SUPERPERIDOT1,
+	"Sky",			// SKINCOLOR_SUPERSKY1,
+	"Purple",		// SKINCOLOR_SUPERPURPLE1,
+	"Rust",			// SKINCOLOR_SUPERRUST1,
+	"Tan"			// SKINCOLOR_SUPERTAN1,
 };
 
 /*
 A word of warning: If the following array is non-symmetrical,
 A_SignPlayer's prefoppositecolor behaviour will break.
 */
-const UINT8 Color_Opposite[MAXSKINCOLORS*2] =
+const UINT8 Color_Opposite[(MAXSKINCOLORS - 1)*2] =
 {
-	SKINCOLOR_NONE,8,   	// SKINCOLOR_NONE
-	SKINCOLOR_BLACK,10, 	// SKINCOLOR_WHITE
-	SKINCOLOR_GREY,4,   	// SKINCOLOR_SILVER
-	SKINCOLOR_SILVER,12,	// SKINCOLOR_GREY
-	SKINCOLOR_WHITE,8,  	// SKINCOLOR_BLACK
-	SKINCOLOR_BEIGE,8,   	// SKINCOLOR_BEIGE - needs new offset
-	SKINCOLOR_BROWN,8,   	// SKINCOLOR_PEACH - ditto
-	SKINCOLOR_PEACH,8,   	// SKINCOLOR_BROWN - ditto
-	SKINCOLOR_GREEN,5,  	// SKINCOLOR_RED
-	SKINCOLOR_CYAN,8,  		// SKINCOLOR_CRIMSON - ditto
-	SKINCOLOR_BLUE,11,  	// SKINCOLOR_ORANGE
-	SKINCOLOR_TAN,8,   		// SKINCOLOR_RUST - ditto
-	SKINCOLOR_LAVENDER,8,	// SKINCOLOR_GOLD
-	SKINCOLOR_TEAL,8,   	// SKINCOLOR_YELLOW - ditto
-	SKINCOLOR_RUST,8,   	// SKINCOLOR_TAN - ditto
-	SKINCOLOR_MAGENTA,3, 	// SKINCOLOR_MOSS
-	SKINCOLOR_PURPLE,8,   	// SKINCOLOR_PERIDOT - ditto
-	SKINCOLOR_RED,11,   	// SKINCOLOR_GREEN
-	SKINCOLOR_PASTEL,8,   	// SKINCOLOR_EMERALD - ditto
-	SKINCOLOR_ROSY,8,   	// SKINCOLOR_AQUA - ditto
-	SKINCOLOR_YELLOW,8,   	// SKINCOLOR_TEAL - ditto
-	SKINCOLOR_CRIMSON,8,    // SKINCOLOR_CYAN - ditto
-	SKINCOLOR_ORANGE,9, 	// SKINCOLOR_BLUE
-	SKINCOLOR_PINK,8,   	// SKINCOLOR_AZURE - ditto
-	SKINCOLOR_EMERALD,8,   	// SKINCOLOR_PASTEL - ditto
-	SKINCOLOR_PERIDOT,10,   // SKINCOLOR_PURPLE
-	SKINCOLOR_GOLD,10,      // SKINCOLOR_LAVENDER
-	SKINCOLOR_MOSS,8,   	// SKINCOLOR_MAGENTA - ditto
-	SKINCOLOR_AZURE,8,   	// SKINCOLOR_PINK - ditto
-	SKINCOLOR_AQUA,14   	// SKINCOLOR_ROSY
+	// SKINCOLOR_NONE,8,   	// SKINCOLOR_NONE
+
+	// Greyscale ranges
+	SKINCOLOR_BLACK,5,		// SKINCOLOR_WHITE,
+	SKINCOLOR_JET,7,     	// SKINCOLOR_BONE,
+	SKINCOLOR_CARBON,7,     // SKINCOLOR_CLOUDY,
+	SKINCOLOR_AETHER,12,    // SKINCOLOR_GREY,
+	SKINCOLOR_SLATE,12,    	// SKINCOLOR_SILVER,
+	SKINCOLOR_CLOUDY,7,     // SKINCOLOR_CARBON,
+	SKINCOLOR_BONE,7,     	// SKINCOLOR_JET,
+	SKINCOLOR_WHITE,7,     	// SKINCOLOR_BLACK,
+
+	// Desaturated
+	SKINCOLOR_GREY,15,		// SKINCOLOR_AETHER,
+	SKINCOLOR_SILVER,12,	// SKINCOLOR_SLATE,
+	SKINCOLOR_AZURE,9,		// SKINCOLOR_PINK,
+	SKINCOLOR_RUST,7,		// SKINCOLOR_YOGURT,
+	SKINCOLOR_TAN,2,		// SKINCOLOR_BROWN,
+	SKINCOLOR_BROWN,12,		// SKINCOLOR_TAN,
+	SKINCOLOR_MOSS,5,     	// SKINCOLOR_BEIGE,
+	SKINCOLOR_BEIGE,13,		// SKINCOLOR_MOSS,
+	SKINCOLOR_PINK,5,		// SKINCOLOR_AZURE,
+	SKINCOLOR_GOLD,4,		// SKINCOLOR_LAVENDER,
+
+	// Viv's vivid colours (toast 21/07/17)
+	SKINCOLOR_EMERALD,10,	// SKINCOLOR_RUBY,
+	SKINCOLOR_FOREST,6,		// SKINCOLOR_SALMON,
+	SKINCOLOR_GREEN,10,		// SKINCOLOR_RED,
+	SKINCOLOR_ICY,10,		// SKINCOLOR_CRIMSON,
+	SKINCOLOR_PURPLE,8,		// SKINCOLOR_FLAME,
+	SKINCOLOR_TEAL,7,		// SKINCOLOR_PEACHY,
+	SKINCOLOR_WAVE,5,		// SKINCOLOR_QUAIL,
+	SKINCOLOR_SAPPHIRE,5,	// SKINCOLOR_SUNSET,
+	SKINCOLOR_CYAN,4,		// SKINCOLOR_APRICOT,
+	SKINCOLOR_BLUE,4,		// SKINCOLOR_ORANGE,
+	SKINCOLOR_YOGURT,8,		// SKINCOLOR_RUST,
+	SKINCOLOR_LAVENDER,10,	// SKINCOLOR_GOLD,
+	SKINCOLOR_SKY,8,		// SKINCOLOR_SANDY,
+	SKINCOLOR_CORNFLOWER,8,	// SKINCOLOR_YELLOW,
+	SKINCOLOR_DUSK,3,		// SKINCOLOR_OLIVE,
+	SKINCOLOR_MAGENTA,9,	// SKINCOLOR_LIME,
+	SKINCOLOR_COBALT,2,		// SKINCOLOR_PERIDOT,
+	SKINCOLOR_RED,6,		// SKINCOLOR_GREEN,
+	SKINCOLOR_SALMON,9,		// SKINCOLOR_FOREST,
+	SKINCOLOR_RUBY,4,     	// SKINCOLOR_EMERALD,
+	SKINCOLOR_VIOLET,5,		// SKINCOLOR_MINT,
+	SKINCOLOR_PLUM,6,		// SKINCOLOR_SEAFOAM,
+	SKINCOLOR_ROSY,7,		// SKINCOLOR_AQUA,
+	SKINCOLOR_PEACHY,7,		// SKINCOLOR_TEAL,
+	SKINCOLOR_QUAIL,5,		// SKINCOLOR_WAVE,
+	SKINCOLOR_APRICOT,6,	// SKINCOLOR_CYAN,
+	SKINCOLOR_SANDY,1,		// SKINCOLOR_SKY,
+	SKINCOLOR_NEON,4,		// SKINCOLOR_CERULEAN,
+	SKINCOLOR_CRIMSON,0,	// SKINCOLOR_ICY,
+	SKINCOLOR_SUNSET,5,     // SKINCOLOR_SAPPHIRE,
+	SKINCOLOR_YELLOW,4,		// SKINCOLOR_CORNFLOWER,
+	SKINCOLOR_ORANGE,5,     // SKINCOLOR_BLUE,
+	SKINCOLOR_PERIDOT,5,	// SKINCOLOR_COBALT,
+	SKINCOLOR_LILAC,4,		// SKINCOLOR_VAPOR,
+	SKINCOLOR_OLIVE,0,		// SKINCOLOR_DUSK,
+	SKINCOLOR_BUBBLEGUM,9,	// SKINCOLOR_PASTEL,
+	SKINCOLOR_FLAME,7,		// SKINCOLOR_PURPLE,
+	SKINCOLOR_PASTEL,8,		// SKINCOLOR_BUBBLEGUM,
+	SKINCOLOR_LIME,6,		// SKINCOLOR_MAGENTA,
+	SKINCOLOR_CERULEAN,2,	// SKINCOLOR_NEON,
+	SKINCOLOR_MINT,6,		// SKINCOLOR_VIOLET,
+	SKINCOLOR_VAPOR,4,		// SKINCOLOR_LILAC,
+	SKINCOLOR_MINT,7,		// SKINCOLOR_PLUM,
+	SKINCOLOR_AQUA,1		// SKINCOLOR_ROSY,
 };
 
 CV_PossibleValue_t Color_cons_t[MAXSKINCOLORS+1];
@@ -258,40 +464,7 @@ void R_InitTranslationTables(void)
 */
 static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, UINT8 color)
 {
-	// Table of indices into the palette of the first entries of each translated ramp
-	const UINT8 skinbasecolors[] = {
-		0x00, // SKINCOLOR_WHITE
-		0x03, // SKINCOLOR_SILVER
-		0x08, // SKINCOLOR_GREY
-		0x18, // SKINCOLOR_BLACK
-		0xf0, // SKINCOLOR_BEIGE
-		0xd8, // SKINCOLOR_PEACH
-		0xe0, // SKINCOLOR_BROWN
-		0x21, // SKINCOLOR_RED
-		0x28, // SKINCOLOR_CRIMSON
-		0x31, // SKINCOLOR_ORANGE
-		0x3a, // SKINCOLOR_RUST
-		0x40, // SKINCOLOR_GOLD
-		0x48, // SKINCOLOR_YELLOW
-		0x54, // SKINCOLOR_TAN
-		0x58, // SKINCOLOR_MOSS
-		0xbc, // SKINCOLOR_PERIDOT
-		0x60, // SKINCOLOR_GREEN
-		0x70, // SKINCOLOR_EMERALD
-		0x78, // SKINCOLOR_AQUA
-		0x8c, // SKINCOLOR_TEAL
-		0x80, // SKINCOLOR_CYAN
-		0x92, // SKINCOLOR_BLUE
-		0xaa, // SKINCOLOR_AZURE
-		0xa0, // SKINCOLOR_PASTEL
-		0xa0, // SKINCOLOR_PURPLE
-		0xc0, // SKINCOLOR_LAVENDER
-		0xb3, // SKINCOLOR_MAGENTA
-		0xd0, // SKINCOLOR_PINK
-		0xc8, // SKINCOLOR_ROSY
-	};
-	INT32 i;
-	INT32 starttranscolor;
+	INT32 i, starttranscolor, skinramplength;
 
 	// Handle a couple of simple special cases
 	if (skinnum == TC_BOSS || skinnum == TC_ALLWHITE || skinnum == TC_METALSONIC || color == SKINCOLOR_NONE)
@@ -311,776 +484,28 @@ static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, U
 		return;
 	}
 
+	if (color >= MAXTRANSLATIONS)
+		I_Error("Invalid skin color #%hu.", (UINT16)color);
+
 	starttranscolor = (skinnum != TC_DEFAULT) ? skins[skinnum].starttranscolor : DEFAULT_STARTTRANSCOLOR;
 
 	// Fill in the entries of the palette that are fixed
 	for (i = 0; i < starttranscolor; i++)
 		dest_colormap[i] = (UINT8)i;
 
-	for (i = (UINT8)(starttranscolor + 16); i < NUM_PALETTE_ENTRIES; i++)
-		dest_colormap[i] = (UINT8)i;
-
-	// Build the translated ramp
-	switch (color)
+	i = starttranscolor + 16;
+	if (i < NUM_PALETTE_ENTRIES)
 	{
-	case SKINCOLOR_SILVER:
-	case SKINCOLOR_GREY:
-	case SKINCOLOR_BROWN:
-	case SKINCOLOR_GREEN:
-		// 16 color ramp
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + i);
-		break;
-
-	case SKINCOLOR_WHITE:
-	case SKINCOLOR_CYAN:
-		// 12 color ramp
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + (12*i/SKIN_RAMP_LENGTH));
-		break;
-
-	case SKINCOLOR_BLACK:
-	case SKINCOLOR_MOSS:
-	case SKINCOLOR_EMERALD:
-	case SKINCOLOR_LAVENDER:
-	case SKINCOLOR_PINK:
-		// 8 color ramp
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + (i >> 1));
-		break;
-
-	case SKINCOLOR_BEIGE:
-		// 13 colors
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-		{
-			if (i == 15)
-				dest_colormap[starttranscolor + i] = 0xed; // Darkest
-			else if (i <= 6)
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + ((i + 1) >> 1)); // Brightest
-			else
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + i - 3);
-		}
-		break;
-
-	case SKINCOLOR_PEACH:
-		// 11 colors
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-		{
-			if (i == 0)
-				dest_colormap[starttranscolor + i] = 0xD0; // Lightest 1
-			else if (i == 1)
-				dest_colormap[starttranscolor + i] = 0x30; // Lightest 2
-			else if (i <= 11)
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + (i >> 1) - 1);
-			else
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + i - 7); // Darkest
-		}
-		break;
-
-	case SKINCOLOR_RED:
-		// 16 colors
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-		{
-			if (i == 13)
-				dest_colormap[starttranscolor + i] = 0x47; // Semidark
-			else if (i > 13)
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + i - 1); // Darkest
-			else
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + i);
-		}
-		break;
-
-	case SKINCOLOR_CRIMSON:
-		// 9 colors
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-		{
-			if (i/2 == 6)
-				dest_colormap[starttranscolor + i] = 0x47; // Semidark
-			else if (i/2 == 7)
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + i - 8); // Darkest
-			else
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + (i >> 1));
-		}
-		break;
-
-	case SKINCOLOR_ORANGE:
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-		{
-			if (i == 15)
-				dest_colormap[starttranscolor + i] = 0x2c; // Darkest
-			else
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + i);
-		}
-		break;
-
-	case SKINCOLOR_RUST:
-		// 10 colors
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-		{
-			if (i <= 11)
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + (i >> 1));
-			else if (i == 12)
-				dest_colormap[starttranscolor + i] = 0x2c;
-			else if (i <= 14)
-				dest_colormap[starttranscolor + i] = 0x2d;
-			else
-				dest_colormap[starttranscolor + i] = 0x48;
-		}
-		break;
-
-	case SKINCOLOR_GOLD:
-		// 10 colors
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-		{
-			if (i == 0)
-				dest_colormap[starttranscolor + i] = 0x50; // Lightest 1
-			else if (i == 1)
-				dest_colormap[starttranscolor + i] = 0x53; // Lightest 2
-			else if (i/2 == 7)
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + i - 8); //Darkest
-			else
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + (i >> 1) - 1);
-		}
-		break;
-
-	case SKINCOLOR_YELLOW:
-		// 10 colors
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-		{
-			if (i == 0)
-				dest_colormap[starttranscolor + i] = 0x53; // Lightest
-			else if (i == 15)
-				dest_colormap[starttranscolor + i] = 0xed; // Darkest
-			else
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + (i >> 1));
-		}
-		break;
-
-	case SKINCOLOR_TAN:
-		// 8 colors
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-		{
-			if (i/2 == 0)
-				dest_colormap[starttranscolor + i] = 0x51; // Lightest
-			else if (i/2 == 5)
-				dest_colormap[starttranscolor + i] = 0xf5; // Darkest 1
-			else if (i/2 == 6)
-				dest_colormap[starttranscolor + i] = 0xf9; // Darkest 2
-			else if (i/2 == 7)
-				dest_colormap[starttranscolor + i] = 0xed; // Darkest 3
-			else
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + (i >> 1) - 1);
-		}
-		break;
-
-	case SKINCOLOR_PERIDOT:
-		// 8 colors
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-		{
-			if (i/2 == 0)
-				dest_colormap[starttranscolor + i] = 0x58; // Lightest
-			else if (i/2 == 7)
-				dest_colormap[starttranscolor + i] = 0x77; // Darkest
-			else if (i/2 >= 5)
-				dest_colormap[starttranscolor + i] = (UINT8)(0x5e + (i >> 1) - 5); // Semidark
-			else
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + (i >> 1) - 1);
-		}
-		break;
-
-	case SKINCOLOR_AQUA:
-		// 10 colors
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-		{
-			if (i == 0)
-				dest_colormap[starttranscolor + i] = 0x78; // Lightest
-			else if (i >= 14)
-				dest_colormap[starttranscolor + i] = (UINT8)(0x76 + i - 14); // Darkest
-			else
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + (i >> 1) + 1);
-		}
-		break;
-
-	case SKINCOLOR_TEAL:
-		// 6 colors
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-		{
-			if (i <= 1)
-				dest_colormap[starttranscolor + i] = 0x78; // Lightest
-			else if (i >= 13)
-				dest_colormap[starttranscolor + i] = 0x8a; // Darkest
-			else
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + ((i - 1)/3));
-		}
-		break;
-
-	case SKINCOLOR_AZURE:
-		// 8 colors
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-		{
-			if (i <= 3)
-				dest_colormap[starttranscolor + i] = (UINT8)(0x90 + i/2); // Lightest
-			else
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + (i >> 1) - 2);
-		}
-		break;
-
-	case SKINCOLOR_BLUE:
-		// 16 colors
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-		{
-			if (i == 15)
-				dest_colormap[starttranscolor + i] = 0xfe; //Darkest 1
-			else if (i == 14)
-				dest_colormap[starttranscolor + i] = 0xfd; //Darkest 2
-			else
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + i);
-		}
-		break;
-
-	case SKINCOLOR_PASTEL:
-		// 10 colors
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-		{
-			if (i >= 12)
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + i - 7); // Darkest
-			else if (i <= 1)
-				dest_colormap[starttranscolor + i] = 0x90; // Lightest
-			else
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + (i >> 1) - 1);
-		}
-		break;
-
-	case SKINCOLOR_PURPLE:
-		// 10 colors
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-		{
-			if (i <= 3)
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + i); // Lightest
-			else
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + (i >> 1) + 2);
-		}
-		break;
-
-	case SKINCOLOR_MAGENTA:
-		// 9 colors
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-			if (i == 0)
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1]); // Lightest
-			else
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + (i >> 1) + 1);
-		break;
-
-	case SKINCOLOR_ROSY:
-		// 9 colors
-		for (i = 0; i < SKIN_RAMP_LENGTH; i++)
-		{
-			if (i == 0)
-				dest_colormap[starttranscolor + i] = 0xfc; // Lightest
-			else
-				dest_colormap[starttranscolor + i] = (UINT8)(skinbasecolors[color - 1] + ((i - 1) >> 1));
-		}
-		break;
-
-	// Super colors, from lightest to darkest!
-
-	// Super silvers.
-	case SKINCOLOR_SUPERSILVER1:
-		for (i = 0; i < 12; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)0;
-		for (; i < 14; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)1;
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(i-12);
-		break;
-
-	case SKINCOLOR_SUPERSILVER2:
-		for (i = 0; i < 3; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(i);
-		dest_colormap[starttranscolor + (i++)] = (UINT8)2;
-		for (; i < 8; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)3;
-		for (; i < 14; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)4;
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(i-9);
-		break;
-
-	case SKINCOLOR_SUPERSILVER3:
-		dest_colormap[starttranscolor] = (UINT8)1;
-		for (i = 1; i < 3; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)2;
-		for (; i < 6; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)3;
-		for (; i < 12; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)4;
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(5 + ((i-12)*2));
-		break;
-
-	case SKINCOLOR_SUPERSILVER4:
-		dest_colormap[starttranscolor] = (UINT8)2;
-		for (i = 1; i < 3; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)3;
-		for (; i < 9; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)4;
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(5 + ((i-9)*2));
-		break;
-
-	case SKINCOLOR_SUPERSILVER5:
-		for (i = 0; i < 2; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)3;
-		for (; i < 8; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)4;
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(5 + ((i-8)*2));
-		break;
-
-	// Super reds.
-	case SKINCOLOR_SUPERRED1:
-		for (i = 0; i < 10; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)0;
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(208 + ((i-10) >> 1));
-		break;
-
-	case SKINCOLOR_SUPERRED2:
-		for (i = 0; i < 3; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)0;
-		for (; i < 12; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(208 + ((i-3) / 3));
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(32 + ((i-12) >> 1));
-		break;
-
-	case SKINCOLOR_SUPERRED3:
-		for (i = 0; i < 2; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)0;
-		for (; i < 8; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(208 + ((i-2) >> 1));
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(32 + ((i-8) >> 1));
-		break;
-
-	case SKINCOLOR_SUPERRED4:
-		dest_colormap[starttranscolor] = (UINT8)0;
-		for (i = 1; i < 6; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(208 + (i >> 1));
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(32 + ((i-6) >> 1));
-		break;
-
-	case SKINCOLOR_SUPERRED5:
-		dest_colormap[starttranscolor] = (UINT8)208;
-		for (i = 1; i < 4; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(209 + (i >> 1));
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(32 + ((i-4) >> 1));
-		break;
-
-	// Super oranges.
-	case SKINCOLOR_SUPERORANGE1:
-		for (i = 0; i < 10; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)0;
-		dest_colormap[starttranscolor + (i++)] = (UINT8)208;
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(48 + (i-11));
-		break;
-
-	case SKINCOLOR_SUPERORANGE2:
-		for (i = 0; i < 4; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)0;
-		for (; i < 6; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)208;
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(48 + ((i-6) >> 1));
-		break;
-
-	case SKINCOLOR_SUPERORANGE3:
-		for (i = 0; i < 2; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)0;
-		for (; i < 4; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)208;
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(48 + ((i-4) >> 1));
-		break;
-
-	case SKINCOLOR_SUPERORANGE4:
-		dest_colormap[starttranscolor] = (UINT8)0;
-		dest_colormap[starttranscolor + 1] = (UINT8)208;
-		for (i = 2; i < 13; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(48 + (i-2));
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(68 + (i-13));
-		break;
-
-	case SKINCOLOR_SUPERORANGE5:
-		dest_colormap[starttranscolor] = (UINT8)208;
-		for (i = 1; i < 12; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(48 + (i-1));
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(68 + (i-12));
-		break;
-
-	// Super golds.
-	case SKINCOLOR_SUPERGOLD1:
-		for (i = 0; i < 10; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)0; // True white
-		for (; i < 12; i++) // White-yellow fade
-			dest_colormap[starttranscolor + i] = (UINT8)80;
-		for (; i < 15; i++) // White-yellow fade
-			dest_colormap[starttranscolor + i] = (UINT8)(81 + (i-12));
-		dest_colormap[starttranscolor + 15] = (UINT8)72;
-		break;
-
-	case SKINCOLOR_SUPERGOLD2:
-		dest_colormap[starttranscolor] = (UINT8)(0);
-		for (i = 1; i < 4; i++) // White-yellow fade
-			dest_colormap[starttranscolor + i] = (UINT8)(80 + (i-1));
-		for (; i < 6; i++) // Yellow
-			dest_colormap[starttranscolor + i] = (UINT8)83;
-		for (; i < 8; i++) // Yellow
-			dest_colormap[starttranscolor + i] = (UINT8)72;
-		for (; i < 14; i++) // Yellow
-			dest_colormap[starttranscolor + i] = (UINT8)73;
-		for (; i < 16; i++) // With a fine golden finish! :3
-			dest_colormap[starttranscolor + i] = (UINT8)(64 + (i-14));
-		break;
-
-	case SKINCOLOR_SUPERGOLD3:
-		for (i = 0; i < 2; i++) // White-yellow fade
-			dest_colormap[starttranscolor + i] = (UINT8)(81 + i);
-		for (; i < 4; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)83;
-		for (; i < 6; i++) // Yellow
-			dest_colormap[starttranscolor + i] = (UINT8)72;
-		for (; i < 12; i++) // Yellow
-			dest_colormap[starttranscolor + i] = (UINT8)73;
-		for (; i < 16; i++) // With a fine golden finish! :3
-			dest_colormap[starttranscolor + i] = (UINT8)(64 + (i-12));
-		break;
-
-	case SKINCOLOR_SUPERGOLD4: // "The SSNTails"
-		dest_colormap[starttranscolor] = (UINT8)83; // Golden shine
-		for (i = 1; i < 3; i++) // Yellow
-			dest_colormap[starttranscolor + i] = (UINT8)(72);
-		for (; i < 9; i++) // Yellow
-			dest_colormap[starttranscolor + i] = (UINT8)(73);
-		for (; i < 16; i++) // With a fine golden finish! :3
-			dest_colormap[starttranscolor + i] = (UINT8)(64 + (i-9));
-		break;
-
-	case SKINCOLOR_SUPERGOLD5: // Golden Delicious
-		for (i = 0; i < 2; i++) // Yellow
-			dest_colormap[starttranscolor + i] = (UINT8)(72);
-		for (; i < 8; i++) // Yellow
-			dest_colormap[starttranscolor + i] = (UINT8)(73);
-		for (; i < 16; i++) // With a fine golden finish! :3
-			dest_colormap[starttranscolor + i] = (UINT8)(64 + (i-8));
-		break;
-
-	// Super peridots. (nyeheheheh)
-	case SKINCOLOR_SUPERPERIDOT1:
-		for (i = 0; i < 10; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)0;
-		for (; i < 13; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)88;
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)188;
-		break;
-
-	case SKINCOLOR_SUPERPERIDOT2:
-		dest_colormap[starttranscolor] = (UINT8)(0);
-		for (i = 1; i < 4; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)88;
-		for (; i < 8; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)188;
-		for (; i < 14; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)189;
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)190;
-		break;
-
-	case SKINCOLOR_SUPERPERIDOT3:
-		for (i = 0; i < 2; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)88;
-		for (; i < 6; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)188;
-		for (; i < 12; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)189;
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(190 + ((i-12) >> 1));
-		break;
-
-	case SKINCOLOR_SUPERPERIDOT4:
-		dest_colormap[starttranscolor] = (UINT8)88;
-		for (i = 1; i < 3; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)188;
-		for (; i < 9; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)189;
-		for (; i < 13; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(190 + ((i-9) >> 1));
-		for (; i < 15; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)94;
-		dest_colormap[starttranscolor + i] = (UINT8)95;
-		break;
-
-	case SKINCOLOR_SUPERPERIDOT5:
-		for (i = 0; i < 2; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)188;
-		for (; i < 8; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)189;
-		for (; i < 12; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(190 + ((i-8) >> 1));
-		for (; i < 14; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)94;
-		dest_colormap[starttranscolor + (i++)] = (UINT8)95;
-		dest_colormap[starttranscolor + i] = (UINT8)119;
-		break;
-
-	// Super cyans.
-	case SKINCOLOR_SUPERCYAN1:
-		for (i = 0; i < 10; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)0;
-		for (; i < 12; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)128;
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(129 + (i-12));
-		break;
-
-	case SKINCOLOR_SUPERCYAN2:
-		dest_colormap[starttranscolor] = (UINT8)0;
-		for (i = 1; i < 4; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(128 + (i-1));
-		for (; i < 8; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(131 + ((i-4) >> 1));
-		for (; i < 14; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)133;
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)134;
-		break;
-
-	case SKINCOLOR_SUPERCYAN3:
-		for (i = 0; i < 2; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(129 + i);
-		for (; i < 6; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(131 + ((i-2) >> 1));
-		for (; i < 12; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)133;
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(134 + ((i-12) >> 1));
-		break;
-
-	case SKINCOLOR_SUPERCYAN4:
-		dest_colormap[starttranscolor] = (UINT8)131;
-		for (i = 1; i < 3; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)132;
-		for (; i < 9; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)133;
-		for (; i < 13; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(134 + ((i-9) >> 1));
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(136 + (i-13));
-		break;
-
-	case SKINCOLOR_SUPERCYAN5:
-		for (i = 0; i < 2; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)132;
-		for (; i < 8; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)133;
-		for (; i < 12; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(134 + ((i-8) >> 1));
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(136 + (i-12));
-		break;
-
-	// Super purples.
-	case SKINCOLOR_SUPERPURPLE1:
-		for (i = 0; i < 10; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)0;
-		for (; i < 12; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)144;
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(160 + (i-12));
-		break;
-
-	case SKINCOLOR_SUPERPURPLE2:
-		dest_colormap[starttranscolor] = (UINT8)0;
-		dest_colormap[starttranscolor + 1] = (UINT8)144;
-		for (i = 2; i < 4; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(160 + (i-2));
-		for (; i < 8; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(162 + ((i-4) >> 1));
-		for (; i < 14; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)164;
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)165;
-		break;
-
-	case SKINCOLOR_SUPERPURPLE3:
-		for (i = 0; i < 2; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(160 + i);
-		for (; i < 6; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(162 + ((i-2) >> 1));
-		for (; i < 12; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)164;
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(165 + ((i-12) >> 1));
-		break;
-
-	case SKINCOLOR_SUPERPURPLE4:
-		dest_colormap[starttranscolor] = (UINT8)162;
-		for (i = 1; i < 3; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)163;
-		for (; i < 9; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)164;
-		for (; i < 13; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(165 + ((i-9) >> 1));
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(167 + (i-13));
-		break;
-
-	case SKINCOLOR_SUPERPURPLE5:
-		for (i = 0; i < 2; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)163;
-		for (; i < 8; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)164;
-		for (; i < 12; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(165 + ((i-8) >> 1));
-		for (; i < 15; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(167 + (i-12));
-		dest_colormap[starttranscolor + i] = (UINT8)253;
-		break;
-
-	// Super rusts.
-	case SKINCOLOR_SUPERRUST1:
-		for (i = 0; i < 2; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)0;
-		for (; i < 5; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)208;
-		for (; i < 7; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)48;
-		for (; i < 10; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(49 + (i-7));
-		for (; i < 12; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(55 + ((i-10)*3));
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(68 + (i-11));
-		break;
-
-	case SKINCOLOR_SUPERRUST2:
-		for (i = 0; i < 4; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)48;
-		for (; i < 9; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(49 + (i-4));
-		for (; i < 11; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(56 + ((i-9)*2));
-		for (; i < 15; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(68 + (i-11));
-		dest_colormap[starttranscolor + i] = (UINT8)71;
-		break;
-
-	case SKINCOLOR_SUPERRUST3:
-		dest_colormap[starttranscolor] = (UINT8)49;
-		for (i = 1; i < 3; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)50;
-		for (; i < 5; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(51 + (i-3));
-		for (; i < 8; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(54 + (i-5));
-		dest_colormap[starttranscolor + (i++)] = (UINT8)58;
-		for (; i < 15; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(68 + ((i-7) >> 1));
-		dest_colormap[starttranscolor + i] = (UINT8)46;
-		break;
-
-	case SKINCOLOR_SUPERRUST4:
-		dest_colormap[starttranscolor] = (UINT8)83;
-		dest_colormap[starttranscolor + 1] = (UINT8)72;
-		for (i = 2; i < 6; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(64 + (i-2));
-		for (; i < 14; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(68 + ((i-6) >> 1));
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)46;
-		break;
-
-	case SKINCOLOR_SUPERRUST5:
-		for (i = 0; i < 3; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(64 + i);
-		for (; i < 7; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(67 + ((i-3) >> 1));
-		for (; i < 12; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(233 + (i-7));
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(238 + ((i-12) >> 1));
-		break;
-
-	// Super tans.
-	case SKINCOLOR_SUPERTAN1:
-		for (i = 0; i < 10; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)0;
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(80 + ((i-10) >> 1));
-		break;
-
-	case SKINCOLOR_SUPERTAN2:
-		dest_colormap[starttranscolor] = (UINT8)0;
-		for (i = 1; i < 7; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(80 + ((i-1) >> 1));
-		dest_colormap[starttranscolor + (i++)] = (UINT8)82;
-		for (; i < 12; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)84;
-		for (; i < 15; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(85 + (i-12));
-		dest_colormap[starttranscolor + i] = (UINT8)245;
-		break;
-
-	case SKINCOLOR_SUPERTAN3:
-		dest_colormap[starttranscolor] = (UINT8)80;
-		for (i = 1; i < 5; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(81 + ((i-1) >> 1));
-		dest_colormap[starttranscolor + (i++)] = (UINT8)82;
-		for (; i < 10; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)84;
-		for (; i < 13; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(85 + (i-10));
-		for (; i < 16; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(245 + ((i-13)*2));
-		break;
-
-	case SKINCOLOR_SUPERTAN4:
-		dest_colormap[starttranscolor] = (UINT8)81;
-		for (i = 1; i < 5; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)82;
-		for (; i < 8; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)84;
-		for (; i < 11; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(85 + (i-8));
-		for (; i < 15; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(245 + ((i-11)*2));
-		dest_colormap[starttranscolor + i] = (UINT8)237;
-		break;
-
-	case SKINCOLOR_SUPERTAN5:
-		for (i = 0; i < 2; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)82;
-		for (; i < 5; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)84;
-		for (; i < 8; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(85 + (i-5));
-		for (; i < 12; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(245 + (i-8));
-		for (; i < 15; i++)
-			dest_colormap[starttranscolor + i] = (UINT8)(237 + (i-12));
-		dest_colormap[starttranscolor + i] = (UINT8)239;
-		break;
-
-	default:
-		I_Error("Invalid skin color #%hu.", (UINT16)color);
-		break;
+		for (i = (UINT8)i; i < NUM_PALETTE_ENTRIES; i++)
+			dest_colormap[i] = (UINT8)i;
+		skinramplength = 16;
 	}
+	else
+		skinramplength = i - NUM_PALETTE_ENTRIES;
+
+	// Build the translated ramp
+	for (i = 0; i < skinramplength; i++)
+		dest_colormap[starttranscolor + i] = (UINT8)Color_Index[color-1][i];
 }
 
 
@@ -1155,7 +580,7 @@ UINT8 R_GetColorByName(const char *name)
 	for (color = 1; color < MAXSKINCOLORS; color++)
 		if (!stricmp(Color_Names[color], name))
 			return color;
-	return 0;
+	return SKINCOLOR_GREEN;
 }
 
 UINT8 R_GetSuperColorByName(const char *name)
@@ -1166,7 +591,7 @@ UINT8 R_GetSuperColorByName(const char *name)
 	for (color = 0; color < NUMSUPERCOLORS; color++)
 		if (!stricmp(Color_Names[color + MAXSKINCOLORS], name))
 			return ((color*5) + MAXSKINCOLORS);
-	return 0;
+	return SKINCOLOR_SUPERGOLD1;
 }
 
 // ==========================================================================
diff --git a/src/r_main.c b/src/r_main.c
index f970762f4efa60b40668aaa5c0e80b00a0eee67b..c998a7d93e3eef39addb34b9e39b5a5f70880c75 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -60,7 +60,6 @@ fixed_t projectiony; // aspect ratio
 // just for profiling purposes
 size_t framecount;
 
-size_t sscount;
 size_t loopcount;
 
 fixed_t viewx, viewy, viewz;
@@ -491,9 +490,6 @@ static void R_InitTextureMapping(void)
 	// Take out the fencepost cases from viewangletox.
 	for (i = 0; i < FINEANGLES/2; i++)
 	{
-		t = FixedMul(FINETANGENT(i), focallength);
-		t = centerx - t;
-
 		if (viewangletox[i] == -1)
 			viewangletox[i] = 0;
 		else if (viewangletox[i] == viewwidth+1)
@@ -802,157 +798,77 @@ void R_SkyboxFrame(player_t *player)
 
 	viewx = viewmobj->x;
 	viewy = viewmobj->y;
-	viewz = 0;
-	if (viewmobj->spawnpoint)
-		viewz = ((fixed_t)viewmobj->spawnpoint->angle)<<FRACBITS;
-
-	viewx += quake.x;
-	viewy += quake.y;
-	viewz += quake.z;
+	viewz = viewmobj->z; // 26/04/17: use actual Z position instead of spawnpoint angle!
 
 	if (mapheaderinfo[gamemap-1])
 	{
 		mapheader_t *mh = mapheaderinfo[gamemap-1];
-		if (player->awayviewtics)
+		vector3_t campos = {0,0,0}; // Position of player's actual view point
+
+		if (player->awayviewtics) {
+			campos.x = player->awayviewmobj->x;
+			campos.y = player->awayviewmobj->y;
+			campos.z = player->awayviewmobj->z + 20*FRACUNIT;
+		} else if (thiscam->chase) {
+			campos.x = thiscam->x;
+			campos.y = thiscam->y;
+			campos.z = thiscam->z + (thiscam->height>>1);
+		} else {
+			campos.x = player->mo->x;
+			campos.y = player->mo->y;
+			campos.z = player->viewz;
+		}
+
+		// Earthquake effects should be scaled in the skybox
+		// (if an axis isn't used, the skybox won't shake in that direction)
+		campos.x += quake.x;
+		campos.y += quake.y;
+		campos.z += quake.z;
+
+		if (skyboxmo[1]) // Is there a viewpoint?
 		{
-			if (skyboxmo[1])
+			fixed_t x = 0, y = 0;
+			if (mh->skybox_scalex > 0)
+				x = (campos.x - skyboxmo[1]->x) / mh->skybox_scalex;
+			else if (mh->skybox_scalex < 0)
+				x = (campos.x - skyboxmo[1]->x) * -mh->skybox_scalex;
+
+			if (mh->skybox_scaley > 0)
+				y = (campos.y - skyboxmo[1]->y) / mh->skybox_scaley;
+			else if (mh->skybox_scaley < 0)
+				y = (campos.y - skyboxmo[1]->y) * -mh->skybox_scaley;
+
+			if (viewmobj->angle == 0)
 			{
-				fixed_t x = 0, y = 0;
-				if (mh->skybox_scalex > 0)
-					x = (player->awayviewmobj->x - skyboxmo[1]->x) / mh->skybox_scalex;
-				else if (mh->skybox_scalex < 0)
-					x = (player->awayviewmobj->x - skyboxmo[1]->x) * -mh->skybox_scalex;
-
-				if (mh->skybox_scaley > 0)
-					y = (player->awayviewmobj->y - skyboxmo[1]->y) / mh->skybox_scaley;
-				else if (mh->skybox_scaley < 0)
-					y = (player->awayviewmobj->y - skyboxmo[1]->y) * -mh->skybox_scaley;
-
-				if (viewmobj->angle == 0)
-				{
-					viewx += x;
-					viewy += y;
-				}
-				else if (viewmobj->angle == ANGLE_90)
-				{
-					viewx -= y;
-					viewy += x;
-				}
-				else if (viewmobj->angle == ANGLE_180)
-				{
-					viewx -= x;
-					viewy -= y;
-				}
-				else if (viewmobj->angle == ANGLE_270)
-				{
-					viewx += y;
-					viewy -= x;
-				}
-				else
-				{
-					angle_t ang = viewmobj->angle>>ANGLETOFINESHIFT;
-					viewx += FixedMul(x,FINECOSINE(ang)) - FixedMul(y,  FINESINE(ang));
-					viewy += FixedMul(x,  FINESINE(ang)) + FixedMul(y,FINECOSINE(ang));
-				}
+				viewx += x;
+				viewy += y;
 			}
-			if (mh->skybox_scalez > 0)
-				viewz += (player->awayviewmobj->z + 20*FRACUNIT) / mh->skybox_scalez;
-			else if (mh->skybox_scalez < 0)
-				viewz += (player->awayviewmobj->z + 20*FRACUNIT) * -mh->skybox_scalez;
-		}
-		else if (thiscam->chase)
-		{
-			if (skyboxmo[1])
+			else if (viewmobj->angle == ANGLE_90)
 			{
-				fixed_t x = 0, y = 0;
-				if (mh->skybox_scalex > 0)
-					x = (thiscam->x - skyboxmo[1]->x) / mh->skybox_scalex;
-				else if (mh->skybox_scalex < 0)
-					x = (thiscam->x - skyboxmo[1]->x) * -mh->skybox_scalex;
-
-				if (mh->skybox_scaley > 0)
-					y = (thiscam->y - skyboxmo[1]->y) / mh->skybox_scaley;
-				else if (mh->skybox_scaley < 0)
-					y = (thiscam->y - skyboxmo[1]->y) * -mh->skybox_scaley;
-
-				if (viewmobj->angle == 0)
-				{
-					viewx += x;
-					viewy += y;
-				}
-				else if (viewmobj->angle == ANGLE_90)
-				{
-					viewx -= y;
-					viewy += x;
-				}
-				else if (viewmobj->angle == ANGLE_180)
-				{
-					viewx -= x;
-					viewy -= y;
-				}
-				else if (viewmobj->angle == ANGLE_270)
-				{
-					viewx += y;
-					viewy -= x;
-				}
-				else
-				{
-					angle_t ang = viewmobj->angle>>ANGLETOFINESHIFT;
-					viewx += FixedMul(x,FINECOSINE(ang)) - FixedMul(y,  FINESINE(ang));
-					viewy += FixedMul(x,  FINESINE(ang)) + FixedMul(y,FINECOSINE(ang));
-				}
+				viewx -= y;
+				viewy += x;
 			}
-			if (mh->skybox_scalez > 0)
-				viewz += (thiscam->z + (thiscam->height>>1)) / mh->skybox_scalez;
-			else if (mh->skybox_scalez < 0)
-				viewz += (thiscam->z + (thiscam->height>>1)) * -mh->skybox_scalez;
-		}
-		else
-		{
-			if (skyboxmo[1])
+			else if (viewmobj->angle == ANGLE_180)
+			{
+				viewx -= x;
+				viewy -= y;
+			}
+			else if (viewmobj->angle == ANGLE_270)
 			{
-				fixed_t x = 0, y = 0;
-				if (mh->skybox_scalex > 0)
-					x = (player->mo->x - skyboxmo[1]->x) / mh->skybox_scalex;
-				else if (mh->skybox_scalex < 0)
-					x = (player->mo->x - skyboxmo[1]->x) * -mh->skybox_scalex;
-				if (mh->skybox_scaley > 0)
-					y = (player->mo->y - skyboxmo[1]->y) / mh->skybox_scaley;
-				else if (mh->skybox_scaley < 0)
-					y = (player->mo->y - skyboxmo[1]->y) * -mh->skybox_scaley;
-
-				if (viewmobj->angle == 0)
-				{
-					viewx += x;
-					viewy += y;
-				}
-				else if (viewmobj->angle == ANGLE_90)
-				{
-					viewx -= y;
-					viewy += x;
-				}
-				else if (viewmobj->angle == ANGLE_180)
-				{
-					viewx -= x;
-					viewy -= y;
-				}
-				else if (viewmobj->angle == ANGLE_270)
-				{
-					viewx += y;
-					viewy -= x;
-				}
-				else
-				{
-					angle_t ang = viewmobj->angle>>ANGLETOFINESHIFT;
-					viewx += FixedMul(x,FINECOSINE(ang)) - FixedMul(y,  FINESINE(ang));
-					viewy += FixedMul(x,  FINESINE(ang)) + FixedMul(y,FINECOSINE(ang));
-				}
+				viewx += y;
+				viewy -= x;
+			}
+			else
+			{
+				angle_t ang = viewmobj->angle>>ANGLETOFINESHIFT;
+				viewx += FixedMul(x,FINECOSINE(ang)) - FixedMul(y,  FINESINE(ang));
+				viewy += FixedMul(x,  FINESINE(ang)) + FixedMul(y,FINECOSINE(ang));
 			}
-			if (mh->skybox_scalez > 0)
-				viewz += player->viewz / mh->skybox_scalez;
-			else if (mh->skybox_scalez < 0)
-				viewz += player->viewz * -mh->skybox_scalez;
 		}
+		if (mh->skybox_scalez > 0)
+			viewz += campos.z / mh->skybox_scalez;
+		else if (mh->skybox_scalez < 0)
+			viewz += campos.z * -mh->skybox_scalez;
 	}
 
 	if (viewmobj->subsector)
@@ -963,8 +879,6 @@ void R_SkyboxFrame(player_t *player)
 	viewsin = FINESINE(viewangle>>ANGLETOFINESHIFT);
 	viewcos = FINECOSINE(viewangle>>ANGLETOFINESHIFT);
 
-	sscount = 0;
-
 	// recalc necessary stuff for mouseaiming
 	// slopes are already calculated for the full possible view (which is 4*viewheight).
 
@@ -1088,8 +1002,6 @@ void R_SetupFrame(player_t *player, boolean skybox)
 	viewsin = FINESINE(viewangle>>ANGLETOFINESHIFT);
 	viewcos = FINECOSINE(viewangle>>ANGLETOFINESHIFT);
 
-	sscount = 0;
-
 	// recalc necessary stuff for mouseaiming
 	// slopes are already calculated for the full possible view (which is 4*viewheight).
 
diff --git a/src/r_plane.c b/src/r_plane.c
index 11dd79d41e7a8aa4ac4862411feb8655cf168505..670152eda24685b6a05529e46b20430b02e50fd8 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -445,18 +445,36 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel,
 #ifdef ESLOPE
 	if (slope); else // Don't mess with this right now if a slope is involved
 #endif
-	if (plangle != 0)
-	{
-		// Add the view offset, rotated by the plane angle.
-		angle_t angle = plangle>>ANGLETOFINESHIFT;
-		xoff += FixedMul(viewx,FINECOSINE(angle))-FixedMul(viewy,FINESINE(angle));
-		yoff += -FixedMul(viewx,FINESINE(angle))-FixedMul(viewy,FINECOSINE(angle));
-	}
-	else
 	{
 		xoff += viewx;
 		yoff -= viewy;
+		if (plangle != 0)
+		{
+			// Add the view offset, rotated by the plane angle.
+			fixed_t cosinecomponent = FINECOSINE(plangle>>ANGLETOFINESHIFT);
+			fixed_t sinecomponent = FINESINE(plangle>>ANGLETOFINESHIFT);
+			fixed_t oldxoff = xoff;
+			xoff = FixedMul(xoff,cosinecomponent)+FixedMul(yoff,sinecomponent);
+			yoff = -FixedMul(oldxoff,sinecomponent)+FixedMul(yoff,cosinecomponent);
+		}
+	}
+
+#ifdef POLYOBJECTS_PLANES
+	if (polyobj)
+	{
+		if (polyobj->angle != 0)
+		{
+			angle_t fineshift = polyobj->angle >> ANGLETOFINESHIFT;
+			xoff -= FixedMul(FINECOSINE(fineshift), polyobj->centerPt.x)+FixedMul(FINESINE(fineshift), polyobj->centerPt.y);
+			yoff -= FixedMul(FINESINE(fineshift), polyobj->centerPt.x)-FixedMul(FINECOSINE(fineshift), polyobj->centerPt.y);
+		}
+		else
+		{
+			xoff -= polyobj->centerPt.x;
+			yoff += polyobj->centerPt.y;
+		}
 	}
+#endif
 
 	// This appears to fix the Nimbus Ruins sky bug.
 	if (picnum == skyflatnum && pfloor)
@@ -483,6 +501,7 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel,
 			&& !pfloor && !check->ffloor
 			&& check->viewx == viewx && check->viewy == viewy && check->viewz == viewz
 			&& check->viewangle == viewangle
+			&& check->plangle == plangle
 #ifdef ESLOPE
 			&& check->slope == slope
 #endif
@@ -951,19 +970,57 @@ void R_DrawSinglePlane(visplane_t *pl)
 		floatv3_t p, m, n;
 		float ang;
 		float vx, vy, vz;
-		float fudge;
 		// compiler complains when P_GetZAt is used in FLOAT_TO_FIXED directly
 		// use this as a temp var to store P_GetZAt's return value each time
 		fixed_t temp;
+		// Okay, look, don't ask me why this works, but without this setup there's a disgusting-looking misalignment with the textures. -Red
+		const float fudge = ((1<<nflatshiftup)+1.0f)/(1<<nflatshiftup);
 
-		xoffs &= ((1 << (32-nflatshiftup))-1);
-		yoffs &= ((1 << (32-nflatshiftup))-1);
+		angle_t hack = (pl->plangle & (ANGLE_90-1));
 
-		xoffs -= (pl->slope->o.x + (1 << (31-nflatshiftup))) & ~((1 << (32-nflatshiftup))-1);
-		yoffs += (pl->slope->o.y + (1 << (31-nflatshiftup))) & ~((1 << (32-nflatshiftup))-1);
+		yoffs *= 1;
 
-		// Okay, look, don't ask me why this works, but without this setup there's a disgusting-looking misalignment with the textures. -Red
-		fudge = ((1<<nflatshiftup)+1.0f)/(1<<nflatshiftup);
+		if (hack)
+		{
+			/*
+			Essentially: We can't & the components along the regular axes when the plane is rotated.
+			This is because the distance on each regular axis in order to loop is different.
+			We rotate them, & the components, add them together, & them again, and then rotate them back.
+			These three seperate & operations are done per axis in order to prevent overflows.
+			toast 10/04/17
+			*/
+			const fixed_t cosinecomponent = FINECOSINE(hack>>ANGLETOFINESHIFT);
+			const fixed_t sinecomponent = FINESINE(hack>>ANGLETOFINESHIFT);
+
+			const fixed_t modmask = ((1 << (32-nflatshiftup)) - 1);
+
+			fixed_t ox = (FixedMul(pl->slope->o.x,cosinecomponent) & modmask) - (FixedMul(pl->slope->o.y,sinecomponent) & modmask);
+			fixed_t oy = (-FixedMul(pl->slope->o.x,sinecomponent) & modmask) - (FixedMul(pl->slope->o.y,cosinecomponent) & modmask);
+
+			temp = ox & modmask;
+			oy &= modmask;
+			ox = FixedMul(temp,cosinecomponent)+FixedMul(oy,-sinecomponent); // negative sine for opposite direction
+			oy = -FixedMul(temp,-sinecomponent)+FixedMul(oy,cosinecomponent);
+
+			temp = xoffs;
+			xoffs = (FixedMul(temp,cosinecomponent) & modmask) + (FixedMul(yoffs,sinecomponent) & modmask);
+			yoffs = (-FixedMul(temp,sinecomponent) & modmask) + (FixedMul(yoffs,cosinecomponent) & modmask);
+
+			temp = xoffs & modmask;
+			yoffs &= modmask;
+			xoffs = FixedMul(temp,cosinecomponent)+FixedMul(yoffs,-sinecomponent); // ditto
+			yoffs = -FixedMul(temp,-sinecomponent)+FixedMul(yoffs,cosinecomponent);
+
+			xoffs -= (pl->slope->o.x - ox);
+			yoffs += (pl->slope->o.y + oy);
+		}
+		else
+		{
+			xoffs &= ((1 << (32-nflatshiftup))-1);
+			yoffs &= ((1 << (32-nflatshiftup))-1);
+			xoffs -= (pl->slope->o.x + (1 << (31-nflatshiftup))) & ~((1 << (32-nflatshiftup))-1);
+			yoffs += (pl->slope->o.y + (1 << (31-nflatshiftup))) & ~((1 << (32-nflatshiftup))-1);
+		}
 
 		xoffs = (fixed_t)(xoffs*fudge);
 		yoffs = (fixed_t)(yoffs/fudge);
diff --git a/src/r_state.h b/src/r_state.h
index 49d0457b262c767621dde508f85f3a481a9feb3d..ac3e1fa42576a050c4edfdf177b560defc08eead 100644
--- a/src/r_state.h
+++ b/src/r_state.h
@@ -108,7 +108,4 @@ extern angle_t rw_normalangle;
 // angle to line origin
 extern angle_t rw_angle1;
 
-// Segs count?
-extern size_t sscount;
-
 #endif
diff --git a/src/r_things.c b/src/r_things.c
index 38e35afe058c0f9c67c8f922fa720d870e2119b4..b196156b318933cf6a3b4da3e8b9fd0ff13acc0e 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -20,6 +20,7 @@
 #include "z_zone.h"
 #include "m_menu.h" // character select
 #include "m_misc.h"
+#include "info.h" // spr2names
 #include "i_video.h" // rendermode
 #include "r_things.h"
 #include "r_plane.h"
@@ -30,6 +31,7 @@
 #include "d_netfil.h" // blargh. for nameonly().
 #include "m_cheat.h" // objectplace
 #include "m_cond.h"
+#include "fastcmp.h"
 #ifdef HWRENDER
 #include "hardware/hw_md2.h"
 #endif
@@ -429,117 +431,6 @@ void R_AddSpriteDefs(UINT16 wadnum)
 	CONS_Printf(M_GetText("%s added %d frames in %s sprites\n"), wadname, end-start, sizeu1(addsprites));
 }
 
-#ifdef DELFILE
-static void R_RemoveSpriteLump(UINT16 wad,            // graphics patch
-                               UINT16 lump,
-                               size_t lumpid,      // identifier
-                               UINT8 frame,
-                               UINT8 rotation,
-                               UINT8 flipped)
-{
-	(void)wad; /// \todo: how do I remove sprites?
-	(void)lump;
-	(void)lumpid;
-	(void)frame;
-	(void)rotation;
-	(void)flipped;
-}
-
-static boolean R_DelSingleSpriteDef(const char *sprname, spritedef_t *spritedef, UINT16 wadnum, UINT16 startlump, UINT16 endlump)
-{
-	UINT16 l;
-	UINT8 frame;
-	UINT8 rotation;
-	lumpinfo_t *lumpinfo;
-
-	maxframe = (size_t)-1;
-
-	// scan the lumps,
-	//  filling in the frames for whatever is found
-	lumpinfo = wadfiles[wadnum]->lumpinfo;
-	if (endlump > wadfiles[wadnum]->numlumps)
-		endlump = wadfiles[wadnum]->numlumps;
-
-	for (l = startlump; l < endlump; l++)
-	{
-		if (memcmp(lumpinfo[l].name,sprname,4)==0)
-		{
-			frame = (UINT8)(lumpinfo[l].name[4] - 'A');
-			rotation = (UINT8)(lumpinfo[l].name[5] - '0');
-
-			// skip NULL sprites from very old dmadds pwads
-			if (W_LumpLengthPwad(wadnum,l)<=8)
-				continue;
-
-			//----------------------------------------------------
-
-			R_RemoveSpriteLump(wadnum, l, numspritelumps, frame, rotation, 0);
-
-			if (lumpinfo[l].name[6])
-			{
-				frame = (UINT8)(lumpinfo[l].name[6] - 'A');
-				rotation = (UINT8)(lumpinfo[l].name[7] - '0');
-				R_RemoveSpriteLump(wadnum, l, numspritelumps, frame, rotation, 1);
-			}
-		}
-	}
-
-	if (maxframe == (size_t)-1)
-		return false;
-
-	spritedef->numframes = 0;
-	Z_Free(spritedef->spriteframes);
-	spritedef->spriteframes = NULL;
-	return true;
-}
-
-void R_DelSpriteDefs(UINT16 wadnum)
-{
-	size_t i, delsprites = 0;
-	UINT16 start, end;
-
-	// find the sprites section in this pwad
-	// we need at least the S_END
-	// (not really, but for speedup)
-
-	start = W_CheckNumForNamePwad("S_START", wadnum, 0);
-	if (start == INT16_MAX)
-		start = W_CheckNumForNamePwad("SS_START", wadnum, 0); //deutex compatib.
-	if (start == INT16_MAX)
-		start = 0; //let say S_START is lump 0
-	else
-		start++;   // just after S_START
-
-	end = W_CheckNumForNamePwad("S_END",wadnum,start);
-	if (end == INT16_MAX)
-		end = W_CheckNumForNamePwad("SS_END",wadnum,start);     //deutex compatib.
-	if (end == INT16_MAX)
-	{
-		CONS_Debug(DBG_SETUP, "no sprites in pwad %d\n", wadnum);
-		return;
-		//I_Error("R_DelSpriteDefs: S_END, or SS_END missing for sprites "
-		//         "in pwad %d\n",wadnum);
-	}
-
-	//
-	// scan through lumps, for each sprite, find all the sprite frames
-	//
-	for (i = 0; i < numsprites; i++)
-	{
-		spritename = sprnames[i];
-
-		if (R_DelSingleSpriteDef(spritename, &sprites[i], wadnum, start, end))
-		{
-			// if a new sprite was removed (not just replaced)
-			delsprites++;
-			CONS_Debug(DBG_SETUP, "sprite %s set in pwad %d\n", spritename, wadnum);
-		}
-	}
-
-	CONS_Printf(M_GetText("%s sprites removed from file %s\n"), sizeu1(delsprites), wadfiles[wadnum]->filename);
-}
-#endif
-
 //
 // GAME FUNCTIONS
 //
@@ -817,7 +708,7 @@ static void R_DrawVisSprite(vissprite_t *vis)
 
 	colfunc = basecolfunc; // hack: this isn't resetting properly somewhere.
 	dc_colormap = vis->colormap;
-	if ((vis->mobj->flags & MF_BOSS) && (vis->mobj->flags2 & MF2_FRET) && (leveltime & 1)) // Bosses "flash"
+	if (!(vis->cut & SC_PRECIP) && (vis->mobj->flags & MF_BOSS) && (vis->mobj->flags2 & MF2_FRET) && (leveltime & 1)) // Bosses "flash"
 	{
 		// translate certain pixels to white
 		colfunc = transcolfunc;
@@ -832,7 +723,7 @@ static void R_DrawVisSprite(vissprite_t *vis)
 	{
 		colfunc = transtransfunc;
 		dc_transmap = vis->transmap;
-		if (vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // MT_GHOST LOOKS LIKE A PLAYER SO USE THE PLAYER TRANSLATION TABLES. >_>
+		if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // MT_GHOST LOOKS LIKE A PLAYER SO USE THE PLAYER TRANSLATION TABLES. >_>
 		{
 			size_t skinnum = (skin_t*)vis->mobj->skin-skins;
 			dc_translation = R_GetTranslationColormap((INT32)skinnum, vis->mobj->color, GTC_CACHE);
@@ -851,7 +742,7 @@ static void R_DrawVisSprite(vissprite_t *vis)
 		colfunc = transcolfunc;
 
 		// New colormap stuff for skins Tails 06-07-2002
-		if (vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // This thing is a player!
+		if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // This thing is a player!
 		{
 			size_t skinnum = (skin_t*)vis->mobj->skin-skins;
 			dc_translation = R_GetTranslationColormap((INT32)skinnum, vis->mobj->color, GTC_CACHE);
@@ -881,18 +772,18 @@ static void R_DrawVisSprite(vissprite_t *vis)
 	frac = vis->startfrac;
 	windowtop = windowbottom = sprbotscreen = INT32_MAX;
 
-	if (vis->mobj->skin && ((skin_t *)vis->mobj->skin)->flags & SF_HIRES)
+	if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && ((skin_t *)vis->mobj->skin)->flags & SF_HIRES)
 		this_scale = FixedMul(this_scale, ((skin_t *)vis->mobj->skin)->highresscale);
 	if (this_scale <= 0)
 		this_scale = 1;
 	if (this_scale != FRACUNIT)
 	{
-		if (!vis->isScaled)
+		if (!(vis->cut & SC_ISSCALED))
 		{
 			vis->scale = FixedMul(vis->scale, this_scale);
 			vis->scalestep = FixedMul(vis->scalestep, this_scale);
 			vis->xiscale = FixedDiv(vis->xiscale,this_scale);
-			vis->isScaled = true;
+			vis->cut |= SC_ISSCALED;
 		}
 		dc_texturemid = FixedDiv(dc_texturemid,this_scale);
 	}
@@ -924,7 +815,7 @@ static void R_DrawVisSprite(vissprite_t *vis)
 		texturecolumn = frac>>FRACBITS;
 
 		if (texturecolumn < 0 || texturecolumn >= SHORT(patch->width))
-			I_Error("R_DrawSpriteRange: bad texturecolumn");
+			I_Error("R_DrawSpriteRange: bad texturecolumn at %d from end", vis->x2 - dc_x);
 		column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[texturecolumn]));
 #else
 		column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[frac>>FRACBITS]));
@@ -934,7 +825,7 @@ static void R_DrawVisSprite(vissprite_t *vis)
 			sprtopscreen = (centeryfrac - FixedMul(dc_texturemid, spryscale));
 			dc_iscale = (0xffffffffu / (unsigned)spryscale);
 		}
-		if (vis->vflip)
+		if (vis->cut & SC_VFLIP)
 			R_DrawFlippedMaskedColumn(column, patch->height);
 		else
 			R_DrawMaskedColumn(column);
@@ -1013,7 +904,7 @@ static void R_DrawPrecipitationVisSprite(vissprite_t *vis)
 //
 // R_SplitSprite
 // runs through a sector's lightlist and
-static void R_SplitSprite(vissprite_t *sprite, mobj_t *thing)
+static void R_SplitSprite(vissprite_t *sprite)
 {
 	INT32 i, lightnum, lindex;
 	INT16 cutfrac;
@@ -1049,6 +940,8 @@ static void R_SplitSprite(vissprite_t *sprite, mobj_t *thing)
 		// adjust the heights.
 		newsprite = M_Memcpy(R_NewVisSprite(), sprite, sizeof (vissprite_t));
 
+		newsprite->cut |= (sprite->cut & SC_FLAGMASK);
+
 		sprite->cut |= SC_BOTTOM;
 		sprite->gz = testheight;
 
@@ -1081,15 +974,7 @@ static void R_SplitSprite(vissprite_t *sprite, mobj_t *thing)
 
 			newsprite->extra_colormap = sector->lightlist[i].extra_colormap;
 
-/*
-			if (thing->frame & FF_TRANSMASK)
-				;
-			else if (thing->flags2 & MF2_SHADOW)
-				;
-			else
-*/
-			if (!((thing->frame & (FF_FULLBRIGHT|FF_TRANSMASK) || thing->flags2 & MF2_SHADOW)
-				&& (!newsprite->extra_colormap || !newsprite->extra_colormap->fog)))
+			if (!((newsprite->cut & SC_FULLBRIGHT) && (!newsprite->extra_colormap || !newsprite->extra_colormap->fog)))
 			{
 				lindex = FixedMul(sprite->xscale, FixedDiv(640, vid.width))>>(LIGHTSCALESHIFT);
 
@@ -1109,6 +994,7 @@ static void R_SplitSprite(vissprite_t *sprite, mobj_t *thing)
 //
 static void R_ProjectSprite(mobj_t *thing)
 {
+	mobj_t *oldthing = thing;
 	fixed_t tr_x, tr_y;
 	fixed_t gxt, gyt;
 	fixed_t tx, tz;
@@ -1128,6 +1014,8 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	vissprite_t *vis;
 
+	spritecut_e cut = SC_NONE;
+
 	angle_t ang = 0; // compiler complaints
 	fixed_t iscale;
 	fixed_t scalestep;
@@ -1265,7 +1153,7 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	offset2 = FixedMul(spritecachedinfo[lump].width, this_scale);
 	tx += FixedMul(offset2, ang_scale);
-	x2 = ((centerxfrac + FixedMul (tx,xscale)) >>FRACBITS) - 1;
+	x2 = ((centerxfrac + FixedMul (tx,xscale)) >> FRACBITS) - (papersprite ? 2 : 1);
 
 	// off the left side
 	if (x2 < 0)
@@ -1326,24 +1214,14 @@ static void R_ProjectSprite(mobj_t *thing)
 	if ((thing->flags2 & MF2_LINKDRAW) && thing->tracer) // toast 16/09/16 (SYMMETRY)
 	{
 		fixed_t linkscale;
-#if 0 // support for chains of linkdraw - probably not network safe to modify mobjs during rendering
-		mobj_t *link, *link2;
-
-		for (link = thing->tracer; (link->tracer && (link->flags2 & MF2_LINKDRAW)); link = link->tracer)
-			link->flags2 &= ~MF2_LINKDRAW; // to prevent infinite loops, otherwise would just be a ;
 
-		for (link2 = thing->tracer; (link2->tracer && (link2 != link)); link2 = link2->tracer)
-			link->flags2 |= MF2_LINKDRAW; // only needed for correction of the above
+		thing = thing->tracer;
 
-		if (link->flags2 & MF2_LINKDRAW)
-			link->flags2 &= ~MF2_LINKDRAW; // let's try and make sure this doesn't happen again...
+		if (thing->sprite == SPR_NULL || thing->flags2 & MF2_DONTDRAW)
+			return;
 
-		tr_x = link->x - viewx;
-		tr_y = link->y - viewy;
-#else
-		tr_x = thing->tracer->x - viewx;
-		tr_y = thing->tracer->y - viewy;
-#endif
+		tr_x = thing->x - viewx;
+		tr_y = thing->y - viewy;
 		gxt = FixedMul(tr_x, viewcos);
 		gyt = -FixedMul(tr_y, viewsin);
 		tz = gxt-gyt;
@@ -1356,6 +1234,7 @@ static void R_ProjectSprite(mobj_t *thing)
 			dispoffset *= -1; // if it's physically behind, make sure it's ordered behind (if dispoffset > 0)
 
 		sortscale = linkscale; // now make sure it's linked
+		cut = SC_LINKDRAW;
 	}
 
 	// PORTAL SPRITE CLIPPING
@@ -1374,12 +1253,12 @@ static void R_ProjectSprite(mobj_t *thing)
 		// When vertical flipped, draw sprites from the top down, at least as far as offsets are concerned.
 		// sprite height - sprite topoffset is the proper inverse of the vertical offset, of course.
 		// remember gz and gzt should be seperated by sprite height, not thing height - thing height can be shorter than the sprite itself sometimes!
-		gz = thing->z + thing->height - FixedMul(spritecachedinfo[lump].topoffset, this_scale);
+		gz = oldthing->z + oldthing->height - FixedMul(spritecachedinfo[lump].topoffset, this_scale);
 		gzt = gz + FixedMul(spritecachedinfo[lump].height, this_scale);
 	}
 	else
 	{
-		gzt = thing->z + FixedMul(spritecachedinfo[lump].topoffset, this_scale);
+		gzt = oldthing->z + FixedMul(spritecachedinfo[lump].topoffset, this_scale);
 		gz = gzt - FixedMul(spritecachedinfo[lump].height, this_scale);
 	}
 
@@ -1469,7 +1348,7 @@ static void R_ProjectSprite(mobj_t *thing)
 	vis->sector = thing->subsector->sector;
 	vis->szt = (INT16)((centeryfrac - FixedMul(vis->gzt - viewz, sortscale))>>FRACBITS);
 	vis->sz = (INT16)((centeryfrac - FixedMul(vis->gz - viewz, sortscale))>>FRACBITS);
-	vis->cut = SC_NONE;
+	vis->cut = cut;
 	if (thing->subsector->sector->numlights)
 		vis->extra_colormap = thing->subsector->sector->lightlist[light].extra_colormap;
 	else
@@ -1506,12 +1385,15 @@ static void R_ProjectSprite(mobj_t *thing)
 	// specific translucency
 	if (!cv_translucency.value)
 		; // no translucency
-	else if (thing->flags2 & MF2_SHADOW) // actually only the player should use this (temporary invisibility)
+	else if (oldthing->flags2 & MF2_SHADOW) // actually only the player should use this (temporary invisibility)
 		vis->transmap = transtables + ((tr_trans80-1)<<FF_TRANSSHIFT); // because now the translucency is set through FF_TRANSMASK
-	else if (thing->frame & FF_TRANSMASK)
-		vis->transmap = transtables + (thing->frame & FF_TRANSMASK) - 0x10000;
+	else if (oldthing->frame & FF_TRANSMASK)
+		vis->transmap = transtables + (oldthing->frame & FF_TRANSMASK) - 0x10000;
 
-	if (((thing->frame & FF_FULLBRIGHT) || (thing->flags2 & MF2_SHADOW))
+	if ((oldthing->frame & FF_FULLBRIGHT) || (oldthing->flags2 & MF2_SHADOW))
+		vis->cut |= SC_FULLBRIGHT;
+
+	if (vis->cut & SC_FULLBRIGHT
 		&& (!vis->extra_colormap || !vis->extra_colormap->fog))
 	{
 		// full bright: goggles
@@ -1528,14 +1410,11 @@ static void R_ProjectSprite(mobj_t *thing)
 		vis->colormap = spritelights[lindex];
 	}
 
-	vis->precip = false;
-
-	vis->vflip = vflip;
-
-	vis->isScaled = false;
+	if (vflip)
+		vis->cut |= SC_VFLIP;
 
 	if (thing->subsector->sector->numlights)
-		R_SplitSprite(vis, thing);
+		R_SplitSprite(vis);
 
 	// Debug
 	++objectsdrawn;
@@ -1559,7 +1438,7 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing)
 	fixed_t iscale;
 
 	//SoM: 3/17/2000
-	fixed_t gz ,gzt;
+	fixed_t gz, gzt;
 
 	// transform the origin point
 	tr_x = thing->x - viewx;
@@ -1695,16 +1574,14 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing)
 	else
 		vis->transmap = NULL;
 
+	vis->mobj = (mobj_t *)thing;
 	vis->mobjflags = 0;
-	vis->cut = SC_NONE;
+	vis->cut = SC_PRECIP;
 	vis->extra_colormap = thing->subsector->sector->extra_colormap;
 	vis->heightsec = thing->subsector->sector->heightsec;
 
 	// Fullbright
 	vis->colormap = colormaps;
-	vis->precip = true;
-	vis->vflip = false;
-	vis->isScaled = false;
 }
 
 // R_AddSprites
@@ -1797,7 +1674,7 @@ static vissprite_t vsprsortedhead;
 
 void R_SortVisSprites(void)
 {
-	UINT32       i;
+	UINT32       i, linkedvissprites = 0;
 	vissprite_t *ds, *dsprev, *dsnext, *dsfirst;
 	vissprite_t *best = NULL;
 	vissprite_t  unsorted;
@@ -1821,22 +1698,91 @@ void R_SortVisSprites(void)
 
 		ds->next = dsnext;
 		ds->prev = dsprev;
+		ds->linkdraw = NULL;
 	}
 
 	// Fix first and last. ds still points to the last one after the loop
 	dsfirst->prev = &unsorted;
 	unsorted.next = dsfirst;
 	if (ds)
+	{
 		ds->next = &unsorted;
+		ds->linkdraw = NULL;
+	}
 	unsorted.prev = ds;
 
+	// bundle linkdraw
+	for (ds = unsorted.prev; ds != &unsorted; ds = ds->prev)
+	{
+		if (!(ds->cut & SC_LINKDRAW))
+			continue;
+
+		// reuse dsfirst...
+		for (dsfirst = unsorted.prev; dsfirst != &unsorted; dsfirst = dsfirst->prev)
+		{
+			// don't connect if it's also a link
+			if (dsfirst->cut & SC_LINKDRAW)
+				continue;
+
+			// don't connect if it's not the tracer
+			if (dsfirst->mobj != ds->mobj)
+				continue;
+
+			// don't connect if the tracer's top is cut off, but lower than the link's top
+			if ((dsfirst->cut & SC_TOP)
+			&& dsfirst->szt > ds->szt)
+				continue;
+
+			// don't connect if the tracer's bottom is cut off, but higher than the link's bottom
+			if ((dsfirst->cut & SC_BOTTOM)
+			&& dsfirst->sz < ds->sz)
+				continue;
+
+			break;
+		}
+
+		// remove from chain
+		ds->next->prev = ds->prev;
+		ds->prev->next = ds->next;
+		linkedvissprites++;
+
+		if (dsfirst != &unsorted)
+		{
+			if (!(ds->cut & SC_FULLBRIGHT))
+				ds->colormap = dsfirst->colormap;
+			ds->extra_colormap = dsfirst->extra_colormap;
+
+			// reusing dsnext...
+			dsnext = dsfirst->linkdraw;
+
+			if (!dsnext || ds->dispoffset < dsnext->dispoffset)
+			{
+				ds->next = dsnext;
+				dsfirst->linkdraw = ds;
+			}
+			else
+			{
+				for (; dsnext->next != NULL; dsnext = dsnext->next)
+					if (ds->dispoffset < dsnext->next->dispoffset)
+						break;
+				ds->next = dsnext->next;
+				dsnext->next = ds;
+			}
+		}
+	}
+
 	// pull the vissprites out by scale
 	vsprsortedhead.next = vsprsortedhead.prev = &vsprsortedhead;
-	for (i = 0; i < visspritecount; i++)
+	for (i = 0; i < visspritecount-linkedvissprites; i++)
 	{
 		bestscale = bestdispoffset = INT32_MAX;
 		for (ds = unsorted.next; ds != &unsorted; ds = ds->next)
 		{
+#ifdef PARANOIA
+			if (ds->cut & SC_LINKDRAW)
+				I_Error("R_SortVisSprites: no link or discardal made for linkdraw!");
+#endif
+
 			if (ds->sortscale < bestscale)
 			{
 				bestscale = ds->sortscale;
@@ -2090,20 +2036,6 @@ static void R_CreateDrawNodes(void)
 			}
 			else if (r2->seg)
 			{
-#if 0 //#ifdef POLYOBJECTS_PLANES
-				if (r2->seg->curline->polyseg && rover->mobj && P_MobjInsidePolyobj(r2->seg->curline->polyseg, rover->mobj)) {
-					// Determine if we need to sort in front of the polyobj, based on the planes. This fixes the issue where
-					// polyobject planes render above the object standing on them. (A bit hacky... but it works.) -Red
-					mobj_t *mo = rover->mobj;
-					sector_t *po = r2->seg->curline->backsector;
-
-					if (po->ceilingheight < viewz && mo->z+mo->height > po->ceilingheight)
-						continue;
-
-					if (po->floorheight > viewz && mo->z < po->floorheight)
-						continue;
-				}
-#endif
 				if (rover->x1 > r2->seg->x2 || rover->x2 < r2->seg->x1)
 					continue;
 
@@ -2230,7 +2162,7 @@ static void R_DrawPrecipitationSprite(vissprite_t *spr)
 void R_ClipSprites(void)
 {
 	vissprite_t *spr;
-	for (;clippedvissprites < visspritecount; clippedvissprites++)
+	for (; clippedvissprites < visspritecount; clippedvissprites++)
 	{
 		drawseg_t *ds;
 		INT32		x;
@@ -2451,11 +2383,25 @@ void R_DrawMasked(void)
 			next = r2->prev;
 
 			// Tails 08-18-2002
-			if (r2->sprite->precip == true)
+			if (r2->sprite->cut & SC_PRECIP)
 				R_DrawPrecipitationSprite(r2->sprite);
-			else
+			else if (!r2->sprite->linkdraw)
+				R_DrawSprite(r2->sprite);
+			else // unbundle linkdraw
+			{
+				vissprite_t *ds = r2->sprite->linkdraw;
+
+				for (;
+				(ds != NULL && r2->sprite->dispoffset > ds->dispoffset);
+				ds = ds->next)
+					R_DrawSprite(ds);
+
 				R_DrawSprite(r2->sprite);
 
+				for (; ds != NULL; ds = ds->next)
+					R_DrawSprite(ds);
+			}
+
 			R_DoneWithNode(r2);
 			r2 = next;
 		}
@@ -2477,6 +2423,193 @@ skin_t skins[MAXSKINS+1];
 CV_PossibleValue_t skin_cons_t[MAXSKINS+1];
 #endif
 
+//
+// P_GetSkinSprite2
+// For non-super players, tries each sprite2's immediate predecessor until it finds one with a number of frames or ends up at standing.
+// For super players, does the same as above - but tries the super equivalent for each sprite2 before the non-super version.
+//
+
+UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player)
+{
+	UINT8 super = (spr2 & FF_SPR2SUPER);
+
+	if (!skin)
+		return 0;
+
+	while (!(skin->sprites[spr2].numframes)
+		&& spr2 != SPR2_STND)
+	{
+		if (spr2 & FF_SPR2SUPER)
+		{
+			spr2 &= ~FF_SPR2SUPER;
+			continue;
+		}
+
+		switch(spr2)
+		{
+		case SPR2_RUN:
+			spr2 = SPR2_WALK;
+			break;
+		case SPR2_STUN:
+			spr2 = SPR2_PAIN;
+			break;
+		case SPR2_DRWN:
+			spr2 = SPR2_DEAD;
+			break;
+		case SPR2_SPIN:
+			spr2 = SPR2_ROLL;
+			break;
+		case SPR2_GASP:
+			spr2 = SPR2_SPNG;
+			break;
+		case SPR2_JUMP:
+			spr2 = ((player
+					? player->charflags
+					: skin->flags)
+					& SF_NOJUMPSPIN) ? SPR2_SPNG : SPR2_ROLL;
+			break;
+		case SPR2_SPNG: // spring
+			spr2 = SPR2_FALL;
+			break;
+		case SPR2_FALL:
+			spr2 = SPR2_WALK;
+			break;
+		case SPR2_RIDE:
+			spr2 = SPR2_FALL;
+			break;
+
+		case SPR2_FLY :
+			spr2 = SPR2_SPNG;
+			break;
+		case SPR2_SWIM:
+			spr2 = SPR2_FLY ;
+			break;
+		case SPR2_TIRE:
+			spr2 = (player && player->charability == CA_SWIM) ? SPR2_SWIM : SPR2_FLY;
+			break;
+
+		case SPR2_GLID:
+			spr2 = SPR2_FLY;
+			break;
+		case SPR2_CLMB:
+			spr2 = SPR2_ROLL;
+			break;
+		case SPR2_CLNG:
+			spr2 = SPR2_CLMB;
+			break;
+
+		case SPR2_FLT :
+			spr2 = SPR2_WALK;
+			break;
+		case SPR2_FRUN:
+			spr2 = SPR2_RUN ;
+			break;
+
+		case SPR2_DASH:
+			spr2 = SPR2_FRUN;
+			break;
+
+		case SPR2_BNCE:
+			spr2 = SPR2_FALL;
+			break;
+		case SPR2_BLND:
+			spr2 = SPR2_ROLL;
+			break;
+
+		case SPR2_TWIN:
+			spr2 = SPR2_ROLL;
+			break;
+
+		case SPR2_MLEE:
+			spr2 = SPR2_TWIN;
+			break;
+
+		// NiGHTS sprites.
+		case SPR2_NSTD:
+			spr2 = SPR2_STND;
+			super = FF_SPR2SUPER;
+			break;
+		case SPR2_NFLT:
+			spr2 = SPR2_FLT ;
+			super = FF_SPR2SUPER;
+			break;
+		case SPR2_NSTN:
+			spr2 = SPR2_STUN;
+			break;
+		case SPR2_NPUL:
+			spr2 = SPR2_NSTN;
+			break;
+		case SPR2_NATK:
+			spr2 = SPR2_ROLL;
+			super = FF_SPR2SUPER;
+			break;
+		/*case SPR2_NGT0:
+			spr2 = SPR2_NFLT;
+			break;*/
+		case SPR2_NGT1:
+		case SPR2_NGT7:
+		case SPR2_DRL0:
+			spr2 = SPR2_NGT0;
+			break;
+		case SPR2_NGT2:
+		case SPR2_DRL1:
+			spr2 = SPR2_NGT1;
+			break;
+		case SPR2_NGT3:
+		case SPR2_DRL2:
+			spr2 = SPR2_NGT2;
+			break;
+		case SPR2_NGT4:
+		case SPR2_DRL3:
+			spr2 = SPR2_NGT3;
+			break;
+		case SPR2_NGT5:
+		case SPR2_DRL4:
+			spr2 = SPR2_NGT4;
+			break;
+		case SPR2_NGT6:
+		case SPR2_DRL5:
+			spr2 = SPR2_NGT5;
+			break;
+		case SPR2_DRL6:
+			spr2 = SPR2_NGT6;
+			break;
+		case SPR2_NGT8:
+		case SPR2_DRL7:
+			spr2 = SPR2_NGT7;
+			break;
+		case SPR2_NGT9:
+		case SPR2_DRL8:
+			spr2 = SPR2_NGT8;
+			break;
+		case SPR2_NGTA:
+		case SPR2_DRL9:
+			spr2 = SPR2_NGT9;
+			break;
+		case SPR2_NGTB:
+		case SPR2_DRLA:
+			spr2 = SPR2_NGTA;
+			break;
+		case SPR2_NGTC:
+		case SPR2_DRLB:
+			spr2 = SPR2_NGTB;
+			break;
+		case SPR2_DRLC:
+			spr2 = SPR2_NGTC;
+			break;
+
+		// Dunno? Just go to standing then.
+		default:
+			spr2 = SPR2_STND;
+			break;
+		}
+
+		spr2 |= super;
+	}
+
+	return spr2;
+}
+
 static void Sk_SetDefaultValue(skin_t *skin)
 {
 	INT32 i;
@@ -2530,7 +2663,7 @@ static void Sk_SetDefaultValue(skin_t *skin)
 	skin->spinitem = -1;
 	skin->revitem = -1;
 
-	skin->highresscale = FRACUNIT>>1;
+	skin->highresscale = FRACUNIT;
 
 	skin->availability = 0;
 
@@ -2669,16 +2802,17 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
 
 		if (player->mo)
 		{
+			fixed_t radius = FixedMul(skin->radius, player->mo->scale);
 			if ((player->powers[pw_carry] == CR_NIGHTSMODE) && (skin->sprites[SPR2_NGT0].numframes == 0)) // If you don't have a sprite for flying horizontally, use the default NiGHTS skin.
 			{
 				skin = &skins[DEFAULTNIGHTSSKIN];
-				newcolor = ((skin->flags & SF_SUPER) ? skin->supercolor : skin->prefcolor);
+				newcolor = skin->prefcolor; // will be updated in thinker to flashing
 			}
 			player->mo->skin = skin;
 			if (newcolor)
 				player->mo->color = newcolor;
 			P_SetScale(player->mo, player->mo->scale);
-			player->mo->radius = FixedMul(skin->radius, player->mo->scale);
+			player->mo->radius = radius;
 
 			P_SetPlayerMobjState(player->mo, player->mo->state-states); // Prevent visual errors when switching between skins with differing number of frames
 		}
@@ -2723,6 +2857,189 @@ static UINT16 W_CheckForSkinMarkerInPwad(UINT16 wadid, UINT16 startlump)
 #define HUDNAMEWRITE(value) STRBUFCPY(skin->hudname, value)
 #endif
 
+// turn _ into spaces and . into katana dot
+#define SYMBOLCONVERT(name) for (value = name; *value; value++)\
+					{\
+						if (*value == '_') *value = ' ';\
+						else if (*value == '.') *value = '\x1E';\
+					}
+
+//
+// Patch skins from a pwad, each skin preceded by 'P_SKIN' marker
+//
+
+// Does the same is in w_wad, but check only for
+// the first 6 characters (this is so we can have P_SKIN1, P_SKIN2..
+// for wad editors that don't like multiple resources of the same name)
+//
+static UINT16 W_CheckForPatchSkinMarkerInPwad(UINT16 wadid, UINT16 startlump)
+{
+	UINT16 i;
+	const char *P_SKIN = "P_SKIN";
+	lumpinfo_t *lump_p;
+
+	// scan forward, start at <startlump>
+	if (startlump < wadfiles[wadid]->numlumps)
+	{
+		lump_p = wadfiles[wadid]->lumpinfo + startlump;
+		for (i = startlump; i < wadfiles[wadid]->numlumps; i++, lump_p++)
+			if (memcmp(lump_p->name,P_SKIN,6)==0)
+				return i;
+	}
+	return INT16_MAX; // not found
+}
+
+static void R_LoadSkinSprites(UINT16 wadnum, UINT16 *lump, UINT16 *lastlump, skin_t *skin)
+{
+	UINT16 newlastlump;
+	UINT8 sprite2;
+
+	*lump += 1; // start after S_SKIN
+	*lastlump = W_CheckNumForNamePwad("S_END",wadnum,*lump); // stop at S_END
+
+	// old wadding practices die hard -- stop at S_SKIN (or P_SKIN) or S_START if they come before S_END.
+	newlastlump = W_CheckForSkinMarkerInPwad(wadnum,*lump);
+	if (newlastlump < *lastlump) *lastlump = newlastlump;
+	newlastlump = W_CheckForPatchSkinMarkerInPwad(wadnum,*lump);
+	if (newlastlump < *lastlump) *lastlump = newlastlump;
+	newlastlump = W_CheckNumForNamePwad("S_START",wadnum,*lump);
+	if (newlastlump < *lastlump) *lastlump = newlastlump;
+
+	// ...and let's handle super, too
+	newlastlump = W_CheckNumForNamePwad("S_SUPER",wadnum,*lump);
+	if (newlastlump < *lastlump)
+	{
+		newlastlump++;
+		// load all sprite sets we are aware of... for super!
+		for (sprite2 = 0; sprite2 < free_spr2; sprite2++)
+			R_AddSingleSpriteDef((spritename = spr2names[sprite2]), &skin->sprites[FF_SPR2SUPER|sprite2], wadnum, newlastlump, *lastlump);
+
+		newlastlump--;
+		*lastlump = newlastlump; // okay, make the normal sprite set loading end there
+	}
+
+	// load all sprite sets we are aware of... for normal stuff.
+	for (sprite2 = 0; sprite2 < free_spr2; sprite2++)
+		R_AddSingleSpriteDef((spritename = spr2names[sprite2]), &skin->sprites[sprite2], wadnum, *lump, *lastlump);
+
+}
+
+// returns whether found appropriate property
+static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value)
+{
+	// custom translation table
+	if (!stricmp(stoken, "startcolor"))
+		skin->starttranscolor = atoi(value);
+
+#define FULLPROCESS(field) else if (!stricmp(stoken, #field)) skin->field = get_number(value);
+	// character type identification
+	FULLPROCESS(flags)
+	FULLPROCESS(ability)
+	FULLPROCESS(ability2)
+
+	FULLPROCESS(thokitem)
+	FULLPROCESS(spinitem)
+	FULLPROCESS(revitem)
+#undef FULLPROCESS
+
+#define GETFRACBITS(field) else if (!stricmp(stoken, #field)) skin->field = atoi(value)<<FRACBITS;
+	GETFRACBITS(normalspeed)
+	GETFRACBITS(runspeed)
+
+	GETFRACBITS(mindash)
+	GETFRACBITS(maxdash)
+	GETFRACBITS(actionspd)
+
+	GETFRACBITS(radius)
+	GETFRACBITS(height)
+	GETFRACBITS(spinheight)
+#undef GETFRACBITS
+
+#define GETINT(field) else if (!stricmp(stoken, #field)) skin->field = atoi(value);
+	GETINT(thrustfactor)
+	GETINT(accelstart)
+	GETINT(acceleration)
+#undef GETINT
+
+#define GETSKINCOLOR(field) else if (!stricmp(stoken, #field)) skin->field = R_GetColorByName(value);
+	GETSKINCOLOR(prefcolor)
+	GETSKINCOLOR(prefoppositecolor)
+#undef GETSKINCOLOR
+	else if (!stricmp(stoken, "supercolor"))
+		skin->supercolor = R_GetSuperColorByName(value);
+
+#define GETFLOAT(field) else if (!stricmp(stoken, #field)) skin->field = FLOAT_TO_FIXED(atof(value));
+	GETFLOAT(jumpfactor)
+	GETFLOAT(highresscale)
+	GETFLOAT(shieldscale)
+	GETFLOAT(camerascale)
+#undef GETFLOAT
+
+#define GETFLAG(field) else if (!stricmp(stoken, #field)) { \
+	strupr(value); \
+	if (atoi(value) || value[0] == 'T' || value[0] == 'Y') \
+		skin->flags |= (SF_##field); \
+	else \
+		skin->flags &= ~(SF_##field); \
+}
+	// parameters for individual character flags
+	// these are uppercase so they can be concatenated with SF_
+	// 1, true, yes are all valid values
+	GETFLAG(SUPER)
+	GETFLAG(NOSUPERSPIN)
+	GETFLAG(NOSPINDASHDUST)
+	GETFLAG(HIRES)
+	GETFLAG(NOSKID)
+	GETFLAG(NOSPEEDADJUST)
+	GETFLAG(RUNONWATER)
+	GETFLAG(NOJUMPSPIN)
+	GETFLAG(NOJUMPDAMAGE)
+	GETFLAG(STOMPDAMAGE)
+	GETFLAG(MARIODAMAGE)
+	GETFLAG(MACHINE)
+	GETFLAG(DASHMODE)
+	GETFLAG(FASTEDGE)
+	GETFLAG(MULTIABILITY)
+#undef GETFLAG
+
+	else // let's check if it's a sound, otherwise error out
+	{
+		boolean found = false;
+		sfxenum_t i;
+		size_t stokenadjust;
+
+		// Remove the prefix. (We need to affect an adjusting variable so that we can print error messages if it's not actually a sound.)
+		if ((stoken[0] == 'D' || stoken[0] == 'd') && (stoken[1] == 'S' || stoken[1] == 's')) // DS*
+			stokenadjust = 2;
+		else // sfx_*
+			stokenadjust = 4;
+
+		// Remove the prefix. (We can affect this directly since we're not going to use it again.)
+		if ((value[0] == 'D' || value[0] == 'd') && (value[1] == 'S' || value[1] == 's')) // DS*
+			value += 2;
+		else // sfx_*
+			value += 4;
+
+		// copy name of sounds that are remapped
+		// for this skin
+		for (i = 0; i < sfx_skinsoundslot0; i++)
+		{
+			if (!S_sfx[i].name)
+				continue;
+			if (S_sfx[i].skinsound != -1
+				&& !stricmp(S_sfx[i].name,
+					stoken + stokenadjust))
+			{
+				skin->soundsid[S_sfx[i].skinsound] =
+					S_AddSoundFx(value, S_sfx[i].singularity, S_sfx[i].pitch, true);
+				found = true;
+			}
+		}
+		return found;
+	}
+	return true;
+}
+
 //
 // Find skin sprites, sounds & optional status bar face, & add them
 //
@@ -2782,6 +3099,8 @@ void R_AddSkins(UINT16 wadnum)
 			if (!value)
 				I_Error("R_AddSkins: syntax error in S_SKIN lump# %d(%s) in WAD %s\n", lump, W_CheckNameForNumPwad(wadnum,lump), wadfiles[wadnum]->filename);
 
+			// Some of these can't go in R_ProcessPatchableFields because they have side effects for future lines.
+			// Others can't go in there because we don't want them to be patchable.
 			if (!stricmp(stoken, "name"))
 			{
 				INT32 skinnum = R_SkinAvailable(value);
@@ -2811,22 +3130,23 @@ void R_AddSkins(UINT16 wadnum)
 				{
 					STRBUFCPY(skin->realname, skin->name);
 					for (value = skin->realname; *value; value++)
+					{
 						if (*value == '_') *value = ' '; // turn _ into spaces.
+						else if (*value == '.') *value = '\x1E'; // turn . into katana dot.
+					}
 				}
 				if (!hudname)
 				{
 					HUDNAMEWRITE(skin->name);
 					strupr(skin->hudname);
-					for (value = skin->hudname; *value; value++)
-						if (*value == '_') *value = ' '; // turn _ into spaces.
+					SYMBOLCONVERT(skin->hudname)
 				}
 			}
 			else if (!stricmp(stoken, "realname"))
 			{ // Display name (eg. "Knuckles")
 				realname = true;
 				STRBUFCPY(skin->realname, value);
-				for (value = skin->realname; *value; value++)
-					if (*value == '_') *value = ' '; // turn _ into spaces.
+				SYMBOLCONVERT(skin->realname)
 				if (!hudname)
 					HUDNAMEWRITE(skin->realname);
 			}
@@ -2834,12 +3154,10 @@ void R_AddSkins(UINT16 wadnum)
 			{ // Life icon name (eg. "K.T.E")
 				hudname = true;
 				HUDNAMEWRITE(value);
-				for (value = skin->hudname; *value; value++)
-					if (*value == '_') *value = ' '; // turn _ into spaces.
+				SYMBOLCONVERT(skin->hudname)
 				if (!realname)
 					STRBUFCPY(skin->realname, skin->hudname);
 			}
-
 			else if (!stricmp(stoken, "charsel"))
 			{
 				strupr(value);
@@ -2858,37 +3176,6 @@ void R_AddSkins(UINT16 wadnum)
 				strupr(value);
 				strncpy(skin->superface, value, sizeof skin->superface);
 			}
-
-#define FULLPROCESS(field) else if (!stricmp(stoken, #field)) skin->field = get_number(value);
-			// character type identification
-			FULLPROCESS(flags)
-			FULLPROCESS(ability)
-			FULLPROCESS(ability2)
-
-			FULLPROCESS(thokitem)
-			FULLPROCESS(spinitem)
-			FULLPROCESS(revitem)
-#undef FULLPROCESS
-
-#define GETFRACBITS(field) else if (!stricmp(stoken, #field)) skin->field = atoi(value)<<FRACBITS;
-			GETFRACBITS(normalspeed)
-			GETFRACBITS(runspeed)
-
-			GETFRACBITS(mindash)
-			GETFRACBITS(maxdash)
-			GETFRACBITS(actionspd)
-
-			GETFRACBITS(radius)
-			GETFRACBITS(height)
-			GETFRACBITS(spinheight)
-#undef GETFRACBITS
-
-#define GETINT(field) else if (!stricmp(stoken, #field)) skin->field = atoi(value);
-			GETINT(thrustfactor)
-			GETINT(accelstart)
-			GETINT(acceleration)
-#undef GETINT
-
 			else if (!stricmp(stoken, "availability"))
 			{
 				skin->availability = atoi(value);
@@ -2897,125 +3184,16 @@ void R_AddSkins(UINT16 wadnum)
 				if (skin->availability)
 					STRBUFCPY(unlockables[skin->availability - 1].name, skin->realname);
 			}
+			else if (!R_ProcessPatchableFields(skin, stoken, value))
+				CONS_Debug(DBG_SETUP, "R_AddSkins: Unknown keyword '%s' in S_SKIN lump #%d (WAD %s)\n", stoken, lump, wadfiles[wadnum]->filename);
 
-			// custom translation table
-			else if (!stricmp(stoken, "startcolor"))
-				skin->starttranscolor = atoi(value);
-
-#define GETSKINCOLOR(field) else if (!stricmp(stoken, #field)) skin->field = R_GetColorByName(value);
-			GETSKINCOLOR(prefcolor)
-			GETSKINCOLOR(prefoppositecolor)
-#undef GETSKINCOLOR
-			else if (!stricmp(stoken, "supercolor"))
-				skin->supercolor = R_GetSuperColorByName(value);
-
-#define GETFLOAT(field) else if (!stricmp(stoken, #field)) skin->field = FLOAT_TO_FIXED(atof(value));
-			GETFLOAT(jumpfactor)
-			GETFLOAT(highresscale)
-			GETFLOAT(shieldscale)
-			GETFLOAT(camerascale)
-#undef GETFLOAT
-
-#define GETFLAG(field) else if (!stricmp(stoken, #field)) { \
-	strupr(value); \
-	if (atoi(value) || value[0] == 'T' || value[0] == 'Y') \
-		skin->flags |= (SF_##field); \
-	else \
-		skin->flags &= ~(SF_##field); \
-}
-			// parameters for individual character flags
-			// these are uppercase so they can be concatenated with SF_
-			// 1, true, yes are all valid values
-			GETFLAG(SUPER)
-			GETFLAG(NOSUPERSPIN)
-			GETFLAG(NOSPINDASHDUST)
-			GETFLAG(HIRES)
-			GETFLAG(NOSKID)
-			GETFLAG(NOSPEEDADJUST)
-			GETFLAG(RUNONWATER)
-			GETFLAG(NOJUMPSPIN)
-			GETFLAG(NOJUMPDAMAGE)
-			GETFLAG(STOMPDAMAGE)
-			GETFLAG(MARIODAMAGE)
-			GETFLAG(MACHINE)
-			GETFLAG(DASHMODE)
-			GETFLAG(FASTEDGE)
-			GETFLAG(MULTIABILITY)
-#undef GETFLAG
-
-			else // let's check if it's a sound, otherwise error out
-			{
-				boolean found = false;
-				sfxenum_t i;
-				size_t stokenadjust;
-
-				// Remove the prefix. (We need to affect an adjusting variable so that we can print error messages if it's not actually a sound.)
-				if ((stoken[0] == 'D' || stoken[0] == 'd') && (stoken[1] == 'S' || stoken[1] == 's')) // DS*
-					stokenadjust = 2;
-				else // sfx_*
-					stokenadjust = 4;
-
-				// Remove the prefix. (We can affect this directly since we're not going to use it again.)
-				if ((value[0] == 'D' || value[0] == 'd') && (value[1] == 'S' || value[1] == 's')) // DS*
-					value += 2;
-				else // sfx_*
-					value += 4;
-
-				// copy name of sounds that are remapped
-				// for this skin
-				for (i = 0; i < sfx_skinsoundslot0; i++)
-				{
-					if (!S_sfx[i].name)
-						continue;
-					if (S_sfx[i].skinsound != -1
-						&& !stricmp(S_sfx[i].name,
-							stoken + stokenadjust))
-					{
-						skin->soundsid[S_sfx[i].skinsound] =
-							S_AddSoundFx(value, S_sfx[i].singularity, S_sfx[i].pitch, true);
-						found = true;
-					}
-				}
-				if (!found)
-					CONS_Debug(DBG_SETUP, "R_AddSkins: Unknown keyword '%s' in S_SKIN lump #%d (WAD %s)\n", stoken, lump, wadfiles[wadnum]->filename);
-			}
 next_token:
 			stoken = strtok(NULL, "\r\n= ");
 		}
 		free(buf2);
 
 		// Add sprites
-		{
-			UINT16 newlastlump;
-			UINT8 sprite2;
-
-			lump++; // start after S_SKIN
-			lastlump = W_CheckNumForNamePwad("S_END",wadnum,lump); // stop at S_END
-
-			// old wadding practices die hard -- stop at S_SKIN or S_START if they come before S_END.
-			newlastlump = W_CheckNumForNamePwad("S_SKIN",wadnum,lump);
-			if (newlastlump < lastlump) lastlump = newlastlump;
-			newlastlump = W_CheckNumForNamePwad("S_START",wadnum,lump);
-			if (newlastlump < lastlump) lastlump = newlastlump;
-
-			// ...and let's handle super, too
-			newlastlump = W_CheckNumForNamePwad("S_SUPER",wadnum,lump);
-			if (newlastlump < lastlump)
-			{
-				newlastlump++;
-				// load all sprite sets we are aware of... for super!
-				for (sprite2 = 0; sprite2 < free_spr2; sprite2++)
-					R_AddSingleSpriteDef(spr2names[sprite2], &skin->sprites[FF_SPR2SUPER|sprite2], wadnum, newlastlump, lastlump);
-
-				newlastlump--;
-				lastlump = newlastlump; // okay, make the normal sprite set loading end there
-			}
-
-			// load all sprite sets we are aware of... for normal stuff.
-			for (sprite2 = 0; sprite2 < free_spr2; sprite2++)
-				R_AddSingleSpriteDef(spr2names[sprite2], &skin->sprites[sprite2], wadnum, lump, lastlump);
-
-		}
+		R_LoadSkinSprites(wadnum, &lump, &lastlump, skin);
 
 		R_FlushTranslationColormapCache();
 
@@ -3039,50 +3217,124 @@ next_token:
 	return;
 }
 
-#undef HUDNAMEWRITE
-
-#ifdef DELFILE
-void R_DelSkins(UINT16 wadnum)
+//
+// Patch skin sprites
+//
+void R_PatchSkins(UINT16 wadnum)
 {
 	UINT16 lump, lastlump = 0;
-	while ((lump = W_CheckForSkinMarkerInPwad(wadnum, lastlump)) != INT16_MAX)
+	char *buf;
+	char *buf2;
+	char *stoken;
+	char *value;
+	size_t size;
+	skin_t *skin;
+	boolean noskincomplain, realname, hudname;
+
+	//
+	// search for all skin patch markers in pwad
+	//
+
+	while ((lump = W_CheckForPatchSkinMarkerInPwad(wadnum, lastlump)) != INT16_MAX)
 	{
-		if (skins[numskins].wadnum != wadnum)
-			break;
-		numskins--;
-		ST_UnLoadFaceGraphics(numskins); // only used by DELFILE
-		if (skins[numskins].sprite[0] != '\0')
-		{
-			const char *csprname = W_CheckNameForNumPwad(wadnum, lump);
-
-			// skip to end of this skin's frames
-			lastlump = lump;
-			while (W_CheckNameForNumPwad(wadnum,lastlump) && memcmp(W_CheckNameForNumPwad(wadnum, lastlump),csprname,4)==0)
-				lastlump++;
-			// allocate (or replace) sprite frames, and set spritedef
-			R_DelSingleSpriteDef(csprname, &skins[numskins].spritedef, wadnum, lump, lastlump);
-		}
-		else
+		INT32 skinnum = 0;
+
+		// advance by default
+		lastlump = lump + 1;
+
+		buf = W_CacheLumpNumPwad(wadnum, lump, PU_CACHE);
+		size = W_LumpLengthPwad(wadnum, lump);
+
+		// for strtok
+		buf2 = malloc(size+1);
+		if (!buf2)
+			I_Error("R_PatchSkins: No more free memory\n");
+		M_Memcpy(buf2,buf,size);
+		buf2[size] = '\0';
+
+		skin = NULL;
+		noskincomplain = realname = hudname = false;
+
+		/*
+		Parse. Has more phases than the parser in R_AddSkins because it needs to have the patching name first (no default skin name is acceptible for patching, unlike skin creation)
+		*/
+
+		stoken = strtok(buf2, "\r\n= ");
+		while (stoken)
 		{
-			// search in the normal sprite tables
-			size_t name;
-			boolean found = false;
-			const char *sprname = skins[numskins].sprite;
-			for (name = 0;sprnames[name][0] != '\0';name++)
-				if (strcmp(sprnames[name], sprname) == 0)
+			if ((stoken[0] == '/' && stoken[1] == '/')
+				|| (stoken[0] == '#'))// skip comments
+			{
+				stoken = strtok(NULL, "\r\n"); // skip end of line
+				goto next_token;              // find the real next token
+			}
+
+			value = strtok(NULL, "\r\n= ");
+
+			if (!value)
+				I_Error("R_PatchSkins: syntax error in P_SKIN lump# %d(%s) in WAD %s\n", lump, W_CheckNameForNumPwad(wadnum,lump), wadfiles[wadnum]->filename);
+
+			if (!skin) // Get the name!
+			{
+				if (!stricmp(stoken, "name"))
 				{
-					found = true;
-					skins[numskins].spritedef = sprites[name];
+					strlwr(value);
+					skinnum = R_SkinAvailable(value);
+					if (skinnum != -1)
+						skin = &skins[skinnum];
+					else
+					{
+						CONS_Debug(DBG_SETUP, "R_PatchSkins: unknown skin name in P_SKIN lump# %d(%s) in WAD %s\n", lump, W_CheckNameForNumPwad(wadnum,lump), wadfiles[wadnum]->filename);
+						noskincomplain = true;
+					}
 				}
+			}
+			else // Get the properties!
+			{
+				// Some of these can't go in R_ProcessPatchableFields because they have side effects for future lines.
+				if (!stricmp(stoken, "realname"))
+				{ // Display name (eg. "Knuckles")
+					realname = true;
+					STRBUFCPY(skin->realname, value);
+					SYMBOLCONVERT(skin->realname)
+					if (!hudname)
+						HUDNAMEWRITE(skin->realname);
+				}
+				else if (!stricmp(stoken, "hudname"))
+				{ // Life icon name (eg. "K.T.E")
+					hudname = true;
+					HUDNAMEWRITE(value);
+					SYMBOLCONVERT(skin->hudname)
+					if (!realname)
+						STRBUFCPY(skin->realname, skin->hudname);
+				}
+				else if (!R_ProcessPatchableFields(skin, stoken, value))
+					CONS_Debug(DBG_SETUP, "R_PatchSkins: Unknown keyword '%s' in P_SKIN lump #%d (WAD %s)\n", stoken, lump, wadfiles[wadnum]->filename);
+			}
 
-			// not found so make a new one
-			if (!found)
-				R_DelSingleSpriteDef(sprname, &skins[numskins].spritedef, wadnum, 0, INT16_MAX);
+			if (!skin)
+				break;
 
-			while (W_CheckNameForNumPwad(wadnum,lastlump) && memcmp(W_CheckNameForNumPwad(wadnum, lastlump),sprname,4)==0)
-				lastlump++;
+next_token:
+			stoken = strtok(NULL, "\r\n= ");
 		}
-		CONS_Printf(M_GetText("Removed skin '%s'\n"), skins[numskins].name);
+		free(buf2);
+
+		if (!skin) // Didn't include a name parameter? What a waste.
+		{
+			if (!noskincomplain)
+				CONS_Debug(DBG_SETUP, "R_PatchSkins: no skin name given in P_SKIN lump #%d (WAD %s)\n", lump, wadfiles[wadnum]->filename);
+			continue;
+		}
+
+		// Patch sprites
+		R_LoadSkinSprites(wadnum, &lump, &lastlump, skin);
+
+		if (!skin->availability) // Safe to print...
+			CONS_Printf(M_GetText("Patched skin '%s'\n"), skin->name);
 	}
+	return;
 }
-#endif
+
+#undef HUDNAMEWRITE
+#undef SYMBOLCONVERT
diff --git a/src/r_things.h b/src/r_things.h
index 3907fd2ae265717ff1b81ca46bc2183763d944b4..18cc701ab72b0090293d0a6303716458683852c9 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -51,10 +51,6 @@ void R_SortVisSprites(void);
 //     (only sprites from namelist are added or replaced)
 void R_AddSpriteDefs(UINT16 wadnum);
 
-#ifdef DELFILE
-void R_DelSpriteDefs(UINT16 wadnum);
-#endif
-
 //SoM: 6/5/2000: Light sprites correctly!
 void R_AddSprites(sector_t *sec, INT32 lightlevel);
 void R_InitSprites(void);
@@ -127,9 +123,19 @@ typedef struct
 // -----------
 typedef enum
 {
+	// actual cuts
 	SC_NONE = 0,
 	SC_TOP = 1,
-	SC_BOTTOM = 2
+	SC_BOTTOM = 1<<1,
+	// other flags
+	SC_PRECIP = 1<<2,
+	SC_LINKDRAW = 1<<3,
+	SC_FULLBRIGHT = 1<<4,
+	SC_VFLIP = 1<<5,
+	SC_ISSCALED = 1>>6,
+	// masks
+	SC_CUTMASK = SC_TOP|SC_BOTTOM,
+	SC_FLAGMASK = ~SC_CUTMASK
 } spritecut_e;
 
 // A vissprite_t is a thing that will be drawn during a refresh,
@@ -140,6 +146,9 @@ typedef struct vissprite_s
 	struct vissprite_s *prev;
 	struct vissprite_s *next;
 
+	// Bonus linkdraw pointer.
+	struct vissprite_s *linkdraw;
+
 	mobj_t *mobj; // for easy access
 
 	INT32 x1, x2;
@@ -178,9 +187,6 @@ typedef struct vissprite_s
 
 	INT16 clipbot[MAXVIDWIDTH], cliptop[MAXVIDWIDTH];
 
-	boolean precip;
-	boolean vflip; // Flip vertically
-	boolean isScaled;
 	INT32 dispoffset; // copy of info->dispoffset, affects ordering but not drawing
 } vissprite_t;
 
@@ -206,11 +212,10 @@ void SetPlayerSkinByNum(INT32 playernum,INT32 skinnum); // Tails 03-16-2002
 boolean R_SkinUsable(INT32 playernum, INT32 skinnum);
 UINT32 R_GetSkinAvailabilities(void);
 INT32 R_SkinAvailable(const char *name);
+void R_PatchSkins(UINT16 wadnum);
 void R_AddSkins(UINT16 wadnum);
 
-#ifdef DELFILE
-void R_DelSkins(UINT16 wadnum);
-#endif
+UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player);
 
 void R_InitDrawNodes(void);
 
diff --git a/src/s_sound.c b/src/s_sound.c
index d3189d7b48d0e813704c65a76a6e938a43b53136..2f3b1ae93f28bd7050fe59fc22817068c65346ef 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -36,6 +36,7 @@ extern INT32 msg_id;
 #include "d_main.h"
 #include "r_sky.h" // skyflatnum
 #include "p_local.h" // camera info
+#include "fastcmp.h"
 
 #ifdef HW3SOUND
 // 3D Sound Interface
@@ -81,6 +82,16 @@ static consvar_t precachesound = {"precachesound", "Off", CV_SAVE, CV_OnOff, NUL
 consvar_t cv_soundvolume = {"soundvolume", "31", CV_SAVE, soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_digmusicvolume = {"digmusicvolume", "18", CV_SAVE, soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_midimusicvolume = {"midimusicvolume", "18", CV_SAVE, soundvolume_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+static void Captioning_OnChange(void)
+{
+	S_ResetCaptions();
+	if (cv_closedcaptioning.value)
+		S_StartSound(NULL, sfx_menu1);
+}
+
+consvar_t cv_closedcaptioning = {"closedcaptioning", "Off", CV_SAVE|CV_CALL, CV_OnOff, Captioning_OnChange, 0, NULL, NULL, 0, 0, NULL};
+
 // number of channels available
 #if defined (_WIN32_WCE) || defined (DC) || defined (PSP) || defined(GP2X)
 consvar_t cv_numChannels = {"snd_channels", "8", CV_SAVE|CV_CALL, CV_Unsigned, SetChannelsNum, 0, NULL, NULL, 0, 0, NULL};
@@ -124,23 +135,24 @@ static consvar_t surround = {"surround", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL
 // percent attenuation from front to back
 #define S_IFRACVOL 30
 
-typedef struct
-{
-	// sound information (if null, channel avail.)
-	sfxinfo_t *sfxinfo;
-
-	// origin of sound
-	const void *origin;
-
-	// handle of the sound being played
-	INT32 handle;
-
-} channel_t;
-
 // the set of channels available
 static channel_t *channels = NULL;
 static INT32 numofchannels = 0;
 
+caption_t closedcaptions[NUMCAPTIONS];
+
+void S_ResetCaptions(void)
+{
+	UINT8 i;
+	for (i = 0; i < NUMCAPTIONS; i++)
+	{
+		closedcaptions[i].c = NULL;
+		closedcaptions[i].s = NULL;
+		closedcaptions[i].t = 0;
+		closedcaptions[i].b = 0;
+	}
+}
+
 //
 // Internals.
 //
@@ -297,6 +309,8 @@ static void SetChannelsNum(void)
 	// Free all channels for use
 	for (i = 0; i < numofchannels; i++)
 		channels[i].sfxinfo = 0;
+
+	S_ResetCaptions();
 }
 
 
@@ -339,6 +353,8 @@ void S_StopSounds(void)
 	for (cnum = 0; cnum < numofchannels; cnum++)
 		if (channels[cnum].sfxinfo)
 			S_StopChannel(cnum);
+
+	S_ResetCaptions();
 }
 
 void S_StopSoundByID(void *origin, sfxenum_t sfx_id)
@@ -387,6 +403,92 @@ void S_StopSoundByNum(sfxenum_t sfxnum)
 	}
 }
 
+void S_StartCaption(sfxenum_t sfx_id, INT32 cnum, UINT16 lifespan)
+{
+	UINT8 i, set, moveup, start;
+	boolean same = false;
+	sfxinfo_t *sfx;
+
+	if (!cv_closedcaptioning.value) // no captions at all
+		return;
+
+	// check for bogus sound #
+	// I_Assert(sfx_id >= 0); -- allowing sfx_None; this shouldn't be allowed directly if S_StartCaption is ever exposed to Lua by itself
+	I_Assert(sfx_id < NUMSFX);
+
+	sfx = &S_sfx[sfx_id];
+
+	if (sfx->caption[0] == '/') // no caption for this one
+		return;
+
+	start = ((closedcaptions[0].s && (closedcaptions[0].s-S_sfx == sfx_None)) ? 1 : 0);
+
+	if (sfx_id)
+	{
+		for (i = start; i < (set = NUMCAPTIONS-1); i++)
+		{
+			same = ((sfx == closedcaptions[i].s) || (closedcaptions[i].s && fastcmp(sfx->caption, closedcaptions[i].s->caption)));
+			if (same)
+			{
+				set = i;
+				break;
+			}
+		}
+	}
+	else
+	{
+		set = 0;
+		same = (closedcaptions[0].s == sfx);
+	}
+
+	moveup = 255;
+
+	if (!same)
+	{
+		for (i = start; i < set; i++)
+		{
+			if (!(closedcaptions[i].c || closedcaptions[i].s) || (sfx->priority >= closedcaptions[i].s->priority))
+			{
+				set = i;
+				if (closedcaptions[i].s && (sfx->priority >= closedcaptions[i].s->priority))
+					moveup = i;
+				break;
+			}
+		}
+		for (i = NUMCAPTIONS-1; i > set; i--)
+		{
+			if (sfx == closedcaptions[i].s)
+			{
+				closedcaptions[i].c = NULL;
+				closedcaptions[i].s = NULL;
+				closedcaptions[i].t = 0;
+				closedcaptions[i].b = 0;
+			}
+		}
+	}
+
+	if (moveup != 255)
+	{
+		for (i = moveup; i < NUMCAPTIONS-1; i++)
+		{
+			if (!(closedcaptions[i].c || closedcaptions[i].s))
+				break;
+		}
+		for (; i > set; i--)
+		{
+			closedcaptions[i].c = closedcaptions[i-1].c;
+			closedcaptions[i].s = closedcaptions[i-1].s;
+			closedcaptions[i].t = closedcaptions[i-1].t;
+			closedcaptions[i].b = closedcaptions[i-1].b;
+		}
+	}
+
+	closedcaptions[set].c = ((cnum == -1) ? NULL : &channels[cnum]);
+	closedcaptions[set].s = sfx;
+	closedcaptions[set].t = lifespan;
+	closedcaptions[set].b = 2; // bob
+}
+
 void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume)
 {
 	INT32 sep, pitch, priority, cnum;
@@ -527,6 +629,9 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume)
 			sep = (~sep) & 255;
 #endif
 
+		// Handle closed caption input.
+		S_StartCaption(sfx_id, cnum, MAXCAPTIONTICS);
+
 		// Assigns the handle to one of the channels in the
 		// mix/output buffer.
 		channels[cnum].handle = I_StartSound(sfx_id, volume, sep, pitch, priority);
@@ -577,6 +682,9 @@ dontplay:
 		sep = (~sep) & 255;
 #endif
 
+	// Handle closed caption input.
+	S_StartCaption(sfx_id, cnum, MAXCAPTIONTICS);
+
 	// Assigns the handle to one of the channels in the
 	// mix/output buffer.
 	channels[cnum].handle = I_StartSound(sfx_id, volume, sep, pitch, priority);
@@ -598,6 +706,7 @@ void S_StartSound(const void *origin, sfxenum_t sfx_id)
 //				sfx_id = sfx_mario8;
 //				break;
 			case sfx_thok:
+			case sfx_wepfir:
 				sfx_id = sfx_mario7;
 				break;
 			case sfx_pop:
@@ -692,6 +801,7 @@ static INT32 actualmidimusicvolume;
 void S_UpdateSounds(void)
 {
 	INT32 audible, cnum, volume, sep, pitch;
+	UINT8 i;
 	channel_t *c;
 
 	listener_t listener;
@@ -719,9 +829,7 @@ void S_UpdateSounds(void)
 		I_UpdateMumble(NULL, listener);
 #endif
 
-		// Stop cutting FMOD out. WE'RE sick of it.
-		I_UpdateSound();
-		return;
+		goto notinlevel;
 	}
 
 	if (dedicated || nosound)
@@ -760,8 +868,7 @@ void S_UpdateSounds(void)
 	if (hws_mode != HWS_DEFAULT_MODE)
 	{
 		HW3S_UpdateSources();
-		I_UpdateSound();
-		return;
+		goto notinlevel;
 	}
 #endif
 
@@ -849,7 +956,26 @@ void S_UpdateSounds(void)
 		}
 	}
 
+notinlevel:
 	I_UpdateSound();
+
+	for (i = 0; i < NUMCAPTIONS; i++) // update captions
+	{
+		if (!closedcaptions[i].s)
+			continue;
+
+		if (!(--closedcaptions[i].t))
+		{
+			closedcaptions[i].c = NULL;
+			closedcaptions[i].s = NULL;
+		}
+		else if (closedcaptions[i].c && !I_SoundIsPlaying(closedcaptions[i].c->handle))
+		{
+			closedcaptions[i].c = NULL;
+			if (closedcaptions[i].t > CAPTIONFADETICS)
+				closedcaptions[i].t = CAPTIONFADETICS;
+		}
+	}
 }
 
 void S_SetSfxVolume(INT32 volume)
@@ -1305,6 +1431,12 @@ void S_StopMusic(void)
 
 	music_data = NULL;
 	music_name[0] = 0;
+
+	if (cv_closedcaptioning.value)
+	{
+		if (closedcaptions[0].s-S_sfx == sfx_None)
+			closedcaptions[0].t = CAPTIONFADETICS;
+	}
 }
 
 void S_SetDigMusicVolume(INT32 volume)
diff --git a/src/s_sound.h b/src/s_sound.h
index 39ec769a68b6aa13ac4056e26c4edec3c8af59fb..4b9735480b7ea1cd666431e5f6dac592d2bc6ec5 100644
--- a/src/s_sound.h
+++ b/src/s_sound.h
@@ -23,7 +23,7 @@
 #define PICKUP_SOUND 0x8000
 
 extern consvar_t stereoreverse;
-extern consvar_t cv_soundvolume, cv_digmusicvolume, cv_midimusicvolume;
+extern consvar_t cv_soundvolume, cv_closedcaptioning, cv_digmusicvolume, cv_midimusicvolume;
 extern consvar_t cv_numChannels;
 
 #ifdef SNDSERV
@@ -64,6 +64,34 @@ typedef struct {
 	angle_t angle;
 } listener_t;
 
+typedef struct
+{
+	// sound information (if null, channel avail.)
+	sfxinfo_t *sfxinfo;
+
+	// origin of sound
+	const void *origin;
+
+	// handle of the sound being played
+	INT32 handle;
+
+} channel_t;
+
+typedef struct {
+	channel_t *c;
+	sfxinfo_t *s;
+	UINT16 t;
+	UINT8 b;
+} caption_t;
+
+#define NUMCAPTIONS 8
+#define MAXCAPTIONTICS (2*TICRATE)
+#define CAPTIONFADETICS 20
+
+extern caption_t closedcaptions[NUMCAPTIONS];
+void S_StartCaption(sfxenum_t sfx_id, INT32 cnum, UINT16 lifespan);
+void S_ResetCaptions(void);
+
 // register sound vars and commands at game startup
 void S_RegisterSoundStuff(void);
 
diff --git a/src/screen.c b/src/screen.c
index 7fdef4b193db450efac798efbe6d48e05f0315ff..2e3d2e0f419545492d1660e4479b3bf335edfa92 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -28,6 +28,9 @@
 #include "d_main.h"
 #include "d_clisrv.h"
 #include "f_finale.h"
+#include "i_sound.h" // closed captions
+#include "s_sound.h" // ditto
+#include "g_game.h" // ditto
 
 
 #if defined (USEASM) && !defined (NORUSEASM)//&& (!defined (_MSC_VER) || (_MSC_VER <= 1200))
@@ -434,3 +437,39 @@ void SCR_DisplayTicRate(void)
 
 	lasttic = ontic;
 }
+
+void SCR_ClosedCaptions(void)
+{
+	UINT8 i;
+
+	for (i = 0; i < NUMCAPTIONS; i++)
+	{
+		INT32 flags, y;
+		char dot;
+		boolean music;
+
+		if (!closedcaptions[i].s)
+			continue;
+
+		if ((music = (closedcaptions[i].s-S_sfx == sfx_None)) && (closedcaptions[i].t < flashingtics) && (closedcaptions[i].t & 1))
+			continue;
+
+		flags = V_NOSCALESTART|V_ALLOWLOWERCASE;
+		y = vid.height-((i + 2)*10*vid.dupy);
+		dot = ' ';
+
+		if (closedcaptions[i].b)
+			y -= (closedcaptions[i].b--)*vid.dupy;
+
+		if (closedcaptions[i].t < CAPTIONFADETICS)
+			flags |= (((CAPTIONFADETICS-closedcaptions[i].t)/2)*V_10TRANS);
+
+		if (music)
+			dot = '\x19';
+		else if (closedcaptions[i].c && closedcaptions[i].c->origin)
+			dot = '\x1E';
+
+		V_DrawRightAlignedString(vid.width-(20*vid.dupx), y,
+		flags, va("%c [%s]", dot, (closedcaptions[i].s->caption[0] ? closedcaptions[i].s->caption : closedcaptions[i].s->name)));
+	}
+}
diff --git a/src/screen.h b/src/screen.h
index a61de7f92c4e8910424a71d0a74919f75562498e..d2d0e87ab6662d6c4ed31eda034d0c2d9c7fe6d3 100644
--- a/src/screen.h
+++ b/src/screen.h
@@ -180,5 +180,6 @@ FUNCMATH boolean SCR_IsAspectCorrect(INT32 width, INT32 height);
 
 // move out to main code for consistency
 void SCR_DisplayTicRate(void);
+void SCR_ClosedCaptions(void);
 #undef DNWH
 #endif //__SCREEN_H__
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index 71ee3f79474b6bb48b23a2fbf4127a74fd40e115..f72a9857d694d82c3f3d40c3ae3f06ee741cfcd8 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -590,70 +590,78 @@ static BOOL I_ReadyConsole(HANDLE ci)
 
 static boolean entering_con_command = false;
 
+static void Impl_HandleKeyboardConsoleEvent(KEY_EVENT_RECORD evt, HANDLE co)
+{
+	event_t event;
+	CONSOLE_SCREEN_BUFFER_INFO CSBI;
+	DWORD t;
+
+	memset(&event,0x00,sizeof (event));
+
+	if (evt.bKeyDown)
+	{
+		event.type = ev_console;
+		entering_con_command = true;
+		switch (evt.wVirtualKeyCode)
+		{
+			case VK_ESCAPE:
+			case VK_TAB:
+				event.data1 = KEY_NULL;
+				break;
+			case VK_SHIFT:
+				event.data1 = KEY_LSHIFT;
+				break;
+			case VK_RETURN:
+				entering_con_command = false;
+				// Fall through.
+			default:
+				event.data1 = MapVirtualKey(evt.wVirtualKeyCode,2); // convert in to char
+		}
+		if (co != INVALID_HANDLE_VALUE && GetFileType(co) == FILE_TYPE_CHAR && GetConsoleMode(co, &t))
+		{
+			if (event.data1 && event.data1 != KEY_LSHIFT && event.data1 != KEY_RSHIFT)
+			{
+#ifdef _UNICODE
+				WriteConsole(co, &evt.uChar.UnicodeChar, 1, &t, NULL);
+#else
+				WriteConsole(co, &evt.uChar.AsciiChar, 1 , &t, NULL);
+#endif
+			}
+			if (evt.wVirtualKeyCode == VK_BACK
+				&& GetConsoleScreenBufferInfo(co,&CSBI))
+			{
+				WriteConsoleOutputCharacterA(co, " ",1, CSBI.dwCursorPosition, &t);
+			}
+		}
+	}
+	else
+	{
+		event.type = ev_keyup;
+		switch (evt.wVirtualKeyCode)
+		{
+			case VK_SHIFT:
+				event.data1 = KEY_LSHIFT;
+				break;
+			default:
+				break;
+		}
+	}
+	if (event.data1) D_PostEvent(&event);
+}
+
 void I_GetConsoleEvents(void)
 {
-	event_t ev = {0,0,0,0};
 	HANDLE ci = GetStdHandle(STD_INPUT_HANDLE);
 	HANDLE co = GetStdHandle(STD_OUTPUT_HANDLE);
-	CONSOLE_SCREEN_BUFFER_INFO CSBI;
 	INPUT_RECORD input;
 	DWORD t;
 
 	while (I_ReadyConsole(ci) && ReadConsoleInput(ci, &input, 1, &t) && t)
 	{
-		memset(&ev,0x00,sizeof (ev));
 		switch (input.EventType)
 		{
 			case KEY_EVENT:
-				if (input.Event.KeyEvent.bKeyDown)
-				{
-					ev.type = ev_console;
-					entering_con_command = true;
-					switch (input.Event.KeyEvent.wVirtualKeyCode)
-					{
-						case VK_ESCAPE:
-						case VK_TAB:
-							ev.data1 = KEY_NULL;
-							break;
-						case VK_SHIFT:
-							ev.data1 = KEY_LSHIFT;
-							break;
-						case VK_RETURN:
-							entering_con_command = false;
-							// Fall through.
-						default:
-							ev.data1 = MapVirtualKey(input.Event.KeyEvent.wVirtualKeyCode,2); // convert in to char
-					}
-					if (co != INVALID_HANDLE_VALUE && GetFileType(co) == FILE_TYPE_CHAR && GetConsoleMode(co, &t))
-					{
-						if (ev.data1 && ev.data1 != KEY_LSHIFT && ev.data1 != KEY_RSHIFT)
-						{
-#ifdef _UNICODE
-							WriteConsole(co, &input.Event.KeyEvent.uChar.UnicodeChar, 1, &t, NULL);
-#else
-							WriteConsole(co, &input.Event.KeyEvent.uChar.AsciiChar, 1 , &t, NULL);
-#endif
-						}
-						if (input.Event.KeyEvent.wVirtualKeyCode == VK_BACK
-							&& GetConsoleScreenBufferInfo(co,&CSBI))
-						{
-							WriteConsoleOutputCharacterA(co, " ",1, CSBI.dwCursorPosition, &t);
-						}
-					}
-				}
-				else
-				{
-					ev.type = ev_keyup;
-					switch (input.Event.KeyEvent.wVirtualKeyCode)
-					{
-						case VK_SHIFT:
-							ev.data1 = KEY_LSHIFT;
-							break;
-						default:
-							break;
-					}
-				}
-				if (ev.data1) D_PostEvent(&ev);
+				Impl_HandleKeyboardConsoleEvent(input.Event.KeyEvent, co);
 				break;
 			case MOUSE_EVENT:
 			case WINDOW_BUFFER_SIZE_EVENT:
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index 7d33f2554520fa6ccdc82919d3ecb3f1b5803a0f..f4ebb6df9fce7df919b0ba68aec1b96f7e51cc85 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -158,7 +158,7 @@ static INT32 windowedModes[MAXWINMODES][2] =
 static void Impl_VideoSetupSDLBuffer(void);
 static void Impl_VideoSetupBuffer(void);
 static SDL_bool Impl_CreateWindow(SDL_bool fullscreen);
-static void Impl_SetWindowName(const char *title);
+//static void Impl_SetWindowName(const char *title);
 static void Impl_SetWindowIcon(void);
 
 static void SDLSetMode(INT32 width, INT32 height, SDL_bool fullscreen)
@@ -181,15 +181,13 @@ static void SDLSetMode(INT32 width, INT32 height, SDL_bool fullscreen)
 			wasfullscreen = SDL_TRUE;
 			SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
 		}
-		else if (wasfullscreen)
-		{
-			wasfullscreen = SDL_FALSE;
-			SDL_SetWindowFullscreen(window, 0);
-			SDL_SetWindowSize(window, width, height);
-			SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED_DISPLAY(1), SDL_WINDOWPOS_CENTERED_DISPLAY(1));
-		}
-		else
+		else // windowed mode
 		{
+			if (wasfullscreen)
+			{
+				wasfullscreen = SDL_FALSE;
+				SDL_SetWindowFullscreen(window, 0);
+			}
 			// Reposition window only in windowed mode
 			SDL_SetWindowSize(window, width, height);
 			SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED_DISPLAY(1), SDL_WINDOWPOS_CENTERED_DISPLAY(1));
@@ -899,7 +897,7 @@ static inline boolean I_SkipFrame(void)
 {
 	static boolean skip = false;
 
-	if (render_soft != rendermode)
+	if (rendermode != render_soft)
 		return false;
 
 	skip = !skip;
@@ -928,10 +926,14 @@ void I_FinishUpdate(void)
 	if (I_SkipFrame())
 		return;
 
+	// draw captions if enabled
+	if (cv_closedcaptioning.value)
+		SCR_ClosedCaptions();
+
 	if (cv_ticrate.value)
 		SCR_DisplayTicRate();
 
-	if (render_soft == rendermode && screens[0])
+	if (rendermode == render_soft && screens[0])
 	{
 		SDL_Rect rect;
 
@@ -958,7 +960,7 @@ void I_FinishUpdate(void)
 	}
 
 #ifdef HWRENDER
-	else
+	else if (rendermode == render_opengl)
 	{
 		OglSdlFinishUpdate(cv_vidwait.value);
 	}
@@ -1186,11 +1188,11 @@ INT32 VID_SetMode(INT32 modeNum)
 		}
 		vid.modenum = -1;
 	}
-	Impl_SetWindowName("SRB2 "VERSIONSTRING);
+	//Impl_SetWindowName("SRB2 "VERSIONSTRING);
 
-	SDLSetMode(windowedModes[modeNum][0], windowedModes[modeNum][1], USE_FULLSCREEN);
+	SDLSetMode(vid.width, vid.height, USE_FULLSCREEN);
 
-	if (render_soft == rendermode)
+	if (rendermode == render_soft)
 	{
 		if (bufSurface)
 		{
@@ -1209,30 +1211,20 @@ static SDL_bool Impl_CreateWindow(SDL_bool fullscreen)
 	int flags = 0;
 
 	if (rendermode == render_none) // dedicated
-	{
 		return SDL_TRUE; // Monster Iestyn -- not sure if it really matters what we return here tbh
-	}
 
 	if (window != NULL)
-	{
 		return SDL_FALSE;
-	}
 
 	if (fullscreen)
-	{
 		flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
-	}
 
 	if (borderlesswindow)
-	{
 		flags |= SDL_WINDOW_BORDERLESS;
-	}
 
 #ifdef HWRENDER
 	if (rendermode == render_opengl)
-	{
 		flags |= SDL_WINDOW_OPENGL;
-	}
 #endif
 
 	// Create a window
@@ -1261,7 +1253,13 @@ static SDL_bool Impl_CreateWindow(SDL_bool fullscreen)
 #endif
 	if (rendermode == render_soft)
 	{
-		renderer = SDL_CreateRenderer(window, -1, (usesdl2soft ? SDL_RENDERER_SOFTWARE : 0) | (cv_vidwait.value && !usesdl2soft ? SDL_RENDERER_PRESENTVSYNC : 0));
+		flags = 0; // Use this to set SDL_RENDERER_* flags now
+		if (usesdl2soft)
+			flags |= SDL_RENDERER_SOFTWARE;
+		else if (cv_vidwait.value)
+			flags |= SDL_RENDERER_PRESENTVSYNC;
+
+		renderer = SDL_CreateRenderer(window, -1, flags);
 		if (renderer == NULL)
 		{
 			CONS_Printf(M_GetText("Couldn't create rendering context: %s\n"), SDL_GetError());
@@ -1273,14 +1271,16 @@ static SDL_bool Impl_CreateWindow(SDL_bool fullscreen)
 	return SDL_TRUE;
 }
 
+/*
 static void Impl_SetWindowName(const char *title)
 {
-	if (window != NULL)
+	if (window == NULL)
 	{
 		return;
 	}
 	SDL_SetWindowTitle(window, title);
 }
+*/
 
 static void Impl_SetWindowIcon(void)
 {
@@ -1483,7 +1483,7 @@ void I_ShutdownGraphics(void)
 	rendermode = render_none;
 	if (icoSurface) SDL_FreeSurface(icoSurface);
 	icoSurface = NULL;
-	if (render_soft == oldrendermode)
+	if (oldrendermode == render_soft)
 	{
 		if (vidSurface) SDL_FreeSurface(vidSurface);
 		vidSurface = NULL;
diff --git a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
index 32ae88c0280168841ada1ff854aafa1a896b115c..68391f99ca9168b9f5c68d62657df71fae5ba167 100644
--- a/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
+++ b/src/sdl/macosx/Srb2mac.xcodeproj/project.pbxproj
@@ -1214,7 +1214,7 @@
 		C01FCF4B08A954540054247B /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.17;
+				CURRENT_PROJECT_VERSION = 2.1.19;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
 					NORMALSRB2,
@@ -1226,7 +1226,7 @@
 		C01FCF4C08A954540054247B /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.17;
+				CURRENT_PROJECT_VERSION = 2.1.19;
 				GCC_ENABLE_FIX_AND_CONTINUE = NO;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (
diff --git a/src/sdl/ogl_sdl.c b/src/sdl/ogl_sdl.c
index 21afd831dc1b6f74070c828680f6f0a02bc81753..cd7ced7cab1eed7886af073f1c187b40ee18fd7e 100644
--- a/src/sdl/ogl_sdl.c
+++ b/src/sdl/ogl_sdl.c
@@ -71,7 +71,6 @@ INT32 oglflags = 0;
 void *GLUhandle = NULL;
 SDL_GLContext sdlglcontext = 0;
 
-#ifndef STATIC_OPENGL
 void *GetGLFunc(const char *proc)
 {
 	if (strncmp(proc, "glu", 3) == 0)
@@ -83,7 +82,6 @@ void *GetGLFunc(const char *proc)
 	}
 	return SDL_GL_GetProcAddress(proc);
 }
-#endif
 
 boolean LoadGL(void)
 {
diff --git a/src/sdl12/i_video.c b/src/sdl12/i_video.c
index 1fa80e7d4343fd69a921bbe256e6354dffec8e93..0063fc09571f59f237199ab31c99729a2ce10076 100644
--- a/src/sdl12/i_video.c
+++ b/src/sdl12/i_video.c
@@ -1341,6 +1341,10 @@ void I_FinishUpdate(void)
 	if (I_SkipFrame())
 		return;
 
+	// draw captions if enabled
+	if (cv_closedcaptioning.value)
+		SCR_ClosedCaptions();
+
 	if (cv_ticrate.value)
 		SCR_DisplayTicRate();
 
diff --git a/src/sdl12/macosx/Srb2mac.xcodeproj/project.pbxproj b/src/sdl12/macosx/Srb2mac.xcodeproj/project.pbxproj
index 13e78f31472cb3c1f6565c4aa55e9a9f15a27c8e..fada7849c2a2305aed694cd3ebb52f10597f5217 100644
--- a/src/sdl12/macosx/Srb2mac.xcodeproj/project.pbxproj
+++ b/src/sdl12/macosx/Srb2mac.xcodeproj/project.pbxproj
@@ -1214,7 +1214,7 @@
 		C01FCF4B08A954540054247B /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.17;
+				CURRENT_PROJECT_VERSION = 2.1.19;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"$(inherited)",
 					NORMALSRB2,
@@ -1226,7 +1226,7 @@
 		C01FCF4C08A954540054247B /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				CURRENT_PROJECT_VERSION = 2.1.17;
+				CURRENT_PROJECT_VERSION = 2.1.19;
 				GCC_ENABLE_FIX_AND_CONTINUE = NO;
 				GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (
diff --git a/src/sounds.c b/src/sounds.c
index 395015296001e17d7bc528ea49bfb249c83157d0..2c1c5f3af6754747cbc41e52dee84abc3d6ba769 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -32,448 +32,448 @@ sfxinfo_t S_sfx[NUMSFX] =
 	various flags. See soundflags_t.
 *****/
   // S_sfx[0] needs to be a dummy for odd reasons. (don't modify this comment)
-//  name, singularity, priority, pitch, volume, data, length, skinsound, usefulness, lumpnum
-  {"none" ,  false,   0,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
+//  name, singularity, priority, pitch, volume, data, length, skinsound, usefulness, lumpnum, caption
+  {"none" ,  false,   0,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "///////////////////////////////"}, // maximum length
 
   // Skin Sounds
-  {"altdi1", false, 192, 16, -1, NULL, 0, SKSPLDET1,  -1, LUMPERROR},
-  {"altdi2", false, 192, 16, -1, NULL, 0, SKSPLDET2,  -1, LUMPERROR},
-  {"altdi3", false, 192, 16, -1, NULL, 0, SKSPLDET3,  -1, LUMPERROR},
-  {"altdi4", false, 192, 16, -1, NULL, 0, SKSPLDET4,  -1, LUMPERROR},
-  {"altow1", false, 192, 16, -1, NULL, 0, SKSPLPAN1,  -1, LUMPERROR},
-  {"altow2", false, 192, 16, -1, NULL, 0, SKSPLPAN2,  -1, LUMPERROR},
-  {"altow3", false, 192, 16, -1, NULL, 0, SKSPLPAN3,  -1, LUMPERROR},
-  {"altow4", false, 192, 16, -1, NULL, 0, SKSPLPAN4,  -1, LUMPERROR},
-  {"victr1", false,  64, 16, -1, NULL, 0, SKSPLVCT1,  -1, LUMPERROR},
-  {"victr2", false,  64, 16, -1, NULL, 0, SKSPLVCT2,  -1, LUMPERROR},
-  {"victr3", false,  64, 16, -1, NULL, 0, SKSPLVCT3,  -1, LUMPERROR},
-  {"victr4", false,  64, 16, -1, NULL, 0, SKSPLVCT4,  -1, LUMPERROR},
-  {"gasp" ,  false,  64,  0, -1, NULL, 0,   SKSGASP,  -1, LUMPERROR},
-  {"jump" ,  false, 140,  0, -1, NULL, 0,   SKSJUMP,  -1, LUMPERROR},
-  {"pudpud", false,  64,  0, -1, NULL, 0, SKSPUDPUD,  -1, LUMPERROR},
-  {"putput", false,  64,  0, -1, NULL, 0, SKSPUTPUT,  -1, LUMPERROR}, // not as high a priority
-  {"spin" ,  false, 100,  0, -1, NULL, 0,   SKSSPIN,  -1, LUMPERROR},
-  {"spndsh", false,  64,  1, -1, NULL, 0, SKSSPNDSH,  -1, LUMPERROR},
-  {"thok" ,  false,  96,  0, -1, NULL, 0,   SKSTHOK,  -1, LUMPERROR},
-  {"zoom" ,  false, 120,  1, -1, NULL, 0,   SKSZOOM,  -1, LUMPERROR},
-  {"skid",   false,  64, 32, -1, NULL, 0,   SKSSKID,  -1, LUMPERROR},
+  {"altdi1", false, 192, 16, -1, NULL, 0, SKSPLDET1,  -1, LUMPERROR, "Dying"},
+  {"altdi2", false, 192, 16, -1, NULL, 0, SKSPLDET2,  -1, LUMPERROR, "Dying"},
+  {"altdi3", false, 192, 16, -1, NULL, 0, SKSPLDET3,  -1, LUMPERROR, "Dying"},
+  {"altdi4", false, 192, 16, -1, NULL, 0, SKSPLDET4,  -1, LUMPERROR, "Dying"},
+  {"altow1", false, 192, 16, -1, NULL, 0, SKSPLPAN1,  -1, LUMPERROR, "Ring loss"},
+  {"altow2", false, 192, 16, -1, NULL, 0, SKSPLPAN2,  -1, LUMPERROR, "Ring loss"},
+  {"altow3", false, 192, 16, -1, NULL, 0, SKSPLPAN3,  -1, LUMPERROR, "Ring loss"},
+  {"altow4", false, 192, 16, -1, NULL, 0, SKSPLPAN4,  -1, LUMPERROR, "Ring loss"},
+  {"victr1", false,  64, 16, -1, NULL, 0, SKSPLVCT1,  -1, LUMPERROR, "/"},
+  {"victr2", false,  64, 16, -1, NULL, 0, SKSPLVCT2,  -1, LUMPERROR, "/"},
+  {"victr3", false,  64, 16, -1, NULL, 0, SKSPLVCT3,  -1, LUMPERROR, "/"},
+  {"victr4", false,  64, 16, -1, NULL, 0, SKSPLVCT4,  -1, LUMPERROR, "/"},
+  {"gasp" ,  false,  64,  0, -1, NULL, 0,   SKSGASP,  -1, LUMPERROR, "Bubble gasp"},
+  {"jump" ,  false, 140,  0, -1, NULL, 0,   SKSJUMP,  -1, LUMPERROR, "Jump"},
+  {"pudpud", false,  64,  0, -1, NULL, 0, SKSPUDPUD,  -1, LUMPERROR, "Tired flight"},
+  {"putput", false,  64,  0, -1, NULL, 0, SKSPUTPUT,  -1, LUMPERROR, "Flight"}, // not as high a priority
+  {"spin" ,  false, 100,  0, -1, NULL, 0,   SKSSPIN,  -1, LUMPERROR, "Spin"},
+  {"spndsh", false,  64,  1, -1, NULL, 0, SKSSPNDSH,  -1, LUMPERROR, "Spindash"},
+  {"thok" ,  false,  96,  0, -1, NULL, 0,   SKSTHOK,  -1, LUMPERROR, "Thok"},
+  {"zoom" ,  false, 120,  1, -1, NULL, 0,   SKSZOOM,  -1, LUMPERROR, "Spin launch"},
+  {"skid",   false,  64, 32, -1, NULL, 0,   SKSSKID,  -1, LUMPERROR, "Skid"},
 
   // Ambience/background objects/etc
-  {"ambint",  true,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"ambint",  true,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Obnoxious disco music"},
 
-  {"alarm",  false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"buzz1",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"buzz2",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"buzz3",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"buzz4",  false,   8,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"crumbl",  true, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Platform Crumble Tails 03-16-2001
-  {"fire",   false,   8, 32, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"grind",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"laser",   true,  16,  2, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"mswing", false,  16,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"pstart", false, 100,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"pstop",  false, 100,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"steam1", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Tails 06-19-2001
-  {"steam2", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Tails 06-19-2001
-  {"wbreak", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"alarm",  false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Alarm"},
+  {"buzz1",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Electric zap"},
+  {"buzz2",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Electric zap"},
+  {"buzz3",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Wacky worksurface"},
+  {"buzz4",  false,   8,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Buzz"},
+  {"crumbl",  true, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Crumbling"}, // Platform Crumble Tails 03-16-2001
+  {"fire",   false,   8, 32, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flamethrower"},
+  {"grind",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Metallic grinding"},
+  {"laser",   true,  16,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Laser hum"},
+  {"mswing", false,  16,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Swinging mace"},
+  {"pstart", false, 100,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "/"},
+  {"pstop",  false, 100,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Crusher stomp"},
+  {"steam1", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Steam jet"}, // Tails 06-19-2001
+  {"steam2", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Steam jet"}, // Tails 06-19-2001
+  {"wbreak", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Wood breaking"},
 
-  {"rainin",  true,  24,  4, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"litng1", false,  16,  2, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"litng2", false,  16,  2, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"litng3", false,  16,  2, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"litng4", false,  16,  2, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"athun1", false,  16,  2, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"athun2", false,  16,  2, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"rainin",  true,  24,  4, -1, NULL, 0,        -1,  -1, LUMPERROR, "Rain"},
+  {"litng1", false,  16,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Lightning"},
+  {"litng2", false,  16,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Lightning"},
+  {"litng3", false,  16,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Lightning"},
+  {"litng4", false,  16,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Lightning"},
+  {"athun1", false,  16,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Thunder"},
+  {"athun2", false,  16,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Thunder"},
 
-  {"amwtr1", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"amwtr2", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"amwtr3", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"amwtr4", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"amwtr5", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"amwtr6", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"amwtr7", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"amwtr8", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"bubbl1", false,  11,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"bubbl2", false,  11,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"bubbl3", false,  11,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"bubbl4", false,  11,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"bubbl5", false,  11,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"floush", false,  16,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"splash", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"splish", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Splish Tails 12-08-2000
-  {"wdrip1", false,   8,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"wdrip2", false,   8 , 0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"wdrip3", false,   8,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"wdrip4", false,   8,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"wdrip5", false,   8,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"wdrip6", false,   8,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"wdrip7", false,   8,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"wdrip8", false,   8,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"wslap",  false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Water Slap Tails 12-13-2000
+  {"amwtr1", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
+  {"amwtr2", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
+  {"amwtr3", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
+  {"amwtr4", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
+  {"amwtr5", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
+  {"amwtr6", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
+  {"amwtr7", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
+  {"amwtr8", false,  12,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running water"},
+  {"bubbl1", false,  11,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Glub"},
+  {"bubbl2", false,  11,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Glub"},
+  {"bubbl3", false,  11,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Glub"},
+  {"bubbl4", false,  11,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Glub"},
+  {"bubbl5", false,  11,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Glub"},
+  {"floush", false,  16,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Glub"},
+  {"splash", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Glub"}, // labeling sfx_splash as "glub" and sfx_splish as "splash" seems wrong but isn't
+  {"splish", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Splash"}, // Splish Tails 12-08-2000
+  {"wdrip1", false,   8,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drip"},
+  {"wdrip2", false,   8 , 0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drip"},
+  {"wdrip3", false,   8,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drip"},
+  {"wdrip4", false,   8,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drip"},
+  {"wdrip5", false,   8,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drip"},
+  {"wdrip6", false,   8,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drip"},
+  {"wdrip7", false,   8,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drip"},
+  {"wdrip8", false,   8,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drip"},
+  {"wslap",  false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Splash"}, // Water Slap Tails 12-13-2000
 
-  {"doora1", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"doorb1", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"doorc1", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"doorc2", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"doord1", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"doord2", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"eleva1", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"eleva2", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"eleva3", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"elevb1", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"elevb2", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"elevb3", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"doora1", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Sliding open"},
+  {"doorb1", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Sliding open"},
+  {"doorc1", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Wooden door opening"},
+  {"doorc2", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Slamming shut"},
+  {"doord1", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Creaking open"},
+  {"doord2", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Slamming shut"},
+  {"eleva1", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Starting elevator"},
+  {"eleva2", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Moving elevator"},
+  {"eleva3", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Stopping elevator"},
+  {"elevb1", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Starting elevator"},
+  {"elevb2", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Moving elevator"},
+  {"elevb3", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Stopping elevator"},
 
-  {"ambin2", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"lavbub", false,  64,  8, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"rocks1", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"rocks2", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"rocks3", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"rocks4", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"rumbam", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"rumble", false,  64, 24, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"ambin2", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Natural vibrations"},
+  {"lavbub", false,  64,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bubbling lava"},
+  {"rocks1", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Falling rocks"},
+  {"rocks2", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Falling rocks"},
+  {"rocks3", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Falling rocks"},
+  {"rocks4", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Falling rocks"},
+  {"rumbam", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominous rumbling"},
+  {"rumble", false,  64, 24, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominous rumbling"},
 
   // Game objects, etc
-  {"appear", false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"bkpoof", false,  70,  8, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"bnce1",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Boing!
-  {"bnce2",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Boing!
-  {"cannon", false,  64,  8, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"cgot" ,   true, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Got Emerald! Tails 09-02-2001
-  {"cybdth", false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"deton",   true,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"ding",   false, 127,  8, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"dmpain", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"drown",  false, 192,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"fizzle", false, 127,  8, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"gbeep",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Grenade beep
-  {"gclose", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"ghit" ,  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"gloop",  false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"gspray", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"gravch", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"itemup",  true, 255,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"jet",    false,   8,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"jshard",  true, 167,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"lose" ,  false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"lvpass", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"mindig", false,   8, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"mixup",   true, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"monton", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"pogo" ,  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"pop"  ,  false,  78,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"rail1",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"rail2",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"rlaunc", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"shield", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // generic GET!
-  {"wirlsg", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Whirlwind GET!
-  {"forcsg", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Force GET!
-  {"elemsg", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Elemental GET!
-  {"armasg", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Armaggeddon GET!
-  {"attrsg", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Attract GET!
-  {"shldls", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // You LOSE!
-  {"spdpad", false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"spkdth", false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"spring", false, 112,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"statu1",  true,  64,  2, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"statu2",  true,  64,  2, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"strpst",  true, 192,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Starpost Sound Tails 07-04-2002
-  {"supert",  true, 127,  2, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"telept", false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"tink" ,  false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"token" ,  true, 224,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // SS token
-  {"trfire",  true,  60,  8, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"trpowr",  true, 127,  8, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"turhit", false,  40,  8, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"wdjump", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"mswarp", false,  60, 16, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"mspogo", false,  60,  8, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"boingf", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"corkp",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"corkh",  false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"appear", false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Appearing platform"},
+  {"bkpoof", false,  70,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Armageddon explosion"},
+  {"bnce1",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bounce"}, // Boing!
+  {"bnce2",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Scatter"}, // Boing!
+  {"cannon", false,  64,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Powerful shot"},
+  {"cgot" ,   true, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got Chaos Emerald"}, // Got Emerald! Tails 09-02-2001
+  {"cybdth", false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Explosion"},
+  {"deton",   true,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominous beeping"},
+  {"ding",   false, 127,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ding"},
+  {"dmpain", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Machine damage"},
+  {"drown",  false, 192,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drowning"},
+  {"fizzle", false, 127,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Electric fizzle"},
+  {"gbeep",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominous beeping"}, // Grenade beep
+  {"wepfir", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Firing weapon"}, // defaults to thok
+  {"ghit" ,  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Goop splash"},
+  {"gloop",  false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Splash"},
+  {"gspray", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Goop sling"},
+  {"gravch", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Recycler"},
+  {"itemup",  true, 255,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Sparkle"},
+  {"jet",    false,   8,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Jet engine"},
+  {"jshard",  true, 167,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Life transfer"}, // placeholder repurpose; original string was "Got Shard"
+  {"lose" ,  false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Failure"},
+  {"lvpass", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spinning signpost"},
+  {"mindig", false,   8, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Tunnelling"},
+  {"mixup",   true, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Teleport"},
+  {"monton", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Golden Monitor activated"},
+  {"pogo" ,  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Mechanical pogo"},
+  {"pop"  ,  false,  78,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Pop"},
+  {"rail1",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Firing rail"},
+  {"rail2",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Crashing rail"},
+  {"rlaunc", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Firing"},
+  {"shield", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Pity Shield"}, // generic GET!
+  {"wirlsg", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Whirlwind Shield"}, // Whirlwind GET!
+  {"forcsg", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Force Shield"}, // Force GET!
+  {"elemsg", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Elemental Shield"}, // Elemental GET!
+  {"armasg", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Armageddon Shield"}, // Armaggeddon GET!
+  {"attrsg", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Attraction Shield"}, // Attract GET!
+  {"shldls", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Hurt"}, // You LOSE!
+  {"spdpad", false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Speedpad"},
+  {"spkdth", false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spiked"},
+  {"spring", false, 112,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spring"},
+  {"statu1",  true,  64,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Pushing a statue"},
+  {"statu2",  true,  64,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Pushing a statue"},
+  {"strpst",  true, 192,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Starpost"}, // Starpost Sound Tails 07-04-2002
+  {"supert",  true, 127,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Transformation"},
+  {"telept", false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Dash"},
+  {"tink" ,  false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Tink"},
+  {"token" ,  true, 224,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got Token"}, // SS token
+  {"trfire",  true,  60,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Laser fired"},
+  {"trpowr",  true, 127,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Powering up"},
+  {"turhit", false,  40,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Laser hit"},
+  {"wdjump", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Whirlwind jump"},
+  {"mswarp", false,  60, 16, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spinning out"},
+  {"mspogo", false,  60,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Breaking through"},
+  {"boingf", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bouncing"},
+  {"corkp",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Cork fired"},
+  {"corkh",  false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Cork hit"},
 
   // Menu, interface
-  {"chchng", false, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"dwnind", false, 212,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"emfind", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"flgcap", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"menu1",   true,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"oneup",   true, 192,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"ptally",  true,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Point tally is identical to menu for now
-  {"radio",  false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"wepchg",  true,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Weapon switch is identical to menu for now
-  {"wtrdng",  true, 212,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // make sure you can hear the DING DING! Tails 03-08-2000
-  {"zelda",  false, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"chchng", false, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Score"},
+  {"dwnind", false, 212,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Thinking about air"},
+  {"emfind", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Radar beep"},
+  {"flgcap", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flag captured"},
+  {"menu1",   true,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Menu beep"},
+  {"oneup",   true, 192,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "One-up"},
+  {"ptally",  true,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Tally"}, // Point tally is identical to menu for now
+  {"radio",  false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Notification"},
+  {"wepchg",  true,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Weapon change"}, // Weapon switch is identical to menu for now
+  {"wtrdng",  true, 212,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Aquaphobia"}, // make sure you can hear the DING DING! Tails 03-08-2000
+  {"zelda",  false, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Discovery"},
 
   // NiGHTS
-  {"ideya",  false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"xideya", false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Xmas
-  {"nbmper", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"nxbump", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Xmas
-  {"ncitem", false, 204,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"nxitem", false, 204,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Xmas
-  {"ngdone",  true, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"nxdone",  true, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Xmas
-  {"drill1", false,  48,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"drill2", false,  48,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"ncspec", false, 204,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Tails 12-15-2003
-  {"nghurt", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"ngskid", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"hoop1",  false, 192,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"hoop2",  false, 192,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"hoop3",  false, 192,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"hidden", false, 204,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"prloop", false, 104,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"timeup",  true, 256,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"ideya",  false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Success"},
+  {"xideya", false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Success"}, // Xmas
+  {"nbmper", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bumper"},
+  {"nxbump", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bumper"}, // Xmas
+  {"ncitem", false, 204,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got special"},
+  {"nxitem", false, 204,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got special"}, // Xmas
+  {"ngdone",  true, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bonus time start"},
+  {"nxdone",  true, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bonus time start"}, // Xmas
+  {"drill1", false,  48,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drill start"},
+  {"drill2", false,  48,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drill"},
+  {"ncspec", false, 204,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Power-up"}, // Tails 12-15-2003
+  {"nghurt", false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Hurt"},
+  {"ngskid", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Force stop"},
+  {"hoop1",  false, 192,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Hoop"},
+  {"hoop2",  false, 192,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Hoop+"},
+  {"hoop3",  false, 192,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Hoop++"},
+  {"hidden", false, 204,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Discovery"},
+  {"prloop", false, 104,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Gust of wind"},
+  {"timeup",  true, 256,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominous Countdown"},
 
   // Mario
-  {"koopfr" , true, 127,  8, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"mario1", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"mario2", false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"mario3", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"mario4",  true,  78,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"mario5", false,  78,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"mario6", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"mario7", false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"mario8", false,  48,  8, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"mario9",  true, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"marioa",  true, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"thwomp",  true, 127,  8, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"koopfr" , true, 127,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Fire"},
+  {"mario1", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Hitting a ceiling"},
+  {"mario2", false, 127,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Koopa shell"},
+  {"mario3", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Power-up"},
+  {"mario4",  true,  78,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got coin"},
+  {"mario5", false,  78,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Boot"},
+  {"mario6", false,  60,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Jump"},
+  {"mario7", false,  32,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Fire"},
+  {"mario8", false,  48,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Hurt"},
+  {"mario9",  true, 120,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Emerging"},
+  {"marioa",  true, 192,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "One-up"},
+  {"thwomp",  true, 127,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, "Thwomp"},
 
   // Black Eggman
-  {"bebomb", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"bechrg", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"becrsh", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"bedeen", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"bedie1", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"bedie2", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"beeyow", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"befall", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"befire", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"beflap", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"begoop", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"begrnd", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"behurt", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"bejet1", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"belnch", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"beoutb", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"beragh", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"beshot", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"bestep", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"bestp2", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"bewar1", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"bewar2", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"bewar3", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"bewar4", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"bexpld", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"bgxpld", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"bebomb", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Big explosion"},
+  {"bechrg", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Powering up"},
+  {"becrsh", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Crash"},
+  {"bedeen", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Metallic crash"},
+  {"bedie1", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Eggman crying"},
+  {"bedie2", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Eggman crying"},
+  {"beeyow", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Failing machinery"},
+  {"befall", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Metallic slam"},
+  {"befire", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Firing goop"},
+  {"beflap", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Mechanical jump"},
+  {"begoop", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Powerful shot"},
+  {"begrnd", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Metallic grinding"},
+  {"behurt", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Eggman shocked"},
+  {"bejet1", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Jetpack charge"},
+  {"belnch", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Mechanical jump"},
+  {"beoutb", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Failed shot"},
+  {"beragh", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Eggman screaming"},
+  {"beshot", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Firing missile"},
+  {"bestep", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Mechanical stomp"},
+  {"bestp2", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Mechanical stomp"},
+  {"bewar1", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Eggman laughing"},
+  {"bewar2", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Eggman laughing"},
+  {"bewar3", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Eggman laughing"},
+  {"bewar4", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Eggman laughing"},
+  {"bexpld", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Explosion"},
+  {"bgxpld", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Explosion"},
 
   // Cybrakdemon
-  {"beelec", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"brakrl", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"brakrx", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"beelec", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Electricity"},
+  {"brakrl", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Rocket launch"},
+  {"brakrx", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Rocket explosion"},
 
   // S3&K sounds
-  {"s3k33",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k34",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k35",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k36",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k37",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k38",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k39",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k3a",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k3b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k3c",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k3d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k3e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k3f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k40",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k41",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k42",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k43",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k44",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k45",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k46",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k47",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k48",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k49",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k4a",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k4b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k4c",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k4d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k4e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k4f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k50",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k51",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k52",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k53",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k54",  false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR}, // MetalSonic shot fire
-  {"s3k55",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k56",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k57",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k58",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k59",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k5a",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k5b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k5c",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k5d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k5e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k5f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k60",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k61",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k62",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k63",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k64",  false,  64,  2, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k65",  false, 255,  0, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Blue Spheres
-  {"s3k66",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k67",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k68",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k69",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k6a",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k6b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k6c",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k6d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k6e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k6f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k70",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k71",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k72",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k73",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k74",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k75",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k76",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k77",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k78",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k79",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k7a",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k7b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k7c",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k7d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k7e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k7f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k80",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k81",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k82",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k83",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k84",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k85",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k86",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k87",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k88",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k89",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k8a",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k8b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k8c",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k8d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k8e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k8f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k90",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k91",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k92",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k93",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k94",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k95",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k96",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k97",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k98",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k99",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k9a",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k9b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k9c",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k9d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k9e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3k9f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3ka0",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3ka1",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3ka2",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3ka3",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3ka4",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3ka5",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3ka6",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3ka7",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3ka8",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3ka9",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kaa",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kab",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kac",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kad",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kae",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kaf",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kb0",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kb1",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kb2",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kb3",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kb4",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kb5",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kb6",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kb7",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kb8",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kb9",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kba",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kbb",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kbcs", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kbcl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kbds", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kbdl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kbes", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kbel", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kbfs", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kbfl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kc0s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kc0l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kc1s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kc1l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kc2s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kc2l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kc3s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kc3l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kc4s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kc4l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kc5s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kc5l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kc6s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kc6l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kc7",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kc8s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kc8l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kc9s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kc9l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kcas", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kcal", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kcbs", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kcbl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kccs", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kccl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kcds", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kcdl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kces", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kcel", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kcfs", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kcfl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kd0s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kd0l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kd1s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kd1l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kd2s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kd2l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kd3s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kd3l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kd4s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kd4l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kd5s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kd5l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kd6s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kd6l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kd7s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kd7l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kd8s", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR}, // Sharp Spin (maybe use the long/L version?)
-  {"s3kd8l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kd9s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kd9l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kdas", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kdal", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kdbs", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
-  {"s3kdbl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR},
+  {"s3k33",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Sparkle"}, // stereo in original game, identical to latter
+  {"s3k34",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Sparkle"}, // mono in original game, identical to previous
+  {"s3k35",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Hurt"},
+  {"s3k36",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Skid"},
+  {"s3k37",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spiked"},
+  {"s3k38",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bubble gasp"},
+  {"s3k39",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Splash"},
+  {"s3k3a",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Shield"},
+  {"s3k3b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drowning"},
+  {"s3k3c",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spin"},
+  {"s3k3d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Pop"},
+  {"s3k3e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flame Shield"},
+  {"s3k3f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bubble Shield"},
+  {"s3k40",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Attraction shot"},
+  {"s3k41",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Lightning Shield"},
+  {"s3k42",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Twinspin"},
+  {"s3k43",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flame burst"},
+  {"s3k44",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bubble bounce"},
+  {"s3k45",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Lightning zap"},
+  {"s3k46",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Transformation"},
+  {"s3k47",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Rising dust"},
+  {"s3k48",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Metallic clink"},
+  {"s3k49",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Falling rock"},
+  {"s3k4a",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Grab"},
+  {"s3k4b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Water splash"},
+  {"s3k4c",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Heavy landing"},
+  {"s3k4d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Firing bullet"},
+  {"s3k4e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bomb explosion"},
+  {"s3k4f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flamethrower"},
+  {"s3k50",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Siren"},
+  {"s3k51",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Falling bomb"},
+  {"s3k52",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spike"},
+  {"s3k53",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Powering up"},
+  {"s3k54",  false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Firing"}, // MetalSonic shot fire
+  {"s3k55",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Mechanical movement"},
+  {"s3k56",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Heavy landing"},
+  {"s3k57",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Splash"},
+  {"s3k58",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Mechanical movement"},
+  {"s3k59",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Crumbling"},
+  {"s3k5a",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Aiming"},
+  {"s3k5b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Menu beep"},
+  {"s3k5c",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Electric spark"},
+  {"s3k5d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bouncing missile"},
+  {"s3k5e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Firing laser"},
+  {"s3k5f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Crusher stomp"},
+  {"s3k60",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flying away"},
+  {"s3k61",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drilling"},
+  {"s3k62",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Jump"},
+  {"s3k63",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Starpost"},
+  {"s3k64",  false,  64,  2, -1, NULL, 0,        -1,  -1, LUMPERROR, "Clatter"},
+  {"s3k65",  false, 255,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got blue sphere"}, // Blue Spheres
+  {"s3k66",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Special stage clear"},
+  {"s3k67",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Firing missile"},
+  {"s3k68",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Unknown possibilities"},
+  {"s3k69",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Switch click"},
+  {"s3k6a",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Special stage clear"},
+  {"s3k6b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s3k6c",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Burst"},
+  {"s3k6d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s3k6e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Mechanical damage"},
+  {"s3k6f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominous rumbling"},
+  {"s3k70",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Burst"},
+  {"s3k71",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Basic Shield"},
+  {"s3k72",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Movement"},
+  {"s3k73",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Warp"},
+  {"s3k74",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Gong"},
+  {"s3k75",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Rising"},
+  {"s3k76",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Click"},
+  {"s3k77",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Balloon pop"},
+  {"s3k78",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Magnet"},
+  {"s3k79",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Electric charge"},
+  {"s3k7a",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Rising from lava"},
+  {"s3k7b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Soft bounce"},
+  {"s3k7c",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Magnet"},
+  {"s3k7d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s3k7e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Eating dirt"},
+  {"s3k7f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Freeze"},
+  {"s3k80",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ice spike burst"},
+  {"s3k81",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Burst"},
+  {"s3k82",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Burst"},
+  {"s3k83",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Collapsing"},
+  {"s3k84",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Powering up"},
+  {"s3k85",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Powering down"},
+  {"s3k86",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Alarm"},
+  {"s3k87",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bounce"},
+  {"s3k88",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Metallic squeak"},
+  {"s3k89",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Advanced technology"},
+  {"s3k8a",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Boing"},
+  {"s3k8b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Powerful hit"},
+  {"s3k8c",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s3k8d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s3k8e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s3k8f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Opening"},
+  {"s3k90",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Closing"},
+  {"s3k91",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Closed"},
+  {"s3k92",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ghost"},
+  {"s3k93",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Rebuilding"},
+  {"s3k94",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spike"},
+  {"s3k95",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Rising from lava"},
+  {"s3k96",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Falling object"},
+  {"s3k97",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Wind"},
+  {"s3k98",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Falling spike"},
+  {"s3k99",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bounce"},
+  {"s3k9a",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Click"},
+  {"s3k9b",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Crusher stomp"},
+  {"s3k9c",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got Super Emerald"},
+  {"s3k9d",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Targeting"},
+  {"s3k9e",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Wham"},
+  {"s3k9f",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Transformation"},
+  {"s3ka0",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Launch"},
+  {"s3ka1",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s3ka2",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Launch"},
+  {"s3ka3",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Lift"},
+  {"s3ka4",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Powering up"},
+  {"s3ka5",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s3ka6",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Attraction fizzle"},
+  {"s3ka7",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Countdown beep"},
+  {"s3ka8",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Energy"},
+  {"s3ka9",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Aquaphobia"},
+  {"s3kaa",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Bumper"},
+  {"s3kab",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spindash"},
+  {"s3kac",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Got Continue"},
+  {"s3kad",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "GO!"},
+  {"s3kae",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Pinball flipper"},
+  {"s3kaf",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "To Special Stage"},
+  {"s3kb0",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Score"},
+  {"s3kb1",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spring"},
+  {"s3kb2",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Failure"},
+  {"s3kb3",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Warp"},
+  {"s3kb4",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Explosion"},
+  {"s3kb5",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Clink"},
+  {"s3kb6",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Spin launch"},
+  {"s3kb7",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Tumbler"},
+  {"s3kb8",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Falling signpost"},
+  {"s3kb9",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ring loss"},
+  {"s3kba",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flight"},
+  {"s3kbb",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Tired flight"},
+  {"s3kbcs", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s3kbcl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""}, // long version of previous
+  {"s3kbds", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flying fortress"},
+  {"s3kbdl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flying fortress"}, // ditto
+  {"s3kbes", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flying away"},
+  {"s3kbel", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flying away"}, // ditto
+  {"s3kbfs", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Turbine"},
+  {"s3kbfl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Turbine"}, // ditto
+  {"s3kc0s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Turbine"},
+  {"s3kc0l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Turbine"}, // ditto
+  {"s3kc1s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Fan"},
+  {"s3kc1l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Fan"}, // ditto
+  {"s3kc2s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flamethrower"},
+  {"s3kc2l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Flamethrower"}, // ditto
+  {"s3kc3s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Levitation"},
+  {"s3kc3l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Levitation"}, // ditto
+  {"s3kc4s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Firing laser"},
+  {"s3kc4l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Firing laser"}, // ditto
+  {"s3kc5s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s3kc5l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""}, // ditto
+  {"s3kc6s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Orbiting"},
+  {"s3kc6l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Orbiting"}, // ditto
+  {"s3kc7",  false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Aiming"},
+  {"s3kc8s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Sliding"},
+  {"s3kc8l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Sliding"}, // ditto
+  {"s3kc9s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Swinging"},
+  {"s3kc9l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Swinging"}, // ditto
+  {"s3kcas", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Energy"},
+  {"s3kcal", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Energy"}, // ditto
+  {"s3kcbs", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominous rumbling"},
+  {"s3kcbl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominuous rumbling"}, // ditto
+  {"s3kccs", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Collapsing"},
+  {"s3kccl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Collapsing"}, // ditto
+  {"s3kcds", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominous rumbling"},
+  {"s3kcdl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Ominous rumbling"}, // ditto
+  {"s3kces", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Wind tunnel"},
+  {"s3kcel", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Wind tunnel"}, // ditto
+  {"s3kcfs", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s3kcfl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""}, // ditto
+  {"s3kd0s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Rising"},
+  {"s3kd0l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Rising"}, // ditto
+  {"s3kd1s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s3kd1l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""}, // ditto
+  {"s3kd2s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Turning"},
+  {"s3kd2l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Turning"}, // ditto
+  {"s3kd3s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s3kd3l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""}, // ditto
+  {"s3kd4s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Engine"},
+  {"s3kd4l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Engine"}, // ditto
+  {"s3kd5s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Falling lava"},
+  {"s3kd5l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Falling lava"}, // ditto
+  {"s3kd6s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"s3kd6l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""}, // ditto
+  {"s3kd7s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Movement"},
+  {"s3kd7l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Movement"}, // ditto
+  {"s3kd8s", false,  64, 64, -1, NULL, 0,        -1,  -1, LUMPERROR, "Acceleration"}, // Sharp Spin (maybe use the long/L version?)
+  {"s3kd8l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Acceleration"}, // ditto
+  {"s3kd9s", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Magnetism"},
+  {"s3kd9l", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Magnetism"}, // ditto
+  {"s3kdas", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Click"},
+  {"s3kdal", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Click"}, // ditto
+  {"s3kdbs", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running on water"},
+  {"s3kdbl", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Running on water"}, // ditto
 
   // skin sounds free slots to add sounds at run time (Boris HACK!!!)
   // initialized to NULL
@@ -513,6 +513,8 @@ void S_InitRuntimeSounds (void)
 		S_sfx[i].skinsound = -1;
 		S_sfx[i].usefulness = -1;
 		S_sfx[i].lumpnum = LUMPERROR;
+		//strlcpy(S_sfx[i].caption, "", 1);
+		S_sfx[i].caption[0] = '\0';
 	}
 }
 
diff --git a/src/sounds.h b/src/sounds.h
index 0442ebb055cb57e9a257bc1aa47e8dfb16bfeed8..60451b6ea8e3475ab0aac20760678c1611b648ef 100644
--- a/src/sounds.h
+++ b/src/sounds.h
@@ -84,6 +84,9 @@ struct sfxinfo_struct
 
 	// lump number of sfx
 	lumpnum_t lumpnum;
+
+	// closed caption info/wiki table bait
+	char caption[32];
 };
 
 // the complete set of sound effects
@@ -210,7 +213,7 @@ typedef enum
 	sfx_drown,
 	sfx_fizzle,
 	sfx_gbeep,
-	sfx_gclose,
+	sfx_wepfir,
 	sfx_ghit,
 	sfx_gloop,
 	sfx_gspray,
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 4af3199a19d231216572b95c0ace17c4b4760a63..4515495af84b621d31d915514ffc112fc279fc2e 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -68,6 +68,7 @@ static patch_t *sboover;
 static patch_t *timeover;
 static patch_t *stlivex;
 static patch_t *sboredrings;
+static patch_t *sboredtime;
 static patch_t *getall; // Special Stage HUD
 static patch_t *timeup; // Special Stage HUD
 static patch_t *hunthoming[6];
@@ -78,12 +79,6 @@ static patch_t *race3;
 static patch_t *racego;
 static patch_t *ttlnum;
 static patch_t *nightslink;
-static patch_t *count5;
-static patch_t *count4;
-static patch_t *count3;
-static patch_t *count2;
-static patch_t *count1;
-static patch_t *count0;
 static patch_t *curweapon;
 static patch_t *normring;
 static patch_t *bouncering;
@@ -137,25 +132,25 @@ hudinfo_t hudinfo[NUMHUDITEMS] =
 
 	{  16,  42}, // HUD_RINGS
 	{ 220,  10}, // HUD_RINGSSPLIT
-	{ 112,  42}, // HUD_RINGSNUM
-	{ 288,  10}, // HUD_RINGSNUMSPLIT
+	{ 120,  42}, // HUD_RINGSNUM
+	{ 296,  10}, // HUD_RINGSNUMSPLIT
 
 	{  16,  10}, // HUD_SCORE
-	{ 128,  10}, // HUD_SCORENUM
+	{ 120,  10}, // HUD_SCORENUM
 
 	{  16,  26}, // HUD_TIME
-	{ 136,  10}, // HUD_TIMESPLIT
-	{  88,  26}, // HUD_MINUTES
+	{ 128,  10}, // HUD_TIMESPLIT
+	{  96,  26}, // HUD_MINUTES
 	{ 188,  10}, // HUD_MINUTESSPLIT
-	{  88,  26}, // HUD_TIMECOLON
+	{  96,  26}, // HUD_TIMECOLON
 	{ 188,  10}, // HUD_TIMECOLONSPLIT
-	{ 112,  26}, // HUD_SECONDS
+	{ 120,  26}, // HUD_SECONDS
 	{ 212,  10}, // HUD_SECONDSSPLIT
-	{ 112,  26}, // HUD_TIMETICCOLON
-	{ 136,  26}, // HUD_TICS
+	{ 120,  26}, // HUD_TIMETICCOLON
+	{ 144,  26}, // HUD_TICS
 
-	{ 112,  56}, // HUD_SS_TOTALRINGS
-	{ 288,  40}, // HUD_SS_TOTALRINGS_SPLIT
+	{ 120,  56}, // HUD_SS_TOTALRINGS
+	{ 296,  40}, // HUD_SS_TOTALRINGS_SPLIT
 
 	{ 110,  93}, // HUD_GETRINGS
 	{ 160,  93}, // HUD_GETRINGSNUM
@@ -257,6 +252,7 @@ void ST_LoadGraphics(void)
 	sboredrings = W_CachePatchName("STTRRING", PU_HUDGFX);
 	sboscore = W_CachePatchName("STTSCORE", PU_HUDGFX);
 	sbotime = W_CachePatchName("STTTIME", PU_HUDGFX); // Time logo
+	sboredtime = W_CachePatchName("STTRTIME", PU_HUDGFX);
 	sbocolon = W_CachePatchName("STTCOLON", PU_HUDGFX); // Colon for time
 	sboperiod = W_CachePatchName("STTPERIO", PU_HUDGFX); // Period for time centiseconds
 
@@ -272,12 +268,6 @@ void ST_LoadGraphics(void)
 	race3 = W_CachePatchName("RACE3", PU_HUDGFX);
 	racego = W_CachePatchName("RACEGO", PU_HUDGFX);
 	nightslink = W_CachePatchName("NGHTLINK", PU_HUDGFX);
-	count5 = W_CachePatchName("DRWNF0", PU_HUDGFX);
-	count4 = W_CachePatchName("DRWNE0", PU_HUDGFX);
-	count3 = W_CachePatchName("DRWND0", PU_HUDGFX);
-	count2 = W_CachePatchName("DRWNC0", PU_HUDGFX);
-	count1 = W_CachePatchName("DRWNB0", PU_HUDGFX);
-	count0 = W_CachePatchName("DRWNA0", PU_HUDGFX);
 
 	for (i = 0; i < 6; ++i)
 	{
@@ -356,15 +346,6 @@ void ST_LoadFaceGraphics(char *facestr, char *superstr, INT32 skinnum)
 	facefreed[skinnum] = false;
 }
 
-#ifdef DELFILE
-void ST_UnLoadFaceGraphics(INT32 skinnum)
-{
-	Z_Free(faceprefix[skinnum]);
-	Z_Free(superprefix[skinnum]);
-	facefreed[skinnum] = true;
-}
-#endif
-
 void ST_ReloadSkinFaceGraphics(void)
 {
 	INT32 i;
@@ -513,12 +494,12 @@ static INT32 SCR(INT32 r)
 #define ST_DrawPaddedOverlayNum(x,y,n,d)   V_DrawPaddedTallNum(x, y, V_NOSCALESTART|V_HUDTRANS, n, d)
 #define ST_DrawOverlayPatch(x,y,p)         V_DrawScaledPatch(x, y, V_NOSCALESTART|V_HUDTRANS, p)
 #define ST_DrawMappedOverlayPatch(x,y,p,c) V_DrawMappedScaledPatch(x, y, V_NOSCALESTART|V_HUDTRANS, p, c)
-#define ST_DrawNumFromHud(h,n)        V_DrawTallNum(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART|V_HUDTRANS, n)
-#define ST_DrawPadNumFromHud(h,n,q)   V_DrawPaddedTallNum(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART|V_HUDTRANS, n, q)
-#define ST_DrawPatchFromHud(h,p)      V_DrawScaledPatch(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART|V_HUDTRANS, p)
-#define ST_DrawNumFromHudWS(h,n)      V_DrawTallNum(SCX(hudinfo[h+!!splitscreen].x), SCY(hudinfo[h+!!splitscreen].y), V_NOSCALESTART|V_HUDTRANS, n)
-#define ST_DrawPadNumFromHudWS(h,n,q) V_DrawPaddedTallNum(SCX(hudinfo[h+!!splitscreen].x), SCY(hudinfo[h+!!splitscreen].y), V_NOSCALESTART|V_HUDTRANS, n, q)
-#define ST_DrawPatchFromHudWS(h,p)    V_DrawScaledPatch(SCX(hudinfo[h+!!splitscreen].x), SCY(hudinfo[h+!!splitscreen].y), V_NOSCALESTART|V_HUDTRANS, p)
+#define ST_DrawNumFromHud(h,n,f)        V_DrawTallNum(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART|f, n)
+#define ST_DrawPadNumFromHud(h,n,q,f)   V_DrawPaddedTallNum(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART|f, n, q)
+#define ST_DrawPatchFromHud(h,p,f)      V_DrawScaledPatch(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART|f, p)
+#define ST_DrawNumFromHudWS(h,n,f)      V_DrawTallNum(SCX(hudinfo[h+!!splitscreen].x), SCY(hudinfo[h+!!splitscreen].y), V_NOSCALESTART|f, n)
+#define ST_DrawPadNumFromHudWS(h,n,q,f) V_DrawPaddedTallNum(SCX(hudinfo[h+!!splitscreen].x), SCY(hudinfo[h+!!splitscreen].y), V_NOSCALESTART|f, n, q)
+#define ST_DrawPatchFromHudWS(h,p,f)    V_DrawScaledPatch(SCX(hudinfo[h+!!splitscreen].x), SCY(hudinfo[h+!!splitscreen].y), V_NOSCALESTART|f, p)
 
 // Draw a number, scaled, over the view, maybe with set translucency
 // Always draw the number completely since it's overlay
@@ -630,16 +611,16 @@ static void ST_drawDebugInfo(void)
 static void ST_drawScore(void)
 {
 	// SCORE:
-	ST_DrawPatchFromHud(HUD_SCORE, sboscore);
+	ST_DrawPatchFromHud(HUD_SCORE, sboscore, V_HUDTRANS);
 	if (objectplacing)
 	{
 		if (op_displayflags > UINT16_MAX)
 			ST_DrawOverlayPatch(SCX(hudinfo[HUD_SCORENUM].x-tallminus->width), SCY(hudinfo[HUD_SCORENUM].y), tallminus);
 		else
-			ST_DrawNumFromHud(HUD_SCORENUM, op_displayflags);
+			ST_DrawNumFromHud(HUD_SCORENUM, op_displayflags, V_HUDTRANS);
 	}
 	else
-		ST_DrawNumFromHud(HUD_SCORENUM, stplyr->score);
+		ST_DrawNumFromHud(HUD_SCORENUM, stplyr->score,V_HUDTRANS);
 }
 
 static void ST_drawTime(void)
@@ -647,7 +628,7 @@ static void ST_drawTime(void)
 	INT32 seconds, minutes, tictrn, tics;
 
 	// TIME:
-	ST_DrawPatchFromHudWS(HUD_TIME, sbotime);
+	ST_DrawPatchFromHudWS(HUD_TIME, ((mapheaderinfo[gamemap-1]->countdown && countdowntimer < 11*TICRATE && leveltime/5 & 1) ? sboredtime : sbotime), V_HUDTRANS);
 
 	if (objectplacing)
 	{
@@ -658,24 +639,24 @@ static void ST_drawTime(void)
 	}
 	else
 	{
-		tics = stplyr->realtime;
+		tics = (mapheaderinfo[gamemap-1]->countdown ? countdowntimer : stplyr->realtime);
 		seconds = G_TicsToSeconds(tics);
 		minutes = G_TicsToMinutes(tics, true);
 		tictrn  = G_TicsToCentiseconds(tics);
 	}
 
 	if (cv_timetic.value == 1) // Tics only -- how simple is this?
-		ST_DrawNumFromHudWS(HUD_SECONDS, tics);
+		ST_DrawNumFromHudWS(HUD_SECONDS, tics, V_HUDTRANS);
 	else
 	{
-		ST_DrawNumFromHudWS(HUD_MINUTES, minutes); // Minutes
-		ST_DrawPatchFromHudWS(HUD_TIMECOLON, sbocolon); // Colon
-		ST_DrawPadNumFromHudWS(HUD_SECONDS, seconds, 2); // Seconds
+		ST_DrawNumFromHudWS(HUD_MINUTES, minutes, V_HUDTRANS); // Minutes
+		ST_DrawPatchFromHudWS(HUD_TIMECOLON, sbocolon, V_HUDTRANS); // Colon
+		ST_DrawPadNumFromHudWS(HUD_SECONDS, seconds, 2, V_HUDTRANS); // Seconds
 
 		if (!splitscreen && (cv_timetic.value == 2 || modeattacking)) // there's not enough room for tics in splitscreen, don't even bother trying!
 		{
-			ST_DrawPatchFromHud(HUD_TIMETICCOLON, sboperiod); // Period
-			ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2); // Tics
+			ST_DrawPatchFromHud(HUD_TIMETICCOLON, sboperiod, V_HUDTRANS); // Period
+			ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2, V_HUDTRANS); // Tics
 		}
 	}
 }
@@ -684,7 +665,7 @@ static inline void ST_drawRings(void)
 {
 	INT32 ringnum = max(stplyr->rings, 0);
 
-	ST_DrawPatchFromHudWS(HUD_RINGS, ((stplyr->rings <= 0 && leveltime/5 & 1) ? sboredrings : sborings));
+	ST_DrawPatchFromHudWS(HUD_RINGS, ((!stplyr->spectator && stplyr->rings <= 0 && leveltime/5 & 1) ? sboredrings : sborings), ((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS));
 
 	if (objectplacing)
 		ringnum = op_currentdoomednum;
@@ -697,7 +678,7 @@ static inline void ST_drawRings(void)
 				ringnum += players[i].rings;
 	}
 
-	ST_DrawNumFromHudWS(HUD_RINGSNUM, ringnum);
+	ST_DrawNumFromHudWS(HUD_RINGSNUM, ringnum, ((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS));
 }
 
 static void ST_drawLives(void)
@@ -740,9 +721,68 @@ static void ST_drawLives(void)
 	// x
 	V_DrawScaledPatch(hudinfo[HUD_LIVESX].x, hudinfo[HUD_LIVESX].y + (v_splitflag ? -4 : 0),
 		V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag, stlivex);
-	// lives
+
+	// lives number
+	if ((netgame || multiplayer) && gametype == GT_COOP)
+	{
+		switch (cv_cooplives.value)
+		{
+			case 0:
+				V_DrawCharacter(hudinfo[HUD_LIVESNUM].x - 8, hudinfo[HUD_LIVESNUM].y + (v_splitflag ? -4 : 0), '\x16' | 0x80 | V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag, false);
+				return;
+			case 3:
+				{
+					INT32 i, sum = 0;
+					boolean canrespawn = (stplyr->lives > 0);
+					for (i = 0; i < MAXPLAYERS; i++)
+					{
+						if (!playeringame[i])
+							continue;
+
+						if (players[i].lives < 1)
+							continue;
+
+						if (players[i].lives > 1)
+							canrespawn = true;
+
+						sum += (players[i].lives);
+					}
+					V_DrawRightAlignedString(hudinfo[HUD_LIVESNUM].x, hudinfo[HUD_LIVESNUM].y + (v_splitflag ? -4 : 0),
+						V_SNAPTOLEFT|V_SNAPTOBOTTOM|(canrespawn ? V_HUDTRANS : V_HUDTRANSHALF)|v_splitflag,
+						va("%d",sum));
+					return;
+				}
+#if 0 // render the number of lives you COULD steal
+			case 2:
+				{
+					INT32 i, sum = 0;
+					for (i = 0; i < MAXPLAYERS; i++)
+					{
+						if (!playeringame[i])
+							continue;
+
+						if (&players[i] == stplyr)
+							continue;
+
+						if (players[i].lives < 2)
+							continue;
+
+						sum += (players[i].lives - 1);
+					}
+					V_DrawString(hudinfo[HUD_LIVESNUM].x, hudinfo[HUD_LIVESNUM].y + (v_splitflag ? -4 : 0),
+						V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANSHALF|v_splitflag, va("/%d",sum));
+				}
+				// intentional fallthrough
+#endif
+			default:
+				// don't return so the SP one can be drawn below
+				break;
+		}
+	}
+
 	V_DrawRightAlignedString(hudinfo[HUD_LIVESNUM].x, hudinfo[HUD_LIVESNUM].y + (v_splitflag ? -4 : 0),
-		V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag, va("%d",stplyr->lives));
+		V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag,
+		va("%d",stplyr->lives));
 }
 
 static void ST_drawLevelTitle(void)
@@ -818,8 +858,15 @@ static void ST_drawFirstPersonHUD(void)
 	// Graue 06-18-2004: no V_NOSCALESTART, no SCX, no SCY, snap to right
 	if ((player->powers[pw_shield] & SH_NOSTACK & ~SH_FORCEHP) == SH_FORCE)
 	{
-		if ((player->powers[pw_shield] & SH_FORCEHP) > 0 || leveltime & 1)
-			p = forceshield;
+		UINT8 i, max = (player->powers[pw_shield] & SH_FORCEHP);
+		for (i = 0; i <= max; i++)
+		{
+			INT32 flags = (V_SNAPTORIGHT|V_SNAPTOTOP)|((i == max) ? V_HUDTRANS : V_HUDTRANSHALF);
+			if (splitscreen)
+				V_DrawSmallScaledPatch(312-(3*i), STRINGY(24)+(3*i), flags, forceshield);
+			else
+				V_DrawScaledPatch(304-(3*i), 24+(3*i), flags, forceshield);
+		}
 	}
 	else switch (player->powers[pw_shield] & SH_NOSTACK)
 	{
@@ -862,50 +909,48 @@ static void ST_drawFirstPersonHUD(void)
 
 	p = NULL;
 
-	// Display the countdown drown numbers!
-	if ((player->powers[pw_underwater] <= 11*TICRATE + 1
-		&& player->powers[pw_underwater] >= 10*TICRATE + 1)
-		|| (player->powers[pw_spacetime] <= 11*TICRATE + 1
-		&& player->powers[pw_spacetime] >= 10*TICRATE + 1))
-	{
-		p = count5;
-	}
-	else if ((player->powers[pw_underwater] <= 9*TICRATE + 1
-		&& player->powers[pw_underwater] >= 8*TICRATE + 1)
-		|| (player->powers[pw_spacetime] <= 9*TICRATE + 1
-		&& player->powers[pw_spacetime] >= 8*TICRATE + 1))
-	{
-		p = count4;
-	}
-	else if ((player->powers[pw_underwater] <= 7*TICRATE + 1
-		&& player->powers[pw_underwater] >= 6*TICRATE + 1)
-		|| (player->powers[pw_spacetime] <= 7*TICRATE + 1
-		&& player->powers[pw_spacetime] >= 6*TICRATE + 1))
-	{
-		p = count3;
-	}
-	else if ((player->powers[pw_underwater] <= 5*TICRATE + 1
-		&& player->powers[pw_underwater] >= 4*TICRATE + 1)
-		|| (player->powers[pw_spacetime] <= 5*TICRATE + 1
-		&& player->powers[pw_spacetime] >= 4*TICRATE + 1))
 	{
-		p = count2;
-	}
-	else if ((player->powers[pw_underwater] <= 3*TICRATE + 1
-		&& player->powers[pw_underwater] >= 2*TICRATE + 1)
-		|| (player->powers[pw_spacetime] <= 3*TICRATE + 1
-		&& player->powers[pw_spacetime] >= 2*TICRATE + 1))
-	{
-		p = count1;
-	}
-	else if ((player->powers[pw_underwater] <= 1*TICRATE + 1
-		&& player->powers[pw_underwater] > 1)
-		|| (player->powers[pw_spacetime] <= 1*TICRATE + 1
-		&& player->powers[pw_spacetime] > 1))
-	{
-		p = count0;
+		UINT32 airtime;
+		UINT32 frame = 0;
+		spriteframe_t *sprframe;
+		// If both air timers are active, use the air timer with the least time left
+		if (player->powers[pw_underwater] && player->powers[pw_spacetime])
+			airtime = min(player->powers[pw_underwater], player->powers[pw_spacetime]);
+		else // Use whichever one is active otherwise
+			airtime = (player->powers[pw_spacetime]) ? player->powers[pw_spacetime] : player->powers[pw_underwater];
+
+		if (!airtime)
+			return; // No air timers are active, nothing would be drawn anyway
+
+		airtime--; // The original code was all n*TICRATE + 1, so let's remove 1 tic for simplicity
+
+		if (airtime > 11*TICRATE)
+			return; // Not time to draw any drown numbers yet
+		// Choose which frame to use based on time left
+		if (airtime <= 11*TICRATE && airtime >= 10*TICRATE)
+			frame = 5;
+		else if (airtime <= 9*TICRATE && airtime >= 8*TICRATE)
+			frame = 4;
+		else if (airtime <= 7*TICRATE && airtime >= 6*TICRATE)
+			frame = 3;
+		else if (airtime <= 5*TICRATE && airtime >= 4*TICRATE)
+			frame = 2;
+		else if (airtime <= 3*TICRATE && airtime >= 2*TICRATE)
+			frame = 1;
+		else if (airtime <= 1*TICRATE && airtime > 0)
+			frame = 0;
+		else
+			return; // Don't draw anything between numbers
+
+		if (player->charflags & SF_MACHINE)
+			frame += 6;  // Robots use different drown numbers
+
+		// Get the front angle patch for the frame
+		sprframe = &sprites[SPR_DRWN].spriteframes[frame];
+		p = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
 	}
 
+	// Display the countdown drown numbers!
 	if (p)
 		V_DrawScaledPatch(SCX((BASEVIDWIDTH/2) - (SHORT(p->width)/2) + SHORT(p->leftoffset)), SCY(60 - SHORT(p->topoffset)),
 			V_NOSCALESTART|V_OFFSET|V_TRANSLUCENT, p);
@@ -915,15 +960,25 @@ static void ST_drawFirstPersonHUD(void)
 /*#define NUMLINKCOLORS 14
 static skincolors_t linkColor[NUMLINKCOLORS] =
 {SKINCOLOR_BEIGE,  SKINCOLOR_LAVENDER, SKINCOLOR_AZURE, SKINCOLOR_PEACH, SKINCOLOR_ORANGE,
- SKINCOLOR_MAGENTA, SKINCOLOR_SILVER,   SKINCOLOR_SUPERGOLD4,    SKINCOLOR_PINK,  SKINCOLOR_RED,
- SKINCOLOR_BLUE,   SKINCOLOR_GREEN,    SKINCOLOR_CYAN,      SKINCOLOR_GOLD};*/
+ SKINCOLOR_MAGENTA, SKINCOLOR_SILVER, SKINCOLOR_SUPERGOLD4, SKINCOLOR_PINK,  SKINCOLOR_RED,
+ SKINCOLOR_BLUE, SKINCOLOR_GREEN, SKINCOLOR_CYAN, SKINCOLOR_GOLD};*/
 
-// 2.2+: (unix time 1470866042) <Rob> Emerald, Aqua, Cyan, Blue, Pastel, Purple, Magenta, Rosy, Red, Orange, Gold, Yellow, Peridot
-#define NUMLINKCOLORS 13
+// 2.2 indev list: (unix time 1470866042) <Rob> Emerald, Aqua, Cyan, Blue, Pastel, Purple, Magenta, Rosy, Red, Orange, Gold, Yellow, Peridot
+/*#define NUMLINKCOLORS 13
 static skincolors_t linkColor[NUMLINKCOLORS] =
-{SKINCOLOR_EMERALD,  SKINCOLOR_AQUA, SKINCOLOR_CYAN, SKINCOLOR_BLUE, SKINCOLOR_PASTEL,
- SKINCOLOR_PURPLE, SKINCOLOR_MAGENTA,   SKINCOLOR_ROSY,    SKINCOLOR_RED,  SKINCOLOR_ORANGE,
- SKINCOLOR_GOLD,   SKINCOLOR_YELLOW,    SKINCOLOR_PERIDOT};
+{SKINCOLOR_EMERALD, SKINCOLOR_AQUA, SKINCOLOR_CYAN, SKINCOLOR_BLUE, SKINCOLOR_PASTEL,
+ SKINCOLOR_PURPLE, SKINCOLOR_MAGENTA, SKINCOLOR_ROSY, SKINCOLOR_RED,  SKINCOLOR_ORANGE,
+ SKINCOLOR_GOLD, SKINCOLOR_YELLOW, SKINCOLOR_PERIDOT};*/
+
+// 2.2+ list: [19:59:52] <baldobo> Ruby > Red > Flame > Sunset > Orange > Gold > Yellow > Lime > Green > Aqua  > cyan > Sky > Blue > Pastel > Purple > Bubblegum > Magenta > Rosy > repeat
+// [20:00:25] <baldobo> Also Icy for the link freeze text color
+// [20:04:03] <baldobo> I would start it on lime
+#define NUMLINKCOLORS 18
+static skincolors_t linkColor[NUMLINKCOLORS] =
+{SKINCOLOR_LIME, SKINCOLOR_EMERALD, SKINCOLOR_AQUA, SKINCOLOR_CYAN, SKINCOLOR_SKY,
+ SKINCOLOR_SAPPHIRE, SKINCOLOR_PASTEL, SKINCOLOR_PURPLE, SKINCOLOR_BUBBLEGUM, SKINCOLOR_MAGENTA,
+ SKINCOLOR_ROSY, SKINCOLOR_RUBY, SKINCOLOR_RED, SKINCOLOR_FLAME, SKINCOLOR_SUNSET,
+ SKINCOLOR_ORANGE, SKINCOLOR_GOLD, SKINCOLOR_YELLOW};
 
 static void ST_drawNightsRecords(void)
 {
@@ -1024,8 +1079,8 @@ static void ST_drawNiGHTSHUD(void)
 	stplyr->linkcount > minlink)
 	{
 		skincolors_t colornum = linkColor[((stplyr->linkcount-1) / 5) % NUMLINKCOLORS];
-		if (stplyr->powers[pw_nights_linkfreeze])
-			colornum = SKINCOLOR_WHITE;
+		if (stplyr->powers[pw_nights_linkfreeze] && (!(stplyr->powers[pw_nights_linkfreeze] & 2) || (stplyr->powers[pw_nights_linkfreeze] > flashingtics)))
+			colornum = SKINCOLOR_ICY;
 
 		if (stplyr->linktimer < 2*TICRATE/3)
 		{
@@ -1304,7 +1359,7 @@ static void ST_drawNiGHTSHUD(void)
 			realnightstime = lowest_time/TICRATE;
 		}
 
-		if (stplyr->powers[pw_flashing] > TICRATE ) // was hit
+		if (stplyr->powers[pw_flashing] > TICRATE) // was hit
 		{
 			UINT16 flashingLeft = stplyr->powers[pw_flashing]-(TICRATE);
 			if (flashingLeft < TICRATE/2) // Start fading out
@@ -1488,7 +1543,7 @@ static inline void ST_drawRaceHUD(void)
 {
 	if (leveltime >= TICRATE && leveltime < 5*TICRATE)
 	{
-		INT32 height = (BASEVIDHEIGHT/2);
+		INT32 height = ((3*BASEVIDHEIGHT)>>2) - 8;
 		INT32 bounce = (leveltime % TICRATE);
 		patch_t *racenum;
 		switch (leveltime/TICRATE)
@@ -1507,7 +1562,11 @@ static inline void ST_drawRaceHUD(void)
 				break;
 		}
 		if (bounce < 3)
+		{
 			height -= (2 - bounce);
+			if (!bounce)
+					S_StartSound(0, ((racenum == racego) ? sfx_s3kad : sfx_s3ka7));
+		}
 		V_DrawScaledPatch(SCX((BASEVIDWIDTH - SHORT(racenum->width))/2), (INT32)(SCY(height)), V_NOSCALESTART, racenum);
 	}
 
@@ -1642,21 +1701,21 @@ static inline void ST_drawTeamName(void)
 static void ST_drawSpecialStageHUD(void)
 {
 	if (totalrings > 0)
-		ST_DrawNumFromHudWS(HUD_SS_TOTALRINGS, totalrings);
+		ST_DrawNumFromHudWS(HUD_SS_TOTALRINGS, totalrings, V_HUDTRANS);
 
 	if (leveltime < 5*TICRATE && totalrings > 0)
 	{
-		ST_DrawPatchFromHud(HUD_GETRINGS, getall);
-		ST_DrawNumFromHud(HUD_GETRINGSNUM, totalrings);
+		ST_DrawPatchFromHud(HUD_GETRINGS, getall, V_HUDTRANS);
+		ST_DrawNumFromHud(HUD_GETRINGSNUM, totalrings, V_HUDTRANS);
 	}
 
 	if (sstimer)
 	{
 		V_DrawString(hudinfo[HUD_TIMELEFT].x, STRINGY(hudinfo[HUD_TIMELEFT].y), V_HUDTRANS, M_GetText("TIME LEFT"));
-		ST_DrawNumFromHud(HUD_TIMELEFTNUM, sstimer/TICRATE);
+		ST_DrawNumFromHud(HUD_TIMELEFTNUM, sstimer/TICRATE, V_HUDTRANS);
 	}
 	else
-		ST_DrawPatchFromHud(HUD_TIMEUP, timeup);
+		ST_DrawPatchFromHud(HUD_TIMEUP, timeup, V_HUDTRANS);
 }
 
 static INT32 ST_drawEmeraldHuntIcon(mobj_t *hunt, patch_t **patches, INT32 offset)
@@ -1819,7 +1878,11 @@ static void ST_overlayDrawer(void)
 	}
 
 	// GAME OVER pic
-	if (G_GametypeUsesLives() && stplyr->lives <= 0 && !(hu_showscores && (netgame || multiplayer)))
+	if ((gametype == GT_COOP)
+		&& (netgame || multiplayer)
+		&& (cv_cooplives.value == 0))
+	;
+	else if (G_GametypeUsesLives() && stplyr->lives <= 0 && !(hu_showscores && (netgame || multiplayer)))
 	{
 		patch_t *p;
 
@@ -1828,7 +1891,29 @@ static void ST_overlayDrawer(void)
 		else
 			p = sboover;
 
-		V_DrawScaledPatch((BASEVIDWIDTH - SHORT(p->width))/2, STRINGY(BASEVIDHEIGHT/2 - (SHORT(p->height)/2)), 0, p);
+		if ((gametype == GT_COOP)
+		&& (netgame || multiplayer)
+		&& (cv_cooplives.value != 1))
+		{
+			INT32 i;
+			for (i = 0; i < MAXPLAYERS; i++)
+			{
+				if (!playeringame[i])
+					continue;
+
+				if (&players[i] == stplyr)
+					continue;
+
+				if (players[i].lives > 0)
+				{
+					p = NULL;
+					break;
+				}
+			}
+		}
+
+		if (p)
+			V_DrawScaledPatch((BASEVIDWIDTH - SHORT(p->width))/2, STRINGY(BASEVIDHEIGHT/2 - (SHORT(p->height)/2)) - (splitscreen ? 4 : 0), (stplyr->spectator ? V_HUDTRANSHALF : V_HUDTRANS), p);
 	}
 
 
@@ -1913,15 +1998,49 @@ static void ST_overlayDrawer(void)
 	)
 		ST_drawLevelTitle();
 
-	if (!hu_showscores && !splitscreen && netgame && displayplayer == consoleplayer)
+	if (!hu_showscores && (netgame || multiplayer) && displayplayer == consoleplayer)
 	{
-		if (G_GametypeUsesLives() && stplyr->lives <= 0 && countdown != 1)
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), 0, M_GetText("Press F12 to watch another player."));
+		if (!stplyr->spectator && stplyr->exiting && cv_playersforexit.value && gametype == GT_COOP)
+		{
+			INT32 i, total = 0, exiting = 0;
+
+			for (i = 0; i < MAXPLAYERS; i++)
+			{
+				if (!playeringame[i] || players[i].spectator)
+					continue;
+				if (players[i].lives <= 0)
+					continue;
+
+				total++;
+				if (players[i].exiting)
+					exiting++;
+			}
+
+			if (cv_playersforexit.value != 4)
+			{
+				total *= cv_playersforexit.value;
+				if (total % 4) total += 4; // round up
+				total /= 4;
+			}
+
+			if (exiting >= total)
+				;
+			else
+			{
+				total -= exiting;
+				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(124), 0, va(M_GetText("%d more player%s required to exit."), total, ((total == 1) ? "" : "s")));
+				if (!splitscreen)
+					V_DrawCenteredString(BASEVIDWIDTH/2, 132, 0, M_GetText("Press F12 to watch another player."));
+			}
+		}
+		else if (!splitscreen && gametype != GT_COOP && (stplyr->exiting || (G_GametypeUsesLives() && stplyr->lives <= 0 && countdown != 1)))
+			V_DrawCenteredString(BASEVIDWIDTH/2, 132, 0, M_GetText("Press F12 to watch another player."));
 		else if (gametype == GT_HIDEANDSEEK &&
 		 (!stplyr->spectator && !(stplyr->pflags & PF_TAGIT)) && (leveltime > hidetime * TICRATE))
 		{
 			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(116), 0, M_GetText("You cannot move while hiding."));
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), 0, M_GetText("Press F12 to watch another player."));
+			if (!splitscreen)
+				V_DrawCenteredString(BASEVIDWIDTH/2, 132, 0, M_GetText("Press F12 to watch another player."));
 		}
 		else if (!G_PlatformGametype() && stplyr->playerstate == PST_DEAD && stplyr->lives) //Death overrides spectator text.
 		{
@@ -1931,7 +2050,7 @@ static void ST_overlayDrawer(void)
 			else
 				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_HUDTRANSHALF, M_GetText("Press Jump to respawn."));
 		}
-		else if (stplyr->spectator
+		else if (stplyr->spectator && (gametype != GT_COOP || stplyr->playerstate == PST_LIVE)
 #ifdef HAVE_BLUA
 		&& LUA_HudEnabled(hud_textspectator)
 #endif
@@ -1941,11 +2060,38 @@ static void ST_overlayDrawer(void)
 			if (G_GametypeHasTeams())
 				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_HUDTRANSHALF, M_GetText("Press Fire to be assigned to a team."));
 			else if (G_IsSpecialStage(gamemap) && useNightsSS)
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_HUDTRANSHALF, M_GetText("You cannot join the game until the stage has ended."));
-			else
+				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_HUDTRANSHALF, M_GetText("You cannot play until the stage has ended."));
+			else if (gametype == GT_COOP && stplyr->lives <= 0)
+			{
+				if (cv_cooplives.value == 1
+				&& (netgame || multiplayer))
+				{
+					INT32 i;
+					for (i = 0; i < MAXPLAYERS; i++)
+					{
+						if (!playeringame[i])
+							continue;
+
+						if (&players[i] == stplyr)
+							continue;
+
+						if (players[i].lives > 1)
+							break;
+					}
+
+					if (i != MAXPLAYERS)
+						V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132)-(splitscreen ? 12 : 0), V_HUDTRANSHALF, M_GetText("You'll steal a life on respawn."));
+				}
+			}
+			else if (!gametype == GT_COOP)
 				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_HUDTRANSHALF, M_GetText("Press Fire to enter the game."));
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(148), V_HUDTRANSHALF, M_GetText("Press F12 to watch another player."));
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(164), V_HUDTRANSHALF, M_GetText("Press Jump to float and Spin to sink."));
+			if (!splitscreen)
+			{
+				V_DrawCenteredString(BASEVIDWIDTH/2, 148, V_HUDTRANSHALF, M_GetText("Press F12 to watch another player."));
+				V_DrawCenteredString(BASEVIDWIDTH/2, 164, V_HUDTRANSHALF, M_GetText("Press Jump to float and Spin to sink."));
+			}
+			else
+				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(136), V_HUDTRANSHALF, M_GetText("Press Jump to float and Spin to sink."));
 		}
 	}
 
diff --git a/src/st_stuff.h b/src/st_stuff.h
index c11559d2b4f73853972b333d7f4d8dee1d6c9490..ddb119fb807566128a3577cfd55f86421dc919b8 100644
--- a/src/st_stuff.h
+++ b/src/st_stuff.h
@@ -44,9 +44,6 @@ void ST_LoadGraphics(void);
 // face load graphics, called when skin changes
 void ST_LoadFaceGraphics(char *facestr, char *superstr, INT32 playernum);
 void ST_ReloadSkinFaceGraphics(void);
-#ifdef DELFILE
-void ST_UnLoadFaceGraphics(INT32 skinnum);
-#endif
 
 void ST_doPaletteStuff(void);
 
diff --git a/src/v_video.c b/src/v_video.c
index fb02dfc968c62d1cae81358b2190eb5c744ff8d0..6ac101f2d6474f6464c787bbafe0b00caa546292 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -38,13 +38,39 @@ UINT8 *screens[5];
 // screens[3] = fade screen start
 // screens[4] = fade screen end, postimage tempoarary buffer
 
-static CV_PossibleValue_t gamma_cons_t[] = {{0, "MIN"}, {4, "MAX"}, {0, NULL}};
-static void CV_usegamma_OnChange(void);
-
 consvar_t cv_ticrate = {"showfps", "No", 0, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_usegamma = {"gamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_usegamma_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
-consvar_t cv_allcaps = {"allcaps", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+static void CV_palette_OnChange(void);
+
+static CV_PossibleValue_t gamma_cons_t[] = {{-15, "MIN"}, {5, "MAX"}, {0, NULL}};
+consvar_t cv_globalgamma = {"gamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+
+static CV_PossibleValue_t saturation_cons_t[] = {{0, "MIN"}, {10, "MAX"}, {0, NULL}};
+consvar_t cv_globalsaturation = {"saturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+
+#define huecoloursteps 4
+
+static CV_PossibleValue_t hue_cons_t[] = {{0, "MIN"}, {(huecoloursteps*6)-1, "MAX"}, {0, NULL}};
+consvar_t cv_rhue = {"rhue",  "0", CV_SAVE|CV_CALL, hue_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_yhue = {"yhue",  "4", CV_SAVE|CV_CALL, hue_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_ghue = {"ghue",  "8", CV_SAVE|CV_CALL, hue_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_chue = {"chue", "12", CV_SAVE|CV_CALL, hue_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_bhue = {"bhue", "16", CV_SAVE|CV_CALL, hue_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_mhue = {"mhue", "20", CV_SAVE|CV_CALL, hue_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+
+consvar_t cv_rgamma = {"rgamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_ygamma = {"ygamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_ggamma = {"ggamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cgamma = {"cgamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_bgamma = {"bgamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_mgamma = {"mgamma", "0", CV_SAVE|CV_CALL, gamma_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+
+consvar_t cv_rsaturation = {"rsaturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_ysaturation = {"ysaturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_gsaturation = {"gsaturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_csaturation = {"csaturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_bsaturation = {"bsaturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_msaturation = {"msaturation", "10", CV_SAVE|CV_CALL, saturation_cons_t, CV_palette_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
 static CV_PossibleValue_t constextsize_cons_t[] = {
 	{V_NOSCALEPATCH, "Small"}, {V_SMALLSCALEPATCH, "Medium"}, {V_MEDSCALEPATCH, "Large"}, {0, "Huge"},
@@ -83,8 +109,209 @@ static CV_PossibleValue_t CV_MD2[] = {{0, "Off"}, {1, "On"}, {2, "Old"}, {0, NUL
 consvar_t cv_grmd2 = {"gr_md2", "Off", CV_SAVE, CV_MD2, NULL, 0, NULL, NULL, 0, 0, NULL};
 #endif
 
-const UINT8 gammatable[5][256] =
+// local copy of the palette for V_GetColor()
+RGBA_t *pLocalPalette = NULL;
+RGBA_t *pMasterPalette = NULL;
+
+/*
+The following was an extremely helpful resource when developing my Colour Cube LUT.
+http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter24.html
+Please check it out if you're trying to maintain this.
+toast 18/04/17
+*/
+float Cubepal[2][2][2][3];
+
+// returns whether to apply cube, selectively avoiding expensive operations
+static boolean InitCube(void)
 {
+	boolean apply = false;
+	UINT8 q;
+	float working[2][2][2][3] = // the initial positions of the corners of the colour cube!
+	{
+		{
+			{
+				{0.0, 0.0, 0.0}, // black corner
+				{0.0, 0.0, 1.0}  // blue corner
+			},
+			{
+				{0.0, 1.0, 0.0}, // green corner
+				{0.0, 1.0, 1.0}  // cyan corner
+			}
+		},
+		{
+			{
+				{1.0, 0.0, 0.0}, // red corner
+				{1.0, 0.0, 1.0}  // magenta corner
+			},
+			{
+				{1.0, 1.0, 0.0}, // yellow corner
+				{1.0, 1.0, 1.0}  // white corner
+			}
+		}
+	};
+	float desatur[3]; // grey
+	float globalgammamul, globalgammaoffs;
+	boolean doinggamma;
+
+#define diffcons(cv) (cv.value != atoi(cv.defaultvalue))
+
+	doinggamma = diffcons(cv_globalgamma);
+
+#define gammascale 8
+	globalgammamul = (cv_globalgamma.value ? ((255 - (gammascale*abs(cv_globalgamma.value)))/255.0) : 1.0);
+	globalgammaoffs = ((cv_globalgamma.value > 0) ? ((gammascale*cv_globalgamma.value)/255.0) : 0.0);
+	desatur[0] = desatur[1] = desatur[2] = globalgammaoffs + (0.33*globalgammamul);
+
+	if (doinggamma
+		|| diffcons(cv_rhue)
+		|| diffcons(cv_yhue)
+		|| diffcons(cv_ghue)
+		|| diffcons(cv_chue)
+		|| diffcons(cv_bhue)
+		|| diffcons(cv_mhue)
+		|| diffcons(cv_rgamma)
+		|| diffcons(cv_ygamma)
+		|| diffcons(cv_ggamma)
+		|| diffcons(cv_cgamma)
+		|| diffcons(cv_bgamma)
+		|| diffcons(cv_mgamma)) // set the gamma'd/hued positions (saturation is done later)
+	{
+		float mod, tempgammamul, tempgammaoffs;
+
+		apply = true;
+
+		working[0][0][0][0] = working[0][0][0][1] = working[0][0][0][2] = globalgammaoffs;
+		working[1][1][1][0] = working[1][1][1][1] = working[1][1][1][2] = globalgammaoffs+globalgammamul;
+
+#define dohue(hue, gamma, loc) \
+		tempgammamul = (gamma ? ((255 - (gammascale*abs(gamma)))/255.0)*globalgammamul : globalgammamul);\
+		tempgammaoffs = ((gamma > 0) ? ((gammascale*gamma)/255.0) + globalgammaoffs : globalgammaoffs);\
+		mod = ((hue % huecoloursteps)*(tempgammamul)/huecoloursteps);\
+		switch (hue/huecoloursteps)\
+		{\
+			case 0:\
+			default:\
+				loc[0] = tempgammaoffs+tempgammamul;\
+				loc[1] = tempgammaoffs+mod;\
+				loc[2] = tempgammaoffs;\
+				break;\
+			case 1:\
+				loc[0] = tempgammaoffs+tempgammamul-mod;\
+				loc[1] = tempgammaoffs+tempgammamul;\
+				loc[2] = tempgammaoffs;\
+				break;\
+			case 2:\
+				loc[0] = tempgammaoffs;\
+				loc[1] = tempgammaoffs+tempgammamul;\
+				loc[2] = tempgammaoffs+mod;\
+				break;\
+			case 3:\
+				loc[0] = tempgammaoffs;\
+				loc[1] = tempgammaoffs+tempgammamul-mod;\
+				loc[2] = tempgammaoffs+tempgammamul;\
+				break;\
+			case 4:\
+				loc[0] = tempgammaoffs+mod;\
+				loc[1] = tempgammaoffs;\
+				loc[2] = tempgammaoffs+tempgammamul;\
+				break;\
+			case 5:\
+				loc[0] = tempgammaoffs+tempgammamul;\
+				loc[1] = tempgammaoffs;\
+				loc[2] = tempgammaoffs+tempgammamul-mod;\
+				break;\
+		}
+		dohue(cv_rhue.value, cv_rgamma.value, working[1][0][0]);
+		dohue(cv_yhue.value, cv_ygamma.value, working[1][1][0]);
+		dohue(cv_ghue.value, cv_ggamma.value, working[0][1][0]);
+		dohue(cv_chue.value, cv_cgamma.value, working[0][1][1]);
+		dohue(cv_bhue.value, cv_bgamma.value, working[0][0][1]);
+		dohue(cv_mhue.value, cv_mgamma.value, working[1][0][1]);
+#undef dohue
+	}
+
+#define dosaturation(a, e) a = ((1 - work)*e + work*a)
+#define docvsat(cv_sat, hue, gamma, r, g, b) \
+	if diffcons(cv_sat)\
+	{\
+		float work, mod, tempgammamul, tempgammaoffs;\
+		apply = true;\
+		work = (cv_sat.value/10.0);\
+		mod = ((hue % huecoloursteps)*(1.0)/huecoloursteps);\
+		if (hue & huecoloursteps)\
+			mod = 2-mod;\
+		else\
+			mod += 1;\
+		tempgammamul = (gamma ? ((255 - (gammascale*abs(gamma)))/255.0)*globalgammamul : globalgammamul);\
+		tempgammaoffs = ((gamma > 0) ? ((gammascale*gamma)/255.0) + globalgammaoffs : globalgammaoffs);\
+		for (q = 0; q < 3; q++)\
+			dosaturation(working[r][g][b][q], (tempgammaoffs+(desatur[q]*mod*tempgammamul)));\
+	}
+
+	docvsat(cv_rsaturation, cv_rhue.value, cv_rgamma.value, 1, 0, 0);
+	docvsat(cv_ysaturation, cv_yhue.value, cv_ygamma.value, 1, 1, 0);
+	docvsat(cv_gsaturation, cv_ghue.value, cv_ggamma.value, 0, 1, 0);
+	docvsat(cv_csaturation, cv_chue.value, cv_cgamma.value, 0, 1, 1);
+	docvsat(cv_bsaturation, cv_bhue.value, cv_bgamma.value, 0, 0, 1);
+	docvsat(cv_msaturation, cv_mhue.value, cv_mgamma.value, 1, 0, 1);
+
+#undef gammascale
+
+	if diffcons(cv_globalsaturation)
+	{
+		float work = (cv_globalsaturation.value/10.0);
+
+		apply = true;
+
+		for (q = 0; q < 3; q++)
+		{
+			dosaturation(working[1][0][0][q], desatur[q]);
+			dosaturation(working[0][1][0][q], desatur[q]);
+			dosaturation(working[0][0][1][q], desatur[q]);
+
+			dosaturation(working[1][1][0][q], 2*desatur[q]);
+			dosaturation(working[0][1][1][q], 2*desatur[q]);
+			dosaturation(working[1][0][1][q], 2*desatur[q]);
+		}
+	}
+
+#undef dosaturation
+
+#undef diffcons
+
+	if (!apply)
+		return false;
+
+#define dowork(i, j, k, l) \
+	if (working[i][j][k][l] > 1.0)\
+		working[i][j][k][l] = 1.0;\
+	else if (working[i][j][k][l] < 0.0)\
+		working[i][j][k][l] = 0.0;\
+	Cubepal[i][j][k][l] = working[i][j][k][l]
+	for (q = 0; q < 3; q++)
+	{
+		dowork(0, 0, 0, q);
+		dowork(1, 0, 0, q);
+		dowork(0, 1, 0, q);
+		dowork(1, 1, 0, q);
+		dowork(0, 0, 1, q);
+		dowork(1, 0, 1, q);
+		dowork(0, 1, 1, q);
+		dowork(1, 1, 1, q);
+	}
+#undef dowork
+
+	return true;
+}
+
+/*
+So it turns out that the way gamma was implemented previously, the default
+colour profile of the game was messed up. Since this bad decision has been
+around for a long time, and the intent is to keep the base game looking the
+same, I'm not gonna be the one to remove this base modification.
+toast 20/04/17
+*/
+const UINT8 correctiontable[256] =
 	{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
 	17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,
 	33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,
@@ -100,95 +327,67 @@ const UINT8 gammatable[5][256] =
 	192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,
 	208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,
 	224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,
-	240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255},
-
-	{2,4,5,7,8,10,11,12,14,15,16,18,19,20,21,23,24,25,26,27,29,30,31,
-	32,33,34,36,37,38,39,40,41,42,44,45,46,47,48,49,50,51,52,54,55,
-	56,57,58,59,60,61,62,63,64,65,66,67,69,70,71,72,73,74,75,76,77,
-	78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,
-	99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,
-	115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,129,
-	130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,
-	146,147,148,148,149,150,151,152,153,154,155,156,157,158,159,160,
-	161,162,163,163,164,165,166,167,168,169,170,171,172,173,174,175,
-	175,176,177,178,179,180,181,182,183,184,185,186,186,187,188,189,
-	190,191,192,193,194,195,196,196,197,198,199,200,201,202,203,204,
-	205,205,206,207,208,209,210,211,212,213,214,214,215,216,217,218,
-	219,220,221,222,222,223,224,225,226,227,228,229,230,230,231,232,
-	233,234,235,236,237,237,238,239,240,241,242,243,244,245,245,246,
-	247,248,249,250,251,252,252,253,254,255},
-
-	{4,7,9,11,13,15,17,19,21,22,24,26,27,29,30,32,33,35,36,38,39,40,42,
-	43,45,46,47,48,50,51,52,54,55,56,57,59,60,61,62,63,65,66,67,68,69,
-	70,72,73,74,75,76,77,78,79,80,82,83,84,85,86,87,88,89,90,91,92,93,
-	94,95,96,97,98,100,101,102,103,104,105,106,107,108,109,110,111,112,
-	113,114,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,
-	129,130,131,132,133,133,134,135,136,137,138,139,140,141,142,143,144,
-	144,145,146,147,148,149,150,151,152,153,153,154,155,156,157,158,159,
-	160,160,161,162,163,164,165,166,166,167,168,169,170,171,172,172,173,
-	174,175,176,177,178,178,179,180,181,182,183,183,184,185,186,187,188,
-	188,189,190,191,192,193,193,194,195,196,197,197,198,199,200,201,201,
-	202,203,204,205,206,206,207,208,209,210,210,211,212,213,213,214,215,
-	216,217,217,218,219,220,221,221,222,223,224,224,225,226,227,228,228,
-	229,230,231,231,232,233,234,235,235,236,237,238,238,239,240,241,241,
-	242,243,244,244,245,246,247,247,248,249,250,251,251,252,253,254,254,
-	255},
-
-	{8,12,16,19,22,24,27,29,31,34,36,38,40,41,43,45,47,49,50,52,53,55,
-	57,58,60,61,63,64,65,67,68,70,71,72,74,75,76,77,79,80,81,82,84,85,
-	86,87,88,90,91,92,93,94,95,96,98,99,100,101,102,103,104,105,106,107,
-	108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,
-	125,126,127,128,129,130,131,132,133,134,135,135,136,137,138,139,140,
-	141,142,143,143,144,145,146,147,148,149,150,150,151,152,153,154,155,
-	155,156,157,158,159,160,160,161,162,163,164,165,165,166,167,168,169,
-	169,170,171,172,173,173,174,175,176,176,177,178,179,180,180,181,182,
-	183,183,184,185,186,186,187,188,189,189,190,191,192,192,193,194,195,
-	195,196,197,197,198,199,200,200,201,202,202,203,204,205,205,206,207,
-	207,208,209,210,210,211,212,212,213,214,214,215,216,216,217,218,219,
-	219,220,221,221,222,223,223,224,225,225,226,227,227,228,229,229,230,
-	231,231,232,233,233,234,235,235,236,237,237,238,238,239,240,240,241,
-	242,242,243,244,244,245,246,246,247,247,248,249,249,250,251,251,252,
-	253,253,254,254,255},
-
-	{16,23,28,32,36,39,42,45,48,50,53,55,57,60,62,64,66,68,69,71,73,75,76,
-	78,80,81,83,84,86,87,89,90,92,93,94,96,97,98,100,101,102,103,105,106,
-	107,108,109,110,112,113,114,115,116,117,118,119,120,121,122,123,124,
-	125,126,128,128,129,130,131,132,133,134,135,136,137,138,139,140,141,
-	142,143,143,144,145,146,147,148,149,150,150,151,152,153,154,155,155,
-	156,157,158,159,159,160,161,162,163,163,164,165,166,166,167,168,169,
-	169,170,171,172,172,173,174,175,175,176,177,177,178,179,180,180,181,
-	182,182,183,184,184,185,186,187,187,188,189,189,190,191,191,192,193,
-	193,194,195,195,196,196,197,198,198,199,200,200,201,202,202,203,203,
-	204,205,205,206,207,207,208,208,209,210,210,211,211,212,213,213,214,
-	214,215,216,216,217,217,218,219,219,220,220,221,221,222,223,223,224,
-	224,225,225,226,227,227,228,228,229,229,230,230,231,232,232,233,233,
-	234,234,235,235,236,236,237,237,238,239,239,240,240,241,241,242,242,
-	243,243,244,244,245,245,246,246,247,247,248,248,249,249,250,250,251,
-	251,252,252,253,254,254,255,255}
-};
-
-// local copy of the palette for V_GetColor()
-RGBA_t *pLocalPalette = NULL;
+	240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255};
 
 // keep a copy of the palette so that we can get the RGB value for a color index at any time.
 static void LoadPalette(const char *lumpname)
 {
-	const UINT8 *usegamma = gammatable[cv_usegamma.value];
+	boolean cube = InitCube();
 	lumpnum_t lumpnum = W_GetNumForName(lumpname);
 	size_t i, palsize = W_LumpLength(lumpnum)/3;
 	UINT8 *pal;
 
 	Z_Free(pLocalPalette);
+	Z_Free(pMasterPalette);
 
 	pLocalPalette = Z_Malloc(sizeof (*pLocalPalette)*palsize, PU_STATIC, NULL);
+	pMasterPalette = Z_Malloc(sizeof (*pMasterPalette)*palsize, PU_STATIC, NULL);
 
 	pal = W_CacheLumpNum(lumpnum, PU_CACHE);
 	for (i = 0; i < palsize; i++)
 	{
-		pLocalPalette[i].s.red = usegamma[*pal++];
-		pLocalPalette[i].s.green = usegamma[*pal++];
-		pLocalPalette[i].s.blue = usegamma[*pal++];
-		pLocalPalette[i].s.alpha = 0xFF;
+		pMasterPalette[i].s.red = pLocalPalette[i].s.red = correctiontable[*pal++];
+		pMasterPalette[i].s.green = pLocalPalette[i].s.green = correctiontable[*pal++];
+		pMasterPalette[i].s.blue = pLocalPalette[i].s.blue = correctiontable[*pal++];
+		pMasterPalette[i].s.alpha = pLocalPalette[i].s.alpha = 0xFF;
+
+		// lerp of colour cubing!
+		if (cube)
+		{
+			float working[4][3];
+			float linear;
+			UINT8 q;
+
+			linear = (pLocalPalette[i].s.red/255.0);
+#define dolerp(e1, e2) ((1 - linear)*e1 + linear*e2)
+			for (q = 0; q < 3; q++)
+			{
+				working[0][q] = dolerp(Cubepal[0][0][0][q], Cubepal[1][0][0][q]);
+				working[1][q] = dolerp(Cubepal[0][1][0][q], Cubepal[1][1][0][q]);
+				working[2][q] = dolerp(Cubepal[0][0][1][q], Cubepal[1][0][1][q]);
+				working[3][q] = dolerp(Cubepal[0][1][1][q], Cubepal[1][1][1][q]);
+			}
+			linear = (pLocalPalette[i].s.green/255.0);
+			for (q = 0; q < 3; q++)
+			{
+				working[0][q] = dolerp(working[0][q], working[1][q]);
+				working[1][q] = dolerp(working[2][q], working[3][q]);
+			}
+			linear = (pLocalPalette[i].s.blue/255.0);
+			for (q = 0; q < 3; q++)
+			{
+				working[0][q] = 255*dolerp(working[0][q], working[1][q]);
+				if (working[0][q] > 255.0)
+					working[0][q] = 255.0;
+				else if (working[0][q]  < 0.0)
+					working[0][q] = 0.0;
+			}
+#undef dolerp
+
+			pLocalPalette[i].s.red = (UINT8)(working[0][0]);
+			pLocalPalette[i].s.green = (UINT8)(working[0][1]);
+			pLocalPalette[i].s.blue = (UINT8)(working[0][2]);
+		}
 	}
 }
 
@@ -250,7 +449,7 @@ void V_SetPaletteLump(const char *pal)
 		I_SetPalette(pLocalPalette);
 }
 
-static void CV_usegamma_OnChange(void)
+static void CV_palette_OnChange(void)
 {
 	// reload palette
 	LoadMapPalette();
@@ -649,7 +848,7 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 			dest = desttop;
 			dest += FixedInt(FixedMul(topdelta<<FRACBITS,fdup))*vid.width;
 
-			for (ofs = sy<<FRACBITS; dest < deststop && (ofs>>FRACBITS) < column->length && (ofs>>FRACBITS) < h; ofs += rowfrac)
+			for (ofs = sy<<FRACBITS; dest < deststop && (ofs>>FRACBITS) < column->length && ((ofs>>FRACBITS) + topdelta) < h; ofs += rowfrac)
 			{
 				if (dest >= screens[scrn&V_PARAMMASK]) // don't draw off the top of the screen (CRASH PREVENTION)
 					*dest = source[ofs>>FRACBITS];
@@ -666,11 +865,7 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 //
 void V_DrawContinueIcon(INT32 x, INT32 y, INT32 flags, INT32 skinnum, UINT8 skincolor)
 {
-	if (skins[skinnum].flags & SF_HIRES
-#ifdef HWRENDER
-//	|| (rendermode != render_soft && rendermode != render_none)
-#endif
-	)
+	if (skins[skinnum].flags & SF_HIRES)
 		V_DrawScaledPatch(x - 10, y - 14, flags, W_CachePatchName("CONTINS", PU_CACHE));
 	else
 	{
@@ -773,79 +968,80 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 {
 	UINT8 *dest;
 	const UINT8 *deststop;
-	INT32 u, v, dupx, dupy;
+
+	if (rendermode == render_none)
+		return;
 
 #ifdef HWRENDER
-	if (rendermode != render_soft && rendermode != render_none)
+	if (rendermode != render_soft && !con_startup)
 	{
 		HWR_DrawFill(x, y, w, h, c);
 		return;
 	}
 #endif
 
-	dupx = vid.dupx;
-	dupy = vid.dupy;
-
-	if (!screens[0])
-		return;
-
-	if (c & V_NOSCALESTART)
-	{
-		dest = screens[0] + y*vid.width + x;
-		deststop = screens[0] + vid.rowbytes * vid.height;
-	}
-	else
+	if (!(c & V_NOSCALESTART))
 	{
+		INT32 dupx = vid.dupx, dupy = vid.dupy;
+
 		if (x == 0 && y == 0 && w == BASEVIDWIDTH && h == BASEVIDHEIGHT)
 		{ // Clear the entire screen, from dest to deststop. Yes, this really works.
 			memset(screens[0], (UINT8)(c&255), vid.width * vid.height * vid.bpp);
 			return;
 		}
 
-		dest = screens[0] + y*dupy*vid.width + x*dupx;
-		deststop = screens[0] + vid.rowbytes * vid.height;
-
-		if (w == BASEVIDWIDTH)
-			w = vid.width;
-		else
-			w *= dupx;
-		if (h == BASEVIDHEIGHT)
-			h = vid.height;
-		else
-			h *= dupy;
+		x *= dupx;
+		y *= dupy;
+		w *= dupx;
+		h *= dupy;
 
-		if (x && y && x + w < vid.width && y + h < vid.height)
+		// Center it if necessary
+		if (vid.width != BASEVIDWIDTH * dupx)
 		{
-			// Center it if necessary
-			if (vid.width != BASEVIDWIDTH * dupx)
-			{
-				// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
-				// so center this imaginary screen
-				if (c & V_SNAPTORIGHT)
-					dest += (vid.width - (BASEVIDWIDTH * dupx));
-				else if (!(c & V_SNAPTOLEFT))
-					dest += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
-			}
-			if (vid.height != BASEVIDHEIGHT * dupy)
-			{
-				// same thing here
-				if (c & V_SNAPTOBOTTOM)
-					dest += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width;
-				else if (!(c & V_SNAPTOTOP))
-					dest += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width / 2;
-			}
+			// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
+			// so center this imaginary screen
+			if (c & V_SNAPTORIGHT)
+				x += (vid.width - (BASEVIDWIDTH * dupx));
+			else if (!(c & V_SNAPTOLEFT))
+				x += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
 		}
+		if (vid.height != BASEVIDHEIGHT * dupy)
+		{
+			// same thing here
+			if (c & V_SNAPTOBOTTOM)
+				y += (vid.height - (BASEVIDHEIGHT * dupy));
+			else if (!(c & V_SNAPTOTOP))
+				y += (vid.height - (BASEVIDHEIGHT * dupy)) / 2;
+		}
+	}
+
+	if (x >= vid.width || y >= vid.height)
+		return; // off the screen
+	if (x < 0)
+	{
+		w += x;
+		x = 0;
+	}
+	if (y < 0)
+	{
+		h += y;
+		y = 0;
 	}
 
+	if (w <= 0 || h <= 0)
+		return; // zero width/height wouldn't draw anything
+	if (x + w > vid.width)
+		w = vid.width - x;
+	if (y + h > vid.height)
+		h = vid.height - y;
+
+	dest = screens[0] + y*vid.width + x;
+	deststop = screens[0] + vid.rowbytes * vid.height;
+
 	c &= 255;
 
-	for (v = 0; v < h; v++, dest += vid.width)
-		for (u = 0; u < w; u++)
-		{
-			if (dest > deststop)
-				return;
-			dest[u] = (UINT8)c;
-		}
+	for (;(--h >= 0) && dest < deststop; dest += vid.width)
+		memset(dest, (UINT8)(c&255), w * vid.bpp);
 }
 
 //
@@ -1783,6 +1979,9 @@ INT32 V_StringWidth(const char *string, INT32 option)
 			w += (charwidth ? charwidth : SHORT(hu_font[c]->width));
 	}
 
+	if (option & V_NOSCALESTART)
+	w *= vid.dupx;
+
 	return w;
 }
 
diff --git a/src/v_video.h b/src/v_video.h
index ca1f58a302220928b6ffc079bbb7fa7873bfbc20..3781f33374ebb5b6b44a58a13889a842427f4f70 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -27,8 +27,11 @@
 
 extern UINT8 *screens[5];
 
-extern const UINT8 gammatable[5][256];
-extern consvar_t cv_ticrate, cv_usegamma, cv_allcaps, cv_constextsize;
+extern consvar_t cv_ticrate, cv_constextsize,\
+cv_globalgamma, cv_globalsaturation, \
+cv_rhue, cv_yhue, cv_ghue, cv_chue, cv_bhue, cv_mhue,\
+cv_rgamma, cv_ygamma, cv_ggamma, cv_cgamma, cv_bgamma, cv_mgamma, \
+cv_rsaturation, cv_ysaturation, cv_gsaturation, cv_csaturation, cv_bsaturation, cv_msaturation;
 
 // Allocates buffer screens, call before R_Init.
 void V_Init(void);
@@ -42,6 +45,7 @@ const char *R_GetPalname(UINT16 num);
 const char *GetPalette(void);
 
 extern RGBA_t *pLocalPalette;
+extern RGBA_t *pMasterPalette;
 
 // Retrieve the ARGB value from a palette color index
 #define V_GetColor(color) (pLocalPalette[color&0xFF])
diff --git a/src/w_wad.c b/src/w_wad.c
index ecba4064fc9f46c217252db5af8e2217cde55dee..22e1836c784dbf9271264b88cba2140939d1bbc8 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -34,6 +34,8 @@
 #include "z_zone.h"
 #include "fastcmp.h"
 
+#include "filesrch.h"
+
 #include "i_video.h" // rendermode
 #include "d_netfil.h"
 #include "dehacked.h"
@@ -133,6 +135,47 @@ void W_Shutdown(void)
 
 static char filenamebuf[MAX_WADPATH];
 
+// W_OpenWadFile
+// Helper function for opening the WAD file.
+// Returns the FILE * handle for the file, or NULL if not found or could not be opened
+// If "useerrors" is true then print errors in the console, else just don't bother
+// "filename" may be modified to have the correct path the actual file is located in, if necessary
+FILE *W_OpenWadFile(const char **filename, boolean useerrors)
+{
+	FILE *handle;
+
+	strncpy(filenamebuf, *filename, MAX_WADPATH);
+	filenamebuf[MAX_WADPATH - 1] = '\0';
+	*filename = filenamebuf;
+
+	// open wad file
+	if ((handle = fopen(*filename, "rb")) == NULL)
+	{
+		// If we failed to load the file with the path as specified by
+		// the user, strip the directories and search for the file.
+		nameonly(filenamebuf);
+
+		// If findfile finds the file, the full path will be returned
+		// in filenamebuf == *filename.
+		if (findfile(filenamebuf, NULL, true))
+		{
+			if ((handle = fopen(*filename, "rb")) == NULL)
+			{
+				if (useerrors)
+					CONS_Alert(CONS_ERROR, M_GetText("Can't open %s\n"), *filename);
+				return NULL;
+			}
+		}
+		else
+		{
+			if (useerrors)
+				CONS_Alert(CONS_ERROR, M_GetText("File %s not found.\n"), *filename);
+			return NULL;
+		}
+	}
+	return handle;
+}
+
 // search for all DEHACKED lump in all wads and load it
 static inline void W_LoadDehackedLumps(UINT16 wadnum)
 {
@@ -234,7 +277,6 @@ static void W_InvalidateLumpnumCache(void)
 	memset(lumpnumcache, 0, sizeof (lumpnumcache));
 }
 
-
 //  Allocate a wadfile, setup the lumpinfo (directory) and
 //  lumpcache, add the wadfile to the current active wadfiles
 //
@@ -254,12 +296,12 @@ UINT16 W_LoadWadFile(const char *filename)
 	UINT32 numlumps;
 	size_t i;
 	INT32 compressed = 0;
-	size_t packetsize = 0;
-	serverinfo_pak *dummycheck = NULL;
+	size_t packetsize;
 	UINT8 md5sum[16];
+	boolean important;
 
-	// Shut the compiler up.
-	(void)dummycheck;
+	if (!(refreshdirmenu & REFRESHDIR_ADDFILE))
+		refreshdirmenu = REFRESHDIR_NORMAL|REFRESHDIR_ADDFILE; // clean out cons_alerts that happened earlier
 
 	//CONS_Debug(DBG_SETUP, "Loading %s\n", filename);
 	//
@@ -268,54 +310,33 @@ UINT16 W_LoadWadFile(const char *filename)
 	if (numwadfiles >= MAX_WADFILES)
 	{
 		CONS_Alert(CONS_ERROR, M_GetText("Maximum wad files reached\n"));
+		refreshdirmenu |= REFRESHDIR_MAX;
 		return INT16_MAX;
 	}
 
-	strncpy(filenamebuf, filename, MAX_WADPATH);
-	filenamebuf[MAX_WADPATH - 1] = '\0';
-	filename = filenamebuf;
-
 	// open wad file
-	if ((handle = fopen(filename, "rb")) == NULL)
-	{
-		// If we failed to load the file with the path as specified by
-		// the user, strip the directories and search for the file.
-		nameonly(filenamebuf);
-
-		// If findfile finds the file, the full path will be returned
-		// in filenamebuf == filename.
-		if (findfile(filenamebuf, NULL, true))
-		{
-			if ((handle = fopen(filename, "rb")) == NULL)
-			{
-				CONS_Alert(CONS_ERROR, M_GetText("Can't open %s\n"), filename);
-				return INT16_MAX;
-			}
-		}
-		else
-		{
-			CONS_Alert(CONS_ERROR, M_GetText("File %s not found.\n"), filename);
-			return INT16_MAX;
-		}
-	}
+	if ((handle = W_OpenWadFile(&filename, true)) == NULL)
+		return INT16_MAX;
 
 	// Check if wad files will overflow fileneededbuffer. Only the filename part
 	// is send in the packet; cf.
-	for (i = 0; i < numwadfiles; i++)
+	// see PutFileNeeded in d_netfil.c
+	if ((important = !W_VerifyNMUSlumps(filename)))
 	{
-		packetsize += nameonlylength(wadfiles[i]->filename);
-		packetsize += 22; // MD5, etc.
-	}
+		packetsize = packetsizetally;
 
-	packetsize += nameonlylength(filename);
-	packetsize += 22;
+		packetsize += nameonlylength(filename) + 22;
 
-	if (packetsize > sizeof(dummycheck->fileneeded))
-	{
-		CONS_Alert(CONS_ERROR, M_GetText("Maximum wad files reached\n"));
-		if (handle)
-			fclose(handle);
-		return INT16_MAX;
+		if (packetsize > MAXFILENEEDED*sizeof(UINT8))
+		{
+			CONS_Alert(CONS_ERROR, M_GetText("Maximum wad files reached\n"));
+			refreshdirmenu |= REFRESHDIR_MAX;
+			if (handle)
+				fclose(handle);
+			return INT16_MAX;
+		}
+
+		packetsizetally = packetsize;
 	}
 
 	// detect dehacked file with the "soc" extension
@@ -454,6 +475,7 @@ UINT16 W_LoadWadFile(const char *filename)
 	wadfile->handle = handle;
 	wadfile->numlumps = (UINT16)numlumps;
 	wadfile->lumpinfo = lumpinfo;
+	wadfile->important = important;
 	fseek(handle, 0, SEEK_END);
 	wadfile->filesize = (unsigned)ftell(handle);
 
@@ -483,38 +505,6 @@ UINT16 W_LoadWadFile(const char *filename)
 	return wadfile->numlumps;
 }
 
-#ifdef DELFILE
-void W_UnloadWadFile(UINT16 num)
-{
-	INT32 i;
-	wadfile_t *delwad = wadfiles[num];
-	lumpcache_t *lumpcache;
-	if (num == 0)
-		return;
-	CONS_Printf(M_GetText("Removing WAD %s...\n"), wadfiles[num]->filename);
-
-	DEH_UnloadDehackedWad(num);
-	wadfiles[num] = NULL;
-	lumpcache = delwad->lumpcache;
-	numwadfiles--;
-#ifdef HWRENDER
-	if (rendermode != render_soft && rendermode != render_none)
-		HWR_FreeTextureCache();
-	M_AATreeFree(delwad->hwrcache);
-#endif
-	if (*lumpcache)
-	{
-		for (i = 0;i < delwad->numlumps;i++)
-			Z_ChangeTag(lumpcache[i], PU_PURGELEVEL);
-	}
-	Z_Free(lumpcache);
-	fclose(delwad->handle);
-	Z_Free(delwad->filename);
-	Z_Free(delwad);
-	CONS_Printf(M_GetText("Done unloading WAD.\n"));
-}
-#endif
-
 /** Tries to load a series of files.
   * All files are wads unless they have an extension of ".soc" or ".lua".
   *
@@ -1115,21 +1105,11 @@ static int W_VerifyFile(const char *filename, lumpchecklist_t *checklist,
 	size_t i, j;
 	int goodfile = false;
 
-	if (!checklist) I_Error("No checklist for %s\n", filename);
-	strlcpy(filenamebuf, filename, MAX_WADPATH);
-	filename = filenamebuf;
+	if (!checklist)
+		I_Error("No checklist for %s\n", filename);
 	// open wad file
-	if ((handle = fopen(filename, "rb")) == NULL)
-	{
-		nameonly(filenamebuf); // leave full path here
-		if (findfile(filenamebuf, NULL, true))
-		{
-			if ((handle = fopen(filename, "rb")) == NULL)
-				return -1;
-		}
-		else
-			return -1;
-	}
+	if ((handle = W_OpenWadFile(&filename, false)) == NULL)
+		return -1;
 
 	// detect dehacked file with the "soc" extension
 	if (stricmp(&filename[strlen(filename) - 4], ".soc") != 0
@@ -1233,6 +1213,7 @@ int W_VerifyNMUSlumps(const char *filename)
 		{"TNYFN", 5}, // Tiny console font changes
 		{"STT", 3}, // Acceptable HUD changes (Score Time Rings)
 		{"YB_", 3}, // Intermission graphics, goes with the above
+		{"M_", 2}, // As does menu stuff
 
 		{NULL, 0},
 	};
diff --git a/src/w_wad.h b/src/w_wad.h
index b03e376bfaf76c62778a163f44994c25fc37efaf..d3ce20e2088984d98d5d5c286c73c59b46c575ef 100644
--- a/src/w_wad.h
+++ b/src/w_wad.h
@@ -70,6 +70,7 @@ typedef struct wadfile_s
 	FILE *handle;
 	UINT32 filesize; // for network
 	UINT8 md5sum[16];
+	boolean important; // also network - !W_VerifyNMUSlumps
 } wadfile_t;
 
 #define WADFILENUM(lumpnum) (UINT16)((lumpnum)>>16) // wad flumpnum>>16) // wad file number in upper word
@@ -82,11 +83,10 @@ extern wadfile_t *wadfiles[MAX_WADFILES];
 
 void W_Shutdown(void);
 
+// Opens a WAD file. Returns the FILE * handle for the file, or NULL if not found or could not be opened
+FILE *W_OpenWadFile(const char **filename, boolean useerrors);
 // Load and add a wadfile to the active wad files, returns numbers of lumps, INT16_MAX on error
 UINT16 W_LoadWadFile(const char *filename);
-#ifdef DELFILE
-void W_UnloadWadFile(UINT16 num);
-#endif
 
 // W_InitMultipleFiles returns 1 if all is okay, 0 otherwise,
 // so that it stops with a message if a file was not found, but not if all is okay.
diff --git a/src/win32/win_cd.c b/src/win32/win_cd.c
index d73b95523e69ac9599463f590e6db5adbae55a30..ae13d3e574895db68c1389f4151b1e4db1ce717a 100644
--- a/src/win32/win_cd.c
+++ b/src/win32/win_cd.c
@@ -180,9 +180,9 @@ static LPSTR hms(UINT seconds)
 	hours = minutes / 60;
 	minutes %= 60;
 	if (hours > 0)
-		sprintf (s, "%lu:%02lu:%02lu", hours, minutes, seconds);
+		sprintf (s, "%lu:%02lu:%02lu", (long unsigned int)hours, (long unsigned int)minutes, (long unsigned int)seconds);
 	else
-		sprintf (s, "%2lu:%02lu", minutes, seconds);
+		sprintf (s, "%2lu:%02lu", (long unsigned int)minutes, (long unsigned int)seconds);
 	return s;
 }
 
diff --git a/src/win32/win_main.c b/src/win32/win_main.c
index d84c862320520940151e0d39017c4a42b7f74147..4ac05f94f4cc02d88cebc2ec9dc645d0c0031476 100644
--- a/src/win32/win_main.c
+++ b/src/win32/win_main.c
@@ -470,7 +470,7 @@ static inline BOOL tlErrorMessage(const TCHAR *err)
 	//
 	// warn user if there is one
 	//
-	printf("Error %Ts..\n", err);
+	printf("Error %s..\n", err);
 	fflush(stdout);
 
 	MessageBox(hWndMain, err, TEXT("ERROR"), MB_OK);
diff --git a/src/win32/win_vid.c b/src/win32/win_vid.c
index 31d1b8120d95174ea8134054a617ee845a5d3af4..5560751de3c858b8c4f424f2b0154dde891e47e7 100644
--- a/src/win32/win_vid.c
+++ b/src/win32/win_vid.c
@@ -362,6 +362,10 @@ void I_FinishUpdate(void)
 	if (I_SkipFrame())
 		return;
 
+	// draw captions if enabled
+	if (cv_closedcaptioning.value)
+		SCR_ClosedCaptions();
+
 	// display a graph of ticrate
 	if (cv_ticrate.value)
 		SCR_DisplayTicRate();
diff --git a/src/win32ce/win_vid.c b/src/win32ce/win_vid.c
index b9c2e131f795b0ddde170367b38a4a50258c6a26..4724ca40db595d2d64bcce36694d6a1e2c5c0f4b 100644
--- a/src/win32ce/win_vid.c
+++ b/src/win32ce/win_vid.c
@@ -194,6 +194,10 @@ void I_FinishUpdate(void)
 	if (rendermode == render_none)
 		return;
 
+	// draw captions if enabled
+	if (cv_closedcaptioning.value)
+		SCR_ClosedCaptions();
+
 	// display a graph of ticrate
 	if (cv_ticrate.value)
 		SCR_DisplayTicRate();
diff --git a/src/y_inter.c b/src/y_inter.c
index c4f425beb843d726a3efdc9a20b52bc0a5fbb1c0..a102aa99f6b62daa6b4112cabd1e6d2ef909b614 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -147,6 +147,7 @@ static boolean useinterpic;
 static INT32 timer;
 
 static INT32 intertic;
+static INT32 tallydonetic = -1;
 static INT32 endtic = -1;
 
 intertype_t intertype = int_none;
@@ -159,6 +160,54 @@ static void Y_CalculateMatchWinners(void);
 static void Y_FollowIntermission(void);
 static void Y_UnloadData(void);
 
+// Stuff copy+pasted from st_stuff.c
+static INT32 SCX(INT32 x)
+{
+	return FixedInt(FixedMul(x<<FRACBITS, vid.fdupx));
+}
+static INT32 SCY(INT32 z)
+{
+	return FixedInt(FixedMul(z<<FRACBITS, vid.fdupy));
+}
+
+#define ST_DrawNumFromHud(h,n)        V_DrawTallNum(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART, n)
+#define ST_DrawPadNumFromHud(h,n,q)   V_DrawPaddedTallNum(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART, n, q)
+#define ST_DrawPatchFromHud(h,p)      V_DrawScaledPatch(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART, p)
+
+static void Y_IntermissionTokenDrawer(void)
+{
+	INT32 y;
+	INT32 offs = 0;
+	UINT32 tokencount;
+	INT32 lowy = BASEVIDHEIGHT - 32;
+	INT16 temp = SHORT(tokenicon->height)/2;
+	INT32 calc;
+
+	if (tallydonetic != -1)
+	{
+		offs = (intertic - tallydonetic)*2;
+		if (offs > 10)
+			offs = 8;
+	}
+
+	V_DrawFill(32, lowy-1, 16, 1, 31); // slot
+
+	y = (lowy + offs + 1) - (temp + (token + 1)*8);
+
+	for (tokencount = token; tokencount; tokencount--)
+	{
+		if (y >= -temp)
+			V_DrawSmallScaledPatch(32, y, 0, tokenicon);
+		y += 8;
+	}
+
+	y += (offs*(temp - 1)/8);
+	calc = (lowy - y)*2;
+
+	if (calc > 0)
+		V_DrawCroppedPatch(32<<FRACBITS, y<<FRACBITS, FRACUNIT/2, 0, tokenicon, 32*FRACUNIT, y<<FRACBITS, SHORT(tokenicon->width), calc);
+}
+
 //
 // Y_IntermissionDrawer
 //
@@ -203,29 +252,35 @@ void Y_IntermissionDrawer(void)
 	{
 		INT32 bonusy;
 
+		if (gottoken) // first to be behind everything else
+			Y_IntermissionTokenDrawer();
+
 		// draw score
-		V_DrawScaledPatch(hudinfo[HUD_SCORE].x, hudinfo[HUD_SCORE].y, V_SNAPTOLEFT, sboscore);
-		V_DrawTallNum(hudinfo[HUD_SCORENUM].x, hudinfo[HUD_SCORENUM].y, V_SNAPTOLEFT, data.coop.score);
+		ST_DrawPatchFromHud(HUD_SCORE, sboscore);
+		ST_DrawNumFromHud(HUD_SCORENUM, data.coop.score);
 
 		// draw time
-		V_DrawScaledPatch(hudinfo[HUD_TIME].x, hudinfo[HUD_TIME].y, V_SNAPTOLEFT, sbotime);
+		ST_DrawPatchFromHud(HUD_TIME, sbotime);
 		if (cv_timetic.value == 1)
-			V_DrawTallNum(hudinfo[HUD_SECONDS].x, hudinfo[HUD_SECONDS].y, V_SNAPTOLEFT, data.coop.tics);
+			ST_DrawNumFromHud(HUD_SECONDS, data.coop.tics);
 		else
 		{
+			INT32 seconds, minutes, tictrn;
+
+			seconds = G_TicsToSeconds(data.coop.tics);
+			minutes = G_TicsToMinutes(data.coop.tics, true);
+			tictrn  = G_TicsToCentiseconds(data.coop.tics);
+
+			ST_DrawNumFromHud(HUD_MINUTES, minutes); // Minutes
+			ST_DrawPatchFromHud(HUD_TIMECOLON, sbocolon); // Colon
+			ST_DrawPadNumFromHud(HUD_SECONDS, seconds, 2); // Seconds
+
 			// we should show centiseconds on the intermission screen too, if the conditions are right.
 			if (modeattacking || cv_timetic.value == 2)
 			{
-				V_DrawPaddedTallNum(hudinfo[HUD_TICS].x, hudinfo[HUD_TICS].y, V_SNAPTOLEFT,
-					G_TicsToCentiseconds(data.coop.tics), 2);
-				V_DrawScaledPatch(hudinfo[HUD_TIMETICCOLON].x, hudinfo[HUD_TIMETICCOLON].y, V_SNAPTOLEFT, sboperiod);
+				ST_DrawPatchFromHud(HUD_TIMETICCOLON, sboperiod); // Period
+				ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2); // Tics
 			}
-
-			V_DrawPaddedTallNum(hudinfo[HUD_SECONDS].x, hudinfo[HUD_SECONDS].y, V_SNAPTOLEFT,
-				G_TicsToSeconds(data.coop.tics), 2);
-			V_DrawScaledPatch(hudinfo[HUD_TIMECOLON].x, hudinfo[HUD_TIMECOLON].y, V_SNAPTOLEFT, sbocolon);
-			V_DrawTallNum(hudinfo[HUD_MINUTES].x, hudinfo[HUD_MINUTES].y, V_SNAPTOLEFT,
-				G_TicsToMinutes(data.coop.tics, false));
 		}
 
 		// draw the "got through act" lines and act number
@@ -261,6 +316,9 @@ void Y_IntermissionDrawer(void)
 		INT32 xoffset3 = 0; // Line 3 x offset
 		UINT8 drawsection = 0;
 
+		if (gottoken) // first to be behind everything else
+			Y_IntermissionTokenDrawer();
+
 		// draw the header
 		if (intertic <= TICRATE)
 			animatetic = 0;
@@ -679,7 +737,10 @@ void Y_Ticker(void)
 		boolean anybonuses = false;
 
 		if (!intertic) // first time only
+		{
 			S_ChangeMusicInternal("_clear", false); // don't loop it
+			tallydonetic = -1;
+		}
 
 		if (intertic < TICRATE) // one second pause before tally begins
 			return;
@@ -709,6 +770,7 @@ void Y_Ticker(void)
 
 		if (!anybonuses)
 		{
+			tallydonetic = intertic;
 			endtic = intertic + 3*TICRATE; // 3 second pause after end of tally
 			S_StartSound(NULL, sfx_chchng); // cha-ching!
 
@@ -736,12 +798,11 @@ void Y_Ticker(void)
 		INT32 i;
 		UINT32 oldscore = data.spec.score;
 		boolean skip = false;
-		static INT32 tallydonetic = 0;
 
 		if (!intertic) // first time only
 		{
 			S_ChangeMusicInternal("_clear", false); // don't loop it
-			tallydonetic = 0;
+			tallydonetic = -1;
 		}
 
 		if (intertic < TICRATE) // one second pause before tally begins
@@ -751,12 +812,12 @@ void Y_Ticker(void)
 			if (playeringame[i] && (players[i].cmd.buttons & BT_USE))
 				skip = true;
 
-		if (tallydonetic != 0)
+		if ((data.spec.continues & 0x80) && tallydonetic != -1)
 		{
-			if (intertic > tallydonetic)
+			if ((intertic - tallydonetic) > (3*TICRATE)/2)
 			{
 				endtic = intertic + 4*TICRATE; // 4 second pause after end of tally
-				S_StartSound(NULL, sfx_flgcap); // cha-ching!
+				S_StartSound(NULL, sfx_s3kac); // cha-ching!
 			}
 			return;
 		}
@@ -772,9 +833,8 @@ void Y_Ticker(void)
 
 		if (!data.spec.bonus.points)
 		{
-			if (data.spec.continues & 0x80) // don't set endtic yet!
-				tallydonetic = intertic + (3*TICRATE)/2;
-			else // okay we're good.
+			tallydonetic = intertic;
+			if (!(data.spec.continues & 0x80)) // don't set endtic yet!
 				endtic = intertic + 4*TICRATE; // 4 second pause after end of tally
 
 			S_StartSound(NULL, sfx_chchng); // cha-ching!
@@ -965,7 +1025,8 @@ void Y_StartIntermission(void)
 	}
 
 	// We couldn't display the intermission even if we wanted to.
-	if (dedicated) return;
+	// But we still need to give the players their score bonuses, dummy.
+	//if (dedicated) return;
 
 	// This should always exist, but just in case...
 	if(!mapheaderinfo[prevmap])
@@ -1556,8 +1617,7 @@ static void Y_SetTimeBonus(player_t *player, y_bonus_t *bstruct)
 
 	// calculate time bonus
 	secs = player->realtime / TICRATE;
-	if      (secs <  30) /*   :30 */ bonus = 100000;
-	else if (secs <  45) /*   :45 */ bonus = 50000;
+	if      (secs <  30) /*   :30 */ bonus = 50000;
 	else if (secs <  60) /*  1:00 */ bonus = 10000;
 	else if (secs <  90) /*  1:30 */ bonus = 5000;
 	else if (secs < 120) /*  2:00 */ bonus = 4000;
@@ -1781,37 +1841,6 @@ void Y_EndIntermission(void)
 	usebuffer = false;
 }
 
-//
-// Y_EndGame
-//
-// Why end the game?
-// Because Y_FollowIntermission and F_EndCutscene would
-// both do this exact same thing *in different ways* otherwise,
-// which made it so that you could only unlock Ultimate mode
-// if you had a cutscene after the final level and crap like that.
-// This function simplifies it so only one place has to be updated
-// when something new is added.
-void Y_EndGame(void)
-{
-	// Only do evaluation and credits in coop games.
-	if (gametype == GT_COOP)
-	{
-		if (nextmap == 1102-1) // end game with credits
-		{
-			F_StartCredits();
-			return;
-		}
-		if (nextmap == 1101-1) // end game with evaluation
-		{
-			F_StartGameEvaluation();
-			return;
-		}
-	}
-
-	// 1100 or competitive multiplayer, so go back to title screen.
-	D_StartTitle();
-}
-
 //
 // Y_FollowIntermission
 //
@@ -1823,21 +1852,10 @@ static void Y_FollowIntermission(void)
 		return;
 	}
 
-	if (nextmap < 1100-1)
-	{
-		// normal level
-		G_AfterIntermission();
-		return;
-	}
-
-	// Start a custom cutscene if there is one.
-	if (mapheaderinfo[gamemap-1]->cutscenenum && !modeattacking)
-	{
-		F_StartCustomCutscene(mapheaderinfo[gamemap-1]->cutscenenum-1, false, false);
-		return;
-	}
-
-	Y_EndGame();
+	// This handles whether to play a post-level cutscene, end the game,
+	// or simply go to the next level.
+	// No need to duplicate the code here!
+	G_AfterIntermission();
 }
 
 #define UNLOAD(x) Z_ChangeTag(x, PU_CACHE); x = NULL
diff --git a/src/y_inter.h b/src/y_inter.h
index 9fe95fcc4868bbc065f666ea0d97c6445c0551fb..26f7dc390fc183de68873b435c6add42dc2cbd69 100644
--- a/src/y_inter.h
+++ b/src/y_inter.h
@@ -15,7 +15,6 @@ void Y_IntermissionDrawer(void);
 void Y_Ticker(void);
 void Y_StartIntermission(void);
 void Y_EndIntermission(void);
-void Y_EndGame(void);
 
 typedef enum
 {