diff --git a/.gitattributes b/.gitattributes
index c2e507352e6419be54e8866cc5bb40f5570a0fc7..9f08509300772556a9466ca012bd087bf7350393 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -10,6 +10,7 @@
 /src/Make*.cfg text=auto
 /src/CMakeLists.txt text=auto
 *.mk -whitespace text=auto
+/comptime.sh text eol=lf
 # Windows EOL
 *.cs -crlf -whitespace
 *.bat -crlf -whitespace
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 4e284ce65464fc244f364459f8f9b254f2aefee3..46fa6584d456dcfa698b547436a79fd78125650e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,7 +3,7 @@ variables:
   GIT_CLONE_PATH: $CI_BUILDS_DIR/$CI_CONCURRENT_ID/$CI_PROJECT_PATH
 
 default:
-  image: debian:stable-slim
+  image: git.do.srb2.org:5050/stjr/srb2ci/srb2ci:stable
 
   cache:
     - key: ccache-$CI_PROJECT_PATH_SLUG-$CI_JOB_NAME_SLUG
@@ -19,6 +19,11 @@ default:
         - apt-cache
       unprotect: true
 
+    - key: apk-$CI_JOB_IMAGE
+      paths:
+        - apk-cache
+      unprotect: true
+
   before_script:
     - - |
          # debconf
@@ -167,6 +172,10 @@ Debian testing GCC:
   allow_failure: true
 
   artifacts:
+    paths:
+      - "bin/"
+      - "src/comptime.h"
+    expose_as: "testing-gcc"
     name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-testing-gcc"
 
   variables:
@@ -401,6 +410,10 @@ Debian stable Clang:
   allow_failure: true
 
   artifacts:
+    paths:
+      - "bin/"
+      - "src/comptime.h"
+    expose_as: "clang"
     name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-clang"
 
   variables:
@@ -442,6 +455,10 @@ Debian stable musl:
   allow_failure: true
 
   artifacts:
+    paths:
+      - "bin/"
+      - "src/comptime.h"
+    expose_as: "musl"
     name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-musl"
 
   variables:
@@ -481,6 +498,10 @@ Debian testing Clang:
   image: debian:testing-slim
 
   artifacts:
+    paths:
+      - "bin/"
+      - "src/comptime.h"
+    expose_as: "testing-clang"
     name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-testing-clang"
 
   variables:
@@ -497,9 +518,143 @@ Debian testing musl:
   image: debian:testing-slim
 
   artifacts:
+    paths:
+      - "bin/"
+      - "src/comptime.h"
+    expose_as: "testing-musl"
     name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-testing-musl"
 
   variables:
     CC: musl-gcc
     LDD: musl-ldd
     LDFLAGS: -Wl,-fuse-ld=gold
+
+Alpine 3 GCC:
+  stage: build
+
+  when: on_success
+
+  image: alpine:3
+
+  allow_failure: true
+
+  artifacts:
+    paths:
+      - "bin/"
+      - "src/comptime.h"
+    expose_as: "Apline-3"
+    name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-Apline-3"
+
+  before_script:
+    - - |
+          # apk_cache
+          echo -e "\e[0Ksection_start:`date +%s`:apk_cache[collapsed=true]\r\e[0KUpdating APK listing"
+      - export APK_CACHE_DIR=`pwd`/apk-cache
+      - mkdir --parents --verbose $APK_CACHE_DIR/
+      - ln -sf /etc/apk/cache $APK_CACHE_DIR
+      - |
+          # apk_cache
+          echo -e "\e[0Ksection_end:`date +%s`:apk_cache\r\e[0K"
+
+    - - |
+          # apk_update
+          echo -e "\e[0Ksection_start:`date +%s`:apk_update[collapsed=true]\r\e[0KUpdating APK listing"
+      - apk update
+      - |
+          # apk_update
+          echo -e "\e[0Ksection_end:`date +%s`:apk_update\r\e[0K"
+
+    - - |
+          # apk_upgrade
+          echo -e "\e[0Ksection_start:`date +%s`:apk_upgrade[collapsed=true]\r\e[0KUpdating existing packages"
+      - apk upgrade
+      - |
+          # apk_update
+          echo -e "\e[0Ksection_end:`date +%s`:apk_upgrade\r\e[0K"
+
+    - - |
+          # apk_common
+          echo -e "\e[0Ksection_start:`date +%s`:apk_common[collapsed=true]\r\e[0KInstalling common packages"
+      - apk add make git ccache nasm
+      - |
+          # apk_common
+          echo -e "\e[0Ksection_end:`date +%s`:apk_common\r\e[0K"
+
+    - - |
+          # ccache_config
+          echo -e "\e[0Ksection_start:`date +%s`:ccache_config[collapsed=true]\r\e[0KSetting up ccache config"
+      - mkdir --parents --verbose ~/.ccache/
+      - touch ~/.ccache/ccache.conf
+      - |
+          # cache.conf
+          echo Adding ccache configution option
+      - |
+          # base_dir
+          echo base_dir = $PWD                  | tee -a ~/.ccache/ccache.conf
+      - |
+          # cache_dir
+          echo cache_dir = $PWD/ccache          | tee -a ~/.ccache/ccache.conf
+      - |
+          # compiler_check
+          echo compiler_check = content         | tee -a ~/.ccache/ccache.conf
+      - |
+          # stats_log
+          echo stats_log = $PWD/ccache_statslog | tee -a ~/.ccache/ccache.conf
+      - |
+          # max_size
+          echo max_size = 50M                   | tee -a ~/.ccache/ccache.conf
+      - |
+          # ccache_config
+          echo -e "\e[0Ksection_end:`date +%s`:ccache_config\r\e[0K"
+
+    - - |
+          # cache_reset
+          echo -e "\e[0Ksection_start:`date +%s`:ccache_reset[collapsed=true]\r\e[0KResetting ccache statistics"
+      - ccache --zero-stats
+      - ccache --show-stats
+      - |
+          # ccache_reset
+          echo -e "\e[0Ksection_end:`date +%s`:ccache_reset\r\e[0K"
+
+  script:
+    - - |
+          # apk_toolchain
+          echo -e "\e[0Ksection_start:`date +%s`:apk_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
+      - apk add gcc
+      - |
+          # apk_toolchain
+          echo -e "\e[0Ksection_end:`date +%s`:apk_toolchain\r\e[0K"
+
+    - - |
+          # apk_development
+          echo -e "\e[0Ksection_start:`date +%s`:apk_development[collapsed=true]\r\e[0KInstalling development packages"
+      - apk add musl-dev sdl2_mixer-dev libpng-dev curl-dev libgme-dev libopenmpt-dev
+      - |
+          # apk_development
+          echo -e "\e[0Ksection_end:`date +%s`:apk_development\r\e[0K"
+
+    - - |
+          # make
+          echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2"
+      - make --directory=src --keep-going CCACHE=1 ERRORMODE=1 NONX86=1 NOEXECINFO=1 || make --directory=src --keep-going CCACHE=1 ERRORMODE=1 NONX86=1 NOEXECINFO=1
+      - |
+          # make
+          echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
+
+  after_script:
+    - - |
+           # apk_clean
+           echo -e "\e[0Ksection_start:`date +%s`:apk_clean[collapsed=true]\r\e[0KCleaning of unneeded APK packages"
+      - apk cache clean
+      - |
+          # apk_clean
+          echo -e "\e[0Ksection_end:`date +%s`:apk_clean\r\e[0K"
+
+    - - |
+          # ccache_stats
+          echo -e "\e[0Ksection_start:`date +%s`:ccache_stats[collapsed=true]\r\e[0Kccache statistics:"
+      - ccache --show-stats --verbose
+      - ccache --show-log-stats --verbose
+      - |
+          # ccahe_stats
+          echo -e "\e[0Ksection_end:`date +%s`:ccache_stats\r\e[0K"
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 80a3bdcd6798a48d10ea928d60ca3c4fb77bf353..8803620e7a9305cb28359aa31c973223d4df247f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -75,6 +75,7 @@ option(SRB2_CONFIG_ERRORMODE "Compile C code with warnings treated as errors." O
 option(SRB2_CONFIG_DEBUGMODE "Compile with PARANOIA, ZDEBUG, RANGECHECK and PACKETDROP defined." OFF)
 option(SRB2_CONFIG_MOBJCONSISTANCY "Compile with MOBJCONSISTANCY defined." OFF)
 option(SRB2_CONFIG_PACKETDROP "Compile with PACKETDROP defined." OFF)
+option(SRB2_CONFIG_EXECINFO "Enable stack trace dump support." ON)
 option(SRB2_CONFIG_ZDEBUG "Compile with ZDEBUG defined." OFF)
 # SRB2_CONFIG_PROFILEMODE is probably superceded by some CMake setting.
 option(SRB2_CONFIG_PROFILEMODE "Compile for profiling (GCC only)." OFF)
diff --git a/comptime.bat b/comptime.bat
index 0c7ea06d60955ec1cd5015a9b660625dae0b6d2c..c03f088b23b23ba43abc9839e39a99c78317a618 100644
--- a/comptime.bat
+++ b/comptime.bat
@@ -1,6 +1,7 @@
 @echo off
 set BRA=Unknown
 set REV=illegal
+set GL1=Dummy
 
 copy nul: /b +%1\comptime.c tmp.$$$ > nul
 move tmp.$$$ %1\comptime.c > nul
@@ -13,8 +14,9 @@ goto filwri
 :gitrev
 set GIT=%2
 if "%GIT%"=="" set GIT=git
-for /f "usebackq" %%s in (`%GIT% rev-parse --abbrev-ref HEAD`) do @set BRA=%%s
-for /f "usebackq" %%s in (`%GIT% rev-parse HEAD`) do @set REV=%%s
+for /f "tokens=* usebackq" %%s in (`%GIT% rev-parse --abbrev-ref HEAD`) do @set BRA=%%s
+for /f "tokens=* usebackq" %%s in (`%GIT% rev-parse HEAD`) do @set REV=%%s
+for /f "tokens=* usebackq" %%s in (`%GIT% log -1 --format^=%%f`) do @set GL1=%%s
 set REV=%REV:~0,8%
 goto filwri
 
@@ -30,3 +32,4 @@ echo // by the %0 batch file >> %1\comptime.h
 echo // >> %1\comptime.h
 echo const char* compbranch = "%BRA%"; >> %1\comptime.h
 echo const char* comprevision = "%REV%"; >> %1\comptime.h
+echo const char* compnote = "%GL1%"; >> %1\comptime.h
diff --git a/comptime.sh b/comptime.sh
index ce771f631781c153dd87e60a20fdad8064d347e9..3338ecc35506d8b9e9b001679da881b21e81772c 100755
--- a/comptime.sh
+++ b/comptime.sh
@@ -12,24 +12,26 @@ version() {
 //
 const char* compbranch = "$1";
 const char* comprevision = "$2";
+const char* compnote = "$3";
 EOF
 }
 
 versiongit() {
 	gitbranch="$(git rev-parse --abbrev-ref HEAD)"
 	gitversion="$(git rev-parse HEAD | cut -c -8)"
-	version "$gitbranch" "$gitversion";
+	gitsubject="$(git log -1 --format=%f)"
+	version "$gitbranch" "$gitversion" "$gitsubject";
 	exit 0
 }
 
 versionsvn() {
 	svnrevision="$(svnversion -n "$1")"
-	version "Subversion" "r$svnrevision";
+	version "Subversion" "r$svnrevision" "dummy";
 	exit 0
 }
 
 versionfake() {
-	version "Unknown" "illegal";
+	version "Unknown" "illegal" "dummy";
 }
 
 compversion() {
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 15d845c29cacacfc004956b2aba54023a92783b4..49f783722ab429986fb45b317a2d00c36a4b5f94 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -254,6 +254,7 @@ target_compile_options(SRB2SDL2 PRIVATE
 		-Winline
 		-Wformat-y2k
 		-Wformat-security
+		-fwrapv
 
 		$<$<VERSION_LESS:$<C_COMPILER_VERSION>,2.9.5>:
 			-Wno-div-by-zero
@@ -277,7 +278,7 @@ target_compile_options(SRB2SDL2 PRIVATE
 
 		$<$<VERSION_GREATER_EQUAL:$<C_COMPILER_VERSION>,4.5.0>:
 			-Wlogical-op
-			-Wno-error=array-bounds
+			#-Wno-error=array-bounds
 		>
 
 		$<$<VERSION_GREATER_EQUAL:$<C_COMPILER_VERSION>,4.6.0>:
@@ -323,6 +324,7 @@ target_compile_options(SRB2SDL2 PRIVATE
 		-Wno-error=non-literal-null-conversion
 		-Wno-error=constant-conversion
 		-Wno-error=unused-but-set-variable
+		-fwrapv
 	>
 
 	# C, MSVC
@@ -376,6 +378,11 @@ endif()
 if(SRB2_CONFIG_PACKETDROP)
 	target_compile_definitions(SRB2SDL2 PRIVATE -DPACKETDROP)
 endif()
+if(SRB2_CONFIG_EXECINFO)
+else()
+	target_compile_definitions(SRB2SDL2 PRIVATE -DNOEXECINFO)
+	message(STATUS "You have disabled stack trace dump support")
+endif()
 if(SRB2_CONFIG_ZDEBUG)
 	target_compile_definitions(SRB2SDL2 PRIVATE -DZDEBUG)
 endif()
diff --git a/src/Makefile b/src/Makefile
index a0a18be769e0cc64b98d8774b4b3a1aeb3c177ad..6dba19c2418d9ee465b435b40332245762ecd21f 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -66,6 +66,7 @@
 # NOPOSTPROCESSING=1 - ?
 # MOBJCONSISTANCY=1 - ??
 # PACKETDROP=1 - ??
+# NOEXECINFO=1 - Disable stack trace dump support
 # DEBUGMODE=1 - Enable various debugging capabilities.
 #               Also disables optimizations.
 # NOZLIB=1 - Disable some compression capability. Implies
@@ -164,7 +165,7 @@ sources:=
 makedir:=../make
 
 # -DCOMPVERSION: flag to use comptime.h
-opts:=-DCOMPVERSION -g
+opts:=-DCOMPVERSION -g -fwrapv
 libs:=
 
 # This is a list of variables names, of which if defined,
diff --git a/src/Makefile.d/features.mk b/src/Makefile.d/features.mk
index 8b713718cdd604ff83193e560566455453f4a7ff..29587302f5fdc7758834a3e74ba792164a0beb77 100644
--- a/src/Makefile.d/features.mk
+++ b/src/Makefile.d/features.mk
@@ -5,7 +5,7 @@
 passthru_opts+=\
 	NO_IPV6 NOHW NOMD5 NOPOSTPROCESSING\
 	MOBJCONSISTANCY PACKETDROP ZDEBUG\
-	HAVE_MINIUPNPC\
+	HAVE_MINIUPNPC NOEXECINFO\
 
 # build with debugging information
 ifdef DEBUGMODE
diff --git a/src/Makefile.d/versions.mk b/src/Makefile.d/versions.mk
index d2877b374346115621773c822af00a92dd4de72c..2523d7f3c25f6c519e67d728417a40e9e052e883 100644
--- a/src/Makefile.d/versions.mk
+++ b/src/Makefile.d/versions.mk
@@ -116,7 +116,7 @@ ifdef GCC43
  #WFLAGS+=-Wno-error=clobbered
 endif
 ifdef GCC44
- WFLAGS+=-Wno-error=array-bounds
+#WFLAGS+=-Wno-error=array-bounds
 endif
 ifdef GCC46
  WFLAGS+=-Wno-error=suggest-attribute=noreturn
diff --git a/src/b_bot.c b/src/b_bot.c
index 033288a867a171b91e2903c3a89549f62d934b8a..c8228e840f751b676a00e920cde61dcb8f06311c 100644
--- a/src/b_bot.c
+++ b/src/b_bot.c
@@ -584,11 +584,11 @@ void B_RespawnBot(INT32 playernum)
 	P_SetOrigin(tails, x, y, z);
 	if (player->charability == CA_FLY)
 	{
-		P_SetPlayerMobjState(tails, S_PLAY_FLY);
+		P_SetMobjState(tails, S_PLAY_FLY);
 		tails->player->powers[pw_tailsfly] = (UINT16)-1;
 	}
 	else
-		P_SetPlayerMobjState(tails, S_PLAY_FALL);
+		P_SetMobjState(tails, S_PLAY_FALL);
 	P_SetScale(tails, sonic->scale);
 	tails->destscale = sonic->destscale;
 }
@@ -614,6 +614,9 @@ void B_HandleFlightIndicator(player_t *player)
 
 		// otherwise, spawn it
 		P_SetTarget(&tails->hnext, P_SpawnMobjFromMobj(tails, 0, 0, 0, MT_OVERLAY));
+		if (P_MobjWasRemoved(tails->hnext))
+			return;  // we can't spawn one, so it can't exist
+
 		P_SetTarget(&tails->hnext->target, tails);
 		P_SetTarget(&tails->hnext->hprev, tails);
 		P_SetMobjState(tails->hnext, S_FLIGHTINDICATOR);
diff --git a/src/blua/lbaselib.c b/src/blua/lbaselib.c
index 0fc222038dd97dbc4306018a88f87b69b612c167..2bb3d9cf09d030e439231c08a591141518c62b25 100644
--- a/src/blua/lbaselib.c
+++ b/src/blua/lbaselib.c
@@ -275,18 +275,36 @@ static int luaB_dofile (lua_State *L) {
 	int n = lua_gettop(L);
 
 	if (!W_FileHasFolders(wadfiles[numwadfiles - 1]))
-		luaL_error(L, "dofile() only works with PK3 files");
+		luaL_error(L, "dofile() only works with PK3 files and folders");
 
 	snprintf(fullfilename, sizeof(fullfilename), "Lua/%s", filename);
 	lumpnum = W_CheckNumForFullNamePK3(fullfilename, numwadfiles - 1, 0);
 	if (lumpnum == INT16_MAX)
 		luaL_error(L, "can't find script " LUA_QS, fullfilename);
 
-	LUA_LoadLump(numwadfiles - 1, lumpnum, false);
+	LUA_DoLump(numwadfiles - 1, lumpnum, false);
 
 	return lua_gettop(L) - n;
 }
 
+// Edited to load PK3 entries instead
+static int luaB_loadfile (lua_State *L) {
+  const char *filename = luaL_checkstring(L, 1);
+  char fullfilename[256];
+  UINT16 lumpnum;
+
+	if (!W_FileHasFolders(wadfiles[numwadfiles - 1]))
+    luaL_error(L, "loadfile() only works with PK3 files and folders");
+
+  snprintf(fullfilename, sizeof(fullfilename), "Lua/%s", filename);
+  lumpnum = W_CheckNumForFullNamePK3(fullfilename, numwadfiles - 1, 0);
+  if (lumpnum == INT16_MAX)
+    luaL_error(L, "can't find script " LUA_QS, fullfilename);
+
+  LUA_LoadLump(numwadfiles - 1, lumpnum);
+
+  return 1;
+}
 
 static int luaB_assert (lua_State *L) {
   luaL_checkany(L, 1);
@@ -406,6 +424,7 @@ static const luaL_Reg base_funcs[] = {
   {"collectgarbage", luaB_collectgarbage},
   {"error", luaB_error},
   {"dofile", luaB_dofile},
+  {"loadfile", luaB_loadfile},
   {"gcinfo", luaB_gcinfo},
   {"getfenv", luaB_getfenv},
   {"getmetatable", luaB_getmetatable},
diff --git a/src/console.c b/src/console.c
index 01b90ebc39474a547e5666e8e714fa826b032570..0917d916b02c8adaf3b74dc90ddaaf57ef0689c7 100644
--- a/src/console.c
+++ b/src/console.c
@@ -220,13 +220,16 @@ static char *bindtable[NUMINPUTS];
 static void CONS_Bind_f(void)
 {
 	size_t na;
+	char *newcmd;
+	//size_t newlen = 0;
+	unsigned int i;
 	INT32 key;
 
 	na = COM_Argc();
 
-	if (na != 2 && na != 3)
+	if (na < 2)
 	{
-		CONS_Printf(M_GetText("bind <keyname> [<command>]: create shortcut keys to command(s)\n"));
+		CONS_Printf(M_GetText("bind <keyname> [<command>] [<arg1>] [...]: create shortcut keys to command(s)\n"));
 		CONS_Printf("\x82%s", M_GetText("Bind table :\n"));
 		na = 0;
 		for (key = 0; key < NUMINPUTS; key++)
@@ -250,8 +253,36 @@ static void CONS_Bind_f(void)
 	Z_Free(bindtable[key]);
 	bindtable[key] = NULL;
 
-	if (na == 3)
-		bindtable[key] = Z_StrDup(COM_Argv(2));
+	if (na < 3)
+		return;
+
+	for (i = 2; i < na; ++i)
+	{
+		const char *arg = COM_Argv(i);
+
+		// on the second iteration, and after
+		if (i > 2)
+		{
+			size_t newlen = strlen(bindtable[key]) + strlen(arg) + 1; // new length, allow space for ' ' and '\0'
+			size_t curpos = newcmd - bindtable[key]; // offset from newcmd to original pointer
+
+			newcmd = bindtable[key] = Z_Realloc(bindtable[key], newlen, PU_STATIC, NULL);
+			newcmd += curpos; // reapply offset
+
+			newcmd[0] = ' '; // replace previous '\0' w/ ' '
+			++newcmd; // make sure later strcpy doesnt overwrite ' '
+		}
+		// first iteration
+		else
+			// allocate space for argument and a ' ' or '\0'
+			newcmd = bindtable[key] = Z_Calloc(strlen(arg) + 1, PU_STATIC, NULL);
+
+		// the copy
+		strcpy(newcmd, arg);
+
+		// move window past copied argument for next iteration
+		newcmd += strlen(arg);
+	}
 }
 
 //======================================================================
@@ -937,7 +968,7 @@ boolean CON_Responder(event_t *ev)
 		return false;
 
 	// let go keyup events, don't eat them
-	if (ev->type != ev_keydown && ev->type != ev_console)
+	if (ev->type != ev_keydown && ev->type != ev_text && ev->type != ev_console)
 	{
 		if (ev->key == gamecontrol[GC_CONSOLE][0] || ev->key == gamecontrol[GC_CONSOLE][1])
 			consdown = false;
@@ -952,7 +983,7 @@ boolean CON_Responder(event_t *ev)
 		if (modeattacking || metalrecording || marathonmode)
 			return false;
 
-		if (key == gamecontrol[GC_CONSOLE][0] || key == gamecontrol[GC_CONSOLE][1])
+		if ((key == gamecontrol[GC_CONSOLE][0] || key == gamecontrol[GC_CONSOLE][1]) && !shiftdown)
 		{
 			if (consdown) // ignore repeat
 				return true;
@@ -964,7 +995,7 @@ boolean CON_Responder(event_t *ev)
 		// check other keys only if console prompt is active
 		if (!consoleready && key < NUMINPUTS) // metzgermeister: boundary check!!
 		{
-			if (! menuactive && bindtable[key])
+			if (ev->type == ev_keydown && !menuactive && bindtable[key])
 			{
 				COM_BufAddText(bindtable[key]);
 				COM_BufAddText("\n");
@@ -981,6 +1012,13 @@ boolean CON_Responder(event_t *ev)
 		}
 	}
 
+	if (ev->type == ev_text)
+	{
+		if (!consoletoggle)
+			CON_InputAddChar(key);
+		return true;
+	}
+
 	// Always eat ctrl/shift/alt if console open, so the menu doesn't get ideas
 	if (key == KEY_LSHIFT || key == KEY_RSHIFT
 	 || key == KEY_LCTRL || key == KEY_RCTRL
@@ -1295,21 +1333,12 @@ boolean CON_Responder(event_t *ev)
 	else if (key == KEY_KPADSLASH)
 		key = '/';
 
-	if (key >= 'a' && key <= 'z')
-	{
-		if (capslock ^ shiftdown)
-			key = shiftxform[key];
-	}
-	else if (shiftdown)
-		key = shiftxform[key];
-
 	// enter a char into the command prompt
 	if (key < 32 || key > 127)
 		return true;
 
 	if (input_sel != input_cur)
 		CON_InputDelSelection();
-	CON_InputAddChar(key);
 
 	return true;
 }
@@ -1864,9 +1893,9 @@ void CON_Drawer(void)
 
 	if (con_curlines > 0)
 		CON_DrawConsole();
-	else if (gamestate == GS_LEVEL
-	|| gamestate == GS_INTERMISSION || gamestate == GS_ENDING || gamestate == GS_CUTSCENE
-	|| gamestate == GS_CREDITS || gamestate == GS_EVALUATION || gamestate == GS_WAITINGPLAYERS)
+	else if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_CREDITS
+	|| gamestate == GS_EVALUATION || gamestate == GS_ENDING || gamestate == GS_CUTSCENE
+	|| gamestate == GS_WAITINGPLAYERS || cv_debug)
 		CON_DrawHudlines();
 
 	Unlock_state();
diff --git a/src/d_event.h b/src/d_event.h
index 5aa4350600b85a2aafc5a59b87081492a134e048..7743d860947c8b5e2546f2df6a80cc3bdb9679a7 100644
--- a/src/d_event.h
+++ b/src/d_event.h
@@ -22,6 +22,7 @@ typedef enum
 {
 	ev_keydown,
 	ev_keyup,
+	ev_text,
 	ev_console,
 	ev_mouse,
 	ev_joystick,
diff --git a/src/d_main.c b/src/d_main.c
index f8e5d2ea46a3dd5aaeb75d379397d7493f8cf6a4..a07f4a5127545cd69230d90bf285ddf8cedfe6fc 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -192,19 +192,19 @@ void D_ProcessEvents(void)
 		ev = &events[eventtail];
 
 		// Set mouse buttons early in case event is eaten later
-		if (ev->type == ev_keydown || ev->type == ev_keyup)
+		if (ev->type == ev_keydown || ev->type == ev_keyup || ev->type == ev_text)
 		{
 			// Mouse buttons
 			if ((UINT32)(ev->key - KEY_MOUSE1) < MOUSEBUTTONS)
 			{
-				if (ev->type == ev_keydown)
+				if (ev->type == ev_keydown || ev->type == ev_text)
 					mouse.buttons |= 1 << (ev->key - KEY_MOUSE1);
 				else
 					mouse.buttons &= ~(1 << (ev->key - KEY_MOUSE1));
 			}
 			else if ((UINT32)(ev->key - KEY_2MOUSE1) < MOUSEBUTTONS)
 			{
-				if (ev->type == ev_keydown)
+				if (ev->type == ev_keydown || ev->type == ev_text)
 					mouse2.buttons |= 1 << (ev->key - KEY_2MOUSE1);
 				else
 					mouse2.buttons &= ~(1 << (ev->key - KEY_2MOUSE1));
diff --git a/src/d_think.h b/src/d_think.h
index efc1589bf62e277a67ab309f05d33d66af741865..58912458783a2f3b2187e50ebaf00a667a0e0a78 100644
--- a/src/d_think.h
+++ b/src/d_think.h
@@ -51,6 +51,7 @@ typedef struct thinker_s
 	// killough 11/98: count of how many other objects reference
 	// this one using pointers. Used for garbage collection.
 	INT32 references;
+	boolean cachable;
 
 #ifdef PARANOIA
 	INT32 debug_mobjtype;
diff --git a/src/deh_lua.c b/src/deh_lua.c
index b8daa04309d39c0e4167d83797307838dec41a9d..9679780d78f09982fac730208c442fdd7e1717e2 100644
--- a/src/deh_lua.c
+++ b/src/deh_lua.c
@@ -600,14 +600,9 @@ static int ScanConstants(lua_State *L, boolean mathlib, const char *word)
 	return 0;
 }
 
-static inline int lib_getenum(lua_State *L)
+static inline int getEnum(lua_State *L, boolean mathlib, const char *word)
 {
-	const char *word;
 	fixed_t i;
-	boolean mathlib = lua_toboolean(L, lua_upvalueindex(1));
-	if (lua_type(L,2) != LUA_TSTRING)
-		return 0;
-	word = lua_tostring(L,2);
 
 	// check actions, super and globals first, as they don't have _G caching implemented
 	// so they benefit from being checked first
@@ -674,6 +669,46 @@ static inline int lib_getenum(lua_State *L)
 	else if ((!mathlib && LUA_PushGlobals(L, word)) || ScanConstants(L, mathlib, word))
 		return 1;
 
+	return -1;
+}
+
+static int constants_get(lua_State *L)
+{
+	const char *key;
+	int ret;
+
+	if (!lua_isstring(L, 2))
+		return 0;
+
+	key = luaL_checkstring(L, 2);
+
+	// In Lua, mathlib is never there
+	ret = getEnum(L, false, key);
+
+	if (ret != -1)
+		// Don't allow A_* or super.
+		// All userdata is meant to be considered global variables,
+		// so no need to get more specific than "is it userdata?"
+		if (!lua_isuserdata(L, -1) && !lua_isfunction(L, -1))
+			return ret;
+
+	return 0;
+}
+
+static inline int lib_getenum(lua_State *L)
+{
+	const char *word;
+	int ret;
+	boolean mathlib = lua_toboolean(L, lua_upvalueindex(1));
+	if (lua_type(L,2) != LUA_TSTRING)
+		return 0;
+	word = lua_tostring(L,2);
+
+	ret = getEnum(L, mathlib, word);
+
+	if (ret != -1)
+		return ret;
+
 	if (mathlib) return luaL_error(L, "constant '%s' could not be parsed.\n", word);
 
 	return 0;
@@ -776,6 +811,15 @@ int LUA_SOCLib(lua_State *L)
 		LUA_SetCFunctionField(L, "__call", action_call);
 	lua_pop(L, 1);
 
+	// Allow access to constants without forcing the use of name comparison checks Lua-side
+	// This table will not access global variables, only constants
+	lua_newuserdata(L, 0);
+		lua_createtable(L, 0, 2);
+			lua_pushcfunction(L, constants_get);
+			lua_setfield(L, -2, "__index");
+		lua_setmetatable(L, -2);
+	lua_setglobal(L, "constants");
+
 	return 0;
 }
 
diff --git a/src/deh_tables.c b/src/deh_tables.c
index b53cd00c8fc23149a9a9310776335d59e7edd27a..baa8ece163ecff24dc6392ad7f48e9477f7409bc 100644
--- a/src/deh_tables.c
+++ b/src/deh_tables.c
@@ -370,8 +370,9 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi
 	"S_XDEATHSTATE",
 	"S_RAISESTATE",
 
-	// Thok
+	// Thok effect and spin trail
 	"S_THOK",
+	"S_THOKEFFECT",
 
 	// Player
 	"S_PLAY_STND",
@@ -3560,7 +3561,8 @@ const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for sanity t
 	"MT_NULL",
 	"MT_UNKNOWN",
 
-	"MT_THOK", // Thok! mobj
+	"MT_THOK", // Spin trail mobj
+	"MT_THOKEFFECT", // Thok boom effect
 	"MT_PLAYER",
 	"MT_TAILSOVERLAY", // c:
 	"MT_METALJETFUME",
diff --git a/src/doomdef.h b/src/doomdef.h
index 03558c6c233a78ef7ea06cc93326d22b820b9225..29cd4852f00ecd511d379d7698ec6534161a820b 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -540,7 +540,7 @@ extern char liveeventbackup[256];
 #define M_GetText(x) (x)
 #endif
 void M_StartupLocale(void);
-extern void *(*M_Memcpy)(void* dest, const void* src, size_t n) FUNCNONNULL;
+void *M_Memcpy(void* dest, const void* src, size_t n);
 char *va(const char *format, ...) FUNCPRINTF;
 char *M_GetToken(const char *inputString);
 void M_UnGetToken(void);
diff --git a/src/dummy/i_system.c b/src/dummy/i_system.c
index 70e1ef4ec2b7aba9699731c0d743733750bc26fc..fe33cfe3ef46bdfecf38b3187d3946242b601551 100644
--- a/src/dummy/i_system.c
+++ b/src/dummy/i_system.c
@@ -1,6 +1,7 @@
 #include "../doomdef.h"
 #include "../doomtype.h"
 #include "../i_system.h"
+#include "../i_time.h"
 
 FILE *logstream = NULL;
 
@@ -19,6 +20,11 @@ void I_Sleep(UINT32 ms)
 	(void)ms;
 }
 
+void I_SleepDuration(precise_t duration)
+{
+	(void)duration;
+}
+
 precise_t I_GetPreciseTime(void)
 {
 	return 0;
diff --git a/src/f_finale.c b/src/f_finale.c
index 1e2276854804274bce5b4303cf5c23d40145a8a3..cb64618535659f329bb357f65c2cec0bbe006b2d 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -4524,7 +4524,7 @@ void F_TextPromptDrawer(void)
 			if (players[j].mo->state == states+S_PLAY_STND && players[j].mo->tics != -1)\
 				players[j].mo->tics++;\
 			else if (players[j].mo->state == states+S_PLAY_WAIT)\
-				P_SetPlayerMobjState(players[j].mo, S_PLAY_STND);\
+				P_SetMobjState(players[j].mo, S_PLAY_STND);\
 		}\
 	}
 
diff --git a/src/filesrch.c b/src/filesrch.c
index 111dfd6e7a8be99e9f9b2764d773ec9d09dadd2a..ad0cf02a13026dfc7dd109b1ef025061d1e46ca0 100644
--- a/src/filesrch.c
+++ b/src/filesrch.c
@@ -23,6 +23,16 @@
 #include <windows.h>
 #endif
 #include <sys/stat.h>
+
+#ifndef S_ISLNK
+#define IGNORE_SYMLINKS
+#endif
+
+#ifndef IGNORE_SYMLINKS
+#include <unistd.h>
+#include <libgen.h>
+#include <limits.h>
+#endif
 #include <string.h>
 
 #include "filesrch.h"
@@ -461,6 +471,9 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want
 		}
 		else if (!strcasecmp(searchname, dent->d_name))
 		{
+#ifndef IGNORE_SYMLINKS
+			struct stat statbuf;
+#endif
 			switch (checkfilemd5(searchpath, wantedmd5sum))
 			{
 				case FS_FOUND:
@@ -468,6 +481,19 @@ filestatus_t filesearch(char *filename, const char *startpath, const UINT8 *want
 						strcpy(filename,searchpath);
 					else
 						strcpy(filename,dent->d_name);
+#ifndef IGNORE_SYMLINKS
+					if (lstat(filename, &statbuf) != -1)
+					{
+						if (S_ISLNK(statbuf.st_mode))
+						{
+							char *tempbuf = realpath(filename, NULL);
+							if (!tempbuf)
+								I_Error("Error parsing link %s: %s", filename, strerror(errno));
+							strncpy(filename, tempbuf, MAX_WADPATH);
+							free(tempbuf);
+						}
+					}
+#endif
 					retval = FS_FOUND;
 					found = 1;
 					break;
diff --git a/src/g_demo.c b/src/g_demo.c
index 4acc9776b87491272e2ee3df42b97f9659723681..6afea12a2995cf12a2392d4ab3a4071877496df7 100644
--- a/src/g_demo.c
+++ b/src/g_demo.c
@@ -798,32 +798,53 @@ void G_GhostTicker(void)
 					if (type == MT_GHOST)
 					{
 						mobj = P_SpawnGhostMobj(g->mo); // does a large portion of the work for us
-						mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|tr_trans60<<FF_TRANSSHIFT; // P_SpawnGhostMobj sets trans50, we want trans60
+						if (!P_MobjWasRemoved(mobj))
+							mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|tr_trans60<<FF_TRANSSHIFT; // P_SpawnGhostMobj sets trans50, we want trans60
 					}
-					else
+					else if (type == MT_THOKEFFECT)
 					{
-						mobj = P_SpawnMobjFromMobj(g->mo, 0, 0, -FixedDiv(FixedMul(g->mo->info->height, g->mo->scale) - g->mo->height,3*FRACUNIT), MT_THOK);
-						mobj->sprite = states[mobjinfo[type].spawnstate].sprite;
-						mobj->frame = (states[mobjinfo[type].spawnstate].frame & FF_FRAMEMASK) | tr_trans60<<FF_TRANSSHIFT;
+						mobj = P_SpawnMobjFromMobj(g->mo, 0, 0, FixedDiv(g->mo->height, g->mo->scale)*3/4, type);
+						mobj->angle = g->mo->angle + ANGLE_90;
+						mobj->fuse = 7;
+						mobj->scale = g->mo->scale / 3;
+						mobj->destscale = 10 * g->mo->scale;
+						mobj->colorized = true;
 						mobj->color = g->mo->color;
-						mobj->skin = g->mo->skin;
-						P_SetScale(mobj, (mobj->destscale = g->mo->scale));
+						mobj->momx = -g->mo->momx / 2;
+						mobj->momy = -g->mo->momy / 2;
+					}
 
-						if (type == MT_THOK) // spintrail-specific modification for MT_THOK
+					else
+					{
+						mobj = P_SpawnMobjFromMobj(g->mo, 0, 0, -FixedDiv(FixedMul(g->mo->info->height, g->mo->scale) - g->mo->height,3*FRACUNIT), MT_THOK);
+						if (!P_MobjWasRemoved(mobj))
 						{
-							mobj->frame = FF_TRANS80;
-							mobj->fuse = mobj->tics;
+							mobj->sprite = states[mobjinfo[type].spawnstate].sprite;
+							mobj->frame = (states[mobjinfo[type].spawnstate].frame & FF_FRAMEMASK) | tr_trans60<<FF_TRANSSHIFT;
+							mobj->color = g->mo->color;
+							mobj->skin = g->mo->skin;
+							P_SetScale(mobj, (mobj->destscale = g->mo->scale));
+
+							if (type == MT_THOK) // spintrail-specific modification for MT_THOK
+							{
+								mobj->frame = FF_TRANS80;
+								mobj->fuse = mobj->tics;
+							}
+							mobj->tics = -1; // nope.
 						}
-						mobj->tics = -1; // nope.
 					}
-					mobj->floorz = mobj->z;
-					mobj->ceilingz = mobj->z+mobj->height;
-					P_UnsetThingPosition(mobj);
-					mobj->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; // make an ATTEMPT to curb crazy SOCs fucking stuff up...
-					P_SetThingPosition(mobj);
-					if (!mobj->fuse)
-						mobj->fuse = 8;
-					P_SetTarget(&mobj->target, g->mo);
+
+					if (!P_MobjWasRemoved(mobj))
+					{
+						mobj->floorz = mobj->z;
+						mobj->ceilingz = mobj->z+mobj->height;
+						P_UnsetThingPosition(mobj);
+						mobj->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; // make an ATTEMPT to curb crazy SOCs fucking stuff up...
+						P_SetThingPosition(mobj);
+						if (!mobj->fuse)
+							mobj->fuse = 8;
+						P_SetTarget(&mobj->target, g->mo);
+					}
 				}
 			}
 			if (xziptic & EZT_HIT)
@@ -847,6 +868,8 @@ void G_GhostTicker(void)
 					|| health != 0 || i >= 4) // only spawn for the first 4 hits per frame, to prevent ghosts from splode-spamming too bad.
 						continue;
 					poof = P_SpawnMobj(x, y, z, MT_GHOST);
+					if (P_MobjWasRemoved(poof))
+						continue;
 					poof->angle = angle;
 					poof->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; // make an ATTEMPT to curb crazy SOCs fucking stuff up...
 					poof->health = 0;
@@ -892,19 +915,22 @@ void G_GhostTicker(void)
 				if (follow)
 					P_RemoveMobj(follow);
 				P_SetTarget(&follow, P_SpawnMobjFromMobj(g->mo, 0, 0, 0, MT_GHOST));
-				P_SetTarget(&follow->tracer, g->mo);
-				follow->tics = -1;
-				temp = READINT16(g->p)<<FRACBITS;
-				follow->height = FixedMul(follow->scale, temp);
+				if (!P_MobjWasRemoved(follow))
+				{
+					P_SetTarget(&follow->tracer, g->mo);
+					follow->tics = -1;
+					temp = READINT16(g->p)<<FRACBITS;
+					follow->height = FixedMul(follow->scale, temp);
 
-				if (followtic & FZT_LINKDRAW)
-					follow->flags2 |= MF2_LINKDRAW;
+					if (followtic & FZT_LINKDRAW)
+						follow->flags2 |= MF2_LINKDRAW;
 
-				if (followtic & FZT_COLORIZED)
-					follow->colorized = true;
+					if (followtic & FZT_COLORIZED)
+						follow->colorized = true;
 
-				if (followtic & FZT_SKIN)
-					follow->skin = skins[READUINT8(g->p)];
+					if (followtic & FZT_SKIN)
+						follow->skin = &skins[READUINT8(g->p)];
+				}
 			}
 			if (follow)
 			{
@@ -1094,31 +1120,50 @@ void G_ReadMetalTic(mobj_t *metal)
 				{
 					mobj = P_SpawnGhostMobj(metal); // does a large portion of the work for us
 				}
+				else if (type == MT_THOKEFFECT)
+				{
+					mobj = P_SpawnMobjFromMobj(metal, 0, 0, FixedDiv(metal->height, metal->scale)*3/4, type);
+					mobj->angle = metal->angle + ANGLE_90;
+					mobj->fuse = 7;
+					mobj->scale = metal->scale / 3;
+					mobj->destscale = 10 * metal->scale;
+					mobj->colorized = true;
+					mobj->color = metal->color;
+					mobj->momx = -metal->momx / 2;
+					mobj->momy = -metal->momy / 2;
+				}
 				else
 				{
 					mobj = P_SpawnMobjFromMobj(metal, 0, 0, -FixedDiv(FixedMul(metal->info->height, metal->scale) - metal->height,3*FRACUNIT), MT_THOK);
-					mobj->sprite = states[mobjinfo[type].spawnstate].sprite;
-					mobj->frame = states[mobjinfo[type].spawnstate].frame;
-					mobj->angle = metal->angle;
-					mobj->color = metal->color;
-					mobj->skin = metal->skin;
-					P_SetScale(mobj, (mobj->destscale = metal->scale));
-
-					if (type == MT_THOK) // spintrail-specific modification for MT_THOK
+					if (!P_MobjWasRemoved(mobj))
 					{
-						mobj->frame = FF_TRANS70;
-						mobj->fuse = mobj->tics;
+						mobj->sprite = states[mobjinfo[type].spawnstate].sprite;
+						mobj->frame = states[mobjinfo[type].spawnstate].frame;
+						mobj->angle = metal->angle;
+						mobj->color = metal->color;
+						mobj->skin = metal->skin;
+						P_SetScale(mobj, (mobj->destscale = metal->scale));
+
+						if (type == MT_THOK) // spintrail-specific modification for MT_THOK
+						{
+							mobj->frame = FF_TRANS70;
+							mobj->fuse = mobj->tics;
+						}
+						mobj->tics = -1; // nope.
 					}
-					mobj->tics = -1; // nope.
 				}
-				mobj->floorz = mobj->z;
-				mobj->ceilingz = mobj->z+mobj->height;
-				P_UnsetThingPosition(mobj);
-				mobj->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; // make an ATTEMPT to curb crazy SOCs fucking stuff up...
-				P_SetThingPosition(mobj);
-				if (!mobj->fuse)
-					mobj->fuse = 8;
-				P_SetTarget(&mobj->target, metal);
+
+				if (!P_MobjWasRemoved(mobj))
+				{
+					mobj->floorz = mobj->z;
+					mobj->ceilingz = mobj->z+mobj->height;
+					P_UnsetThingPosition(mobj);
+					mobj->flags = MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY; // make an ATTEMPT to curb crazy SOCs fucking stuff up...
+					P_SetThingPosition(mobj);
+					if (!mobj->fuse)
+						mobj->fuse = 8;
+					P_SetTarget(&mobj->target, metal);
+				}
 			}
 		}
 		if (xziptic & EZT_SPRITE)
@@ -1140,19 +1185,23 @@ void G_ReadMetalTic(mobj_t *metal)
 				if (follow)
 					P_RemoveMobj(follow);
 				P_SetTarget(&follow, P_SpawnMobjFromMobj(metal, 0, 0, 0, MT_GHOST));
-				P_SetTarget(&follow->tracer, metal);
-				follow->tics = -1;
-				temp = READINT16(metal_p)<<FRACBITS;
-				follow->height = FixedMul(follow->scale, temp);
+				if (!P_MobjWasRemoved(follow))
+				{
+					P_SetTarget(&follow->tracer, metal);
+					follow->tics = -1;
+					temp = READINT16(metal_p)<<FRACBITS;
+					follow->height = FixedMul(follow->scale, temp);
 
-				if (followtic & FZT_LINKDRAW)
-					follow->flags2 |= MF2_LINKDRAW;
+					if (followtic & FZT_LINKDRAW)
+						follow->flags2 |= MF2_LINKDRAW;
 
-				if (followtic & FZT_COLORIZED)
-					follow->colorized = true;
+					if (followtic & FZT_COLORIZED)
+						follow->colorized = true;
+
+					if (followtic & FZT_SKIN)
+						follow->skin = &skins[READUINT8(metal_p)];
+				}
 
-				if (followtic & FZT_SKIN)
-					follow->skin = skins[READUINT8(metal_p)];
 			}
 			if (follow)
 			{
@@ -2535,7 +2584,9 @@ void G_AddGhost(char *defdemoname)
 	{ // A bit more complex than P_SpawnPlayer because ghosts aren't solid and won't just push themselves out of the ceiling.
 		fixed_t z,f,c;
 		fixed_t offset = mthing->z << FRACBITS;
-		gh->mo = P_SpawnMobj(mthing->x << FRACBITS, mthing->y << FRACBITS, 0, MT_GHOST);
+		P_SetTarget(&gh->mo, P_SpawnMobj(mthing->x << FRACBITS, mthing->y << FRACBITS, 0, MT_GHOST));
+		if (P_MobjWasRemoved(gh->mo))
+			return;
 		gh->mo->angle = FixedAngle(mthing->angle << FRACBITS);
 		f = gh->mo->floorz;
 		c = gh->mo->ceilingz - mobjinfo[MT_PLAYER].height;
diff --git a/src/g_game.c b/src/g_game.c
index b71f5f61d98d505f90375d7764d55b26f4489851..77d50b2911512f2d54e178ef9491838bb453019d 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -308,7 +308,9 @@ consvar_t cv_chatheight= CVAR_INIT ("chatheight", "8", CV_SAVE, chatheight_cons_
 consvar_t cv_chatnotifications= CVAR_INIT ("chatnotifications", "On", CV_SAVE, CV_OnOff, NULL);
 
 // chat spam protection (why would you want to disable that???)
-consvar_t cv_chatspamprotection= CVAR_INIT ("chatspamprotection", "On", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_chatspamprotection= CVAR_INIT ("chatspamprotection", "On", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
+consvar_t cv_chatspamspeed= CVAR_INIT ("chatspamspeed", "35", CV_SAVE|CV_NETVAR, CV_Unsigned, NULL);
+consvar_t cv_chatspamburst= CVAR_INIT ("chatspamburst", "3", CV_SAVE|CV_NETVAR, CV_Unsigned, NULL);
 
 // minichat text background
 consvar_t cv_chatbacktint = CVAR_INIT ("chatbacktint", "On", CV_SAVE, CV_OnOff, NULL);
@@ -5380,16 +5382,29 @@ void G_FreeMapSearch(mapsearchfreq_t *freq, INT32 freqc)
 INT32 G_FindMapByNameOrCode(const char *mapname, char **realmapnamep)
 {
 	boolean usemapcode = false;
-
 	INT32 newmapnum;
-
-	size_t mapnamelen;
-
+	size_t mapnamelen = strlen(mapname);
 	char *p;
 
-	mapnamelen = strlen(mapname);
-
-	if (mapnamelen == 2)/* maybe two digit code */
+	if (mapnamelen == 1)
+	{
+		if (mapname[0] == '*') // current map
+		{
+			usemapcode = true;
+			newmapnum = gamemap;
+		}
+		else if (mapname[0] == '+' && mapheaderinfo[gamemap-1]) // next map
+		{
+			usemapcode = true;
+			newmapnum = mapheaderinfo[gamemap-1]->nextlevel;
+			if (newmapnum < 1 || newmapnum > NUMMAPS)
+			{
+				CONS_Alert(CONS_ERROR, M_GetText("NextLevel (%d) is not a valid map.\n"), newmapnum);
+				return 0;
+			}
+		}
+	}
+	else if (mapnamelen == 2)/* maybe two digit code */
 	{
 		if (( newmapnum = M_MapNumber(mapname[0], mapname[1]) ))
 			usemapcode = true;
diff --git a/src/g_game.h b/src/g_game.h
index 4d86a0455e560e22afc9d75b24f5d97acaf4e95c..2612224a1252266346c352aa0294fee28047fe15 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -53,7 +53,7 @@ extern consvar_t cv_instantretry;
 
 // used in game menu
 extern consvar_t cv_tutorialprompt;
-extern consvar_t cv_chatwidth, cv_chatnotifications, cv_chatheight, cv_chattime, cv_consolechat, cv_chatbacktint, cv_chatspamprotection, cv_compactscoreboard;
+extern consvar_t cv_chatwidth, cv_chatnotifications, cv_chatheight, cv_chattime, cv_consolechat, cv_chatbacktint, cv_chatspamprotection, cv_chatspamspeed, cv_chatspamburst, cv_compactscoreboard;
 extern consvar_t cv_crosshair, cv_crosshair2;
 extern consvar_t cv_invertmouse, cv_alwaysfreelook, cv_chasefreelook, cv_mousemove;
 extern consvar_t cv_invertmouse2, cv_alwaysfreelook2, cv_chasefreelook2, cv_mousemove2;
diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h
index b02f2824aad4437cdbd5f496534e019893870557..3b660cc70c36515dab60b4ae3eabb914b5de7d5d 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -283,6 +283,7 @@ enum hwdsetspecialstate
 	HWD_SET_SHADERS,
 	HWD_SET_TEXTUREFILTERMODE,
 	HWD_SET_TEXTUREANISOTROPICMODE,
+	HWD_SET_WIREFRAME,
 	HWD_NUMSTATE
 };
 
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index ec9dc76130425df42ade0d53b9d597494d8bd7f5..bc473c1f8de04b8c2a1fcbfa3a46a8272592a28f 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -1319,6 +1319,7 @@ void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color)
 	FOutVector v[4];
 	FSurfaceInfo Surf;
 	float fx, fy, fw, fh;
+	UINT8 alphalevel = ((color & V_ALPHAMASK) >> V_ALPHASHIFT);
 
 	UINT8 perplayershuffle = 0;
 
@@ -1483,8 +1484,16 @@ void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color)
 
 	Surf.PolyColor = V_GetColor(color);
 
+	if (alphalevel)
+	{
+		if (alphalevel == 10) Surf.PolyColor.s.alpha = softwaretranstogl_lo[st_translucency]; // V_HUDTRANSHALF
+		else if (alphalevel == 11) Surf.PolyColor.s.alpha = softwaretranstogl[st_translucency]; // V_HUDTRANS
+		else if (alphalevel == 12) Surf.PolyColor.s.alpha = softwaretranstogl_hi[st_translucency]; // V_HUDTRANSDOUBLE
+		else Surf.PolyColor.s.alpha = softwaretranstogl[10-alphalevel];
+	}
+
 	HWD.pfnDrawPolygon(&Surf, v, 4,
-		PF_Modulated|PF_NoTexture|PF_NoDepthTest);
+		PF_Modulated|PF_NoTexture|PF_NoDepthTest|PF_Translucent);
 }
 
 #ifdef HAVE_PNG
diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c
index bcfdfa960d7b723ecb0be2437667bcf09b1030e9..94f01e2991ab8629676198272d76696c42d3afe1 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -138,6 +138,7 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_UNKN
 
 	&lspr[NOLIGHT],     // SPR_THOK
+	&lspr[NOLIGHT],     // SPR_THKE
 	&lspr[SUPERSONIC_L],// SPR_PLAY
 
 	// Enemies
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 4f6bdef0f0e70d3b8c99922d4cddd3f177ef14d0..60a5b256245367858a736ae56342ba34b6bc8b7d 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -180,6 +180,11 @@ static boolean HWR_UseShader(void)
 	return (cv_glshaders.value && gl_shadersavailable);
 }
 
+static boolean HWR_IsWireframeMode(void)
+{
+	return (cv_glwireframe.value && cv_debug);
+}
+
 void HWR_Lighting(FSurfaceInfo *Surface, INT32 light_level, extracolormap_t *colormap)
 {
 	RGBA_t poly_color, tint_color, fade_color;
@@ -372,11 +377,9 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 	INT32 i;
 
 	float height; // constant y for all points on the convex flat polygon
-	float flatxref, flatyref, anglef = 0.0f;
+	float anglef = 0.0f;
 	float fflatwidth = 64.0f, fflatheight = 64.0f;
-	UINT16 flatflag = 63;
-
-	boolean texflat = false;
+	float xscale = 1.0f, yscale = 1.0f;
 
 	float tempxsow, tempytow;
 	float scrollx = 0.0f, scrolly = 0.0f;
@@ -411,11 +414,7 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 			slope = gl_frontsector->c_slope;
 	}
 
-	// Set fixedheight to the slope's height from our viewpoint, if we have a slope
-	if (slope)
-		fixedheight = P_GetSlopeZAt(slope, viewx, viewy);
-
-	height = FIXED_TO_FLOAT(fixedheight);
+	height = FixedToFloat(fixedheight);
 
 	// Allocate plane-vertex buffer if we need to
 	if (!planeVerts || nrPlaneVerts > numAllocedPlaneVerts)
@@ -431,8 +430,8 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 		if (levelflat->type == LEVELFLAT_FLAT)
 		{
 			size_t len = W_LumpLength(levelflat->u.flat.lumpnum);
-			flatflag = R_GetFlatSize(len) - 1;
-			fflatwidth = fflatheight = (float)(flatflag + 1);
+			unsigned flatflag = R_GetFlatSize(len);
+			fflatwidth = fflatheight = (float)flatflag;
 		}
 		else
 		{
@@ -446,29 +445,28 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 				fflatwidth = levelflat->width;
 				fflatheight = levelflat->height;
 			}
-			texflat = true;
 		}
 	}
 	else // set no texture
 		HWR_SetCurrentTexture(NULL);
 
-	// reference point for flat texture coord for each vertex around the polygon
-	flatxref = (float)(((fixed_t)pv->x & (~flatflag)) / fflatwidth);
-	flatyref = (float)(((fixed_t)pv->y & (~flatflag)) / fflatheight);
-
 	// transform
 	if (FOFsector != NULL)
 	{
 		if (!isceiling) // it's a floor
 		{
-			scrollx = FIXED_TO_FLOAT(FOFsector->floorxoffset)/fflatwidth;
-			scrolly = FIXED_TO_FLOAT(FOFsector->flooryoffset)/fflatheight;
+			xscale = FixedToFloat(FOFsector->floorxscale);
+			yscale = FixedToFloat(FOFsector->flooryscale);
+			scrollx = FixedToFloat(FOFsector->floorxoffset) / fflatwidth;
+			scrolly = FixedToFloat(FOFsector->flooryoffset) / fflatheight;
 			angle = FOFsector->floorangle;
 		}
 		else // it's a ceiling
 		{
-			scrollx = FIXED_TO_FLOAT(FOFsector->ceilingxoffset)/fflatwidth;
-			scrolly = FIXED_TO_FLOAT(FOFsector->ceilingyoffset)/fflatheight;
+			xscale = FixedToFloat(FOFsector->ceilingxscale);
+			yscale = FixedToFloat(FOFsector->ceilingyscale);
+			scrollx = FixedToFloat(FOFsector->ceilingxoffset) / fflatwidth;
+			scrolly = FixedToFloat(FOFsector->ceilingyoffset) / fflatheight;
 			angle = FOFsector->ceilingangle;
 		}
 	}
@@ -476,41 +474,28 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 	{
 		if (!isceiling) // it's a floor
 		{
-			scrollx = FIXED_TO_FLOAT(gl_frontsector->floorxoffset)/fflatwidth;
-			scrolly = FIXED_TO_FLOAT(gl_frontsector->flooryoffset)/fflatheight;
+			xscale = FixedToFloat(gl_frontsector->floorxscale);
+			yscale = FixedToFloat(gl_frontsector->flooryscale);
+			scrollx = FixedToFloat(gl_frontsector->floorxoffset) / fflatwidth;
+			scrolly = FixedToFloat(gl_frontsector->flooryoffset) / fflatheight;
 			angle = gl_frontsector->floorangle;
 		}
 		else // it's a ceiling
 		{
-			scrollx = FIXED_TO_FLOAT(gl_frontsector->ceilingxoffset)/fflatwidth;
-			scrolly = FIXED_TO_FLOAT(gl_frontsector->ceilingyoffset)/fflatheight;
+			xscale = FixedToFloat(gl_frontsector->ceilingxscale);
+			yscale = FixedToFloat(gl_frontsector->ceilingyscale);
+			scrollx = FixedToFloat(gl_frontsector->ceilingxoffset) / fflatwidth;
+			scrolly = FixedToFloat(gl_frontsector->ceilingyoffset) / fflatheight;
 			angle = gl_frontsector->ceilingangle;
 		}
 	}
 
-	if (angle) // Only needs to be done if there's an altered angle
-	{
-		tempxsow = flatxref;
-		tempytow = flatyref;
-
-		anglef = ANG2RAD(InvAngle(angle));
-
-		flatxref = (tempxsow * cos(anglef)) - (tempytow * sin(anglef));
-		flatyref = (tempxsow * sin(anglef)) + (tempytow * cos(anglef));
-	}
+	anglef = ANG2RAD(InvAngle(angle));
 
 #define SETUP3DVERT(vert, vx, vy) {\
 		/* Hurdler: add scrolling texture on floor/ceiling */\
-		if (texflat)\
-		{\
-			vert->s = (float)((vx) / fflatwidth) + scrollx;\
-			vert->t = -(float)((vy) / fflatheight) + scrolly;\
-		}\
-		else\
-		{\
-			vert->s = (float)(((vx) / fflatwidth) - flatxref + scrollx);\
-			vert->t = (float)(flatyref - ((vy) / fflatheight) + scrolly);\
-		}\
+		vert->s = ((vx) / fflatwidth) + (scrollx / xscale);\
+		vert->t = -((vy) / fflatheight) + (scrolly / yscale);\
 \
 		/* Need to rotate before translate */\
 		if (angle) /* Only needs to be done if there's an altered angle */\
@@ -521,15 +506,18 @@ static void HWR_RenderPlane(subsector_t *subsector, extrasubsector_t *xsub, bool
 			vert->t = (tempxsow * sin(anglef)) + (tempytow * cos(anglef));\
 		}\
 \
-		vert->x = (vx);\
-		vert->y = height;\
-		vert->z = (vy);\
+		vert->s *= xscale;\
+		vert->t *= yscale;\
 \
 		if (slope)\
 		{\
-			fixedheight = P_GetSlopeZAt(slope, FLOAT_TO_FIXED((vx)), FLOAT_TO_FIXED((vy)));\
-			vert->y = FIXED_TO_FLOAT(fixedheight);\
+			fixedheight = P_GetSlopeZAt(slope, FloatToFixed((vx)), FloatToFixed((vy)));\
+			height = FixedToFloat(fixedheight);\
 		}\
+\
+		vert->x = (vx);\
+		vert->y = height;\
+		vert->z = (vy);\
 }
 
 	for (i = 0, v3d = planeVerts; i < (INT32)nrPlaneVerts; i++,v3d++,pv++)
@@ -1081,6 +1069,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 	fixed_t h, l; // 3D sides and 2s middle textures
 	fixed_t hS, lS;
+	float xscale, yscale;
 
 	gl_sidedef = gl_curline->sidedef;
 	gl_linedef = gl_curline->linedef;
@@ -1179,47 +1168,53 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 		// check TOP TEXTURE
 		if ((worldhighslope < worldtopslope || worldhigh < worldtop) && gl_toptexture)
 		{
+			grTex = HWR_GetTexture(gl_toptexture);
+			xscale = FixedToFloat(gl_sidedef->scalex_top);
+			yscale = FixedToFloat(gl_sidedef->scaley_top);
+
+			fixed_t texheight = FixedDiv(textureheight[gl_toptexture], gl_sidedef->scaley_top);
+
 			// PEGGING
 			if (gl_linedef->flags & ML_DONTPEGTOP)
 				texturevpeg = 0;
 			else if (gl_linedef->flags & ML_SKEWTD)
-				texturevpeg = worldhigh + textureheight[gl_toptexture] - worldtop;
+				texturevpeg = worldhigh + texheight - worldtop;
 			else
-				texturevpeg = gl_backsector->ceilingheight + textureheight[gl_toptexture] - gl_frontsector->ceilingheight;
+				texturevpeg = gl_backsector->ceilingheight + texheight - gl_frontsector->ceilingheight;
+
+			texturevpeg *= yscale;
 
 			texturevpeg += gl_sidedef->rowoffset + gl_sidedef->offsety_top;
 
 			// This is so that it doesn't overflow and screw up the wall, it doesn't need to go higher than the texture's height anyway
-			texturevpeg %= textureheight[gl_toptexture];
-
-			grTex = HWR_GetTexture(gl_toptexture);
+			texturevpeg %= texheight;
 
 			wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY;
-			wallVerts[0].t = wallVerts[1].t = (texturevpeg + gl_frontsector->ceilingheight - gl_backsector->ceilingheight) * grTex->scaleY;
-			wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_top) * grTex->scaleX;
-			wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_top) * grTex->scaleX;
+			wallVerts[0].t = wallVerts[1].t = (texturevpeg + (gl_frontsector->ceilingheight - gl_backsector->ceilingheight) * yscale) * grTex->scaleY;
+			wallVerts[0].s = wallVerts[3].s = ((cliplow * xscale) + gl_sidedef->offsetx_top) * grTex->scaleX;
+			wallVerts[2].s = wallVerts[1].s = ((cliphigh * xscale) + gl_sidedef->offsetx_top) * grTex->scaleX;
 
 			// Adjust t value for sloped walls
 			if (!(gl_linedef->flags & ML_SKEWTD))
 			{
 				// Unskewed
-				wallVerts[3].t -= (worldtop - gl_frontsector->ceilingheight) * grTex->scaleY;
-				wallVerts[2].t -= (worldtopslope - gl_frontsector->ceilingheight) * grTex->scaleY;
-				wallVerts[0].t -= (worldhigh - gl_backsector->ceilingheight) * grTex->scaleY;
-				wallVerts[1].t -= (worldhighslope - gl_backsector->ceilingheight) * grTex->scaleY;
+				wallVerts[3].t -= (worldtop - gl_frontsector->ceilingheight) * yscale * grTex->scaleY;
+				wallVerts[2].t -= (worldtopslope - gl_frontsector->ceilingheight) * yscale * grTex->scaleY;
+				wallVerts[0].t -= (worldhigh - gl_backsector->ceilingheight) * yscale * grTex->scaleY;
+				wallVerts[1].t -= (worldhighslope - gl_backsector->ceilingheight) * yscale * grTex->scaleY;
 			}
 			else if (gl_linedef->flags & ML_DONTPEGTOP)
 			{
 				// Skewed by top
-				wallVerts[0].t = (texturevpeg + worldtop - worldhigh) * grTex->scaleY;
-				wallVerts[1].t = (texturevpeg + worldtopslope - worldhighslope) * grTex->scaleY;
+				wallVerts[0].t = (texturevpeg + (worldtop - worldhigh) * yscale) * grTex->scaleY;
+				wallVerts[1].t = (texturevpeg + (worldtopslope - worldhighslope) * yscale) * grTex->scaleY;
 			}
 			else
 			{
 				// Skewed by bottom
-				wallVerts[0].t = wallVerts[1].t = (texturevpeg + worldtop - worldhigh) * grTex->scaleY;
-				wallVerts[3].t = wallVerts[0].t - (worldtop - worldhigh) * grTex->scaleY;
-				wallVerts[2].t = wallVerts[1].t - (worldtopslope - worldhighslope) * grTex->scaleY;
+				wallVerts[0].t = wallVerts[1].t = (texturevpeg + (worldtop - worldhigh) * yscale) * grTex->scaleY;
+				wallVerts[3].t = wallVerts[0].t - (worldtop - worldhigh) * yscale * grTex->scaleY;
+				wallVerts[2].t = wallVerts[1].t - (worldtopslope - worldhighslope) * yscale * grTex->scaleY;
 			}
 
 			// set top/bottom coords
@@ -1239,6 +1234,10 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 		// check BOTTOM TEXTURE
 		if ((worldlowslope > worldbottomslope || worldlow > worldbottom) && gl_bottomtexture)
 		{
+			grTex = HWR_GetTexture(gl_bottomtexture);
+			xscale = FixedToFloat(gl_sidedef->scalex_bottom);
+			yscale = FixedToFloat(gl_sidedef->scaley_bottom);
+
 			// PEGGING
 			if (!(gl_linedef->flags & ML_DONTPEGBOTTOM))
 				texturevpeg = 0;
@@ -1247,38 +1246,38 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			else
 				texturevpeg = gl_frontsector->floorheight - gl_backsector->floorheight;
 
-			texturevpeg += gl_sidedef->rowoffset + gl_sidedef->offsety_bot;
+			texturevpeg *= yscale;
 
-			// This is so that it doesn't overflow and screw up the wall, it doesn't need to go higher than the texture's height anyway
-			texturevpeg %= textureheight[gl_bottomtexture];
+			texturevpeg += gl_sidedef->rowoffset + gl_sidedef->offsety_bottom;
 
-			grTex = HWR_GetTexture(gl_bottomtexture);
+			// This is so that it doesn't overflow and screw up the wall, it doesn't need to go higher than the texture's height anyway
+			texturevpeg %= FixedDiv(textureheight[gl_bottomtexture], gl_sidedef->scaley_bottom);
 
 			wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY;
-			wallVerts[0].t = wallVerts[1].t = (texturevpeg + gl_backsector->floorheight - gl_frontsector->floorheight) * grTex->scaleY;
-			wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_bot) * grTex->scaleX;
-			wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_bot) * grTex->scaleX;
+			wallVerts[0].t = wallVerts[1].t = (texturevpeg + (gl_backsector->floorheight - gl_frontsector->floorheight) * yscale) * grTex->scaleY;
+			wallVerts[0].s = wallVerts[3].s = ((cliplow * xscale) + gl_sidedef->offsetx_bottom) * grTex->scaleX;
+			wallVerts[2].s = wallVerts[1].s = ((cliphigh * xscale) + gl_sidedef->offsetx_bottom) * grTex->scaleX;
 
 			// Adjust t value for sloped walls
 			if (!(gl_linedef->flags & ML_SKEWTD))
 			{
 				// Unskewed
-				wallVerts[0].t -= (worldbottom - gl_frontsector->floorheight) * grTex->scaleY;
-				wallVerts[1].t -= (worldbottomslope - gl_frontsector->floorheight) * grTex->scaleY;
-				wallVerts[3].t -= (worldlow - gl_backsector->floorheight) * grTex->scaleY;
-				wallVerts[2].t -= (worldlowslope - gl_backsector->floorheight) * grTex->scaleY;
+				wallVerts[0].t -= (worldbottom - gl_frontsector->floorheight) * yscale * grTex->scaleY;
+				wallVerts[1].t -= (worldbottomslope - gl_frontsector->floorheight) * yscale * grTex->scaleY;
+				wallVerts[3].t -= (worldlow - gl_backsector->floorheight) * yscale * grTex->scaleY;
+				wallVerts[2].t -= (worldlowslope - gl_backsector->floorheight) * yscale * grTex->scaleY;
 			}
 			else if (gl_linedef->flags & ML_DONTPEGBOTTOM)
 			{
 				// Skewed by bottom
-				wallVerts[0].t = wallVerts[1].t = (texturevpeg + worldlow - worldbottom) * grTex->scaleY;
-				wallVerts[2].t = wallVerts[1].t - (worldlowslope - worldbottomslope) * grTex->scaleY;
+				wallVerts[0].t = wallVerts[1].t = (texturevpeg + (worldlow - worldbottom) * yscale) * grTex->scaleY;
+				wallVerts[2].t = wallVerts[1].t - (worldlowslope - worldbottomslope) * yscale * grTex->scaleY;
 			}
 			else
 			{
 				// Skewed by top
-				wallVerts[0].t = (texturevpeg + worldlow - worldbottom) * grTex->scaleY;
-				wallVerts[1].t = (texturevpeg + worldlowslope - worldbottomslope) * grTex->scaleY;
+				wallVerts[0].t = (texturevpeg + (worldlow - worldbottom) * yscale) * grTex->scaleY;
+				wallVerts[1].t = (texturevpeg + (worldlowslope - worldbottomslope) * yscale) * grTex->scaleY;
 			}
 
 			// set top/bottom coords
@@ -1299,6 +1298,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 		if (gl_midtexture && HWR_BlendMidtextureSurface(&Surf))
 		{
 			sector_t *front, *back;
+			fixed_t texheight = FixedDiv(textureheight[gl_midtexture], gl_sidedef->scaley_mid);
 			INT32 repeats;
 
 			if (gl_linedef->frontsector->heightsec != -1)
@@ -1327,13 +1327,17 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				else
 					low = back->floorheight;
 
-				repeats = (high - low) / textureheight[gl_midtexture];
-				if ((high - low) % textureheight[gl_midtexture])
+				repeats = (high - low) / texheight;
+				if ((high - low) % texheight)
 					repeats++; // tile an extra time to fill the gap -- Monster Iestyn
 			}
 			else
 				repeats = 1;
 
+			grTex = HWR_GetTexture(gl_midtexture);
+			xscale = FixedToFloat(gl_sidedef->scalex_mid);
+			yscale = FixedToFloat(gl_sidedef->scaley_mid);
+
 			// SoM: a little note: popentop and popenbottom
 			// record the limits the texture can be displayed in.
 			// polytop and polybottom, are the ideal (i.e. unclipped)
@@ -1351,7 +1355,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				popenbottom = popenbottomslope = back->floorheight;
 			}
 			else
-            {
+			{
 				popentop = min(worldtop, worldhigh);
 				popenbottom = max(worldbottom, worldlow);
 				popentopslope = min(worldtopslope, worldhighslope);
@@ -1359,7 +1363,9 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			}
 
 			// Find the wall's coordinates
-			fixed_t midtexheight = textureheight[gl_midtexture] * repeats;
+			fixed_t midtexheight = texheight * repeats;
+
+			fixed_t rowoffset = FixedDiv(gl_sidedef->rowoffset + gl_sidedef->offsety_mid, gl_sidedef->scaley_mid);
 
 			// Texture is not skewed
 			if (gl_linedef->flags & ML_NOSKEW)
@@ -1367,13 +1373,13 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				// Peg it to the floor
 				if (gl_linedef->flags & ML_MIDPEG)
 				{
-					polybottom = max(front->floorheight, back->floorheight) + gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
+					polybottom = max(front->floorheight, back->floorheight) + rowoffset;
 					polytop = polybottom + midtexheight;
 				}
 				// Peg it to the ceiling
 				else
 				{
-					polytop = min(front->ceilingheight, back->ceilingheight) + gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
+					polytop = min(front->ceilingheight, back->ceilingheight) + rowoffset;
 					polybottom = polytop - midtexheight;
 				}
 
@@ -1384,17 +1390,17 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 			// Skew the texture, but peg it to the floor
 			else if (gl_linedef->flags & ML_MIDPEG)
 			{
-				polybottom = popenbottom + gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
+				polybottom = popenbottom + rowoffset;
 				polytop = polybottom + midtexheight;
-				polybottomslope = popenbottomslope + gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
+				polybottomslope = popenbottomslope + rowoffset;
 				polytopslope = polybottomslope + midtexheight;
 			}
 			// Skew it according to the ceiling's slope
 			else
 			{
-				polytop = popentop + gl_sidedef->rowoffset;
+				polytop = popentop + rowoffset;
 				polybottom = polytop - midtexheight;
-				polytopslope = popentopslope + gl_sidedef->rowoffset;
+				polytopslope = popentopslope + rowoffset;
 				polybottomslope = polytopslope - midtexheight;
 			}
 
@@ -1436,17 +1442,15 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				texturevpegslope = polytopslope - hS;
 			}
 
-			grTex = HWR_GetTexture(gl_midtexture);
-
 			// Left side
-			wallVerts[3].t = texturevpeg * grTex->scaleY;
-			wallVerts[0].t = (h - l + texturevpeg) * grTex->scaleY;
-			wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * grTex->scaleX;
+			wallVerts[3].t = texturevpeg * yscale * grTex->scaleY;
+			wallVerts[0].t = (h - l + texturevpeg) * yscale * grTex->scaleY;
+			wallVerts[0].s = wallVerts[3].s = ((cliplow * xscale) + gl_sidedef->offsetx_mid) * grTex->scaleX;
 
 			// Right side
-			wallVerts[2].t = texturevpegslope * grTex->scaleY;
-			wallVerts[1].t = (hS - lS + texturevpegslope) * grTex->scaleY;
-			wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * grTex->scaleX;
+			wallVerts[2].t = texturevpegslope * yscale * grTex->scaleY;
+			wallVerts[1].t = (hS - lS + texturevpegslope) * yscale * grTex->scaleY;
+			wallVerts[2].s = wallVerts[1].s = ((cliphigh * xscale) + gl_sidedef->offsetx_mid) * grTex->scaleX;
 
 			// set top/bottom coords
 			// Take the texture peg into account, rather than changing the offsets past
@@ -1504,36 +1508,40 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 		// Single sided line... Deal only with the middletexture (if one exists)
 		if (gl_midtexture && gl_linedef->special != HORIZONSPECIAL) // (Ignore horizon line for OGL)
 		{
+			grTex = HWR_GetTexture(gl_midtexture);
+			xscale = FixedToFloat(gl_sidedef->scalex_mid);
+			yscale = FixedToFloat(gl_sidedef->scaley_mid);
+
 			fixed_t texturevpeg;
 
 			// PEGGING
 			if ((gl_linedef->flags & (ML_DONTPEGBOTTOM|ML_NOSKEW)) == (ML_DONTPEGBOTTOM|ML_NOSKEW))
-				texturevpeg = gl_frontsector->floorheight + textureheight[gl_sidedef->midtexture] - gl_frontsector->ceilingheight + gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
+				texturevpeg = (gl_frontsector->floorheight + textureheight[gl_sidedef->midtexture] - gl_frontsector->ceilingheight) * yscale;
 			else if (gl_linedef->flags & ML_DONTPEGBOTTOM)
-				texturevpeg = worldbottom + textureheight[gl_sidedef->midtexture] - worldtop + gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
+				texturevpeg = (worldbottom + textureheight[gl_sidedef->midtexture] - worldtop) * yscale;
 			else
 				// top of texture at top
-				texturevpeg = gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
+				texturevpeg = 0;
 
-			grTex = HWR_GetTexture(gl_midtexture);
+			texturevpeg += gl_sidedef->rowoffset + gl_sidedef->offsety_mid;
 
 			wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY;
 			wallVerts[0].t = wallVerts[1].t = (texturevpeg + gl_frontsector->ceilingheight - gl_frontsector->floorheight) * grTex->scaleY;
-			wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * grTex->scaleX;
-			wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * grTex->scaleX;
+			wallVerts[0].s = wallVerts[3].s = ((cliplow * xscale) + gl_sidedef->offsetx_mid) * grTex->scaleX;
+			wallVerts[2].s = wallVerts[1].s = ((cliphigh * xscale) + gl_sidedef->offsetx_mid) * grTex->scaleX;
 
 			// Texture correction for slopes
 			if (gl_linedef->flags & ML_NOSKEW) {
-				wallVerts[3].t += (gl_frontsector->ceilingheight - worldtop) * grTex->scaleY;
-				wallVerts[2].t += (gl_frontsector->ceilingheight - worldtopslope) * grTex->scaleY;
-				wallVerts[0].t += (gl_frontsector->floorheight - worldbottom) * grTex->scaleY;
-				wallVerts[1].t += (gl_frontsector->floorheight - worldbottomslope) * grTex->scaleY;
+				wallVerts[3].t += (gl_frontsector->ceilingheight - worldtop) * yscale * grTex->scaleY;
+				wallVerts[2].t += (gl_frontsector->ceilingheight - worldtopslope) * yscale * grTex->scaleY;
+				wallVerts[0].t += (gl_frontsector->floorheight - worldbottom) * yscale * grTex->scaleY;
+				wallVerts[1].t += (gl_frontsector->floorheight - worldbottomslope) * yscale * yscale;
 			} else if (gl_linedef->flags & ML_DONTPEGBOTTOM) {
-				wallVerts[3].t = wallVerts[0].t + (worldbottom-worldtop) * grTex->scaleY;
-				wallVerts[2].t = wallVerts[1].t + (worldbottomslope-worldtopslope) * grTex->scaleY;
+				wallVerts[3].t = wallVerts[0].t + ((worldbottom - worldtop) * yscale) * grTex->scaleY;
+				wallVerts[2].t = wallVerts[1].t + ((worldbottomslope - worldtopslope) * yscale) * grTex->scaleY;
 			} else {
-				wallVerts[0].t = wallVerts[3].t - (worldbottom-worldtop) * grTex->scaleY;
-				wallVerts[1].t = wallVerts[2].t - (worldbottomslope-worldtopslope) * grTex->scaleY;
+				wallVerts[0].t = wallVerts[3].t - ((worldbottom - worldtop) * yscale) * grTex->scaleY;
+				wallVerts[1].t = wallVerts[2].t - ((worldbottomslope - worldtopslope) * yscale) * grTex->scaleY;
 			}
 
 			//Set textures properly on single sided walls that are sloped
@@ -1618,15 +1626,17 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				if ((high1 < lowcut && highslope1 < lowcutslope) || (low1 > highcut && lowslope1 > highcutslope))
 					continue;
 
-				texnum = R_GetTextureNum(sides[rover->master->sidenum[0]].midtexture);
+				side_t *side = &sides[rover->master->sidenum[0]];
 
 				if (rover->master->flags & ML_TFERLINE)
 				{
 					size_t linenum = gl_curline->linedef-gl_backsector->lines[0];
 					newline = rover->master->frontsector->lines[0] + linenum;
-					texnum = R_GetTextureNum(sides[newline->sidenum[0]].midtexture);
+					side = &sides[newline->sidenum[0]];
 				}
 
+				texnum = R_GetTextureNum(side->midtexture);
+
 				h  = P_GetFFloorTopZAt   (rover, v1x, v1y);
 				hS = P_GetFFloorTopZAt   (rover, v2x, v2y);
 				l  = P_GetFFloorBottomZAt(rover, v1x, v1y);
@@ -1642,14 +1652,13 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 					l = lowcut;
 					lS = lowcutslope;
 				}
-				//Hurdler: HW code starts here
-				//FIXME: check if peging is correct
-				// set top/bottom coords
 
+				// set top/bottom coords
 				wallVerts[3].y = FIXED_TO_FLOAT(h);
 				wallVerts[2].y = FIXED_TO_FLOAT(hS);
 				wallVerts[0].y = FIXED_TO_FLOAT(l);
 				wallVerts[1].y = FIXED_TO_FLOAT(lS);
+
 				if (rover->fofflags & FOF_FOG)
 				{
 					wallVerts[3].t = wallVerts[2].t = 0;
@@ -1659,56 +1668,46 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				}
 				else
 				{
-					fixed_t texturevpeg;
-					boolean attachtobottom = false;
-					boolean slopeskew = false; // skew FOF walls with slopes?
-
 					// Wow, how was this missing from OpenGL for so long?
 					// ...Oh well, anyway, Lower Unpegged now changes pegging of FOFs like in software
 					// -- Monster Iestyn 26/06/18
-					if (newline)
-					{
-						texturevpeg = sides[newline->sidenum[0]].rowoffset + sides[newline->sidenum[0]].offsety_mid;
-						attachtobottom = !!(newline->flags & ML_DONTPEGBOTTOM);
-						slopeskew = !!(newline->flags & ML_SKEWTD);
-					}
-					else
-					{
-						texturevpeg = sides[rover->master->sidenum[0]].rowoffset + sides[rover->master->sidenum[0]].offsety_mid;
-						attachtobottom = !!(gl_linedef->flags & ML_DONTPEGBOTTOM);
-						slopeskew = !!(rover->master->flags & ML_SKEWTD);
-					}
+					fixed_t texturevpeg = side->rowoffset + side->offsety_mid;
+					boolean attachtobottom = !!(rover->master->flags & ML_DONTPEGBOTTOM);
 
 					grTex = HWR_GetTexture(texnum);
+					xscale = FixedToFloat(side->scalex_mid);
+					yscale = FixedToFloat(side->scaley_mid);
 
-					if (!slopeskew) // no skewing
+					if (!(rover->master->flags & ML_SKEWTD)) // no skewing
 					{
 						if (attachtobottom)
-							texturevpeg -= *rover->topheight - *rover->bottomheight;
-						wallVerts[3].t = (*rover->topheight - h + texturevpeg) * grTex->scaleY;
-						wallVerts[2].t = (*rover->topheight - hS + texturevpeg) * grTex->scaleY;
-						wallVerts[0].t = (*rover->topheight - l + texturevpeg) * grTex->scaleY;
-						wallVerts[1].t = (*rover->topheight - lS + texturevpeg) * grTex->scaleY;
+							texturevpeg -= (*rover->topheight - *rover->bottomheight) * yscale;
+
+						wallVerts[3].t = (((*rover->topheight - h) * yscale) + texturevpeg) * grTex->scaleY;
+						wallVerts[2].t = (((*rover->topheight - hS) * yscale) + texturevpeg) * grTex->scaleY;
+						wallVerts[0].t = (((*rover->topheight - l) * yscale) + texturevpeg) * grTex->scaleY;
+						wallVerts[1].t = (((*rover->topheight - lS) * yscale) + texturevpeg) * grTex->scaleY;
 					}
 					else
 					{
 						if (!attachtobottom) // skew by top
 						{
 							wallVerts[3].t = wallVerts[2].t = texturevpeg * grTex->scaleY;
-							wallVerts[0].t = (h - l + texturevpeg) * grTex->scaleY;
-							wallVerts[1].t = (hS - lS + texturevpeg) * grTex->scaleY;
+							wallVerts[0].t = (((h - l) * yscale) + texturevpeg) * grTex->scaleY;
+							wallVerts[1].t = (((hS - lS) * yscale) + texturevpeg) * grTex->scaleY;
 						}
 						else // skew by bottom
 						{
 							wallVerts[0].t = wallVerts[1].t = texturevpeg * grTex->scaleY;
-							wallVerts[3].t = wallVerts[0].t - (h - l) * grTex->scaleY;
-							wallVerts[2].t = wallVerts[1].t - (hS - lS) * grTex->scaleY;
+							wallVerts[3].t = wallVerts[0].t - ((h - l) * yscale) * grTex->scaleY;
+							wallVerts[2].t = wallVerts[1].t - ((hS - lS) * yscale) * grTex->scaleY;
 						}
 					}
 
-					wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * grTex->scaleX;
-					wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * grTex->scaleX;
+					wallVerts[0].s = wallVerts[3].s = ((cliplow * xscale) + side->offsetx_mid) * grTex->scaleX;
+					wallVerts[2].s = wallVerts[1].s = ((cliphigh * xscale) + side->offsetx_mid) * grTex->scaleX;
 				}
+
 				if (rover->fofflags & FOF_FOG)
 				{
 					FBITFIELD blendmode;
@@ -1775,14 +1774,17 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				if ((high1 < lowcut && highslope1 < lowcutslope) || (low1 > highcut && lowslope1 > highcutslope))
 					continue;
 
-				texnum = R_GetTextureNum(sides[rover->master->sidenum[0]].midtexture);
+				side_t *side = &sides[rover->master->sidenum[0]];
 
 				if (rover->master->flags & ML_TFERLINE)
 				{
 					size_t linenum = gl_curline->linedef-gl_backsector->lines[0];
 					newline = rover->master->frontsector->lines[0] + linenum;
-					texnum = R_GetTextureNum(sides[newline->sidenum[0]].midtexture);
+					side = &sides[newline->sidenum[0]];
 				}
+
+				texnum = R_GetTextureNum(side->midtexture);
+
 				h  = P_GetFFloorTopZAt   (rover, v1x, v1y);
 				hS = P_GetFFloorTopZAt   (rover, v2x, v2y);
 				l  = P_GetFFloorBottomZAt(rover, v1x, v1y);
@@ -1816,20 +1818,16 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 				else
 				{
 					grTex = HWR_GetTexture(texnum);
+					xscale = FixedToFloat(side->scalex_mid);
+					yscale = FixedToFloat(side->scaley_mid);
 
-					if (newline)
-					{
-						wallVerts[3].t = wallVerts[2].t = (*rover->topheight - h + sides[newline->sidenum[0]].rowoffset + sides[newline->sidenum[0]].offsety_mid) * grTex->scaleY;
-						wallVerts[0].t = wallVerts[1].t = (h - l + (*rover->topheight - h + sides[newline->sidenum[0]].rowoffset) + sides[newline->sidenum[0]].offsety_mid) * grTex->scaleY;
-					}
-					else
-					{
-						wallVerts[3].t = wallVerts[2].t = (*rover->topheight - h + sides[rover->master->sidenum[0]].rowoffset + sides[rover->master->sidenum[0]].offsety_mid) * grTex->scaleY;
-						wallVerts[0].t = wallVerts[1].t = (h - l + (*rover->topheight - h + sides[rover->master->sidenum[0]].rowoffset + sides[rover->master->sidenum[0]].offsety_mid)) * grTex->scaleY;
-					}
+					fixed_t diff = (*rover->topheight - h) * yscale;
+
+					wallVerts[3].t = wallVerts[2].t = (diff + side->rowoffset + side->offsety_mid) * grTex->scaleY;
+					wallVerts[0].t = wallVerts[1].t = (((h - l) * yscale) + (diff + side->rowoffset + side->offsety_mid)) * grTex->scaleY;
 
-					wallVerts[0].s = wallVerts[3].s = (cliplow + gl_sidedef->offsetx_mid) * grTex->scaleX;
-					wallVerts[2].s = wallVerts[1].s = (cliphigh + gl_sidedef->offsetx_mid) * grTex->scaleX;
+					wallVerts[0].s = wallVerts[3].s = ((cliplow * xscale) + side->offsetx_mid) * grTex->scaleX;
+					wallVerts[2].s = wallVerts[1].s = ((cliphigh * xscale) + side->offsetx_mid) * grTex->scaleX;
 				}
 
 				if (rover->fofflags & FOF_FOG)
@@ -2715,11 +2713,8 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 	INT32 i;
 
 	float height = FIXED_TO_FLOAT(fixedheight); // constant y for all points on the convex flat polygon
-	float flatxref, flatyref;
 	float fflatwidth = 64.0f, fflatheight = 64.0f;
-	UINT16 flatflag = 63;
-
-	boolean texflat = false;
+	float xscale = 1.0f, yscale = 1.0f;
 
 	float scrollx = 0.0f, scrolly = 0.0f;
 	float tempxsow, tempytow, anglef = 0.0f;
@@ -2750,8 +2745,8 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 		if (levelflat->type == LEVELFLAT_FLAT)
 		{
 			size_t len = W_LumpLength(levelflat->u.flat.lumpnum);
-			flatflag = R_GetFlatSize(len) - 1;
-			fflatwidth = fflatheight = (float)(flatflag + 1);
+			unsigned flatflag = R_GetFlatSize(len);
+			fflatwidth = fflatheight = (float)flatflag;
 		}
 		else
 		{
@@ -2765,19 +2760,11 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 				fflatwidth = levelflat->width;
 				fflatheight = levelflat->height;
 			}
-			texflat = true;
 		}
 	}
 	else // set no texture
 		HWR_SetCurrentTexture(NULL);
 
-	// reference point for flat texture coord for each vertex around the polygon
-	flatxref = FIXED_TO_FLOAT(polysector->origVerts[0].x);
-	flatyref = FIXED_TO_FLOAT(polysector->origVerts[0].y);
-
-	flatxref = (float)(((fixed_t)flatxref & (~flatflag)) / fflatwidth);
-	flatyref = (float)(((fixed_t)flatyref & (~flatflag)) / fflatheight);
-
 	// transform
 	v3d = planeVerts;
 
@@ -2785,14 +2772,18 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 	{
 		if (!isceiling) // it's a floor
 		{
-			scrollx = FIXED_TO_FLOAT(FOFsector->floorxoffset)/fflatwidth;
-			scrolly = FIXED_TO_FLOAT(FOFsector->flooryoffset)/fflatheight;
+			xscale = FixedToFloat(FOFsector->floorxscale);
+			yscale = FixedToFloat(FOFsector->flooryscale);
+			scrollx = FixedToFloat(FOFsector->floorxoffset) / fflatwidth;
+			scrolly = FixedToFloat(FOFsector->flooryoffset) / fflatheight;
 			angle = FOFsector->floorangle;
 		}
 		else // it's a ceiling
 		{
-			scrollx = FIXED_TO_FLOAT(FOFsector->ceilingxoffset)/fflatwidth;
-			scrolly = FIXED_TO_FLOAT(FOFsector->ceilingyoffset)/fflatheight;
+			xscale = FixedToFloat(FOFsector->ceilingxscale);
+			yscale = FixedToFloat(FOFsector->ceilingyscale);
+			scrollx = FixedToFloat(FOFsector->ceilingxoffset) / fflatwidth;
+			scrolly = FixedToFloat(FOFsector->ceilingyoffset) / fflatheight;
 			angle = FOFsector->ceilingangle;
 		}
 	}
@@ -2800,43 +2791,30 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 	{
 		if (!isceiling) // it's a floor
 		{
-			scrollx = FIXED_TO_FLOAT(gl_frontsector->floorxoffset)/fflatwidth;
-			scrolly = FIXED_TO_FLOAT(gl_frontsector->flooryoffset)/fflatheight;
+			xscale = FixedToFloat(gl_frontsector->floorxscale);
+			yscale = FixedToFloat(gl_frontsector->flooryscale);
+			scrollx = FixedToFloat(gl_frontsector->floorxoffset) / fflatwidth;
+			scrolly = FixedToFloat(gl_frontsector->flooryoffset) / fflatheight;
 			angle = gl_frontsector->floorangle;
 		}
 		else // it's a ceiling
 		{
-			scrollx = FIXED_TO_FLOAT(gl_frontsector->ceilingxoffset)/fflatwidth;
-			scrolly = FIXED_TO_FLOAT(gl_frontsector->ceilingyoffset)/fflatheight;
+			xscale = FixedToFloat(gl_frontsector->ceilingxscale);
+			yscale = FixedToFloat(gl_frontsector->ceilingyscale);
+			scrollx = FixedToFloat(gl_frontsector->ceilingxoffset) / fflatwidth;
+			scrolly = FixedToFloat(gl_frontsector->ceilingyoffset) / fflatheight;
 			angle = gl_frontsector->ceilingangle;
 		}
 	}
 
-	if (angle) // Only needs to be done if there's an altered angle
-	{
-		tempxsow = flatxref;
-		tempytow = flatyref;
-
-		anglef = ANG2RAD(InvAngle(angle));
-
-		flatxref = (tempxsow * cos(anglef)) - (tempytow * sin(anglef));
-		flatyref = (tempxsow * sin(anglef)) + (tempytow * cos(anglef));
-	}
+	anglef = ANG2RAD(InvAngle(angle));
 
 	for (i = 0; i < (INT32)nrPlaneVerts; i++,v3d++)
 	{
 		// Go from the polysector's original vertex locations
 		// Means the flat is offset based on the original vertex locations
-		if (texflat)
-		{
-			v3d->s = (float)(FIXED_TO_FLOAT(polysector->origVerts[i].x) / fflatwidth) + scrollx;
-			v3d->t = -(float)(FIXED_TO_FLOAT(polysector->origVerts[i].y) / fflatheight) + scrolly;
-		}
-		else
-		{
-			v3d->s = (float)((FIXED_TO_FLOAT(polysector->origVerts[i].x) / fflatwidth) - flatxref + scrollx);
-			v3d->t = (float)(flatyref - (FIXED_TO_FLOAT(polysector->origVerts[i].y) / fflatheight) + scrolly);
-		}
+		v3d->s = (FixedToFloat(polysector->origVerts[i].x) / fflatwidth) + (scrollx / xscale);
+		v3d->t = -(FixedToFloat(polysector->origVerts[i].y) / fflatheight) + (scrolly / yscale);
 
 		// Need to rotate before translate
 		if (angle) // Only needs to be done if there's an altered angle
@@ -2848,6 +2826,9 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 			v3d->t = (tempxsow * sin(anglef)) + (tempytow * cos(anglef));
 		}
 
+		v3d->s *= xscale;
+		v3d->t *= yscale;
+
 		v3d->x = FIXED_TO_FLOAT(polysector->vertices[i]->x);
 		v3d->y = height;
 		v3d->z = FIXED_TO_FLOAT(polysector->vertices[i]->y);
@@ -2946,6 +2927,46 @@ static FBITFIELD HWR_RippleBlend(sector_t *sector, ffloor_t *rover, boolean ceil
 	return /*R_IsRipplePlane(sector, rover, ceiling)*/ (rover->fofflags & FOF_RIPPLE) ? PF_Ripple : 0;
 }
 
+//
+// HWR_DoCulling
+// Hardware version of R_DoCulling
+// (see r_main.c)
+static boolean HWR_DoCulling(line_t *cullheight, line_t *viewcullheight, float vz, float bottomh, float toph)
+{
+	float cullplane;
+
+	if (!cullheight)
+		return false;
+
+	cullplane = FIXED_TO_FLOAT(cullheight->frontsector->floorheight);
+	if (cullheight->args[1]) // Group culling
+	{
+		if (!viewcullheight)
+			return false;
+
+		// Make sure this is part of the same group
+		if (viewcullheight->frontsector == cullheight->frontsector)
+		{
+			// OK, we can cull
+			if (vz > cullplane && toph < cullplane) // Cull if below plane
+				return true;
+
+			if (bottomh > cullplane && vz <= cullplane) // Cull if above plane
+				return true;
+		}
+	}
+	else // Quick culling
+	{
+		if (vz > cullplane && toph < cullplane) // Cull if below plane
+			return true;
+
+		if (bottomh > cullplane && vz <= cullplane) // Cull if above plane
+			return true;
+	}
+
+	return false;
+}
+
 // -----------------+
 // HWR_Subsector    : Determine floor/ceiling planes.
 //                  : Add sprites of things in sector.
@@ -3120,27 +3141,36 @@ static void HWR_Subsector(size_t num)
 		for (rover = gl_frontsector->ffloors;
 			rover; rover = rover->next)
 		{
-			fixed_t cullHeight, centerHeight;
-
-            // bottom plane
-			cullHeight   = P_GetFFloorBottomZAt(rover, viewx, viewy);
-			centerHeight = P_GetFFloorBottomZAt(rover, gl_frontsector->soundorg.x, gl_frontsector->soundorg.y);
+			fixed_t bottomCullHeight, topCullHeight, centerHeight;
 
 			if (!(rover->fofflags & FOF_EXISTS) || !(rover->fofflags & FOF_RENDERPLANES))
 				continue;
 			if (sub->validcount == validcount)
 				continue;
+			
+			// rendering heights for bottom and top planes
+			bottomCullHeight = P_GetFFloorBottomZAt(rover, viewx, viewy);
+			topCullHeight = P_GetFFloorTopZAt(rover, viewx, viewy);
+			
+			if (gl_frontsector->cullheight)
+			{
+				if (HWR_DoCulling(gl_frontsector->cullheight, viewsector->cullheight, gl_viewz, FIXED_TO_FLOAT(bottomCullHeight), FIXED_TO_FLOAT(topCullHeight)))
+					continue;
+			}
+
+			// bottom plane
+			centerHeight = P_GetFFloorBottomZAt(rover, gl_frontsector->soundorg.x, gl_frontsector->soundorg.y);
 
 			if (centerHeight <= locCeilingHeight &&
 			    centerHeight >= locFloorHeight &&
-			    ((dup_viewz < cullHeight && (rover->fofflags & FOF_BOTHPLANES || !(rover->fofflags & FOF_INVERTPLANES))) ||
-			     (dup_viewz > cullHeight && (rover->fofflags & FOF_BOTHPLANES || rover->fofflags & FOF_INVERTPLANES))))
+			    ((dup_viewz < bottomCullHeight && (rover->fofflags & FOF_BOTHPLANES || !(rover->fofflags & FOF_INVERTPLANES))) ||
+			     (dup_viewz > bottomCullHeight && (rover->fofflags & FOF_BOTHPLANES || rover->fofflags & FOF_INVERTPLANES))))
 			{
 				if (rover->fofflags & FOF_FOG)
 				{
 					UINT8 alpha;
 
-					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
+					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < bottomCullHeight ? true : false);
 					alpha = HWR_FogBlockAlpha(*gl_frontsector->lightlist[light].lightlevel, rover->master->frontsector->extra_colormap);
 
 					HWR_AddTransparentFloor(0,
@@ -3153,7 +3183,7 @@ static void HWR_Subsector(size_t num)
 				}
 				else if ((rover->fofflags & FOF_TRANSLUCENT && !(rover->fofflags & FOF_SPLAT)) || rover->blend) // SoM: Flags are more efficient
 				{
-					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
+					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < bottomCullHeight ? true : false);
 
 					HWR_AddTransparentFloor(&levelflats[*rover->bottompic],
 					                       &extrasubsectors[num],
@@ -3167,26 +3197,25 @@ static void HWR_Subsector(size_t num)
 				else
 				{
 					HWR_GetLevelFlat(&levelflats[*rover->bottompic]);
-					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
+					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < bottomCullHeight ? true : false);
 					HWR_RenderPlane(sub, &extrasubsectors[num], false, *rover->bottomheight, HWR_RippleBlend(gl_frontsector, rover, false)|PF_Occlude, *gl_frontsector->lightlist[light].lightlevel, &levelflats[*rover->bottompic],
 					                rover->master->frontsector, 255, *gl_frontsector->lightlist[light].extra_colormap);
 				}
 			}
 
 			// top plane
-			cullHeight   = P_GetFFloorTopZAt(rover, viewx, viewy);
 			centerHeight = P_GetFFloorTopZAt(rover, gl_frontsector->soundorg.x, gl_frontsector->soundorg.y);
 
 			if (centerHeight >= locFloorHeight &&
 			    centerHeight <= locCeilingHeight &&
-			    ((dup_viewz > cullHeight && (rover->fofflags & FOF_BOTHPLANES || !(rover->fofflags & FOF_INVERTPLANES))) ||
-			     (dup_viewz < cullHeight && (rover->fofflags & FOF_BOTHPLANES || rover->fofflags & FOF_INVERTPLANES))))
+			    ((dup_viewz > topCullHeight && (rover->fofflags & FOF_BOTHPLANES || !(rover->fofflags & FOF_INVERTPLANES))) ||
+			     (dup_viewz < topCullHeight && (rover->fofflags & FOF_BOTHPLANES || rover->fofflags & FOF_INVERTPLANES))))
 			{
 				if (rover->fofflags & FOF_FOG)
 				{
 					UINT8 alpha;
 
-					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
+					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < topCullHeight ? true : false);
 					alpha = HWR_FogBlockAlpha(*gl_frontsector->lightlist[light].lightlevel, rover->master->frontsector->extra_colormap);
 
 					HWR_AddTransparentFloor(0,
@@ -3199,7 +3228,7 @@ static void HWR_Subsector(size_t num)
 				}
 				else if ((rover->fofflags & FOF_TRANSLUCENT && !(rover->fofflags & FOF_SPLAT)) || rover->blend)
 				{
-					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
+					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < topCullHeight ? true : false);
 
 					HWR_AddTransparentFloor(&levelflats[*rover->toppic],
 					                        &extrasubsectors[num],
@@ -3213,7 +3242,7 @@ static void HWR_Subsector(size_t num)
 				else
 				{
 					HWR_GetLevelFlat(&levelflats[*rover->toppic]);
-					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
+					light = R_GetPlaneLight(gl_frontsector, centerHeight, dup_viewz < topCullHeight ? true : false);
 					HWR_RenderPlane(sub, &extrasubsectors[num], true, *rover->topheight, HWR_RippleBlend(gl_frontsector, rover, false)|PF_Occlude, *gl_frontsector->lightlist[light].lightlevel, &levelflats[*rover->toppic],
 					                  rover->master->frontsector, 255, *gl_frontsector->lightlist[light].extra_colormap);
 				}
@@ -3567,46 +3596,6 @@ static void HWR_LinkDrawHackFinish(void)
 	linkdrawcount = 0;
 }
 
-//
-// HWR_DoCulling
-// Hardware version of R_DoCulling
-// (see r_main.c)
-static boolean HWR_DoCulling(line_t *cullheight, line_t *viewcullheight, float vz, float bottomh, float toph)
-{
-	float cullplane;
-
-	if (!cullheight)
-		return false;
-
-	cullplane = FIXED_TO_FLOAT(cullheight->frontsector->floorheight);
-	if (cullheight->args[1]) // Group culling
-	{
-		if (!viewcullheight)
-			return false;
-
-		// Make sure this is part of the same group
-		if (viewcullheight->frontsector == cullheight->frontsector)
-		{
-			// OK, we can cull
-			if (vz > cullplane && toph < cullplane) // Cull if below plane
-				return true;
-
-			if (bottomh > cullplane && vz <= cullplane) // Cull if above plane
-				return true;
-		}
-	}
-	else // Quick culling
-	{
-		if (vz > cullplane && toph < cullplane) // Cull if below plane
-			return true;
-
-		if (bottomh > cullplane && vz <= cullplane) // Cull if above plane
-			return true;
-	}
-
-	return false;
-}
-
 static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale)
 {
 	patch_t *gpatch;
@@ -5180,6 +5169,8 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	spriteyscale = FIXED_TO_FLOAT(interp.spriteyscale);
 
 	// transform the origin point
+	if (thing->type == MT_OVERLAY) // Handle overlays
+		R_ThingOffsetOverlay(thing, &interp.x, &interp.y);
 	tr_x = FIXED_TO_FLOAT(interp.x) - gl_viewx;
 	tr_y = FIXED_TO_FLOAT(interp.y) - gl_viewy;
 
@@ -5496,6 +5487,8 @@ static void HWR_ProjectSprite(mobj_t *thing)
 
 		// calculate tz for tracer, same way it is calculated for this sprite
 		// transform the origin point
+		if (thing->tracer->type == MT_OVERLAY) // Handle overlays
+			R_ThingOffsetOverlay(thing->tracer, &tracer_interp.x, &tracer_interp.y);
 		tr_x = FIXED_TO_FLOAT(tracer_interp.x) - gl_viewx;
 		tr_y = FIXED_TO_FLOAT(tracer_interp.y) - gl_viewy;
 
@@ -5939,6 +5932,9 @@ void HWR_BuildSkyDome(void)
 
 static void HWR_DrawSkyBackground(player_t *player)
 {
+	if (HWR_IsWireframeMode())
+		return;
+
 	HWD.pfnSetBlend(PF_Translucent|PF_NoDepthTest|PF_Modulated);
 
 	if (cv_glskydome.value)
@@ -6295,6 +6291,9 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 	// Reset the shader state.
 	HWR_SetShaderState();
 
+	if (HWR_IsWireframeMode())
+		HWD.pfnSetSpecialState(HWD_SET_WIREFRAME, 1);
+
 	validcount++;
 
 	if (cv_glbatching.value)
@@ -6357,6 +6356,9 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 		HWR_CreateDrawNodes();
 	}
 
+	if (HWR_IsWireframeMode())
+		HWD.pfnSetSpecialState(HWD_SET_WIREFRAME, 0);
+
 	HWD.pfnSetTransform(NULL);
 	HWD.pfnUnSetShader();
 
@@ -6511,6 +6513,9 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	// Reset the shader state.
 	HWR_SetShaderState();
 
+	if (HWR_IsWireframeMode())
+		HWD.pfnSetSpecialState(HWD_SET_WIREFRAME, 1);
+
 	ps_numbspcalls.value.i = 0;
 	ps_numpolyobjects.value.i = 0;
 	PS_START_TIMING(ps_bsptime);
@@ -6587,6 +6592,9 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 		HWR_CreateDrawNodes();
 	}
 
+	if (HWR_IsWireframeMode())
+		HWD.pfnSetSpecialState(HWD_SET_WIREFRAME, 0);
+
 	HWD.pfnSetTransform(NULL);
 	HWD.pfnUnSetShader();
 
@@ -6664,6 +6672,8 @@ consvar_t cv_glsolvetjoin = CVAR_INIT ("gr_solvetjoin", "On", 0, CV_OnOff, NULL)
 
 consvar_t cv_glbatching = CVAR_INIT ("gr_batching", "On", 0, CV_OnOff, NULL);
 
+consvar_t cv_glwireframe = CVAR_INIT ("gr_wireframe", "Off", 0, CV_OnOff, NULL);
+
 static void CV_glfiltermode_OnChange(void)
 {
 	if (rendermode == render_opengl)
@@ -6705,6 +6715,8 @@ void HWR_AddCommands(void)
 
 	CV_RegisterVar(&cv_glbatching);
 
+	CV_RegisterVar(&cv_glwireframe);
+
 #ifndef NEWCLIP
 	CV_RegisterVar(&cv_glclipwalls);
 #endif
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index cce4e8f0ac4bb48e04c5443656b8dfe1ffa35b2c..6348592af688a1e40ad0ec9dc2ff9a846508b561 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -107,6 +107,8 @@ extern consvar_t cv_glslopecontrast;
 
 extern consvar_t cv_glbatching;
 
+extern consvar_t cv_glwireframe;
+
 extern float gl_viewwidth, gl_viewheight, gl_baseviewwindowy;
 
 extern float gl_viewwindowx, gl_basewindowcentery;
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 143e5dc469eaa7bd105ebc010a7c236e117d53cb..18648a4999a1263b012f4d71120fbb0687f03d53 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -1487,6 +1487,8 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 #undef INTERPOLERATION_LIMIT
 #endif
 
+		if (spr->mobj->type == MT_OVERLAY) // Handle overlays
+			R_ThingOffsetOverlay(spr->mobj, &interp.x, &interp.y);
 		//Hurdler: it seems there is still a small problem with mobj angle
 		p.x = FIXED_TO_FLOAT(interp.x);
 		p.y = FIXED_TO_FLOAT(interp.y)+md2->offset;
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 6c0c90fc5b06136f6ad06918e3bd027b4f57779a..ea831e41dee3b7afff7eed47d4ead3f858870648 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -302,6 +302,8 @@ typedef void (APIENTRY * PFNglDisable) (GLenum cap);
 static PFNglDisable pglDisable;
 typedef void (APIENTRY * PFNglGetFloatv) (GLenum pname, GLfloat *params);
 static PFNglGetFloatv pglGetFloatv;
+typedef void (APIENTRY * PFNglPolygonMode) (GLenum, GLenum);
+static PFNglPolygonMode pglPolygonMode;
 
 /* Depth Buffer */
 typedef void (APIENTRY * PFNglClearDepth) (GLclampd depth);
@@ -476,6 +478,7 @@ boolean SetupGLfunc(void)
 	GETOPENGLFUNC(pglGetFloatv, glGetFloatv)
 	GETOPENGLFUNC(pglGetIntegerv, glGetIntegerv)
 	GETOPENGLFUNC(pglGetString, glGetString)
+	GETOPENGLFUNC(pglPolygonMode, glPolygonMode)
 
 	GETOPENGLFUNC(pglClearDepth, glClearDepth)
 	GETOPENGLFUNC(pglDepthFunc, glDepthFunc)
@@ -2474,6 +2477,10 @@ EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value)
 				Flush(); //??? if we want to change filter mode by texture, remove this
 			break;
 
+		case HWD_SET_WIREFRAME:
+			pglPolygonMode(GL_FRONT_AND_BACK, Value ? GL_LINE : GL_FILL);
+			break;
+
 		default:
 			break;
 	}
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 6d8d900fb9c36e8013865c15e361871f2687c525..4200e85edb5be16c4ae61eec74ac64a28ad407ea 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -79,6 +79,7 @@ patch_t *nto_font[NT_FONTSIZE];
 
 static player_t *plr;
 boolean chat_on; // entering a chat message?
+boolean chat_on_first_event; // blocker for first chat input event
 static char w_chat[HU_MAXMSGLEN + 1];
 static size_t c_input = 0; // let's try to make the chat input less shitty.
 static boolean headsupactive = false;
@@ -618,7 +619,9 @@ static void Command_CSay_f(void)
 
 	DoSayCommand(0, 1, HU_CSAY);
 }
-static tic_t stop_spamming[MAXPLAYERS];
+
+static tic_t spam_tokens[MAXPLAYERS];
+static tic_t spam_tics[MAXPLAYERS];
 
 /** Receives a message, processing an ::XD_SAY command.
   * \sa DoSayCommand
@@ -670,14 +673,14 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 	// before we do anything, let's verify the guy isn't spamming, get this easier on us.
 
 	//if (stop_spamming[playernum] != 0 && cv_chatspamprotection.value && !(flags & HU_CSAY))
-	if (stop_spamming[playernum] != 0 && consoleplayer != playernum && cv_chatspamprotection.value && !(flags & HU_CSAY))
+	if (spam_tokens[playernum] <= 0 && cv_chatspamprotection.value && !(flags & HU_CSAY))
 	{
 		CONS_Debug(DBG_NETPLAY,"Received SAY cmd too quickly from Player %d (%s), assuming as spam and blocking message.\n", playernum+1, player_names[playernum]);
-		stop_spamming[playernum] = 4;
+		spam_tics[playernum] = 0;
 		spam_eatmsg = 1;
 	}
 	else
-		stop_spamming[playernum] = 4; // you can hold off for 4 tics, can you?
+		spam_tokens[playernum] -= 1;
 
 	// run the lua hook even if we were supposed to eat the msg, netgame consistency goes first.
 
@@ -857,6 +860,25 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 //
 void HU_Ticker(void)
 {
+	// do this server-side, too
+	if (netgame)
+	{
+		size_t i = 0;
+
+		// handle spam while we're at it:
+		for(; (i<MAXPLAYERS); i++)
+		{
+			if (spam_tokens[i] < (tic_t)cv_chatspamburst.value)
+			{
+				if (++spam_tics[i] >= (tic_t)cv_chatspamspeed.value)
+				{
+					spam_tokens[i]++;
+					spam_tics[i] = 0;
+				}
+			}
+		}
+	}
+
 	if (dedicated)
 		return;
 
@@ -879,13 +901,6 @@ void HU_Ticker(void)
 	{
 		size_t i = 0;
 
-		// handle spam while we're at it:
-		for(; (i<MAXPLAYERS); i++)
-		{
-			if (stop_spamming[i] > 0)
-				stop_spamming[i]--;
-		}
-
 		// handle chat timers
 		for (i=0; (i<chat_nummsg_min); i++)
 		{
@@ -1025,7 +1040,7 @@ boolean HU_Responder(event_t *ev)
 {
 	INT32 c=0;
 
-	if (ev->type != ev_keydown)
+	if (ev->type != ev_keydown && ev->type != ev_text)
 		return false;
 
 	// only KeyDown events now...
@@ -1054,11 +1069,15 @@ boolean HU_Responder(event_t *ev)
 
 	if (!chat_on)
 	{
+		if (ev->type == ev_text)
+			return false;
+
 		// enter chat mode
 		if ((ev->key == gamecontrol[GC_TALKKEY][0] || ev->key == gamecontrol[GC_TALKKEY][1])
 			&& netgame && !OLD_MUTE) // check for old chat mute, still let the players open the chat incase they want to scroll otherwise.
 		{
 			chat_on = true;
+			chat_on_first_event = false;
 			w_chat[0] = 0;
 			teamtalk = false;
 			chat_scrollmedown = true;
@@ -1069,6 +1088,7 @@ boolean HU_Responder(event_t *ev)
 			&& netgame && !OLD_MUTE)
 		{
 			chat_on = true;
+			chat_on_first_event = false;
 			w_chat[0] = 0;
 			teamtalk = G_GametypeHasTeams(); // Don't teamtalk if we don't have teams.
 			chat_scrollmedown = true;
@@ -1078,6 +1098,31 @@ boolean HU_Responder(event_t *ev)
 	}
 	else // if chat_on
 	{
+		if (!chat_on_first_event)
+		{
+			// since the text event is sent immediately after the keydown event,
+			// we need to make sure that nothing is displayed once the chat
+			// opens, otherwise a 't' would be outputted.
+			chat_on_first_event = true;
+			return true;
+		}
+
+		if (ev->type == ev_text)
+		{
+			if ((c < HU_FONTSTART || c > HU_FONTEND || !hu_font[c-HU_FONTSTART])
+				&& c != ' ') // Allow spaces, of course
+			{
+				return false;
+			}
+
+			if (CHAT_MUTE || strlen(w_chat) >= HU_MAXMSGLEN)
+				return true;
+
+			memmove(&w_chat[c_input + 1], &w_chat[c_input], strlen(w_chat) - c_input + 1);
+			w_chat[c_input] = c;
+			c_input++;
+			return true;
+		}
 
 		// Ignore modifier keys
 		// Note that we do this here so users can still set
@@ -1087,23 +1132,8 @@ boolean HU_Responder(event_t *ev)
 		 || ev->key == KEY_LALT || ev->key == KEY_RALT)
 			return true;
 
-		c = (INT32)ev->key;
-
-		// I know this looks very messy but this works. If it ain't broke, don't fix it!
-		// shift LETTERS to uppercase if we have capslock or are holding shift
-		if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
-		{
-			if (shiftdown ^ capslock)
-				c = shiftxform[c];
-		}
-		else // if we're holding shift we should still shift non letter symbols
-		{
-			if (shiftdown)
-				c = shiftxform[c];
-		}
-
 		// pasting. pasting is cool. chat is a bit limited, though :(
-		if ((c == 'v' || c == 'V') && ctrldown)
+		if (c == 'v' && ctrldown)
 		{
 			const char *paste;
 			size_t chatlen;
@@ -1171,16 +1201,6 @@ boolean HU_Responder(event_t *ev)
 			else
 				c_input++;
 		}
-		else if ((c >= HU_FONTSTART && c <= HU_FONTEND && hu_font[c-HU_FONTSTART])
-			|| c == ' ') // Allow spaces, of course
-		{
-			if (CHAT_MUTE || strlen(w_chat) >= HU_MAXMSGLEN)
-				return true;
-
-			memmove(&w_chat[c_input + 1], &w_chat[c_input], strlen(w_chat) - c_input + 1);
-			w_chat[c_input] = c;
-			c_input++;
-		}
 		else if (c == KEY_BACKSPACE)
 		{
 			if (CHAT_MUTE || c_input <= 0)
diff --git a/src/i_time.c b/src/i_time.c
index fae26abede3f646252abd7c04ff826bbf89ae2ba..39854b242db15dae5b89c634a938783e35691df2 100644
--- a/src/i_time.c
+++ b/src/i_time.c
@@ -30,12 +30,6 @@ static precise_t enterprecise, oldenterprecise;
 static fixed_t entertic, oldentertics;
 static double tictimer;
 
-// A little more than the minimum sleep duration on Windows.
-// May be incorrect for other platforms, but we don't currently have a way to
-// query the scheduler granularity. SDL will do what's needed to make this as
-// low as possible though.
-#define MIN_SLEEP_DURATION_MS 2.1
-
 tic_t I_GetTime(void)
 {
 	return g_time.time;
@@ -88,38 +82,3 @@ void I_UpdateTime(fixed_t timescale)
 		g_time.timefrac = FLOAT_TO_FIXED(fractional);
 	}
 }
-
-void I_SleepDuration(precise_t duration)
-{
-	UINT64 precision = I_GetPrecisePrecision();
-	INT32 sleepvalue = cv_sleep.value;
-	UINT64 delaygranularity;
-	precise_t cur;
-	precise_t dest;
-
-	{
-		double gran = round(((double)(precision / 1000) * sleepvalue * MIN_SLEEP_DURATION_MS));
-		delaygranularity = (UINT64)gran;
-	}
-
-	cur = I_GetPreciseTime();
-	dest = cur + duration;
-
-	// the reason this is not dest > cur is because the precise counter may wrap
-	// two's complement arithmetic is our friend here, though!
-	// e.g. cur 0xFFFFFFFFFFFFFFFE = -2, dest 0x0000000000000001 = 1
-	// 0x0000000000000001 - 0xFFFFFFFFFFFFFFFE = 3
-	while ((INT64)(dest - cur) > 0)
-	{
-		// If our cv_sleep value exceeds the remaining sleep duration, use the
-		// hard sleep function.
-		if (sleepvalue > 0 && (dest - cur) > delaygranularity)
-		{
-			I_Sleep(sleepvalue);
-		}
-
-		// Otherwise, this is a spinloop.
-
-		cur = I_GetPreciseTime();
-	}
-}
diff --git a/src/info.c b/src/info.c
index 5790dd7c56ce3ee5f625f63581903a6d185a3a73..d1707c86a597902fdbbba249387cfa591a11c661 100644
--- a/src/info.c
+++ b/src/info.c
@@ -33,7 +33,8 @@ char sprnames[NUMSPRITES + 1][5] =
 	"NULL", // invisible object
 	"UNKN",
 
-	"THOK", // Thok! mobj
+	"THOK", // Spin trail mobj
+	"THKE", // Thok boom effect
 	"PLAY",
 
 	// Enemies
@@ -703,8 +704,9 @@ state_t states[NUMSTATES] =
 	{SPR_UNKN, FF_FULLBRIGHT, -1, {A_InfoState}, 5, 0, S_NULL}, // S_XDEATHSTATE
 	{SPR_UNKN, FF_FULLBRIGHT, -1, {A_InfoState}, 6, 0, S_NULL}, // S_RAISESTATE
 
-	// Thok
+	// Spin trail and thok boom effect
 	{SPR_THOK, FF_TRANS50, 8, {NULL}, 0, 0, S_NULL}, // S_THOK
+	{SPR_THKE, FF_TRANS50|FF_PAPERSPRITE, 8, {NULL}, 0, 0, S_NULL}, // S_THOKEFFECT
 
 	// Player
 	{SPR_PLAY, SPR2_STND|FF_ANIMATE,    105, {NULL}, 0,  7, S_PLAY_WAIT}, // S_PLAY_STND
@@ -4078,6 +4080,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
+	
+	{           // MT_THOKEFFECT
+		-1,             // doomednum
+		S_THOKEFFECT,   // 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
+		32*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
+		0,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
 
 	{           // MT_PLAYER
 		-1,             // doomednum
diff --git a/src/info.h b/src/info.h
index a2d87dbdc10644edaaf34187d45495d70ba49f85..32aff18fbd27b94eb60505cc9dd0028304f8241b 100644
--- a/src/info.h
+++ b/src/info.h
@@ -580,7 +580,8 @@ typedef enum sprite
 	SPR_NULL, // invisible object
 	SPR_UNKN,
 
-	SPR_THOK, // Thok! mobj
+	SPR_THOK, // Spin trail mobj
+	SPR_THKE, // Thok boom effect
 	SPR_PLAY,
 
 	// Enemies
@@ -1182,8 +1183,9 @@ typedef enum state
 	S_XDEATHSTATE,
 	S_RAISESTATE,
 
-	// Thok
+	// Thok boom effect and spin trail
 	S_THOK,
+	S_THOKEFFECT,
 
 	// Player
 	S_PLAY_STND,
@@ -4392,7 +4394,8 @@ typedef enum mobj_type
 	MT_NULL,
 	MT_UNKNOWN,
 
-	MT_THOK, // Thok! mobj
+	MT_THOK, // Spin trail mobj
+	MT_THOKEFFECT, // Thok boom effect
 	MT_PLAYER,
 	MT_TAILSOVERLAY, // c:
 	MT_METALJETFUME,
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 1927697eeb7a746bef9e45524270c01c3392703b..85f4f63c59302214d8c9bc13952ac50d3ea4b39b 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -1346,6 +1346,17 @@ static int lib_pSetObjectMomZ(lua_State *L)
 	return 0;
 }
 
+static int lib_pIsLocalPlayer(lua_State *L)
+{
+	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+	//NOHUD
+	//INLEVEL
+	if (!player)
+		return LUA_ErrInvalid(L, "player_t");
+	lua_pushboolean(L, P_IsLocalPlayer(player));
+	return 1;
+}
+
 static int lib_pPlayJingle(lua_State *L)
 {
 	player_t *player = NULL;
@@ -4277,6 +4288,7 @@ static luaL_Reg lib[] = {
 	{"P_InQuicksand",lib_pInQuicksand},
 	{"P_InJumpFlipSector",lib_pInJumpFlipSector},
 	{"P_SetObjectMomZ",lib_pSetObjectMomZ},
+	{"P_IsLocalPlayer",lib_pIsLocalPlayer},
 	{"P_PlayJingle",lib_pPlayJingle},
 	{"P_PlayJingleMusic",lib_pPlayJingleMusic},
 	{"P_RestoreMusic",lib_pRestoreMusic},
diff --git a/src/lua_hook.h b/src/lua_hook.h
index cb0e30d870ad3672d1c6076627ff73b90b103d6b..026e5c4aaf97c54326a22d5de56f4e44cbddd09f 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -126,7 +126,9 @@ int  LUA_HookPlayer(player_t *, int hook);
 int  LUA_HookTiccmd(player_t *, ticcmd_t *, int hook);
 int  LUA_HookKey(event_t *event, int hook); // Hooks for key events
 
+void LUA_HookPreThinkFrame(void);
 void LUA_HookThinkFrame(void);
+void LUA_HookPostThinkFrame(void);
 int  LUA_HookMobjLineCollide(mobj_t *, line_t *);
 int  LUA_HookTouchSpecial(mobj_t *special, mobj_t *toucher);
 int  LUA_HookShouldDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype);
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 0fc25ee6c9927b96e893a3fc1a0be9470b8f4938..529c189ff7883bbd377327ad5b44d2ae5af4e196 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -76,12 +76,12 @@ static boolean mobj_hook_available(int hook_type, mobjtype_t mobj_type)
 		);
 }
 
-static int hook_in_list
+static unsigned hook_in_list
 (
 		const char * const         name,
 		const char * const * const list
 ){
-	int type;
+	unsigned type;
 
 	for (type = 0; list[type] != NULL; ++type)
 	{
@@ -200,7 +200,7 @@ static void add_hook_ref(lua_State *L, int idx)
 static int lib_addHook(lua_State *L)
 {
 	const char * name;
-	int type;
+	unsigned type;
 
 	if (!lua_lumploading)
 		return luaL_error(L, "This function cannot be called from within a hook or coroutine!");
@@ -671,10 +671,8 @@ void LUA_HookHUD(int hook_type, huddrawlist_h list)
                                SPECIALIZED HOOKS
    ========================================================================= */
 
-void LUA_HookThinkFrame(void)
+static void hook_think_frame(int type)
 {
-	const int type = HOOK(ThinkFrame);
-
 	// variables used by perf stats
 	int hook_index = 0;
 	precise_t time_taken = 0;
@@ -692,7 +690,7 @@ void LUA_HookThinkFrame(void)
 		{
 			get_hook(&hook, map->ids, k);
 
-			if (cv_perfstats.value == 3)
+			if (cv_perfstats.value >= 3)
 			{
 				lua_pushvalue(gL, -1);/* need the function again */
 				time_taken = I_GetPreciseTime();
@@ -700,12 +698,18 @@ void LUA_HookThinkFrame(void)
 
 			call_single_hook(&hook);
 
-			if (cv_perfstats.value == 3)
+			if (cv_perfstats.value >= 3)
 			{
 				lua_Debug ar;
 				time_taken = I_GetPreciseTime() - time_taken;
 				lua_getinfo(gL, ">S", &ar);
-				PS_SetThinkFrameHookInfo(hook_index, time_taken, ar.short_src);
+				if (type == 4) // sorry for magic numbers
+					PS_SetPreThinkFrameHookInfo(hook_index, time_taken, ar.short_src);
+				else if (type == 5)
+					PS_SetThinkFrameHookInfo(hook_index, time_taken, ar.short_src);
+				else if (type == 6)
+					PS_SetPostThinkFrameHookInfo(hook_index, time_taken, ar.short_src);
+				
 				hook_index++;
 			}
 		}
@@ -714,6 +718,21 @@ void LUA_HookThinkFrame(void)
 	}
 }
 
+void LUA_HookPreThinkFrame(void)
+{
+	hook_think_frame(HOOK(PreThinkFrame));
+}
+
+void LUA_HookThinkFrame(void)
+{
+	hook_think_frame(HOOK(ThinkFrame));
+}
+
+void LUA_HookPostThinkFrame(void)
+{
+	hook_think_frame(HOOK(PostThinkFrame));
+}
+
 int LUA_HookMobjLineCollide(mobj_t *mobj, line_t *line)
 {
 	Hook_State hook;
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 603fc0d8f033825fe89d708a978879adfec5d6e0..5b80d4d38c9db705c59c7003f37af2ce87bb0272 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -35,10 +35,14 @@ enum sector_e {
 	sector_floorpic,
 	sector_floorxoffset,
 	sector_flooryoffset,
+	sector_floorxscale,
+	sector_flooryscale,
 	sector_floorangle,
 	sector_ceilingpic,
 	sector_ceilingxoffset,
 	sector_ceilingyoffset,
+	sector_ceilingxscale,
+	sector_ceilingyscale,
 	sector_ceilingangle,
 	sector_lightlevel,
 	sector_floorlightlevel,
@@ -74,10 +78,14 @@ static const char *const sector_opt[] = {
 	"floorpic",
 	"floorxoffset",
 	"flooryoffset",
+	"floorxscale",
+	"flooryscale",
 	"floorangle",
 	"ceilingpic",
 	"ceilingxoffset",
 	"ceilingyoffset",
+	"ceilingxscale",
+	"ceilingyscale",
 	"ceilingangle",
 	"lightlevel",
 	"floorlightlevel",
@@ -188,8 +196,16 @@ enum side_e {
 	side_offsety_top,
 	side_offsetx_mid,
 	side_offsety_mid,
+	side_offsetx_bottom,
 	side_offsetx_bot,
+	side_offsety_bottom,
 	side_offsety_bot,
+	side_scalex_top,
+	side_scaley_top,
+	side_scalex_mid,
+	side_scaley_mid,
+	side_scalex_bottom,
+	side_scaley_bottom,
 	side_toptexture,
 	side_bottomtexture,
 	side_midtexture,
@@ -208,8 +224,16 @@ static const char *const side_opt[] = {
 	"offsety_top",
 	"offsetx_mid",
 	"offsety_mid",
+	"offsetx_bottom",
 	"offsetx_bot",
+	"offsety_bottom",
 	"offsety_bot",
+	"scalex_top",
+	"scaley_top",
+	"scalex_mid",
+	"scaley_mid",
+	"scalex_bottom",
+	"scaley_bottom",
 	"toptexture",
 	"bottomtexture",
 	"midtexture",
@@ -249,8 +273,16 @@ enum ffloor_e {
 	ffloor_topheight,
 	ffloor_toppic,
 	ffloor_toplightlevel,
+	ffloor_topxoffs,
+	ffloor_topyoffs,
+	ffloor_topxscale,
+	ffloor_topyscale,
 	ffloor_bottomheight,
 	ffloor_bottompic,
+	ffloor_bottomxoffs,
+	ffloor_bottomyoffs,
+	ffloor_bottomxscale,
+	ffloor_bottomyscale,
 	ffloor_tslope,
 	ffloor_bslope,
 	ffloor_sector,
@@ -275,8 +307,16 @@ static const char *const ffloor_opt[] = {
 	"topheight",
 	"toppic",
 	"toplightlevel",
+	"topxoffs",
+	"topyoffs",
+	"topxscale",
+	"topyscale",
 	"bottomheight",
 	"bottompic",
+	"bottomxoffs",
+	"bottomyoffs",
+	"bottomxscale",
+	"bottomyscale",
 	"t_slope",
 	"b_slope",
 	"sector", // secnum pushed as control sector userdata
@@ -656,20 +696,20 @@ static int sector_get(lua_State *L)
 		return 1;
 	}
 	case sector_floorxoffset:
-	{
 		lua_pushfixed(L, sector->floorxoffset);
 		return 1;
-	}
 	case sector_flooryoffset:
-	{
 		lua_pushfixed(L, sector->flooryoffset);
 		return 1;
-	}
+	case sector_floorxscale:
+		lua_pushfixed(L, sector->floorxscale);
+		return 1;
+	case sector_flooryscale:
+		lua_pushfixed(L, sector->flooryscale);
+		return 1;
 	case sector_floorangle:
-	{
 		lua_pushangle(L, sector->floorangle);
 		return 1;
-	}
 	case sector_ceilingpic: // ceilingpic
 	{
 		levelflat_t *levelflat = &levelflats[sector->ceilingpic];
@@ -680,20 +720,20 @@ static int sector_get(lua_State *L)
 		return 1;
 	}
 	case sector_ceilingxoffset:
-	{
 		lua_pushfixed(L, sector->ceilingxoffset);
 		return 1;
-	}
 	case sector_ceilingyoffset:
-	{
 		lua_pushfixed(L, sector->ceilingyoffset);
 		return 1;
-	}
+	case sector_ceilingxscale:
+		lua_pushfixed(L, sector->ceilingxscale);
+		return 1;
+	case sector_ceilingyscale:
+		lua_pushfixed(L, sector->ceilingyscale);
+		return 1;
 	case sector_ceilingangle:
-	{
 		lua_pushangle(L, sector->ceilingangle);
 		return 1;
-	}
 	case sector_lightlevel:
 		lua_pushinteger(L, sector->lightlevel);
 		return 1;
@@ -845,6 +885,12 @@ static int sector_set(lua_State *L)
 	case sector_flooryoffset:
 		sector->flooryoffset = luaL_checkfixed(L, 3);
 		break;
+	case sector_floorxscale:
+		sector->floorxscale = luaL_checkfixed(L, 3);
+		break;
+	case sector_flooryscale:
+		sector->flooryscale = luaL_checkfixed(L, 3);
+		break;
 	case sector_floorangle:
 		sector->floorangle = luaL_checkangle(L, 3);
 		break;
@@ -857,6 +903,12 @@ static int sector_set(lua_State *L)
 	case sector_ceilingyoffset:
 		sector->ceilingyoffset = luaL_checkfixed(L, 3);
 		break;
+	case sector_ceilingxscale:
+		sector->ceilingxscale = luaL_checkfixed(L, 3);
+		break;
+	case sector_ceilingyscale:
+		sector->ceilingyscale = luaL_checkfixed(L, 3);
+		break;
 	case sector_ceilingangle:
 		sector->ceilingangle = luaL_checkangle(L, 3);
 		break;
@@ -1143,7 +1195,7 @@ static int line_num(lua_State *L)
 
 static int sidenum_get(lua_State *L)
 {
-	UINT16 *sidenum = *((UINT16 **)luaL_checkudata(L, 1, META_SIDENUM));
+	UINT32 *sidenum = *((UINT32 **)luaL_checkudata(L, 1, META_SIDENUM));
 	int i;
 	lua_settop(L, 2);
 	if (!lua_isnumber(L, 2))
@@ -1210,11 +1262,31 @@ static int side_get(lua_State *L)
 	case side_offsety_mid:
 		lua_pushfixed(L, side->offsety_mid);
 		return 1;
+	case side_offsetx_bottom:
 	case side_offsetx_bot:
-		lua_pushfixed(L, side->offsetx_bot);
+		lua_pushfixed(L, side->offsetx_bottom);
 		return 1;
+	case side_offsety_bottom:
 	case side_offsety_bot:
-		lua_pushfixed(L, side->offsety_bot);
+		lua_pushfixed(L, side->offsety_bottom);
+		return 1;
+	case side_scalex_top:
+		lua_pushfixed(L, side->scalex_top);
+		return 1;
+	case side_scaley_top:
+		lua_pushfixed(L, side->scaley_top);
+		return 1;
+	case side_scalex_mid:
+		lua_pushfixed(L, side->scalex_mid);
+		return 1;
+	case side_scaley_mid:
+		lua_pushfixed(L, side->scaley_mid);
+		return 1;
+	case side_scalex_bottom:
+		lua_pushfixed(L, side->scalex_bottom);
+		return 1;
+	case side_scaley_bottom:
+		lua_pushfixed(L, side->scaley_bottom);
 		return 1;
 	case side_toptexture:
 		lua_pushinteger(L, side->toptexture);
@@ -1302,10 +1374,30 @@ static int side_set(lua_State *L)
 		side->offsety_mid = luaL_checkfixed(L, 3);
 		break;
 	case side_offsetx_bot:
-		side->offsetx_bot = luaL_checkfixed(L, 3);
+	case side_offsetx_bottom:
+		side->offsetx_bottom = luaL_checkfixed(L, 3);
 		break;
 	case side_offsety_bot:
-		side->offsety_bot = luaL_checkfixed(L, 3);
+	case side_offsety_bottom:
+		side->offsety_bottom = luaL_checkfixed(L, 3);
+		break;
+	case side_scalex_top:
+		side->scalex_top = luaL_checkfixed(L, 3);
+		break;
+	case side_scaley_top:
+		side->scaley_top = luaL_checkfixed(L, 3);
+		break;
+	case side_scalex_mid:
+		side->scalex_mid = luaL_checkfixed(L, 3);
+		break;
+	case side_scaley_mid:
+		side->scaley_mid = luaL_checkfixed(L, 3);
+		break;
+	case side_scalex_bottom:
+		side->scalex_bottom = luaL_checkfixed(L, 3);
+		break;
+	case side_scaley_bottom:
+		side->scaley_bottom = luaL_checkfixed(L, 3);
 		break;
 	case side_toptexture:
 		side->toptexture = luaL_checkinteger(L, 3);
@@ -2122,6 +2214,18 @@ static int ffloor_get(lua_State *L)
 	case ffloor_toplightlevel:
 		lua_pushinteger(L, *ffloor->toplightlevel);
 		return 1;
+	case ffloor_topxoffs:
+		lua_pushfixed(L, *ffloor->topxoffs);
+		return 1;
+	case ffloor_topyoffs:
+		lua_pushfixed(L, *ffloor->topyoffs);
+		return 1;
+	case ffloor_topxscale:
+		lua_pushfixed(L, *ffloor->topxscale);
+		return 1;
+	case ffloor_topyscale:
+		lua_pushfixed(L, *ffloor->topyscale);
+		return 1;
 	case ffloor_bottomheight:
 		lua_pushfixed(L, *ffloor->bottomheight);
 		return 1;
@@ -2133,6 +2237,18 @@ static int ffloor_get(lua_State *L)
 		lua_pushlstring(L, levelflat->name, i);
 		return 1;
 	}
+	case ffloor_bottomxoffs:
+		lua_pushfixed(L, *ffloor->bottomxoffs);
+		return 1;
+	case ffloor_bottomyoffs:
+		lua_pushfixed(L, *ffloor->bottomyoffs);
+		return 1;
+	case ffloor_bottomxscale:
+		lua_pushfixed(L, *ffloor->bottomxscale);
+		return 1;
+	case ffloor_bottomyscale:
+		lua_pushfixed(L, *ffloor->bottomyscale);
+		return 1;
 	case ffloor_tslope:
 		LUA_PushUserdata(L, *ffloor->t_slope, META_SLOPE);
 		return 1;
@@ -2317,6 +2433,18 @@ static int ffloor_set(lua_State *L)
 	case ffloor_toplightlevel:
 		*ffloor->toplightlevel = (INT16)luaL_checkinteger(L, 3);
 		break;
+	case ffloor_topxoffs:
+		*ffloor->topxoffs = luaL_checkfixed(L, 3);
+		break;
+	case ffloor_topyoffs:
+		*ffloor->topyoffs = luaL_checkfixed(L, 3);
+		break;
+	case ffloor_topxscale:
+		*ffloor->topxscale = luaL_checkfixed(L, 3);
+		break;
+	case ffloor_topyscale:
+		*ffloor->topyscale = luaL_checkfixed(L, 3);
+		break;
 	case ffloor_bottomheight: { // bottomheight
 		boolean flag;
 		fixed_t lastpos = *ffloor->bottomheight;
@@ -2335,6 +2463,18 @@ static int ffloor_set(lua_State *L)
 	case ffloor_bottompic:
 		*ffloor->bottompic = P_AddLevelFlatRuntime(luaL_checkstring(L, 3));
 		break;
+	case ffloor_bottomxoffs:
+		*ffloor->bottomxoffs = luaL_checkfixed(L, 3);
+		break;
+	case ffloor_bottomyoffs:
+		*ffloor->bottomyoffs = luaL_checkfixed(L, 3);
+		break;
+	case ffloor_bottomxscale:
+		*ffloor->bottomxscale = luaL_checkfixed(L, 3);
+		break;
+	case ffloor_bottomyscale:
+		*ffloor->bottomyscale = luaL_checkfixed(L, 3);
+		break;
 	case ffloor_fofflags: {
 		ffloortype_e oldflags = ffloor->fofflags; // store FOF's old flags
 		ffloor->fofflags = luaL_checkinteger(L, 3);
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index e328eb1759b808dd67ee1e65bf48805415520291..b5c6c0329556e3463dc95e1dc3ce6f8b16cd7f50 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -641,10 +641,7 @@ static int mobj_set(lua_State *L)
 		mo->tics = luaL_checkinteger(L, 3);
 		break;
 	case mobj_state: // set state by enum
-		if (mo->player)
-			P_SetPlayerMobjState(mo, luaL_checkinteger(L, 3));
-		else
-			P_SetMobjState(mo, luaL_checkinteger(L, 3));
+		P_SetMobjState(mo, luaL_checkinteger(L, 3));
 		break;
 	case mobj_flags: // special handling for MF_NOBLOCKMAP and MF_NOSECTOR
 	{
diff --git a/src/lua_script.c b/src/lua_script.c
index 643974106611f8922961dbbe640fdd52bb076055..72e5a3f456505a81314a0ebd124604004ae44936 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -604,64 +604,114 @@ void LUA_ClearExtVars(void)
 INT32 lua_lumploading = 0;
 
 // Load a script from a MYFILE
-static inline void LUA_LoadFile(MYFILE *f, char *name, boolean noresults)
+static inline boolean LUA_LoadFile(MYFILE *f, char *name)
 {
 	int errorhandlerindex;
+	boolean success;
 
 	if (!name)
 		name = wadfiles[f->wad]->filename;
+
 	CONS_Printf("Loading Lua script from %s\n", name);
+
 	if (!gL) // Lua needs to be initialized
 		LUA_ClearState();
+
 	lua_pushinteger(gL, f->wad);
 	lua_setfield(gL, LUA_REGISTRYINDEX, "WAD");
 
+	lua_pushcfunction(gL, LUA_GetErrorMessage);
+	errorhandlerindex = lua_gettop(gL);
+
+	success = !luaL_loadbuffer(gL, f->data, f->size, va("@%s",name));
+
+	if (!success) {
+		CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
+		lua_pop(gL,1);
+	}
+
+	lua_gc(gL, LUA_GCCOLLECT, 0);
+	lua_remove(gL, errorhandlerindex);
+
+	return success;
+}
+
+// Runs a script loaded by LUA_LoadFile.
+static inline void LUA_DoFile(boolean noresults)
+{
+	int errorhandlerindex;
+
+	if (!gL) // LUA_LoadFile should've allocated gL for us!
+		return;
+
 	lua_lumploading++; // turn on loading flag
 
 	lua_pushcfunction(gL, LUA_GetErrorMessage);
-	errorhandlerindex = lua_gettop(gL);
-	if (luaL_loadbuffer(gL, f->data, f->size, va("@%s",name)) || lua_pcall(gL, 0, noresults ? 0 : LUA_MULTRET, lua_gettop(gL) - 1)) {
+	lua_insert(gL, -2); // move the function we're calling to the top.
+	errorhandlerindex = lua_gettop(gL) - 1;
+
+	if (lua_pcall(gL, 0, noresults ? 0 : LUA_MULTRET, lua_gettop(gL) - 1)) {
 		CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
 		lua_pop(gL,1);
 	}
+
 	lua_gc(gL, LUA_GCCOLLECT, 0);
 	lua_remove(gL, errorhandlerindex);
 
 	lua_lumploading--; // turn off again
 }
 
-// Load a script from a lump
-void LUA_LoadLump(UINT16 wad, UINT16 lump, boolean noresults)
+static inline MYFILE *LUA_GetFile(UINT16 wad, UINT16 lump, char **name)
 {
-	MYFILE f;
-	char *name;
+	MYFILE *f = Z_Malloc(sizeof(MYFILE), PU_LUA, NULL);
 	size_t len;
-	f.wad = wad;
-	f.size = W_LumpLengthPwad(wad, lump);
-	f.data = Z_Malloc(f.size, PU_LUA, NULL);
-	W_ReadLumpPwad(wad, lump, f.data);
-	f.curpos = f.data;
+
+	f->wad = wad;
+	f->size = W_LumpLengthPwad(wad, lump);
+	f->data = Z_Malloc(f->size, PU_LUA, NULL);
+	W_ReadLumpPwad(wad, lump, f->data);
+	f->curpos = f->data;
 
 	len = strlen(wadfiles[wad]->filename); // length of file name
 
 	if (wadfiles[wad]->type == RET_LUA)
 	{
-		name = malloc(len+1);
-		strcpy(name, wadfiles[wad]->filename);
+		*name = malloc(len+1);
+		strcpy(*name, wadfiles[wad]->filename);
 	}
 	else // If it's not a .lua file, copy the lump name in too.
 	{
 		lumpinfo_t *lump_p = &wadfiles[wad]->lumpinfo[lump];
 		len += 1 + strlen(lump_p->fullname); // length of file name, '|', and lump name
-		name = malloc(len+1);
-		sprintf(name, "%s|%s", wadfiles[wad]->filename, lump_p->fullname);
-		name[len] = '\0';
+		*name = malloc(len+1);
+		sprintf(*name, "%s|%s", wadfiles[wad]->filename, lump_p->fullname);
+		(*name)[len] = '\0'; // annoying that index takes priority over dereference, but w/e
 	}
 
-	LUA_LoadFile(&f, name, noresults); // actually load file!
+	return f;
+}
+
+// Load a script from a lump
+boolean LUA_LoadLump(UINT16 wad, UINT16 lump)
+{
+	char *name = NULL;
+	MYFILE *f = LUA_GetFile(wad, lump, &name);
+	boolean success = LUA_LoadFile(f, name); // actually load file!
 
 	free(name);
-	Z_Free(f.data);
+
+	Z_Free(f->data);
+	Z_Free(f);
+
+	return success;
+}
+
+void LUA_DoLump(UINT16 wad, UINT16 lump, boolean noresults)
+{
+	boolean success = LUA_LoadLump(wad, lump);
+
+	if (success)
+		LUA_DoFile(noresults); // run it
 }
 
 #ifdef LUA_ALLOW_BYTECODE
diff --git a/src/lua_script.h b/src/lua_script.h
index 53d848f8e878a4234b49fd8e38bbc94325488f61..45d2a37ffd1a636f1e6aaf6848fa868a25fba74b 100644
--- a/src/lua_script.h
+++ b/src/lua_script.h
@@ -45,7 +45,8 @@ extern INT32 lua_lumploading; // is LUA_LoadLump being called?
 
 int LUA_GetErrorMessage(lua_State *L);
 int LUA_Call(lua_State *L, int nargs, int nresults, int errorhandlerindex);
-void LUA_LoadLump(UINT16 wad, UINT16 lump, boolean noresults);
+boolean LUA_LoadLump(UINT16 wad, UINT16 lump);
+void LUA_DoLump(UINT16 wad, UINT16 lump, boolean noresults);
 #ifdef LUA_ALLOW_BYTECODE
 void LUA_DumpFile(const char *filename);
 #endif
diff --git a/src/lua_taglib.c b/src/lua_taglib.c
index 9e73a050c9011f796be0a2ae41399d79f2a7f836..a040a7efcf5a7a5ef6f0532669c255bce0856373 100644
--- a/src/lua_taglib.c
+++ b/src/lua_taglib.c
@@ -228,7 +228,7 @@ static int taglist_get(lua_State *L)
 	}
 	else
 	{
-		lua_getmetatable(L, 1);
+		lua_getglobal(L, "taglist");
 		lua_replace(L, 1);
 		lua_rawget(L, 1);
 		return 1;
diff --git a/src/m_cheat.c b/src/m_cheat.c
index 2bcf43ad1ee1971c13ab3a769ef8b0fafc708883..e61db2c2ee3d49e58f570567ee576ba94d3959eb 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -1019,7 +1019,7 @@ static void OP_CycleThings(INT32 amt)
 	if (players[0].mo->eflags & MFE_VERTICALFLIP) // correct z when flipped
 		players[0].mo->z += players[0].mo->height - FixedMul(mobjinfo[op_currentthing].height, players[0].mo->scale);
 	players[0].mo->height = FixedMul(mobjinfo[op_currentthing].height, players[0].mo->scale);
-	P_SetPlayerMobjState(players[0].mo, S_OBJPLACE_DUMMY);
+	P_SetMobjState(players[0].mo, S_OBJPLACE_DUMMY);
 
 	op_currentdoomednum = mobjinfo[op_currentthing].doomednum;
 }
@@ -1528,7 +1528,7 @@ void Command_ObjectPlace_f(void)
 		else
 			OP_CycleThings(0); // sets all necessary height values without cycling op_currentthing
 
-		P_SetPlayerMobjState(players[0].mo, S_OBJPLACE_DUMMY);
+		P_SetMobjState(players[0].mo, S_OBJPLACE_DUMMY);
 	}
 	// Or are we leaving it instead?
 	else
@@ -1542,7 +1542,7 @@ void Command_ObjectPlace_f(void)
 
 		// If still in dummy state, get out of it.
 		if (players[0].mo->state == &states[S_OBJPLACE_DUMMY])
-			P_SetPlayerMobjState(players[0].mo, op_oldstate);
+			P_SetMobjState(players[0].mo, op_oldstate);
 
 		// Reset everything back to how it was before we entered objectplace.
 		P_UnsetThingPosition(players[0].mo);
diff --git a/src/m_fixed.h b/src/m_fixed.h
index 94bd6a16bbf86e70c4f38c86a4758424da325db1..4e644c9b6d5db3687a211526e3a50a5f03e9702f 100644
--- a/src/m_fixed.h
+++ b/src/m_fixed.h
@@ -34,6 +34,7 @@
 */
 
 typedef INT32 fixed_t;
+typedef UINT32 ufixed_t;
 
 /*!
   \brief convert fixed_t into floating number
@@ -106,7 +107,7 @@ FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FixedInt(fixed_t a)
 */
 FUNCMATH FUNCINLINE static ATTRINLINE fixed_t FixedDiv(fixed_t a, fixed_t b)
 {
-	if ((abs(a) >> (FRACBITS-2)) >= abs(b))
+	if (((ufixed_t)abs(a) >> (FRACBITS-2)) >= (ufixed_t)abs(b))
 		return (a^b) < 0 ? INT32_MIN : INT32_MAX;
 
 	return FixedDiv2(a, b);
diff --git a/src/m_menu.c b/src/m_menu.c
index 2864df31ab84761adfad67a75b38e5e156eee7c4..0012bd599c3558236fde8b150333e8fa78a50117 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -3179,40 +3179,42 @@ boolean M_Responder(event_t *ev)
 	}
 	else if (menuactive)
 	{
-		if (ev->type == ev_keydown)
+		if (ev->type == ev_keydown || ev->type == ev_text)
 		{
-			keydown++;
 			ch = ev->key;
-
-			// added 5-2-98 remap virtual keys (mouse & joystick buttons)
-			switch (ch)
+			if (ev->type == ev_keydown)
 			{
-				case KEY_MOUSE1:
-				case KEY_JOY1:
-					ch = KEY_ENTER;
-					break;
-				case KEY_JOY1 + 3:
-					ch = 'n';
-					break;
-				case KEY_MOUSE1 + 1:
-				case KEY_JOY1 + 1:
-					ch = KEY_ESCAPE;
-					break;
-				case KEY_JOY1 + 2:
-					ch = KEY_BACKSPACE;
-					break;
-				case KEY_HAT1:
-					ch = KEY_UPARROW;
-					break;
-				case KEY_HAT1 + 1:
-					ch = KEY_DOWNARROW;
-					break;
-				case KEY_HAT1 + 2:
-					ch = KEY_LEFTARROW;
-					break;
-				case KEY_HAT1 + 3:
-					ch = KEY_RIGHTARROW;
-					break;
+				keydown++;
+				// added 5-2-98 remap virtual keys (mouse & joystick buttons)
+				switch (ch)
+				{
+					case KEY_MOUSE1:
+					case KEY_JOY1:
+						ch = KEY_ENTER;
+						break;
+					case KEY_JOY1 + 3:
+						ch = 'n';
+						break;
+					case KEY_MOUSE1 + 1:
+					case KEY_JOY1 + 1:
+						ch = KEY_ESCAPE;
+						break;
+					case KEY_JOY1 + 2:
+						ch = KEY_BACKSPACE;
+						break;
+					case KEY_HAT1:
+						ch = KEY_UPARROW;
+						break;
+					case KEY_HAT1 + 1:
+						ch = KEY_DOWNARROW;
+						break;
+					case KEY_HAT1 + 2:
+						ch = KEY_LEFTARROW;
+						break;
+					case KEY_HAT1 + 3:
+						ch = KEY_RIGHTARROW;
+						break;
+				}
 			}
 		}
 		else if (ev->type == ev_joystick  && ev->key == 0 && joywait < I_GetTime())
@@ -3374,8 +3376,11 @@ boolean M_Responder(event_t *ev)
 	// Handle menuitems which need a specific key handling
 	if (routine && (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_KEYHANDLER)
 	{
-		if (shiftdown && ch >= 32 && ch <= 127)
-			ch = shiftxform[ch];
+		// ignore ev_keydown events if the key maps to a character, since
+		// the ev_text event will follow immediately after in that case.
+		if (ev->type == ev_keydown && ch >= 32 && ch <= 127)
+			return true;
+
 		routine(ch);
 		return true;
 	}
@@ -3417,6 +3422,11 @@ boolean M_Responder(event_t *ev)
 	{
 		if ((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_STRING)
 		{
+			// ignore ev_keydown events if the key maps to a character, since
+			// the ev_text event will follow immediately after in that case.
+			if (ev->type == ev_keydown && ch >= 32 && ch <= 127)
+				return false;
+
 			if (M_ChangeStringCvar(ch))
 				return true;
 			else
@@ -4130,31 +4140,97 @@ void M_DrawTextBox(INT32 x, INT32 y, INT32 width, INT32 boxlines)
 */
 }
 
-static fixed_t staticalong = 0;
-
+//
+// Draw the TV static effect on unavailable map icons
+//
 static void M_DrawStaticBox(fixed_t x, fixed_t y, INT32 flags, fixed_t w, fixed_t h)
 {
-	patch_t *patch;
-	fixed_t sw, pw;
+	patch_t *patch = W_CachePatchName("LSSTATIC", PU_PATCH);
+	static fixed_t staticx = 0, staticy = 0; // Keep track of where we are across function calls
 
-	patch = W_CachePatchName("LSSTATIC", PU_PATCH);
-	pw = patch->width - (sw = w*2); //FixedDiv(w, scale); -- for scale FRACUNIT/2
 
-	/*if (pw > 0) -- model code for modders providing weird LSSTATIC
+	if (!patch->width || !patch->height) // Shouldn't be necessary, but I don't want to get in trouble!
 	{
-		if (staticalong > pw)
-			staticalong -= pw;
+		W_UnlockCachedPatch(patch);
+		return;
 	}
-	else
-		staticalong = 0;*/
+	if (patch->width == 1) // Nothing to randomise or tile
+	{
+		// Just stretch the patch over the whole box - no need to draw it 160 times over
+		// Technically, we should crop and maybe tile and randomise the Y axis, but... it's not worth it here
+		V_DrawStretchyFixedPatch(x*FRACUNIT, y*FRACUNIT, (w*FRACUNIT) / patch->width, (h*FRACUNIT) / patch->height, flags, patch, NULL);
+		W_UnlockCachedPatch(patch);
+		return;
+	}
+	if (patch->width == 160) // Something to randomise or tile - but don't!
+	{
+		// If it's 160 pixels wide, the modder probably wants the patch fixed in place
+		// But instead of "just drawing" it, why not allow sequential frames of animation on the Y axis?
+		// For example, this could be used to make a vignette effect, whether animated or not
+		// This function is primarily called with a patch region of 160x100, so the frames must be 160x100 too
+		fixed_t temp = patch->height / 100; // Amount of 160x100 frames in the patch
+		if (temp) // Don't modulo by zero
+			temp = (gametic % temp) * h*2*FRACUNIT; // Which frame to draw
+
+		V_DrawCroppedPatch(x*FRACUNIT, y*FRACUNIT, (w*FRACUNIT) / 160, (h*FRACUNIT) / 100, flags, patch, NULL, 0, temp, w*2*FRACUNIT, h*2*FRACUNIT);
+		
+		W_UnlockCachedPatch(patch);
+		return;
+	}
+
+
+	// If the patch isn't 1 or 160 pixels wide, that means that it's time for randomised static!
+
+	// First, randomise the static patch's offset - It's largely just based on "w", but there's...
+	// ...the tiniest bit of randomisation, to keep it animated even when the patch width is 160 x [static boxes on-screen]
+	// Making it TOO randomised could make it randomise to the almost-same position multiple frames in a row - that's bad
+	staticx = (staticx + (w*4) + (M_RandomByte() % 4)) % (patch->width*2);
 
-	if (staticalong > pw) // simplified for base LSSTATIC
-		staticalong -= pw;
+	if (patch->height == h*2) // Is the patch 100 pixels tall? If so, reset "staticy"...
+		staticy = 0; // ...in case that one add-on would randomise it and a later add-on wouldn't
+	else // Otherwise, as we already make "staticx" near-sequential, I think that making "staticy"...
+		staticy = M_RandomRange(0, (patch->height*2) - 1); // ...fully random instead of sequential increases the... randomness
 
-	V_DrawCroppedPatch(x<<FRACBITS, y<<FRACBITS, FRACUNIT/2, FRACUNIT/2, flags, patch, NULL, staticalong<<FRACBITS, 0, sw<<FRACBITS, h*2<<FRACBITS); // FixedDiv(h, scale)); -- for scale FRACUNIT/2
+	// The drawing function calls can get a bit lengthy, so let's make a little shortcut
+#define DRAWSTATIC(_x,_y,_sx,_sy,_w,_h) V_DrawCroppedPatch((x*FRACUNIT)+((_x)*FRACUNIT/4), (y*FRACUNIT)+((_y)*FRACUNIT/4),\
+FRACUNIT/2, FRACUNIT/2, flags, patch, NULL, (_sx)*FRACUNIT/2, (_sy)*FRACUNIT/2, (w*2*FRACUNIT)+((_w)*FRACUNIT/2), (h*2*FRACUNIT)+((_h)*FRACUNIT/2))
 
-	staticalong += sw; //M_RandomRange(sw/2, 2*sw); -- turns out less randomisation looks better because immediately adjacent frames can't end up close to each other
+	// And finally, let's draw it! Don't worry about "staticx" plus "w*2" potentially going off-patch
+	DRAWSTATIC(0, 0, staticx, staticy, 0, 0); // This gets drawn in all cases
+
+	if ((patch->width*2) - staticx >= w*4) // No horizontal tiling
+	{
+		if ((patch->height*2) - staticy >= h*4) // Simplest-case scenario, no tiling at all
+			{}
+		else // Vertical tiling only
+		{
+			for (INT16 j = 2; ((patch->height*j) - staticy) < h*4; j += 2)
+				DRAWSTATIC(0, (patch->height*j) - staticy, staticx, 0, 0, staticy - (patch->height*j));
+		}
+	}
+	else // Horizontal tiling
+	{
+		if ((patch->height*2) - staticy >= h*4) // Horizontal tiling only
+		{
+			for (INT16 i = 2; ((patch->width*i) - staticx) < w*4; i += 2)
+				DRAWSTATIC((patch->width*i) - staticx, 0, 0, staticy, staticx - (patch->width*i), 0);
+		}
+		else // Horizontal and vertical tiling
+		{
+			for (INT16 j = 2; ((patch->height*j) - staticy) < h*4; j += 2)
+				DRAWSTATIC(0, (patch->height*j) - staticy, staticx, 0, 0, staticy - (patch->height*j));
+
+			for (INT16 i = 2; ((patch->width*i) - staticx) < w*4; i += 2)
+			{
+				DRAWSTATIC((patch->width*i) - staticx, 0, 0, staticy, staticx - (patch->width*i), 0);
+				for (INT16 j = 2; ((patch->height*j) - staticy) < h*4; j += 2)
+					DRAWSTATIC((patch->width*i) - staticx, (patch->height*j) - staticy, 0, 0, staticx - (patch->width*i), staticy - (patch->height*j));
+			}
+		}
+	}
+#undef DRAWSTATIC
 
+	// Now that we're done with the patch, it's time to say our goodbyes. Until next time, patch!
 	W_UnlockCachedPatch(patch);
 }
 
@@ -11268,6 +11344,8 @@ static void M_DrawConnectMenu(void)
 			V_DrawSmallString(currentMenu->x+202, S_LINEY(i)+8, globalflags, "\x85" "Mod");
 		if (serverlist[slindex].info.cheatsenabled)
 			V_DrawSmallString(currentMenu->x+222, S_LINEY(i)+8, globalflags, "\x83" "Cheats");
+		if (Net_IsNodeIPv6(serverlist[slindex].node))
+			V_DrawSmallString(currentMenu->x+252, S_LINEY(i)+8, globalflags, "\x84" "IPv6");
 
 		V_DrawSmallString(currentMenu->x, S_LINEY(i)+8, globalflags,
 		                     va("Ping: %u", (UINT32)LONG(serverlist[slindex].info.time)));
@@ -11961,7 +12039,7 @@ static void M_HandleConnectIP(INT32 choice)
 			// Rudimentary number and period enforcing - also allows letters so hostnames can be used instead
 			// and square brackets for RFC 2732 IPv6 addresses
 			if ((choice >= '-' && choice <= ':') ||
-					(choice == '[' || choice == ']') ||
+					(choice == '[' || choice == ']' || choice == '%') ||
 					(choice >= 'A' && choice <= 'Z') ||
 					(choice >= 'a' && choice <= 'z'))
 			{
diff --git a/src/m_misc.c b/src/m_misc.c
index 923a188c62346d4c9d72ea1759898fa78aa867ac..ce332910dc8ccf78a8c751adfc96c28c3721aea4 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -2207,430 +2207,11 @@ char *sizeu5(size_t num)
 	return sizeu5_buf;
 }
 
-#if defined (__GNUC__) && defined (__i386__) // from libkwave, under GPL
-// Alam: note libkwave memcpy code comes from mplayer's libvo/aclib_template.c, r699
-
-/* for small memory blocks (<256 bytes) this version is faster */
-#define small_memcpy(dest,src,n)\
-{\
-register unsigned long int dummy;\
-__asm__ __volatile__(\
-	"cld\n\t"\
-	"rep; movsb"\
-	:"=&D"(dest), "=&S"(src), "=&c"(dummy)\
-	:"0" (dest), "1" (src),"2" (n)\
-	: "memory", "cc");\
-}
-/* linux kernel __memcpy (from: /include/asm/string.h) */
-ATTRINLINE static FUNCINLINE void *__memcpy (void *dest, const void * src, size_t n)
-{
-	int d0, d1, d2;
-
-	if ( n < 4 )
-	{
-		small_memcpy(dest, src, n);
-	}
-	else
-	{
-		__asm__ __volatile__ (
-			"rep ; movsl;"
-			"testb $2,%b4;"
-			"je 1f;"
-			"movsw;"
-			"1:\ttestb $1,%b4;"
-			"je 2f;"
-			"movsb;"
-			"2:"
-		: "=&c" (d0), "=&D" (d1), "=&S" (d2)
-		:"0" (n/4), "q" (n),"1" ((long) dest),"2" ((long) src)
-		: "memory");
-	}
-
-	return dest;
-}
-
-#define SSE_MMREG_SIZE 16
-#define MMX_MMREG_SIZE 8
-
-#define MMX1_MIN_LEN 0x800  /* 2K blocks */
-#define MIN_LEN 0x40  /* 64-byte blocks */
-
-/* SSE note: i tried to move 128 bytes a time instead of 64 but it
-didn't make any measureable difference. i'm using 64 for the sake of
-simplicity. [MF] */
-static /*FUNCTARGET("sse2")*/ void *sse_cpy(void * dest, const void * src, size_t n)
-{
-	void *retval = dest;
-	size_t i;
-
-	/* PREFETCH has effect even for MOVSB instruction ;) */
-	__asm__ __volatile__ (
-		"prefetchnta (%0);"
-		"prefetchnta 32(%0);"
-		"prefetchnta 64(%0);"
-		"prefetchnta 96(%0);"
-		"prefetchnta 128(%0);"
-		"prefetchnta 160(%0);"
-		"prefetchnta 192(%0);"
-		"prefetchnta 224(%0);"
-		"prefetchnta 256(%0);"
-		"prefetchnta 288(%0);"
-		: : "r" (src) );
-
-	if (n >= MIN_LEN)
-	{
-		register unsigned long int delta;
-		/* Align destinition to MMREG_SIZE -boundary */
-		delta = ((unsigned long int)dest)&(SSE_MMREG_SIZE-1);
-		if (delta)
-		{
-			delta=SSE_MMREG_SIZE-delta;
-			n -= delta;
-			small_memcpy(dest, src, delta);
-		}
-		i = n >> 6; /* n/64 */
-		n&=63;
-		if (((unsigned long)src) & 15)
-		/* if SRC is misaligned */
-		 for (; i>0; i--)
-		 {
-			__asm__ __volatile__ (
-				"prefetchnta 320(%0);"
-				"prefetchnta 352(%0);"
-				"movups (%0), %%xmm0;"
-				"movups 16(%0), %%xmm1;"
-				"movups 32(%0), %%xmm2;"
-				"movups 48(%0), %%xmm3;"
-				"movntps %%xmm0, (%1);"
-				"movntps %%xmm1, 16(%1);"
-				"movntps %%xmm2, 32(%1);"
-				"movntps %%xmm3, 48(%1);"
-			:: "r" (src), "r" (dest) : "memory");
-			src = (const unsigned char *)src + 64;
-			dest = (unsigned char *)dest + 64;
-		}
-		else
-			/*
-			   Only if SRC is aligned on 16-byte boundary.
-			   It allows to use movaps instead of movups, which required data
-			   to be aligned or a general-protection exception (#GP) is generated.
-			*/
-		 for (; i>0; i--)
-		 {
-			__asm__ __volatile__ (
-				"prefetchnta 320(%0);"
-				"prefetchnta 352(%0);"
-				"movaps (%0), %%xmm0;"
-				"movaps 16(%0), %%xmm1;"
-				"movaps 32(%0), %%xmm2;"
-				"movaps 48(%0), %%xmm3;"
-				"movntps %%xmm0, (%1);"
-				"movntps %%xmm1, 16(%1);"
-				"movntps %%xmm2, 32(%1);"
-				"movntps %%xmm3, 48(%1);"
-			:: "r" (src), "r" (dest) : "memory");
-			src = ((const unsigned char *)src) + 64;
-			dest = ((unsigned char *)dest) + 64;
-		}
-		/* since movntq is weakly-ordered, a "sfence"
-		 * is needed to become ordered again. */
-		__asm__ __volatile__ ("sfence":::"memory");
-		/* enables to use FPU */
-		__asm__ __volatile__ ("emms":::"memory");
-	}
-	/*
-	 *	Now do the tail of the block
-	 */
-	if (n) __memcpy(dest, src, n);
-	return retval;
-}
-
-static FUNCTARGET("mmx") void *mmx2_cpy(void *dest, const void *src, size_t n)
-{
-	void *retval = dest;
-	size_t i;
-
-	/* PREFETCH has effect even for MOVSB instruction ;) */
-	__asm__ __volatile__ (
-		"prefetchnta (%0);"
-		"prefetchnta 32(%0);"
-		"prefetchnta 64(%0);"
-		"prefetchnta 96(%0);"
-		"prefetchnta 128(%0);"
-		"prefetchnta 160(%0);"
-		"prefetchnta 192(%0);"
-		"prefetchnta 224(%0);"
-		"prefetchnta 256(%0);"
-		"prefetchnta 288(%0);"
-	: : "r" (src));
-
-	if (n >= MIN_LEN)
-	{
-		register unsigned long int delta;
-		/* Align destinition to MMREG_SIZE -boundary */
-		delta = ((unsigned long int)dest)&(MMX_MMREG_SIZE-1);
-		if (delta)
-		{
-			delta=MMX_MMREG_SIZE-delta;
-			n -= delta;
-			small_memcpy(dest, src, delta);
-		}
-		i = n >> 6; /* n/64 */
-		n&=63;
-		for (; i>0; i--)
-		{
-			__asm__ __volatile__ (
-				"prefetchnta 320(%0);"
-				"prefetchnta 352(%0);"
-				"movq (%0), %%mm0;"
-				"movq 8(%0), %%mm1;"
-				"movq 16(%0), %%mm2;"
-				"movq 24(%0), %%mm3;"
-				"movq 32(%0), %%mm4;"
-				"movq 40(%0), %%mm5;"
-				"movq 48(%0), %%mm6;"
-				"movq 56(%0), %%mm7;"
-				"movntq %%mm0, (%1);"
-				"movntq %%mm1, 8(%1);"
-				"movntq %%mm2, 16(%1);"
-				"movntq %%mm3, 24(%1);"
-				"movntq %%mm4, 32(%1);"
-				"movntq %%mm5, 40(%1);"
-				"movntq %%mm6, 48(%1);"
-				"movntq %%mm7, 56(%1);"
-			:: "r" (src), "r" (dest) : "memory");
-			src = ((const unsigned char *)src) + 64;
-			dest = ((unsigned char *)dest) + 64;
-		}
-		/* since movntq is weakly-ordered, a "sfence"
-		* is needed to become ordered again. */
-		__asm__ __volatile__ ("sfence":::"memory");
-		__asm__ __volatile__ ("emms":::"memory");
-	}
-	/*
-	 *	Now do the tail of the block
-	 */
-	if (n) __memcpy(dest, src, n);
-	return retval;
-}
-
-static FUNCTARGET("mmx") void *mmx1_cpy(void *dest, const void *src, size_t n) //3DNOW
-{
-	void *retval = dest;
-	size_t i;
-
-	/* PREFETCH has effect even for MOVSB instruction ;) */
-	__asm__ __volatile__ (
-		"prefetch (%0);"
-		"prefetch 32(%0);"
-		"prefetch 64(%0);"
-		"prefetch 96(%0);"
-		"prefetch 128(%0);"
-		"prefetch 160(%0);"
-		"prefetch 192(%0);"
-		"prefetch 224(%0);"
-		"prefetch 256(%0);"
-		"prefetch 288(%0);"
-	: : "r" (src));
-
-	if (n >= MMX1_MIN_LEN)
-	{
-		register unsigned long int delta;
-		/* Align destinition to MMREG_SIZE -boundary */
-		delta = ((unsigned long int)dest)&(MMX_MMREG_SIZE-1);
-		if (delta)
-		{
-			delta=MMX_MMREG_SIZE-delta;
-			n -= delta;
-			small_memcpy(dest, src, delta);
-		}
-		i = n >> 6; /* n/64 */
-		n&=63;
-		for (; i>0; i--)
-		{
-			__asm__ __volatile__ (
-				"prefetch 320(%0);"
-				"prefetch 352(%0);"
-				"movq (%0), %%mm0;"
-				"movq 8(%0), %%mm1;"
-				"movq 16(%0), %%mm2;"
-				"movq 24(%0), %%mm3;"
-				"movq 32(%0), %%mm4;"
-				"movq 40(%0), %%mm5;"
-				"movq 48(%0), %%mm6;"
-				"movq 56(%0), %%mm7;"
-				"movq %%mm0, (%1);"
-				"movq %%mm1, 8(%1);"
-				"movq %%mm2, 16(%1);"
-				"movq %%mm3, 24(%1);"
-				"movq %%mm4, 32(%1);"
-				"movq %%mm5, 40(%1);"
-				"movq %%mm6, 48(%1);"
-				"movq %%mm7, 56(%1);"
-			:: "r" (src), "r" (dest) : "memory");
-			src = ((const unsigned char *)src) + 64;
-			dest = ((unsigned char *)dest) + 64;
-		}
-		__asm__ __volatile__ ("femms":::"memory"); // same as mmx_cpy() but with a femms
-	}
-	/*
-	 *	Now do the tail of the block
-	 */
-	if (n) __memcpy(dest, src, n);
-	return retval;
-}
-#endif
-
-// Alam: why? memcpy may be __cdecl/_System and our code may be not the same type
-static void *cpu_cpy(void *dest, const void *src, size_t n)
+void *M_Memcpy(void *dest, const void *src, size_t n)
 {
-	if (src == NULL)
-	{
-		CONS_Debug(DBG_MEMORY, "Memcpy from 0x0?!: %p %p %s\n", dest, src, sizeu1(n));
-		return dest;
-	}
-
-	if(dest == NULL)
-	{
-		CONS_Debug(DBG_MEMORY, "Memcpy to 0x0?!: %p %p %s\n", dest, src, sizeu1(n));
-		return dest;
-	}
-
 	return memcpy(dest, src, n);
 }
 
-static /*FUNCTARGET("mmx")*/ void *mmx_cpy(void *dest, const void *src, size_t n)
-{
-#if defined (_MSC_VER) && defined (_X86_)
-	_asm
-	{
-		mov ecx, [n]
-		mov esi, [src]
-		mov edi, [dest]
-		shr ecx, 6 // mit mmx: 64bytes per iteration
-		jz lower_64 // if lower than 64 bytes
-		loop_64: // MMX transfers multiples of 64bytes
-		movq mm0,  0[ESI] // read sources
-		movq mm1,  8[ESI]
-		movq mm2, 16[ESI]
-		movq mm3, 24[ESI]
-		movq mm4, 32[ESI]
-		movq mm5, 40[ESI]
-		movq mm6, 48[ESI]
-		movq mm7, 56[ESI]
-
-		movq  0[EDI], mm0 // write destination
-		movq  8[EDI], mm1
-		movq 16[EDI], mm2
-		movq 24[EDI], mm3
-		movq 32[EDI], mm4
-		movq 40[EDI], mm5
-		movq 48[EDI], mm6
-		movq 56[EDI], mm7
-
-		add esi, 64
-		add edi, 64
-		dec ecx
-		jnz loop_64
-		emms // close mmx operation
-		lower_64:// transfer rest of buffer
-		mov ebx,esi
-		sub ebx,src
-		mov ecx,[n]
-		sub ecx,ebx
-		shr ecx, 3 // multiples of 8 bytes
-		jz lower_8
-		loop_8:
-		movq  mm0, [esi] // read source
-		movq [edi], mm0 // write destination
-		add esi, 8
-		add edi, 8
-		dec ecx
-		jnz loop_8
-		emms // close mmx operation
-		lower_8:
-		mov ebx,esi
-		sub ebx,src
-		mov ecx,[n]
-		sub ecx,ebx
-		rep movsb
-		mov eax, [dest] // return dest
-	}
-#elif defined (__GNUC__) && defined (__i386__)
-	void *retval = dest;
-	size_t i;
-
-	if (n >= MMX1_MIN_LEN)
-	{
-		register unsigned long int delta;
-		/* Align destinition to MMREG_SIZE -boundary */
-		delta = ((unsigned long int)dest)&(MMX_MMREG_SIZE-1);
-		if (delta)
-		{
-			delta=MMX_MMREG_SIZE-delta;
-			n -= delta;
-			small_memcpy(dest, src, delta);
-		}
-		i = n >> 6; /* n/64 */
-		n&=63;
-		for (; i>0; i--)
-		{
-			__asm__ __volatile__ (
-				"movq (%0), %%mm0;"
-				"movq 8(%0), %%mm1;"
-				"movq 16(%0), %%mm2;"
-				"movq 24(%0), %%mm3;"
-				"movq 32(%0), %%mm4;"
-				"movq 40(%0), %%mm5;"
-				"movq 48(%0), %%mm6;"
-				"movq 56(%0), %%mm7;"
-				"movq %%mm0, (%1);"
-				"movq %%mm1, 8(%1);"
-				"movq %%mm2, 16(%1);"
-				"movq %%mm3, 24(%1);"
-				"movq %%mm4, 32(%1);"
-				"movq %%mm5, 40(%1);"
-				"movq %%mm6, 48(%1);"
-				"movq %%mm7, 56(%1);"
-			:: "r" (src), "r" (dest) : "memory");
-			src = ((const unsigned char *)src) + 64;
-			dest = ((unsigned char *)dest) + 64;
-		}
-		__asm__ __volatile__ ("emms":::"memory");
-	}
-	/*
-	 *	Now do the tail of the block
-	 */
-	if (n) __memcpy(dest, src, n);
-	return retval;
-#else
-	return cpu_cpy(dest, src, n);
-#endif
-}
-
-void *(*M_Memcpy)(void* dest, const void* src, size_t n) = cpu_cpy;
-
-/** Memcpy that uses MMX, 3DNow, MMXExt or even SSE
-  * Do not use on overlapped memory, use memmove for that
-  */
-void M_SetupMemcpy(void)
-{
-#if defined (__GNUC__) && defined (__i386__)
-	if (R_SSE2)
-		M_Memcpy = sse_cpy;
-	else if (R_MMXExt)
-		M_Memcpy = mmx2_cpy;
-	else if (R_3DNow)
-		M_Memcpy = mmx1_cpy;
-	else
-#endif
-	if (R_MMX)
-		M_Memcpy = mmx_cpy;
-#if 0
-	M_Memcpy = cpu_cpy;
-#endif
-}
-
 /** Return the appropriate message for a file error or end of file.
 */
 const char *M_FileError(FILE *fp)
diff --git a/src/m_perfstats.c b/src/m_perfstats.c
index 1511859324059a5ce8e5836ff99dbdbe841c02a1..b9948bdc0c3284a3473e7e6e8b95b2e3bd8fb568 100644
--- a/src/m_perfstats.c
+++ b/src/m_perfstats.c
@@ -65,7 +65,10 @@ static ps_metric_t ps_removecount = {0};
 
 ps_metric_t ps_checkposition_calls = {0};
 
+ps_metric_t ps_lua_prethinkframe_time = {0};
 ps_metric_t ps_lua_thinkframe_time = {0};
+ps_metric_t ps_lua_postthinkframe_time = {0};
+
 ps_metric_t ps_lua_mobjhooks = {0};
 
 ps_metric_t ps_otherlogictime = {0};
@@ -157,7 +160,9 @@ perfstatrow_t gamelogic_rows[] = {
 	{"  mobjs  ", "  Mobjs:          ", &ps_thlist_times[THINK_MOBJ], PS_TIME|PS_LEVEL},
 	{"  dynslop", "  Dynamic slopes: ", &ps_thlist_times[THINK_DYNSLOPE], PS_TIME|PS_LEVEL},
 	{"  precip ", "  Precipitation:  ", &ps_thlist_times[THINK_PRECIP], PS_TIME|PS_LEVEL},
+	{" lprethinkf", " LUAh_PreThinkFrame:", &ps_lua_prethinkframe_time, PS_TIME|PS_LEVEL},
 	{" lthinkf", " LUAh_ThinkFrame:", &ps_lua_thinkframe_time, PS_TIME|PS_LEVEL},
+	{" lpostthinkf", " LUAh_PostThinkFrame:", &ps_lua_postthinkframe_time, PS_TIME|PS_LEVEL},
 	{" other  ", " Other:          ", &ps_otherlogictime, PS_TIME|PS_LEVEL},
 	{0}
 };
@@ -192,10 +197,43 @@ int ps_frame_index = 0;
 int ps_tick_index = 0;
 
 // dynamically allocated resizeable array for thinkframe hook stats
+ps_hookinfo_t *prethinkframe_hooks = NULL;
+int prethinkframe_hooks_length = 0;
+int prethinkframe_hooks_capacity = 16;
+
 ps_hookinfo_t *thinkframe_hooks = NULL;
 int thinkframe_hooks_length = 0;
 int thinkframe_hooks_capacity = 16;
 
+ps_hookinfo_t *postthinkframe_hooks = NULL;
+int postthinkframe_hooks_length = 0;
+int postthinkframe_hooks_capacity = 16;
+
+void PS_SetPreThinkFrameHookInfo(int index, precise_t time_taken, char* short_src)
+{
+	if (!prethinkframe_hooks)
+	{
+		// array needs to be initialized
+		prethinkframe_hooks = Z_Calloc(sizeof(ps_hookinfo_t) * prethinkframe_hooks_capacity, PU_STATIC, NULL);
+	}
+	if (index >= prethinkframe_hooks_capacity)
+	{
+		// array needs more space, realloc with double size
+		int new_capacity = prethinkframe_hooks_capacity * 2;
+		prethinkframe_hooks = Z_Realloc(prethinkframe_hooks,
+			sizeof(ps_hookinfo_t) * new_capacity, PU_STATIC, NULL);
+		// initialize new memory with zeros so the pointers in the structs are null
+		memset(&prethinkframe_hooks[prethinkframe_hooks_capacity], 0,
+			sizeof(ps_hookinfo_t) * prethinkframe_hooks_capacity);
+		prethinkframe_hooks_capacity = new_capacity;
+	}
+	prethinkframe_hooks[index].time_taken.value.p = time_taken;
+	memcpy(prethinkframe_hooks[index].short_src, short_src, LUA_IDSIZE * sizeof(char));
+	// since the values are set sequentially from begin to end, the last call should leave
+	// the correct value to this variable
+	prethinkframe_hooks_length = index + 1;
+}
+
 void PS_SetThinkFrameHookInfo(int index, precise_t time_taken, char* short_src)
 {
 	if (!thinkframe_hooks)
@@ -221,6 +259,31 @@ void PS_SetThinkFrameHookInfo(int index, precise_t time_taken, char* short_src)
 	thinkframe_hooks_length = index + 1;
 }
 
+void PS_SetPostThinkFrameHookInfo(int index, precise_t time_taken, char* short_src)
+{
+	if (!postthinkframe_hooks)
+	{
+		// array needs to be initialized
+		postthinkframe_hooks = Z_Calloc(sizeof(ps_hookinfo_t) * postthinkframe_hooks_capacity, PU_STATIC, NULL);
+	}
+	if (index >= postthinkframe_hooks_capacity)
+	{
+		// array needs more space, realloc with double size
+		int new_capacity = postthinkframe_hooks_capacity * 2;
+		postthinkframe_hooks = Z_Realloc(postthinkframe_hooks,
+			sizeof(ps_hookinfo_t) * new_capacity, PU_STATIC, NULL);
+		// initialize new memory with zeros so the pointers in the structs are null
+		memset(&postthinkframe_hooks[postthinkframe_hooks_capacity], 0,
+			sizeof(ps_hookinfo_t) * postthinkframe_hooks_capacity);
+		postthinkframe_hooks_capacity = new_capacity;
+	}
+	postthinkframe_hooks[index].time_taken.value.p = time_taken;
+	memcpy(postthinkframe_hooks[index].short_src, short_src, LUA_IDSIZE * sizeof(char));
+	// since the values are set sequentially from begin to end, the last call should leave
+	// the correct value to this variable
+	postthinkframe_hooks_length = index + 1;
+}
+
 static boolean PS_HighResolution(void)
 {
 	return (vid.width >= 640 && vid.height >= 400);
@@ -564,7 +627,9 @@ void PS_UpdateTickStats(void)
 				ps_tictime.value.p -
 				ps_playerthink_time.value.p -
 				ps_thinkertime.value.p -
-				ps_lua_thinkframe_time.value.p;
+				ps_lua_prethinkframe_time.value.p -
+				ps_lua_thinkframe_time.value.p -
+				ps_lua_postthinkframe_time.value.p;
 
 			PS_CountThinkers();
 		}
@@ -576,21 +641,35 @@ void PS_UpdateTickStats(void)
 			PS_UpdateRowHistories(misc_calls_rows, false);
 		}
 	}
-	if (cv_perfstats.value == 3 && cv_ps_samplesize.value > 1 && PS_IsLevelActive())
+	if (cv_ps_samplesize.value > 1)
 	{
-		int i;
-		for (i = 0; i < thinkframe_hooks_length; i++)
+		if(cv_perfstats.value >= 3 && PS_IsLevelActive())
 		{
-			PS_UpdateMetricHistory(&thinkframe_hooks[i].time_taken, true, false, false);
+			int i;
+			if (cv_perfstats.value == 3)
+			{
+				for (i = 0; i < thinkframe_hooks_length; i++)
+					PS_UpdateMetricHistory(&thinkframe_hooks[i].time_taken, true, false, false);
+			}
+			else if (cv_perfstats.value == 4)
+			{
+				for (i = 0; i < prethinkframe_hooks_length; i++)
+					PS_UpdateMetricHistory(&prethinkframe_hooks[i].time_taken, true, false, false);
+			}
+			else if (cv_perfstats.value == 5)
+			{
+				for (i = 0; i < postthinkframe_hooks_length; i++)
+					PS_UpdateMetricHistory(&postthinkframe_hooks[i].time_taken, true, false, false);
+			}
+		}
+		if (cv_perfstats.value)
+		{
+			ps_tick_index++;
+			if (ps_tick_index >= cv_ps_samplesize.value)
+				ps_tick_index = 0;
+			if (ps_tick_samples_left)
+				ps_tick_samples_left--;
 		}
-	}
-	if (cv_perfstats.value && cv_ps_samplesize.value > 1)
-	{
-		ps_tick_index++;
-		if (ps_tick_index >= cv_ps_samplesize.value)
-			ps_tick_index = 0;
-		if (ps_tick_samples_left)
-			ps_tick_samples_left--;
 	}
 }
 
@@ -610,7 +689,7 @@ static void PS_DrawDescriptorHeader(void)
 		int samples_left = max(ps_frame_samples_left, ps_tick_samples_left);
 		int x, y;
 
-		if (cv_perfstats.value == 3)
+		if (cv_perfstats.value >= 3)
 		{
 			x = 2;
 			y = 0;
@@ -697,7 +776,7 @@ static void PS_DrawGameLogicStats(void)
 	PS_DrawPerfRows(x, y, V_PURPLEMAP, misc_calls_rows);
 }
 
-static void PS_DrawThinkFrameStats(void)
+static void draw_think_frame_stats(int hook_length, ps_hookinfo_t *hook)
 {
 	char s[100];
 	int i;
@@ -711,7 +790,7 @@ static void PS_DrawThinkFrameStats(void)
 
 	PS_DrawDescriptorHeader();
 
-	for (i = 0; i < thinkframe_hooks_length; i++)
+	for (i = 0; i < hook_length; i++)
 	{
 
 #define NEXT_ROW() \
@@ -724,7 +803,7 @@ if (y > 192) \
 		break; \
 }
 
-		char* str = thinkframe_hooks[i].short_src;
+		char* str = hook[i].short_src;
 		char* tempstr = tempbuffer;
 		int len = (int)strlen(str);
 		char* str_ptr;
@@ -771,7 +850,7 @@ if (y > 192) \
 		if (len > 20)
 			str += len - 20;
 		snprintf(s, sizeof s - 1, "%20s: %d", str,
-				PS_GetMetricScreenValue(&thinkframe_hooks[i].time_taken, true));
+				PS_GetMetricScreenValue(&hook[i].time_taken, true));
 		V_DrawSmallString(x, y, V_MONOSPACE | V_ALLOWLOWERCASE | text_color, s);
 		NEXT_ROW()
 
@@ -780,6 +859,21 @@ if (y > 192) \
 	}
 }
 
+static void PS_DrawPreThinkFrameStats(void)
+{
+	draw_think_frame_stats(prethinkframe_hooks_length, prethinkframe_hooks);
+}
+
+static void PS_DrawThinkFrameStats(void)
+{
+	draw_think_frame_stats(thinkframe_hooks_length, thinkframe_hooks);
+}
+
+static void PS_DrawPostThinkFrameStats(void)
+{
+	draw_think_frame_stats(postthinkframe_hooks_length, postthinkframe_hooks);
+}
+
 void M_DrawPerfStats(void)
 {
 	if (cv_perfstats.value == 1) // rendering
@@ -793,7 +887,7 @@ void M_DrawPerfStats(void)
 		// tics when frame skips happen
 		PS_DrawGameLogicStats();
 	}
-	else if (cv_perfstats.value == 3) // lua thinkframe
+	else if (cv_perfstats.value >= 3) // lua thinkframe
 	{
 		if (!PS_IsLevelActive())
 			return;
@@ -802,13 +896,22 @@ void M_DrawPerfStats(void)
 			// Low resolutions can't really use V_DrawSmallString that is used by thinkframe stats.
 			// A low-res version using V_DrawThinString could be implemented,
 			// but it would have much less space for information.
-			V_DrawThinString(80, 92, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, "Perfstats 3 is not available");
+			V_DrawThinString(80, 92, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, "Lua Perfstats is not available");
 			V_DrawThinString(80, 100, V_MONOSPACE | V_ALLOWLOWERCASE | V_YELLOWMAP, "for resolutions below 640x400.");
+			return;
 		}
-		else
+		if (cv_perfstats.value == 3)
 		{
 			PS_DrawThinkFrameStats();
 		}
+		else if (cv_perfstats.value == 4)
+		{
+			PS_DrawPreThinkFrameStats();
+		}
+		else if (cv_perfstats.value == 5)
+		{
+			PS_DrawPostThinkFrameStats();
+		}
 	}
 }
 
diff --git a/src/m_perfstats.h b/src/m_perfstats.h
index db250be2a273a970d9209d343f16f948d7d53231..592ab31d2d62adac2704b740e40f5476158e38c6 100644
--- a/src/m_perfstats.h
+++ b/src/m_perfstats.h
@@ -43,12 +43,16 @@ extern ps_metric_t ps_thlist_times[];
 
 extern ps_metric_t ps_checkposition_calls;
 
+extern ps_metric_t ps_lua_prethinkframe_time;
 extern ps_metric_t ps_lua_thinkframe_time;
+extern ps_metric_t ps_lua_postthinkframe_time;
 extern ps_metric_t ps_lua_mobjhooks;
 
 extern ps_metric_t ps_otherlogictime;
 
+void PS_SetPreThinkFrameHookInfo(int index, precise_t time_taken, char* short_src);
 void PS_SetThinkFrameHookInfo(int index, precise_t time_taken, char* short_src);
+void PS_SetPostThinkFrameHookInfo(int index, precise_t time_taken, char* short_src);
 
 void PS_UpdateTickStats(void);
 
diff --git a/src/netcode/commands.c b/src/netcode/commands.c
index 4b67198babe4157aefa615fc0fb3b319f5f1b47b..e7d51437e42d381827a3e702f9c49a8f9d1780b2 100644
--- a/src/netcode/commands.c
+++ b/src/netcode/commands.c
@@ -79,7 +79,7 @@ static void Ban_Clear(void)
 void Ban_Load_File(boolean warning)
 {
 	FILE *f;
-	const char *address, *mask;
+	char *address, *mask;
 	char buffer[MAX_WADPATH];
 
 	if (!I_ClearBans)
@@ -100,6 +100,14 @@ void Ban_Load_File(boolean warning)
 	{
 		address = strtok(buffer, " \t\r\n");
 		mask = strtok(NULL, " \t\r\n");
+		if (address[0] == '[')
+		{
+			size_t len;
+			address++;
+			len = strlen(address);
+			if (address[len-1] == ']')
+				address[len-1] = '\0';
+		}
 
 		I_SetBanAddress(address, mask);
 
diff --git a/src/netcode/d_clisrv.c b/src/netcode/d_clisrv.c
index 7804b068f7f8d262bd7fab535539af82675bd639..d84759b4236eb8de12102783c4422fe8b5057c78 100644
--- a/src/netcode/d_clisrv.c
+++ b/src/netcode/d_clisrv.c
@@ -90,8 +90,8 @@ INT16 consistancy[BACKUPTICS];
 // true when a player is connecting or disconnecting so that the gameplay has stopped in its tracks
 boolean hu_stopped = false;
 
-UINT8 adminpassmd5[16];
-boolean adminpasswordset = false;
+UINT8 (*adminpassmd5)[16];
+UINT32 adminpasscount = 0;
 
 tic_t neededtic;
 SINT8 servernode = 0; // the number of the server node
@@ -862,26 +862,31 @@ static void PT_Login(SINT8 node, INT32 netconsole)
 
 #ifndef NOMD5
 	UINT8 finalmd5[16];/* Well, it's the cool thing to do? */
+	UINT32 i;
 
 	if (doomcom->datalength < 16)/* ignore partial sends */
 		return;
 
-	if (!adminpasswordset)
+	if (adminpasscount == 0)
 	{
 		CONS_Printf(M_GetText("Password from %s failed (no password set).\n"), player_names[netconsole]);
 		return;
 	}
 
-	// Do the final pass to compare with the sent md5
-	D_MD5PasswordPass(adminpassmd5, 16, va("PNUM%02d", netconsole), &finalmd5);
-
-	if (!memcmp(netbuffer->u.md5sum, finalmd5, 16))
+	for (i = 0; i < adminpasscount; i++)
 	{
-		CONS_Printf(M_GetText("%s passed authentication.\n"), player_names[netconsole]);
-		COM_BufInsertText(va("promote %d\n", netconsole)); // do this immediately
+		// Do the final pass to compare with the sent md5
+		D_MD5PasswordPass(adminpassmd5[i], 16, va("PNUM%02d", netconsole), &finalmd5);
+
+		if (!memcmp(netbuffer->u.md5sum, finalmd5, 16))
+		{
+			CONS_Printf(M_GetText("%s passed authentication.\n"), player_names[netconsole]);
+			COM_BufInsertText(va("promote %d\n", netconsole)); // do this immediately
+			return;
+		}
 	}
-	else
-		CONS_Printf(M_GetText("Password from %s failed.\n"), player_names[netconsole]);
+
+	CONS_Printf(M_GetText("Password from %s failed.\n"), player_names[netconsole]);
 #else
 	(void)netconsole;
 #endif
@@ -1512,7 +1517,7 @@ void NetUpdate(void)
 
 	nowtime /= NEWTICRATERATIO;
 
-	if (nowtime > resptime)
+	if (nowtime != resptime)
 	{
 		resptime = nowtime;
 #ifdef HAVE_THREADS
@@ -1618,6 +1623,17 @@ INT32 D_NumPlayers(void)
 	return num;
 }
 
+/** Returns the number of nodes on the server.
+  */
+INT32 D_NumNodes(void)
+{
+	INT32 num = 0;
+	for (INT32 ix = 0; ix < MAXNETNODES; ix++)
+		if (netnodes[ix].ingame)
+			num++;
+	return num;
+}
+
 /** Similar to the above, but counts only bots.
   * Purpose is to remove bots from both the player count and the
   * max player count on the server view
diff --git a/src/netcode/d_clisrv.h b/src/netcode/d_clisrv.h
index db9a780cd915bf38065391d3b0030c09c60bda4e..f9cbf8876833486c232e7f51bbf8ae699ba6bd96 100644
--- a/src/netcode/d_clisrv.h
+++ b/src/netcode/d_clisrv.h
@@ -121,14 +121,15 @@ extern char motd[254], server_context[8];
 extern UINT8 playernode[MAXPLAYERS];
 
 INT32 D_NumPlayers(void);
+INT32 D_NumNodes(void);
 INT32 D_NumBots(void);
 
 tic_t GetLag(INT32 node);
 
 void D_MD5PasswordPass(const UINT8 *buffer, size_t len, const char *salt, void *dest);
 
-extern UINT8 adminpassmd5[16];
-extern boolean adminpasswordset;
+extern UINT8 (*adminpassmd5)[16];
+extern UINT32 adminpasscount;
 
 extern boolean hu_stopped;
 
diff --git a/src/netcode/d_net.c b/src/netcode/d_net.c
index cfb1963b9f6c2dd4699321470676bf78354cec21..5a2e229d3f47c9eadf66729a1250a1d2b0719343 100644
--- a/src/netcode/d_net.c
+++ b/src/netcode/d_net.c
@@ -313,9 +313,9 @@ static void RemoveAck(INT32 i)
 }
 
 // We have got a packet, proceed the ack request and ack return
-static boolean Processackpak(void)
+static int Processackpak(void)
 {
-	boolean goodpacket = true;
+	int goodpacket = 0;
 	node_t *node = &nodes[doomcom->remotenode];
 
 	// Received an ack return, so remove the ack in the list
@@ -340,7 +340,7 @@ static boolean Processackpak(void)
 		{
 			DEBFILE(va("Discard(1) ack %d (duplicated)\n", ack));
 			duppacket++;
-			goodpacket = false; // Discard packet (duplicate)
+			goodpacket = 1; // Discard packet (duplicate)
 		}
 		else
 		{
@@ -350,10 +350,10 @@ static boolean Processackpak(void)
 				{
 					DEBFILE(va("Discard(2) ack %d (duplicated)\n", ack));
 					duppacket++;
-					goodpacket = false; // Discard packet (duplicate)
+					goodpacket = 1; // Discard packet (duplicate)
 					break;
 				}
-			if (goodpacket)
+			if (goodpacket == 0)
 			{
 				// Is a good packet so increment the acknowledge number,
 				// Then search for a "hole" in the queue
@@ -414,12 +414,13 @@ static boolean Processackpak(void)
 					else // Buffer full discard packet, sender will resend it
 					{ // We can admit the packet but we will not detect the duplication after :(
 						DEBFILE("no more freeackret\n");
-						goodpacket = false;
+						goodpacket = 2;
 					}
 				}
 			}
 		}
 	}
+	// return values: 0 = ok, 1 = duplicate, 2 = out of order
 	return goodpacket;
 }
 
@@ -1069,6 +1070,7 @@ boolean HGetPacket(void)
 	while(true)
 	{
 		//nodejustjoined = I_NetGet();
+		int goodpacket;
 		I_NetGet();
 
 		if (doomcom->remotenode == -1) // No packet received
@@ -1114,8 +1116,15 @@ boolean HGetPacket(void)
 		}*/
 
 		// Proceed the ack and ackreturn field
-		if (!Processackpak())
+		goodpacket = Processackpak();
+		if (goodpacket != 0)
+		{
+			// resend the ACK in case the previous ACK didn't reach the client.
+			// prevents the client's netbuffer from locking up.
+			if (goodpacket == 1)
+				Net_SendAcks(doomcom->remotenode);
 			continue; // discarded (duplicated)
+		}
 
 		// A packet with just ackreturn
 		if (netbuffer->packettype == PT_NOTHING)
diff --git a/src/netcode/d_net.h b/src/netcode/d_net.h
index 549f2b93c18dc86222e39d359d05ff2e5763ba27..2f83939f8c5324883eb11e90dbb4e2897a54254c 100644
--- a/src/netcode/d_net.h
+++ b/src/netcode/d_net.h
@@ -70,6 +70,7 @@ boolean HGetPacket(void);
 void D_SetDoomcom(void);
 boolean D_CheckNetGame(void);
 void D_CloseConnection(void);
+boolean Net_IsNodeIPv6(INT32 node);
 void Net_UnAcknowledgePacket(INT32 node);
 void Net_CloseConnection(INT32 node);
 void Net_ConnectionTimeout(INT32 node);
diff --git a/src/netcode/d_netcmd.c b/src/netcode/d_netcmd.c
index 18463cf5594e2bf856d9cc3448b314d5695f402a..5970db0d7b382d392993c55b6028ca096b79b1db 100644
--- a/src/netcode/d_netcmd.c
+++ b/src/netcode/d_netcmd.c
@@ -150,6 +150,7 @@ static void Command_Clearscores_f(void);
 
 // Remote Administration
 static void Command_Changepassword_f(void);
+static void Command_Clearpassword_f(void);
 static void Command_Login_f(void);
 static void Got_Verification(UINT8 **cp, INT32 playernum);
 static void Got_Removal(UINT8 **cp, INT32 playernum);
@@ -379,7 +380,7 @@ consvar_t cv_mute = CVAR_INIT ("mute", "Off", CV_NETVAR|CV_CALL|CV_ALLOWLUA, CV_
 consvar_t cv_sleep = CVAR_INIT ("cpusleep", "1", CV_SAVE, sleeping_cons_t, NULL);
 
 static CV_PossibleValue_t perfstats_cons_t[] = {
-	{0, "Off"}, {1, "Rendering"}, {2, "Logic"}, {3, "ThinkFrame"}, {0, NULL}};
+	{0, "Off"}, {1, "Rendering"}, {2, "Logic"}, {3, "ThinkFrame"}, {4, "PreThinkFrame"}, {5, "PostThinkFrame"}, {0, NULL}};
 consvar_t cv_perfstats = CVAR_INIT ("perfstats", "Off", CV_CALL, perfstats_cons_t, PS_PerfStats_OnChange);
 static CV_PossibleValue_t ps_samplesize_cons_t[] = {
 	{1, "MIN"}, {1000, "MAX"}, {0, NULL}};
@@ -468,6 +469,7 @@ void D_RegisterServerCommands(void)
 
 	// Remote Administration
 	COM_AddCommand("password", Command_Changepassword_f, COM_LUA);
+	COM_AddCommand("clearpassword", Command_Clearpassword_f, COM_LUA);
 	COM_AddCommand("login", Command_Login_f, COM_LUA); // useful in dedicated to kick off remote admin
 	COM_AddCommand("promote", Command_Verify_f, COM_LUA);
 	RegisterNetXCmd(XD_VERIFIED, Got_Verification);
@@ -620,6 +622,10 @@ void D_RegisterServerCommands(void)
 	CV_RegisterVar(&cv_addons_folder);
 
 	CV_RegisterVar(&cv_dummyconsvar);
+
+	CV_RegisterVar(&cv_chatspamprotection);
+	CV_RegisterVar(&cv_chatspamspeed);
+	CV_RegisterVar(&cv_chatspamburst);
 }
 
 // =========================================================================
@@ -763,7 +769,6 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_chatheight);
 	CV_RegisterVar(&cv_chatwidth);
 	CV_RegisterVar(&cv_chattime);
-	CV_RegisterVar(&cv_chatspamprotection);
 	CV_RegisterVar(&cv_chatbacktint);
 	CV_RegisterVar(&cv_consolechat);
 	CV_RegisterVar(&cv_chatnotifications);
@@ -2841,8 +2846,15 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
 
 void D_SetPassword(const char *pw)
 {
-	D_MD5PasswordPass((const UINT8 *)pw, strlen(pw), BASESALT, &adminpassmd5);
-	adminpasswordset = true;
+	adminpassmd5 = Z_Realloc(adminpassmd5, sizeof(*adminpassmd5) * ++adminpasscount, PU_STATIC, NULL);
+	D_MD5PasswordPass((const UINT8 *)pw, strlen(pw), BASESALT, &adminpassmd5[adminpasscount-1]);
+}
+
+void D_ClearPassword(void)
+{
+	Z_Free(adminpassmd5);
+	adminpassmd5 = NULL;
+	adminpasscount = 0;
 }
 
 // Remote Administration
@@ -2860,12 +2872,30 @@ static void Command_Changepassword_f(void)
 
 	if (COM_Argc() != 2)
 	{
-		CONS_Printf(M_GetText("password <password>: change remote admin password\n"));
+		CONS_Printf(M_GetText("password <password>: add remote admin password\n"));
 		return;
 	}
 
 	D_SetPassword(COM_Argv(1));
-	CONS_Printf(M_GetText("Password set.\n"));
+	CONS_Printf(M_GetText("Password added.\n"));
+#endif
+}
+
+// Remote Administration
+static void Command_Clearpassword_f(void)
+{
+#ifdef NOMD5
+	// If we have no MD5 support then completely disable XD_LOGIN responses for security.
+	CONS_Alert(CONS_NOTICE, "Remote administration commands are not supported in this build.\n");
+#else
+	if (client) // cannot change remotely
+	{
+		CONS_Printf(M_GetText("Only the server can use this.\n"));
+		return;
+	}
+
+	D_ClearPassword();
+	CONS_Printf(M_GetText("Passwords cleared.\n"));
 #endif
 }
 
@@ -3641,11 +3671,11 @@ static void Got_RequestAddfoldercmd(UINT8 **cp, INT32 playernum)
 
 static void Got_Addfilecmd(UINT8 **cp, INT32 playernum)
 {
-	char filename[241];
+	char filename[MAX_WADPATH+1];
 	filestatus_t ncs = FS_NOTCHECKED;
 	UINT8 md5sum[16];
 
-	READSTRINGN(*cp, filename, 240);
+	READSTRINGN(*cp, filename, MAX_WADPATH);
 	READMEM(*cp, md5sum, 16);
 
 	if (playernum != serverplayer)
diff --git a/src/netcode/d_netcmd.h b/src/netcode/d_netcmd.h
index 4849079d0ce622ae6388dfdec9984686c963b614..0f2a1f92ba93c95e16d95afd0cf179cf625c0b5c 100644
--- a/src/netcode/d_netcmd.h
+++ b/src/netcode/d_netcmd.h
@@ -209,6 +209,7 @@ void ClearAdminPlayers(void);
 void RemoveAdminPlayer(INT32 playernum);
 void ItemFinder_OnChange(void);
 void D_SetPassword(const char *pw);
+void D_ClearPassword(void);
 
 // used for the player setup menu
 UINT8 CanChangeSkin(INT32 playernum);
diff --git a/src/netcode/http-mserv.c b/src/netcode/http-mserv.c
index f8bd5da218efc57bb3384d2f9375919760254817..10137e67b042cef0cd192d00d7d4fcbb62d5b842 100644
--- a/src/netcode/http-mserv.c
+++ b/src/netcode/http-mserv.c
@@ -35,6 +35,10 @@ Documentation available here.
 #define Blame( ... ) \
 	CONS_Printf("\x85" __VA_ARGS__)
 
+#define PROTO_ANY 0
+#define PROTO_V4 1
+#define PROTO_V6 2
+
 static void MasterServer_Debug_OnChange (void);
 
 consvar_t cv_masterserver_timeout = CVAR_INIT
@@ -59,12 +63,17 @@ consvar_t cv_masterserver_token = CVAR_INIT
 
 static int hms_started;
 
+static boolean hms_allow_ipv6;
+
 static char *hms_api;
 #ifdef HAVE_THREADS
 static I_mutex hms_api_mutex;
 #endif
 
 static char *hms_server_token;
+#ifndef NO_IPV6
+static char *hms_server_token_ipv6;
+#endif
 
 static char hms_useragent[512];
 
@@ -126,7 +135,7 @@ HMS_on_read (char *s, size_t _1, size_t n, void *userdata)
 }
 
 static struct HMS_buffer *
-HMS_connect (const char *format, ...)
+HMS_connect (int proto, const char *format, ...)
 {
 	va_list ap;
 	CURL *curl;
@@ -136,8 +145,14 @@ HMS_connect (const char *format, ...)
 	size_t token_length;
 	struct HMS_buffer *buffer;
 
+#ifdef NO_IPV6
+	if (proto == PROTO_V6)
+		return NULL;
+#endif
+
 	if (! hms_started)
 	{
+		hms_allow_ipv6 = !M_CheckParm("-noipv6");
 		if (curl_global_init(CURL_GLOBAL_ALL) != 0)
 		{
 			Contact_error();
@@ -219,7 +234,9 @@ HMS_connect (const char *format, ...)
 	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
 
 #ifndef NO_IPV6
-	if (M_CheckParm("-noipv6"))
+	if (proto == PROTO_V6)
+		curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
+	if (proto == PROTO_V4)
 #endif
 		curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
 
@@ -309,7 +326,7 @@ HMS_fetch_rooms (int joining, int query_id)
 
 	(void)query_id;
 
-	hms = HMS_connect("rooms");
+	hms = HMS_connect(PROTO_ANY, "rooms");
 
 	if (! hms)
 		return 0;
@@ -409,7 +426,7 @@ HMS_register (void)
 
 	char *title;
 
-	hms = HMS_connect("rooms/%d/register", ms_RoomId);
+	hms = HMS_connect(PROTO_V4, "rooms/%d/register", ms_RoomId);
 
 	if (! hms)
 		return 0;
@@ -441,6 +458,27 @@ HMS_register (void)
 
 	HMS_end(hms);
 
+#ifndef NO_IPV6
+	if (!hms_allow_ipv6)
+		return ok;
+
+	hms = HMS_connect(PROTO_V6, "rooms/%d/register", ms_RoomId);
+
+	if (! hms)
+		return 0;
+
+	curl_easy_setopt(hms->curl, CURLOPT_POSTFIELDS, post);
+
+	ok = HMS_do(hms);
+
+	if (ok)
+	{
+		hms_server_token_ipv6 = strdup(strtok(hms->buffer, "\n"));
+	}
+
+	HMS_end(hms);
+#endif
+
 	return ok;
 }
 
@@ -450,7 +488,7 @@ HMS_unlist (void)
 	struct HMS_buffer *hms;
 	int ok;
 
-	hms = HMS_connect("servers/%s/unlist", hms_server_token);
+	hms = HMS_connect(PROTO_V4, "servers/%s/unlist", hms_server_token);
 
 	if (! hms)
 		return 0;
@@ -462,6 +500,23 @@ HMS_unlist (void)
 
 	free(hms_server_token);
 
+#ifndef NO_IPV6
+	if (hms_server_token_ipv6 && hms_allow_ipv6)
+	{
+		hms = HMS_connect(PROTO_V6, "servers/%s/unlist", hms_server_token_ipv6);
+
+		if (! hms)
+			return 0;
+
+		curl_easy_setopt(hms->curl, CURLOPT_CUSTOMREQUEST, "POST");
+
+		ok = HMS_do(hms);
+		HMS_end(hms);
+
+		free(hms_server_token_ipv6);
+	}
+#endif
+
 	return ok;
 }
 
@@ -475,7 +530,7 @@ HMS_update (void)
 
 	char *title;
 
-	hms = HMS_connect("servers/%s/update", hms_server_token);
+	hms = HMS_connect(PROTO_V4, "servers/%s/update", hms_server_token);
 
 	if (! hms)
 		return 0;
@@ -494,6 +549,21 @@ HMS_update (void)
 	ok = HMS_do(hms);
 	HMS_end(hms);
 
+#ifndef NO_IPV6
+	if (hms_server_token_ipv6 && hms_allow_ipv6)
+	{
+		hms = HMS_connect(PROTO_V6, "servers/%s/update", hms_server_token_ipv6);
+
+		if (! hms)
+			return ok;
+
+		curl_easy_setopt(hms->curl, CURLOPT_POSTFIELDS, post);
+
+		ok = HMS_do(hms);
+		HMS_end(hms);
+	}
+#endif
+
 	return ok;
 }
 
@@ -505,7 +575,7 @@ HMS_list_servers (void)
 	char *list;
 	char *p;
 
-	hms = HMS_connect("servers");
+	hms = HMS_connect(PROTO_ANY, "servers");
 
 	if (! hms)
 		return;
@@ -554,10 +624,10 @@ HMS_fetch_servers (msg_server_t *list, int room_number, int query_id)
 
 	if (room_number > 0)
 	{
-		hms = HMS_connect("rooms/%d/servers", room_number);
+		hms = HMS_connect(PROTO_ANY, "rooms/%d/servers", room_number);
 	}
 	else
-		hms = HMS_connect("servers");
+		hms = HMS_connect(PROTO_ANY, "servers");
 
 	if (! hms)
 		return NULL;
@@ -661,7 +731,7 @@ HMS_compare_mod_version (char *buffer, size_t buffer_size)
 	char *version;
 	char *version_name;
 
-	hms = HMS_connect("versions/%d", MODID);
+	hms = HMS_connect(PROTO_ANY, "versions/%d", MODID);
 
 	if (! hms)
 		return 0;
diff --git a/src/netcode/i_tcp.c b/src/netcode/i_tcp.c
index 0b650de49eaa430a1b5563fb83bd971f88644c48..2f996cd2923c77501302b5261526ed74aa7d8100 100644
--- a/src/netcode/i_tcp.c
+++ b/src/netcode/i_tcp.c
@@ -83,6 +83,10 @@
 	#undef ETIMEDOUT
 	#endif
 	#define ETIMEDOUT WSAETIMEDOUT
+	#ifdef EHOSTUNREACH
+	#undef EHOSTUNREACH
+	#endif
+	#define EHOSTUNREACH WSAEHOSTUNREACH
 	#ifndef IOC_VENDOR
 	#define IOC_VENDOR 0x18000000
 	#endif
@@ -404,6 +408,20 @@ static const char *SOCK_GetBanMask(size_t ban)
 	return NULL;
 }
 
+#ifdef HAVE_IPV6
+static boolean SOCK_cmpipv6(mysockaddr_t *a, mysockaddr_t *b, UINT8 mask)
+{
+	UINT8 bitmask;
+	I_Assert(mask <= 128);
+	if (memcmp(&a->ip6.sin6_addr, &b->ip6.sin6_addr, mask / 8) != 0)
+		return false;
+	if (mask % 8 == 0)
+		return true;
+	bitmask = 255 << (mask % 8);
+	return (a->ip6.sin6_addr.s6_addr[mask / 8] & bitmask) == (b->ip6.sin6_addr.s6_addr[mask / 8] & bitmask);
+}
+#endif
+
 static boolean SOCK_cmpaddr(mysockaddr_t *a, mysockaddr_t *b, UINT8 mask)
 {
 	UINT32 bitmask = INADDR_NONE;
@@ -416,7 +434,7 @@ static boolean SOCK_cmpaddr(mysockaddr_t *a, mysockaddr_t *b, UINT8 mask)
 			&& (b->ip4.sin_port == 0 || (a->ip4.sin_port == b->ip4.sin_port));
 #ifdef HAVE_IPV6
 	else if (b->any.sa_family == AF_INET6)
-		return !memcmp(&a->ip6.sin6_addr, &b->ip6.sin6_addr, sizeof(b->ip6.sin6_addr))
+		return SOCK_cmpipv6(a, b, mask)
 			&& (b->ip6.sin6_port == 0 || (a->ip6.sin6_port == b->ip6.sin6_port));
 #endif
 	else
@@ -678,7 +696,7 @@ static void SOCK_Send(void)
 	if (c == ERRSOCKET)
 	{
 		int e = errno; // save error code so it can't be modified later
-		if (e != ECONNREFUSED && e != EWOULDBLOCK)
+		if (e != ECONNREFUSED && e != EWOULDBLOCK && e != EHOSTUNREACH)
 			I_Error("SOCK_Send, error sending to node %d (%s) #%u: %s", doomcom->remotenode,
 				SOCK_GetNodeAddress(doomcom->remotenode), e, strerror(e));
 	}
@@ -1362,4 +1380,13 @@ boolean I_InitTcpNetwork(void)
 	return ret;
 }
 
+boolean Net_IsNodeIPv6(INT32 node)
+{
+#ifdef NO_IPV6
+	return false;
+#else
+	return clientaddress[node].any.sa_family == AF_INET6;
+#endif
+}
+
 #include "i_addrinfo.c"
diff --git a/src/netcode/mserv.c b/src/netcode/mserv.c
index 1c7f3e08d4fa67094657de7887d635b67ba0708f..6d6fac26faed55e4b7bfa7512bb16ddab8ee1cfb 100644
--- a/src/netcode/mserv.c
+++ b/src/netcode/mserv.c
@@ -60,7 +60,7 @@ static CV_PossibleValue_t masterserver_update_rate_cons_t[] = {
 	{0,NULL}
 };
 
-consvar_t cv_masterserver = CVAR_INIT ("masterserver", "https://mb.srb2.org/MS/0", CV_SAVE|CV_CALL, NULL, MasterServer_OnChange);
+consvar_t cv_masterserver = CVAR_INIT ("masterserver", "https://ds.ms.srb2.org/MS/0", CV_SAVE|CV_CALL, NULL, MasterServer_OnChange);
 consvar_t cv_servername = CVAR_INIT ("servername", "SRB2 server", CV_SAVE|CV_NETVAR|CV_CALL|CV_NOINIT|CV_ALLOWLUA, NULL, Update_parameters);
 
 consvar_t cv_masterserver_update_rate = CVAR_INIT ("masterserver_update_rate", "15", CV_SAVE|CV_CALL|CV_NOINIT, masterserver_update_rate_cons_t, Update_parameters);
@@ -539,6 +539,13 @@ static void MasterServer_OnChange(void)
 		CV_StealthSet(&cv_masterserver, cv_masterserver.defaultvalue);
 	}
 
+	if (
+			! cv_masterserver.changed &&
+			strcmp(cv_masterserver.string, "https://mb.srb2.org/MS/0") == 0
+	){
+		CV_StealthSet(&cv_masterserver, cv_masterserver.defaultvalue);
+	}
+
 	Set_api(cv_masterserver.string);
 
 	if (Online())
diff --git a/src/netcode/server_connection.c b/src/netcode/server_connection.c
index 2164f411a227527dba862e80bdc48e1019f5b526..bfbe30a08ed89aba1af4bb5ef6f2a51448d00c1c 100644
--- a/src/netcode/server_connection.c
+++ b/src/netcode/server_connection.c
@@ -109,7 +109,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
 	netbuffer->u.serverinfo.leveltime = (tic_t)LONG(leveltime);
 
 	// Exclude bots from both counts
-	netbuffer->u.serverinfo.numberofplayer = (UINT8)(D_NumPlayers() - D_NumBots());
+	netbuffer->u.serverinfo.numberofplayer = (UINT8)(D_NumNodes() - (dedicated ? 1 : 0));
 	netbuffer->u.serverinfo.maxplayer = (UINT8)(cv_maxplayers.value - D_NumBots());
 
 	netbuffer->u.serverinfo.refusereason = GetRefuseReason(node);
@@ -164,7 +164,7 @@ static void SV_SendPlayerInfo(INT32 node)
 
 	for (UINT8 i = 0; i < MAXPLAYERS; i++)
 	{
-		if (!playeringame[i])
+		if (playernode[i] == UINT8_MAX || !netnodes[playernode[i]].ingame)
 		{
 			netbuffer->u.playerinfo[i].num = 255; // This slot is empty.
 			continue;
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 1f001c652374f1e35643fc6f24055213f72eaee0..fa4e0543c8a398966b91171a201fec20e011cd18 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -1201,7 +1201,8 @@ static void P_SharpDust(mobj_t *actor, mobjtype_t type, angle_t ang)
 			-P_ReturnThrustX(actor, ang, 16<<FRACBITS),
 			-P_ReturnThrustY(actor, ang, 16<<FRACBITS),
 			0, type);
-	P_SetObjectMomZ(dust, P_RandomRange(1, 4)<<FRACBITS, false);
+	if (!P_MobjWasRemoved(dust))
+		P_SetObjectMomZ(dust, P_RandomRange(1, 4)<<FRACBITS, false);
 }
 
 static void P_FaceStabFlume(mobj_t *actor)
@@ -1215,6 +1216,9 @@ static void P_FaceStabFlume(mobj_t *actor)
 		-P_ReturnThrustY(actor, actor->angle, actor->radius),
 		actor->height/3,
 		MT_PARTICLE);
+	if (P_MobjWasRemoved(flume))
+		return;
+
 	flume->destscale = actor->scale*3;
 	P_SetScale(flume, flume->destscale);
 	P_SetTarget(&flume->target, actor);
@@ -1329,12 +1333,15 @@ void A_FaceStabHurl(mobj_t *actor)
 				{
 					if (!hwork->hnext)
 						P_SetTarget(&hwork->hnext, P_SpawnMobjFromMobj(actor, 0, 0, 0, MT_FACESTABBERSPEAR));
-					hwork = hwork->hnext;
-					hwork->angle = actor->angle + ANGLE_90;
-					hwork->destscale = FixedSqrt(step*basesize);
-					P_SetScale(hwork, hwork->destscale);
-					hwork->fuse = 2;
-					P_MoveOrigin(hwork, actor->x + xo*(15-step), actor->y + yo*(15-step), actor->z + (actor->height - hwork->height)/2 + (P_MobjFlip(actor)*(8<<FRACBITS)));
+					if (!P_MobjWasRemoved(hwork->hnext))
+					{
+						hwork = hwork->hnext;
+						hwork->angle = actor->angle + ANGLE_90;
+						hwork->destscale = FixedSqrt(step*basesize);
+						P_SetScale(hwork, hwork->destscale);
+						hwork->fuse = 2;
+						P_MoveOrigin(hwork, actor->x + xo*(15-step), actor->y + yo*(15-step), actor->z + (actor->height - hwork->height)/2 + (P_MobjFlip(actor)*(8<<FRACBITS)));
+					}
 					step -= NUMGRADS;
 				}
 
@@ -1438,6 +1445,8 @@ void A_StatueBurst(mobj_t *actor)
 			}
 
 			spawned = P_SpawnMobjFromMobj(actor, a, b, c, chunktype);
+			if (P_MobjWasRemoved(spawned))
+				continue;
 
 			P_InstaThrust(spawned, R_PointToAngle2(0, 0, a, b), 8<<FRACBITS);
 			P_SetObjectMomZ(spawned, v, false);
@@ -2165,6 +2174,8 @@ void A_CrushclawLaunch(mobj_t *actor)
 		for (i = 0; (i < CSEGS); i++)
 		{
 			mobj_t *newchain = P_SpawnMobjFromMobj(actor, 0, 0, 0, (mobjtype_t)actor->info->raisestate);
+			if (P_MobjWasRemoved(newchain))
+				continue;
 			P_SetTarget(&prevchain->target, newchain);
 			prevchain = newchain;
 		}
@@ -2335,10 +2346,13 @@ static void P_VultureHoverParticle(mobj_t *actor)
 			fixed_t pz = P_FloorzAtPos(px, py, actor->z, actor->height);
 
 			dust = P_SpawnMobj(px, py, pz, MT_ARIDDUST);
-			P_SetMobjState(dust, (statenum_t)(dust->state - states + P_RandomRange(0, 2)));
-			P_Thrust(dust, angle, FixedDiv(12*FRACUNIT, max(FRACUNIT, fdist/2)));
-			dust->momx += actor->momx;
-			dust->momy += actor->momy;
+			if (!P_MobjWasRemoved(dust))
+			{
+				P_SetMobjState(dust, (statenum_t)(dust->state - states + P_RandomRange(0, 2)));
+				P_Thrust(dust, angle, FixedDiv(12*FRACUNIT, max(FRACUNIT, fdist/2)));
+				dust->momx += actor->momx;
+				dust->momy += actor->momy;
+			}
 			angle += ANGLE_45;
 		}
 	}
@@ -2436,6 +2450,8 @@ void A_VultureBlast(mobj_t *actor)
 	{
 		angle_t fa = ((i*(angle_t)ANGLE_45) >> ANGLETOFINESHIFT) & FINEMASK;
 		dust = P_SpawnMobj(actor->x + 48*FixedMul(FINECOSINE(fa), -faasin), actor->y + 48*FixedMul(FINECOSINE(fa), faacos), actor->z + actor->height/2 + 48*FINESINE(fa), MT_PARTICLE);
+		if (P_MobjWasRemoved(dust))
+			continue;
 
 		P_SetScale(dust, 4*FRACUNIT);
 		dust->destscale = FRACUNIT;
@@ -2505,10 +2521,13 @@ void A_VultureFly(mobj_t *actor)
 	P_VultureHoverParticle(actor);
 
 	dust = P_SpawnMobj(actor->x + P_RandomFixed() - FRACUNIT/2, actor->y + P_RandomFixed() - FRACUNIT/2, actor->z + actor->height/2 + P_RandomFixed() - FRACUNIT/2, MT_PARTICLE);
-	P_SetScale(dust, 2*FRACUNIT);
-	dust->destscale = FRACUNIT/3;
-	dust->scalespeed = FRACUNIT/40;
-	dust->fuse = TICRATE*2;
+	if (!P_MobjWasRemoved(dust))
+	{
+		P_SetScale(dust, 2*FRACUNIT);
+		dust->destscale = FRACUNIT/3;
+		dust->scalespeed = FRACUNIT/40;
+		dust->fuse = TICRATE*2;
+	}
 
 	actor->momx += FixedDiv(dx, dm)*2;
 	actor->momy += FixedDiv(dy, dm)*2;
@@ -2709,6 +2728,8 @@ void A_LobShot(mobj_t *actor)
 		z = actor->z + FixedMul(locvar2*FRACUNIT, actor->scale);
 
 	shot = P_SpawnMobj(actor->x, actor->y, z, locvar1);
+	if (P_MobjWasRemoved(shot))
+		return;
 
 	if (actor->type == MT_BLACKEGGMAN)
 	{
@@ -3098,15 +3119,21 @@ void A_Boss1Laser(mobj_t *actor)
 			S_StartSound(actor, mobjinfo[locvar1].seesound);
 
 		point = P_SpawnMobj(x + P_ReturnThrustX(actor, actor->angle, actor->radius), y + P_ReturnThrustY(actor, actor->angle, actor->radius), actor->z - actor->height / 2, MT_EGGMOBILE_TARGET);
-		point->angle = actor->angle;
-		point->fuse = dur+1;
-		P_SetTarget(&point->target, actor->target);
-		P_SetTarget(&actor->target, point);
+		if (!P_MobjWasRemoved(point))
+		{
+			point->angle = actor->angle;
+			point->fuse = dur+1;
+			P_SetTarget(&point->target, actor->target);
+			P_SetTarget(&actor->target, point);
+		}
 	}
 
 	angle = R_PointToAngle2(z + (mobjinfo[locvar1].height>>1), 0, actor->target->z, R_PointToDist2(x, y, actor->target->x, actor->target->y));
 
 	point = P_SpawnMobj(x, y, z, locvar1);
+	if (P_MobjWasRemoved(point))
+		return;
+
 	P_SetTarget(&point->target, actor);
 	point->angle = actor->angle;
 	speed = point->radius;
@@ -3117,6 +3144,9 @@ void A_Boss1Laser(mobj_t *actor)
 	for (i = 0; i < 256; i++)
 	{
 		mobj_t *mo = P_SpawnMobj(point->x, point->y, point->z, point->type);
+		if (P_MobjWasRemoved(mo))
+			continue;
+
 		mo->angle = point->angle;
 		mo->color = LASERCOLORS[((UINT8)(i + 3*dur) >> 2) % sizeof(LASERCOLORS)]; // codeing
 		P_UnsetThingPosition(mo);
@@ -3129,9 +3159,12 @@ void A_Boss1Laser(mobj_t *actor)
 			if (mo->info->meleestate)
 			{
 				mobj_t *mo2 = P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_PARTICLE);
-				mo2->flags2 |= MF2_LINKDRAW;
-				P_SetTarget(&mo2->tracer, actor);
-				P_SetMobjState(mo2, mo->info->meleestate);
+				if (!P_MobjWasRemoved(mo2))
+				{
+					mo2->flags2 |= MF2_LINKDRAW;
+					P_SetTarget(&mo2->tracer, actor);
+					P_SetMobjState(mo2, mo->info->meleestate);
+				}
 			}
 		}
 
@@ -3149,37 +3182,42 @@ void A_Boss1Laser(mobj_t *actor)
 	if (z - floorz < mobjinfo[MT_EGGMOBILE_FIRE].height>>1 && dur & 1)
 	{
 		point = P_SpawnMobj(x, y, floorz, MT_EGGMOBILE_FIRE);
-		point->angle = actor->angle;
-		point->destscale = actor->scale;
-		P_SetScale(point, point->destscale);
-		P_SetTarget(&point->target, actor);
-		P_MobjCheckWater(point);
-		if (point->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER))
-		{
-			for (i = 0; i < 2; i++)
+		if (!P_MobjWasRemoved(point))
+		{
+			point->angle = actor->angle;
+			point->destscale = actor->scale;
+			P_SetScale(point, point->destscale);
+			P_SetTarget(&point->target, actor);
+			P_MobjCheckWater(point);
+			if (point->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER))
 			{
-				UINT8 size = 3;
-				mobj_t *steam = P_SpawnMobj(x, y, point->watertop - size*mobjinfo[MT_DUST].height, MT_DUST);
-				P_SetScale(steam, size*actor->scale);
-				P_SetObjectMomZ(steam, FRACUNIT + 2*P_RandomFixed(), true);
-				P_InstaThrust(steam, FixedAngle(P_RandomKey(360)*FRACUNIT), 2*P_RandomFixed());
-				if (point->info->painsound)
-					S_StartSound(steam, point->info->painsound);
+				for (i = 0; i < 2; i++)
+				{
+					UINT8 size = 3;
+					mobj_t *steam = P_SpawnMobj(x, y, point->watertop - size*mobjinfo[MT_DUST].height, MT_DUST);
+					if (P_MobjWasRemoved(steam))
+						continue;
+					P_SetScale(steam, size*actor->scale);
+					P_SetObjectMomZ(steam, FRACUNIT + 2*P_RandomFixed(), true);
+					P_InstaThrust(steam, FixedAngle(P_RandomKey(360)*FRACUNIT), 2*P_RandomFixed());
+					if (point->info->painsound)
+						S_StartSound(steam, point->info->painsound);
+				}
 			}
-		}
-		else
-		{
-			fixed_t distx = P_ReturnThrustX(point, point->angle, point->radius);
-			fixed_t disty = P_ReturnThrustY(point, point->angle, point->radius);
-			if (P_TryMove(point, point->x + distx, point->y + disty, false) // prevents the sprite from clipping into the wall or dangling off ledges
-				&& P_TryMove(point, point->x - 2*distx, point->y - 2*disty, false)
-				&& P_TryMove(point, point->x + distx, point->y + disty, false))
+			else
 			{
-				if (point->info->seesound)
-					S_StartSound(point, point->info->seesound);
+				fixed_t distx = P_ReturnThrustX(point, point->angle, point->radius);
+				fixed_t disty = P_ReturnThrustY(point, point->angle, point->radius);
+				if (P_TryMove(point, point->x + distx, point->y + disty, false) // prevents the sprite from clipping into the wall or dangling off ledges
+					&& P_TryMove(point, point->x - 2*distx, point->y - 2*disty, false)
+					&& P_TryMove(point, point->x + distx, point->y + disty, false))
+				{
+					if (point->info->seesound)
+						S_StartSound(point, point->info->seesound);
+				}
+				else
+					P_RemoveMobj(point);
 			}
-			else
-				P_RemoveMobj(point);
 		}
 	}
 
@@ -3528,6 +3566,8 @@ void A_BossScream(mobj_t *actor)
 		z = actor->z + FixedMul((P_RandomByte()<<(FRACBITS-2)) - 8*FRACUNIT, actor->scale);
 
 	mo = P_SpawnMobj(x, y, z, explodetype);
+	if (P_MobjWasRemoved(mo))
+		return;
 	if (actor->eflags & MFE_VERTICALFLIP)
 		mo->flags2 |= MF2_OBJECTFLIP;
 	mo->destscale = actor->scale;
@@ -3654,6 +3694,9 @@ void A_1upThinker(mobj_t *actor)
 	if (!actor->tracer)
 	{
 		P_SetTarget(&actor->tracer, P_SpawnMobj(actor->x, actor->y, actor->z, MT_OVERLAY));
+		if (P_MobjWasRemoved(actor->tracer))
+			return;
+
 		P_SetTarget(&actor->tracer->target, actor);
 		actor->tracer->skin = skins[players[closestplayer].skin]; // required here to prevent spr2 default showing stand for a single frame
 		P_SetMobjState(actor->tracer, actor->info->seestate);
@@ -3715,31 +3758,41 @@ void A_MonitorPop(mobj_t *actor)
 		return;
 	}
 
-	newmobj = P_SpawnMobjFromMobj(actor, 0, 0, 13*FRACUNIT, item);
-	P_SetTarget(&newmobj->target, actor->target); // Transfer target
-
 	if (item == MT_1UP_ICON)
 	{
 		if (actor->tracer) // Remove the old lives icon.
 			P_RemoveMobj(actor->tracer);
+	}
 
-		if (!newmobj->target
-		 || !newmobj->target->player
-		 || !newmobj->target->skin
-		 || ((skin_t *)newmobj->target->skin)->sprites[SPR2_LIFE].numframes == 0)
-			{} // No lives icon for this player, use the default.
-		else
-		{ // Spawn the lives icon.
-			mobj_t *livesico = P_SpawnMobjFromMobj(newmobj, 0, 0, 0, MT_OVERLAY);
-			P_SetTarget(&livesico->target, newmobj);
-			P_SetTarget(&newmobj->tracer, livesico);
+	newmobj = P_SpawnMobjFromMobj(actor, 0, 0, 13*FRACUNIT, item);
+	if (!P_MobjWasRemoved(newmobj))
+	{
+		P_SetTarget(&newmobj->target, actor->target); // Transfer target
+
+
+		if (item == MT_1UP_ICON)
+		{
+			if (!newmobj->target
+			 || !newmobj->target->player
+			 || !newmobj->target->skin
+			 || ((skin_t *)newmobj->target->skin)->sprites[SPR2_LIFE].numframes == 0)
+				{} // No lives icon for this player, use the default.
+			else
+			{ // Spawn the lives icon.
+				mobj_t *livesico = P_SpawnMobjFromMobj(newmobj, 0, 0, 0, MT_OVERLAY);
+				if (!P_MobjWasRemoved(livesico))
+				{
+					P_SetTarget(&livesico->target, newmobj);
+					P_SetTarget(&newmobj->tracer, livesico);
 
-			livesico->color = newmobj->target->player->mo->color;
-			livesico->skin = skins[newmobj->target->player->skin];
-			P_SetMobjState(livesico, newmobj->info->seestate);
+					livesico->color = newmobj->target->player->mo->color;
+					livesico->skin = &skins[newmobj->target->player->skin];
+					P_SetMobjState(livesico, newmobj->info->seestate);
+				}
 
-			// We're using the overlay, so use the overlay 1up sprite (no text)
-			newmobj->sprite = SPR_TV1P;
+				// We're using the overlay, so use the overlay 1up sprite (no text)
+				newmobj->sprite = SPR_TV1P;
+			}
 		}
 	}
 
@@ -3802,30 +3855,36 @@ void A_GoldMonitorPop(mobj_t *actor)
 
 	// Note: the icon spawns 1 fracunit higher
 	newmobj = P_SpawnMobjFromMobj(actor, 0, 0, 14*FRACUNIT, item);
-	P_SetTarget(&newmobj->target, actor->target); // Transfer target
-
-	if (item == MT_1UP_ICON)
+	if (!P_MobjWasRemoved(newmobj))
 	{
-		if (actor->tracer) // Remove the old lives icon.
-			P_RemoveMobj(actor->tracer);
+		P_SetTarget(&newmobj->target, actor->target); // Transfer target
 
-		if (!newmobj->target
-		 || !newmobj->target->player
-		 || !newmobj->target->skin
-		 || ((skin_t *)newmobj->target->skin)->sprites[SPR2_LIFE].numframes == 0)
-			{} // No lives icon for this player, use the default.
-		else
-		{ // Spawn the lives icon.
-			mobj_t *livesico = P_SpawnMobjFromMobj(newmobj, 0, 0, 0, MT_OVERLAY);
-			P_SetTarget(&livesico->target, newmobj);
-			P_SetTarget(&newmobj->tracer, livesico);
+		if (item == MT_1UP_ICON)
+		{
+			if (actor->tracer) // Remove the old lives icon.
+				P_RemoveMobj(actor->tracer);
 
-			livesico->color = newmobj->target->player->mo->color;
-			livesico->skin = skins[newmobj->target->player->skin];
-			P_SetMobjState(livesico, newmobj->info->seestate);
+			if (!newmobj->target
+			 || !newmobj->target->player
+			 || !newmobj->target->skin
+			 || ((skin_t *)newmobj->target->skin)->sprites[SPR2_LIFE].numframes == 0)
+				{} // No lives icon for this player, use the default.
+			else
+			{ // Spawn the lives icon.
+				mobj_t *livesico = P_SpawnMobjFromMobj(newmobj, 0, 0, 0, MT_OVERLAY);
+				if (!P_MobjWasRemoved(livesico))
+				{
+					P_SetTarget(&livesico->target, newmobj);
+					P_SetTarget(&newmobj->tracer, livesico);
+
+					livesico->color = newmobj->target->player->mo->color;
+					livesico->skin = &skins[newmobj->target->player->skin];
+					P_SetMobjState(livesico, newmobj->info->seestate);
+				}
 
-			// We're using the overlay, so use the overlay 1up sprite (no text)
-			newmobj->sprite = SPR_TV1P;
+				// We're using the overlay, so use the overlay 1up sprite (no text)
+				newmobj->sprite = SPR_TV1P;
+			}
 		}
 	}
 
@@ -3870,7 +3929,13 @@ void A_GoldMonitorSparkle(mobj_t *actor)
 	yofs = FINECOSINE((ngangle>>ANGLETOFINESHIFT) & FINEMASK) * (actor->radius>>FRACBITS);
 
 	for (i = FRACUNIT*2; i <= FRACUNIT*3; i += FRACUNIT/2)
-		P_SetObjectMomZ(P_SpawnMobjFromMobj(actor, xofs, yofs, 0, MT_BOXSPARKLE), i, false);
+	{
+		mobj_t *sparkle = P_SpawnMobjFromMobj(actor, xofs, yofs, 0, MT_BOXSPARKLE);
+		if (P_MobjWasRemoved(sparkle))
+			continue;
+
+		P_SetObjectMomZ(sparkle, i, false);
+	}
 }
 
 // Function: A_Explode
@@ -3992,56 +4057,73 @@ static void P_DoBossVictory(mobj_t *mo)
 static void P_SpawnBoss1Junk(mobj_t *mo)
 {
 	mobj_t *mo2 = P_SpawnMobjFromMobj(mo,
-	P_ReturnThrustX(mo, mo->angle - ANGLE_90, 32<<FRACBITS),
-	P_ReturnThrustY(mo, mo->angle - ANGLE_90, 32<<FRACBITS),
-	32<<FRACBITS, MT_BOSSJUNK);
-	mo2->angle = mo->angle;
-	P_InstaThrust(mo2, mo2->angle - ANGLE_90, 4*mo2->scale);
-	P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
-	P_SetMobjState(mo2, S_BOSSEGLZ1);
+		P_ReturnThrustX(mo, mo->angle - ANGLE_90, 32<<FRACBITS),
+		P_ReturnThrustY(mo, mo->angle - ANGLE_90, 32<<FRACBITS),
+		32<<FRACBITS, MT_BOSSJUNK);
+	if (!P_MobjWasRemoved(mo2))
+	{
+		mo2->angle = mo->angle;
+		P_InstaThrust(mo2, mo2->angle - ANGLE_90, 4*mo2->scale);
+		P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
+		P_SetMobjState(mo2, S_BOSSEGLZ1);
+	}
 
 	mo2 = P_SpawnMobjFromMobj(mo,
 		P_ReturnThrustX(mo, mo->angle + ANGLE_90, 32<<FRACBITS),
 		P_ReturnThrustY(mo, mo->angle + ANGLE_90, 32<<FRACBITS),
 		32<<FRACBITS, MT_BOSSJUNK);
-	mo2->angle = mo->angle;
-	P_InstaThrust(mo2, mo2->angle + ANGLE_90, 4*mo2->scale);
-	P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
-	P_SetMobjState(mo2, S_BOSSEGLZ2);
+	if (!P_MobjWasRemoved(mo2))
+	{
+		mo2->angle = mo->angle;
+		P_InstaThrust(mo2, mo2->angle + ANGLE_90, 4*mo2->scale);
+		P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
+		P_SetMobjState(mo2, S_BOSSEGLZ2);
+	}
 }
 
 static void P_SpawnBoss2Junk(mobj_t *mo)
 {
 	mobj_t *mo2 = P_SpawnMobjFromMobj(mo,
-	P_ReturnThrustX(mo, mo->angle - ANGLE_90, 32<<FRACBITS),
-	P_ReturnThrustY(mo, mo->angle - ANGLE_90, 32<<FRACBITS),
-	32<<FRACBITS, MT_BOSSJUNK);
-	mo2->angle = mo->angle;
-	P_InstaThrust(mo2, mo2->angle - ANGLE_90, 4*mo2->scale);
-	P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
-	P_SetMobjState(mo2, S_BOSSTANK1);
+		P_ReturnThrustX(mo, mo->angle - ANGLE_90, 32<<FRACBITS),
+		P_ReturnThrustY(mo, mo->angle - ANGLE_90, 32<<FRACBITS),
+		32<<FRACBITS, MT_BOSSJUNK);
+	if (!P_MobjWasRemoved(mo2))
+	{
+		mo2->angle = mo->angle;
+		P_InstaThrust(mo2, mo2->angle - ANGLE_90, 4*mo2->scale);
+		P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
+		P_SetMobjState(mo2, S_BOSSTANK1);
+	}
 
 	mo2 = P_SpawnMobjFromMobj(mo,
 		P_ReturnThrustX(mo, mo->angle + ANGLE_90, 32<<FRACBITS),
 		P_ReturnThrustY(mo, mo->angle + ANGLE_90, 32<<FRACBITS),
 		32<<FRACBITS, MT_BOSSJUNK);
-	mo2->angle = mo->angle;
-	P_InstaThrust(mo2, mo2->angle + ANGLE_90, 4*mo2->scale);
-	P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
-	P_SetMobjState(mo2, S_BOSSTANK2);
+	if (!P_MobjWasRemoved(mo2))
+	{
+		mo2->angle = mo->angle;
+		P_InstaThrust(mo2, mo2->angle + ANGLE_90, 4*mo2->scale);
+		P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
+		P_SetMobjState(mo2, S_BOSSTANK2);
+	}
 
 	mo2 = P_SpawnMobjFromMobj(mo, 0, 0,
 		mobjinfo[MT_EGGMOBILE2].height + (32<<FRACBITS),
 		MT_BOSSJUNK);
-	mo2->angle = mo->angle;
-	P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
-	mo2->momz += mo->momz;
-	P_SetMobjState(mo2, S_BOSSSPIGOT);
+	if (!P_MobjWasRemoved(mo2))
+	{
+		mo2->angle = mo->angle;
+		P_SetObjectMomZ(mo2, 4*FRACUNIT, false);
+		mo2->momz += mo->momz;
+		P_SetMobjState(mo2, S_BOSSSPIGOT);
+	}
 }
 
 static void P_SpawnBoss3Junk(mobj_t *mo)
 {
 	mobj_t *mo2 = P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_BOSSJUNK);
+	if (P_MobjWasRemoved(mo2))
+		return;
 	mo2->angle = mo->angle;
 	P_SetMobjState(mo2, S_BOSSSEBH1);
 }
@@ -4081,26 +4163,36 @@ static void P_DoBoss5Death(mobj_t *mo)
 				mo->tracer->y - P_ReturnThrustY(mo->tracer, mo->tracer->angle, speed*time),
 				mo->tracer->floorz + (256+1)*FRACUNIT,
 				MT_FSGNB);
-			P_SetTarget(&pole->tracer, P_SpawnMobj(
-				pole->x, pole->y,
-				pole->z - 256*FRACUNIT,
-				MT_FSGNB));
-			P_SetTarget(&pole->tracer->tracer, P_SpawnMobj(
-				pole->x + P_ReturnThrustX(pole, mo->tracer->angle, FRACUNIT),
-				pole->y + P_ReturnThrustY(pole, mo->tracer->angle, FRACUNIT),
-				pole->z + 256*FRACUNIT,
-				MT_FSGNA));
-			pole->tracer->flags |= MF_NOCLIPTHING;
-			P_SetScale(pole, (pole->destscale = 2*FRACUNIT));
-			P_SetScale(pole->tracer, (pole->tracer->destscale = 2*FRACUNIT));
-			pole->angle = pole->tracer->angle = mo->tracer->angle;
-			pole->tracer->tracer->angle = pole->angle - ANGLE_90;
-			pole->momx = P_ReturnThrustX(pole, pole->angle, speed);
-			pole->momy = P_ReturnThrustY(pole, pole->angle, speed);
-			pole->tracer->momx = pole->momx;
-			pole->tracer->momy = pole->momy;
-			pole->tracer->tracer->momx = pole->momx;
-			pole->tracer->tracer->momy = pole->momy;
+			if (!P_MobjWasRemoved(pole))
+			{
+				P_SetScale(pole, (pole->destscale = 2*FRACUNIT));
+				pole->momx = P_ReturnThrustX(pole, pole->angle, speed);
+				pole->momy = P_ReturnThrustY(pole, pole->angle, speed);
+				P_SetTarget(&pole->tracer, P_SpawnMobj(
+					pole->x, pole->y,
+					pole->z - 256*FRACUNIT,
+					MT_FSGNB));
+				if (!P_MobjWasRemoved(pole->tracer))
+				{
+					pole->tracer->flags |= MF_NOCLIPTHING;
+					P_SetScale(pole->tracer, (pole->tracer->destscale = 2*FRACUNIT));
+					pole->angle = pole->tracer->angle = mo->tracer->angle;
+					pole->tracer->momx = pole->momx;
+					pole->tracer->momy = pole->momy;
+
+					P_SetTarget(&pole->tracer->tracer, P_SpawnMobj(
+						pole->x + P_ReturnThrustX(pole, mo->tracer->angle, FRACUNIT),
+						pole->y + P_ReturnThrustY(pole, mo->tracer->angle, FRACUNIT),
+						pole->z + 256*FRACUNIT,
+						MT_FSGNA));
+					if (!P_MobjWasRemoved(pole->tracer->tracer))
+					{
+						pole->tracer->tracer->angle = pole->angle - ANGLE_90;
+						pole->tracer->tracer->momx = pole->momx;
+						pole->tracer->tracer->momy = pole->momy;
+					}
+				}
+			}
 		}
 	}
 	else
@@ -4760,9 +4852,12 @@ void A_AttractChase(mobj_t *actor)
 	{
 		mobj_t *newring;
 		newring = P_SpawnMobj(actor->x, actor->y, actor->z, actor->info->reactiontime);
-		newring->momx = actor->momx;
-		newring->momy = actor->momy;
-		newring->momz = actor->momz;
+		if (!P_MobjWasRemoved(newring))
+		{
+			newring->momx = actor->momx;
+			newring->momy = actor->momy;
+			newring->momz = actor->momz;
+		}
 		P_RemoveMobj(actor);
 		return;
 	}
@@ -4842,9 +4937,12 @@ void A_DropMine(mobj_t *actor)
 
 	// Use raisestate instead of MT_MINE
 	mine = P_SpawnMobj(actor->x, actor->y, z, (mobjtype_t)actor->info->raisestate);
-	if (actor->eflags & MFE_VERTICALFLIP)
-		mine->eflags |= MFE_VERTICALFLIP;
-	mine->momz = actor->momz + actor->pmomz;
+	if (!P_MobjWasRemoved(mine))
+	{
+		if (actor->eflags & MFE_VERTICALFLIP)
+			mine->eflags |= MFE_VERTICALFLIP;
+		mine->momz = actor->momz + actor->pmomz;
+	}
 
 	S_StartSound(actor, actor->info->attacksound);
 }
@@ -5169,11 +5267,13 @@ void A_SignSpin(mobj_t *actor)
 	if (actor->tracer == NULL || P_MobjWasRemoved(actor->tracer)) return;
 	for (i = -1; i < 2; i += 2)
 	{
-		P_SpawnMobjFromMobj(actor,
+		mobj_t *spinmobj = P_SpawnMobjFromMobj(actor,
 			P_ReturnThrustX(actor, actor->tracer->angle, i * actor->radius),
 			P_ReturnThrustY(actor, actor->tracer->angle, i * actor->radius),
 			(actor->eflags & MFE_VERTICALFLIP) ? 0 : actor->height,
-			actor->info->painchance)->destscale >>= 1;
+			actor->info->painchance);
+		if (!P_MobjWasRemoved(spinmobj))
+			spinmobj->destscale >>= 1;
 	}
 }
 
@@ -5233,8 +5333,11 @@ void A_SignPlayer(mobj_t *actor)
 	if (actor->tracer->tracer == NULL || P_MobjWasRemoved(actor->tracer->tracer))
 	{
 		ov = P_SpawnMobj(actor->x, actor->y, actor->z, MT_OVERLAY);
-		P_SetTarget(&ov->target, actor->tracer);
-		P_SetTarget(&actor->tracer->tracer, ov);
+		if (!P_MobjWasRemoved(ov))
+		{
+			P_SetTarget(&ov->target, actor->tracer);
+			P_SetTarget(&actor->tracer->tracer, ov);
+		}
 	}
 	else
 		ov = actor->tracer->tracer;
@@ -5294,33 +5397,36 @@ void A_SignPlayer(mobj_t *actor)
 			signcolor = skincolors[facecolor].invcolor;
 	}
 
-	if (skin)
-	{
-		if (skin->sprites[SPR2_SIGN].numframes) // player face
+    if (!P_MobjWasRemoved(ov))
+    {
+		if (skin)
 		{
-			ov->color = facecolor;
-			ov->skin = skin;
-			if ((statenum_t)(ov->state-states) != actor->info->seestate)
-				P_SetMobjState(ov, actor->info->seestate); // S_PLAY_SIGN
+			if (skin->sprites[SPR2_SIGN].numframes) // player face
+			{
+				ov->color = facecolor;
+				ov->skin = skin;
+				if ((statenum_t)(ov->state-states) != actor->info->seestate)
+					P_SetMobjState(ov, actor->info->seestate); // S_PLAY_SIGN
+			}
+			else // CLEAR! sign
+			{
+				ov->color = SKINCOLOR_NONE;
+				ov->skin = NULL; // needs to be NULL in the case of SF_HIRES characters
+				if ((statenum_t)(ov->state-states) != actor->info->missilestate)
+					P_SetMobjState(ov, actor->info->missilestate); // S_CLEARSIGN
+			}
 		}
-		else // CLEAR! sign
+		else // Eggman face
 		{
 			ov->color = SKINCOLOR_NONE;
-			ov->skin = NULL; // needs to be NULL in the case of SF_HIRES characters
-			if ((statenum_t)(ov->state-states) != actor->info->missilestate)
-				P_SetMobjState(ov, actor->info->missilestate); // S_CLEARSIGN
+			ov->skin = NULL;
+			if ((statenum_t)(ov->state-states) != actor->info->meleestate)
+				P_SetMobjState(ov, actor->info->meleestate); // S_EGGMANSIGN
+			if (!signcolor)
+				signcolor = SKINCOLOR_CARBON;
+			facecolor = signcolor;
 		}
-	}
-	else // Eggman face
-	{
-		ov->color = SKINCOLOR_NONE;
-		ov->skin = NULL;
-		if ((statenum_t)(ov->state-states) != actor->info->meleestate)
-			P_SetMobjState(ov, actor->info->meleestate); // S_EGGMANSIGN
-		if (!signcolor)
-			signcolor = SKINCOLOR_CARBON;
-		facecolor = signcolor;
-	}
+    }
 
 	actor->tracer->color = signcolor;
 	if (signcolor && signcolor < numskincolors)
@@ -5511,12 +5617,14 @@ void A_JetbThink(mobj_t *actor)
 
 			// use raisestate instead of MT_MINE
 			bomb = P_SpawnMobj(actor->x, actor->y, actor->z - FixedMul((32<<FRACBITS), actor->scale), (mobjtype_t)actor->info->raisestate);
-
-			P_SetTarget(&bomb->target, actor);
-			bomb->destscale = actor->scale;
-			P_SetScale(bomb, actor->scale);
-			actor->reactiontime = TICRATE; // one second
-			S_StartSound(actor, actor->info->attacksound);
+			if (!P_MobjWasRemoved(bomb))
+			{
+				P_SetTarget(&bomb->target, actor);
+				bomb->destscale = actor->scale;
+				P_SetScale(bomb, actor->scale);
+				actor->reactiontime = TICRATE; // one second
+				S_StartSound(actor, actor->info->attacksound);
+			}
 		}
 	}
 	else if (((actor->z - FixedMul((32<<FRACBITS), actor->scale)) < thefloor) && !((thefloor + FixedMul((32<<FRACBITS), actor->scale) + actor->height) > actor->ceilingz))
@@ -5668,9 +5776,12 @@ void A_MinusDigging(mobj_t *actor)
 	}
 
 	par = P_SpawnMobj(actor->x, actor->y, mz, MT_MINUSDIRT);
-	if (actor->eflags & MFE_VERTICALFLIP)
-		par->eflags |= MFE_VERTICALFLIP;
-	P_TryMove(par, x, y, false);
+	if (!P_MobjWasRemoved(par))
+	{
+		if (actor->eflags & MFE_VERTICALFLIP)
+			par->eflags |= MFE_VERTICALFLIP;
+		P_TryMove(par, x, y, false);
+	}
 
 	// If close enough, prepare to attack
 	if (P_AproxDistance(actor->x - actor->target->x, actor->y - actor->target->y) < actor->radius*2)
@@ -5681,6 +5792,8 @@ void A_MinusDigging(mobj_t *actor)
 
 		// Spawn growing dirt pile.
 		par = P_SpawnMobj(actor->x, actor->y, mz, MT_MINUSDIRT);
+		if (P_MobjWasRemoved(par))
+			return;
 		P_SetMobjState(par, actor->info->raisestate);
 		P_SetScale(par, actor->scale*2);
 		if (actor->eflags & MFE_VERTICALFLIP)
@@ -5751,6 +5864,8 @@ void A_MinusPopup(mobj_t *actor)
 	for (i = 1; i <= num; i++)
 	{
 		mobj_t *rock = P_SpawnMobjFromMobj(actor, 0, 0, actor->height/4, MT_ROCKCRUMBLE1);
+		if (P_MobjWasRemoved(rock))
+			continue;
 		P_Thrust(rock, ani*i, FRACUNIT);
 		P_SetObjectMomZ(rock, 3*FRACUNIT, false);
 		P_SetScale(rock, rock->scale/3);
@@ -5788,6 +5903,8 @@ void A_MinusCheck(mobj_t *actor)
 			for (i = 1; i <= num; i++)
 			{
 				mobj_t *rock = P_SpawnMobjFromMobj(actor, 0, 0, actor->height/4, MT_ROCKCRUMBLE1);
+				if (P_MobjWasRemoved(rock))
+					continue;
 				P_Thrust(rock, ani*i, FRACUNIT);
 				P_SetObjectMomZ(rock, 3*FRACUNIT, false);
 				P_SetScale(rock, rock->scale/3);
@@ -6299,6 +6416,8 @@ void A_RockSpawn(mobj_t *actor)
 		dist += actor->spawnpoint->args[2] ? P_RandomByte() * (FRACUNIT/32) : 0; // random oomph
 
 	mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_FALLINGROCK);
+	if (P_MobjWasRemoved(mo))
+		return;
 	P_SetMobjState(mo, mobjinfo[type].spawnstate);
 	mo->angle = FixedAngle(actor->spawnpoint->angle << FRACBITS);
 
@@ -6335,6 +6454,8 @@ void A_SlingAppear(mobj_t *actor)
 	actor->friction = 128;
 
 	hprev = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SMALLGRABCHAIN);
+	if (P_MobjWasRemoved(hprev))
+		return;
 	P_SetTarget(&hprev->tracer, actor);
 	P_SetTarget(&hprev->hprev, actor);
 	P_SetTarget(&actor->hnext, hprev);
@@ -6346,13 +6467,16 @@ void A_SlingAppear(mobj_t *actor)
 	while (mlength > 0)
 	{
 		spawnee = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SMALLMACECHAIN);
-		P_SetTarget(&spawnee->tracer, actor);
-		P_SetTarget(&spawnee->hprev, hprev);
-		P_SetTarget(&hprev->hnext, spawnee);
-		hprev = spawnee;
+		if (!P_MobjWasRemoved(spawnee))
+		{
+			P_SetTarget(&spawnee->tracer, actor);
+			P_SetTarget(&spawnee->hprev, hprev);
+			P_SetTarget(&hprev->hnext, spawnee);
+			hprev = spawnee;
 
-		spawnee->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT;
-		spawnee->movecount = mlength;
+			spawnee->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT;
+			spawnee->movecount = mlength;
+		}
 
 		mlength--;
 	}
@@ -6612,6 +6736,8 @@ void A_OldRingExplode(mobj_t *actor) {
 		const angle_t fa = (i*FINEANGLES/16) & FINEMASK;
 
 		mo = P_SpawnMobjFromMobj(actor, 0, 0, 0, locvar1);
+		if (P_MobjWasRemoved(mo))
+			continue;
 		P_SetTarget(&mo->target, actor->target); // Transfer target so player gets the points
 
 		mo->momx = FixedMul(FINECOSINE(fa),ns);
@@ -6638,33 +6764,37 @@ void A_OldRingExplode(mobj_t *actor) {
 	}
 
 	mo = P_SpawnMobjFromMobj(actor, 0, 0, 0, locvar1);
-
-	P_SetTarget(&mo->target, actor->target);
-	mo->momz = ns;
-	mo->flags2 |= MF2_DEBRIS;
-	mo->fuse = TICRATE/5;
-
-	if (changecolor)
+	if (!P_MobjWasRemoved(mo))
 	{
-		if (!(gametyperules & GTR_TEAMS))
-			mo->color = actor->target->color; //copy color
-		else if (actor->target->player->ctfteam == 2)
-			mo->color = skincolor_bluering;
+		P_SetTarget(&mo->target, actor->target);
+		mo->momz = ns;
+		mo->flags2 |= MF2_DEBRIS;
+		mo->fuse = TICRATE/5;
+
+		if (changecolor)
+		{
+			if (!(gametyperules & GTR_TEAMS))
+				mo->color = actor->target->color; //copy color
+			else if (actor->target->player->ctfteam == 2)
+				mo->color = skincolor_bluering;
+		}
 	}
 
 	mo = P_SpawnMobjFromMobj(actor, 0, 0, 0, locvar1);
-
-	P_SetTarget(&mo->target, actor->target);
-	mo->momz = -ns;
-	mo->flags2 |= MF2_DEBRIS;
-	mo->fuse = TICRATE/5;
-
-	if (changecolor)
+	if (!P_MobjWasRemoved(mo))
 	{
-		if (!(gametyperules & GTR_TEAMS))
-			mo->color = actor->target->color; //copy color
-		else if (actor->target->player->ctfteam == 2)
-			mo->color = skincolor_bluering;
+		P_SetTarget(&mo->target, actor->target);
+		mo->momz = -ns;
+		mo->flags2 |= MF2_DEBRIS;
+		mo->fuse = TICRATE/5;
+
+		if (changecolor)
+		{
+			if (!(gametyperules & GTR_TEAMS))
+				mo->color = actor->target->color; //copy color
+			else if (actor->target->player->ctfteam == 2)
+				mo->color = skincolor_bluering;
+		}
 	}
 }
 
@@ -7300,23 +7430,26 @@ void A_Boss2Chase(mobj_t *actor)
 			fa = (actor->movedir*FINEANGLES/8) & FINEMASK;
 
 			goop = P_SpawnMobj(actor->x, actor->y, fz, actor->info->painchance);
-			goop->momx = FixedMul(FINECOSINE(fa),ns);
-			goop->momy = FixedMul(FINESINE(fa),ns);
-			goop->momz = FixedMul(4*FRACUNIT, actor->scale);
-			goop->fuse = 10*TICRATE;
+			if (!P_MobjWasRemoved(goop))
+			{
+				goop->momx = FixedMul(FINECOSINE(fa),ns);
+				goop->momy = FixedMul(FINESINE(fa),ns);
+				goop->momz = FixedMul(4*FRACUNIT, actor->scale);
+				goop->fuse = 10*TICRATE;
 
-			if (actor->info->attacksound)
-				S_StartAttackSound(actor, actor->info->attacksound);
+				if (actor->info->attacksound)
+					S_StartAttackSound(actor, actor->info->attacksound);
 
-			if (P_RandomChance(FRACUNIT/2))
-			{
-				goop->momx *= 2;
-				goop->momy *= 2;
-			}
-			else if (P_RandomChance(129*FRACUNIT/256))
-			{
-				goop->momx *= 3;
-				goop->momy *= 3;
+				if (P_RandomChance(FRACUNIT/2))
+				{
+					goop->momx *= 2;
+					goop->momy *= 2;
+				}
+				else if (P_RandomChance(129*FRACUNIT/256))
+				{
+					goop->momx *= 3;
+					goop->momy *= 3;
+				}
 			}
 
 			actor->flags2 |= MF2_JUSTATTACKED;
@@ -7357,6 +7490,8 @@ void A_Boss2Pogo(mobj_t *actor)
 			fa = (actor->movedir*FINEANGLES/8) & FINEMASK;
 
 			goop = P_SpawnMobj(actor->x, actor->y, fz, actor->info->painchance);
+			if (P_MobjWasRemoved(goop))
+				continue;
 			goop->momx = FixedMul(FINECOSINE(fa),ns);
 			goop->momy = FixedMul(FINESINE(fa),ns);
 			goop->momz = FixedMul(4*FRACUNIT, actor->scale);
@@ -7685,7 +7820,8 @@ void A_Boss2PogoTarget(mobj_t *actor)
 	if (actor->info->missilestate) // spawn the pogo stick collision box
 	{
 		mobj_t *pogo = P_SpawnMobj(actor->x, actor->y, actor->z - mobjinfo[actor->info->missilestate].height, (mobjtype_t)actor->info->missilestate);
-		P_SetTarget(&pogo->target, actor);
+		if (!P_MobjWasRemoved(pogo))
+			P_SetTarget(&pogo->target, actor);
 	}
 
 	actor->reactiontime = 1;
@@ -8176,6 +8312,8 @@ void A_Boss1Spikeballs(mobj_t *actor)
 		return;
 
 	ball = P_SpawnMobj(actor->x, actor->y, actor->z, MT_EGGMOBILE_BALL);
+	if (P_MobjWasRemoved(ball))
+		return;
 	P_SetTarget(&ball->target, actor);
 	ball->movedir = FixedAngle(FixedMul(FixedDiv(locvar1<<FRACBITS, locvar2<<FRACBITS), 360<<FRACBITS));
 	ball->threshold = ball->radius + actor->radius + ball->info->painchance;
@@ -8363,19 +8501,22 @@ void A_Boss3ShockThink(mobj_t *actor)
 			snew = P_SpawnMobj((x0 >> 1) + (x1 >> 1),
 				(y0 >> 1) + (y1 >> 1),
 				(actor->z >> 1) + (snext->z >> 1), actor->type);
-			snew->momx = (actor->momx + snext->momx) >> 1;
-			snew->momy = (actor->momy + snext->momy) >> 1;
-			snew->momz = (actor->momz + snext->momz) >> 1; // is this really needed?
-			snew->angle = (actor->angle + snext->angle) >> 1;
-			P_SetTarget(&snew->target, actor->target);
-			snew->fuse = actor->fuse;
-
-			P_SetScale(snew, actor->scale);
-			snew->destscale = actor->destscale;
-			snew->scalespeed = actor->scalespeed;
-
-			P_SetTarget(&actor->hnext, snew);
-			P_SetTarget(&snew->hnext, snext);
+			if (!P_MobjWasRemoved(snew))
+			{
+				snew->momx = (actor->momx + snext->momx) >> 1;
+				snew->momy = (actor->momy + snext->momy) >> 1;
+				snew->momz = (actor->momz + snext->momz) >> 1; // is this really needed?
+				snew->angle = (actor->angle + snext->angle) >> 1;
+				P_SetTarget(&snew->target, actor->target);
+				snew->fuse = actor->fuse;
+
+				P_SetScale(snew, actor->scale);
+				snew->destscale = actor->destscale;
+				snew->scalespeed = actor->scalespeed;
+
+				P_SetTarget(&actor->hnext, snew);
+				P_SetTarget(&snew->hnext, snext);
+			}
 		}
 	}
 }
@@ -8560,10 +8701,13 @@ void A_SmokeTrailer(mobj_t *actor)
 	if (actor->eflags & MFE_VERTICALFLIP)
 	{
 		th = P_SpawnMobj(actor->x-actor->momx, actor->y-actor->momy, actor->z + actor->height - FixedMul(mobjinfo[locvar1].height, actor->scale), locvar1);
-		th->flags2 |= MF2_OBJECTFLIP;
+		if (!P_MobjWasRemoved(th))
+			th->flags2 |= MF2_OBJECTFLIP;
 	}
 	else
 		th = P_SpawnMobj(actor->x-actor->momx, actor->y-actor->momy, actor->z, locvar1);
+	if (P_MobjWasRemoved(th))
+		return;
 	P_SetObjectMomZ(th, FRACUNIT, false);
 	th->destscale = actor->scale;
 	P_SetScale(th, actor->scale);
@@ -8600,6 +8744,8 @@ void A_SpawnObjectAbsolute(mobj_t *actor)
 	type = (mobjtype_t)(locvar2&65535);
 
 	mo = P_SpawnMobj(x<<FRACBITS, y<<FRACBITS, z<<FRACBITS, type);
+	if (P_MobjWasRemoved(mo))
+		return;
 
 	// Spawn objects with an angle matching the spawner's, rather than spawning Eastwards - Monster Iestyn
 	mo->angle = actor->angle;
@@ -8642,6 +8788,8 @@ void A_SpawnObjectRelative(mobj_t *actor)
 	mo = P_SpawnMobj(actor->x + FixedMul(x<<FRACBITS, actor->scale),
 		actor->y + FixedMul(y<<FRACBITS, actor->scale),
 		(actor->eflags & MFE_VERTICALFLIP) ? ((actor->z + actor->height - mobjinfo[type].height) - FixedMul(z<<FRACBITS, actor->scale)) : (actor->z + FixedMul(z<<FRACBITS, actor->scale)), type);
+	if (P_MobjWasRemoved(mo))
+		return;
 
 	// Spawn objects with an angle matching the spawner's, rather than spawning Eastwards - Monster Iestyn
 	mo->angle = actor->angle;
@@ -9303,12 +9451,15 @@ void A_BossJetFume(mobj_t *actor)
 			jetz = actor->z + FixedMul(38*FRACUNIT, actor->scale);
 
 		filler = P_SpawnMobj(jetx, jety, jetz, MT_JETFUME1);
-		P_SetTarget(&filler->target, actor);
-		filler->destscale = actor->scale;
-		P_SetScale(filler, filler->destscale);
-		if (actor->eflags & MFE_VERTICALFLIP)
-			filler->flags2 |= MF2_OBJECTFLIP;
-		filler->fuse = 56;
+		if (!P_MobjWasRemoved(filler))
+		{
+			P_SetTarget(&filler->target, actor);
+			filler->destscale = actor->scale;
+			P_SetScale(filler, filler->destscale);
+			if (actor->eflags & MFE_VERTICALFLIP)
+				filler->flags2 |= MF2_OBJECTFLIP;
+			filler->fuse = 56;
+		}
 
 		if (actor->eflags & MFE_VERTICALFLIP)
 			jetz = actor->z + actor->height - FixedMul(12*FRACUNIT + mobjinfo[MT_JETFUME1].height, actor->scale);
@@ -9318,22 +9469,28 @@ void A_BossJetFume(mobj_t *actor)
 		filler = P_SpawnMobj(jetx + P_ReturnThrustX(actor, actor->angle-ANGLE_90, FixedMul(24*FRACUNIT, actor->scale)),
 				jety + P_ReturnThrustY(actor, actor->angle-ANGLE_90, FixedMul(24*FRACUNIT, actor->scale)),
 				jetz, MT_JETFUME1);
-		P_SetTarget(&filler->target, actor);
-		filler->destscale = actor->scale;
-		P_SetScale(filler, filler->destscale);
-		if (actor->eflags & MFE_VERTICALFLIP)
-			filler->flags2 |= MF2_OBJECTFLIP;
-		filler->fuse = 57;
+		if (!P_MobjWasRemoved(filler))
+		{
+			P_SetTarget(&filler->target, actor);
+			filler->destscale = actor->scale;
+			P_SetScale(filler, filler->destscale);
+			if (actor->eflags & MFE_VERTICALFLIP)
+				filler->flags2 |= MF2_OBJECTFLIP;
+			filler->fuse = 57;
+		}
 
 		filler = P_SpawnMobj(jetx + P_ReturnThrustX(actor, actor->angle+ANGLE_90, FixedMul(24*FRACUNIT, actor->scale)),
 				jety + P_ReturnThrustY(actor, actor->angle+ANGLE_90, FixedMul(24*FRACUNIT, actor->scale)),
 				jetz, MT_JETFUME1);
-		P_SetTarget(&filler->target, actor);
-		filler->destscale = actor->scale;
-		P_SetScale(filler, filler->destscale);
-		if (actor->eflags & MFE_VERTICALFLIP)
-			filler->flags2 |= MF2_OBJECTFLIP;
-		filler->fuse = 58;
+		if (!P_MobjWasRemoved(filler))
+		{
+			P_SetTarget(&filler->target, actor);
+			filler->destscale = actor->scale;
+			P_SetScale(filler, filler->destscale);
+			if (actor->eflags & MFE_VERTICALFLIP)
+				filler->flags2 |= MF2_OBJECTFLIP;
+			filler->fuse = 58;
+		}
 
 		P_SetTarget(&actor->tracer, filler);
 	}
@@ -9361,14 +9518,17 @@ void A_BossJetFume(mobj_t *actor)
 	else if (locvar1 == 2) // Metal Sonic jet fumes
 	{
 		filler = P_SpawnMobj(actor->x, actor->y, actor->z, MT_JETFUME1);
-		P_SetTarget(&filler->target, actor);
-		filler->fuse = 59;
-		P_SetTarget(&actor->tracer, filler);
-		P_SetScale(filler, (filler->destscale = actor->scale/3));
-		if (actor->eflags & MFE_VERTICALFLIP)
-			filler->flags2 |= MF2_OBJECTFLIP;
-		filler->color = SKINCOLOR_ICY;
-		filler->colorized = true;
+		if (!P_MobjWasRemoved(filler))
+		{
+			P_SetTarget(&filler->target, actor);
+			filler->fuse = 59;
+			P_SetTarget(&actor->tracer, filler);
+			P_SetScale(filler, (filler->destscale = actor->scale/3));
+			if (actor->eflags & MFE_VERTICALFLIP)
+				filler->flags2 |= MF2_OBJECTFLIP;
+			filler->color = SKINCOLOR_ICY;
+			filler->colorized = true;
+		}
 	}
 	else if (locvar1 == 3) // Boss 4 jet flame
 	{
@@ -9378,12 +9538,15 @@ void A_BossJetFume(mobj_t *actor)
 		else
 			jetz = actor->z - 50*actor->scale;
 		filler = P_SpawnMobj(actor->x, actor->y, jetz, MT_JETFLAME);
-		P_SetTarget(&filler->target, actor);
-		// Boss 4 already uses its tracer for other things
-		filler->destscale = actor->scale;
-		P_SetScale(filler, filler->destscale);
-		if (actor->eflags & MFE_VERTICALFLIP)
-			filler->flags2 |= MF2_OBJECTFLIP;
+		if (!P_MobjWasRemoved(filler))
+		{
+			P_SetTarget(&filler->target, actor);
+			// Boss 4 already uses its tracer for other things
+			filler->destscale = actor->scale;
+			P_SetScale(filler, filler->destscale);
+			if (actor->eflags & MFE_VERTICALFLIP)
+				filler->flags2 |= MF2_OBJECTFLIP;
+		}
 	}
 	else if (locvar1 == 4) // Boss 4 Spectator Eggrobo jet flame
 	{
@@ -9398,12 +9561,15 @@ void A_BossJetFume(mobj_t *actor)
 			jetx = actor->x + P_ReturnThrustX(actor, actor->angle+ANGLE_90, movefactor*actor->scale) - P_ReturnThrustX(actor, actor->angle, 19*actor->scale);
 			jety = actor->y + P_ReturnThrustY(actor, actor->angle+ANGLE_90, movefactor*actor->scale) - P_ReturnThrustY(actor, actor->angle, 19*actor->scale);
 			filler = P_SpawnMobj(jetx, jety, jetz, MT_EGGROBO1JET);
-			filler->movefactor = movefactor;
-			P_SetTarget(&filler->target, actor);
-			filler->destscale = actor->scale;
-			P_SetScale(filler, filler->destscale);
-			if (actor->eflags & MFE_VERTICALFLIP)
-				filler->flags2 |= MF2_OBJECTFLIP;
+			if (!P_MobjWasRemoved(filler))
+			{
+				filler->movefactor = movefactor;
+				P_SetTarget(&filler->target, actor);
+				filler->destscale = actor->scale;
+				P_SetScale(filler, filler->destscale);
+				if (actor->eflags & MFE_VERTICALFLIP)
+					filler->flags2 |= MF2_OBJECTFLIP;
+			}
 			if (movefactor <= 0)
 				break;
 			movefactor = -movefactor;
@@ -9780,7 +9946,7 @@ void A_SetObjectState(mobj_t *actor)
 		if (!target->player)
 			P_SetMobjState(target, locvar1);
 		else
-			P_SetPlayerMobjState(target, locvar1);
+			P_SetMobjState(target, locvar1);
 	}
 }
 
@@ -11110,6 +11276,8 @@ void A_TrapShot(mobj_t *actor)
 		type, frontoff, (INT16)(locvar2 & 65535), vertoff);
 
 	missile = P_SpawnMobj(x, y, z, type);
+	if (P_MobjWasRemoved(missile))
+		return;
 
 	if (actor->eflags & MFE_VERTICALFLIP)
 		missile->flags2 |= MF2_OBJECTFLIP;
@@ -11182,18 +11350,21 @@ void A_VileTarget(mobj_t *actor)
 							actor->target->y,
 							actor->target->z + ((actor->target->eflags & MFE_VERTICALFLIP) ? actor->target->height - mobjinfo[fogtype].height : 0),
 							fogtype);
-		if (actor->target->eflags & MFE_VERTICALFLIP)
+		if (!P_MobjWasRemoved(fog))
 		{
-			fog->eflags |= MFE_VERTICALFLIP;
-			fog->flags2 |= MF2_OBJECTFLIP;
-		}
-		fog->destscale = actor->target->scale;
-		P_SetScale(fog, fog->destscale);
+			if (actor->target->eflags & MFE_VERTICALFLIP)
+			{
+				fog->eflags |= MFE_VERTICALFLIP;
+				fog->flags2 |= MF2_OBJECTFLIP;
+			}
+			fog->destscale = actor->target->scale;
+			P_SetScale(fog, fog->destscale);
 
-		P_SetTarget(&actor->tracer, fog);
-		P_SetTarget(&fog->target, actor);
-		P_SetTarget(&fog->tracer, actor->target);
-		A_VileFire(fog);
+			P_SetTarget(&actor->tracer, fog);
+			P_SetTarget(&fog->target, actor);
+			P_SetTarget(&fog->tracer, actor->target);
+			A_VileFire(fog);
+		}
 	}
 	else
 	{
@@ -11213,19 +11384,22 @@ void A_VileTarget(mobj_t *actor)
 							players[i].mo->y,
 							players[i].mo->z + ((players[i].mo->eflags & MFE_VERTICALFLIP) ? players[i].mo->height - mobjinfo[fogtype].height : 0),
 							fogtype);
-			if (players[i].mo->eflags & MFE_VERTICALFLIP)
+			if (!P_MobjWasRemoved(fog))
 			{
-				fog->eflags |= MFE_VERTICALFLIP;
-				fog->flags2 |= MF2_OBJECTFLIP;
+				if (players[i].mo->eflags & MFE_VERTICALFLIP)
+				{
+					fog->eflags |= MFE_VERTICALFLIP;
+					fog->flags2 |= MF2_OBJECTFLIP;
+				}
+				fog->destscale = players[i].mo->scale;
+				P_SetScale(fog, fog->destscale);
+
+				if (players[i].mo == actor->target) // We only care to track the fog targeting who we REALLY hate right now
+					P_SetTarget(&actor->tracer, fog);
+				P_SetTarget(&fog->target, actor);
+				P_SetTarget(&fog->tracer, players[i].mo);
+				A_VileFire(fog);
 			}
-			fog->destscale = players[i].mo->scale;
-			P_SetScale(fog, fog->destscale);
-
-			if (players[i].mo == actor->target) // We only care to track the fog targeting who we REALLY hate right now
-				P_SetTarget(&actor->tracer, fog);
-			P_SetTarget(&fog->target, actor);
-			P_SetTarget(&fog->tracer, players[i].mo);
-			A_VileFire(fog);
 		}
 	}
 }
@@ -11702,6 +11876,8 @@ void A_BrakLobShot(mobj_t *actor)
 		typeOfShot = MT_CANNONBALL;
 	else typeOfShot = (mobjtype_t)locvar1;
 	shot = P_SpawnMobj(actor->x, actor->y, actor->z + FixedMul(locvar2*FRACUNIT, actor->scale), typeOfShot);
+	if (P_MobjWasRemoved(shot))
+		return;
 	if (shot->info->seesound)
 		S_StartSound(shot, shot->info->seesound);
 	P_SetTarget(&shot->target, actor); // where it came from
@@ -11768,6 +11944,8 @@ void A_NapalmScatter(mobj_t *actor)
 		const angle_t fa = (i*FINEANGLES/numToShoot) & FINEMASK;
 
 		mo = P_SpawnMobj(actor->x, actor->y, actor->z, typeOfShot);
+		if (P_MobjWasRemoved(mo))
+			continue;
 		P_SetTarget(&mo->target, actor->target); // Transfer target so Brak doesn't hit himself like an idiot
 
 		mo->angle = fa << ANGLETOFINESHIFT;
@@ -11793,6 +11971,8 @@ void A_SpawnFreshCopy(mobj_t *actor)
 		return;
 
 	newObject = P_SpawnMobjFromMobj(actor, 0, 0, 0, actor->type);
+	if (P_MobjWasRemoved(newObject))
+		return;
 	newObject->flags2 = actor->flags2 & MF2_AMBUSH;
 	newObject->angle = actor->angle;
 	newObject->color = actor->color;
@@ -11828,6 +12008,8 @@ mobj_t *P_InternalFlickySpawn(mobj_t *actor, mobjtype_t flickytype, fixed_t momz
 	}
 
 	flicky = P_SpawnMobjFromMobj(actor, offsx, offsy, 0, flickytype);
+	if (P_MobjWasRemoved(flicky))
+		return NULL;
 	flicky->angle = actor->angle;
 
 	if (flickytype == MT_SEED)
@@ -11939,8 +12121,11 @@ void A_FlickyCenter(mobj_t *actor)
 	if (!actor->tracer)
 	{
 		mobj_t *flicky = P_InternalFlickySpawn(actor, locvar1, 1, false, 0);
-		P_SetTarget(&flicky->target, actor);
-		P_SetTarget(&actor->tracer, flicky);
+		if (!P_MobjWasRemoved(flicky))
+		{
+			P_SetTarget(&flicky->target, actor);
+			P_SetTarget(&actor->tracer, flicky);
+		}
 
 		if (actor->spawnpoint)
 		{
@@ -11975,18 +12160,21 @@ void A_FlickyCenter(mobj_t *actor)
 			locvar1 = flickytype;
 		}
 
-		if (actor->flags & MF_GRENADEBOUNCE) // in-place
-			actor->tracer->fuse = 0;
-		else if (actor->flags & MF_SLIDEME) // aimless
+		if (!P_MobjWasRemoved(flicky))
 		{
-			actor->tracer->fuse = 0; // less than 2*TICRATE means move aimlessly.
-			actor->tracer->angle = P_RandomKey(180)*ANG2;
-		}
-		else //orbit
-			actor->tracer->fuse = FRACUNIT;
+			if (actor->flags & MF_GRENADEBOUNCE) // in-place
+				actor->tracer->fuse = 0;
+			else if (actor->flags & MF_SLIDEME) // aimless
+			{
+				actor->tracer->fuse = 0; // less than 2*TICRATE means move aimlessly.
+				actor->tracer->angle = P_RandomKey(180)*ANG2;
+			}
+			else //orbit
+				actor->tracer->fuse = FRACUNIT;
 
-		if (locvar1 == MT_FLICKY_08)
-			P_InternalFlickySetColor(actor->tracer, actor->extravalue2);
+			if (locvar1 == MT_FLICKY_08)
+				P_InternalFlickySetColor(actor->tracer, actor->extravalue2);
+		}
 
 		actor->extravalue2 = 0;
 	}
@@ -11997,7 +12185,8 @@ void A_FlickyCenter(mobj_t *actor)
 		fixed_t originy = actor->movefactor;
 		fixed_t originz = actor->watertop;
 
-		actor->tracer->fuse = FRACUNIT;
+		if (!P_MobjWasRemoved(actor->tracer))
+			actor->tracer->fuse = FRACUNIT;
 
 		// Impose default home radius if flicky orbits around player
 		if (!actor->extravalue1)
@@ -12029,6 +12218,8 @@ void P_InternalFlickyBubble(mobj_t *actor)
 			return;
 
 		overlay = P_SpawnMobj(actor->x, actor->y, actor->z, MT_OVERLAY);
+		if (P_MobjWasRemoved(overlay))
+			return;
 		P_SetMobjStateNF(overlay, mobjinfo[actor->type].raisestate);
 		P_SetTarget(&actor->tracer, overlay);
 		P_SetTarget(&overlay->target, actor);
@@ -12403,7 +12594,8 @@ void A_FlameParticle(mobj_t *actor)
 		P_RandomRange(rad, -rad)<<FRACBITS,
 		P_RandomRange(hei/2, hei)<<FRACBITS,
 		type);
-	P_SetObjectMomZ(particle, 2<<FRACBITS, false);
+	if (!P_MobjWasRemoved(particle))
+		P_SetObjectMomZ(particle, 2<<FRACBITS, false);
 }
 
 // Function: A_FadeOverlay
@@ -12422,6 +12614,8 @@ void A_FadeOverlay(mobj_t *actor)
 		return;
 
 	fade = P_SpawnGhostMobj(actor);
+	if (P_MobjWasRemoved(fade))
+		return;
 	fade->frame = actor->frame;
 
 	if (!(locvar1 & 1))
@@ -12589,6 +12783,8 @@ void A_MineExplode(mobj_t *actor)
 				actor->y+P_RandomRange(-dist, dist)*FRACUNIT,
 				actor->z+P_RandomRange(((actor->eflags & MFE_UNDERWATER) ? -dist : 0), dist)*FRACUNIT,
 				type);
+			if (P_MobjWasRemoved(b))
+				continue;
 			fixed_t dx = b->x - actor->x, dy = b->y - actor->y, dz = b->z - actor->z;
 			fixed_t dm = P_AproxDistance(dz, P_AproxDistance(dy, dx));
 			b->momx = FixedDiv(dx, dm)*3;
@@ -12720,6 +12916,8 @@ void A_SpawnParticleRelative(mobj_t *actor)
 	mo = P_SpawnMobj(actor->x + FixedMul(x<<FRACBITS, actor->scale),
 		actor->y + FixedMul(y<<FRACBITS, actor->scale),
 		(actor->eflags & MFE_VERTICALFLIP) ? ((actor->z + actor->height - mobjinfo[MT_PARTICLE].height) - FixedMul(z<<FRACBITS, actor->scale)) : (actor->z + FixedMul(z<<FRACBITS, actor->scale)), MT_PARTICLE);
+	if (P_MobjWasRemoved(mo))
+		return;
 
 	// Spawn objects with an angle matching the spawner's, rather than spawning Eastwards - Monster Iestyn
 	mo->angle = actor->angle;
@@ -13114,11 +13312,14 @@ void A_DoNPCSkid(mobj_t *actor)
 	if (!(leveltime % 3))
 	{
 		mobj_t *particle = P_SpawnMobjFromMobj(actor, 0, 0, 0, MT_SPINDUST);
-		particle->tics = 10;
+		if (!P_MobjWasRemoved(particle))
+		{
+			particle->tics = 10;
 
-		P_SetScale(particle, 2*actor->scale/3);
-		particle->destscale = actor->scale;
-		P_SetObjectMomZ(particle, FRACUNIT, false);
+			P_SetScale(particle, 2*actor->scale/3);
+			particle->destscale = actor->scale;
+			P_SetObjectMomZ(particle, FRACUNIT, false);
+		}
 	}
 }
 
@@ -13421,11 +13622,18 @@ void A_Boss5MakeJunk(mobj_t *actor)
 			if (actor->extravalue2 > 10)
 			{
 				mobj_t *front = P_SpawnMobjFromMobj(actor, 0, 0, 0, MT_VWREF);
-				broked = P_SpawnMobjFromMobj(front, 0, 0, 0, MT_VWREB);
-				front->z = broked->z = front->z - broked->height;
-				P_SetObjectMomZ(front, (4<<FRACBITS), false);
-				broked->momz = front->momz;
-				broked->fuse = front->fuse = (actor->height+(2*front->height))/front->momz;
+				if (!P_MobjWasRemoved(front))
+				{
+					P_SetObjectMomZ(front, (4<<FRACBITS), false);
+					broked = P_SpawnMobjFromMobj(front, 0, 0, 0, MT_VWREB);
+					if (!P_MobjWasRemoved(broked))
+					{
+						front->z -= broked->height;
+						broked->z = front->z;
+						broked->momz = front->momz;
+						broked->fuse = front->fuse = (actor->height+(2*front->height))/front->momz;
+					}
+				}
 			}
 			if (!(actor->colorized = !actor->colorized))
 				actor->frame |= FF_FULLBRIGHT;
@@ -13439,6 +13647,8 @@ void A_Boss5MakeJunk(mobj_t *actor)
 	while (i--)
 	{
 		broked = P_SpawnMobjFromMobj(actor, 0, 0, FRACUNIT, MT_BROKENROBOT);
+		if (P_MobjWasRemoved(broked))
+			continue;
 		if (locvar2 & 2)
 			broked->fuse = TICRATE;
 		else
@@ -13456,13 +13666,17 @@ void A_Boss5MakeJunk(mobj_t *actor)
 	if (locvar2 & 2)
 	{
 		broked = P_SpawnMobjFromMobj(actor, 0, 0, 64<<FRACBITS, MT_GHOST);
-		S_StartSound(broked, sfx_alart);
-		broked->fuse = states[S_FANG_INTRO12].tics+10;
-		P_SetMobjState(broked, S_ALART1);
+		if (!P_MobjWasRemoved(broked))
+		{
+			S_StartSound(broked, sfx_alart);
+			broked->fuse = states[S_FANG_INTRO12].tics+10;
+			P_SetMobjState(broked, S_ALART1);
+		}
 	}
 	else if (locvar2 & 1)
 	{
-		broked->z += broked->momz;
+		if (!P_MobjWasRemoved(broked))
+			broked->z += broked->momz;
 		S_StartSound(actor, sfx_s3kccs);
 		actor->flags &= ~MF_NOCLIPTHING;
 	}
@@ -13521,6 +13735,8 @@ static void P_DustRing(mobjtype_t mobjtype, UINT32 div, fixed_t x, fixed_t y, fi
 			z,
 			mobjtype
 			);
+		if (P_MobjWasRemoved(dust))
+			continue;
 
 		dust->angle = ang*i + ANGLE_90;
 		P_SetScale(dust, FixedMul(initscale, scale));
@@ -13609,7 +13825,7 @@ static boolean PIT_DustDevilLaunch(mobj_t *thing)
 			player->powers[pw_carry] = CR_DUSTDEVIL;
 			player->powers[pw_nocontrol] = 2;
 			P_SetTarget(&thing->tracer, dustdevil);
-			P_SetPlayerMobjState(thing, S_PLAY_PAIN);
+			P_SetMobjState(thing, S_PLAY_PAIN);
 
 			if (dist > dragamount)
 			{
@@ -13632,7 +13848,7 @@ static boolean PIT_DustDevilLaunch(mobj_t *thing)
 			player->powers[pw_nocontrol] = 0;
 			P_SetTarget(&thing->tracer, NULL);
 			S_StartSound(thing, sfx_wdjump);
-			P_SetPlayerMobjState(thing, S_PLAY_FALL);
+			P_SetMobjState(thing, S_PLAY_FALL);
 		}
 
 		thing->momz = thrust;
@@ -13673,9 +13889,12 @@ void A_DustDevilThink(mobj_t *actor)
 	if (P_IsObjectOnGround(actor)) {
 		angle_t dustang = ((P_RandomRange(0, 7)*ANGLE_45)>>ANGLETOFINESHIFT) & FINEMASK;
 		mobj_t *dust = P_SpawnMobj(actor->x + 96 * FixedMul(scale, FINECOSINE(dustang)), actor->y + 96 * FixedMul(scale, FINESINE(dustang)), actor->z, MT_ARIDDUST);
-		P_SetMobjState(dust, dust->info->spawnstate + P_RandomRange(0, 2));
-		dust->destscale = scale * 3;
-		P_SetScale(dust, dust->destscale);
+		if (!P_MobjWasRemoved(dust))
+		{
+			P_SetMobjState(dust, dust->info->spawnstate + P_RandomRange(0, 2));
+			dust->destscale = scale * 3;
+			P_SetScale(dust, dust->destscale);
+		}
 	}
 
 	actor->extravalue1++;
@@ -13691,6 +13910,8 @@ void A_DustDevilThink(mobj_t *actor)
 			fixed_t pz = actor->z;
 
 			layer = P_SpawnMobj(px, py, pz, MT_DUSTLAYER);
+			if (P_MobjWasRemoved(layer))
+				continue;
 			layer->momz = 5 * scale;
 			layer->angle = ANGLE_90 + ANGLE_90*i;
 			layer->extravalue1 = TICRATE * 3;
@@ -13876,6 +14097,8 @@ void A_DebrisRandom(mobj_t *actor)
 static mobj_t *P_TrainSeg(mobj_t *src, fixed_t x, fixed_t y, fixed_t z, angle_t ang, spritenum_t spr, UINT32 frame)
 {
 	mobj_t *s = P_SpawnMobj(x, y, z, MT_TRAINSEG);
+	if (P_MobjWasRemoved(s))
+		return NULL;
 	s->fuse = 16*TICRATE;
 	s->sprite = spr;
 	s->frame = frame|FF_PAPERSPRITE;
@@ -14028,7 +14251,7 @@ static void P_SnapperLegPlace(mobj_t *mo)
 
 	// Move as many legs as available.
 	seg = seg->tracer;
-	do
+	while (seg)
 	{
 		o1 = seg->extravalue1;
 		o2 = seg->extravalue2;
@@ -14053,7 +14276,7 @@ static void P_SnapperLegPlace(mobj_t *mo)
 		seg->angle = R_PointToAngle2(mo->x, mo->y, seg->x, seg->y);
 
 		seg = seg->tracer;
-	} while (seg);
+	}
 }
 
 //
@@ -14077,6 +14300,12 @@ void A_SnapperSpawn(mobj_t *actor)
 
 	// It spawns 1 head.
 	seg = P_SpawnMobjFromMobj(actor, 0, 0, 0, headtype);
+	if (P_MobjWasRemoved(seg))
+	{
+		// if we can't spawn the head, don't spawn the snapper at all
+		P_RemoveMobj(actor);
+		return;
+	}
 	P_SetTarget(&ptr->tracer, seg);
 	ptr = seg;
 
@@ -14084,6 +14313,8 @@ void A_SnapperSpawn(mobj_t *actor)
 	for (i = 1; i <= 4; i++)
 	{
 		seg = P_SpawnMobjFromMobj(actor, 0, 0, 0, legtype);
+		if (P_MobjWasRemoved(seg))
+			continue;
 		P_SetTarget(&ptr->tracer, seg);
 		ptr = seg;
 
@@ -14185,7 +14416,8 @@ void A_SnapperThinker(mobj_t *actor)
 		if (actor->reactiontime < 4)
 		{
 			mobj_t *dust = P_SpawnMobj(x0, y0, actor->z, MT_SPINDUST);
-			P_Thrust(dust, ang + ANGLE_180 + FixedAngle(P_RandomRange(-20, 20)*FRACUNIT), speed*FRACUNIT);
+			if (!P_MobjWasRemoved(dust))
+				P_Thrust(dust, ang + ANGLE_180 + FixedAngle(P_RandomRange(-20, 20)*FRACUNIT), speed*FRACUNIT);
 		}
 
 		if (actor->extravalue2 == 0)
@@ -14295,6 +14527,8 @@ void A_MinecartSparkThink(mobj_t *actor)
 	for (i = 1; i <= 8; i++)
 	{
 		mobj_t *trail = P_SpawnMobj(actor->x - dx*i, actor->y - dy*i, actor->z - dz*i, MT_PARTICLE);
+		if (P_MobjWasRemoved(trail))
+			continue;
 		trail->tics = 2;
 		trail->sprite = actor->sprite;
 		P_SetScale(trail, trail->scale/4);
@@ -14380,7 +14614,8 @@ void A_LavafallLava(mobj_t *actor)
 		return;
 
 	lavafall = P_SpawnMobjFromMobj(actor, 0, 0, -8*FRACUNIT, MT_LAVAFALL_LAVA);
-	lavafall->momz = -P_MobjFlip(actor)*25*FRACUNIT;
+	if (!P_MobjWasRemoved(lavafall))
+		lavafall->momz = -P_MobjFlip(actor)*25*FRACUNIT;
 }
 
 // Function: A_FallingLavaCheck
@@ -14455,9 +14690,18 @@ void A_SpawnPterabytes(mobj_t *actor)
 		c = FINECOSINE(fa);
 		s = FINESINE(fa);
 		waypoint = P_SpawnMobjFromMobj(actor, FixedMul(c, rad), FixedMul(s, rad), 0, MT_PTERABYTEWAYPOINT);
+		if (P_MobjWasRemoved(waypoint))
+			continue;
+
 		waypoint->angle = ang + ANGLE_90;
 		P_SetTarget(&waypoint->tracer, actor);
 		ptera = P_SpawnMobjFromMobj(waypoint, 0, 0, 0, MT_PTERABYTE);
+		if (P_MobjWasRemoved(ptera))
+		{
+			P_RemoveMobj(waypoint);
+			continue;
+		}
+
 		ptera->angle = waypoint->angle;
 		P_SetTarget(&ptera->tracer, waypoint);
 		ptera->extravalue1 = 0;
@@ -14505,14 +14749,17 @@ void A_RolloutSpawn(mobj_t *actor)
 		|| P_MobjWasRemoved(actor->target)
 		|| P_AproxDistance(actor->x - actor->target->x, actor->y - actor->target->y) > locvar1)
 	{
-		actor->target = P_SpawnMobj(actor->x, actor->y, actor->z, locvar2);
-		actor->target->flags2 |= (actor->flags2 & (MF2_AMBUSH | MF2_OBJECTFLIP)) | MF2_SLIDEPUSH;
-		actor->target->eflags |= (actor->eflags & MFE_VERTICALFLIP);
-
-		if (actor->target->flags2 & MF2_AMBUSH)
+		P_SetTarget(&actor->target, P_SpawnMobj(actor->x, actor->y, actor->z, locvar2));
+		if (!P_MobjWasRemoved(actor->target))
 		{
-			actor->target->color = SKINCOLOR_SUPERRUST3;
-			actor->target->colorized = true;
+			actor->target->flags2 |= (actor->flags2 & (MF2_AMBUSH | MF2_OBJECTFLIP)) | MF2_SLIDEPUSH;
+			actor->target->eflags |= (actor->eflags & MFE_VERTICALFLIP);
+
+			if (actor->target->flags2 & MF2_AMBUSH)
+			{
+				actor->target->color = SKINCOLOR_SUPERRUST3;
+				actor->target->colorized = true;
+			}
 		}
 	}
 }
@@ -14633,6 +14880,8 @@ void A_DragonbomberSpawn(mobj_t *actor)
 		x = P_ReturnThrustX(mo, mo->angle, -mo->radius << 1);
 		y = P_ReturnThrustY(mo, mo->angle, -mo->radius << 1);
 		segment = P_SpawnMobjFromMobj(mo, x, y, 0, MT_DRAGONTAIL);
+		if (P_MobjWasRemoved(segment))
+			continue;
 		P_SetTarget(&segment->target, mo);
 		P_SetTarget(&mo->tracer, segment);
 		segment->angle = mo->angle;
@@ -14641,6 +14890,8 @@ void A_DragonbomberSpawn(mobj_t *actor)
 	for (i = 0; i < 2; i++) // spawn wings
 	{
 		mo = P_SpawnMobjFromMobj(actor, 0, 0, 0, MT_DRAGONWING);
+		if (P_MobjWasRemoved(mo))
+			continue;
 		P_SetTarget(&mo->target, actor);
 		mo->movedir = ANGLE_90 + i * ANGLE_180;
 	}
diff --git a/src/p_floor.c b/src/p_floor.c
index 38f0c5a0fbc496564e7b69c96f7cacd8a93287f6..118b4bdbc7c11f7030380dd0472bcf74ecba5b5b 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -1212,6 +1212,19 @@ static boolean T_SectorHasEnemies(sector_t *sec)
 	return false;
 }
 
+static void T_UpdateMobjPlaneZ(sector_t *sec)
+{
+	msecnode_t *node = sec->touching_thinglist; // things touching this sector
+	mobj_t *mo;
+	while (node)
+	{
+		mo = node->m_thing;
+		mo->floorz = P_FloorzAtPos(mo->x, mo->y, mo->z, mo->height);
+		mo->ceilingz = P_CeilingzAtPos(mo->x, mo->y, mo->z, mo->height);
+		node = node->m_thinglist_next;
+	}
+}
+
 //
 // T_NoEnemiesThinker
 //
@@ -1921,6 +1934,9 @@ void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
 				for (c = topz; c > bottomz; c -= spacing)
 				{
 					spawned = P_SpawnMobj(a, b, c, type);
+					if (P_MobjWasRemoved(spawned))
+						continue;
+
 					spawned->angle += P_RandomKey(36)*ANG10; // irrelevant for default objects but might make sense for some custom ones
 
 					if (fromcenter)
@@ -1938,6 +1954,7 @@ void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
 	// no longer exists (can't collide with again)
 	rover->fofflags &= ~FOF_EXISTS;
 	rover->master->frontsector->moved = true;
+	T_UpdateMobjPlaneZ(sec); // prevent objects from floating
 	P_RecalcPrecipInSector(sec);
 }
 
diff --git a/src/p_inter.c b/src/p_inter.c
index c3811cbe4e827f41133f2c1693a932d33d36a48d..9a6f0ad06f6ea6ea65a5eda91bee070fb5827585 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -256,6 +256,9 @@ void P_DoNightsScore(player_t *player)
 		player->linktimer = nightslinktics;
 	}
 
+	if (P_MobjWasRemoved(dummymo))
+		return;
+
 	// Award 10-100 score, doubled if bonus time is active
 	P_AddPlayerScore(player, min(player->linkcount,10)*(player->bonustime ? 20 : 10));
 	P_SetMobjState(dummymo, (player->bonustime ? dummymo->info->xdeathstate : dummymo->info->spawnstate) + min(player->linkcount,10)-1);
@@ -526,7 +529,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				else if (player->pflags & PF_GLIDING && !P_IsObjectOnGround(toucher))
 				{
 					player->pflags &= ~(PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE);
-					P_SetPlayerMobjState(toucher, S_PLAY_FALL);
+					P_SetMobjState(toucher, S_PLAY_FALL);
 					toucher->momz += P_MobjFlip(toucher) * (player->speed >> 3);
 					toucher->momx = 7*toucher->momx>>3;
 					toucher->momy = 7*toucher->momy>>3;
@@ -1246,7 +1249,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 
 					P_ResetPlayer(player);
 
-					P_SetPlayerMobjState(toucher, S_PLAY_FALL);
+					P_SetMobjState(toucher, S_PLAY_FALL);
 				}
 			}
 			return;
@@ -1301,7 +1304,8 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			{
 				// A flicky orbits us now
 				mobj_t *flickyobj = P_SpawnMobj(toucher->x, toucher->y, toucher->z + toucher->info->height, MT_NIGHTOPIANHELPER);
-				P_SetTarget(&flickyobj->target, toucher);
+				if (!P_MobjWasRemoved(flickyobj))
+					P_SetTarget(&flickyobj->target, toucher);
 
 				player->powers[pw_nights_helper] = (UINT16)special->info->speed;
 			}
@@ -1312,7 +1316,8 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 					if (playeringame[i] && players[i].mo && players[i].powers[pw_carry] == CR_NIGHTSMODE) {
 						players[i].powers[pw_nights_helper] = (UINT16)special->info->speed;
 						flickyobj = P_SpawnMobj(players[i].mo->x, players[i].mo->y, players[i].mo->z + players[i].mo->info->height, MT_NIGHTOPIANHELPER);
-						P_SetTarget(&flickyobj->target, players[i].mo);
+						if (!P_MobjWasRemoved(flickyobj))
+							P_SetTarget(&flickyobj->target, players[i].mo);
 					}
 				if (special->info->deathsound != sfx_None)
 					S_StartSound(NULL, special->info->deathsound);
@@ -1553,7 +1558,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				if (player->pflags & PF_GLIDING && !P_IsObjectOnGround(toucher))
 				{
 					player->pflags &= ~(PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE);
-					P_SetPlayerMobjState(toucher, S_PLAY_FALL);
+					P_SetMobjState(toucher, S_PLAY_FALL);
 					toucher->momz += P_MobjFlip(toucher) * (player->speed >> 3);
 					toucher->momx = 7*toucher->momx>>3;
 					toucher->momy = 7*toucher->momy>>3;
@@ -1604,7 +1609,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 					if (player->pflags & PF_GLIDING && !P_IsObjectOnGround(toucher))
 					{
 						player->pflags &= ~(PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE);
-						P_SetPlayerMobjState(toucher, S_PLAY_FALL);
+						P_SetMobjState(toucher, S_PLAY_FALL);
 						toucher->momz += P_MobjFlip(toucher) * (player->speed >> 3);
 						toucher->momx = 7*toucher->momx>>3;
 						toucher->momy = 7*toucher->momy>>3;
@@ -1640,7 +1645,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			special->momx = special->momy = 0;
 
 			// Buenos Dias Mandy
-			P_SetPlayerMobjState(toucher, S_PLAY_STUN);
+			P_SetMobjState(toucher, S_PLAY_STUN);
 			player->pflags &= ~PF_APPLYAUTOBRAKE;
 			P_ResetPlayer(player);
 			player->drawangle = special->angle + ANGLE_180;
@@ -1719,7 +1724,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				{
 					player->powers[pw_carry] = CR_MACESPIN;
 					S_StartSound(toucher, sfx_spin);
-					P_SetPlayerMobjState(toucher, S_PLAY_ROLL);
+					P_SetMobjState(toucher, S_PLAY_ROLL);
 				}
 				else
 					player->powers[pw_carry] = CR_GENERIC;
@@ -1780,7 +1785,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			{
 				if (player->bot && player->bot != BOT_MPAI && toucher->state-states != S_PLAY_GASP)
 					S_StartSound(toucher, special->info->deathsound); // Force it to play a sound for bots
-				P_SetPlayerMobjState(toucher, S_PLAY_GASP);
+				P_SetMobjState(toucher, S_PLAY_GASP);
 				P_ResetPlayer(player);
 			}
 
@@ -1821,9 +1826,12 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			if (!player->bot && player->bot != BOT_MPAI && special->fuse <= TICRATE && player->powers[pw_carry] != CR_MINECART && !(player->powers[pw_ignorelatch] & (1<<15)))
 			{
 				mobj_t *mcart = P_SpawnMobj(special->x, special->y, special->z, MT_MINECART);
-				P_SetTarget(&mcart->target, toucher);
-				mcart->angle = toucher->angle = player->drawangle = special->angle;
-				mcart->friction = FRACUNIT;
+				if (!P_MobjWasRemoved(mcart))
+				{
+					P_SetTarget(&mcart->target, toucher);
+					mcart->angle = toucher->angle = player->drawangle = special->angle;
+					mcart->friction = FRACUNIT;
+				}
 
 				P_ResetPlayer(player);
 				player->pflags |= PF_JUMPDOWN;
@@ -1847,7 +1855,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				toucher->momz = toucher->tracer->momz + P_AproxDistance(toucher->tracer->momx, toucher->tracer->momy)/2;
 				P_ResetPlayer(player);
 				player->pflags &= ~PF_APPLYAUTOBRAKE;
-				P_SetPlayerMobjState(toucher, S_PLAY_FALL);
+				P_SetMobjState(toucher, S_PLAY_FALL);
 				P_SetTarget(&toucher->tracer->target, NULL);
 				P_KillMobj(toucher->tracer, toucher, special, 0);
 				P_SetTarget(&toucher->tracer, NULL);
@@ -2593,7 +2601,8 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 							break;
 					}
 
-					P_SetMobjState(scoremobj, scorestate);
+					if (!P_MobjWasRemoved(scoremobj))
+						P_SetMobjState(scoremobj, scorestate);
 
 					source->player->scoreadd = locscoreadd;
 				}
@@ -2754,6 +2763,8 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 				mo = P_SpawnMobj(inflictor->x + inflictor->momx, inflictor->y + inflictor->momy, inflictor->z + (inflictor->height / 2) + inflictor->momz, MT_EXTRALARGEBUBBLE);
 			else
 				mo = P_SpawnMobj(target->x, target->y, target->z, MT_EXTRALARGEBUBBLE);
+			if (P_MobjWasRemoved(mo))
+				break;
 			mo->destscale = target->scale;
 			P_SetScale(mo, mo->destscale);
 			P_SetMobjState(mo, mo->info->raisestate);
@@ -2832,8 +2843,11 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 					mo->angle = FixedAngle((P_RandomKey(36)*10)<<FRACBITS);
 
 					mo2 = P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_BOSSJUNK);
-					mo2->angle = mo->angle;
-					P_SetMobjState(mo2, S_BOSSSEBH2);
+					if (!P_MobjWasRemoved(mo2))
+					{
+						mo2->angle = mo->angle;
+						P_SetMobjState(mo2, S_BOSSSEBH2);
+					}
 
 					if (++i == 2) // we've already removed 2 of these, let's stop now
 						break;
@@ -2932,7 +2946,10 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 			if (flip)
 				momz *= -1;
 #define makechunk(angtweak, xmov, ymov) \
+			do {\
 			chunk = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_SPIKE);\
+			if (P_MobjWasRemoved(chunk))\
+				break;\
 			P_SetMobjState(chunk, target->info->xdeathstate);\
 			chunk->health = 0;\
 			chunk->angle = angtweak;\
@@ -2942,7 +2959,8 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 			chunk->y += ymov;\
 			P_SetThingPosition(chunk);\
 			P_InstaThrust(chunk,chunk->angle, 4*scale);\
-			chunk->momz = momz
+			chunk->momz = momz;\
+			} while (0)
 
 			makechunk(ang + ANGLE_180, -xoffs, -yoffs);
 			makechunk(ang, xoffs, yoffs);
@@ -2955,20 +2973,23 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 			momz *= -1;
 
 		chunk = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_SPIKE);
-		P_SetMobjState(chunk, target->info->deathstate);
-		chunk->health = 0;
-		chunk->angle = ang + ANGLE_180;
-		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;
+		if (!P_MobjWasRemoved(chunk))
+		{
+			P_SetMobjState(chunk, target->info->deathstate);
+			chunk->health = 0;
+			chunk->angle = ang + ANGLE_180;
+			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;
@@ -2977,7 +2998,10 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 		target->flags = MF_NOCLIP;
 		target->x += xoffs;
 		target->y += yoffs;
-		target->z = chunk->z;
+		if (flip)
+			target->z -= 12*scale;
+		else
+			target->z += 12*scale;
 		P_SetThingPosition(target);
 		P_InstaThrust(target, target->angle, 2*scale);
 		target->momz = momz;
@@ -3000,21 +3024,25 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 			sprflip = P_RandomChance(FRACUNIT/2);
 
 #define makechunk(angtweak, xmov, ymov) \
-			chunk = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_WALLSPIKE);\
-			P_SetMobjState(chunk, target->info->xdeathstate);\
-			chunk->health = 0;\
-			chunk->angle = target->angle;\
-			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
+			do {\
+				chunk = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_WALLSPIKE);\
+				if (P_MobjWasRemoved(chunk))\
+					break;\
+				P_SetMobjState(chunk, target->info->xdeathstate);\
+				chunk->health = 0;\
+				chunk->angle = target->angle;\
+				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;\
+			} while (0)
 
 			makechunk(ang + ANGLE_180, -xoffs, -yoffs);
 			sprflip = !sprflip;
@@ -3026,21 +3054,23 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 		sprflip = P_RandomChance(FRACUNIT/2);
 
 		chunk = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_WALLSPIKE);
-
-		P_SetMobjState(chunk, target->info->deathstate);
-		chunk->health = 0;
-		chunk->angle = target->angle;
-		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;
+		if (!P_MobjWasRemoved(chunk))
+		{
+			P_SetMobjState(chunk, target->info->deathstate);
+			chunk->health = 0;
+			chunk->angle = target->angle;
+			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;
@@ -3059,9 +3089,9 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 	else if (target->player)
 	{
 		if (damagetype == DMG_DROWNED || damagetype == DMG_SPACEDROWN)
-			P_SetPlayerMobjState(target, target->info->xdeathstate);
+			P_SetMobjState(target, target->info->xdeathstate);
 		else
-			P_SetPlayerMobjState(target, target->info->deathstate);
+			P_SetMobjState(target, target->info->deathstate);
 	}
 	else
 #ifdef DEBUG_NULL_DEATHSTATE
@@ -3115,7 +3145,7 @@ static void P_NiGHTSDamage(mobj_t *target, mobj_t *source)
 		}
 
 		player->powers[pw_flashing] = flashingtics;
-		P_SetPlayerMobjState(target, S_PLAY_NIGHTS_STUN);
+		P_SetMobjState(target, S_PLAY_NIGHTS_STUN);
 		S_StartSound(target, sfx_nghurt);
 
 		player->mo->spriteroll = 0;
@@ -3336,7 +3366,7 @@ static void P_KillPlayer(player_t *player, mobj_t *source, INT32 damage)
 	if (!player->spectator)
 		player->mo->flags2 &= ~MF2_DONTDRAW;
 
-	P_SetPlayerMobjState(player->mo, player->mo->info->deathstate);
+	P_SetMobjState(player->mo, player->mo->info->deathstate);
 	if ((gametyperules & GTR_TEAMFLAGS) && (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG)))
 	{
 		P_PlayerFlagBurst(player, false);
@@ -3410,7 +3440,7 @@ static void P_SuperDamage(player_t *player, mobj_t *inflictor, mobj_t *source, I
 
 	P_InstaThrust(player->mo, ang, fallbackspeed);
 
-	P_SetPlayerMobjState(player->mo, S_PLAY_STUN);
+	P_SetMobjState(player->mo, S_PLAY_STUN);
 
 	P_ResetPlayer(player);
 
@@ -3921,6 +3951,8 @@ void P_PlayerRingBurst(player_t *player, INT32 num_rings)
 			z += player->mo->height - mobjinfo[objType].height;
 
 		mo = P_SpawnMobj(player->mo->x, player->mo->y, z, objType);
+		if (P_MobjWasRemoved(mo))
+			continue;
 
 		mo->fuse = 8*TICRATE;
 		P_SetTarget(&mo->target, player->mo);
@@ -4054,6 +4086,9 @@ void P_PlayerWeaponPanelBurst(player_t *player)
 			z += player->mo->height - mobjinfo[weptype].height;
 
 		mo = P_SpawnMobj(player->mo->x, player->mo->y, z, weptype);
+		if (P_MobjWasRemoved(mo))
+			continue;
+
 		mo->reactiontime = ammoamt;
 		mo->flags2 |= MF2_DONTRESPAWN;
 		mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT);
@@ -4087,13 +4122,13 @@ void P_PlayerWeaponAmmoBurst(player_t *player)
 	mobj_t *mo;
 	angle_t fa;
 	fixed_t ns;
-	INT32 i = 0;
+	INT32 i;
 	fixed_t z;
 
 	mobjtype_t weptype = 0;
 	powertype_t power = 0;
 
-	while (true)
+	for (i = 0;; i++)
 	{
 		if (player->powers[pw_bouncering])
 		{
@@ -4138,6 +4173,8 @@ void P_PlayerWeaponAmmoBurst(player_t *player)
 			z += player->mo->height - mobjinfo[weptype].height;
 
 		mo = P_SpawnMobj(player->mo->x, player->mo->y, z, weptype);
+		if (P_MobjWasRemoved(mo))
+			continue;
 		mo->health = player->powers[power];
 		mo->flags2 |= MF2_DONTRESPAWN;
 		mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT);
@@ -4163,8 +4200,6 @@ void P_PlayerWeaponAmmoBurst(player_t *player)
 
 		if (i & 1)
 			P_SetObjectMomZ(mo, 3*FRACUNIT, true);
-
-		i++;
 	}
 }
 
@@ -4189,45 +4224,50 @@ void P_PlayerWeaponPanelOrAmmoBurst(player_t *player)
 		player->ringweapons &= ~rwflag; \
 		SETUP_DROP(pickup) \
 		mo = P_SpawnMobj(player->mo->x, player->mo->y, z, pickup); \
-		mo->reactiontime = 0; \
-		mo->flags2 |= MF2_DONTRESPAWN; \
-		mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT); \
-		P_SetTarget(&mo->target, player->mo); \
-		mo->fuse = 12*TICRATE; \
-		mo->destscale = player->mo->scale; \
-		P_SetScale(mo, player->mo->scale); \
-		mo->momx = FixedMul(FINECOSINE(fa),ns); \
-		if (!(twodlevel || (player->mo->flags2 & MF2_TWOD))) \
-			mo->momy = FixedMul(FINESINE(fa),ns); \
-		P_SetObjectMomZ(mo, 4*FRACUNIT, false); \
-		if (i & 1) \
-			P_SetObjectMomZ(mo, 4*FRACUNIT, true); \
-		if (player->mo->eflags & MFE_VERTICALFLIP) \
-			mo->flags2 |= MF2_OBJECTFLIP; \
-		++i; \
+		if (!P_MobjWasRemoved(mo)) \
+		{ \
+			mo->reactiontime = 0; \
+			mo->flags2 |= MF2_DONTRESPAWN; \
+			mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT); \
+			P_SetTarget(&mo->target, player->mo); \
+			mo->fuse = 12*TICRATE; \
+			mo->destscale = player->mo->scale; \
+			P_SetScale(mo, player->mo->scale); \
+			mo->momx = FixedMul(FINECOSINE(fa),ns); \
+			if (!(twodlevel || (player->mo->flags2 & MF2_TWOD))) \
+				mo->momy = FixedMul(FINESINE(fa),ns); \
+			P_SetObjectMomZ(mo, 4*FRACUNIT, false); \
+			if (i & 1) \
+				P_SetObjectMomZ(mo, 4*FRACUNIT, true); \
+			if (player->mo->eflags & MFE_VERTICALFLIP) \
+				mo->flags2 |= MF2_OBJECTFLIP; \
+		} \
 	} \
 	else if (player->powers[power] > 0) \
 	{ \
 		SETUP_DROP(ammo) \
 		mo = P_SpawnMobj(player->mo->x, player->mo->y, z, ammo); \
-		mo->health = player->powers[power]; \
-		mo->flags2 |= MF2_DONTRESPAWN; \
-		mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT); \
-		P_SetTarget(&mo->target, player->mo); \
-		mo->fuse = 12*TICRATE; \
-		mo->destscale = player->mo->scale; \
-		P_SetScale(mo, player->mo->scale); \
-		mo->momx = FixedMul(FINECOSINE(fa),ns); \
-		if (!(twodlevel || (player->mo->flags2 & MF2_TWOD))) \
-			mo->momy = FixedMul(FINESINE(fa),ns); \
-		P_SetObjectMomZ(mo, 3*FRACUNIT, false); \
-		if (i & 1) \
-			P_SetObjectMomZ(mo, 3*FRACUNIT, true); \
-		if (player->mo->eflags & MFE_VERTICALFLIP) \
-			mo->flags2 |= MF2_OBJECTFLIP; \
-		player->powers[power] = 0; \
-		++i; \
-	}
+		if (!P_MobjWasRemoved(mo)) \
+		{ \
+			mo->health = player->powers[power]; \
+			mo->flags2 |= MF2_DONTRESPAWN; \
+			mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT); \
+			P_SetTarget(&mo->target, player->mo); \
+			mo->fuse = 12*TICRATE; \
+			mo->destscale = player->mo->scale; \
+			P_SetScale(mo, player->mo->scale); \
+			mo->momx = FixedMul(FINECOSINE(fa),ns); \
+			if (!(twodlevel || (player->mo->flags2 & MF2_TWOD))) \
+				mo->momy = FixedMul(FINESINE(fa),ns); \
+			P_SetObjectMomZ(mo, 3*FRACUNIT, false); \
+			if (i & 1) \
+				P_SetObjectMomZ(mo, 3*FRACUNIT, true); \
+			if (player->mo->eflags & MFE_VERTICALFLIP) \
+				mo->flags2 |= MF2_OBJECTFLIP; \
+			player->powers[power] = 0; \
+		} \
+	} \
+	++i
 
 	DROP_WEAPON(RW_BOUNCE, MT_BOUNCEPICKUP, MT_BOUNCERING, pw_bouncering);
 	DROP_WEAPON(RW_RAIL, MT_RAILPICKUP, MT_RAILRING, pw_railring);
@@ -4351,23 +4391,26 @@ void P_PlayerEmeraldBurst(player_t *player, boolean toss)
 				momy = 0;
 
 			mo = P_SpawnMobj(player->mo->x, player->mo->y, z, MT_FLINGEMERALD);
-			mo->health = 1;
-			mo->threshold = stoneflag;
-			mo->flags2 |= (MF2_DONTRESPAWN|MF2_SLIDEPUSH);
-			mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT);
-			P_SetTarget(&mo->target, player->mo);
-			mo->fuse = 12*TICRATE;
-			P_SetMobjState(mo, statenum);
+			if (!P_MobjWasRemoved(mo))
+			{
+				mo->health = 1;
+				mo->threshold = stoneflag;
+				mo->flags2 |= (MF2_DONTRESPAWN|MF2_SLIDEPUSH);
+				mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT);
+				P_SetTarget(&mo->target, player->mo);
+				mo->fuse = 12*TICRATE;
+				P_SetMobjState(mo, statenum);
 
-			mo->momx = momx;
-			mo->momy = momy;
+				mo->momx = momx;
+				mo->momy = momy;
 
-			P_SetObjectMomZ(mo, 3*FRACUNIT, false);
+				P_SetObjectMomZ(mo, 3*FRACUNIT, false);
 
-			if (player->mo->eflags & MFE_VERTICALFLIP)
-			{
-				mo->momz = -mo->momz;
-				mo->flags2 |= MF2_OBJECTFLIP;
+				if (player->mo->eflags & MFE_VERTICALFLIP)
+				{
+					mo->momz = -mo->momz;
+					mo->flags2 |= MF2_OBJECTFLIP;
+				}
 			}
 
 			if (toss)
@@ -4395,6 +4438,8 @@ void P_PlayerFlagBurst(player_t *player, boolean toss)
 		type = MT_BLUEFLAG;
 
 	flag = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, type);
+	if (P_MobjWasRemoved(flag))
+		return;
 
 	if (player->mo->eflags & MFE_VERTICALFLIP)
 	{
diff --git a/src/p_local.h b/src/p_local.h
index ba033dc06fda53ac1dda5db3a1fb3e2192fc4bc8..84a0aace09b83b4a6f5bc71552ccb910a486ae43 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -71,6 +71,7 @@ typedef enum
 	NUM_THINKERLISTS
 } thinklistnum_t; /**< Thinker lists. */
 extern thinker_t thlist[];
+extern mobj_t *mobjcache;
 
 void P_InitThinkers(void);
 void P_AddThinker(const thinklistnum_t n, thinker_t *thinker);
@@ -288,7 +289,7 @@ mobjtype_t P_GetMobjtype(UINT16 mthingtype);
 
 void P_RespawnSpecials(void);
 
-mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type);
+mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type, ...);
 
 void P_RecalcPrecipInSector(sector_t *sector);
 void P_PrecipitationEffects(void);
@@ -296,7 +297,6 @@ void P_PrecipitationEffects(void);
 void P_RemoveMobj(mobj_t *th);
 boolean P_MobjWasRemoved(mobj_t *th);
 void P_RemoveSavegameMobj(mobj_t *th);
-boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state);
 boolean P_SetMobjState(mobj_t *mobj, statenum_t state);
 void P_RunShields(void);
 void P_RunOverlays(void);
diff --git a/src/p_map.c b/src/p_map.c
index 6a152c563de0636c160f25103a82566d85e19b08..e01798054970ee3364fa65883fa25f577c700ed7 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -264,7 +264,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 				UINT8 secondjump = object->player->secondjump;
 				UINT16 tailsfly = object->player->powers[pw_tailsfly];
 				if (object->player->pflags & PF_GLIDING)
-					P_SetPlayerMobjState(object, S_PLAY_FALL);
+					P_SetMobjState(object, S_PLAY_FALL);
 				P_ResetPlayer(object->player);
 				object->player->pflags |= pflags;
 				object->player->secondjump = secondjump;
@@ -403,7 +403,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 		}
 
 		if (object->player->pflags & PF_GLIDING)
-			P_SetPlayerMobjState(object, S_PLAY_FALL);
+			P_SetMobjState(object, S_PLAY_FALL);
 		if ((spring->info->painchance == 3))
 		{
 			if (!(pflags = (object->player->pflags & PF_SPINNING)) &&
@@ -411,11 +411,11 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 				|| (spring->flags2 & MF2_AMBUSH)))
 			{
 				pflags = PF_SPINNING;
-				P_SetPlayerMobjState(object, S_PLAY_ROLL);
+				P_SetMobjState(object, S_PLAY_ROLL);
 				S_StartSound(object, sfx_spin);
 			}
 			else
-				P_SetPlayerMobjState(object, S_PLAY_ROLL);
+				P_SetMobjState(object, S_PLAY_ROLL);
 		}
 		else
 		{
@@ -424,7 +424,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 			pflags = object->player->pflags & (PF_STARTJUMP | PF_JUMPED | PF_NOJUMPDAMAGE | PF_SPINNING | PF_THOKKED | PF_BOUNCING); // I still need these.
 
 			if (wasSpindashing) // Ensure we're in the rolling state, and not spindash.
-				P_SetPlayerMobjState(object, S_PLAY_ROLL);
+				P_SetMobjState(object, S_PLAY_ROLL);
 
 			if (object->player->charability == CA_GLIDEANDCLIMB && object->player->skidtime && (pflags & PF_JUMPED))
 			{
@@ -439,7 +439,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 		if (spring->info->painchance == 1) // For all those ancient, SOC'd abilities.
 		{
 			object->player->pflags |= P_GetJumpFlags(object->player);
-			P_SetPlayerMobjState(object, S_PLAY_JUMP);
+			P_SetMobjState(object, S_PLAY_JUMP);
 		}
 		else if ((spring->info->painchance == 2) || ((spring->info->painchance != 3) && (pflags & PF_BOUNCING))) // Adding momentum only.
 		{
@@ -456,16 +456,16 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 				object->player->secondjump = secondjump;
 			}
 			else if (object->player->dashmode >= DASHMODE_THRESHOLD)
-				P_SetPlayerMobjState(object, S_PLAY_DASH);
+				P_SetMobjState(object, S_PLAY_DASH);
 			else if (P_IsObjectOnGround(object))
-				P_SetPlayerMobjState(object, (horizspeed >= FixedMul(object->player->runspeed, object->scale)) ? S_PLAY_RUN : S_PLAY_WALK);
+				P_SetMobjState(object, (horizspeed >= FixedMul(object->player->runspeed, object->scale)) ? S_PLAY_RUN : S_PLAY_WALK);
 			else
-				P_SetPlayerMobjState(object, (object->momz > 0) ? S_PLAY_SPRING : S_PLAY_FALL);
+				P_SetMobjState(object, (object->momz > 0) ? S_PLAY_SPRING : S_PLAY_FALL);
 		}
 		else if (P_MobjFlip(object)*vertispeed > 0)
-			P_SetPlayerMobjState(object, S_PLAY_SPRING);
+			P_SetMobjState(object, S_PLAY_SPRING);
 		else
-			P_SetPlayerMobjState(object, S_PLAY_FALL);
+			P_SetMobjState(object, S_PLAY_FALL);
 	}
 	else if (horizspeed
 		&& object->tracer
@@ -547,7 +547,7 @@ static void P_DoFanAndGasJet(mobj_t *spring, mobj_t *object)
 			if (p && !p->powers[pw_tailsfly] && !p->powers[pw_carry]) // doesn't reset anim for Tails' flight
 			{
 				P_ResetPlayer(p);
-				P_SetPlayerMobjState(object, S_PLAY_FALL);
+				P_SetMobjState(object, S_PLAY_FALL);
 				P_SetTarget(&object->tracer, spring);
 				p->powers[pw_carry] = CR_FAN;
 			}
@@ -565,7 +565,7 @@ static void P_DoFanAndGasJet(mobj_t *spring, mobj_t *object)
 			{
 				P_ResetPlayer(p);
 				if (p->panim != PA_FALL)
-					P_SetPlayerMobjState(object, S_PLAY_FALL);
+					P_SetMobjState(object, S_PLAY_FALL);
 			}
 			break;
 		default:
@@ -1047,7 +1047,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			thing->flags2 &= ~MF2_DONTDRAW; // don't leave the rock invisible if it was flashing prior to boarding
 			P_SetTarget(&thing->tracer, tmthing);
 			P_ResetPlayer(tmthing->player);
-			P_SetPlayerMobjState(tmthing, S_PLAY_WALK);
+			P_SetMobjState(tmthing, S_PLAY_WALK);
 			tmthing->player->powers[pw_carry] = CR_ROLLOUT;
 			P_SetTarget(&tmthing->tracer, thing);
 			if (!P_IsObjectOnGround(thing))
@@ -2865,6 +2865,8 @@ boolean P_CheckMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 {
 	boolean moveok;
 	mobj_t *hack = P_SpawnMobjFromMobj(thing, 0, 0, 0, MT_RAY);
+	if (P_MobjWasRemoved(hack))
+		return false;
 
 	hack->radius = thing->radius;
 	hack->height = thing->height;
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 3bce95f596ba613dea9cb96fd351bc1cf9680374..f16fef2f00312ef6dd7d8a0cc60bffb447377ccc 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -45,6 +45,8 @@ actioncache_t actioncachehead;
 
 static mobj_t *overlaycap = NULL;
 
+mobj_t *mobjcache = NULL;
+
 void P_InitCachedActions(void)
 {
 	actioncachehead.prev = actioncachehead.next = &actioncachehead;
@@ -177,7 +179,7 @@ static void P_CyclePlayerMobjState(mobj_t *mobj)
 
 		// you can cycle through multiple states in a tic
 		if (!mobj->tics && mobj->state)
-			if (!P_SetPlayerMobjState(mobj, mobj->state->nextstate))
+			if (!P_SetMobjState(mobj, mobj->state->nextstate))
 				return; // freed itself
 	}
 }
@@ -188,7 +190,7 @@ static void P_CyclePlayerMobjState(mobj_t *mobj)
 //
 // Separate from P_SetMobjState because of the pw_flashing check and Super states
 //
-boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
+static boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 {
 	state_t *st;
 	player_t *player = mobj->player;
@@ -514,10 +516,8 @@ boolean P_SetMobjState(mobj_t *mobj, statenum_t state)
 	statenum_t i = state; // initial state
 	statenum_t tempstate[NUMSTATES]; // for use with recursion
 
-#ifdef PARANOIA
 	if (mobj->player != NULL)
-		I_Error("P_SetMobjState used for player mobj. Use P_SetPlayerMobjState instead!\n(State called: %d)", state);
-#endif
+		return P_SetPlayerMobjState(mobj, state);
 
 	if (recursion++) // if recursion detected,
 		memset(seenstate = tempstate, 0, sizeof tempstate); // clear state table
@@ -915,29 +915,41 @@ void P_ExplodeMissile(mobj_t *mo)
 		P_RadiusAttack(mo, mo, 96*FRACUNIT, 0, true);
 
 		explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_EXPLODE);
-		P_SetScale(explodemo, mo->scale);
-		explodemo->destscale = mo->destscale;
-		explodemo->momx += (P_RandomByte() % 32) * FixedMul(FRACUNIT/8, explodemo->scale);
-		explodemo->momy += (P_RandomByte() % 32) * FixedMul(FRACUNIT/8, explodemo->scale);
-		S_StartSound(explodemo, sfx_pop);
+		if (!P_MobjWasRemoved(explodemo))
+		{
+			P_SetScale(explodemo, mo->scale);
+			explodemo->destscale = mo->destscale;
+			explodemo->momx += (P_RandomByte() % 32) * FixedMul(FRACUNIT/8, explodemo->scale);
+			explodemo->momy += (P_RandomByte() % 32) * FixedMul(FRACUNIT/8, explodemo->scale);
+			S_StartSound(explodemo, sfx_pop);
+		}
 		explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_EXPLODE);
-		P_SetScale(explodemo, mo->scale);
-		explodemo->destscale = mo->destscale;
-		explodemo->momx += (P_RandomByte() % 64) * FixedMul(FRACUNIT/8, explodemo->scale);
-		explodemo->momy -= (P_RandomByte() % 64) * FixedMul(FRACUNIT/8, explodemo->scale);
-		S_StartSound(explodemo, sfx_dmpain);
+		if (!P_MobjWasRemoved(explodemo))
+		{
+			P_SetScale(explodemo, mo->scale);
+			explodemo->destscale = mo->destscale;
+			explodemo->momx += (P_RandomByte() % 64) * FixedMul(FRACUNIT/8, explodemo->scale);
+			explodemo->momy -= (P_RandomByte() % 64) * FixedMul(FRACUNIT/8, explodemo->scale);
+			S_StartSound(explodemo, sfx_dmpain);
+		}
 		explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_EXPLODE);
-		P_SetScale(explodemo, mo->scale);
-		explodemo->destscale = mo->destscale;
-		explodemo->momx -= (P_RandomByte() % 128) * FixedMul(FRACUNIT/8, explodemo->scale);
-		explodemo->momy += (P_RandomByte() % 128) * FixedMul(FRACUNIT/8, explodemo->scale);
-		S_StartSound(explodemo, sfx_pop);
+		if (!P_MobjWasRemoved(explodemo))
+		{
+			P_SetScale(explodemo, mo->scale);
+			explodemo->destscale = mo->destscale;
+			explodemo->momx -= (P_RandomByte() % 128) * FixedMul(FRACUNIT/8, explodemo->scale);
+			explodemo->momy += (P_RandomByte() % 128) * FixedMul(FRACUNIT/8, explodemo->scale);
+			S_StartSound(explodemo, sfx_pop);
+		}
 		explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_EXPLODE);
-		P_SetScale(explodemo, mo->scale);
-		explodemo->destscale = mo->destscale;
-		explodemo->momx -= (P_RandomByte() % 96) * FixedMul(FRACUNIT/8, explodemo->scale);
-		explodemo->momy -= (P_RandomByte() % 96) * FixedMul(FRACUNIT/8, explodemo->scale);
-		S_StartSound(explodemo, sfx_cybdth);
+		if (!P_MobjWasRemoved(explodemo))
+		{
+			P_SetScale(explodemo, mo->scale);
+			explodemo->destscale = mo->destscale;
+			explodemo->momx -= (P_RandomByte() % 96) * FixedMul(FRACUNIT/8, explodemo->scale);
+			explodemo->momy -= (P_RandomByte() % 96) * FixedMul(FRACUNIT/8, explodemo->scale);
+			S_StartSound(explodemo, sfx_cybdth);
+		}
 	}
 
 	mo->flags &= ~MF_MISSILE;
@@ -1109,7 +1121,7 @@ fixed_t P_MobjFloorZ(mobj_t *mobj, sector_t *sector, sector_t *boundsec, fixed_t
 		testy += y;
 
 		// If the highest point is in the sector, then we have it easy! Just get the Z at that point
-		if (R_PointInSubsector(testx, testy)->sector == (boundsec ? boundsec : sector))
+		if (R_IsPointInSector(boundsec ? boundsec : sector, testx, testy))
 			return P_GetSlopeZAt(slope, testx, testy);
 
 		// If boundsec is set, we're looking for specials. In that case, iterate over every line in this sector to find the TRUE highest/lowest point
@@ -1186,7 +1198,7 @@ fixed_t P_MobjCeilingZ(mobj_t *mobj, sector_t *sector, sector_t *boundsec, fixed
 		testy += y;
 
 		// If the highest point is in the sector, then we have it easy! Just get the Z at that point
-		if (R_PointInSubsector(testx, testy)->sector == (boundsec ? boundsec : sector))
+		if (R_IsPointInSector(boundsec ? boundsec : sector, testx, testy))
 			return P_GetSlopeZAt(slope, testx, testy);
 
 		// If boundsec is set, we're looking for specials. In that case, iterate over every line in this sector to find the TRUE highest/lowest point
@@ -1264,7 +1276,7 @@ fixed_t P_CameraFloorZ(camera_t *mobj, sector_t *sector, sector_t *boundsec, fix
 		testy += y;
 
 		// If the highest point is in the sector, then we have it easy! Just get the Z at that point
-		if (R_PointInSubsector(testx, testy)->sector == (boundsec ? boundsec : sector))
+		if (R_IsPointInSector(boundsec ? boundsec : sector, testx, testy))
 			return P_GetSlopeZAt(slope, testx, testy);
 
 		// If boundsec is set, we're looking for specials. In that case, iterate over every line in this sector to find the TRUE highest/lowest point
@@ -1341,7 +1353,7 @@ fixed_t P_CameraCeilingZ(camera_t *mobj, sector_t *sector, sector_t *boundsec, f
 		testy += y;
 
 		// If the highest point is in the sector, then we have it easy! Just get the Z at that point
-		if (R_PointInSubsector(testx, testy)->sector == (boundsec ? boundsec : sector))
+		if (R_IsPointInSector(boundsec ? boundsec : sector, testx, testy))
 			return P_GetSlopeZAt(slope, testx, testy);
 
 		// If boundsec is set, we're looking for specials. In that case, iterate over every line in this sector to find the TRUE highest/lowest point
@@ -1648,7 +1660,7 @@ static void P_XYFriction(mobj_t *mo, fixed_t oldx, fixed_t oldy)
 		{
 			// if in a walking frame, stop moving
 			if (player->panim == PA_WALK)
-				P_SetPlayerMobjState(mo, S_PLAY_STND);
+				P_SetMobjState(mo, S_PLAY_STND);
 			mo->momx = player->cmomx;
 			mo->momy = player->cmomy;
 		}
@@ -1664,8 +1676,6 @@ static void P_XYFriction(mobj_t *mo, fixed_t oldx, fixed_t oldy)
 				mo->momx = FixedMul(mo->momx, mo->friction);
 				mo->momy = FixedMul(mo->momy, mo->friction);
 			}
-
-			mo->friction = ORIG_FRICTION;
 		}
 	}
 	else
@@ -2370,6 +2380,55 @@ boolean P_ZMovement(mobj_t *mo)
 		else if (!onground)
 			P_SlopeLaunch(mo);
 	}
+	
+	if (!mo->player && P_CheckDeathPitCollide(mo) && mo->health
+	&& !(mo->flags & MF_NOCLIPHEIGHT) && !(mo->flags2 & MF2_BOSSDEAD))
+	{
+		switch (mo->type)
+		{
+			case MT_GHOST:
+			case MT_METALSONIC_RACE:
+			case MT_EXPLODE:
+			case MT_BOSSEXPLODE:
+			case MT_SONIC3KBOSSEXPLODE:
+				break;
+			case MT_REDFLAG:
+			case MT_BLUEFLAG:
+				// Remove from death pits.  DON'T FUCKING DESPAWN IT DAMMIT
+				mo->fuse = 1;
+				return false;
+			case MT_BOUNCERING:
+			case MT_INFINITYRING:
+			case MT_AUTOMATICRING:
+			case MT_RAILRING:
+			case MT_EXPLOSIONRING:
+			case MT_SCATTERRING:
+			case MT_GRENADERING:
+			case MT_BOUNCEPICKUP:
+			case MT_RAILPICKUP:
+			case MT_AUTOPICKUP:
+			case MT_EXPLODEPICKUP:
+			case MT_SCATTERPICKUP:
+			case MT_GRENADEPICKUP:
+				//Don't remove respawning ringslinger collectables on death pits
+				if (!(mo->flags2 & MF2_DONTRESPAWN))
+					break;
+				/* FALLTHRU */
+			default:
+				if (mo->flags & MF_ENEMY || mo->flags & MF_BOSS || mo->type == MT_MINECART)
+				{
+					// Kill enemies, bosses and minecarts that fall into death pits.
+					P_KillMobj(mo, NULL, NULL, 0);
+					return !P_MobjWasRemoved(mo); // allows explosion states to run
+				}
+				else
+				{
+					P_RemoveMobj(mo);
+					return false;
+				}
+				break;
+		}
+	}
 
 	switch (mo->type)
 	{
@@ -2397,19 +2456,7 @@ boolean P_ZMovement(mobj_t *mo)
 				mo->flags |= MF_NOGRAVITY;
 			}
 			break;
-		case MT_SPINFIRE:
-			if (P_CheckDeathPitCollide(mo))
-			{
-				P_RemoveMobj(mo);
-				return false;
-			}
-			break;
 		case MT_GOOP:
-			if (P_CheckDeathPitCollide(mo))
-			{
-				P_RemoveMobj(mo);
-				return false;
-			}
 			if (mo->z <= mo->floorz && mo->momz)
 			{
 				P_SetMobjState(mo, mo->info->meleestate);
@@ -2419,27 +2466,6 @@ boolean P_ZMovement(mobj_t *mo)
 					S_StartSound(mo, mo->info->painsound);
 			}
 			break;
-		case MT_FALLINGROCK:
-		case MT_BIGTUMBLEWEED:
-		case MT_LITTLETUMBLEWEED:
-		case MT_SHELL:
-			// Remove stuff from death pits.
-			if (P_CheckDeathPitCollide(mo))
-			{
-				P_RemoveMobj(mo);
-				return false;
-			}
-			break;
-		case MT_REDFLAG:
-		case MT_BLUEFLAG:
-			// Remove from death pits.  DON'T FUCKING DESPAWN IT DAMMIT
-			if (P_CheckDeathPitCollide(mo))
-			{
-				mo->fuse = 1;
-				return false;
-			}
-			break;
-
 		case MT_RING: // Ignore still rings
 		case MT_COIN:
 		case MT_BLUESPHERE:
@@ -2453,15 +2479,6 @@ boolean P_ZMovement(mobj_t *mo)
 		case MT_FLINGBLUESPHERE:
 		case MT_FLINGNIGHTSCHIP:
 		case MT_FLINGEMERALD:
-			// Remove flinged stuff from death pits.
-			if (P_CheckDeathPitCollide(mo))
-			{
-				P_RemoveMobj(mo);
-				return false;
-			}
-			if (!(mo->momx || mo->momy || mo->momz))
-				return true;
-			break;
 		case MT_BOUNCERING:
 		case MT_INFINITYRING:
 		case MT_AUTOMATICRING:
@@ -2475,12 +2492,6 @@ boolean P_ZMovement(mobj_t *mo)
 		case MT_EXPLODEPICKUP:
 		case MT_SCATTERPICKUP:
 		case MT_GRENADEPICKUP:
-			// Remove flinged stuff from death pits.
-			if (P_CheckDeathPitCollide(mo) && (mo->flags2 & MF2_DONTRESPAWN))
-			{
-				P_RemoveMobj(mo);
-				return false;
-			}
 			if (!(mo->momx || mo->momy || mo->momz))
 				return true;
 			break;
@@ -2502,35 +2513,6 @@ boolean P_ZMovement(mobj_t *mo)
 			break;
 	}
 
-	if (!mo->player && P_CheckDeathPitCollide(mo))
-	{
-		switch (mo->type)
-		{
-			case MT_GHOST:
-			case MT_METALSONIC_RACE:
-			case MT_EXPLODE:
-			case MT_BOSSEXPLODE:
-			case MT_SONIC3KBOSSEXPLODE:
-				break;
-			default:
-				if (mo->flags & MF_ENEMY || mo->flags & MF_BOSS || mo->type == MT_MINECART)
-				{
-					// Kill enemies, bosses and minecarts that fall into death pits.
-					if (mo->health)
-					{
-						P_KillMobj(mo, NULL, NULL, 0);
-					}
-					return !P_MobjWasRemoved(mo); // allows explosion states to run
-				}
-				else
-				{
-					P_RemoveMobj(mo);
-					return false;
-				}
-				break;
-		}
-	}
-
 	if (P_MobjFlip(mo)*mo->momz < 0
 	&& (mo->flags2 & MF2_CLASSICPUSH))
 		mo->momx = mo->momy = 0;
@@ -2974,7 +2956,7 @@ void P_PlayerZMovement(mobj_t *mo)
 		}
 		// Get up if you fell.
 		if (mo->player->panim == PA_PAIN)
-			P_SetPlayerMobjState(mo, S_PLAY_WALK);
+			P_SetMobjState(mo, S_PLAY_WALK);
 
 		if (!mo->standingslope && (mo->eflags & MFE_VERTICALFLIP ? tmceilingslope : tmfloorslope)) {
 			// Handle landing on slope during Z movement
@@ -2983,7 +2965,7 @@ void P_PlayerZMovement(mobj_t *mo)
 
 		if (P_MobjFlip(mo)*mo->momz < 0) // falling
 		{
-			boolean clipmomz = !(P_CheckDeathPitCollide(mo));
+			boolean clipmomz;
 
 			mo->pmomz = 0; // We're on a new floor, don't keep doing platform movement.
 
@@ -3091,6 +3073,13 @@ boolean P_SceneryZMovement(mobj_t *mo)
 		mo->eflags &= ~MFE_APPLYPMOMZ;
 	}
 	mo->z += mo->momz;
+	
+	if (!mo->player && P_CheckDeathPitCollide(mo) && mo->health
+	&& !(mo->flags & MF_NOCLIPHEIGHT) && !(mo->flags2 & MF2_BOSSDEAD))
+	{
+		P_RemoveMobj(mo);
+		return false;
+	}
 
 	switch (mo->type)
 	{
@@ -3104,11 +3093,6 @@ boolean P_SceneryZMovement(mobj_t *mo)
 			}
 			break;
 		case MT_MEDIUMBUBBLE:
-			if (P_CheckDeathPitCollide(mo)) // Don't split if you fell in a pit
-			{
-				P_RemoveMobj(mo);
-				return false;
-			}
 			if ((!(mo->eflags & MFE_VERTICALFLIP) && mo->z <= mo->floorz)
 			|| (mo->eflags & MFE_VERTICALFLIP && mo->z+mo->height >= mo->ceilingz)) // Hit the floor, so split!
 			{
@@ -3120,6 +3104,8 @@ boolean P_SceneryZMovement(mobj_t *mo)
 				{
 					prandom = P_RandomByte();
 					explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_SMALLBUBBLE);
+					if (P_MobjWasRemoved(explodemo))
+						continue;
 					explodemo->momx += ((prandom & 0x0F) << (FRACBITS-2)) * (i & 2 ? -1 : 1);
 					explodemo->momy += ((prandom & 0xF0) << (FRACBITS-6)) * (i & 1 ? -1 : 1);
 					explodemo->destscale = mo->scale;
@@ -3141,11 +3127,6 @@ boolean P_SceneryZMovement(mobj_t *mo)
 			}
 			break;
 		case MT_SEED: // now scenery
-			if (P_CheckDeathPitCollide(mo)) // No flowers for death pits
-			{
-				P_RemoveMobj(mo);
-				return false;
-			}
 			// Soniccd seed turns into a flower!
 			if ((!(mo->eflags & MFE_VERTICALFLIP) && mo->z <= mo->floorz)
 			|| (mo->eflags & MFE_VERTICALFLIP && mo->z+mo->height >= mo->ceilingz))
@@ -3166,13 +3147,6 @@ boolean P_SceneryZMovement(mobj_t *mo)
 			break;
 	}
 
-	if (P_CheckDeathPitCollide(mo))
-	{
-		if (mo->type != MT_GHOST)  // ghosts play death animations instead, so don't remove them
-			P_RemoveMobj(mo);
-		return false;
-	}
-
 	// clip movement
 	if (((mo->z <= mo->floorz && !(mo->eflags & MFE_VERTICALFLIP))
 		|| (mo->z + mo->height >= mo->ceilingz && mo->eflags & MFE_VERTICALFLIP))
@@ -3392,13 +3366,19 @@ void P_MobjCheckWater(mobj_t *mobj)
 				if (mobj->eflags & MFE_VERTICALFLIP)
 				{
 					splish = P_SpawnMobj(mobj->x, mobj->y, mobj->waterbottom-FixedMul(mobjinfo[splishtype].height, mobj->scale), splishtype);
-					splish->flags2 |= MF2_OBJECTFLIP;
-					splish->eflags |= MFE_VERTICALFLIP;
+					if (!P_MobjWasRemoved(splish))
+					{
+						splish->flags2 |= MF2_OBJECTFLIP;
+						splish->eflags |= MFE_VERTICALFLIP;
+					}
 				}
 				else
 					splish = P_SpawnMobj(mobj->x, mobj->y, mobj->watertop, splishtype);
-				splish->destscale = mobj->scale;
-				P_SetScale(splish, mobj->scale);
+				if (!P_MobjWasRemoved(splish))
+				{
+					splish->destscale = mobj->scale;
+					P_SetScale(splish, mobj->scale);
+				}
 			}
 
 			// skipping stone!
@@ -3428,13 +3408,19 @@ void P_MobjCheckWater(mobj_t *mobj)
 				if (mobj->eflags & MFE_VERTICALFLIP)
 				{
 					splish = P_SpawnMobj(mobj->x, mobj->y, mobj->waterbottom-FixedMul(mobjinfo[splishtype].height, mobj->scale), splishtype);
-					splish->flags2 |= MF2_OBJECTFLIP;
-					splish->eflags |= MFE_VERTICALFLIP;
+					if (!P_MobjWasRemoved(splish))
+					{
+						splish->flags2 |= MF2_OBJECTFLIP;
+						splish->eflags |= MFE_VERTICALFLIP;
+					}
 				}
 				else
 					splish = P_SpawnMobj(mobj->x, mobj->y, mobj->watertop, splishtype);
-				splish->destscale = mobj->scale;
-				P_SetScale(splish, mobj->scale);
+				if (!P_MobjWasRemoved(splish))
+				{
+					splish->destscale = mobj->scale;
+					P_SetScale(splish, mobj->scale);
+				}
 			}
 		}
 
@@ -3957,7 +3943,7 @@ static void P_PlayerMobjThinker(mobj_t *mobj)
 		{
 			mobj->player->secondjump = 0;
 			mobj->player->powers[pw_tailsfly] = 0;
-			P_SetPlayerMobjState(mobj, S_PLAY_WALK);
+			P_SetMobjState(mobj, S_PLAY_WALK);
 		}
 #endif
 		mobj->eflags &= ~MFE_JUSTHITFLOOR;
@@ -4444,11 +4430,14 @@ static void P_Boss3Thinker(mobj_t *mobj)
 			}
 
 			dummy = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->mass);
-			dummy->angle = mobj->angle;
-			dummy->threshold = way1;
-			P_SetTarget(&dummy->tracer, mobj);
-			dummy->movefactor = mobj->movefactor;
-			dummy->cusval = mobj->cusval;
+			if (!P_MobjWasRemoved(dummy))
+			{
+				dummy->angle = mobj->angle;
+				dummy->threshold = way1;
+				P_SetTarget(&dummy->tracer, mobj);
+				dummy->movefactor = mobj->movefactor;
+				dummy->cusval = mobj->cusval;
+			}
 
 			way2 = P_RandomKey(8-3);
 			if (way2 >= curpath)
@@ -4467,11 +4456,14 @@ static void P_Boss3Thinker(mobj_t *mobj)
 			}
 
 			dummy = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->mass);
-			dummy->angle = mobj->angle;
-			dummy->threshold = way2;
-			P_SetTarget(&dummy->tracer, mobj);
-			dummy->movefactor = mobj->movefactor;
-			dummy->cusval = mobj->cusval;
+			if (!P_MobjWasRemoved(dummy))
+			{
+				dummy->angle = mobj->angle;
+				dummy->threshold = way2;
+				P_SetTarget(&dummy->tracer, mobj);
+				dummy->movefactor = mobj->movefactor;
+				dummy->cusval = mobj->cusval;
+			}
 
 			CONS_Debug(DBG_GAMELOGIC, "Eggman path %d - Dummy selected paths %d and %d\n", way0, way1, way2);
 			if (mobj->spawnpoint)
@@ -4589,6 +4581,8 @@ static void P_Boss3Thinker(mobj_t *mobj)
 				for (i = 0; i < numtospawn; i++)
 				{
 					shock = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_SHOCKWAVE);
+					if (P_MobjWasRemoved(shock))
+						continue;
 					P_SetTarget(&shock->target, mobj);
 					shock->fuse = shock->info->painchance;
 
@@ -4608,7 +4602,8 @@ static void P_Boss3Thinker(mobj_t *mobj)
 					ang += interval;
 					sprev = shock;
 				}
-				S_StartSound(mobj, shock->info->seesound);
+				if (!P_MobjWasRemoved(shock))
+					S_StartSound(mobj, shock->info->seesound);
 
 				// look for a new target
 				P_BossTargetPlayer(mobj, true);
@@ -4858,12 +4853,17 @@ static void P_Boss4Thinker(mobj_t *mobj)
 			for (arm = 0; arm <3 ; arm++)
 			{
 				seg = P_SpawnMobj(mobj->x, mobj->y, z, MT_EGGMOBILE4_MACE);
+				if (P_MobjWasRemoved(seg))
+					continue;
+
 				P_SetTarget(&base->tracer, seg);
 				base = seg;
 				P_SetTarget(&seg->target, mobj);
 				for (i = 0; i < 9; i++)
 				{
 					P_SetTarget(&seg->hnext, P_SpawnMobj(mobj->x, mobj->y, z, MT_EGGMOBILE4_MACE));
+					if (P_MobjWasRemoved(seg->hnext))
+						continue;
 					P_SetTarget(&seg->hnext->hprev, seg);
 					seg = seg->hnext;
 				}
@@ -5134,9 +5134,12 @@ static void P_Boss7Thinker(mobj_t *mobj)
 	if (mobj->health >= mobj->info->spawnhealth && (leveltime & 14) == 0)
 	{
 		mobj_t *smoke = P_SpawnMobj(mobj->x, mobj->y, mobj->z + mobj->height, MT_SMOKE);
-		smoke->destscale = mobj->destscale;
-		P_SetScale(smoke, smoke->destscale);
-		smoke->momz = FixedMul(FRACUNIT, smoke->scale);
+		if (!P_MobjWasRemoved(smoke))
+		{
+			smoke->destscale = mobj->destscale;
+			P_SetScale(smoke, smoke->destscale);
+			smoke->momz = FixedMul(FRACUNIT, smoke->scale);
+		}
 	}
 
 	if (mobj->state == &states[S_BLACKEGG_STND] && mobj->tics == mobj->state->tics)
@@ -5410,6 +5413,8 @@ static void P_Boss7Thinker(mobj_t *mobj)
 				y = mobj->y + FixedMul(FINECOSINE(fa),ns);
 
 				mo2 = P_SpawnMobj(x, y, z, MT_EXPLODE);
+				if (P_MobjWasRemoved(mo2))
+					continue;
 				ns = 16 * FRACUNIT;
 				mo2->momx = FixedMul(FINESINE(fa),ns);
 				mo2->momy = FixedMul(FINECOSINE(fa),ns);
@@ -5603,76 +5608,84 @@ static void P_Boss9Thinker(mobj_t *mobj)
 						mobj_t *missile;
 
 						missile = P_SpawnMissile(mobj, mobj->target, MT_MSGATHER);
-						S_StopSound(missile);
-						if (mobj->extravalue1 >= 2)
-							P_SetScale(missile, FRACUNIT>>1);
-						missile->destscale = missile->scale>>1;
-						missile->fuse = TICRATE/2;
-						missile->scalespeed = abs(missile->destscale - missile->scale)/missile->fuse;
-						missile->z -= missile->height/2;
-						missile->momx *= -1;
-						missile->momy *= -1;
-						missile->momz *= -1;
-
-						if (mobj->extravalue1 == 2)
-						{
-							UINT8 i;
-							mobj_t *spread;
-							for (i = 0; i < 5; i++)
-							{
-								if (i == 2)
-									continue;
-								spread = P_SpawnMobj(missile->x, missile->y, missile->z, missile->type);
-								spread->angle = missile->angle+(ANGLE_11hh/2)*(i-2);
-								P_InstaThrust(spread,spread->angle,-spread->info->speed);
-								spread->momz = missile->momz;
-								P_SetScale(spread, missile->scale);
-								spread->destscale = missile->destscale;
-								spread->scalespeed = missile->scalespeed;
-								spread->fuse = missile->fuse;
-								P_UnsetThingPosition(spread);
-								spread->x -= spread->fuse*spread->momx;
-								spread->y -= spread->fuse*spread->momy;
-								spread->z -= spread->fuse*spread->momz;
-								P_SetThingPosition(spread);
-							}
-							P_InstaThrust(missile,missile->angle,-missile->info->speed);
-						}
-						else if (mobj->extravalue1 >= 3)
+						if (!P_MobjWasRemoved(missile))
 						{
-							UINT8 i;
-							mobj_t *spread;
-							mobj->target->z -= (4*missile->height);
-							for (i = 0; i < 5; i++)
+							S_StopSound(missile);
+							if (mobj->extravalue1 >= 2)
+								P_SetScale(missile, FRACUNIT>>1);
+							missile->destscale = missile->scale>>1;
+							missile->fuse = TICRATE/2;
+							missile->scalespeed = abs(missile->destscale - missile->scale)/missile->fuse;
+							missile->z -= missile->height/2;
+							missile->momx *= -1;
+							missile->momy *= -1;
+							missile->momz *= -1;
+
+							if (mobj->extravalue1 == 2)
 							{
-								if (i != 2)
+								UINT8 i;
+								mobj_t *spread;
+								for (i = 0; i < 5; i++)
 								{
-									spread = P_SpawnMissile(mobj, mobj->target, missile->type);
+									if (i == 2)
+										continue;
+									spread = P_SpawnMobj(missile->x, missile->y, missile->z, missile->type);
+									if (P_MobjWasRemoved(spread))
+										continue;
+
+									spread->angle = missile->angle+(ANGLE_11hh/2)*(i-2);
+									P_InstaThrust(spread,spread->angle,-spread->info->speed);
+									spread->momz = missile->momz;
 									P_SetScale(spread, missile->scale);
 									spread->destscale = missile->destscale;
+									spread->scalespeed = missile->scalespeed;
 									spread->fuse = missile->fuse;
-									spread->z -= spread->height/2;
-									spread->momx *= -1;
-									spread->momy *= -1;
-									spread->momz *= -1;
 									P_UnsetThingPosition(spread);
 									spread->x -= spread->fuse*spread->momx;
 									spread->y -= spread->fuse*spread->momy;
 									spread->z -= spread->fuse*spread->momz;
 									P_SetThingPosition(spread);
 								}
-								mobj->target->z += missile->height*2;
+								P_InstaThrust(missile,missile->angle,-missile->info->speed);
+							}
+							else if (mobj->extravalue1 >= 3)
+							{
+								UINT8 i;
+								mobj_t *spread;
+								mobj->target->z -= (4*missile->height);
+								for (i = 0; i < 5; i++)
+								{
+									if (i != 2)
+									{
+										spread = P_SpawnMissile(mobj, mobj->target, missile->type);
+										if (P_MobjWasRemoved(spread))
+											continue;
+										P_SetScale(spread, missile->scale);
+										spread->destscale = missile->destscale;
+										spread->fuse = missile->fuse;
+										spread->z -= spread->height/2;
+										spread->momx *= -1;
+										spread->momy *= -1;
+										spread->momz *= -1;
+										P_UnsetThingPosition(spread);
+										spread->x -= spread->fuse*spread->momx;
+										spread->y -= spread->fuse*spread->momy;
+										spread->z -= spread->fuse*spread->momz;
+										P_SetThingPosition(spread);
+									}
+									mobj->target->z += missile->height*2;
+								}
+								mobj->target->z -= (6*missile->height);
 							}
-							mobj->target->z -= (6*missile->height);
-						}
 
-						P_UnsetThingPosition(missile);
-						missile->x -= missile->fuse*missile->momx;
-						missile->y -= missile->fuse*missile->momy;
-						missile->z -= missile->fuse*missile->momz;
-						P_SetThingPosition(missile);
+							P_UnsetThingPosition(missile);
+							missile->x -= missile->fuse*missile->momx;
+							missile->y -= missile->fuse*missile->momy;
+							missile->z -= missile->fuse*missile->momz;
+							P_SetThingPosition(missile);
 
-						S_StartSound(mobj, sfx_s3kb3);
+							S_StartSound(mobj, sfx_s3kb3);
+						}
 					}
 				}
 			}
@@ -5690,29 +5703,32 @@ static void P_Boss9Thinker(mobj_t *mobj)
 			if (spawner && dist)
 			{
 				mobj_t *missile = P_SpawnMissile(spawner, mobj, MT_MSGATHER);
-				missile->fuse = (dist/P_AproxDistance(missile->momx, missile->momy));
-				if (missile->fuse <= 0) // Prevents a division by zero when calculating missile->scalespeed
-					missile->fuse = 1;
-
-				if (missile->fuse > mobj->fuse)
+				if (!P_MobjWasRemoved(missile))
 				{
-					P_RemoveMobj(missile);
-				}
-				else
-				{
-					if (mobj->health > mobj->info->damage)
+					missile->fuse = (dist/P_AproxDistance(missile->momx, missile->momy));
+					if (missile->fuse <= 0) // Prevents a division by zero when calculating missile->scalespeed
+						missile->fuse = 1;
+
+					if (missile->fuse > mobj->fuse)
 					{
-						P_SetScale(missile, FRACUNIT/3);
-						missile->color = SKINCOLOR_MAGENTA; // sonic OVA/4 purple power
+						P_RemoveMobj(missile);
 					}
 					else
 					{
-						P_SetScale(missile, FRACUNIT/5);
-						missile->color = SKINCOLOR_SUNSET; // sonic cd electric power
+						if (mobj->health > mobj->info->damage)
+						{
+							P_SetScale(missile, FRACUNIT/3);
+							missile->color = SKINCOLOR_MAGENTA; // sonic OVA/4 purple power
+						}
+						else
+						{
+							P_SetScale(missile, FRACUNIT/5);
+							missile->color = SKINCOLOR_SUNSET; // sonic cd electric power
+						}
+						missile->destscale = missile->scale*2;
+						missile->scalespeed = abs(missile->scale - missile->destscale)/missile->fuse;
+						missile->colorized = true;
 					}
-					missile->destscale = missile->scale*2;
-					missile->scalespeed = abs(missile->scale - missile->destscale)/missile->fuse;
-					missile->colorized = true;
 				}
 			}
 
@@ -5746,6 +5762,7 @@ static void P_Boss9Thinker(mobj_t *mobj)
 
 		// threshold is used for attacks/maneuvers.
 		if (mobj->threshold && mobj->movecount != 2) {
+			mobj_t *ghost;
 			fixed_t speed = 20*FRACUNIT + FixedMul(40*FRACUNIT, FixedDiv((mobj->info->spawnhealth - mobj->health)<<FRACBITS, mobj->info->spawnhealth<<FRACBITS));
 			UINT8 tries = 0;
 
@@ -5765,50 +5782,59 @@ static void P_Boss9Thinker(mobj_t *mobj)
 
 					A_FaceTarget(mobj);
 					missile = P_SpawnMissile(mobj, mobj->target, mobj->info->speed);
-					if (mobj->extravalue1 >= 2)
-					{
-						missile->destscale = FRACUNIT>>1;
-						P_SetScale(missile, missile->destscale);
-					}
-					missile->fuse = 3*TICRATE;
-					missile->z -= missile->height/2;
-
-					if (mobj->extravalue1 == 2)
+					if (!P_MobjWasRemoved(missile))
 					{
-						UINT8 i;
-						mobj_t *spread;
-						for (i = 0; i < 5; i++)
+						if (mobj->extravalue1 >= 2)
 						{
-							if (i == 2)
-								continue;
-							spread = P_SpawnMobj(missile->x, missile->y, missile->z, missile->type);
-							spread->angle = missile->angle+(ANGLE_11hh/2)*(i-2);
-							P_InstaThrust(spread,spread->angle,spread->info->speed);
-							spread->momz = missile->momz;
-							spread->destscale = FRACUNIT>>1;
-							P_SetScale(spread, spread->destscale);
-							spread->fuse = missile->fuse;
+							missile->destscale = FRACUNIT>>1;
+							P_SetScale(missile, missile->destscale);
 						}
-						P_InstaThrust(missile,missile->angle,missile->info->speed);
-					}
-					else if (mobj->extravalue1 >= 3)
-					{
-						UINT8 i;
-						mobj_t *spread;
-						mobj->target->z -= (2*missile->height);
-						for (i = 0; i < 5; i++)
+						missile->fuse = 3*TICRATE;
+						missile->z -= missile->height/2;
+
+						if (mobj->extravalue1 == 2)
 						{
-							if (i != 2)
+							UINT8 i;
+							mobj_t *spread;
+							for (i = 0; i < 5; i++)
 							{
-								spread = P_SpawnMissile(mobj, mobj->target, missile->type);
+								if (i == 2)
+									continue;
+								spread = P_SpawnMobj(missile->x, missile->y, missile->z, missile->type);
+								if (P_MobjWasRemoved(spread))
+									continue;
+
+								spread->angle = missile->angle+(ANGLE_11hh/2)*(i-2);
+								P_InstaThrust(spread,spread->angle,spread->info->speed);
+								spread->momz = missile->momz;
 								spread->destscale = FRACUNIT>>1;
 								P_SetScale(spread, spread->destscale);
 								spread->fuse = missile->fuse;
-								spread->z -= spread->height/2;
 							}
-							mobj->target->z += missile->height;
+							P_InstaThrust(missile,missile->angle,missile->info->speed);
+						}
+						else if (mobj->extravalue1 >= 3)
+						{
+							UINT8 i;
+							mobj_t *spread;
+							mobj->target->z -= (2*missile->height);
+							for (i = 0; i < 5; i++)
+							{
+								if (i != 2)
+								{
+									spread = P_SpawnMissile(mobj, mobj->target, missile->type);
+									if (!P_MobjWasRemoved(spread))
+									{
+										spread->destscale = FRACUNIT>>1;
+										P_SetScale(spread, spread->destscale);
+										spread->fuse = missile->fuse;
+										spread->z -= spread->height/2;
+									}
+								}
+								mobj->target->z += missile->height;
+							}
+							mobj->target->z -= (3*missile->height);
 						}
-						mobj->target->z -= (3*missile->height);
 					}
 				}
 				else
@@ -5867,7 +5893,9 @@ static void P_Boss9Thinker(mobj_t *mobj)
 				return;
 			}
 
-			P_SpawnGhostMobj(mobj)->colorized = false;
+			ghost = P_SpawnGhostMobj(mobj);
+			if (!P_MobjWasRemoved(ghost))
+				ghost->colorized = false;
 
 			// Vector form dodge!
 			mobj->angle += mobj->movedir;
@@ -5965,8 +5993,11 @@ static void P_Boss9Thinker(mobj_t *mobj)
 				if (mobj->health > mobj->info->damage)
 				{ // No more bubble if we're broken (pinch phase)
 					mobj_t *shield = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_MSSHIELD_FRONT);
-					P_SetTarget(&mobj->hprev, shield);
-					P_SetTarget(&shield->target, mobj);
+					if (!P_MobjWasRemoved(shield))
+					{
+						P_SetTarget(&mobj->hprev, shield);
+						P_SetTarget(&shield->target, mobj);
+					}
 
 					// Attack 2: Energy shot!
 					switch (mobj->health)
@@ -6301,7 +6332,8 @@ void P_SpawnHoopOfSomething(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT
 		finalz = z + v.z;
 
 		mobj = P_SpawnMobj(finalx, finaly, finalz, type);
-		mobj->z -= mobj->height/2;
+		if (!P_MobjWasRemoved(mobj))
+			mobj->z -= mobj->height/2;
 	}
 }
 
@@ -6342,6 +6374,8 @@ void P_SpawnParaloop(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 numb
 		finalz = z + v.z;
 
 		mobj = P_SpawnMobj(finalx, finaly, finalz, type);
+		if (P_MobjWasRemoved(mobj))
+			continue;
 
 		mobj->z -= mobj->height>>1;
 
@@ -6766,15 +6800,33 @@ static boolean P_ShieldLook(mobj_t *thing, shieldtype_t shield)
 
 	P_SetScale(thing, FixedMul(thing->target->scale, thing->target->player->shieldscale));
 	thing->destscale = thing->scale;
+	thing->old_scale = FixedMul(thing->target->old_scale, thing->target->player->shieldscale);
+
+#define NewMH(mobj)   mobj->height // Ugly mobj-height and player-height defines, for the sake of prettier code
+#define NewPH(player) P_GetPlayerHeight(player)
+#define OldMH(mobj)   FixedMul(mobj->height, FixedDiv(mobj->old_scale, mobj->scale))
+#define OldPH(player) FixedMul(player->height, player->mo->old_scale)
 	P_UnsetThingPosition(thing);
 	thing->x = thing->target->x;
 	thing->y = thing->target->y;
+	thing->old_x = thing->target->old_x;
+	thing->old_y = thing->target->old_y;
 	if (thing->eflags & MFE_VERTICALFLIP)
-		thing->z = thing->target->z + (thing->target->height - thing->height + FixedDiv(P_GetPlayerHeight(thing->target->player) - thing->target->height, 3*FRACUNIT)) - FixedMul(2*FRACUNIT, thing->target->scale);
+	{
+		thing->z     = thing->target->z     + NewMH(thing->target) - NewMH(thing) + ((NewPH(thing->target->player) - NewMH(thing->target)) / 3) - (thing->target->scale     * 2);
+		thing->old_z = thing->target->old_z + OldMH(thing->target) - OldMH(thing) + ((OldPH(thing->target->player) - OldMH(thing->target)) / 3) - (thing->target->old_scale * 2);
+	}
 	else
-		thing->z = thing->target->z - (FixedDiv(P_GetPlayerHeight(thing->target->player) - thing->target->height, 3*FRACUNIT)) + FixedMul(2*FRACUNIT, thing->target->scale);
+	{
+		thing->z     = thing->target->z     - ((NewPH(thing->target->player) - NewMH(thing->target)) / 3) + (thing->target->scale     * 2);
+		thing->old_z = thing->target->old_z - ((OldPH(thing->target->player) - OldMH(thing->target)) / 3) + (thing->target->old_scale * 2);
+	}
 	P_SetThingPosition(thing);
 	P_CheckPosition(thing, thing->x, thing->y);
+#undef NewMH
+#undef NewPH
+#undef OldMH
+#undef OldPH
 
 	if (P_MobjWasRemoved(thing))
 		return false;
@@ -6840,10 +6892,11 @@ void P_RunOverlays(void)
 {
 	// run overlays
 	mobj_t *mo, *next = NULL;
-	fixed_t destx,desty,zoffs;
 
 	for (mo = overlaycap; mo; mo = next)
 	{
+		fixed_t zoffs;
+
 		I_Assert(!P_MobjWasRemoved(mo));
 
 		// grab next in chain, then unset the chain target
@@ -6859,31 +6912,11 @@ void P_RunOverlays(void)
 			continue;
 		}
 
-		if (!splitscreen /*&& rendermode != render_soft*/)
-		{
-			angle_t viewingangle;
-
-			if (players[displayplayer].awayviewtics && players[displayplayer].awayviewmobj != NULL && !P_MobjWasRemoved(players[displayplayer].awayviewmobj))
-				viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, players[displayplayer].awayviewmobj->x, players[displayplayer].awayviewmobj->y);
-			else if (!camera.chase && players[displayplayer].mo)
-				viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, players[displayplayer].mo->x, players[displayplayer].mo->y);
-			else
-				viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, camera.x, camera.y);
-
-			if (!(mo->state->frame & FF_ANIMATE) && mo->state->var1)
-				viewingangle += ANGLE_180;
-			destx = mo->target->x + P_ReturnThrustX(mo->target, viewingangle, FixedMul(FRACUNIT/4, mo->scale));
-			desty = mo->target->y + P_ReturnThrustY(mo->target, viewingangle, FixedMul(FRACUNIT/4, mo->scale));
-		}
-		else
-		{
-			destx = mo->target->x;
-			desty = mo->target->y;
-		}
-
 		mo->eflags = (mo->eflags & ~MFE_VERTICALFLIP) | (mo->target->eflags & MFE_VERTICALFLIP);
 		mo->scale = mo->destscale = mo->target->scale;
+		mo->old_scale = mo->target->old_scale;
 		mo->angle = (mo->target->player ? mo->target->player->drawangle : mo->target->angle) + mo->movedir;
+		mo->old_angle = (mo->target->player ? mo->target->player->old_drawangle : mo->target->old_angle) + mo->movedir;
 
 		if (!(mo->state->frame & FF_ANIMATE))
 			zoffs = FixedMul(((signed)mo->state->var2)*FRACUNIT, mo->scale);
@@ -6893,15 +6926,26 @@ void P_RunOverlays(void)
 			zoffs = 0;
 
 		P_UnsetThingPosition(mo);
-		mo->x = destx;
-		mo->y = desty;
+		mo->x = mo->target->x;
+		mo->y = mo->target->y;
+		mo->old_x = mo->target->old_x;
+		mo->old_y = mo->target->old_y;
 		mo->radius = mo->target->radius;
 		mo->height = mo->target->height;
 		if (mo->eflags & MFE_VERTICALFLIP)
-			mo->z = (mo->target->z + mo->target->height - mo->height) - zoffs;
+		{
+			mo->z         = mo->target->z     + mo->target->height - mo->height - zoffs;
+			if (mo->scale == mo->old_scale)
+				mo->old_z = mo->target->old_z + mo->target->height - mo->height - zoffs;
+			else // Interpolate height scale changes - mo and mo->target have the same scales here, so don't interpolate them individually
+				mo->old_z = mo->target->old_z + FixedMul(mo->target->height - mo->height, FixedDiv(mo->old_scale, mo->scale)) - zoffs;
+		}
 		else
-			mo->z = mo->target->z + zoffs;
-		if (mo->state->var1)
+		{
+			mo->z     = mo->target->z     + zoffs;
+			mo->old_z = mo->target->old_z + zoffs;
+		}
+		if (!(mo->state->frame & FF_ANIMATE) && mo->state->var1)
 			P_SetUnderlayPosition(mo);
 		else
 			P_SetThingPosition(mo);
@@ -6998,6 +7042,8 @@ static void P_KoopaThinker(mobj_t *koopa)
 	{
 		mobj_t *flame;
 		flame = P_SpawnMobj(koopa->x - koopa->radius + FixedMul(5*FRACUNIT, koopa->scale), koopa->y, koopa->z + (P_RandomByte()<<(FRACBITS-2)), MT_KOOPAFLAME);
+		if (P_MobjWasRemoved(flame))
+			return;
 		flame->momx = -FixedMul(flame->info->speed, flame->scale);
 		S_StartSound(flame, sfx_koopfr);
 	}
@@ -7005,6 +7051,8 @@ static void P_KoopaThinker(mobj_t *koopa)
 	{
 		mobj_t *hammer;
 		hammer = P_SpawnMobj(koopa->x - koopa->radius, koopa->y, koopa->z + koopa->height, MT_HAMMER);
+		if (P_MobjWasRemoved(hammer))
+			return;
 		hammer->momx = FixedMul(-5*FRACUNIT, hammer->scale);
 		hammer->momz = FixedMul(7*FRACUNIT, hammer->scale);
 	}
@@ -7023,6 +7071,9 @@ static void P_SpawnMinecartSegments(mobj_t *mobj, boolean mode)
 	for (i = 0; i < 4; i++)
 	{
 		seg = P_SpawnMobj(x, y, z, MT_MINECARTSEG);
+		if (P_MobjWasRemoved(seg))
+			continue;
+
 		P_SetMobjState(seg, (statenum_t)(S_MINECARTSEG_FRONT + i));
 		if (i >= 2)
 			seg->extravalue1 = (i == 2) ? -18 : 18; // make -20/20 when papersprite projection fixed
@@ -7078,6 +7129,8 @@ static void P_PyreFlyBurn(mobj_t *mobj, fixed_t hoffs, INT16 vrange, mobjtype_t
 	fixed_t yoffs = FixedMul(FINESINE(fa), mobj->radius + hoffs);
 	fixed_t zoffs = P_RandomRange(-vrange, vrange)*FRACUNIT;
 	mobj_t *particle = P_SpawnMobjFromMobj(mobj, xoffs, yoffs, zoffs, mobjtype);
+	if (P_MobjWasRemoved(particle))
+		return;
 	particle->momz = momz;
 	particle->flags2 |= MF2_LINKDRAW;
 	P_SetTarget(&particle->tracer, mobj);
@@ -7236,6 +7289,8 @@ static void P_FlameJetSceneryThink(mobj_t *mobj)
 		mobj->fuse -= 2;
 
 	flame = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_FLAMEJETFLAME);
+	if (P_MobjWasRemoved(flame))
+		return;
 	P_SetMobjState(flame, S_FLAMEJETFLAME4);
 
 	flame->angle = mobj->angle;
@@ -7274,6 +7329,8 @@ static void P_VerticalFlameJetSceneryThink(mobj_t *mobj)
 		mobj->fuse--;
 
 	flame = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_FLAMEJETFLAME);
+	if (P_MobjWasRemoved(flame))
+		return;
 
 	strength = (mobj->movedir ? mobj->movedir : 80)<<(FRACBITS-2);
 
@@ -7347,13 +7404,16 @@ static boolean P_ParticleGenSceneryThink(mobj_t *mobj)
 				mobj->y + FixedMul(FixedMul(mobj->friction, mobj->scale), FINESINE(mobj->angle >> ANGLETOFINESHIFT)),
 				mobj->z,
 				(mobjtype_t)mobj->threshold);
-			P_SetScale(spawn, mobj->scale);
-			spawn->momz = FixedMul(mobj->movefactor, spawn->scale);
-			spawn->destscale = spawn->scale/100;
-			spawn->scalespeed = spawn->scale/mobj->health;
-			spawn->tics = (tic_t)mobj->health;
-			spawn->flags2 |= (mobj->flags2 & MF2_OBJECTFLIP);
-			spawn->angle += P_RandomKey(36)*ANG10; // irrelevant for default objects but might make sense for some custom ones
+			if (!P_MobjWasRemoved(spawn))
+			{
+				P_SetScale(spawn, mobj->scale);
+				spawn->momz = FixedMul(mobj->movefactor, spawn->scale);
+				spawn->destscale = spawn->scale/100;
+				spawn->scalespeed = spawn->scale/mobj->health;
+				spawn->tics = (tic_t)mobj->health;
+				spawn->flags2 |= (mobj->flags2 & MF2_OBJECTFLIP);
+				spawn->angle += P_RandomKey(36)*ANG10; // irrelevant for default objects but might make sense for some custom ones
+			}
 
 			mobj->angle += mobj->movedir;
 		}
@@ -7549,7 +7609,7 @@ static void P_RosySceneryThink(mobj_t *mobj)
 		if (stat == S_ROSY_HUG)
 		{
 			if (player->panim != PA_IDLE)
-				P_SetPlayerMobjState(mobj->target, S_PLAY_STND);
+				P_SetMobjState(mobj->target, S_PLAY_STND);
 			player->pflags |= PF_STASIS;
 		}
 
@@ -7565,13 +7625,16 @@ static void P_RosySceneryThink(mobj_t *mobj)
 		if (makeheart)
 		{
 			mobj_t *cdlhrt = P_SpawnMobjFromMobj(mobj, 0, 0, mobj->height, MT_CDLHRT);
-			cdlhrt->destscale = (5*mobj->scale) >> 4;
-			P_SetScale(cdlhrt, cdlhrt->destscale);
-			cdlhrt->fuse = (5*TICRATE) >> 1;
-			cdlhrt->momz = mobj->scale;
-			P_SetTarget(&cdlhrt->target, mobj);
-			cdlhrt->extravalue1 = mobj->x;
-			cdlhrt->extravalue2 = mobj->y;
+			if (!P_MobjWasRemoved(cdlhrt))
+			{
+				cdlhrt->destscale = (5*mobj->scale) >> 4;
+				P_SetScale(cdlhrt, cdlhrt->destscale);
+				cdlhrt->fuse = (5*TICRATE) >> 1;
+				cdlhrt->momz = mobj->scale;
+				P_SetTarget(&cdlhrt->target, mobj);
+				cdlhrt->extravalue1 = mobj->x;
+				cdlhrt->extravalue2 = mobj->y;
+			}
 		}
 	}
 }
@@ -7698,13 +7761,16 @@ static void P_MobjSceneryThink(mobj_t *mobj)
 		&& */ (mobj->target->player->pflags & PF_SHIELDABILITY))
 		{
 			mobj_t *whoosh = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_GHOST); // done here so the offset is correct
-			P_SetMobjState(whoosh, mobj->info->raisestate);
-			whoosh->destscale = whoosh->scale << 1;
-			whoosh->scalespeed = FixedMul(whoosh->scalespeed, whoosh->scale);
-			whoosh->height = 38*whoosh->scale;
-			whoosh->fuse = 10;
-			whoosh->flags |= MF_NOCLIPHEIGHT;
-			whoosh->momz = mobj->target->momz; // Stay reasonably centered for a few frames
+			if (!P_MobjWasRemoved(whoosh))
+			{
+				P_SetMobjState(whoosh, mobj->info->raisestate);
+				whoosh->destscale = whoosh->scale << 1;
+				whoosh->scalespeed = FixedMul(whoosh->scalespeed, whoosh->scale);
+				whoosh->height = 38*whoosh->scale;
+				whoosh->fuse = 10;
+				whoosh->flags |= MF_NOCLIPHEIGHT;
+				whoosh->momz = mobj->target->momz; // Stay reasonably centered for a few frames
+			}
 			mobj->target->player->pflags &= ~PF_SHIELDABILITY; // prevent eternal whoosh
 		}
 		/* FALLTHRU */
@@ -7795,7 +7861,6 @@ static void P_MobjSceneryThink(mobj_t *mobj)
 		if (!(mobj->eflags & MFE_UNDERWATER)
 			|| (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z + mobj->height >= mobj->ceilingz)
 			|| (mobj->eflags & MFE_VERTICALFLIP && mobj->z <= mobj->floorz)
-			|| (P_CheckDeathPitCollide(mobj))
 			|| --mobj->fuse <= 0) // Bubbles eventually dissipate if they can't reach the surface.
 		{
 			// no playing sound: no point; the object is being removed
@@ -8040,8 +8105,11 @@ static boolean P_MobjBossThink(mobj_t *mobj)
 					P_RandomRange(rad, -rad) << FRACBITS,
 					P_RandomRange(hei / 2, hei) << FRACBITS,
 					MT_SMOKE);
-				P_SetObjectMomZ(particle, 2 << FRACBITS, false);
-				particle->momz += mobj->momz;
+				if (!P_MobjWasRemoved(particle))
+				{
+					P_SetObjectMomZ(particle, 2 << FRACBITS, false);
+					particle->momz += mobj->momz;
+				}
 			}
 			if (mobj->flags2 & MF2_SKULLFLY)
 #if 1
@@ -8050,8 +8118,11 @@ static boolean P_MobjBossThink(mobj_t *mobj)
 			{
 				mobj_t *spawnmobj;
 				spawnmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->painchance);
-				P_SetTarget(&spawnmobj->target, mobj);
-				spawnmobj->color = SKINCOLOR_GREY;
+				if (!P_MobjWasRemoved(spawnmobj))
+				{
+					P_SetTarget(&spawnmobj->target, mobj);
+					spawnmobj->color = SKINCOLOR_GREY;
+				}
 			}
 #endif
 			P_Boss1Thinker(mobj);
@@ -8066,8 +8137,11 @@ static boolean P_MobjBossThink(mobj_t *mobj)
 					P_RandomRange(rad, -rad) << FRACBITS,
 					P_RandomRange(hei/2, hei) << FRACBITS,
 					MT_SMOKE);
-				P_SetObjectMomZ(particle, 2 << FRACBITS, false);
-				particle->momz += mobj->momz;
+				if (!P_MobjWasRemoved(particle))
+				{
+					P_SetObjectMomZ(particle, 2 << FRACBITS, false);
+					particle->momz += mobj->momz;
+				}
 			}
 			P_Boss2Thinker(mobj);
 			break;
@@ -8081,8 +8155,11 @@ static boolean P_MobjBossThink(mobj_t *mobj)
 					P_RandomRange(rad, -rad) << FRACBITS,
 					P_RandomRange(hei/2, hei) << FRACBITS,
 					MT_SMOKE);
-				P_SetObjectMomZ(particle, 2 << FRACBITS, false);
-				particle->momz += mobj->momz;
+				if (!P_MobjWasRemoved(particle))
+				{
+					P_SetObjectMomZ(particle, 2 << FRACBITS, false);
+					particle->momz += mobj->momz;
+				}
 			}
 			P_Boss3Thinker(mobj);
 			break;
@@ -8096,8 +8173,11 @@ static boolean P_MobjBossThink(mobj_t *mobj)
 					P_RandomRange(rad, -rad) << FRACBITS,
 					P_RandomRange(hei/2, hei) << FRACBITS,
 					MT_SMOKE);
-				P_SetObjectMomZ(particle, 2 << FRACBITS, false);
-				particle->momz += mobj->momz;
+				if (!P_MobjWasRemoved(particle))
+				{
+					P_SetObjectMomZ(particle, 2 << FRACBITS, false);
+					particle->momz += mobj->momz;
+				}
 			}
 			P_Boss4Thinker(mobj);
 			break;
@@ -8225,6 +8305,9 @@ static boolean P_MobjDeadThink(mobj_t *mobj)
 				y = mobj->y + FixedMul(FINECOSINE(fa), ns);
 
 				mo2 = P_SpawnMobj(x, y, z, MT_EXPLODE);
+				if (P_MobjWasRemoved(mo2))
+					continue;
+
 				P_SetMobjStateNF(mo2, S_XPLD_EGGTRAP); // so the flickies don't lose their target if they spawn
 				ns = 4*FRACUNIT;
 				mo2->momx = FixedMul(FINESINE(fa), ns);
@@ -8270,7 +8353,8 @@ static boolean P_MobjDeadThink(mobj_t *mobj)
 					mobj->y + (P_RandomRange(r, -r) << FRACBITS),
 					mobj->z + (P_RandomKey(mobj->height >> FRACBITS) << FRACBITS),
 					MT_SONIC3KBOSSEXPLODE);
-				S_StartSound(explosion, sfx_s3kb4);
+				if (!P_MobjWasRemoved(explosion))
+					S_StartSound(explosion, sfx_s3kb4);
 			}
 			if (mobj->movedir == DMG_DROWNED)
 				P_SetObjectMomZ(mobj, -FRACUNIT/2, true); // slower fall from drowning
@@ -8288,7 +8372,8 @@ static boolean P_MobjDeadThink(mobj_t *mobj)
 				mobj->y + (P_RandomRange(r, -r) << FRACBITS),
 				mobj->z + (P_RandomKey(mobj->height >> FRACBITS) << FRACBITS),
 				MT_SONIC3KBOSSEXPLODE);
-			S_StartSound(explosion, sfx_s3kb4);
+			if (!P_MobjWasRemoved(explosion))
+				S_StartSound(explosion, sfx_s3kb4);
 		}
 		P_SetObjectMomZ(mobj, -2*FRACUNIT/3, true);
 	}
@@ -8386,9 +8471,12 @@ static void P_ArrowThink(mobj_t *mobj)
 		if (leveltime & 1)
 		{
 			mobj_t *dust = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_PARTICLE);
-			dust->tics = 18;
-			dust->scalespeed = 4096;
-			dust->destscale = FRACUNIT/32;
+			if (!P_MobjWasRemoved(dust))
+			{
+				dust->tics = 18;
+				dust->scalespeed = 4096;
+				dust->destscale = FRACUNIT/32;
+			}
 		}
 	}
 	else
@@ -8991,6 +9079,9 @@ static boolean P_TurretThink(mobj_t *mobj)
 				y = mobj->y + FixedMul(FINECOSINE(fa), ns);
 
 				mo2 = P_SpawnMobj(x, y, z, MT_EXPLODE);
+				if (P_MobjWasRemoved(mo2))
+					continue;
+
 				ns = FixedMul(16*FRACUNIT, mobj->scale);
 				mo2->momx = FixedMul(FINESINE(fa), ns);
 				mo2->momy = FixedMul(FINECOSINE(fa), ns);
@@ -9212,10 +9303,13 @@ static void P_DragonbomberThink(mobj_t *mobj)
 			if (segment != mobj) // found an unactivated segment?
 			{
 				mobj_t *mine = P_SpawnMobjFromMobj(segment, 0, 0, 0, segment->info->painchance);
-				mine->angle = segment->angle;
-				P_InstaThrust(mine, mobj->angle, P_AproxDistance(mobj->momx, mobj->momy) >> 1);
-				P_SetObjectMomZ(mine, -2*FRACUNIT, true);
-				S_StartSound(mine, mine->info->seesound);
+				if (!P_MobjWasRemoved(mine))
+				{
+					mine->angle = segment->angle;
+					P_InstaThrust(mine, mobj->angle, P_AproxDistance(mobj->momx, mobj->momy) >> 1);
+					P_SetObjectMomZ(mine, -2*FRACUNIT, true);
+					S_StartSound(mine, mine->info->seesound);
+				}
 				P_SetMobjState(segment, segment->info->raisestate);
 				mobj->threshold = mobj->info->painchance;
 			}
@@ -9434,9 +9528,12 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 			if (!mobj->threshold && !mobj->target && mobj->reactiontime)
 			{
 				mobj_t *emerald = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->reactiontime);
-				emerald->threshold = 42;
-				P_SetTarget(&mobj->target, emerald);
-				P_SetTarget(&emerald->target, mobj);
+				if (!P_MobjWasRemoved(emerald))
+				{
+					emerald->threshold = 42;
+					P_SetTarget(&mobj->target, emerald);
+					P_SetTarget(&emerald->target, mobj);
+				}
 			}
 		}
 		break;
@@ -9783,6 +9880,8 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 	case MT_TRAINDUSTSPAWNER:
 		if (leveltime % 5 == 0) {
 			mobj_t* traindust = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_PARTICLE);
+			if (P_MobjWasRemoved(traindust))
+				break;
 			traindust->flags = MF_SCENERY;
 			P_SetMobjState(traindust, S_TRAINDUST);
 			traindust->frame = P_RandomRange(0, 8)|FF_TRANS90;
@@ -9796,6 +9895,8 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 	case MT_TRAINSTEAMSPAWNER:
 		if (leveltime % 5 == 0) {
 			mobj_t *steam = P_SpawnMobj(mobj->x + FRACUNIT*P_SignedRandom()/2, mobj->y + FRACUNIT*P_SignedRandom()/2, mobj->z, MT_PARTICLE);
+			if (P_MobjWasRemoved(steam))
+				break;
 			P_SetMobjState(steam, S_TRAINSTEAM);
 			steam->frame = P_RandomRange(0, 1)|FF_TRANS90;
 			steam->tics = TICRATE*8;
@@ -9997,7 +10098,8 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s
 		newmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->type);
 
 	// Transfer flags2 (ambush, strongbox, objectflip)
-	newmobj->flags2 = mobj->flags2;
+	if (!P_MobjWasRemoved(newmobj))
+		newmobj->flags2 = mobj->flags2;
 	P_RemoveMobj(mobj); // make sure they disappear
 }
 
@@ -10019,6 +10121,8 @@ static void P_FlagFuseThink(mobj_t *mobj)
 	else
 		z = ss->sector->floorheight + z;
 	flagmo = P_SpawnMobj(x, y, z, mobj->type);
+	if (P_MobjWasRemoved(flagmo))
+		return;
 	flagmo->spawnpoint = mobj->spawnpoint;
 	if (mobj->spawnpoint->options & MTF_OBJECTFLIP)
 	{
@@ -10456,12 +10560,15 @@ void P_PushableThinker(mobj_t *mobj)
 					z = ss->sector->floorheight;
 
 				spawnmo = P_SpawnMobj(x, y, z, mobj->type);
-				spawnmo->spawnpoint = mobj->spawnpoint;
-				P_UnsetThingPosition(spawnmo);
-				spawnmo->flags = mobj->flags;
-				P_SetThingPosition(spawnmo);
-				spawnmo->flags2 = mobj->flags2;
-				spawnmo->flags |= MF_PUSHABLE;
+				if (!P_MobjWasRemoved(spawnmo))
+				{
+					spawnmo->spawnpoint = mobj->spawnpoint;
+					P_UnsetThingPosition(spawnmo);
+					spawnmo->flags = mobj->flags;
+					P_SetThingPosition(spawnmo);
+					spawnmo->flags2 = mobj->flags2;
+					spawnmo->flags |= MF_PUSHABLE;
+				}
 				P_RemoveMobj(mobj);
 				break;
 			default:
@@ -10637,29 +10744,28 @@ static fixed_t P_DefaultMobjShadowScale (mobj_t *thing)
 //
 // P_SpawnMobj
 //
-mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
+mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type, ...)
 {
 	const mobjinfo_t *info = &mobjinfo[type];
 	SINT8 sc = -1;
 	state_t *st;
 	mobj_t *mobj;
+	int status;
+	va_list args;
 
 	if (type == MT_NULL)
-	{
-#if 0
-#ifdef PARANOIA
-		I_Error("Tried to spawn MT_NULL\n");
-#endif
 		return NULL;
-#endif
-		// Hack: Some code assumes that P_SpawnMobj can never return NULL
-		// So replace MT_NULL with MT_RAY in the meantime
-		// Remove when dealt properly
-		CONS_Debug(DBG_GAMELOGIC, "Tried to spawn MT_NULL, using MT_RAY\n");
-		type = MT_RAY;
-	}
 
-	mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL);
+	if (mobjcache != NULL)
+	{
+		mobj = mobjcache;
+		mobjcache = mobjcache->hnext;
+		memset(mobj, 0, sizeof(*mobj));
+	}
+	else
+	{
+		mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL);
+	}
 
 	// this is officially a mobj, declared as soon as possible.
 	mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker;
@@ -10751,9 +10857,27 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 	// Set shadowscale here, before spawn hook so that Lua can change it
 	mobj->shadowscale = P_DefaultMobjShadowScale(mobj);
 
+	if (!(mobj->flags & MF_NOTHINK))
+		P_AddThinker(THINK_MOBJ, &mobj->thinker);
+
+
+	if (type == MT_PLAYER)
+	{
+		// when spawning MT_PLAYER, set mobj->player before calling MobjSpawn hook to prevent P_RemoveMobj from succeeding on player mobj.
+		va_start(args, type);
+		mobj->player = va_arg(args, player_t *);
+		va_end(args);
+	}
+
+	// increment mobj reference, so we don't get a dangling reference in case MobjSpawn calls P_RemoveMobj
+	mobj->thinker.references++;
+
 	// DANGER! This can cause P_SpawnMobj to return NULL!
 	// Avoid using P_RemoveMobj on the newly created mobj in "MobjSpawn" Lua hooks!
-	if (LUA_HookMobj(mobj, MOBJ_HOOK(MobjSpawn)))
+	status = LUA_HookMobj(mobj, MOBJ_HOOK(MobjSpawn));
+	mobj->thinker.references--;
+
+	if (status)
 	{
 		if (P_MobjWasRemoved(mobj))
 			return NULL;
@@ -10775,6 +10899,9 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 		case MT_BLACKEGGMAN:
 			{
 				mobj_t *spawn = P_SpawnMobj(mobj->x, mobj->z, mobj->z+mobj->height-16*FRACUNIT, MT_BLACKEGGMAN_HELPER);
+				if (P_MobjWasRemoved(spawn))
+					break;
+
 				spawn->destscale = mobj->scale;
 				P_SetScale(spawn, mobj->scale);
 				P_SetTarget(&spawn->target, mobj);
@@ -10790,6 +10917,9 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 		case MT_EGGGUARD:
 			{
 				mobj_t *spawn = P_SpawnMobj(x, y, z, MT_EGGSHIELD);
+				if (P_MobjWasRemoved(spawn))
+					break;
+
 				spawn->destscale = mobj->scale;
 				P_SetScale(spawn, mobj->scale);
 				P_SetTarget(&mobj->tracer, spawn);
@@ -10805,6 +10935,9 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 				for (i = 0; i < mobj->info->damage; i++)
 				{
 					ball = P_SpawnMobj(x, y, z, mobj->info->painchance);
+					if (P_MobjWasRemoved(ball))
+						continue;
+
 					ball->destscale = mobj->scale;
 					P_SetScale(ball, mobj->scale);
 					P_SetTarget(&ball->target, mobj);
@@ -10824,6 +10957,9 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 				for (q = 0; q < mobj->info->painchance; q++)
 				{
 					ball = P_SpawnMobj(x, y, z, mobj->info->mass);
+					if (P_MobjWasRemoved(ball))
+						continue;
+
 					ball->destscale = mobj->scale;
 					P_SetScale(ball, mobj->scale);
 					P_SetTarget(&lastball->tracer, ball);
@@ -10835,18 +10971,24 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 		case MT_CRUSHSTACEAN:
 			{
 				mobj_t *bigmeatyclaw = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_CRUSHCLAW);
-				bigmeatyclaw->angle = mobj->angle + ((mobj->flags2 & MF2_AMBUSH) ? ANGLE_90 : ANGLE_270);;
-				P_SetTarget(&mobj->tracer, bigmeatyclaw);
-				P_SetTarget(&bigmeatyclaw->tracer, mobj);
+				if (!P_MobjWasRemoved(bigmeatyclaw))
+				{
+					bigmeatyclaw->angle = mobj->angle + ((mobj->flags2 & MF2_AMBUSH) ? ANGLE_90 : ANGLE_270);
+					P_SetTarget(&mobj->tracer, bigmeatyclaw);
+					P_SetTarget(&bigmeatyclaw->tracer, mobj);
+				}
 				mobj->reactiontime >>= 1;
 			}
 			break;
 		case MT_BANPYURA:
 			{
 				mobj_t *bigmeatyclaw = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_BANPSPRING);
-				bigmeatyclaw->angle = mobj->angle + ((mobj->flags2 & MF2_AMBUSH) ? ANGLE_90 : ANGLE_270);;
-				P_SetTarget(&mobj->tracer, bigmeatyclaw);
-				P_SetTarget(&bigmeatyclaw->tracer, mobj);
+				if (!P_MobjWasRemoved(bigmeatyclaw))
+				{
+					bigmeatyclaw->angle = mobj->angle + ((mobj->flags2 & MF2_AMBUSH) ? ANGLE_90 : ANGLE_270);
+					P_SetTarget(&mobj->tracer, bigmeatyclaw);
+					P_SetTarget(&bigmeatyclaw->tracer, mobj);
+				}
 				mobj->reactiontime >>= 1;
 			}
 			break;
@@ -10861,6 +11003,9 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 				for (i = 0; i <= 16; i++) // probably should be < but staying authentic to the Lua version
 				{
 					cur = P_SpawnMobjFromMobj(mobj, 0, 0, 0, ((mobj->type == MT_WAVINGFLAG1) ? MT_WAVINGFLAGSEG1 : MT_WAVINGFLAGSEG2));;
+					if (P_MobjWasRemoved(cur))
+						continue;
+
 					P_SetTarget(&prev->tracer, cur);
 					cur->extravalue1 = i;
 					prev = cur;
@@ -10898,11 +11043,17 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 			{
 				mobj_t *fire;
 				fire = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_SPINBOBERT_FIRE1);
-				P_SetTarget(&fire->target, mobj);
-				P_SetTarget(&mobj->hnext, fire);
+				if (!P_MobjWasRemoved(fire))
+				{
+					P_SetTarget(&fire->target, mobj);
+					P_SetTarget(&mobj->hnext, fire);
+				}
 				fire = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_SPINBOBERT_FIRE2);
-				P_SetTarget(&fire->target, mobj);
-				P_SetTarget(&mobj->hprev, fire);
+				if (!P_MobjWasRemoved(fire))
+				{
+					P_SetTarget(&fire->target, mobj);
+					P_SetTarget(&mobj->hprev, fire);
+				}
 			}
 			break;
 		case MT_REDRING: // Make MT_REDRING red by default
@@ -10919,8 +11070,8 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 		case MT_EGGCAPSULE:
 			mobj->reactiontime = 0;
 			mobj->extravalue1 = mobj->cvmem =\
-			 mobj->cusval = mobj->movecount =\
-			 mobj->lastlook = mobj->extravalue2 = -1;
+			mobj->cusval = mobj->movecount =\
+			mobj->lastlook = mobj->extravalue2 = -1;
 			break;
 		case MT_REDTEAMRING:
 			mobj->color = skincolor_redteam;
@@ -10956,6 +11107,8 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 		case MT_OILLAMP:
 			{
 				mobj_t* overlay = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_OVERLAY);
+				if (P_MobjWasRemoved(overlay))
+					break;
 				P_SetTarget(&overlay->target, mobj);
 				P_SetMobjState(overlay, S_OILLAMPFLARE);
 				break;
@@ -10965,13 +11118,19 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 			mobj->flags2 |= MF2_INVERTAIMABLE;
 			break;
 		case MT_MINECARTEND:
-			P_SetTarget(&mobj->tracer, P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_MINECARTENDSOLID));
-			mobj->tracer->angle = mobj->angle + ANGLE_90;
+			{
+				mobj_t *mcsolid = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_MINECARTENDSOLID);
+				if (P_MobjWasRemoved(mcsolid))
+					break;
+				P_SetTarget(&mobj->tracer, mcsolid);
+				mcsolid->angle = mobj->angle + ANGLE_90;
+			}
 			break;
 		case MT_TORCHFLOWER:
 			{
 				mobj_t *fire = P_SpawnMobjFromMobj(mobj, 0, 0, 46*FRACUNIT, MT_FLAME);
-				P_SetTarget(&mobj->target, fire);
+				if (!P_MobjWasRemoved(fire))
+					P_SetTarget(&mobj->target, fire);
 				break;
 			}
 		case MT_PYREFLY:
@@ -10980,10 +11139,16 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 			mobj->fuse = 100;
 			break;
 		case MT_SIGN:
-			P_SetTarget(&mobj->tracer, P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_OVERLAY));
-			P_SetTarget(&mobj->tracer->target, mobj);
-			P_SetMobjState(mobj->tracer, S_SIGNBOARD);
-			mobj->tracer->movedir = ANGLE_90;
+			{
+				mobj_t *sign = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_OVERLAY);
+				if (P_MobjWasRemoved(sign))
+					break;
+
+				P_SetTarget(&mobj->tracer, sign);
+				P_SetTarget(&sign->target, mobj);
+				P_SetMobjState(sign, S_SIGNBOARD);
+				sign->movedir = ANGLE_90;
+			}
 		default:
 			break;
 	}
@@ -11006,9 +11171,6 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 		}
 	}
 
-	if (!(mobj->flags & MF_NOTHINK))
-		P_AddThinker(THINK_MOBJ, &mobj->thinker);
-
 	if (mobj->skin) // correct inadequecies above.
 	{
 		mobj->sprite2 = P_GetSkinSprite2(mobj->skin, (mobj->frame & FF_FRAMEMASK), NULL);
@@ -11218,7 +11380,9 @@ void P_RemoveMobj(mobj_t *mobj)
 		INT32 prevreferences;
 		if (!mobj->thinker.references)
 		{
-			Z_Free(mobj); // No refrrences? Can be removed immediately! :D
+			// no references, dump it directly in the mobj cache
+			mobj->hnext = mobjcache;
+			mobjcache = mobj;
 			return;
 		}
 
@@ -11617,8 +11781,10 @@ void P_SpawnPlayer(INT32 playernum)
 	if ((netgame || multiplayer) && ((gametyperules & GTR_SPAWNINVUL) || 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;
+    // MT_PLAYER cannot be removed, so this shouldn't be able to return NULL.
+	mobj = P_SpawnMobj(0, 0, 0, MT_PLAYER, p);
+	I_Assert(mobj != NULL);
+	p->mo = mobj;
 
 	mobj->angle = 0;
 
@@ -11669,10 +11835,13 @@ void P_SpawnPlayer(INT32 playernum)
 			if (p == players) // this is totally the wrong place to do this aaargh.
 			{
 				mobj_t *idya = P_SpawnMobjFromMobj(mobj, 0, 0, mobj->height, MT_GOTEMERALD);
-				idya->health = 0; // for identification
-				P_SetTarget(&idya->target, mobj);
-				P_SetMobjState(idya, mobjinfo[MT_GOTEMERALD].missilestate);
-				P_SetTarget(&mobj->tracer, idya);
+				if (!P_MobjWasRemoved(idya))
+				{
+					idya->health = 0; // for identification
+					P_SetTarget(&idya->target, mobj);
+					P_SetMobjState(idya, mobjinfo[MT_GOTEMERALD].missilestate);
+					P_SetTarget(&mobj->tracer, idya);
+				}
 			}
 		}
 		else if (sstimer)
@@ -11771,9 +11940,9 @@ void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing)
 			mobj->flags2 |= MF2_OBJECTFLIP;
 		}
 		if (mthing->args[0])
-			P_SetPlayerMobjState(mobj, S_PLAY_FALL);
+			P_SetMobjState(mobj, S_PLAY_FALL);
 		else if (metalrecording)
-			P_SetPlayerMobjState(mobj, S_PLAY_WAIT);
+			P_SetMobjState(mobj, S_PLAY_WAIT);
 	}
 	else
 		z = floor;
@@ -12394,8 +12563,10 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
 	mphase = (FixedAngle(mphase << FRACBITS) >> ANGLETOFINESHIFT);
 	mroll = (FixedAngle(mroll << FRACBITS) >> ANGLETOFINESHIFT);
 
-#define makemace(mobjtype, dist, moreflags2) {\
+#define makemace(mobjtype, dist, moreflags2) do {\
 	spawnee = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobjtype);\
+	if (P_MobjWasRemoved(spawnee))\
+		break;\
 	P_SetTarget(&spawnee->tracer, mobj);\
 	spawnee->threshold = mphase;\
 	spawnee->friction = mroll;\
@@ -12408,7 +12579,7 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
 	P_SetTarget(&hprev->hnext, spawnee);\
 	P_SetTarget(&spawnee->hprev, hprev);\
 	hprev = spawnee;\
-}
+} while (0)
 
 	mdosound = (mspeed && !(mthing->args[8] & TMM_SILENT));
 	mdocenter = (macetype && (mthing->args[8] & TMM_CENTERLINK));
@@ -12473,7 +12644,7 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
 
 		if (!mwidth)
 		{
-			if (mdosound && mnumspokes <= mmin) // Can it make a sound?
+			if (!P_MobjWasRemoved(spawnee) && mdosound && mnumspokes <= mmin) // Can it make a sound?
 				spawnee->flags2 |= MF2_BOSSNOTRAP;
 		}
 		else
@@ -12486,7 +12657,7 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
 				while ((mwidthset -= widthfactor) > -mwidth)
 				{
 					makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH);
-					if (mdosound && (mwidthset == msound) && mnumspokes <= mmin) // Can it make a sound?
+					if (!P_MobjWasRemoved(spawnee) && mdosound && (mwidthset == msound) && mnumspokes <= mmin) // Can it make a sound?
 						spawnee->flags2 |= MF2_BOSSNOTRAP;
 				}
 			}
@@ -12495,7 +12666,7 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
 				while ((mwidthset += widthfactor) < -mwidth)
 				{
 					makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH);
-					if (mdosound && (mwidthset == msound) && mnumspokes <= mmin) // Can it make a sound?
+					if (!P_MobjWasRemoved(spawnee) && mdosound && (mwidthset == msound) && mnumspokes <= mmin) // Can it make a sound?
 						spawnee->flags2 |= MF2_BOSSNOTRAP;
 				}
 			}
@@ -12677,15 +12848,21 @@ static boolean P_SetupNiGHTSDrone(mapthing_t *mthing, mobj_t *mobj)
 		mobj_t *droneman = P_SpawnMobjFromMobj(mobj, 0, 0, dronemanoffset, MT_NIGHTSDRONE_MAN);
 
 		P_SetTarget(&mobj->target, goalpost);
-		P_SetTarget(&goalpost->target, sparkle);
-		P_SetTarget(&goalpost->tracer, droneman);
+		if (!P_MobjWasRemoved(goalpost))
+		{
+			P_SetTarget(&goalpost->target, sparkle);
+			P_SetTarget(&goalpost->tracer, droneman);
+		}
 
 		// correct Z position
 		if (flip)
 		{
-			P_MoveOrigin(goalpost, goalpost->x, goalpost->y, mobj->z + goaloffset);
-			P_MoveOrigin(sparkle, sparkle->x, sparkle->y, mobj->z + sparkleoffset);
-			P_MoveOrigin(droneman, droneman->x, droneman->y, mobj->z + dronemanoffset);
+			if (!P_MobjWasRemoved(goalpost))
+				P_MoveOrigin(goalpost, goalpost->x, goalpost->y, mobj->z + goaloffset);
+			if (!P_MobjWasRemoved(sparkle))
+				P_MoveOrigin(sparkle, sparkle->x, sparkle->y, mobj->z + sparkleoffset);
+			if (!P_MobjWasRemoved(droneman))
+				P_MoveOrigin(droneman, droneman->x, droneman->y, mobj->z + dronemanoffset);
 		}
 
 		// Remember position preference for later
@@ -12707,9 +12884,12 @@ static boolean P_SetupNiGHTSDrone(mapthing_t *mthing, mobj_t *mobj)
 		}
 
 		// Remember old Z position and flags for correction detection
-		goalpost->movefactor = mobj->z;
-		goalpost->friction = mobj->height;
-		goalpost->threshold = mobj->flags & (MF_SLIDEME|MF_GRENADEBOUNCE);
+		if (!P_MobjWasRemoved(goalpost))
+		{
+			goalpost->movefactor = mobj->z;
+			goalpost->friction = mobj->height;
+			goalpost->threshold = mobj->flags & (MF_SLIDEME|MF_GRENADEBOUNCE);
+		}
 	}
 	return true;
 }
@@ -12727,30 +12907,54 @@ static boolean P_SetupBooster(mapthing_t* mthing, mobj_t* mobj, boolean strong)
 	statenum_t rollerstate = strong ? S_REDBOOSTERROLLER : S_YELLOWBOOSTERROLLER;
 
 	mobj_t *seg = P_SpawnMobjFromMobj(mobj, 26*x1, 26*y1, 0, MT_BOOSTERSEG);
-	seg->angle = angle - ANGLE_90;
-	P_SetMobjState(seg, facestate);
+	if (!P_MobjWasRemoved(seg))
+	{
+		seg->angle = angle - ANGLE_90;
+		P_SetMobjState(seg, facestate);
+	}
 	seg = P_SpawnMobjFromMobj(mobj, -26*x1, -26*y1, 0, MT_BOOSTERSEG);
-	seg->angle = angle + ANGLE_90;
-	P_SetMobjState(seg, facestate);
+	if (!P_MobjWasRemoved(seg))
+	{
+		seg->angle = angle + ANGLE_90;
+		P_SetMobjState(seg, facestate);
+	}
 	seg = P_SpawnMobjFromMobj(mobj, 21*x2, 21*y2, 0, MT_BOOSTERSEG);
-	seg->angle = angle;
-	P_SetMobjState(seg, leftstate);
+	if (!P_MobjWasRemoved(seg))
+	{
+		seg->angle = angle;
+		P_SetMobjState(seg, leftstate);
+	}
 	seg = P_SpawnMobjFromMobj(mobj, -21*x2, -21*y2, 0, MT_BOOSTERSEG);
-	seg->angle = angle;
-	P_SetMobjState(seg, rightstate);
+	if (!P_MobjWasRemoved(seg))
+	{
+		seg->angle = angle;
+		P_SetMobjState(seg, rightstate);
+	}
 
 	seg = P_SpawnMobjFromMobj(mobj, 13*(x1 + x2), 13*(y1 + y2), 0, MT_BOOSTERROLLER);
-	seg->angle = angle;
-	P_SetMobjState(seg, rollerstate);
+	if (!P_MobjWasRemoved(seg))
+	{
+		seg->angle = angle;
+		P_SetMobjState(seg, rollerstate);
+	}
 	seg = P_SpawnMobjFromMobj(mobj, 13*(x1 - x2), 13*(y1 - y2), 0, MT_BOOSTERROLLER);
-	seg->angle = angle;
-	P_SetMobjState(seg, rollerstate);
+	if (!P_MobjWasRemoved(seg))
+	{
+		seg->angle = angle;
+		P_SetMobjState(seg, rollerstate);
+	}
 	seg = P_SpawnMobjFromMobj(mobj, -13*(x1 + x2), -13*(y1 + y2), 0, MT_BOOSTERROLLER);
-	seg->angle = angle;
-	P_SetMobjState(seg, rollerstate);
+	if (!P_MobjWasRemoved(seg))
+	{
+		seg->angle = angle;
+		P_SetMobjState(seg, rollerstate);
+	}
 	seg = P_SpawnMobjFromMobj(mobj, -13*(x1 - x2), -13*(y1 - y2), 0, MT_BOOSTERROLLER);
-	seg->angle = angle;
-	P_SetMobjState(seg, rollerstate);
+	if (!P_MobjWasRemoved(seg))
+	{
+		seg->angle = angle;
+		P_SetMobjState(seg, rollerstate);
+	}
 
 	return true;
 }
@@ -12758,6 +12962,9 @@ static boolean P_SetupBooster(mapthing_t* mthing, mobj_t* mobj, boolean strong)
 static mobj_t *P_MakeSoftwareCorona(mobj_t *mo, INT32 height)
 {
 	mobj_t *corona = P_SpawnMobjFromMobj(mo, 0, 0, height<<FRACBITS, MT_PARTICLE);
+	if (P_MobjWasRemoved(corona))
+		return NULL;
+
 	corona->sprite = SPR_FLAM;
 	corona->frame = (FF_FULLBRIGHT|FF_TRANS90|12);
 	corona->tics = -1;
@@ -12893,6 +13100,9 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		if (mthing->args[0])
 		{
 			mobj_t *corona = P_MakeSoftwareCorona(mobj, 20);
+			if (P_MobjWasRemoved(corona))
+				break;
+
 			P_SetScale(corona, (corona->destscale = mobj->scale*3));
 			P_SetTarget(&mobj->tracer, corona);
 		}
@@ -12901,11 +13111,17 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		if (!(mthing->args[0] & TMFH_NOFLAME)) // Spawn the fire
 		{
 			mobj_t *flame = P_SpawnMobjFromMobj(mobj, 0, 0, mobj->height, MT_FLAME);
+			if (P_MobjWasRemoved(flame))
+				break;
+
 			P_SetTarget(&flame->target, mobj);
 			flame->flags2 |= MF2_BOSSNOTRAP;
 			if (mthing->args[0] & TMFH_CORONA)
 			{
 				mobj_t *corona = P_MakeSoftwareCorona(flame, 20);
+				if (P_MobjWasRemoved(corona))
+					break;
+
 				P_SetScale(corona, (corona->destscale = flame->scale*3));
 				P_SetTarget(&flame->tracer, corona);
 			}
@@ -12922,6 +13138,8 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		if (!(mthing->args[0])) // take the torch out of the crafting recipe
 		{
 			mobj_t *overlay = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_OVERLAY);
+			if (P_MobjWasRemoved(overlay))
+				break;
 			P_SetTarget(&overlay->target, mobj);
 			P_SetMobjState(overlay, mobj->info->raisestate);
 		}
@@ -13001,9 +13219,15 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 	case MT_THZTREE:
 	{ // Spawn the branches
 		angle_t mobjangle = FixedAngle((mthing->angle % 113) << FRACBITS);
-		P_SpawnMobjFromMobj(mobj, FRACUNIT, 0, 0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_22h;
-		P_SpawnMobjFromMobj(mobj, 0, FRACUNIT, 0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_157h;
-		P_SpawnMobjFromMobj(mobj, -FRACUNIT, 0, 0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_270;
+		mobj_t *branch = P_SpawnMobjFromMobj(mobj, FRACUNIT, 0, 0, MT_THZTREEBRANCH);
+		if (!P_MobjWasRemoved(branch))
+			branch->angle = mobjangle + ANGLE_22h;
+		branch = P_SpawnMobjFromMobj(mobj, 0, FRACUNIT, 0, MT_THZTREEBRANCH);
+		if (!P_MobjWasRemoved(branch))
+			branch->angle = mobjangle + ANGLE_157h;
+		branch = P_SpawnMobjFromMobj(mobj, -FRACUNIT, 0, 0, MT_THZTREEBRANCH);
+		if (!P_MobjWasRemoved(branch))
+			branch->angle = mobjangle + ANGLE_270;
 	}
 	break;
 	case MT_TUTORIALPLANT:
@@ -13013,26 +13237,34 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		for (i = 0; i < 6; i++)
 		{
 			segment = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_TUTORIALLEAF);
+			if (P_MobjWasRemoved(segment))
+				continue;
 			segment->angle = mobj->angle + FixedAngle(i*60*FRACUNIT);
 			P_SetMobjState(segment, S_TUTORIALLEAF1 + mthing->args[0]);
 		}
 		for (i = 0; i < 3; i++)
 		{
 			segment = P_SpawnMobjFromMobj(mobj, 0, 0, 112*FRACUNIT, MT_TUTORIALFLOWER);
+			if (P_MobjWasRemoved(segment))
+				continue;
 			segment->angle = mobj->angle + FixedAngle(i*120*FRACUNIT);
 			P_SetMobjState(segment, S_TUTORIALFLOWER1 + mthing->args[0]);
 		}
-		P_SetMobjState(P_SpawnMobjFromMobj(mobj, 0, 0, 112*FRACUNIT, MT_TUTORIALFLOWERF), S_TUTORIALFLOWERF1 + mthing->args[0]);
+		segment = P_SpawnMobjFromMobj(mobj, 0, 0, 112*FRACUNIT, MT_TUTORIALFLOWERF);
+		if (!P_MobjWasRemoved(segment))
+			P_SetMobjState(segment, S_TUTORIALFLOWERF1 + mthing->args[0]);
 	}
 	break;
 	case MT_CEZPOLE1:
 	case MT_CEZPOLE2:
 	{ // Spawn the banner
 		angle_t mobjangle = FixedAngle(mthing->angle << FRACBITS);
-		P_SpawnMobjFromMobj(mobj,
+		mobj_t *banner = P_SpawnMobjFromMobj(mobj,
 			P_ReturnThrustX(mobj, mobjangle, 4 << FRACBITS),
 			P_ReturnThrustY(mobj, mobjangle, 4 << FRACBITS),
-			0, ((mobj->type == MT_CEZPOLE1) ? MT_CEZBANNER1 : MT_CEZBANNER2))->angle = mobjangle + ANGLE_90;
+			0, ((mobj->type == MT_CEZPOLE1) ? MT_CEZBANNER1 : MT_CEZBANNER2));
+		if (!P_MobjWasRemoved(banner))
+			banner->angle = mobjangle + ANGLE_90;
 	}
 	break;
 	case MT_HHZTREE_TOP:
@@ -13041,8 +13273,11 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		mobj_t* leaf;
 #define doleaf(x, y) \
 			leaf = P_SpawnMobjFromMobj(mobj, x, y, 0, MT_HHZTREE_PART);\
-			leaf->angle = mobjangle;\
-			P_SetMobjState(leaf, leaf->info->seestate);\
+			if (!P_MobjWasRemoved(leaf))\
+			{\
+				leaf->angle = mobjangle;\
+				P_SetMobjState(leaf, leaf->info->seestate);\
+			}\
 			mobjangle += ANGLE_90
 		doleaf(FRACUNIT, 0);
 		doleaf(0, FRACUNIT);
@@ -13081,8 +13316,9 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 			angle_t fa = (angle >> ANGLETOFINESHIFT) & FINEMASK;
 			fixed_t xoffs = FINECOSINE(fa);
 			fixed_t yoffs = FINESINE(fa);
-			mobj_t* leaf = P_SpawnMobjFromMobj(mobj, xoffs, yoffs, 0, MT_BIGFERNLEAF);
-			leaf->angle = angle;
+			mobj_t *leaf = P_SpawnMobjFromMobj(mobj, xoffs, yoffs, 0, MT_BIGFERNLEAF);
+			if (!P_MobjWasRemoved(leaf))
+				leaf->angle = angle;
 			angle += ANGLE_45;
 		}
 		break;
@@ -13119,6 +13355,8 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		{
 			mobj_t* elecmobj;
 			elecmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_CYBRAKDEMON_ELECTRIC_BARRIER);
+			if (P_MobjWasRemoved(elecmobj))
+				break;
 			P_SetTarget(&elecmobj->target, mobj);
 			elecmobj->angle = FixedAngle(mthing->angle << FRACBITS);
 			elecmobj->destscale = mobj->scale*2;
@@ -13173,6 +13411,12 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 				mobj->x - P_ReturnThrustX(mobj, mobjangle, baseradius),
 				mobj->y - P_ReturnThrustY(mobj, mobjangle, baseradius),
 				mobj->z, MT_WALLSPIKEBASE);
+			if (P_MobjWasRemoved(base))
+			{
+				// if we can't spawn the base, don't spawn the spike at all.
+				P_RemoveMobj(mobj);
+				return false;
+			}
 			base->angle = mobjangle + ANGLE_90;
 			base->destscale = mobj->destscale;
 			P_SetScale(base, mobj->scale);
@@ -13348,6 +13592,8 @@ static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y,
 	boolean doangle = true;
 
 	mobj = P_SpawnMobj(x, y, z, i);
+	if (mobj == NULL)
+		return NULL;
 	mobj->spawnpoint = mthing;
 
 	P_SetScale(mobj, FixedMul(mobj->scale, mthing->scale));
@@ -13445,6 +13691,9 @@ void P_SpawnHoop(mapthing_t *mthing)
 	fixed_t z = P_GetMobjSpawnHeight(MT_HOOP, x, y, mthing->z << FRACBITS, 0, false, mthing->scale, mthing->options & MTF_ABSOLUTEZ);
 
 	hoopcenter = P_SpawnMobj(x, y, z, MT_HOOPCENTER);
+	if (P_MobjWasRemoved(hoopcenter))
+		return;
+
 	hoopcenter->spawnpoint = mthing;
 	hoopcenter->z -= hoopcenter->height/2;
 
@@ -13475,6 +13724,8 @@ void P_SpawnHoop(mapthing_t *mthing)
 		FV4_Copy(&v, FM_MultMatrixVec4(&yawmatrix, &v, &res));
 
 		mobj = P_SpawnMobj(x + v.x, y + v.y, z + v.z, MT_HOOP);
+		if (P_MobjWasRemoved(mobj))
+			continue;
 		mobj->z -= mobj->height/2;
 
 		if (maptol & TOL_XMAS)
@@ -13519,6 +13770,8 @@ void P_SpawnHoop(mapthing_t *mthing)
 			FV4_Copy(&v, FM_MultMatrixVec4(&yawmatrix, &v, &res));
 
 			mobj = P_SpawnMobj(x + v.x, y + v.y, z + v.z, MT_HOOPCOLLIDE);
+			if (P_MobjWasRemoved(mobj))
+				continue;
 			mobj->z -= mobj->height/2;
 
 			// Link all the collision sprites together.
@@ -13791,6 +14044,8 @@ mobj_t *P_SpawnXYZMissile(mobj_t *source, mobj_t *dest, mobjtype_t type,
 		z -= FixedMul(mobjinfo[type].height, source->scale);
 
 	th = P_SpawnMobj(x, y, z, type);
+	if (P_MobjWasRemoved(th))
+		return NULL;
 
 	if (source->eflags & MFE_VERTICALFLIP)
 		th->flags2 |= MF2_OBJECTFLIP;
@@ -13853,6 +14108,8 @@ mobj_t *P_SpawnAlteredDirectionMissile(mobj_t *source, mobjtype_t type, fixed_t
 		z -= FixedMul(mobjinfo[type].height, source->scale);
 
 	th = P_SpawnMobj(x, y, z, type);
+	if (P_MobjWasRemoved(th))
+		return NULL;
 
 	if (source->eflags & MFE_VERTICALFLIP)
 		th->flags2 |= MF2_OBJECTFLIP;
@@ -13918,6 +14175,8 @@ mobj_t *P_SpawnPointMissile(mobj_t *source, fixed_t xa, fixed_t ya, fixed_t za,
 		z -= FixedMul(mobjinfo[type].height, source->scale);
 
 	th = P_SpawnMobj(x, y, z, type);
+	if (P_MobjWasRemoved(th))
+		return NULL;
 
 	if (source->eflags & MFE_VERTICALFLIP)
 		th->flags2 |= MF2_OBJECTFLIP;
@@ -13988,6 +14247,8 @@ mobj_t *P_SpawnMissile(mobj_t *source, mobj_t *dest, mobjtype_t type)
 		z -= FixedMul(mobjinfo[type].height, source->scale);
 
 	th = P_SpawnMobj(source->x, source->y, z, type);
+	if (P_MobjWasRemoved(th))
+		return NULL;
 
 	if (source->eflags & MFE_VERTICALFLIP)
 		th->flags2 |= MF2_OBJECTFLIP;
@@ -14089,6 +14350,8 @@ mobj_t *P_SPMAngle(mobj_t *source, mobjtype_t type, angle_t angle, UINT8 allowai
 		z = source->z + source->height/3;
 
 	th = P_SpawnMobj(x, y, z, type);
+	if (P_MobjWasRemoved(th))
+		return NULL;
 
 	if (source->eflags & MFE_VERTICALFLIP)
 		th->flags2 |= MF2_OBJECTFLIP;
diff --git a/src/p_polyobj.c b/src/p_polyobj.c
index b207bb740f6b6c6cd87f88da38e1b1f74958f0d2..331bc5c7f0397876d10c92ad6c316d2ffeb56d41 100644
--- a/src/p_polyobj.c
+++ b/src/p_polyobj.c
@@ -1243,6 +1243,8 @@ boolean Polyobj_rotate(polyobj_t *po, angle_t delta, boolean turnplayers, boolea
 // Returns NULL if no such polyobject exists.
 polyobj_t *Polyobj_GetForNum(INT32 id)
 {
+	if (numPolyObjects == 0)
+		return NULL;
 	INT32 curidx  = PolyObjects[id % numPolyObjects].first;
 
 	while (curidx != numPolyObjects && PolyObjects[curidx].id != id)
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 47448c78a76e9d90d5d84a3235d126a64bff0b5a..0f3668f5afb5dca5258eda7c9970c6059fce1c4a 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -867,24 +867,47 @@ static void P_NetUnArchiveWaypoints(void)
 #define SD_TRIGGERTAG 0x02
 #define SD_TRIGGERER 0x04
 #define SD_GRAVITY   0x08
-
-#define LD_FLAG     0x01
-#define LD_SPECIAL  0x02
-#define LD_CLLCOUNT 0x04
-#define LD_S1TEXOFF 0x08
-#define LD_S1TOPTEX 0x10
-#define LD_S1BOTTEX 0x20
-#define LD_S1MIDTEX 0x40
-#define LD_DIFF2    0x80
-
-// diff2 flags
-#define LD_S2TEXOFF      0x01
-#define LD_S2TOPTEX      0x02
-#define LD_S2BOTTEX      0x04
-#define LD_S2MIDTEX      0x08
-#define LD_ARGS          0x10
-#define LD_STRINGARGS    0x20
-#define LD_EXECUTORDELAY 0x40
+#define SD_FXSCALE   0x10
+#define SD_FYSCALE   0x20
+#define SD_CXSCALE   0x40
+#define SD_CYSCALE   0x80
+
+#define LD_FLAG          0x01
+#define LD_SPECIAL       0x02
+#define LD_CLLCOUNT      0x04
+#define LD_ARGS          0x08
+#define LD_STRINGARGS    0x10
+#define LD_EXECUTORDELAY 0x20
+#define LD_SIDE1         0x40
+#define LD_SIDE2         0x80
+
+// sidedef flags
+enum
+{
+	LD_SDTEXOFFX     = 1,
+	LD_SDTEXOFFY     = 1<<1,
+	LD_SDTOPTEX      = 1<<2,
+	LD_SDBOTTEX      = 1<<3,
+	LD_SDMIDTEX      = 1<<4,
+	LD_SDTOPOFFX     = 1<<5,
+	LD_SDTOPOFFY     = 1<<6,
+	LD_SDMIDOFFX     = 1<<7,
+	LD_SDMIDOFFY     = 1<<8,
+	LD_SDBOTOFFX     = 1<<9,
+	LD_SDBOTOFFY     = 1<<10,
+	LD_SDTOPSCALEX   = 1<<11,
+	LD_SDTOPSCALEY   = 1<<12,
+	LD_SDMIDSCALEX   = 1<<13,
+	LD_SDMIDSCALEY   = 1<<14,
+	LD_SDBOTSCALEX   = 1<<15,
+	LD_SDBOTSCALEY   = 1<<16,
+	LD_SDLIGHT       = 1<<17,
+	LD_SDTOPLIGHT    = 1<<18,
+	LD_SDMIDLIGHT    = 1<<19,
+	LD_SDBOTLIGHT    = 1<<20,
+	LD_SDREPEATCNT   = 1<<21,
+	LD_SDFLAGS       = 1<<22
+};
 
 static boolean P_AreArgsEqual(const line_t *li, const line_t *spawnli)
 {
@@ -1035,6 +1058,14 @@ static void ArchiveSectors(void)
 			diff2 |= SD_CXOFFS;
 		if (ss->ceilingyoffset != spawnss->ceilingyoffset)
 			diff2 |= SD_CYOFFS;
+		if (ss->floorxscale != spawnss->floorxscale)
+			diff2 |= SD_FXSCALE;
+		if (ss->flooryscale != spawnss->flooryscale)
+			diff2 |= SD_FYSCALE;
+		if (ss->ceilingxscale != spawnss->ceilingxscale)
+			diff2 |= SD_CXSCALE;
+		if (ss->ceilingyscale != spawnss->ceilingyscale)
+			diff2 |= SD_CYSCALE;
 		if (ss->floorangle != spawnss->floorangle)
 			diff2 |= SD_FLOORANG;
 		if (ss->ceilingangle != spawnss->ceilingangle)
@@ -1145,6 +1176,14 @@ static void ArchiveSectors(void)
 				WRITEUINT8(save_p, ss->triggerer);
 			if (diff4 & SD_GRAVITY)
 				WRITEFIXED(save_p, ss->gravity);
+			if (diff4 & SD_FXSCALE)
+				WRITEFIXED(save_p, ss->floorxscale);
+			if (diff4 & SD_FYSCALE)
+				WRITEFIXED(save_p, ss->flooryscale);
+			if (diff4 & SD_CXSCALE)
+				WRITEFIXED(save_p, ss->ceilingxscale);
+			if (diff4 & SD_CYSCALE)
+				WRITEFIXED(save_p, ss->ceilingyscale);
 			if (diff & SD_FFLOORS)
 				ArchiveFFloors(ss);
 		}
@@ -1266,24 +1305,117 @@ static void UnArchiveSectors(void)
 			sectors[i].triggerer = READUINT8(save_p);
 		if (diff4 & SD_GRAVITY)
 			sectors[i].gravity = READFIXED(save_p);
+		if (diff4 & SD_FXSCALE)
+			sectors[i].floorxscale = READFIXED(save_p);
+		if (diff4 & SD_FYSCALE)
+			sectors[i].flooryscale = READFIXED(save_p);
+		if (diff4 & SD_CXSCALE)
+			sectors[i].ceilingxscale = READFIXED(save_p);
+		if (diff4 & SD_CYSCALE)
+			sectors[i].ceilingyscale = READFIXED(save_p);
 
 		if (diff & SD_FFLOORS)
 			UnArchiveFFloors(&sectors[i]);
 	}
 }
 
+static UINT32 GetSideDiff(const side_t *si, const side_t *spawnsi)
+{
+	UINT32 diff = 0;
+	if (si->textureoffset != spawnsi->textureoffset)
+		diff |= LD_SDTEXOFFX;
+	if (si->rowoffset != spawnsi->rowoffset)
+		diff |= LD_SDTEXOFFY;
+	//SoM: 4/1/2000: Some textures are colormaps. Don't worry about invalid textures.
+	if (si->toptexture != spawnsi->toptexture)
+		diff |= LD_SDTOPTEX;
+	if (si->bottomtexture != spawnsi->bottomtexture)
+		diff |= LD_SDBOTTEX;
+	if (si->midtexture != spawnsi->midtexture)
+		diff |= LD_SDMIDTEX;
+	if (si->offsetx_top != spawnsi->offsetx_top)
+		diff |= LD_SDTOPOFFX;
+	if (si->offsetx_mid != spawnsi->offsetx_mid)
+		diff |= LD_SDMIDOFFX;
+	if (si->offsetx_bottom != spawnsi->offsetx_bottom)
+		diff |= LD_SDBOTOFFX;
+	if (si->offsety_top != spawnsi->offsety_top)
+		diff |= LD_SDTOPOFFY;
+	if (si->offsety_mid != spawnsi->offsety_mid)
+		diff |= LD_SDMIDOFFY;
+	if (si->offsety_bottom != spawnsi->offsety_bottom)
+		diff |= LD_SDBOTOFFY;
+	if (si->scalex_top != spawnsi->scalex_top)
+		diff |= LD_SDTOPSCALEX;
+	if (si->scalex_mid != spawnsi->scalex_mid)
+		diff |= LD_SDMIDSCALEX;
+	if (si->scalex_bottom != spawnsi->scalex_bottom)
+		diff |= LD_SDBOTSCALEX;
+	if (si->scaley_top != spawnsi->scaley_top)
+		diff |= LD_SDTOPSCALEY;
+	if (si->scaley_mid != spawnsi->scaley_mid)
+		diff |= LD_SDMIDSCALEY;
+	if (si->scaley_bottom != spawnsi->scaley_bottom)
+		diff |= LD_SDBOTSCALEY;
+	if (si->repeatcnt != spawnsi->repeatcnt)
+		diff |= LD_SDREPEATCNT;
+	return diff;
+}
+
+static void ArchiveSide(const side_t *si, UINT32 diff)
+{
+	WRITEUINT32(save_p, diff);
+
+	if (diff & LD_SDTEXOFFX)
+		WRITEFIXED(save_p, si->textureoffset);
+	if (diff & LD_SDTEXOFFY)
+		WRITEFIXED(save_p, si->rowoffset);
+	if (diff & LD_SDTOPTEX)
+		WRITEINT32(save_p, si->toptexture);
+	if (diff & LD_SDBOTTEX)
+		WRITEINT32(save_p, si->bottomtexture);
+	if (diff & LD_SDMIDTEX)
+		WRITEINT32(save_p, si->midtexture);
+	if (diff & LD_SDTOPOFFX)
+		WRITEFIXED(save_p, si->offsetx_top);
+	if (diff & LD_SDMIDOFFX)
+		WRITEFIXED(save_p, si->offsetx_mid);
+	if (diff & LD_SDBOTOFFX)
+		WRITEFIXED(save_p, si->offsetx_bottom);
+	if (diff & LD_SDTOPOFFY)
+		WRITEFIXED(save_p, si->offsety_top);
+	if (diff & LD_SDMIDOFFY)
+		WRITEFIXED(save_p, si->offsety_mid);
+	if (diff & LD_SDBOTOFFY)
+		WRITEFIXED(save_p, si->offsety_bottom);
+	if (diff & LD_SDTOPSCALEX)
+		WRITEFIXED(save_p, si->scalex_top);
+	if (diff & LD_SDMIDSCALEX)
+		WRITEFIXED(save_p, si->scalex_mid);
+	if (diff & LD_SDBOTSCALEX)
+		WRITEFIXED(save_p, si->scalex_bottom);
+	if (diff & LD_SDTOPSCALEY)
+		WRITEFIXED(save_p, si->scaley_top);
+	if (diff & LD_SDMIDSCALEY)
+		WRITEFIXED(save_p, si->scaley_mid);
+	if (diff & LD_SDBOTSCALEY)
+		WRITEFIXED(save_p, si->scaley_bottom);
+	if (diff & LD_SDREPEATCNT)
+		WRITEINT16(save_p, si->repeatcnt);
+}
+
 static void ArchiveLines(void)
 {
 	size_t i;
 	const line_t *li = lines;
 	const line_t *spawnli = spawnlines;
-	const side_t *si;
-	const side_t *spawnsi;
-	UINT8 diff, diff2; // no diff3
+	UINT8 diff;
+	UINT32 diff2;
+	UINT32 diff3;
 
 	for (i = 0; i < numlines; i++, spawnli++, li++)
 	{
-		diff = diff2 = 0;
+		diff = diff2 = diff3 = 0;
 
 		if (li->special != spawnli->special)
 			diff |= LD_SPECIAL;
@@ -1292,84 +1424,44 @@ static void ArchiveLines(void)
 			diff |= LD_CLLCOUNT;
 
 		if (!P_AreArgsEqual(li, spawnli))
-			diff2 |= LD_ARGS;
+			diff |= LD_ARGS;
 
 		if (!P_AreStringArgsEqual(li, spawnli))
-			diff2 |= LD_STRINGARGS;
+			diff |= LD_STRINGARGS;
 
 		if (li->executordelay != spawnli->executordelay)
-			diff2 |= LD_EXECUTORDELAY;
+			diff |= LD_EXECUTORDELAY;
 
 		if (li->sidenum[0] != NO_SIDEDEF)
 		{
-			si = &sides[li->sidenum[0]];
-			spawnsi = &spawnsides[li->sidenum[0]];
-			if (si->textureoffset != spawnsi->textureoffset)
-				diff |= LD_S1TEXOFF;
-			//SoM: 4/1/2000: Some textures are colormaps. Don't worry about invalid textures.
-			if (si->toptexture != spawnsi->toptexture)
-				diff |= LD_S1TOPTEX;
-			if (si->bottomtexture != spawnsi->bottomtexture)
-				diff |= LD_S1BOTTEX;
-			if (si->midtexture != spawnsi->midtexture)
-				diff |= LD_S1MIDTEX;
+			diff2 = GetSideDiff(&sides[li->sidenum[0]], &spawnsides[li->sidenum[0]]);
+			if (diff2)
+				diff |= LD_SIDE1;
 		}
 		if (li->sidenum[1] != NO_SIDEDEF)
 		{
-			si = &sides[li->sidenum[1]];
-			spawnsi = &spawnsides[li->sidenum[1]];
-			if (si->textureoffset != spawnsi->textureoffset)
-				diff2 |= LD_S2TEXOFF;
-			if (si->toptexture != spawnsi->toptexture)
-				diff2 |= LD_S2TOPTEX;
-			if (si->bottomtexture != spawnsi->bottomtexture)
-				diff2 |= LD_S2BOTTEX;
-			if (si->midtexture != spawnsi->midtexture)
-				diff2 |= LD_S2MIDTEX;
+			diff3 = GetSideDiff(&sides[li->sidenum[1]], &spawnsides[li->sidenum[1]]);
+			if (diff3)
+				diff |= LD_SIDE2;
 		}
 
-		if (diff2)
-			diff |= LD_DIFF2;
-
 		if (diff)
 		{
 			WRITEUINT32(save_p, i);
 			WRITEUINT8(save_p, diff);
-			if (diff & LD_DIFF2)
-				WRITEUINT8(save_p, diff2);
 			if (diff & LD_FLAG)
 				WRITEINT16(save_p, li->flags);
 			if (diff & LD_SPECIAL)
 				WRITEINT16(save_p, li->special);
 			if (diff & LD_CLLCOUNT)
 				WRITEINT16(save_p, li->callcount);
-
-			si = &sides[li->sidenum[0]];
-			if (diff & LD_S1TEXOFF)
-				WRITEFIXED(save_p, si->textureoffset);
-			if (diff & LD_S1TOPTEX)
-				WRITEINT32(save_p, si->toptexture);
-			if (diff & LD_S1BOTTEX)
-				WRITEINT32(save_p, si->bottomtexture);
-			if (diff & LD_S1MIDTEX)
-				WRITEINT32(save_p, si->midtexture);
-
-			si = &sides[li->sidenum[1]];
-			if (diff2 & LD_S2TEXOFF)
-				WRITEFIXED(save_p, si->textureoffset);
-			if (diff2 & LD_S2TOPTEX)
-				WRITEINT32(save_p, si->toptexture);
-			if (diff2 & LD_S2BOTTEX)
-				WRITEINT32(save_p, si->bottomtexture);
-			if (diff2 & LD_S2MIDTEX)
-				WRITEINT32(save_p, si->midtexture);
-			if (diff2 & LD_ARGS)
+			if (diff & LD_ARGS)
 			{
 				UINT8 j;
 				for (j = 0; j < NUMLINEARGS; j++)
 					WRITEINT32(save_p, li->args[j]);
 			}
-			if (diff2 & LD_STRINGARGS)
+			if (diff & LD_STRINGARGS)
 			{
 				UINT8 j;
 				for (j = 0; j < NUMLINESTRINGARGS; j++)
@@ -1388,19 +1480,64 @@ static void ArchiveLines(void)
 						WRITECHAR(save_p, li->stringargs[j][k]);
 				}
 			}
-			if (diff2 & LD_EXECUTORDELAY)
+			if (diff & LD_EXECUTORDELAY)
 				WRITEINT32(save_p, li->executordelay);
+			if (diff & LD_SIDE1)
+				ArchiveSide(&sides[li->sidenum[0]], diff2);
+			if (diff & LD_SIDE2)
+				ArchiveSide(&sides[li->sidenum[1]], diff3);
 		}
 	}
 	WRITEUINT32(save_p, 0xffffffff);
 }
 
+static void UnArchiveSide(side_t *si)
+{
+	UINT32 diff = READUINT32(save_p);
+
+	if (diff & LD_SDTEXOFFX)
+		si->textureoffset = READFIXED(save_p);
+	if (diff & LD_SDTEXOFFY)
+		si->rowoffset = READFIXED(save_p);
+	if (diff & LD_SDTOPTEX)
+		si->toptexture = READINT32(save_p);
+	if (diff & LD_SDBOTTEX)
+		si->bottomtexture = READINT32(save_p);
+	if (diff & LD_SDMIDTEX)
+		si->midtexture = READINT32(save_p);
+	if (diff & LD_SDTOPOFFX)
+		si->offsetx_top = READFIXED(save_p);
+	if (diff & LD_SDMIDOFFX)
+		si->offsetx_mid = READFIXED(save_p);
+	if (diff & LD_SDBOTOFFX)
+		si->offsetx_bottom = READFIXED(save_p);
+	if (diff & LD_SDTOPOFFY)
+		si->offsety_top = READFIXED(save_p);
+	if (diff & LD_SDMIDOFFY)
+		si->offsety_mid = READFIXED(save_p);
+	if (diff & LD_SDBOTOFFY)
+		si->offsety_bottom = READFIXED(save_p);
+	if (diff & LD_SDTOPSCALEX)
+		si->scalex_top = READFIXED(save_p);
+	if (diff & LD_SDMIDSCALEX)
+		si->scalex_mid = READFIXED(save_p);
+	if (diff & LD_SDBOTSCALEX)
+		si->scalex_bottom = READFIXED(save_p);
+	if (diff & LD_SDTOPSCALEY)
+		si->scaley_top = READFIXED(save_p);
+	if (diff & LD_SDMIDSCALEY)
+		si->scaley_mid = READFIXED(save_p);
+	if (diff & LD_SDBOTSCALEY)
+		si->scaley_bottom = READFIXED(save_p);
+	if (diff & LD_SDREPEATCNT)
+		si->repeatcnt = READINT16(save_p);
+}
+
 static void UnArchiveLines(void)
 {
 	UINT32 i;
 	line_t *li;
-	side_t *si;
-	UINT8 diff, diff2; // no diff3
+	UINT8 diff;
 
 	for (;;)
 	{
@@ -1414,44 +1551,19 @@ static void UnArchiveLines(void)
 		diff = READUINT8(save_p);
 		li = &lines[i];
 
-		if (diff & LD_DIFF2)
-			diff2 = READUINT8(save_p);
-		else
-			diff2 = 0;
-
 		if (diff & LD_FLAG)
 			li->flags = READINT16(save_p);
 		if (diff & LD_SPECIAL)
 			li->special = READINT16(save_p);
 		if (diff & LD_CLLCOUNT)
 			li->callcount = READINT16(save_p);
-
-		si = &sides[li->sidenum[0]];
-		if (diff & LD_S1TEXOFF)
-			si->textureoffset = READFIXED(save_p);
-		if (diff & LD_S1TOPTEX)
-			si->toptexture = READINT32(save_p);
-		if (diff & LD_S1BOTTEX)
-			si->bottomtexture = READINT32(save_p);
-		if (diff & LD_S1MIDTEX)
-			si->midtexture = READINT32(save_p);
-
-		si = &sides[li->sidenum[1]];
-		if (diff2 & LD_S2TEXOFF)
-			si->textureoffset = READFIXED(save_p);
-		if (diff2 & LD_S2TOPTEX)
-			si->toptexture = READINT32(save_p);
-		if (diff2 & LD_S2BOTTEX)
-			si->bottomtexture = READINT32(save_p);
-		if (diff2 & LD_S2MIDTEX)
-			si->midtexture = READINT32(save_p);
-		if (diff2 & LD_ARGS)
+		if (diff & LD_ARGS)
 		{
 			UINT8 j;
 			for (j = 0; j < NUMLINEARGS; j++)
 				li->args[j] = READINT32(save_p);
 		}
-		if (diff2 & LD_STRINGARGS)
+		if (diff & LD_STRINGARGS)
 		{
 			UINT8 j;
 			for (j = 0; j < NUMLINESTRINGARGS; j++)
@@ -1472,9 +1584,12 @@ static void UnArchiveLines(void)
 				li->stringargs[j][len] = '\0';
 			}
 		}
-		if (diff2 & LD_EXECUTORDELAY)
+		if (diff & LD_EXECUTORDELAY)
 			li->executordelay = READINT32(save_p);
-
+		if (diff & LD_SIDE1)
+			UnArchiveSide(&sides[li->sidenum[0]]);
+		if (diff & LD_SIDE2)
+			UnArchiveSide(&sides[li->sidenum[1]]);
 	}
 }
 
diff --git a/src/p_setup.c b/src/p_setup.c
index 3071e6ee9db67f154b4a0398b28a902478263ffc..d2126b0fcf99bc579580fed57747db3b504c6038 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -1055,6 +1055,9 @@ static void P_LoadSectors(UINT8 *data)
 		ss->floorxoffset = ss->flooryoffset = 0;
 		ss->ceilingxoffset = ss->ceilingyoffset = 0;
 
+		ss->floorxscale = ss->flooryscale = FRACUNIT;
+		ss->ceilingxscale = ss->ceilingyscale = FRACUNIT;
+
 		ss->floorangle = ss->ceilingangle = 0;
 
 		ss->floorlightlevel = ss->ceilinglightlevel = 0;
@@ -1352,8 +1355,11 @@ static void P_LoadSidedefs(UINT8 *data)
 		}
 		sd->rowoffset = SHORT(msd->rowoffset)<<FRACBITS;
 
-		sd->offsetx_top = sd->offsetx_mid = sd->offsetx_bot = 0;
-		sd->offsety_top = sd->offsety_mid = sd->offsety_bot = 0;
+		sd->offsetx_top = sd->offsetx_mid = sd->offsetx_bottom = 0;
+		sd->offsety_top = sd->offsety_mid = sd->offsety_bottom = 0;
+
+		sd->scalex_top = sd->scalex_mid = sd->scalex_bottom = FRACUNIT;
+		sd->scaley_top = sd->scaley_mid = sd->scaley_bottom = FRACUNIT;
 
 		P_SetSidedefSector(i, (UINT16)SHORT(msd->sector));
 
@@ -1693,6 +1699,14 @@ static void ParseTextmapSectorParameter(UINT32 i, const char *param, const char
 		sectors[i].ceilingxoffset = FLOAT_TO_FIXED(atof(val));
 	else if (fastcmp(param, "ypanningceiling"))
 		sectors[i].ceilingyoffset = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "xscalefloor"))
+		sectors[i].floorxscale = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "yscalefloor"))
+		sectors[i].flooryscale = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "xscaleceiling"))
+		sectors[i].ceilingxscale = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "yscaleceiling"))
+		sectors[i].ceilingyscale = FLOAT_TO_FIXED(atof(val));
 	else if (fastcmp(param, "rotationfloor"))
 		sectors[i].floorangle = FixedAngle(FLOAT_TO_FIXED(atof(val)));
 	else if (fastcmp(param, "rotationceiling"))
@@ -1888,13 +1902,25 @@ static void ParseTextmapSidedefParameter(UINT32 i, const char *param, const char
 	else if (fastcmp(param, "offsetx_mid"))
 		sides[i].offsetx_mid = atol(val) << FRACBITS;
 	else if (fastcmp(param, "offsetx_bottom"))
-		sides[i].offsetx_bot = atol(val) << FRACBITS;
+		sides[i].offsetx_bottom = atol(val) << FRACBITS;
 	else if (fastcmp(param, "offsety_top"))
 		sides[i].offsety_top = atol(val) << FRACBITS;
 	else if (fastcmp(param, "offsety_mid"))
 		sides[i].offsety_mid = atol(val) << FRACBITS;
 	else if (fastcmp(param, "offsety_bottom"))
-		sides[i].offsety_bot = atol(val) << FRACBITS;
+		sides[i].offsety_bottom = atol(val) << FRACBITS;
+	else if (fastcmp(param, "scalex_top"))
+		sides[i].scalex_top = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "scalex_mid"))
+		sides[i].scalex_mid = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "scalex_bottom"))
+		sides[i].scalex_bottom = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "scaley_top"))
+		sides[i].scaley_top = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "scaley_mid"))
+		sides[i].scaley_mid = FLOAT_TO_FIXED(atof(val));
+	else if (fastcmp(param, "scaley_bottom"))
+		sides[i].scaley_bottom = FLOAT_TO_FIXED(atof(val));
 	else if (fastcmp(param, "texturetop"))
 		sides[i].toptexture = R_TextureNumForName(val);
 	else if (fastcmp(param, "texturebottom"))
@@ -2599,10 +2625,22 @@ static void P_WriteTextmap(void)
 			fprintf(f, "offsetx_mid = %d;\n", wsides[i].offsetx_mid >> FRACBITS);
 		if (wsides[i].offsety_mid != 0)
 			fprintf(f, "offsety_mid = %d;\n", wsides[i].offsety_mid >> FRACBITS);
-		if (wsides[i].offsetx_bot != 0)
-			fprintf(f, "offsetx_bottom = %d;\n", wsides[i].offsetx_bot >> FRACBITS);
-		if (wsides[i].offsety_bot != 0)
-			fprintf(f, "offsety_bottom = %d;\n", wsides[i].offsety_bot >> FRACBITS);
+		if (wsides[i].offsetx_bottom != 0)
+			fprintf(f, "offsetx_bottom = %d;\n", wsides[i].offsetx_bottom >> FRACBITS);
+		if (wsides[i].offsety_bottom != 0)
+			fprintf(f, "offsety_bottom = %d;\n", wsides[i].offsety_bottom >> FRACBITS);
+		if (wsides[i].scalex_top != FRACUNIT)
+			fprintf(f, "scalex_top = %f;\n", FIXED_TO_FLOAT(wsides[i].scalex_top));
+		if (wsides[i].scaley_top != FRACUNIT)
+			fprintf(f, "scaley_top = %f;\n", FIXED_TO_FLOAT(wsides[i].scaley_top));
+		if (wsides[i].scalex_mid != FRACUNIT)
+			fprintf(f, "scalex_mid = %f;\n", FIXED_TO_FLOAT(wsides[i].scalex_mid));
+		if (wsides[i].scaley_mid != FRACUNIT)
+			fprintf(f, "scaley_mid = %f;\n", FIXED_TO_FLOAT(wsides[i].scaley_mid));
+		if (wsides[i].scalex_bottom != FRACUNIT)
+			fprintf(f, "scalex_bottom = %f;\n", FIXED_TO_FLOAT(wsides[i].scalex_bottom));
+		if (wsides[i].scaley_bottom != FRACUNIT)
+			fprintf(f, "scaley_bottom = %f;\n", FIXED_TO_FLOAT(wsides[i].scaley_bottom));
 		if (wsides[i].toptexture > 0 && wsides[i].toptexture < numtextures)
 			fprintf(f, "texturetop = \"%.*s\";\n", 8, textures[wsides[i].toptexture]->name);
 		if (wsides[i].bottomtexture > 0 && wsides[i].bottomtexture < numtextures)
@@ -2658,6 +2696,14 @@ static void P_WriteTextmap(void)
 			fprintf(f, "xpanningceiling = %f;\n", FIXED_TO_FLOAT(tempsec.ceilingxoffset));
 		if (tempsec.ceilingyoffset != 0)
 			fprintf(f, "ypanningceiling = %f;\n", FIXED_TO_FLOAT(tempsec.ceilingyoffset));
+		if (tempsec.floorxscale != 0)
+			fprintf(f, "xscalefloor = %f;\n", FIXED_TO_FLOAT(tempsec.floorxscale));
+		if (tempsec.flooryscale != 0)
+			fprintf(f, "yscalefloor = %f;\n", FIXED_TO_FLOAT(tempsec.flooryscale));
+		if (tempsec.ceilingxscale != 0)
+			fprintf(f, "xscaleceiling = %f;\n", FIXED_TO_FLOAT(tempsec.ceilingxscale));
+		if (tempsec.ceilingyscale != 0)
+			fprintf(f, "yscaleceiling = %f;\n", FIXED_TO_FLOAT(tempsec.ceilingyscale));
 		if (wsectors[i].floorangle != 0)
 			fprintf(f, "rotationfloor = %f;\n", FIXED_TO_FLOAT(AngleFixed(wsectors[i].floorangle)));
 		if (wsectors[i].ceilingangle != 0)
@@ -2893,6 +2939,9 @@ static void P_LoadTextmap(void)
 		sc->floorxoffset = sc->flooryoffset = 0;
 		sc->ceilingxoffset = sc->ceilingyoffset = 0;
 
+		sc->floorxscale = sc->flooryscale = FRACUNIT;
+		sc->ceilingxscale = sc->ceilingyscale = FRACUNIT;
+
 		sc->floorangle = sc->ceilingangle = 0;
 
 		sc->floorlightlevel = sc->ceilinglightlevel = 0;
@@ -2927,22 +2976,26 @@ static void P_LoadTextmap(void)
 		P_InitializeSector(sc);
 		if (textmap_colormap.used)
 		{
-			INT32 rgba = P_ColorToRGBA(textmap_colormap.lightcolor, textmap_colormap.lightalpha);
-			INT32 fadergba = P_ColorToRGBA(textmap_colormap.fadecolor, textmap_colormap.fadealpha);
+			// Convert alpha values from old 0-25 (A-Z) range to 0-255 range
+			UINT8 lightalpha = (textmap_colormap.lightalpha * 102) / 10;
+			UINT8 fadealpha = (textmap_colormap.fadealpha * 102) / 10;
+			
+			INT32 rgba = P_ColorToRGBA(textmap_colormap.lightcolor, lightalpha);
+			INT32 fadergba = P_ColorToRGBA(textmap_colormap.fadecolor, fadealpha);
 			sc->extra_colormap = sc->spawn_extra_colormap = R_CreateColormap(rgba, fadergba, textmap_colormap.fadestart, textmap_colormap.fadeend, textmap_colormap.flags);
 		}
 
 		if (textmap_planefloor.defined == (PD_A|PD_B|PD_C|PD_D))
-        {
+		{
 			sc->f_slope = MakeViaEquationConstants(textmap_planefloor.a, textmap_planefloor.b, textmap_planefloor.c, textmap_planefloor.d);
 			sc->hasslope = true;
-        }
+		}
 
 		if (textmap_planeceiling.defined == (PD_A|PD_B|PD_C|PD_D))
-        {
+		{
 			sc->c_slope = MakeViaEquationConstants(textmap_planeceiling.a, textmap_planeceiling.b, textmap_planeceiling.c, textmap_planeceiling.d);
 			sc->hasslope = true;
-        }
+		}
 
 		TextmapFixFlatOffsets(sc);
 	}
@@ -2979,8 +3032,10 @@ static void P_LoadTextmap(void)
 		// Defaults.
 		sd->textureoffset = 0;
 		sd->rowoffset = 0;
-		sd->offsetx_top = sd->offsetx_mid = sd->offsetx_bot = 0;
-		sd->offsety_top = sd->offsety_mid = sd->offsety_bot = 0;
+		sd->offsetx_top = sd->offsetx_mid = sd->offsetx_bottom = 0;
+		sd->offsety_top = sd->offsety_mid = sd->offsety_bottom = 0;
+		sd->scalex_top = sd->scalex_mid = sd->scalex_bottom = FRACUNIT;
+		sd->scaley_top = sd->scaley_mid = sd->scaley_bottom = FRACUNIT;
 		sd->toptexture = R_TextureNumForName("-");
 		sd->midtexture = R_TextureNumForName("-");
 		sd->bottomtexture = R_TextureNumForName("-");
@@ -3306,7 +3361,7 @@ static void P_LoadSegs(UINT8 *data)
 
 		seg->length = P_SegLength(seg);
 #ifdef HWRENDER
-		seg->flength = (rendermode == render_opengl) ? P_SegLengthFloat(seg) : 0;
+		seg->flength = P_SegLengthFloat(seg);
 #endif
 
 		seg->glseg = false;
@@ -3548,7 +3603,7 @@ static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype
 		}
 		seg->length = P_SegLength(seg);
 #ifdef HWRENDER
-		seg->flength = (rendermode == render_opengl) ? P_SegLengthFloat(seg) : 0;
+		seg->flength = P_SegLengthFloat(seg);
 #endif
 	}
 
@@ -7821,6 +7876,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
 	Patch_FreeTag(PU_PATCH_LOWPRIORITY);
 	Patch_FreeTag(PU_PATCH_ROTATED);
 	Z_FreeTags(PU_LEVEL, PU_PURGELEVEL - 1);
+	mobjcache = NULL;
 
 	R_InitializeLevelInterpolators();
 
diff --git a/src/p_spec.c b/src/p_spec.c
index 5bc886c8df38de59e93390e90a93846f55b1f730..3dbbc5352784bcbdf56800a5f450af35fc089d50 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -2735,7 +2735,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				mo->player->rmomx = mo->player->rmomy = 1;
 				mo->player->cmomx = mo->player->cmomy = 0;
 				P_ResetPlayer(mo->player);
-				P_SetPlayerMobjState(mo, S_PLAY_STND);
+				P_SetMobjState(mo, S_PLAY_STND);
 
 				// Reset bot too.
 				if (bot) {
@@ -2746,7 +2746,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					bot->player->rmomx = bot->player->rmomy = 1;
 					bot->player->cmomx = bot->player->cmomy = 0;
 					P_ResetPlayer(bot->player);
-					P_SetPlayerMobjState(bot, S_PLAY_STND);
+					P_SetMobjState(bot, S_PLAY_STND);
 				}
 			}
 			break;
@@ -4566,7 +4566,7 @@ static void P_ProcessSpeedPad(player_t *player, sector_t *sector, sector_t *rove
 		if (!(player->pflags & PF_SPINNING))
 			player->pflags |= PF_SPINNING;
 
-		P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+		P_SetMobjState(player->mo, S_PLAY_ROLL);
 	}
 
 	player->powers[pw_flashing] = TICRATE/3;
@@ -4744,7 +4744,7 @@ static void P_ProcessZoomTube(player_t *player, mtag_t sectag, boolean end)
 
 	if (player->mo->state-states != S_PLAY_ROLL)
 	{
-		P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+		P_SetMobjState(player->mo, S_PLAY_ROLL);
 		S_StartSound(player->mo, sfx_spin);
 	}
 }
@@ -4958,7 +4958,7 @@ static void P_ProcessRopeHang(player_t *player, mtag_t sectag)
 	player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_GLIDING|PF_BOUNCING|PF_SLIDING|PF_CANCARRY);
 	player->climbing = 0;
 	P_SetThingPosition(player->mo);
-	P_SetPlayerMobjState(player->mo, S_PLAY_RIDE);
+	P_SetMobjState(player->mo, S_PLAY_RIDE);
 }
 
 static boolean P_SectorHasSpecial(sector_t *sec)
@@ -5017,7 +5017,7 @@ static void P_EvaluateSpecialFlags(player_t *player, sector_t *sector, sector_t
 		if (!player->powers[pw_carry])
 		{
 			P_ResetPlayer(player);
-			P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+			P_SetMobjState(player->mo, S_PLAY_FALL);
 			P_SetTarget(&player->mo->tracer, player->mo);
 			player->powers[pw_carry] = CR_FAN;
 		}
@@ -5032,7 +5032,7 @@ static void P_EvaluateSpecialFlags(player_t *player, sector_t *sector, sector_t
 		if (!(player->pflags & PF_SPINNING))
 		{
 			player->pflags |= PF_SPINNING;
-			P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+			P_SetMobjState(player->mo, S_PLAY_ROLL);
 			S_StartAttackSound(player->mo, sfx_spin);
 
 			if (abs(player->rmomx) < FixedMul(5*FRACUNIT, player->mo->scale)
@@ -5605,6 +5605,8 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, I
 	fflr->bottompic = &sec2->floorpic;
 	fflr->bottomxoffs = &sec2->floorxoffset;
 	fflr->bottomyoffs = &sec2->flooryoffset;
+	fflr->bottomxscale = &sec2->floorxscale;
+	fflr->bottomyscale = &sec2->flooryscale;
 	fflr->bottomangle = &sec2->floorangle;
 
 	// Add the ceiling
@@ -5613,6 +5615,8 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, I
 	fflr->toplightlevel = &sec2->lightlevel;
 	fflr->topxoffs = &sec2->ceilingxoffset;
 	fflr->topyoffs = &sec2->ceilingyoffset;
+	fflr->topxscale = &sec2->ceilingxscale;
+	fflr->topyscale = &sec2->ceilingyscale;
 	fflr->topangle = &sec2->ceilingangle;
 
 	// Add slopes
diff --git a/src/p_telept.c b/src/p_telept.c
index 66b05ff01c6b6ba2c4efbee60b15acecdcfd412b..32669edaf7a0014991c7a6bff84f2a8df78d2933 100644
--- a/src/p_telept.c
+++ b/src/p_telept.c
@@ -96,7 +96,7 @@ void P_MixUp(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle,
 		P_ClearStarPost(starpostnum);
 
 		P_ResetPlayer(thing->player);
-		P_SetPlayerMobjState(thing, S_PLAY_STND);
+		P_SetMobjState(thing, S_PLAY_STND);
 
 		P_FlashPal(thing->player, PAL_MIXUP, 10);
 	}
@@ -153,7 +153,7 @@ boolean P_Teleport(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle
 			thing->player->rmomx = thing->player->rmomy = 0;
 			thing->player->speed = 0;
 			P_ResetPlayer(thing->player);
-			P_SetPlayerMobjState(thing, S_PLAY_STND);
+			P_SetMobjState(thing, S_PLAY_STND);
 
 			thing->reactiontime = TICRATE/2; // don't move for about half a second
 			thing->player->drawangle = angle;
diff --git a/src/p_tick.c b/src/p_tick.c
index 1bc7b78bf1a39499576794b8e80d7181f58cd43c..143fa7e36844ca01d8a6bd2b6f41aed3bb04df1e 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -217,6 +217,7 @@ void P_AddThinker(const thinklistnum_t n, thinker_t *thinker)
 	thlist[n].prev = thinker;
 
 	thinker->references = 0;    // killough 11/98: init reference counter to 0
+	thinker->cachable = n == THINK_MOBJ;
 
 #ifdef PARANOIA
 	thinker->debug_mobjtype = MT_NULL;
@@ -226,21 +227,22 @@ void P_AddThinker(const thinklistnum_t n, thinker_t *thinker)
 #ifdef PARANOIA
 static const char *MobjTypeName(const mobj_t *mobj)
 {
+	mobjtype_t type;
 	actionf_p1 p1 = mobj->thinker.function.acp1;
 
 	if (p1 == (actionf_p1)P_MobjThinker)
-	{
-		return MOBJTYPE_LIST[mobj->type];
-	}
-	else if (p1 == (actionf_p1)P_RemoveThinkerDelayed)
-	{
-		if (mobj->thinker.debug_mobjtype != MT_NULL)
-		{
-			return MOBJTYPE_LIST[mobj->thinker.debug_mobjtype];
-		}
-	}
+		type = mobj->type;
+	else if (p1 == (actionf_p1)P_RemoveThinkerDelayed && mobj->thinker.debug_mobjtype != MT_NULL)
+		type = mobj->thinker.debug_mobjtype;
+	else
+		return "<Not a mobj>";
 
-	return "<Not a mobj>";
+	if (type < 0 || type >= NUMMOBJTYPES || (type >= MT_FIRSTFREESLOT && !FREE_MOBJS[type - MT_FIRSTFREESLOT]))
+		return "<Invalid mobj type>";
+	else if (type >= MT_FIRSTFREESLOT)
+		return FREE_MOBJS[type - MT_FIRSTFREESLOT]; // This doesn't include "MT_"...
+	else
+		return MOBJTYPE_LIST[type];
 }
 
 static const char *MobjThinkerName(const mobj_t *mobj)
@@ -319,7 +321,16 @@ void P_RemoveThinkerDelayed(thinker_t *thinker)
 	(next->prev = currentthinker = thinker->prev)->next = next;
 
 	R_DestroyLevelInterpolators(thinker);
-	Z_Free(thinker);
+	if (thinker->cachable)
+	{
+		// put cachable thinkers in the mobj cache, so we can avoid allocations
+		((mobj_t *)thinker)->hnext = mobjcache;
+		mobjcache = (mobj_t *)thinker;
+	}
+	else
+	{
+		Z_Free(thinker);
+	}
 }
 
 //
@@ -758,7 +769,9 @@ void P_Ticker(boolean run)
 		ps_lua_mobjhooks.value.i = 0;
 		ps_checkposition_calls.value.i = 0;
 
-		LUA_HOOK(PreThinkFrame);
+		PS_START_TIMING(ps_lua_prethinkframe_time);
+		LUA_HookPreThinkFrame();
+		PS_STOP_TIMING(ps_lua_prethinkframe_time);
 
 		PS_START_TIMING(ps_playerthink_time);
 		for (i = 0; i < MAXPLAYERS; i++)
@@ -856,7 +869,9 @@ void P_Ticker(boolean run)
 		if (modeattacking)
 			G_GhostTicker();
 
-		LUA_HOOK(PostThinkFrame);
+		PS_START_TIMING(ps_lua_postthinkframe_time);
+		LUA_HookPostThinkFrame();
+		PS_STOP_TIMING(ps_lua_postthinkframe_time);
 	}
 
 	if (run)
diff --git a/src/p_user.c b/src/p_user.c
index 3335c3126624214df805bf975d40e11001139463..6517771d8a0a313247665cc34c206b4e0175c3b1 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -393,6 +393,8 @@ void P_GiveFinishFlags(player_t *player)
 		fixed_t xoffs = FINECOSINE(fa);
 		fixed_t yoffs = FINESINE(fa);
 		mobj_t* flag = P_SpawnMobjFromMobj(player->mo, xoffs, yoffs, 0, MT_FINISHFLAG);
+		if (P_MobjWasRemoved(flag))
+			continue;
 		flag->angle = angle;
 		angle += FixedAngle(120*FRACUNIT);
 
@@ -694,7 +696,7 @@ static void P_DeNightserizePlayer(player_t *player)
 	else if (player == &players[secondarydisplayplayer])
 		localaiming2 = 0;
 
-	P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+	P_SetMobjState(player->mo, S_PLAY_FALL);
 
 	// If in a special stage, add some preliminary exit time.
 	if (G_IsSpecialStage(gamemap))
@@ -948,7 +950,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 	P_RunNightserizeExecutors(player->mo);
 
 	player->powers[pw_carry] = CR_NIGHTSMODE;
-	P_SetPlayerMobjState(player->mo, S_PLAY_NIGHTS_TRANS1);
+	P_SetMobjState(player->mo, S_PLAY_NIGHTS_TRANS1);
 }
 
 pflags_t P_GetJumpFlags(player_t *player)
@@ -994,7 +996,7 @@ void P_DoPlayerPain(player_t *player, mobj_t *source, mobj_t *inflictor)
 		fixed_t fallbackspeed;
 
 		P_ResetPlayer(player);
-		P_SetPlayerMobjState(player->mo, player->mo->info->painstate);
+		P_SetMobjState(player->mo, player->mo->info->painstate);
 
 		if (player->mo->eflags & MFE_VERTICALFLIP)
 			player->mo->z--;
@@ -1345,7 +1347,7 @@ void P_DoSuperTransformation(player_t *player, boolean giverings)
 	player->mo->momx = player->mo->momy = player->mo->momz = player->cmomx = player->cmomy = player->rmomx = player->rmomy = 0;
 
 	// Transformation animation
-	P_SetPlayerMobjState(player->mo, S_PLAY_SUPER_TRANS1);
+	P_SetMobjState(player->mo, S_PLAY_SUPER_TRANS1);
 
 	if (giverings && player->rings < 50)
 		player->rings = 50;
@@ -1398,7 +1400,7 @@ static void P_DoSuperDetransformation(player_t *player)
 		player->powers[pw_flashing] = flashingtics-1;
 
 	if (player->mo->sprite2 & FF_SPR2SUPER)
-		P_SetPlayerMobjState(player->mo, player->mo->state-states);
+		P_SetMobjState(player->mo, player->mo->state-states);
 
 	// Inform the netgame that the champion has fallen in the heat of battle.
 	if (!G_CoopGametype())
@@ -1889,6 +1891,9 @@ void P_SpawnShieldOrb(player_t *player)
 	}
 
 	shieldobj = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, orbtype);
+	if (P_MobjWasRemoved(shieldobj))
+		return;
+
 	shieldobj->flags2 |= MF2_SHIELD;
 	P_SetTarget(&shieldobj->target, player->mo);
 	P_SetTarget(&shieldobj->dontdrawforviewmobj, player->mo); // Hide the shield in first-person
@@ -1904,24 +1909,33 @@ void P_SpawnShieldOrb(player_t *player)
 	if (shieldobj->info->seestate)
 	{
 		ov = P_SpawnMobj(shieldobj->x, shieldobj->y, shieldobj->z, MT_OVERLAY);
-		P_SetTarget(&ov->target, shieldobj);
+		if (!P_MobjWasRemoved(ov))
+		{
+			P_SetTarget(&ov->target, shieldobj);
 		P_SetTarget(&ov->dontdrawforviewmobj, player->mo); // Hide the shield in first-person
-		P_SetMobjState(ov, shieldobj->info->seestate);
-		P_SetTarget(&shieldobj->tracer, ov);
+			P_SetMobjState(ov, shieldobj->info->seestate);
+			P_SetTarget(&shieldobj->tracer, ov);
+		}
 	}
 	if (shieldobj->info->meleestate)
 	{
 		ov = P_SpawnMobj(shieldobj->x, shieldobj->y, shieldobj->z, MT_OVERLAY);
-		P_SetTarget(&ov->target, shieldobj);
+		if (!P_MobjWasRemoved(ov))
+		{
+			P_SetTarget(&ov->target, shieldobj);
 		P_SetTarget(&ov->dontdrawforviewmobj, player->mo); // Hide the shield in first-person
-		P_SetMobjState(ov, shieldobj->info->meleestate);
+			P_SetMobjState(ov, shieldobj->info->meleestate);
+		}
 	}
 	if (shieldobj->info->missilestate)
 	{
 		ov = P_SpawnMobj(shieldobj->x, shieldobj->y, shieldobj->z, MT_OVERLAY);
-		P_SetTarget(&ov->target, shieldobj);
+		if (!P_MobjWasRemoved(ov))
+		{
+			P_SetTarget(&ov->target, shieldobj);
 		P_SetTarget(&ov->dontdrawforviewmobj, player->mo); // Hide the shield in first-person
-		P_SetMobjState(ov, shieldobj->info->missilestate);
+			P_SetMobjState(ov, shieldobj->info->missilestate);
+		}
 	}
 	if (player->powers[pw_shield] & SH_FORCE)
 	{
@@ -2017,6 +2031,8 @@ void P_SetPower(player_t *player, powertype_t power, UINT16 value)
 mobj_t *P_SpawnGhostMobj(mobj_t *mobj)
 {
 	mobj_t *ghost = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_GHOST);
+	if (P_MobjWasRemoved(ghost))
+		return NULL;
 
 	P_SetTarget(&ghost->target, mobj);
 	P_SetTarget(&ghost->dontdrawforviewmobj, mobj); // Hide the ghost in first-person
@@ -2063,10 +2079,13 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj)
 	if (mobj->player && mobj->player->followmobj)
 	{
 		mobj_t *ghost2 = P_SpawnGhostMobj(mobj->player->followmobj);
-		P_SetTarget(&ghost2->tracer, ghost);
-		P_SetTarget(&ghost->tracer, ghost2);
+		if (!P_MobjWasRemoved(ghost2))
+		{
+			P_SetTarget(&ghost2->tracer, ghost);
+			P_SetTarget(&ghost->tracer, ghost2);
 		P_SetTarget(&ghost2->dontdrawforviewmobj, mobj); // Hide the follow-ghost for the non-follow target
-		ghost2->flags2 |= (mobj->player->followmobj->flags2 & MF2_LINKDRAW);
+			ghost2->flags2 |= (mobj->player->followmobj->flags2 & MF2_LINKDRAW);
+		}
 	}
 
 	// Copy interpolation data :)
@@ -2103,7 +2122,19 @@ void P_SpawnThokMobj(player_t *player)
 
 	if (type == MT_GHOST)
 		mobj = P_SpawnGhostMobj(player->mo); // virtually does everything here for us
-	else
+	else if (type == MT_THOKEFFECT) // Thok boom effect for Sonic
+	{
+		mobj = P_SpawnMobjFromMobj(player->mo, 0, 0, FixedDiv(player->mo->height, player->mo->scale)*3/4, type);
+		mobj->angle = player->mo->angle + ANGLE_90;
+		mobj->fuse = 7;
+		mobj->scale = player->mo->scale / 3;
+		mobj->destscale = 10 * player->mo->scale;
+		mobj->colorized = true;
+		mobj->color = player->mo->color;
+		mobj->momx = -player->mo->momx / 2;
+		mobj->momy = -player->mo->momy / 2;
+	}
+	else // Normal thok object handling
 	{
 		if (player->mo->eflags & MFE_VERTICALFLIP)
 			zheight = player->mo->z + player->mo->height + FixedDiv(P_GetPlayerHeight(player) - player->mo->height, 3*FRACUNIT) - FixedMul(mobjinfo[type].height, player->mo->scale);
@@ -2116,6 +2147,8 @@ void P_SpawnThokMobj(player_t *player)
 			zheight = player->mo->ceilingz - FixedMul(mobjinfo[type].height, player->mo->scale);
 
 		mobj = P_SpawnMobj(player->mo->x, player->mo->y, zheight, type);
+		if (P_MobjWasRemoved(mobj))
+			return;
 
 		// set to player's angle, just in case
 		mobj->angle = player->drawangle;
@@ -2139,7 +2172,8 @@ void P_SpawnThokMobj(player_t *player)
 		}
 	}
 
-	P_SetTarget(&mobj->target, player->mo); // the one thing P_SpawnGhostMobj doesn't do
+	if (!P_MobjWasRemoved(mobj))
+		P_SetTarget(&mobj->target, player->mo); // the one thing P_SpawnGhostMobj doesn't do
 	G_GhostAddThok();
 }
 
@@ -2177,6 +2211,8 @@ void P_SpawnSpinMobj(player_t *player, mobjtype_t type)
 			zheight = player->mo->ceilingz - FixedMul(mobjinfo[type].height, player->mo->scale);
 
 		mobj = P_SpawnMobj(player->mo->x, player->mo->y, zheight, type);
+		if (P_MobjWasRemoved(mobj))
+			return;
 
 		// set to player's angle, just in case
 		mobj->angle = player->drawangle;
@@ -2201,7 +2237,8 @@ void P_SpawnSpinMobj(player_t *player, mobjtype_t type)
 		}
 	}
 
-	P_SetTarget(&mobj->target, player->mo); // the one thing P_SpawnGhostMobj doesn't do
+	if (!P_MobjWasRemoved(mobj))
+		P_SetTarget(&mobj->target, player->mo); // the one thing P_SpawnGhostMobj doesn't do
 }
 
 /** Called when \p player finishes the level.
@@ -2265,12 +2302,12 @@ void P_DoPlayerExit(player_t *player)
 	{
 		player->climbing = 0;
 		player->pflags |= P_GetJumpFlags(player);
-		P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
+		P_SetMobjState(player->mo, S_PLAY_JUMP);
 	}
 	else if (player->pflags & PF_STARTDASH)
 	{
 		player->pflags &= ~PF_STARTDASH;
-		P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+		P_SetMobjState(player->mo, S_PLAY_STND);
 	}
 	player->powers[pw_underwater] = 0;
 	player->powers[pw_spacetime] = 0;
@@ -2359,7 +2396,7 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 				if (!(player->pflags & PF_STARTDASH) && player->panim != PA_ROLL && player->panim != PA_ETC
 				&& player->panim != PA_ABILITY && player->panim != PA_ABILITY2)
 				{
-					P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+					P_SetMobjState(player->mo, S_PLAY_ROLL);
 					S_StartSound(player->mo, sfx_spin);
 				}
 			}
@@ -2368,7 +2405,7 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 				if (dorollstuff)
 				{
 					player->skidtime = TICRATE;
-					P_SetPlayerMobjState(player->mo, S_PLAY_GLIDE);
+					P_SetMobjState(player->mo, S_PLAY_GLIDE);
 					P_SpawnSkidDust(player, player->mo->radius, true); // make sure the player knows they landed
 					player->mo->tics = -1;
 				}
@@ -2381,7 +2418,7 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 				if (player->mo->state-states != S_PLAY_GLIDE_LANDING)
 				{
 					P_ResetPlayer(player);
-					P_SetPlayerMobjState(player->mo, S_PLAY_GLIDE_LANDING);
+					P_SetMobjState(player->mo, S_PLAY_GLIDE_LANDING);
 					player->pflags |= PF_STASIS;
 					if (player->speed > FixedMul(player->runspeed, player->mo->scale))
 						player->skidtime += player->mo->tics;
@@ -2402,7 +2439,7 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 				if (player->mo->state-states != S_PLAY_MELEE_LANDING)
 				{
 					mobjtype_t type = player->revitem;
-					P_SetPlayerMobjState(player->mo, S_PLAY_MELEE_LANDING);
+					P_SetMobjState(player->mo, S_PLAY_MELEE_LANDING);
 					player->mo->tics = (player->mo->movefactor == FRACUNIT) ? TICRATE/2 : (FixedDiv(35<<(FRACBITS-1), FixedSqrt(player->mo->movefactor)))>>FRACBITS;
 					S_StartSound(player->mo, sfx_s3k8b);
 					player->pflags |= PF_FULLSTASIS;
@@ -2426,15 +2463,18 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 						while (i < 5)
 						{
 							missile = P_SpawnMobjFromMobj(player->mo, xo, yo, zo, type);
-							P_SetTarget(&missile->target, player->mo);
-							missile->angle = throwang + player->drawangle;
-							P_Thrust(missile, player->drawangle + ANGLE_90,
-								P_ReturnThrustY(missile, throwang, mu)); // side to side component
-							P_Thrust(missile, player->drawangle, mu2); // forward component
-							P_SetObjectMomZ(missile, (4 + ((i&1)<<1))*FRACUNIT, true);
-							missile->momz += player->mo->pmomz;
-							missile->fuse = TICRATE/2;
-							missile->extravalue2 = ev;
+							if (!P_MobjWasRemoved(missile))
+							{
+								P_SetTarget(&missile->target, player->mo);
+								missile->angle = throwang + player->drawangle;
+								P_Thrust(missile, player->drawangle + ANGLE_90,
+									P_ReturnThrustY(missile, throwang, mu)); // side to side component
+								P_Thrust(missile, player->drawangle, mu2); // forward component
+								P_SetObjectMomZ(missile, (4 + ((i&1)<<1))*FRACUNIT, true);
+								missile->momz += player->mo->pmomz;
+								missile->fuse = TICRATE/2;
+								missile->extravalue2 = ev;
+							}
 
 							i++;
 							throwang += ANG30;
@@ -2465,28 +2505,28 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 				if (player->cmomx || player->cmomy)
 				{
 					if (player->charflags & SF_DASHMODE && player->dashmode >= DASHMODE_THRESHOLD && player->panim != PA_DASH)
-						P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
+						P_SetMobjState(player->mo, S_PLAY_DASH);
 					else if (player->speed >= runspd
 					&& (player->panim != PA_RUN || player->mo->state-states == S_PLAY_FLOAT_RUN))
-						P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
+						P_SetMobjState(player->mo, S_PLAY_RUN);
 					else if ((player->rmomx || player->rmomy)
 					&& (player->panim != PA_WALK || player->mo->state-states == S_PLAY_FLOAT))
-						P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+						P_SetMobjState(player->mo, S_PLAY_WALK);
 					else if (!player->rmomx && !player->rmomy && player->panim != PA_IDLE)
-						P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+						P_SetMobjState(player->mo, S_PLAY_STND);
 				}
 				else
 				{
 					if (player->charflags & SF_DASHMODE && player->dashmode >= DASHMODE_THRESHOLD && player->panim != PA_DASH)
-						P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
+						P_SetMobjState(player->mo, S_PLAY_DASH);
 					else if (player->speed >= runspd
 					&& (player->panim != PA_RUN || player->mo->state-states == S_PLAY_FLOAT_RUN))
-						P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
+						P_SetMobjState(player->mo, S_PLAY_RUN);
 					else if ((player->mo->momx || player->mo->momy)
 					&& (player->panim != PA_WALK || player->mo->state-states == S_PLAY_FLOAT))
-						P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+						P_SetMobjState(player->mo, S_PLAY_WALK);
 					else if (!player->mo->momx && !player->mo->momy && player->panim != PA_IDLE)
-						P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+						P_SetMobjState(player->mo, S_PLAY_STND);
 				}
 			}
 
@@ -2516,7 +2556,7 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 					? 6*FRACUNIT/5
 					: 5*FRACUNIT/2,
 					false);
-					P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+					P_SetMobjState(player->mo, S_PLAY_FALL);
 					player->mo->momx = player->mo->momy = 0;
 					clipmomz = false;
 				}
@@ -2989,24 +3029,29 @@ static void P_CheckUnderwaterAndSpaceTimer(player_t *player)
 		: player->mo->z + player->mo->height + FixedMul(8*FRACUNIT, FixedMul(player->mo->scale, player->shieldscale));
 
 		mobj_t *numbermobj = P_SpawnMobj(player->mo->x, player->mo->y, height, MT_DROWNNUMBERS);
-
-		timeleft /= (2*TICRATE); // To be strictly accurate it'd need to be ((timeleft/TICRATE) - 1)/2, but integer division rounds down for us
-
-		if (player->charflags & SF_MACHINE)
+		if (!P_MobjWasRemoved(numbermobj))
 		{
-			S_StartSound(player->mo, sfx_buzz1);
-			timeleft += 6;
-		}
-		else
-			S_StartSound(player->mo, sfx_dwnind);
+			timeleft /= (2*TICRATE); // To be strictly accurate it'd need to be ((timeleft/TICRATE) - 1)/2, but integer division rounds down for us
 
-		if (timeleft) // Don't waste time setting the state if the time is 0.
-			P_SetMobjState(numbermobj, numbermobj->info->spawnstate+timeleft);
+			if (player->charflags & SF_MACHINE)
+			{
+				S_StartSound(player->mo, sfx_buzz1);
+				timeleft += 6;
+			}
+			else
+				S_StartSound(player->mo, sfx_dwnind);
+
+			if (!P_MobjWasRemoved(numbermobj))
+			{
+				if (timeleft) // Don't waste time setting the state if the time is 0.
+					P_SetMobjState(numbermobj, numbermobj->info->spawnstate+timeleft);
 
-		P_SetTarget(&numbermobj->target, player->mo);
-		numbermobj->threshold = 40;
-		numbermobj->destscale = player->mo->scale;
-		P_SetScale(numbermobj, player->mo->scale);
+				P_SetTarget(&numbermobj->target, player->mo);
+				numbermobj->threshold = 40;
+				numbermobj->destscale = player->mo->scale;
+				P_SetScale(numbermobj, player->mo->scale);
+			}
+		}
 	}
 	// Underwater timer runs out
 	else if (timeleft == 1)
@@ -3065,8 +3110,11 @@ static void P_CheckInvincibilityTimer(player_t *player)
 	else if (leveltime % (TICRATE/7) == 0)
 	{
 		mobj_t *sparkle = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_IVSP);
-		sparkle->destscale = player->mo->scale;
-		P_SetScale(sparkle, player->mo->scale);
+		if (!P_MobjWasRemoved(sparkle))
+		{
+			sparkle->destscale = player->mo->scale;
+			P_SetScale(sparkle, player->mo->scale);
+		}
 	}
 
 	// Resume normal music stuff.
@@ -3121,7 +3169,8 @@ static void P_DoBubbleBreath(player_t *player)
 			y += (P_RandomRange(r, -r)<<FRACBITS);
 			z += (P_RandomKey(player->mo->height>>FRACBITS)<<FRACBITS);
 			bubble = P_SpawnMobj(x, y, z, MT_WATERZAP);
-			S_StartSound(bubble, sfx_beelec);
+			if (!P_MobjWasRemoved(bubble))
+				S_StartSound(bubble, sfx_beelec);
 		}
 	}
 	else
@@ -3162,15 +3211,21 @@ static void P_DoBubbleBreath(player_t *player)
 			player->mo->x + stirwaterx,
 			player->mo->y + stirwatery,
 			stirwaterz, MT_SMALLBUBBLE);
-		bubble->destscale = player->mo->scale;
-		P_SetScale(bubble,bubble->destscale);
+		if (!P_MobjWasRemoved(bubble))
+		{
+			bubble->destscale = player->mo->scale;
+			P_SetScale(bubble,bubble->destscale);
+		}
 
 		bubble = P_SpawnMobj(
 			player->mo->x - stirwaterx,
 			player->mo->y - stirwatery,
 			stirwaterz, MT_SMALLBUBBLE);
-		bubble->destscale = player->mo->scale;
-		P_SetScale(bubble,bubble->destscale);
+		if (!P_MobjWasRemoved(bubble))
+		{
+			bubble->destscale = player->mo->scale;
+			P_SetScale(bubble,bubble->destscale);
+		}
 	}
 }
 
@@ -3190,22 +3245,25 @@ static void P_DoPlayerHeadSigns(player_t *player)
 		if (player->pflags & PF_TAGIT && (!P_IsLocalPlayer(player) || consoleplayer != displayplayer || splitscreen))
 		{
 			sign = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_TAG);
-			sign->x = player->mo->x;
-			sign->y = player->mo->y;
-			sign->z = player->mo->z;
-			sign->old_x = player->mo->old_x;
-			sign->old_y = player->mo->old_y;
-			sign->old_z = player->mo->old_z;
-
-			if (!(player->mo->eflags & MFE_VERTICALFLIP))
-			{
-				sign->z += player->mo->height;
-				sign->old_z += player->mo->height;
-			}
-			else
+			if (!P_MobjWasRemoved(sign))
 			{
-				sign->z -= mobjinfo[MT_TAG].height;
-				sign->old_z -= mobjinfo[MT_TAG].height;
+				sign->x = player->mo->x;
+				sign->y = player->mo->y;
+				sign->z = player->mo->z;
+				sign->old_x = player->mo->old_x;
+				sign->old_y = player->mo->old_y;
+				sign->old_z = player->mo->old_z;
+
+				if (!(player->mo->eflags & MFE_VERTICALFLIP))
+				{
+					sign->z += player->mo->height;
+					sign->old_z += player->mo->height;
+				}
+				else
+				{
+					sign->z -= mobjinfo[MT_TAG].height;
+					sign->old_z -= mobjinfo[MT_TAG].height;
+				}
 			}
 		}
 	}
@@ -3229,24 +3287,26 @@ static void P_DoPlayerHeadSigns(player_t *player)
 			}
 
 			sign = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_GOTFLAG);
-			sign->x = player->mo->x;
-			sign->y = player->mo->y;
-			sign->z = player->mo->z + zofs;
-			sign->old_x = player->mo->old_x;
-			sign->old_y = player->mo->old_y;
-			sign->old_z = player->mo->old_z + zofs;
-
-			if (player_is_flipped)
+			if (!P_MobjWasRemoved(sign))
 			{
-				sign->eflags |= MFE_VERTICALFLIP;
-			}
+				sign->x = player->mo->x;
+				sign->y = player->mo->y;
+				sign->z = player->mo->z + zofs;
+				sign->old_x = player->mo->old_x;
+				sign->old_y = player->mo->old_y;
+				sign->old_z = player->mo->old_z + zofs;
 
-			if (player->gotflag & GF_REDFLAG)
-				sign->frame = 1|FF_FULLBRIGHT;
-			else //if (player->gotflag & GF_BLUEFLAG)
-				sign->frame = 2|FF_FULLBRIGHT;
+				if (player_is_flipped)
+				{
+					sign->eflags |= MFE_VERTICALFLIP;
+				}
+
+				if (player->gotflag & GF_REDFLAG)
+					sign->frame = 1|FF_FULLBRIGHT;
+				else //if (player->gotflag & GF_BLUEFLAG)
+					sign->frame = 2|FF_FULLBRIGHT;
+			}
 		}
-	}
 
 	if (!P_MobjWasRemoved(sign) && splitscreen) // Hide the sign from yourself in splitscreen - In single-screen, it wouldn't get spawned if it shouldn't be visible
 	{
@@ -3284,6 +3344,7 @@ static void P_DoPlayerHeadSigns(player_t *player)
 		}
 #endif
 	}
+	}
 }
 
 //
@@ -3648,9 +3709,9 @@ static void P_DoClimbing(player_t *player)
 
 		if (player->climbing && climb && (player->mo->momx || player->mo->momy || player->mo->momz)
 			&& player->mo->state-states != S_PLAY_CLIMB)
-			P_SetPlayerMobjState(player->mo, S_PLAY_CLIMB);
+			P_SetMobjState(player->mo, S_PLAY_CLIMB);
 		else if ((!(player->mo->momx || player->mo->momy || player->mo->momz) || !climb) && player->mo->state-states != S_PLAY_CLING)
-			P_SetPlayerMobjState(player->mo, S_PLAY_CLING);
+			P_SetMobjState(player->mo, S_PLAY_CLING);
 
 		if (!floorclimb)
 		{
@@ -3665,14 +3726,14 @@ static void P_DoClimbing(player_t *player)
 
 			player->climbing = 0;
 			player->pflags |= P_GetJumpFlags(player);
-			P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
+			P_SetMobjState(player->mo, S_PLAY_JUMP);
 		}
 
 		if (skyclimber)
 		{
 			player->climbing = 0;
 			player->pflags |= P_GetJumpFlags(player);
-			P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
+			P_SetMobjState(player->mo, S_PLAY_JUMP);
 		}
 	}
 
@@ -3683,15 +3744,15 @@ static void P_DoClimbing(player_t *player)
 
 	if (player->climbing && climb && (player->mo->momx || player->mo->momy || player->mo->momz)
 		&& player->mo->state-states != S_PLAY_CLIMB)
-		P_SetPlayerMobjState(player->mo, S_PLAY_CLIMB);
+		P_SetMobjState(player->mo, S_PLAY_CLIMB);
 	else if ((!(player->mo->momx || player->mo->momy || player->mo->momz) || !climb) && player->mo->state-states != S_PLAY_CLING)
-		P_SetPlayerMobjState(player->mo, S_PLAY_CLING);
+		P_SetMobjState(player->mo, S_PLAY_CLING);
 
 	if (cmd->buttons & BT_SPIN && !(player->pflags & PF_JUMPSTASIS))
 	{
 		player->climbing = 0;
 		player->pflags |= P_GetJumpFlags(player);
-		P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
+		P_SetMobjState(player->mo, S_PLAY_JUMP);
 		P_SetObjectMomZ(player->mo, 4*FRACUNIT, false);
 		P_Thrust(player->mo, player->mo->angle, FixedMul(-4*FRACUNIT, player->mo->scale));
 	}
@@ -3707,12 +3768,12 @@ static void P_DoClimbing(player_t *player)
 	}
 
 	if (player->climbing == 0)
-		P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
+		P_SetMobjState(player->mo, S_PLAY_JUMP);
 
 	if (player->climbing && P_IsObjectOnGround(player->mo))
 	{
 		P_ResetPlayer(player);
-		P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+		P_SetMobjState(player->mo, S_PLAY_STND);
 	}
 }
 
@@ -4074,10 +4135,10 @@ teeterdone:
 	if (teeter)
 	{
 		if (player->panim == PA_IDLE)
-			P_SetPlayerMobjState(player->mo, S_PLAY_EDGE);
+			P_SetMobjState(player->mo, S_PLAY_EDGE);
 	}
 	else if (player->panim == PA_EDGE)
-		P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+		P_SetMobjState(player->mo, S_PLAY_STND);
 }
 
 //
@@ -4371,8 +4432,11 @@ static void P_DoSuperStuff(player_t *player)
 		&& !(leveltime % TICRATE) && (player->mo->momx || player->mo->momy))
 		{
 			spark = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SUPERSPARK);
-			spark->destscale = player->mo->scale;
-			P_SetScale(spark, player->mo->scale);
+			if (!P_MobjWasRemoved(spark))
+			{
+				spark->destscale = player->mo->scale;
+				P_SetScale(spark, player->mo->scale);
+			}
 		}
 
 		// Ran out of rings while super!
@@ -4602,7 +4666,7 @@ void P_DoJump(player_t *player, boolean soundandstate)
 		if (!player->spectator)
 			S_StartSound(player->mo, sfx_jump); // Play jump sound!
 
-		P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
+		P_SetMobjState(player->mo, S_PLAY_JUMP);
 	}
 }
 
@@ -4613,6 +4677,8 @@ void P_DoSpinDashDust(player_t *player)
 	INT32 prandom[3];
 	for (i = 0; i <= (leveltime%7)/2; i++) { // 1, 2, 3 or 4 particles
 		particle = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_SPINDUST);
+		if (P_MobjWasRemoved(particle))
+			return;
 
 		if (player->mo->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER)) // overrides fire version
 			P_SetMobjState(particle, S_SPINDUST_BUBBLE1);
@@ -4672,7 +4738,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 					player->mo->momy = player->cmomy;
 					player->pflags |= (PF_SPINDOWN|PF_STARTDASH|PF_SPINNING);
 					player->dashspeed = player->mindash;
-					P_SetPlayerMobjState(player->mo, S_PLAY_SPINDASH);
+					P_SetMobjState(player->mo, S_PLAY_SPINDASH);
 					if (!player->spectator)
 						S_StartSound(player->mo, sfx_spndsh); // Make the rev sound!
 				}
@@ -4682,7 +4748,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 					if (player->speed > 5*player->mo->scale)
 					{
 						player->pflags &= ~PF_STARTDASH;
-						P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+						P_SetMobjState(player->mo, S_PLAY_ROLL);
 						S_StartSound(player->mo, sfx_spin);
 						break;
 					}
@@ -4715,7 +4781,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 						|| !canstand) && !(player->pflags & (PF_SPINDOWN|PF_SPINNING)))
 				{
 					player->pflags |= (PF_SPINDOWN|PF_SPINNING);
-					P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+					P_SetMobjState(player->mo, S_PLAY_ROLL);
 					if (!player->spectator)
 						S_StartSound(player->mo, sfx_spin);
 				}
@@ -4731,12 +4797,12 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 					{
 						if (player->dashspeed)
 						{
-							P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+							P_SetMobjState(player->mo, S_PLAY_ROLL);
 							P_InstaThrust(player->mo, player->mo->angle, (player->speed = FixedMul(player->dashspeed, player->mo->scale))); // catapult forward ho!!
 						}
 						else
 						{
-							P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+							P_SetMobjState(player->mo, S_PLAY_STND);
 							player->pflags &= ~PF_SPINNING;
 						}
 
@@ -4760,7 +4826,8 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 							if (P_IsLocalPlayer(player)) // Only display it on your own view. Don't display it for spectators
 							{
 								mobj_t *visual = P_SpawnMobj(lockon->x, lockon->y, lockon->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
-								P_SetTarget(&visual->target, lockon);
+								if (!P_MobjWasRemoved(visual))
+									P_SetTarget(&visual->target, lockon);
 								visual->drawonlyforplayer = player; // Hide it from the other player in splitscreen, and yourself when spectating
 							}
 						}
@@ -4768,7 +4835,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 						{
 							mobj_t *bullet;
 
-							P_SetPlayerMobjState(player->mo, S_PLAY_FIRE);
+							P_SetMobjState(player->mo, S_PLAY_FIRE);
 
 #define zpos(posmo) (posmo->z + (posmo->height - mobjinfo[player->revitem].height)/2)
 							if (lockon)
@@ -4812,7 +4879,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 						P_DoJump(player, false);
 						player->pflags &= ~PF_STARTJUMP;
 						player->mo->momz = FixedMul(player->mo->momz, 3*FRACUNIT/2); // NOT 1.5 times the jump height, but 2.25 times.
-						P_SetPlayerMobjState(player->mo, S_PLAY_TWINSPIN);
+						P_SetMobjState(player->mo, S_PLAY_TWINSPIN);
 						S_StartSound(player->mo, sfx_s3k8b);
 					}
 					else
@@ -4838,7 +4905,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 						}
 						player->mo->momx += player->cmomx;
 						player->mo->momy += player->cmomy;
-						P_SetPlayerMobjState(player->mo, S_PLAY_MELEE);
+						P_SetMobjState(player->mo, S_PLAY_MELEE);
 						player->powers[pw_strong] = STR_MELEE;
 						S_StartSound(player->mo, sfx_s3k42);
 					}
@@ -4862,7 +4929,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 		{
 			player->skidtime = 0;
 			player->pflags &= ~PF_SPINNING;
-			P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+			P_SetMobjState(player->mo, S_PLAY_STND);
 			player->mo->momx = player->cmomx;
 			player->mo->momy = player->cmomy;
 		}
@@ -4903,21 +4970,24 @@ void P_DoJumpShield(player_t *player)
 		for (i = 0; i < numangles; i++)
 		{
 			spark = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_THUNDERCOIN_SPARK);
-			P_InstaThrust(spark, travelangle + i*(ANGLE_MAX/numangles), FixedMul(4*FRACUNIT, spark->scale));
-			if (i % 2)
-				P_SetObjectMomZ(spark, -4*FRACUNIT, false);
-			spark->fuse = 18;
+			if (!P_MobjWasRemoved(spark))
+			{
+				P_InstaThrust(spark, travelangle + i*(ANGLE_MAX/numangles), FixedMul(4*FRACUNIT, spark->scale));
+				if (i % 2)
+					P_SetObjectMomZ(spark, -4*FRACUNIT, false);
+				spark->fuse = 18;
+			}
 		}
 #undef limitangle
 #undef numangles
 		player->pflags &= ~PF_NOJUMPDAMAGE;
-		P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+		P_SetMobjState(player->mo, S_PLAY_ROLL);
 		S_StartSound(player->mo, sfx_s3k45);
 	}
 	else
 	{
 		player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE);
-		P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+		P_SetMobjState(player->mo, S_PLAY_FALL);
 		S_StartSound(player->mo, sfx_wdjump);
 	}
 }
@@ -4934,9 +5004,9 @@ void P_DoBubbleBounce(player_t *player)
 	P_MobjCheckWater(player->mo);
 	P_DoJump(player, false);
 	if (player->charflags & SF_NOJUMPSPIN)
-		P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+		P_SetMobjState(player->mo, S_PLAY_FALL);
 	else
-		P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+		P_SetMobjState(player->mo, S_PLAY_ROLL);
 	player->pflags |= PF_THOKKED;
 	player->pflags &= ~PF_STARTJUMP;
 	player->secondjump = UINT8_MAX;
@@ -4973,7 +5043,7 @@ void P_DoAbilityBounce(player_t *player, boolean changemomz)
 	}
 
 	S_StartSound(player->mo, sfx_boingf);
-	P_SetPlayerMobjState(player->mo, S_PLAY_BOUNCE_LANDING);
+	P_SetMobjState(player->mo, S_PLAY_BOUNCE_LANDING);
 	player->pflags |= PF_BOUNCING|PF_THOKKED;
 }
 
@@ -5013,14 +5083,17 @@ void P_TwinSpinRejuvenate(player_t *player, mobjtype_t type)
 			yo,
 			player->mo->height/2 + zo,
 			type);
-		P_SetTarget(&missile->target, player->mo);
-		P_SetScale(missile, (missile->destscale >>= 1));
-		missile->angle = ang + movang;
-		missile->fuse = TICRATE/2;
-		missile->extravalue2 = (99*FRACUNIT)/100;
-		missile->momx = xo;
-		missile->momy = yo;
-		missile->momz = zo;
+		if (!P_MobjWasRemoved(missile))
+		{
+			P_SetTarget(&missile->target, player->mo);
+			P_SetScale(missile, (missile->destscale >>= 1));
+			missile->angle = ang + movang;
+			missile->fuse = TICRATE/2;
+			missile->extravalue2 = (99*FRACUNIT)/100;
+			missile->momx = xo;
+			missile->momy = yo;
+			missile->momz = zo;
+		}
 
 		ang += ANGLE_45;
 	}
@@ -5086,7 +5159,7 @@ static void P_DoTwinSpin(player_t *player)
 	player->pflags |= P_GetJumpFlags(player) | PF_THOKKED;
 	S_StartSound(player->mo, sfx_s3k42);
 	player->mo->frame = 0;
-	P_SetPlayerMobjState(player->mo, S_PLAY_TWINSPIN);
+	P_SetMobjState(player->mo, S_PLAY_TWINSPIN);
 	player->powers[pw_strong] = STR_TWINSPIN;
 }
 
@@ -5118,9 +5191,12 @@ static boolean P_PlayerShieldThink(player_t *player, ticcmd_t *cmd, mobj_t *lock
 					if (dovis)
 					{
 						visual = P_SpawnMobj(lockonshield->x, lockonshield->y, lockonshield->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
-						P_SetTarget(&visual->target, lockonshield);
+						if (!P_MobjWasRemoved(visual))
+						{
+							P_SetTarget(&visual->target, lockonshield);
 						visual->drawonlyforplayer = player; // Hide it from the other player in splitscreen, and yourself when spectating
-						P_SetMobjStateNF(visual, visual->info->spawnstate+1);
+							P_SetMobjStateNF(visual, visual->info->spawnstate+1);
+						}
 					}
 				}
 			}
@@ -5160,7 +5236,7 @@ static boolean P_PlayerShieldThink(player_t *player, ticcmd_t *cmd, mobj_t *lock
 							{
 								player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockonshield->x, lockonshield->y);
 									player->pflags &= ~PF_NOJUMPDAMAGE;
-									P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+									P_SetMobjState(player->mo, S_PLAY_ROLL);
 									S_StartSound(player->mo, sfx_s3k40);
 									player->homing = 3*TICRATE;
 							}
@@ -5184,7 +5260,7 @@ static boolean P_PlayerShieldThink(player_t *player, ticcmd_t *cmd, mobj_t *lock
 									player->mo->momx -= (player->mo->momx/3);
 									player->mo->momy -= (player->mo->momy/3);
 									player->pflags &= ~PF_NOJUMPDAMAGE;
-									P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+									P_SetMobjState(player->mo, S_PLAY_ROLL);
 									S_StartSound(player->mo, sfx_s3k44);
 								}
 								player->secondjump = 0;
@@ -5197,7 +5273,7 @@ static boolean P_PlayerShieldThink(player_t *player, ticcmd_t *cmd, mobj_t *lock
 							P_Thrust(player->mo, player->mo->angle, FixedMul(30*FRACUNIT - FixedSqrt(FixedDiv(player->speed, player->mo->scale)), player->mo->scale));
 							player->drawangle = player->mo->angle;
 							player->pflags &= ~(PF_NOJUMPDAMAGE|PF_SPINNING);
-							P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+							P_SetMobjState(player->mo, S_PLAY_ROLL);
 							S_StartSound(player->mo, sfx_s3k43);
 						default:
 							break;
@@ -5226,7 +5302,8 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 		if (P_IsLocalPlayer(player)) // Only display it on your own view. Don't display it for spectators
 		{
 			visual = P_SpawnMobj(lockonthok->x, lockonthok->y, lockonthok->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
-			P_SetTarget(&visual->target, lockonthok);
+			if (!P_MobjWasRemoved(visual))
+				P_SetTarget(&visual->target, lockonthok);
 			visual->drawonlyforplayer = player; // Hide it from the other player in splitscreen, and yourself when spectating
 		}
 	}
@@ -5259,9 +5336,9 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 							if (player->panim != PA_RUN && player->panim != PA_WALK)
 							{
 								if (player->speed >= FixedMul(player->runspeed, player->mo->scale))
-									P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT_RUN);
+									P_SetMobjState(player->mo, S_PLAY_FLOAT_RUN);
 								else
-									P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT);
+									P_SetMobjState(player->mo, S_PLAY_FLOAT);
 							}
 
 							player->mo->momz = 0;
@@ -5392,13 +5469,13 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 							P_SetTarget(&player->mo->target, P_SetTarget(&player->mo->tracer, lockonthok));
 							if (lockonthok)
 							{
-								P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+								P_SetMobjState(player->mo, S_PLAY_ROLL);
 								player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockonthok->x, lockonthok->y);
 								player->homing = 3*TICRATE;
 							}
 							else
 							{
-								P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+								P_SetMobjState(player->mo, S_PLAY_FALL);
 								player->pflags &= ~PF_JUMPED;
 								player->mo->height = P_GetPlayerHeight(player);
 							}
@@ -5435,7 +5512,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 						; // Can't do anything if you're a fish out of water!
 					else if (!(player->pflags & PF_THOKKED) && !(player->powers[pw_tailsfly]))
 					{
-						P_SetPlayerMobjState(player->mo, S_PLAY_FLY); // Change to the flying animation
+						P_SetMobjState(player->mo, S_PLAY_FLY); // Change to the flying animation
 
 						player->powers[pw_tailsfly] = tailsflytics + 1; // Set the fly timer
 
@@ -5468,7 +5545,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 						player->pflags |= PF_GLIDING|PF_THOKKED;
 						player->glidetime = 0;
 
-						P_SetPlayerMobjState(player->mo, S_PLAY_GLIDE);
+						P_SetMobjState(player->mo, S_PLAY_GLIDE);
 						if (playerspeed < glidespeed)
 							P_Thrust(player->mo, player->mo->angle, glidespeed - playerspeed);
 						player->pflags &= ~(PF_JUMPED|PF_SPINNING|PF_STARTDASH);
@@ -5489,11 +5566,11 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 					if (!(player->pflags & PF_THOKKED) || player->charflags & SF_MULTIABILITY)
 					{
 						if (player->charflags & SF_DASHMODE && player->dashmode >= DASHMODE_THRESHOLD)
-							P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
+							P_SetMobjState(player->mo, S_PLAY_DASH);
 						else if (player->speed >= FixedMul(player->runspeed, player->mo->scale))
-							P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT_RUN);
+							P_SetMobjState(player->mo, S_PLAY_FLOAT_RUN);
 						else
-							P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT);
+							P_SetMobjState(player->mo, S_PLAY_FLOAT);
 						player->pflags |= PF_THOKKED;
 						player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING);
 						player->secondjump = 1;
@@ -5529,7 +5606,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 				case CA_BOUNCE:
 					if (!(player->pflags & PF_THOKKED) || player->charflags & SF_MULTIABILITY)
 					{
-						P_SetPlayerMobjState(player->mo, S_PLAY_BOUNCE);
+						P_SetMobjState(player->mo, S_PLAY_BOUNCE);
 						player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING);
 						player->pflags |= PF_THOKKED|PF_BOUNCING;
 						player->powers[pw_strong] = STR_BOUNCE;
@@ -5619,7 +5696,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 				if (!(player->mo->tracer->flags & MF_BOSS))
 					player->pflags &= ~PF_THOKKED;
 
-				P_SetPlayerMobjState(player->mo, S_PLAY_SPRING);
+				P_SetMobjState(player->mo, S_PLAY_SPRING);
 				player->pflags |= PF_NOJUMPDAMAGE;
 			}
 		}
@@ -5667,7 +5744,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 			else
 				player->secondjump = 2;
 
-			P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+			P_SetMobjState(player->mo, S_PLAY_FALL);
 		}
 
 		// If letting go of the jump button while still on ascent, cut the jump height.
@@ -5787,7 +5864,7 @@ static void P_2dMovement(player_t *player)
 			else if (player->exiting)
 			{
 				player->pflags &= ~PF_GLIDING;
-				P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+				P_SetMobjState(player->mo, S_PLAY_WALK);
 				player->skidtime = 0;
 			}
 		}
@@ -5796,7 +5873,7 @@ static void P_2dMovement(player_t *player)
 		if (player->pflags & PF_SPINNING && !player->exiting)
 		{
 			player->pflags &= ~PF_SPINNING;
-			P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+			P_SetMobjState(player->mo, S_PLAY_STND);
 		}
 	}
 
@@ -5923,6 +6000,7 @@ static void P_2dMovement(player_t *player)
 		else if (player->rmomx > -topspeed && cmd->sidemove < 0)
 			P_Thrust(player->mo, movepushangle, movepushforward);
 	}
+	player->mo->friction = ORIG_FRICTION; //katsy: reset player friction AFTER movement code
 }
 
 //#define OLD_MOVEMENT_CODE 1
@@ -5960,7 +6038,7 @@ static void P_3dMovement(player_t *player)
 			else if (player->exiting)
 			{
 				player->pflags &= ~PF_GLIDING;
-				P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+				P_SetMobjState(player->mo, S_PLAY_WALK);
 				player->skidtime = 0;
 			}
 		}
@@ -5969,7 +6047,7 @@ static void P_3dMovement(player_t *player)
 		if (player->pflags & PF_SPINNING && !player->exiting)
 		{
 			player->pflags &= ~PF_SPINNING;
-			P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+			P_SetMobjState(player->mo, S_PLAY_STND);
 		}
 	}
 
@@ -6011,7 +6089,7 @@ static void P_3dMovement(player_t *player)
 	if (player->pflags & PF_SLIDING)
 		cmd->forwardmove = 0;
 	else if (onground && player->mo->state == states+S_PLAY_PAIN)
-		P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+		P_SetMobjState(player->mo, S_PLAY_WALK);
 
 	player->aiming = cmd->aiming<<FRACBITS;
 
@@ -6244,6 +6322,7 @@ static void P_3dMovement(player_t *player)
 			player->mo->momy = tempmomy + player->cmomy;
 		}
 	}
+	player->mo->friction = ORIG_FRICTION; //katsy: reset player friction AFTER movement code
 }
 
 //
@@ -6816,12 +6895,12 @@ static void P_DoNiGHTSCapsule(player_t *player)
 		if (player->mo->momx || player->mo->momy || player->mo->momz)
 		{
 			if (player->mo->state != &states[S_PLAY_NIGHTS_PULL])
-				P_SetPlayerMobjState(player->mo, S_PLAY_NIGHTS_PULL);
+				P_SetMobjState(player->mo, S_PLAY_NIGHTS_PULL);
 		}
 		else if (player->mo->state != &states[S_PLAY_NIGHTS_ATTACK])
 		{
 			S_StartSound(player->mo, sfx_spin);
-			P_SetPlayerMobjState(player->mo, S_PLAY_NIGHTS_ATTACK);
+			P_SetMobjState(player->mo, S_PLAY_NIGHTS_ATTACK);
 		}
 	}
 	else
@@ -6829,7 +6908,7 @@ static void P_DoNiGHTSCapsule(player_t *player)
 		if (!(player->pflags & PF_JUMPED) && !(player->pflags & PF_SPINNING))
 			player->pflags |= PF_JUMPED;
 		if (player->panim != PA_ROLL)
-			P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+			P_SetMobjState(player->mo, S_PLAY_ROLL);
 	}
 
 	if (!(player->charflags & SF_NONIGHTSROTATION))
@@ -6912,10 +6991,14 @@ static void P_DoNiGHTSCapsule(player_t *player)
 
 			// Spawn a 'pop' for every 2 tics
 			if (!((tictimer - firstpoptic) % 2))
-				S_StartSound(P_SpawnMobj(player->capsule->x + ((P_SignedRandom()/2)<<FRACBITS),
-				player->capsule->y + ((P_SignedRandom()/2)<<FRACBITS),
-				player->capsule->z + (player->capsule->height/2) + ((P_SignedRandom()/2)<<FRACBITS),
-				MT_SONIC3KBOSSEXPLODE),sfx_s3kb4);
+			{
+				mobj_t *explodemo = P_SpawnMobj(player->capsule->x + ((P_SignedRandom()/2)<<FRACBITS),
+					player->capsule->y + ((P_SignedRandom()/2)<<FRACBITS),
+					player->capsule->z + (player->capsule->height/2) + ((P_SignedRandom()/2)<<FRACBITS),
+					MT_SONIC3KBOSSEXPLODE);
+				if (!P_MobjWasRemoved(explodemo))
+					S_StartSound(explodemo,sfx_s3kb4);
+			}
 		}
 		else
 		{
@@ -6972,10 +7055,13 @@ static void P_DoNiGHTSCapsule(player_t *player)
 						UINT8 em = P_GetNextEmerald();
 						// Only give it to ONE person, and THAT player has to get to the goal!
 						mobj_t *emmo = P_SpawnMobjFromMobj(player->mo, 0, 0, player->mo->height, MT_GOTEMERALD);
-						emmo->health = em; // for identification
-						P_SetTarget(&emmo->target, player->mo);
-						P_SetMobjState(emmo, mobjinfo[MT_GOTEMERALD].meleestate + em);
-						P_SetTarget(&player->mo->tracer, emmo);
+						if (!P_MobjWasRemoved(emmo))
+						{
+							emmo->health = em; // for identification
+							P_SetTarget(&emmo->target, player->mo);
+							P_SetMobjState(emmo, mobjinfo[MT_GOTEMERALD].meleestate + em);
+							P_SetTarget(&player->mo->tracer, emmo);
+						}
 					}
 
 					// Okay, we're doing this down here because we're handling time weirdly for co-op special stages
@@ -6998,19 +7084,22 @@ static void P_DoNiGHTSCapsule(player_t *player)
 						P_InstaThrust(flicky, flicky->angle, 8*FRACUNIT);
 					}*/
 					mobj_t *idya = P_SpawnMobjFromMobj(player->mo, 0, 0, player->mo->height, MT_GOTEMERALD);
-					idya->extravalue2 = player->mare/5;
-					idya->health = player->mare + 1; // for identification
-					P_SetTarget(&idya->target, player->mo);
-					P_SetMobjState(idya, mobjinfo[MT_GOTEMERALD].missilestate + ((player->mare + 1) % 5));
-
-					if (player->mo->tracer)
+					if (!P_MobjWasRemoved(idya))
 					{
-						P_SetTarget(&idya->hnext, player->mo->tracer);
-						idya->extravalue1 = (angle_t)(player->mo->tracer->extravalue1 - 72*ANG1);
-						if (idya->extravalue1 > player->mo->tracer->extravalue1)
-							idya->extravalue1 -= (72*ANG1)/idya->extravalue1;
+						idya->extravalue2 = player->mare/5;
+						idya->health = player->mare + 1; // for identification
+						P_SetTarget(&idya->target, player->mo);
+						P_SetMobjState(idya, mobjinfo[MT_GOTEMERALD].missilestate + ((player->mare + 1) % 5));
+
+						if (player->mo->tracer)
+						{
+							P_SetTarget(&idya->hnext, player->mo->tracer);
+							idya->extravalue1 = (angle_t)(player->mo->tracer->extravalue1 - 72*ANG1);
+							if (idya->extravalue1 > player->mo->tracer->extravalue1)
+								idya->extravalue1 -= (72*ANG1)/idya->extravalue1;
+						}
+						P_SetTarget(&player->mo->tracer, idya);
 					}
-					P_SetTarget(&player->mo->tracer, idya);
 				}
 				for (i = 0; i < MAXPLAYERS; i++)
 					if (playeringame[i] && players[i].mare == player->mare)
@@ -7308,14 +7397,14 @@ static void P_NiGHTSMovement(player_t *player)
 		if (!(player->charflags & SF_NONIGHTSROTATION) && player->mo->momz)
 		{
 			if (player->mo->state != &states[S_PLAY_NIGHTS_DRILL])
-				P_SetPlayerMobjState(player->mo, S_PLAY_NIGHTS_DRILL);
+				P_SetMobjState(player->mo, S_PLAY_NIGHTS_DRILL);
 			player->mo->spriteroll = ANGLE_90;
 		}
 		else
 #endif
 		{
 			if (player->mo->state != &states[S_PLAY_NIGHTS_FLOAT])
-				P_SetPlayerMobjState(player->mo, S_PLAY_NIGHTS_FLOAT);
+				P_SetMobjState(player->mo, S_PLAY_NIGHTS_FLOAT);
 			player->drawangle += ANGLE_22h;
 		}
 
@@ -7335,19 +7424,25 @@ static void P_NiGHTSMovement(player_t *player)
 			z -= FixedMul(mobjinfo[MT_NIGHTSPARKLE].height, player->mo->scale);
 
 		firstmobj = P_SpawnMobj(player->mo->x + P_ReturnThrustX(player->mo, player->mo->angle+ANGLE_90, spawndist), player->mo->y + P_ReturnThrustY(player->mo, player->mo->angle+ANGLE_90, spawndist), z, MT_NIGHTSPARKLE);
+		if (!P_MobjWasRemoved(firstmobj))
+		{
+			firstmobj->destscale = player->mo->scale;
+			P_SetTarget(&firstmobj->target, player->mo);
+			P_SetScale(firstmobj, player->mo->scale);
+			// Superloop turns sparkles red
+			if (player->powers[pw_nights_superloop])
+				P_SetMobjState(firstmobj, mobjinfo[MT_NIGHTSPARKLE].seestate);
+		}
 		secondmobj = P_SpawnMobj(player->mo->x + P_ReturnThrustX(player->mo, player->mo->angle-ANGLE_90, spawndist), player->mo->y + P_ReturnThrustY(player->mo, player->mo->angle-ANGLE_90, spawndist), z, MT_NIGHTSPARKLE);
-
-		firstmobj->destscale = secondmobj->destscale = player->mo->scale;
-		P_SetTarget(&firstmobj->target, player->mo);
-		P_SetScale(firstmobj, player->mo->scale);
-		P_SetTarget(&secondmobj->target, player->mo);
-		P_SetScale(secondmobj, player->mo->scale);
-
-		// Superloop turns sparkles red
-		if (player->powers[pw_nights_superloop])
+		if (!P_MobjWasRemoved(secondmobj))
 		{
-			P_SetMobjState(firstmobj, mobjinfo[MT_NIGHTSPARKLE].seestate);
-			P_SetMobjState(secondmobj, mobjinfo[MT_NIGHTSPARKLE].seestate);
+			secondmobj->destscale = player->mo->scale;
+			P_SetTarget(&secondmobj->target, player->mo);
+			P_SetScale(secondmobj, player->mo->scale);
+
+			// Superloop turns sparkles red
+			if (player->powers[pw_nights_superloop])
+				P_SetMobjState(secondmobj, mobjinfo[MT_NIGHTSPARKLE].seestate);
 		}
 	}
 
@@ -7355,9 +7450,12 @@ static void P_NiGHTSMovement(player_t *player)
 	// It also spawns every tic to avoid failed paraloops
 	{
 		mobj_t *helpermobj = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_NIGHTSLOOPHELPER);
-		helpermobj->fuse = player->mo->fuse = leveltime;
-		P_SetTarget(&helpermobj->target, player->mo);
-		P_SetScale(helpermobj, player->mo->scale);
+		if (!P_MobjWasRemoved(helpermobj))
+		{
+			helpermobj->fuse = player->mo->fuse = leveltime;
+			P_SetTarget(&helpermobj->target, player->mo);
+			P_SetScale(helpermobj, player->mo->scale);
+		}
 	}
 
 	if (player->bumpertime)
@@ -7546,19 +7644,22 @@ static void P_NiGHTSMovement(player_t *player)
 		mobjtype_t splishtype = (player->mo->eflags & MFE_TOUCHLAVA) ? MT_LAVASPLISH : MT_SPLISH;
 		mobj_t *water = P_SpawnMobj(player->mo->x, player->mo->y,
 			((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[splishtype].height, player->mo->scale) : player->mo->watertop), splishtype);
-		if (player->mo->eflags & MFE_GOOWATER)
-			S_StartSound(water, sfx_ghit);
-		else if (player->mo->eflags & MFE_TOUCHLAVA)
-			S_StartSound(water, sfx_splash);
-		else
-			S_StartSound(water, sfx_wslap);
-		if (player->mo->eflags & MFE_VERTICALFLIP)
+		if (!P_MobjWasRemoved(water))
 		{
-			water->flags2 |= MF2_OBJECTFLIP;
-			water->eflags |= MFE_VERTICALFLIP;
+			if (player->mo->eflags & MFE_GOOWATER)
+				S_StartSound(water, sfx_ghit);
+			else if (player->mo->eflags & MFE_TOUCHLAVA)
+				S_StartSound(water, sfx_splash);
+			else
+				S_StartSound(water, sfx_wslap);
+			if (player->mo->eflags & MFE_VERTICALFLIP)
+			{
+				water->flags2 |= MF2_OBJECTFLIP;
+				water->eflags |= MFE_VERTICALFLIP;
+			}
+			water->destscale = player->mo->scale;
+			P_SetScale(water, player->mo->scale);
 		}
-		water->destscale = player->mo->scale;
-		P_SetScale(water, player->mo->scale);
 	}
 
 	if (player->mo->momx || player->mo->momy)
@@ -7639,7 +7740,7 @@ static void P_NiGHTSMovement(player_t *player)
 	}
 
 	if (player->mo->state != &states[flystate])
-		P_SetPlayerMobjState(player->mo, flystate);
+		P_SetMobjState(player->mo, flystate);
 
 	if (player->charflags & SF_NONIGHTSROTATION)
 		player->mo->spriteroll = 0;
@@ -7791,6 +7892,8 @@ void P_ElementalFire(player_t *player, boolean cropcircle)
 		for (i = 0; i < numangles; i++)
 		{
 			flame = P_SpawnMobj(player->mo->x, player->mo->y, ground, MT_SPINFIRE);
+			if (P_MobjWasRemoved(flame))
+				continue;
 			flame->flags &= ~MF_NOGRAVITY;
 			P_SetTarget(&flame->target, player->mo);
 			flame->angle = travelangle + i*(ANGLE_MAX/numangles);
@@ -7828,6 +7931,8 @@ void P_ElementalFire(player_t *player, boolean cropcircle)
 			}
 
 			flame = P_SpawnMobj(newx, newy, ground, MT_SPINFIRE);
+			if (P_MobjWasRemoved(flame))
+				continue;
 			P_SetTarget(&flame->target, player->mo);
 			flame->angle = travelangle;
 			flame->fuse = TICRATE*6;
@@ -7869,6 +7974,8 @@ void P_SpawnSkidDust(player_t *player, fixed_t radius, boolean sound)
 	mobj_t *particle;
 
 	particle = P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_SPINDUST);
+	if (P_MobjWasRemoved(particle))
+		return;
 	if (radius >>= FRACBITS)
 	{
 		P_UnsetThingPosition(particle);
@@ -7905,13 +8012,13 @@ static void P_SkidStuff(player_t *player)
 			player->skidtime = 0;
 			player->pflags &= ~(PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE);
 			player->pflags |= PF_THOKKED;
-			P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+			P_SetMobjState(player->mo, S_PLAY_FALL);
 		}
 		// Get up and brush yourself off, idiot.
 		else if (player->glidetime > 15 || !(player->cmd.buttons & BT_JUMP))
 		{
 			P_ResetPlayer(player);
-			P_SetPlayerMobjState(player->mo, S_PLAY_GLIDE_LANDING);
+			P_SetMobjState(player->mo, S_PLAY_GLIDE_LANDING);
 			player->pflags |= PF_STASIS;
 			if (player->speed > FixedMul(player->runspeed, player->mo->scale))
 				player->skidtime += player->mo->tics;
@@ -7956,7 +8063,7 @@ static void P_SkidStuff(player_t *player)
 			if (dang > ANGLE_157h)
 			{
 				if (player->mo->state-states != S_PLAY_SKID)
-					P_SetPlayerMobjState(player->mo, S_PLAY_SKID);
+					P_SetMobjState(player->mo, S_PLAY_SKID);
 				player->mo->tics = player->skidtime = (player->mo->movefactor == FRACUNIT) ? TICRATE/2 : (FixedDiv(35<<(FRACBITS-1), FixedSqrt(player->mo->movefactor)))>>FRACBITS;
 				S_StartSound(player->mo, sfx_skid);
 			}
@@ -8188,63 +8295,63 @@ void P_MovePlayer(player_t *player)
 	{
 		// If the player is in dashmode, here's their peelout.
 		if (player->charflags & SF_DASHMODE && player->dashmode >= DASHMODE_THRESHOLD && player->panim == PA_RUN && !player->skidtime && (onground || ((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super]))
-			P_SetPlayerMobjState (player->mo, S_PLAY_DASH);
+			P_SetMobjState (player->mo, S_PLAY_DASH);
 		// If the player is moving fast enough,
 		// break into a run!
 		else if (player->speed >= runspd && player->panim == PA_WALK && !player->skidtime
 		&& (onground || ((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super]))
 		{
 			if (!onground)
-				P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT_RUN);
+				P_SetMobjState(player->mo, S_PLAY_FLOAT_RUN);
 			else
-				P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
+				P_SetMobjState(player->mo, S_PLAY_RUN);
 		}
 
 		// Floating at slow speeds has its own special animation.
 		else if ((((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super]) && player->panim == PA_IDLE && !onground)
-			P_SetPlayerMobjState (player->mo, S_PLAY_FLOAT);
+			P_SetMobjState (player->mo, S_PLAY_FLOAT);
 
 		// Otherwise, just walk.
 		else if ((player->rmomx || player->rmomy) && player->panim == PA_IDLE)
-			P_SetPlayerMobjState (player->mo, S_PLAY_WALK);
+			P_SetMobjState (player->mo, S_PLAY_WALK);
 	}
 
 	// If your peelout animation is playing, and you're
 	// going too slow, switch back to the run.
 	if (player->charflags & SF_DASHMODE && player->panim == PA_DASH && player->dashmode < DASHMODE_THRESHOLD)
-		P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
+		P_SetMobjState(player->mo, S_PLAY_RUN);
 
 	// If your running animation is playing, and you're
 	// going too slow, switch back to the walking frames.
 	if (player->panim == PA_RUN && player->speed < runspd)
 	{
 		if (!onground && (((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super]))
-			P_SetPlayerMobjState(player->mo, S_PLAY_FLOAT);
+			P_SetMobjState(player->mo, S_PLAY_FLOAT);
 		else
-			P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+			P_SetMobjState(player->mo, S_PLAY_WALK);
 	}
 
 	// Correct floating when ending up on the ground.
 	if (onground)
 	{
 		if (player->mo->state-states == S_PLAY_FLOAT)
-			P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+			P_SetMobjState(player->mo, S_PLAY_WALK);
 		else if (player->mo->state-states == S_PLAY_FLOAT_RUN)
-			P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
+			P_SetMobjState(player->mo, S_PLAY_RUN);
 	}
 
 	// If Springing (or nojumpspinning), but travelling DOWNWARD, change back!
 	if ((player->panim == PA_SPRING && P_MobjFlip(player->mo)*player->mo->momz < 0)
 		|| ((((player->charflags & SF_NOJUMPSPIN) && (player->pflags & PF_JUMPED) && player->panim == PA_JUMP))
 			&& (P_MobjFlip(player->mo)*player->mo->momz < 0)))
-		P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+		P_SetMobjState(player->mo, S_PLAY_FALL);
 	// If doing an air animation but on the ground, change back!
 	else if (onground && (player->panim == PA_SPRING || player->panim == PA_FALL || player->panim == PA_RIDE || player->panim == PA_JUMP) && !player->mo->momz)
-		P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+		P_SetMobjState(player->mo, S_PLAY_STND);
 
 	// If you are stopped and are still walking, stand still!
 	if (!player->mo->momx && !player->mo->momy && !player->mo->momz && player->panim == PA_WALK)
-		P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+		P_SetMobjState(player->mo, S_PLAY_STND);
 
 //////////////////
 //GAMEPLAY STUFF//
@@ -8256,18 +8363,18 @@ void P_MovePlayer(player_t *player)
 	{
 		player->pflags &= ~(PF_STARTJUMP|PF_JUMPED|PF_NOJUMPDAMAGE|PF_THOKKED|PF_SHIELDABILITY);
 		player->secondjump = 0;
-		P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+		P_SetMobjState(player->mo, S_PLAY_STND);
 	}
 
 	if ((!(player->charability == CA_GLIDEANDCLIMB) || player->gotflag) // If you can't glide, then why the heck would you be gliding?
 		&& (player->pflags & PF_GLIDING || player->climbing))
 	{
 		if (onground)
-			P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+			P_SetMobjState(player->mo, S_PLAY_WALK);
 		else
 		{
 			player->pflags |= P_GetJumpFlags(player);
-			P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
+			P_SetMobjState(player->mo, S_PLAY_JUMP);
 		}
 		player->pflags &= ~PF_GLIDING;
 		player->glidetime = 0;
@@ -8278,11 +8385,11 @@ void P_MovePlayer(player_t *player)
 		&& (player->pflags & PF_BOUNCING))
 	{
 		if (onground)
-			P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+			P_SetMobjState(player->mo, S_PLAY_WALK);
 		else
 		{
 			player->pflags |= P_GetJumpFlags(player);
-			P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
+			P_SetMobjState(player->mo, S_PLAY_JUMP);
 		}
 		player->pflags &= ~PF_BOUNCING;
 	}
@@ -8399,18 +8506,18 @@ void P_MovePlayer(player_t *player)
 		{
 			P_ResetPlayer(player); // down, stop gliding.
 			if (onground)
-				P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+				P_SetMobjState(player->mo, S_PLAY_WALK);
 			else if (player->charflags & SF_MULTIABILITY)
 			{
 				player->pflags |= P_GetJumpFlags(player);
-				P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
+				P_SetMobjState(player->mo, S_PLAY_JUMP);
 			}
 			else
 			{
 				player->pflags |= PF_THOKKED;
 				player->mo->momx >>= 1;
 				player->mo->momy >>= 1;
-				P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+				P_SetMobjState(player->mo, S_PLAY_FALL);
 			}
 		}
 	}
@@ -8427,23 +8534,23 @@ void P_MovePlayer(player_t *player)
 			P_ResetPlayer(player); // down, stop bouncing.
 			player->pflags |= PF_THOKKED;
 			if (onground)
-				P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+				P_SetMobjState(player->mo, S_PLAY_WALK);
 			else if (player->charflags & SF_MULTIABILITY)
 			{
 				player->pflags |= P_GetJumpFlags(player);
-				P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
+				P_SetMobjState(player->mo, S_PLAY_JUMP);
 			}
 			else
 			{
 				player->mo->momx >>= 1;
 				player->mo->momy >>= 1;
 				player->mo->momz >>= 1;
-				P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+				P_SetMobjState(player->mo, S_PLAY_FALL);
 			}
 		}
 	}
 	else if (player->mo->state-states == S_PLAY_BOUNCE)
-		P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+		P_SetMobjState(player->mo, S_PLAY_FALL);
 
 	// If you're running fast enough, you can create splashes as you run in shallow water.
 	if (!player->climbing
@@ -8455,19 +8562,22 @@ void P_MovePlayer(player_t *player)
 		mobjtype_t splishtype = (player->mo->eflags & MFE_TOUCHLAVA) ? MT_LAVASPLISH : MT_SPLISH;
 		mobj_t *water = P_SpawnMobj(player->mo->x - P_ReturnThrustX(NULL, player->mo->angle, player->mo->radius), player->mo->y - P_ReturnThrustY(NULL, player->mo->angle, player->mo->radius),
 			((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[splishtype].height, player->mo->scale) : player->mo->watertop), splishtype);
-		if (player->mo->eflags & MFE_GOOWATER)
-			S_StartSound(water, sfx_ghit);
-		else if (player->mo->eflags & MFE_TOUCHLAVA)
-			S_StartSound(water, sfx_splash);
-		else
-			S_StartSound(water, sfx_wslap);
-		if (player->mo->eflags & MFE_VERTICALFLIP)
+		if (!P_MobjWasRemoved(water))
 		{
-			water->flags2 |= MF2_OBJECTFLIP;
-			water->eflags |= MFE_VERTICALFLIP;
+			if (player->mo->eflags & MFE_GOOWATER)
+				S_StartSound(water, sfx_ghit);
+			else if (player->mo->eflags & MFE_TOUCHLAVA)
+				S_StartSound(water, sfx_splash);
+			else
+				S_StartSound(water, sfx_wslap);
+			if (player->mo->eflags & MFE_VERTICALFLIP)
+			{
+				water->flags2 |= MF2_OBJECTFLIP;
+				water->eflags |= MFE_VERTICALFLIP;
+			}
+			water->destscale = player->mo->scale;
+			P_SetScale(water, player->mo->scale);
 		}
-		water->destscale = player->mo->scale;
-		P_SetScale(water, player->mo->scale);
 	}
 
 	// Little water sound while touching water - just a nicety.
@@ -8487,11 +8597,11 @@ void P_MovePlayer(player_t *player)
 		|| player->mo->state-states == S_PLAY_FLY_TIRED)
 		{
 			if (onground)
-				P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+				P_SetMobjState(player->mo, S_PLAY_WALK);
 			else
 			{
 				player->pflags |= P_GetJumpFlags(player);
-				P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
+				P_SetMobjState(player->mo, S_PLAY_JUMP);
 			}
 		}
 		player->powers[pw_tailsfly] = 0;
@@ -8525,7 +8635,7 @@ void P_MovePlayer(player_t *player)
 					if (P_MobjFlip(player->mo)*player->mo->momz < FixedMul(5*actionspd, player->mo->scale))
 						P_SetObjectMomZ(player->mo, actionspd/2, true);
 
-					P_SetPlayerMobjState(player->mo, player->mo->state->nextstate);
+					P_SetMobjState(player->mo, player->mo->state->nextstate);
 
 					player->fly1--;
 				}
@@ -8553,7 +8663,7 @@ void P_MovePlayer(player_t *player)
 		{
 			// Tails-gets-tired Stuff
 			if (player->panim == PA_ABILITY && player->mo->state-states != S_PLAY_FLY_TIRED)
-				P_SetPlayerMobjState(player->mo, S_PLAY_FLY_TIRED);
+				P_SetMobjState(player->mo, S_PLAY_FLY_TIRED);
 
 			if (player->charability == CA_FLY && (leveltime % 10 == 0)
 				&& player->mo->state-states == S_PLAY_FLY_TIRED
@@ -8670,7 +8780,7 @@ void P_MovePlayer(player_t *player)
 	// Make sure you're not teetering when you shouldn't be.
 	if (player->panim == PA_EDGE
 	&& (player->mo->momx || player->mo->momy || player->mo->momz))
-		P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+		P_SetMobjState(player->mo, S_PLAY_STND);
 
 	// Check for teeter!
 	if (!(player->mo->momz || player->mo->momx || player->mo->momy) && !(player->mo->eflags & MFE_GOOWATER)
@@ -8736,7 +8846,7 @@ void P_MovePlayer(player_t *player)
 			if (!atspinheight)
 			{
 				player->pflags |= PF_SPINNING;
-				P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+				P_SetMobjState(player->mo, S_PLAY_ROLL);
 			}
 			else if (player->mo->ceilingz - player->mo->floorz < player->mo->height)
 			{
@@ -8917,7 +9027,7 @@ static void P_DoRopeHang(player_t *player)
 	if (player->cmd.buttons & BT_SPIN && !(player->pflags & PF_STASIS)) // Drop off of the rope
 	{
 		player->pflags |= (P_GetJumpFlags(player)|PF_SPINDOWN);
-		P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
+		P_SetMobjState(player->mo, S_PLAY_JUMP);
 
 		P_SetTarget(&player->mo->tracer, NULL);
 		player->powers[pw_carry] = CR_NONE;
@@ -8926,7 +9036,7 @@ static void P_DoRopeHang(player_t *player)
 	}
 
 	if (player->mo->state-states != S_PLAY_RIDE)
-		P_SetPlayerMobjState(player->mo, S_PLAY_RIDE);
+		P_SetMobjState(player->mo, S_PLAY_RIDE);
 
 	// If not allowed to move, we're done here.
 	if (!speed)
@@ -8982,7 +9092,7 @@ static void P_DoRopeHang(player_t *player)
 			if (player->mo->tracer->flags & MF_SLIDEME)
 			{
 				player->pflags |= P_GetJumpFlags(player);
-				P_SetPlayerMobjState(player->mo, S_PLAY_JUMP);
+				P_SetMobjState(player->mo, S_PLAY_JUMP);
 			}
 
 			P_SetTarget(&player->mo->tracer, NULL);
@@ -10893,6 +11003,8 @@ static void P_SpawnSparks(mobj_t *mo, angle_t maindir)
 	fixed_t fm = (maindir >> ANGLETOFINESHIFT) & FINEMASK;
 
 	spark = P_SpawnMobj(mo->x - b2*s + b1*c, mo->y + b2*c + b1*s, mo->z, MT_MINECARTSPARK);
+	if (P_MobjWasRemoved(spark))
+		return;
 	spark->momx = mo->momx + r1 + 8*FINECOSINE(fm);
 	spark->momy = mo->momy + r2 + 8*FINESINE(fm);
 	spark->momz = mo->momz + r3;
@@ -11180,7 +11292,7 @@ static void P_MinecartThink(player_t *player)
 
 	if (player->mo->state-states != S_PLAY_STND)
 	{
-		P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+		P_SetMobjState(player->mo, S_PLAY_STND);
 		player->mo->tics = -1;
 	}
 
@@ -11958,7 +12070,7 @@ void P_PlayerThink(player_t *player)
 		{
 			P_DoZoomTube(player);
 			if (!(player->panim == PA_ROLL))
-				P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+				P_SetMobjState(player->mo, S_PLAY_ROLL);
 		}
 		player->rmomx = player->rmomy = 0; // no actual momentum from your controls
 		P_ResetScore(player);
@@ -12117,7 +12229,7 @@ void P_PlayerThink(player_t *player)
 			{
 				statenum_t stat = player->mo->state-states;
 				if (stat == S_PLAY_WAIT)
-					P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+					P_SetMobjState(player->mo, S_PLAY_STND);
 				else if (stat == S_PLAY_STND && player->mo->tics != -1)
 					player->mo->tics++;
 			}
@@ -12144,7 +12256,7 @@ void P_PlayerThink(player_t *player)
 				else if (!player->skidtime && !(player->mo->eflags & MFE_GOOWATER) && !(player->pflags & (PF_JUMPED|PF_SPINNING|PF_SLIDING)) && !(player->charflags & SF_NOSKID) && P_AproxDistance(player->mo->momx, player->mo->momy) >= FixedMul(player->runspeed, player->mo->scale)) // modified from player->runspeed/2 'cuz the skid was just TOO frequent ngl
 				{
 					if (player->mo->state-states != S_PLAY_SKID)
-						P_SetPlayerMobjState(player->mo, S_PLAY_SKID);
+						P_SetMobjState(player->mo, S_PLAY_SKID);
 					player->mo->tics = player->skidtime = (player->mo->movefactor == FRACUNIT) ? TICRATE/2 : (FixedDiv(35<<(FRACBITS-1), FixedSqrt(player->mo->movefactor)))>>FRACBITS;
 
 					if (P_IsLocalPlayer(player)) // the sound happens way more frequently now, so give co-op players' ears a brake...
@@ -12194,17 +12306,20 @@ void P_PlayerThink(player_t *player)
 	if ((player->powers[pw_super] || player->powers[pw_sneakers]) && (player->speed + abs(player->mo->momz)) > FixedMul(20*FRACUNIT,player->mo->scale))
 	{
 		mobj_t *gmobj = P_SpawnGhostMobj(player->mo);
-		gmobj->fuse = 2;
-		if (gmobj->tracer)
-			gmobj->tracer->fuse = 2;
-		if (leveltime & 1)
+		if (!P_MobjWasRemoved(gmobj))
 		{
-			gmobj->frame &= ~FF_TRANSMASK;
-			gmobj->frame |= tr_trans70<<FF_TRANSSHIFT;
+			gmobj->fuse = 2;
 			if (gmobj->tracer)
+				gmobj->tracer->fuse = 2;
+			if (leveltime & 1)
 			{
-				gmobj->tracer->frame &= ~FF_TRANSMASK;
-				gmobj->tracer->frame |= tr_trans70<<FF_TRANSSHIFT;
+				gmobj->frame &= ~FF_TRANSMASK;
+				gmobj->frame |= tr_trans70<<FF_TRANSSHIFT;
+				if (gmobj->tracer)
+				{
+					gmobj->tracer->frame &= ~FF_TRANSMASK;
+					gmobj->tracer->frame |= tr_trans70<<FF_TRANSSHIFT;
+				}
 			}
 		}
 	}
@@ -12321,7 +12436,7 @@ void P_PlayerThink(player_t *player)
 		if (!player->powers[pw_flashing])
 		{
 			if (player->mo->state != &states[S_PLAY_STND])
-				P_SetPlayerMobjState(player->mo, S_PLAY_STND);
+				P_SetMobjState(player->mo, S_PLAY_STND);
 			else
 				player->mo->tics = 2;
 		}
@@ -12423,9 +12538,12 @@ void P_PlayerThink(player_t *player)
 		if (player->normalspeed >= skins[player->skin]->normalspeed*2)
 		{
 			mobj_t *ghost = P_SpawnGhostMobj(player->mo); // Spawns afterimages
-			ghost->fuse = 2; // Makes the images fade quickly
-			if (ghost->tracer && !P_MobjWasRemoved(ghost->tracer))
-				ghost->tracer->fuse = ghost->fuse;
+			if (!P_MobjWasRemoved(ghost))
+			{
+				ghost->fuse = 2; // Makes the images fade quickly
+				if (ghost->tracer && !P_MobjWasRemoved(ghost->tracer))
+					ghost->tracer->fuse = ghost->fuse;
+			}
 		}
 	}
 	else if (dashmode)
@@ -12682,7 +12800,7 @@ void P_PlayerAfterThink(player_t *player)
 		S_StartSound(NULL, sfx_wepchg);
 
 	if ((player->pflags & PF_SLIDING) && ((player->pflags & (PF_JUMPED|PF_NOJUMPDAMAGE)) != PF_JUMPED))
-		P_SetPlayerMobjState(player->mo, player->mo->info->painstate);
+		P_SetMobjState(player->mo, player->mo->info->painstate);
 
 	/* if (player->powers[pw_carry] == CR_NONE && player->mo->tracer && !player->homing)
 		P_SetTarget(&player->mo->tracer, NULL);
@@ -12743,7 +12861,7 @@ void P_PlayerAfterThink(player_t *player)
 				if (player->powers[pw_carry] == CR_PLAYER)
 				{
 					if (player->mo->state-states != S_PLAY_RIDE)
-						P_SetPlayerMobjState(player->mo, S_PLAY_RIDE);
+						P_SetMobjState(player->mo, S_PLAY_RIDE);
 					if (tails->player && (tails->skin && ((skin_t *)(tails->skin))->sprites[SPR2_SWIM].numframes) && (tails->eflags & MFE_UNDERWATER))
 						tails->player->powers[pw_tailsfly] = 0;
 				}
@@ -12768,7 +12886,7 @@ void P_PlayerAfterThink(player_t *player)
 				player->mo->momx = player->mo->momy = player->mo->momz = 0;
 				P_SetThingPosition(player->mo);
 				if (player->mo->state-states != S_PLAY_RIDE)
-					P_SetPlayerMobjState(player->mo, S_PLAY_RIDE);
+					P_SetMobjState(player->mo, S_PLAY_RIDE);
 
 				// Controllable missile
 				if (item->type == MT_BLACKEGGMAN_MISSILE)
@@ -12889,7 +13007,7 @@ void P_PlayerAfterThink(player_t *player)
 
 				if (player->panim == PA_IDLE && (mo->momx || mo->momy))
 				{
-					P_SetPlayerMobjState(player->mo, S_PLAY_WALK);
+					P_SetMobjState(player->mo, S_PLAY_WALK);
 				}
 
 				if (player->panim == PA_WALK && mo->tics > walktics)
@@ -12945,7 +13063,7 @@ void P_PlayerAfterThink(player_t *player)
 					P_KillMobj(ptera, player->mo, player->mo, 0);
 					P_SetObjectMomZ(player->mo, 12*FRACUNIT, false);
 					player->pflags |= PF_APPLYAUTOBRAKE|PF_JUMPED|PF_THOKKED;
-					P_SetPlayerMobjState(player->mo, S_PLAY_ROLL);
+					P_SetMobjState(player->mo, S_PLAY_ROLL);
 					break;
 				}
 
@@ -12966,7 +13084,7 @@ void P_PlayerAfterThink(player_t *player)
 				ptera->cvmem >>= 1;
 
 				if (player->mo->state-states != S_PLAY_FALL)
-					P_SetPlayerMobjState(player->mo, S_PLAY_FALL);
+					P_SetMobjState(player->mo, S_PLAY_FALL);
 				break;
 
 			dropoff:
@@ -13026,15 +13144,18 @@ void P_PlayerAfterThink(player_t *player)
 		if (!player->followmobj || P_MobjWasRemoved(player->followmobj))
 		{
 			P_SetTarget(&player->followmobj, P_SpawnMobjFromMobj(player->mo, 0, 0, 0, player->followitem));
-			P_SetTarget(&player->followmobj->tracer, player->mo);
-			switch (player->followmobj->type)
+			if (!P_MobjWasRemoved(player->followmobj))
 			{
-				case MT_METALJETFUME:
-					player->followmobj->colorized = true;
-					break;
-				default:
-					player->followmobj->flags2 |= MF2_LINKDRAW;
-					break;
+				P_SetTarget(&player->followmobj->tracer, player->mo);
+				switch (player->followmobj->type)
+				{
+					case MT_METALJETFUME:
+						player->followmobj->colorized = true;
+						break;
+					default:
+						player->followmobj->flags2 |= MF2_LINKDRAW;
+						break;
+				}
 			}
 		}
 
diff --git a/src/r_bsp.c b/src/r_bsp.c
index 42e050adf831f15a7b3e653c57e265cab781b6cc..d99de5981ba789395347a01ae28ad14f4c68bb0d 100644
--- a/src/r_bsp.c
+++ b/src/r_bsp.c
@@ -277,6 +277,8 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 			tempsec->floorpic = s->floorpic;
 			tempsec->floorxoffset = s->floorxoffset;
 			tempsec->flooryoffset = s->flooryoffset;
+			tempsec->floorxscale = s->floorxscale;
+			tempsec->flooryscale = s->flooryscale;
 			tempsec->floorangle = s->floorangle;
 
 			if (underwater)
@@ -287,6 +289,8 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 					tempsec->ceilingpic = tempsec->floorpic;
 					tempsec->ceilingxoffset = tempsec->floorxoffset;
 					tempsec->ceilingyoffset = tempsec->flooryoffset;
+					tempsec->ceilingxscale = tempsec->floorxscale;
+					tempsec->ceilingyscale = tempsec->flooryscale;
 					tempsec->ceilingangle = tempsec->floorangle;
 				}
 				else
@@ -294,6 +298,8 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 					tempsec->ceilingpic = s->ceilingpic;
 					tempsec->ceilingxoffset = s->ceilingxoffset;
 					tempsec->ceilingyoffset = s->ceilingyoffset;
+					tempsec->ceilingxscale = s->ceilingxscale;
+					tempsec->ceilingyscale = s->ceilingyscale;
 					tempsec->ceilingangle = s->ceilingangle;
 				}
 			}
@@ -317,6 +323,8 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 			tempsec->floorpic = tempsec->ceilingpic = s->ceilingpic;
 			tempsec->floorxoffset = tempsec->ceilingxoffset = s->ceilingxoffset;
 			tempsec->flooryoffset = tempsec->ceilingyoffset = s->ceilingyoffset;
+			tempsec->floorxscale = tempsec->ceilingxscale = s->ceilingxscale;
+			tempsec->flooryscale = tempsec->ceilingyscale = s->ceilingyscale;
 			tempsec->floorangle = tempsec->ceilingangle = s->ceilingangle;
 
 			if (s->floorpic == skyflatnum) // SKYFIX?
@@ -325,6 +333,8 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 				tempsec->floorpic = tempsec->ceilingpic;
 				tempsec->floorxoffset = tempsec->ceilingxoffset;
 				tempsec->flooryoffset = tempsec->ceilingyoffset;
+				tempsec->floorxscale = tempsec->ceilingxscale;
+				tempsec->flooryscale = tempsec->ceilingyscale;
 				tempsec->floorangle = tempsec->ceilingangle;
 			}
 			else
@@ -333,6 +343,8 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 				tempsec->floorpic = s->floorpic;
 				tempsec->floorxoffset = s->floorxoffset;
 				tempsec->flooryoffset = s->flooryoffset;
+				tempsec->floorxscale = s->floorxscale;
+				tempsec->flooryscale = s->flooryscale;
 				tempsec->floorangle = s->floorangle;
 			}
 
@@ -362,12 +374,16 @@ boolean R_IsEmptyLine(seg_t *line, sector_t *front, sector_t *back)
 		&& back->c_slope == front->c_slope
 		&& back->lightlevel == front->lightlevel
 		&& !line->sidedef->midtexture
-		// Check offsets too!
+		// Check offsets and scale too!
 		&& back->floorxoffset == front->floorxoffset
 		&& back->flooryoffset == front->flooryoffset
+		&& back->floorxscale == front->floorxscale
+		&& back->flooryscale == front->flooryscale
 		&& back->floorangle == front->floorangle
 		&& back->ceilingxoffset == front->ceilingxoffset
 		&& back->ceilingyoffset == front->ceilingyoffset
+		&& back->ceilingxscale == front->ceilingxscale
+		&& back->ceilingyscale == front->ceilingyscale
 		&& back->ceilingangle == front->ceilingangle
 		// Consider altered lighting.
 		&& back->floorlightlevel == front->floorlightlevel
@@ -909,7 +925,9 @@ static void R_Subsector(size_t num)
 		|| (frontsector->heightsec != -1 && sectors[frontsector->heightsec].ceilingpic == skyflatnum))
 	{
 		floorplane = R_FindPlane(frontsector->floorheight, frontsector->floorpic, floorlightlevel,
-			frontsector->floorxoffset, frontsector->flooryoffset, frontsector->floorangle, floorcolormap, NULL, NULL, frontsector->f_slope);
+			frontsector->floorxoffset, frontsector->flooryoffset,
+			frontsector->floorxscale, frontsector->flooryscale, frontsector->floorangle,
+			floorcolormap, NULL, NULL, frontsector->f_slope);
 	}
 	else
 		floorplane = NULL;
@@ -918,8 +936,9 @@ static void R_Subsector(size_t num)
 		|| frontsector->ceilingpic == skyflatnum
 		|| (frontsector->heightsec != -1 && sectors[frontsector->heightsec].floorpic == skyflatnum))
 	{
-		ceilingplane = R_FindPlane(frontsector->ceilingheight, frontsector->ceilingpic,
-			ceilinglightlevel, frontsector->ceilingxoffset, frontsector->ceilingyoffset, frontsector->ceilingangle,
+		ceilingplane = R_FindPlane(frontsector->ceilingheight, frontsector->ceilingpic, ceilinglightlevel,
+			frontsector->ceilingxoffset, frontsector->ceilingyoffset,
+			frontsector->ceilingxscale, frontsector->ceilingyscale, frontsector->ceilingangle,
 			ceilingcolormap, NULL, NULL, frontsector->c_slope);
 	}
 	else
@@ -962,8 +981,9 @@ static void R_Subsector(size_t num)
 					viewz < heightcheck);
 
 				ffloor[numffloors].plane = R_FindPlane(*rover->bottomheight, *rover->bottompic,
-					*frontsector->lightlist[light].lightlevel, *rover->bottomxoffs,
-					*rover->bottomyoffs, *rover->bottomangle, *frontsector->lightlist[light].extra_colormap, rover, NULL, *rover->b_slope);
+					*frontsector->lightlist[light].lightlevel, *rover->bottomxoffs, *rover->bottomyoffs,
+					*rover->bottomxscale, *rover->bottomyscale, *rover->bottomangle,
+					*frontsector->lightlist[light].extra_colormap, rover, NULL, *rover->b_slope);
 
 				ffloor[numffloors].slope = *rover->b_slope;
 
@@ -991,7 +1011,8 @@ static void R_Subsector(size_t num)
 				light = R_GetPlaneLight(frontsector, planecenterz, viewz < heightcheck);
 
 				ffloor[numffloors].plane = R_FindPlane(*rover->topheight, *rover->toppic,
-					*frontsector->lightlist[light].lightlevel, *rover->topxoffs, *rover->topyoffs, *rover->topangle,
+					*frontsector->lightlist[light].lightlevel, *rover->topxoffs, *rover->topyoffs,
+					*rover->topxscale, *rover->topyscale, *rover->topangle,
 					*frontsector->lightlist[light].extra_colormap, rover, NULL, *rover->t_slope);
 
 				ffloor[numffloors].slope = *rover->t_slope;
@@ -1033,7 +1054,9 @@ static void R_Subsector(size_t num)
 			{
 				light = R_GetPlaneLight(frontsector, polysec->floorheight, viewz < polysec->floorheight);
 				ffloor[numffloors].plane = R_FindPlane(polysec->floorheight, polysec->floorpic,
-					(light == -1 ? frontsector->lightlevel : *frontsector->lightlist[light].lightlevel), polysec->floorxoffset, polysec->flooryoffset,
+					(light == -1 ? frontsector->lightlevel : *frontsector->lightlist[light].lightlevel),
+					polysec->floorxoffset, polysec->flooryoffset,
+					polysec->floorxscale, polysec->flooryscale,
 					polysec->floorangle-po->angle,
 					(light == -1 ? frontsector->extra_colormap : *frontsector->lightlist[light].extra_colormap), NULL, po,
 					NULL); // will ffloors be slopable eventually?
@@ -1057,7 +1080,10 @@ static void R_Subsector(size_t num)
 			{
 				light = R_GetPlaneLight(frontsector, polysec->floorheight, viewz < polysec->floorheight);
 				ffloor[numffloors].plane = R_FindPlane(polysec->ceilingheight, polysec->ceilingpic,
-					(light == -1 ? frontsector->lightlevel : *frontsector->lightlist[light].lightlevel), polysec->ceilingxoffset, polysec->ceilingyoffset, polysec->ceilingangle-po->angle,
+					(light == -1 ? frontsector->lightlevel : *frontsector->lightlist[light].lightlevel),
+					polysec->ceilingxoffset, polysec->ceilingyoffset,
+					polysec->ceilingxscale, polysec->ceilingyscale,
+					polysec->ceilingangle-po->angle,
 					(light == -1 ? frontsector->extra_colormap : *frontsector->lightlist[light].extra_colormap), NULL, po,
 					NULL); // will ffloors be slopable eventually?
 
diff --git a/src/r_defs.h b/src/r_defs.h
index 4cb8c1769699396b84f64a3c68adc95b54e94f60..bce045ab47d910513ef98313e6b4ed24b36026ef 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -218,12 +218,16 @@ typedef struct ffloor_s
 	INT16 *toplightlevel;
 	fixed_t *topxoffs;
 	fixed_t *topyoffs;
+	fixed_t *topxscale;
+	fixed_t *topyscale;
 	angle_t *topangle;
 
 	fixed_t *bottomheight;
 	INT32 *bottompic;
 	fixed_t *bottomxoffs;
 	fixed_t *bottomyoffs;
+	fixed_t *bottomxscale;
+	fixed_t *bottomyscale;
 	angle_t *bottomangle;
 
 	// Pointers to pointers. Yup.
@@ -429,6 +433,10 @@ typedef struct sector_s
 	fixed_t floorxoffset, flooryoffset;
 	fixed_t ceilingxoffset, ceilingyoffset;
 
+	// floor and ceiling texture scale
+	fixed_t floorxscale, flooryscale;
+	fixed_t ceilingxscale, ceilingyscale;
+
 	// flat angle
 	angle_t floorangle;
 	angle_t ceilingangle;
@@ -564,8 +572,11 @@ typedef struct
 	fixed_t rowoffset;
 
 	// per-texture offsets for UDMF
-	fixed_t offsetx_top, offsetx_mid, offsetx_bot;
-	fixed_t offsety_top, offsety_mid, offsety_bot;
+	fixed_t offsetx_top, offsetx_mid, offsetx_bottom;
+	fixed_t offsety_top, offsety_mid, offsety_bottom;
+
+	fixed_t scalex_top, scalex_mid, scalex_bottom;
+	fixed_t scaley_top, scaley_mid, scaley_bottom;
 
 	// Texture indices.
 	// We do not maintain names here.
@@ -759,10 +770,13 @@ typedef struct drawseg_s
 	fixed_t bsilheight; // do not clip sprites above this
 	fixed_t tsilheight; // do not clip sprites below this
 
+	fixed_t offsetx;
+
 	// Pointers to lists for sprite clipping, all three adjusted so [x1] is first value.
 	INT16 *sprtopclip;
 	INT16 *sprbottomclip;
 	fixed_t *maskedtexturecol;
+	fixed_t *invscale;
 
 	struct visplane_s *ffloorplanes[MAXFFLOORS];
 	INT32 numffloorplanes;
diff --git a/src/r_draw.c b/src/r_draw.c
index 098aae445abcb257e9b10468318effd6eebc8790..6e7efd004e656121e895ae4169cc5842b8b01932 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -106,14 +106,13 @@ fixed_t ds_xfrac, ds_yfrac, ds_xstep, ds_ystep;
 INT32 ds_waterofs, ds_bgofs;
 
 UINT16 ds_flatwidth, ds_flatheight;
-boolean ds_powersoftwo, ds_solidcolor;
+boolean ds_powersoftwo, ds_solidcolor, ds_fog;
 
 UINT8 *ds_source; // points to the start of a flat
 UINT8 *ds_transmap; // one of the translucency tables
 
 // Vectors for Software's tilted slope drawers
-floatv3_t *ds_su, *ds_sv, *ds_sz;
-floatv3_t *ds_sup, *ds_svp, *ds_szp;
+floatv3_t ds_su, ds_sv, ds_sz, ds_slopelight;
 float focallengthf, zeroheight;
 
 /**	\brief Variable flat sizes
@@ -930,13 +929,15 @@ static void R_CalcTiltedLighting(fixed_t start, fixed_t end)
 	}
 }
 
+#define PLANELIGHTFLOAT (BASEVIDWIDTH * BASEVIDWIDTH / vid.width / zeroheight / 21.0f * FIXED_TO_FLOAT(fovtan))
+
 // Lighting is simple. It's just linear interpolation from start to end
-#define CALC_SLOPE_LIGHT { \
-	float planelightfloat = PLANELIGHTFLOAT; \
-	float lightstart, lightend; \
-	lightend = (iz + ds_szp->x*width) * planelightfloat; \
-	lightstart = iz * planelightfloat; \
-	R_CalcTiltedLighting(FloatToFixed(lightstart), FloatToFixed(lightend)); \
+static void R_CalcSlopeLight(void)
+{
+	float iz = ds_slopelight.z + ds_slopelight.y * (centery - ds_y) + ds_slopelight.x * (ds_x1 - centerx);
+	float lightstart = iz * PLANELIGHTFLOAT;
+	float lightend = (iz + ds_slopelight.x * (ds_x2 - ds_x1)) * PLANELIGHTFLOAT;
+	R_CalcTiltedLighting(FloatToFixed(lightstart), FloatToFixed(lightend));
 }
 
 // ==========================================================================
diff --git a/src/r_draw.h b/src/r_draw.h
index 21d7e8e4ce2e84cfd8827a62a1e25f6a586a1908..9cde3cf54f4421233ba517996ceee173067a42fe 100644
--- a/src/r_draw.h
+++ b/src/r_draw.h
@@ -61,7 +61,7 @@ extern fixed_t ds_xfrac, ds_yfrac, ds_xstep, ds_ystep;
 extern INT32 ds_waterofs, ds_bgofs;
 
 extern UINT16 ds_flatwidth, ds_flatheight;
-extern boolean ds_powersoftwo, ds_solidcolor;
+extern boolean ds_powersoftwo, ds_solidcolor, ds_fog;
 
 extern UINT8 *ds_source;
 extern UINT8 *ds_transmap;
@@ -71,8 +71,7 @@ typedef struct {
 } floatv3_t;
 
 // Vectors for Software's tilted slope drawers
-extern floatv3_t *ds_su, *ds_sv, *ds_sz;
-extern floatv3_t *ds_sup, *ds_svp, *ds_szp;
+extern floatv3_t ds_su, ds_sv, ds_sz, ds_slopelight;
 extern float focallengthf, zeroheight;
 
 // Variable flat sizes
@@ -178,8 +177,6 @@ void R_Draw2sMultiPatchTranslucentColumn_8(void);
 void R_DrawFogColumn_8(void);
 void R_DrawColumnShadowed_8(void);
 
-#define PLANELIGHTFLOAT (BASEVIDWIDTH * BASEVIDWIDTH / vid.width / zeroheight / 21.0f * FIXED_TO_FLOAT(fovtan))
-
 void R_DrawSpan_8(void);
 void R_DrawTranslucentSpan_8(void);
 void R_DrawTiltedSpan_8(void);
diff --git a/src/r_draw8.c b/src/r_draw8.c
index b80a47984fba4c8914fc1e75c91631aaf37ea3ea..fe7d321dfcf1a1a1c0aa6aabc8ba5169c445acdf 100644
--- a/src/r_draw8.c
+++ b/src/r_draw8.c
@@ -676,12 +676,11 @@ void R_DrawTiltedSpan_8(void)
 	double endz, endu, endv;
 	UINT32 stepu, stepv;
 
-	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
+	iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx);
+	uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx);
+	vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx);
 
-	CALC_SLOPE_LIGHT
-
-	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
-	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
+	R_CalcSlopeLight();
 
 	dest = ylookup[ds_y] + columnofs[ds_x1];
 	source = ds_source;
@@ -700,18 +699,18 @@ void R_DrawTiltedSpan_8(void)
 
 		*dest = colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]];
 		dest++;
-		iz += ds_szp->x;
-		uz += ds_sup->x;
-		vz += ds_svp->x;
+		iz += ds_sz.x;
+		uz += ds_su.x;
+		vz += ds_sv.x;
 	} while (--width >= 0);
 #else
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
 
-	izstep = ds_szp->x * SPANSIZE;
-	uzstep = ds_sup->x * SPANSIZE;
-	vzstep = ds_svp->x * SPANSIZE;
+	izstep = ds_sz.x * SPANSIZE;
+	uzstep = ds_su.x * SPANSIZE;
+	vzstep = ds_sv.x * SPANSIZE;
 	//x1 = 0;
 	width++;
 
@@ -753,9 +752,9 @@ void R_DrawTiltedSpan_8(void)
 		else
 		{
 			double left = width;
-			iz += ds_szp->x * left;
-			uz += ds_sup->x * left;
-			vz += ds_svp->x * left;
+			iz += ds_sz.x * left;
+			uz += ds_su.x * left;
+			vz += ds_sv.x * left;
 
 			endz = 1.f/iz;
 			endu = uz*endz;
@@ -799,12 +798,11 @@ void R_DrawTiltedTranslucentSpan_8(void)
 	double endz, endu, endv;
 	UINT32 stepu, stepv;
 
-	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
-
-	CALC_SLOPE_LIGHT
+	iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx);
+	uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx);
+	vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx);
 
-	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
-	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
+	R_CalcSlopeLight();
 
 	dest = ylookup[ds_y] + columnofs[ds_x1];
 	source = ds_source;
@@ -822,18 +820,18 @@ void R_DrawTiltedTranslucentSpan_8(void)
 		colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
 		*dest = *(ds_transmap + (colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]] << 8) + *dest);
 		dest++;
-		iz += ds_szp->x;
-		uz += ds_sup->x;
-		vz += ds_svp->x;
+		iz += ds_sz.x;
+		uz += ds_su.x;
+		vz += ds_sv.x;
 	} while (--width >= 0);
 #else
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
 
-	izstep = ds_szp->x * SPANSIZE;
-	uzstep = ds_sup->x * SPANSIZE;
-	vzstep = ds_svp->x * SPANSIZE;
+	izstep = ds_sz.x * SPANSIZE;
+	uzstep = ds_su.x * SPANSIZE;
+	vzstep = ds_sv.x * SPANSIZE;
 	//x1 = 0;
 	width++;
 
@@ -875,9 +873,9 @@ void R_DrawTiltedTranslucentSpan_8(void)
 		else
 		{
 			double left = width;
-			iz += ds_szp->x * left;
-			uz += ds_sup->x * left;
-			vz += ds_svp->x * left;
+			iz += ds_sz.x * left;
+			uz += ds_su.x * left;
+			vz += ds_sv.x * left;
 
 			endz = 1.f/iz;
 			endu = uz*endz;
@@ -922,12 +920,11 @@ void R_DrawTiltedWaterSpan_8(void)
 	double endz, endu, endv;
 	UINT32 stepu, stepv;
 
-	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
+	iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx);
+	uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx);
+	vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx);
 
-	CALC_SLOPE_LIGHT
-
-	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
-	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
+	R_CalcSlopeLight();
 
 	dest = ylookup[ds_y] + columnofs[ds_x1];
 	dsrc = screens[1] + (ds_y+ds_bgofs)*vid.width + ds_x1;
@@ -946,18 +943,18 @@ void R_DrawTiltedWaterSpan_8(void)
 		colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
 		*dest = *(ds_transmap + (colormap[source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)]] << 8) + *dsrc++);
 		dest++;
-		iz += ds_szp->x;
-		uz += ds_sup->x;
-		vz += ds_svp->x;
+		iz += ds_sz.x;
+		uz += ds_su.x;
+		vz += ds_sv.x;
 	} while (--width >= 0);
 #else
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
 
-	izstep = ds_szp->x * SPANSIZE;
-	uzstep = ds_sup->x * SPANSIZE;
-	vzstep = ds_svp->x * SPANSIZE;
+	izstep = ds_sz.x * SPANSIZE;
+	uzstep = ds_su.x * SPANSIZE;
+	vzstep = ds_sv.x * SPANSIZE;
 	//x1 = 0;
 	width++;
 
@@ -999,9 +996,9 @@ void R_DrawTiltedWaterSpan_8(void)
 		else
 		{
 			double left = width;
-			iz += ds_szp->x * left;
-			uz += ds_sup->x * left;
-			vz += ds_svp->x * left;
+			iz += ds_sz.x * left;
+			uz += ds_su.x * left;
+			vz += ds_sv.x * left;
 
 			endz = 1.f/iz;
 			endu = uz*endz;
@@ -1044,12 +1041,11 @@ void R_DrawTiltedSplat_8(void)
 	double endz, endu, endv;
 	UINT32 stepu, stepv;
 
-	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
-
-	CALC_SLOPE_LIGHT
+	iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx);
+	uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx);
+	vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx);
 
-	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
-	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
+	R_CalcSlopeLight();
 
 	dest = ylookup[ds_y] + columnofs[ds_x1];
 	source = ds_source;
@@ -1071,18 +1067,18 @@ void R_DrawTiltedSplat_8(void)
 			*dest = colormap[val];
 
 		dest++;
-		iz += ds_szp->x;
-		uz += ds_sup->x;
-		vz += ds_svp->x;
+		iz += ds_sz.x;
+		uz += ds_su.x;
+		vz += ds_sv.x;
 	} while (--width >= 0);
 #else
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
 
-	izstep = ds_szp->x * SPANSIZE;
-	uzstep = ds_sup->x * SPANSIZE;
-	vzstep = ds_svp->x * SPANSIZE;
+	izstep = ds_sz.x * SPANSIZE;
+	uzstep = ds_su.x * SPANSIZE;
+	vzstep = ds_sv.x * SPANSIZE;
 	//x1 = 0;
 	width++;
 
@@ -1128,9 +1124,9 @@ void R_DrawTiltedSplat_8(void)
 		else
 		{
 			double left = width;
-			iz += ds_szp->x * left;
-			uz += ds_sup->x * left;
-			vz += ds_svp->x * left;
+			iz += ds_sz.x * left;
+			uz += ds_su.x * left;
+			vz += ds_sv.x * left;
 
 			endz = 1.f/iz;
 			endu = uz*endz;
@@ -1613,9 +1609,9 @@ void R_DrawTiltedFloorSprite_8(void)
 	double endz, endu, endv;
 	UINT32 stepu, stepv;
 
-	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
-	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
-	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
+	iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx);
+	uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx);
+	vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx);
 
 	dest = ylookup[ds_y] + columnofs[ds_x1];
 	source = (UINT16 *)ds_source;
@@ -1626,9 +1622,9 @@ void R_DrawTiltedFloorSprite_8(void)
 	startu = uz*startz;
 	startv = vz*startz;
 
-	izstep = ds_szp->x * SPANSIZE;
-	uzstep = ds_sup->x * SPANSIZE;
-	vzstep = ds_svp->x * SPANSIZE;
+	izstep = ds_sz.x * SPANSIZE;
+	uzstep = ds_su.x * SPANSIZE;
+	vzstep = ds_sv.x * SPANSIZE;
 	//x1 = 0;
 	width++;
 
@@ -1673,9 +1669,9 @@ void R_DrawTiltedFloorSprite_8(void)
 		else
 		{
 			double left = width;
-			iz += ds_szp->x * left;
-			uz += ds_sup->x * left;
-			vz += ds_svp->x * left;
+			iz += ds_sz.x * left;
+			uz += ds_su.x * left;
+			vz += ds_sv.x * left;
 
 			endz = 1.f/iz;
 			endu = uz*endz;
@@ -1722,9 +1718,9 @@ void R_DrawTiltedTranslucentFloorSprite_8(void)
 	double endz, endu, endv;
 	UINT32 stepu, stepv;
 
-	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
-	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
-	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
+	iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx);
+	uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx);
+	vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx);
 
 	dest = ylookup[ds_y] + columnofs[ds_x1];
 	source = (UINT16 *)ds_source;
@@ -1735,9 +1731,9 @@ void R_DrawTiltedTranslucentFloorSprite_8(void)
 	startu = uz*startz;
 	startv = vz*startz;
 
-	izstep = ds_szp->x * SPANSIZE;
-	uzstep = ds_sup->x * SPANSIZE;
-	vzstep = ds_svp->x * SPANSIZE;
+	izstep = ds_sz.x * SPANSIZE;
+	uzstep = ds_su.x * SPANSIZE;
+	vzstep = ds_sv.x * SPANSIZE;
 	//x1 = 0;
 	width++;
 
@@ -1782,9 +1778,9 @@ void R_DrawTiltedTranslucentFloorSprite_8(void)
 		else
 		{
 			double left = width;
-			iz += ds_szp->x * left;
-			uz += ds_sup->x * left;
-			vz += ds_svp->x * left;
+			iz += ds_sz.x * left;
+			uz += ds_su.x * left;
+			vz += ds_sv.x * left;
 
 			endz = 1.f/iz;
 			endu = uz*endz;
@@ -2013,9 +2009,7 @@ void R_DrawTiltedFogSpan_8(void)
 
 	UINT8 *dest = ylookup[ds_y] + columnofs[ds_x1];
 
-	double iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
-
-	CALC_SLOPE_LIGHT
+	R_CalcSlopeLight();
 
 	do
 	{
@@ -2067,9 +2061,7 @@ void R_DrawTiltedSolidColorSpan_8(void)
 	UINT8 source = ds_source[0];
 	UINT8 *dest = ylookup[ds_y] + columnofs[ds_x1];
 
-	double iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
-
-	CALC_SLOPE_LIGHT
+	R_CalcSlopeLight();
 
 	do
 	{
@@ -2088,9 +2080,7 @@ void R_DrawTiltedTransSolidColorSpan_8(void)
 	UINT8 source = ds_source[0];
 	UINT8 *dest = ylookup[ds_y] + columnofs[ds_x1];
 
-	double iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
-
-	CALC_SLOPE_LIGHT
+	R_CalcSlopeLight();
 
 	do
 	{
@@ -2131,9 +2121,7 @@ void R_DrawTiltedWaterSolidColorSpan_8(void)
 	UINT8 *dest = ylookup[ds_y] + columnofs[ds_x1];
 	UINT8 *dsrc = screens[1] + (ds_y+ds_bgofs)*vid.width + ds_x1;
 
-	double iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
-
-	CALC_SLOPE_LIGHT
+	R_CalcSlopeLight();
 
 	do
 	{
diff --git a/src/r_draw8_npo2.c b/src/r_draw8_npo2.c
index 91f3b06c4270ba28901d35b561f68a002ddbc6f3..78cde8a2ce195678333916f38a1b51bf5ba8e898 100644
--- a/src/r_draw8_npo2.c
+++ b/src/r_draw8_npo2.c
@@ -114,12 +114,11 @@ void R_DrawTiltedSpan_NPO2_8(void)
 	struct libdivide_u32_t x_divider = libdivide_u32_gen(ds_flatwidth);
 	struct libdivide_u32_t y_divider = libdivide_u32_gen(ds_flatheight);
 
-	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
+	iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx);
+	uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx);
+	vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx);
 
-	CALC_SLOPE_LIGHT
-
-	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
-	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
+	R_CalcSlopeLight();
 
 	dest = ylookup[ds_y] + columnofs[ds_x1];
 	source = ds_source;
@@ -154,18 +153,18 @@ void R_DrawTiltedSpan_NPO2_8(void)
 			*dest = colormap[source[((y * ds_flatwidth) + x)]];
 		}
 		dest++;
-		iz += ds_szp->x;
-		uz += ds_sup->x;
-		vz += ds_svp->x;
+		iz += ds_sz.x;
+		uz += ds_su.x;
+		vz += ds_sv.x;
 	} while (--width >= 0);
 #else
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
 
-	izstep = ds_szp->x * SPANSIZE;
-	uzstep = ds_sup->x * SPANSIZE;
-	vzstep = ds_svp->x * SPANSIZE;
+	izstep = ds_sz.x * SPANSIZE;
+	uzstep = ds_su.x * SPANSIZE;
+	vzstep = ds_sv.x * SPANSIZE;
 	//x1 = 0;
 	width++;
 
@@ -239,9 +238,9 @@ void R_DrawTiltedSpan_NPO2_8(void)
 		else
 		{
 			double left = width;
-			iz += ds_szp->x * left;
-			uz += ds_sup->x * left;
-			vz += ds_svp->x * left;
+			iz += ds_sz.x * left;
+			uz += ds_su.x * left;
+			vz += ds_sv.x * left;
 
 			endz = 1.f/iz;
 			endu = uz*endz;
@@ -304,12 +303,11 @@ void R_DrawTiltedTranslucentSpan_NPO2_8(void)
 	struct libdivide_u32_t x_divider = libdivide_u32_gen(ds_flatwidth);
 	struct libdivide_u32_t y_divider = libdivide_u32_gen(ds_flatheight);
 
-	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
-
-	CALC_SLOPE_LIGHT
+	iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx);
+	uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx);
+	vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx);
 
-	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
-	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
+	R_CalcSlopeLight();
 
 	dest = ylookup[ds_y] + columnofs[ds_x1];
 	source = ds_source;
@@ -343,18 +341,18 @@ void R_DrawTiltedTranslucentSpan_NPO2_8(void)
 			*dest = *(ds_transmap + (colormap[source[((y * ds_flatwidth) + x)]] << 8) + *dest);
 		}
 		dest++;
-		iz += ds_szp->x;
-		uz += ds_sup->x;
-		vz += ds_svp->x;
+		iz += ds_sz.x;
+		uz += ds_su.x;
+		vz += ds_sv.x;
 	} while (--width >= 0);
 #else
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
 
-	izstep = ds_szp->x * SPANSIZE;
-	uzstep = ds_sup->x * SPANSIZE;
-	vzstep = ds_svp->x * SPANSIZE;
+	izstep = ds_sz.x * SPANSIZE;
+	uzstep = ds_su.x * SPANSIZE;
+	vzstep = ds_sv.x * SPANSIZE;
 	//x1 = 0;
 	width++;
 
@@ -428,9 +426,9 @@ void R_DrawTiltedTranslucentSpan_NPO2_8(void)
 		else
 		{
 			double left = width;
-			iz += ds_szp->x * left;
-			uz += ds_sup->x * left;
-			vz += ds_svp->x * left;
+			iz += ds_sz.x * left;
+			uz += ds_su.x * left;
+			vz += ds_sv.x * left;
 
 			endz = 1.f/iz;
 			endu = uz*endz;
@@ -492,12 +490,11 @@ void R_DrawTiltedSplat_NPO2_8(void)
 	struct libdivide_u32_t x_divider = libdivide_u32_gen(ds_flatwidth);
 	struct libdivide_u32_t y_divider = libdivide_u32_gen(ds_flatheight);
 
-	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
+	iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx);
+	uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx);
+	vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx);
 
-	CALC_SLOPE_LIGHT
-
-	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
-	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
+	R_CalcSlopeLight();
 
 	dest = ylookup[ds_y] + columnofs[ds_x1];
 	source = ds_source;
@@ -536,18 +533,18 @@ void R_DrawTiltedSplat_NPO2_8(void)
 			*dest = colormap[val];
 
 		dest++;
-		iz += ds_szp->x;
-		uz += ds_sup->x;
-		vz += ds_svp->x;
+		iz += ds_sz.x;
+		uz += ds_su.x;
+		vz += ds_sv.x;
 	} while (--width >= 0);
 #else
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
 
-	izstep = ds_szp->x * SPANSIZE;
-	uzstep = ds_sup->x * SPANSIZE;
-	vzstep = ds_svp->x * SPANSIZE;
+	izstep = ds_sz.x * SPANSIZE;
+	uzstep = ds_su.x * SPANSIZE;
+	vzstep = ds_sv.x * SPANSIZE;
 	//x1 = 0;
 	width++;
 
@@ -625,9 +622,9 @@ void R_DrawTiltedSplat_NPO2_8(void)
 		else
 		{
 			double left = width;
-			iz += ds_szp->x * left;
-			uz += ds_sup->x * left;
-			vz += ds_svp->x * left;
+			iz += ds_sz.x * left;
+			uz += ds_su.x * left;
+			vz += ds_sv.x * left;
 
 			endz = 1.f/iz;
 			endu = uz*endz;
@@ -970,9 +967,9 @@ void R_DrawTiltedFloorSprite_NPO2_8(void)
 	struct libdivide_u32_t x_divider = libdivide_u32_gen(ds_flatwidth);
 	struct libdivide_u32_t y_divider = libdivide_u32_gen(ds_flatheight);
 
-	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
-	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
-	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
+	iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx);
+	uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx);
+	vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx);
 
 	dest = ylookup[ds_y] + columnofs[ds_x1];
 	source = (UINT16 *)ds_source;
@@ -983,9 +980,9 @@ void R_DrawTiltedFloorSprite_NPO2_8(void)
 	startu = uz*startz;
 	startv = vz*startz;
 
-	izstep = ds_szp->x * SPANSIZE;
-	uzstep = ds_sup->x * SPANSIZE;
-	vzstep = ds_svp->x * SPANSIZE;
+	izstep = ds_sz.x * SPANSIZE;
+	uzstep = ds_su.x * SPANSIZE;
+	vzstep = ds_sv.x * SPANSIZE;
 	//x1 = 0;
 	width++;
 
@@ -1060,9 +1057,9 @@ void R_DrawTiltedFloorSprite_NPO2_8(void)
 		else
 		{
 			double left = width;
-			iz += ds_szp->x * left;
-			uz += ds_sup->x * left;
-			vz += ds_svp->x * left;
+			iz += ds_sz.x * left;
+			uz += ds_su.x * left;
+			vz += ds_sv.x * left;
 
 			endz = 1.f/iz;
 			endu = uz*endz;
@@ -1126,9 +1123,9 @@ void R_DrawTiltedTranslucentFloorSprite_NPO2_8(void)
 	struct libdivide_u32_t x_divider = libdivide_u32_gen(ds_flatwidth);
 	struct libdivide_u32_t y_divider = libdivide_u32_gen(ds_flatheight);
 
-	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
-	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
-	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
+	iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx);
+	uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx);
+	vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx);
 
 	dest = ylookup[ds_y] + columnofs[ds_x1];
 	source = (UINT16 *)ds_source;
@@ -1139,9 +1136,9 @@ void R_DrawTiltedTranslucentFloorSprite_NPO2_8(void)
 	startu = uz*startz;
 	startv = vz*startz;
 
-	izstep = ds_szp->x * SPANSIZE;
-	uzstep = ds_sup->x * SPANSIZE;
-	vzstep = ds_svp->x * SPANSIZE;
+	izstep = ds_sz.x * SPANSIZE;
+	uzstep = ds_su.x * SPANSIZE;
+	vzstep = ds_sv.x * SPANSIZE;
 	//x1 = 0;
 	width++;
 
@@ -1216,9 +1213,9 @@ void R_DrawTiltedTranslucentFloorSprite_NPO2_8(void)
 		else
 		{
 			double left = width;
-			iz += ds_szp->x * left;
-			uz += ds_sup->x * left;
-			vz += ds_svp->x * left;
+			iz += ds_sz.x * left;
+			uz += ds_su.x * left;
+			vz += ds_sv.x * left;
 
 			endz = 1.f/iz;
 			endu = uz*endz;
@@ -1411,12 +1408,11 @@ void R_DrawTiltedWaterSpan_NPO2_8(void)
 	struct libdivide_u32_t x_divider = libdivide_u32_gen(ds_flatwidth);
 	struct libdivide_u32_t y_divider = libdivide_u32_gen(ds_flatheight);
 
-	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
-
-	CALC_SLOPE_LIGHT
+	iz = ds_sz.z + ds_sz.y*(centery-ds_y) + ds_sz.x*(ds_x1-centerx);
+	uz = ds_su.z + ds_su.y*(centery-ds_y) + ds_su.x*(ds_x1-centerx);
+	vz = ds_sv.z + ds_sv.y*(centery-ds_y) + ds_sv.x*(ds_x1-centerx);
 
-	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
-	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
+	R_CalcSlopeLight();
 
 	dest = ylookup[ds_y] + columnofs[ds_x1];
 	dsrc = screens[1] + (ds_y+ds_bgofs)*vid.width + ds_x1;
@@ -1451,18 +1447,18 @@ void R_DrawTiltedWaterSpan_NPO2_8(void)
 			*dest = *(ds_transmap + (colormap[source[((y * ds_flatwidth) + x)]] << 8) + *dsrc++);
 		}
 		dest++;
-		iz += ds_szp->x;
-		uz += ds_sup->x;
-		vz += ds_svp->x;
+		iz += ds_sz.x;
+		uz += ds_su.x;
+		vz += ds_sv.x;
 	} while (--width >= 0);
 #else
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
 
-	izstep = ds_szp->x * SPANSIZE;
-	uzstep = ds_sup->x * SPANSIZE;
-	vzstep = ds_svp->x * SPANSIZE;
+	izstep = ds_sz.x * SPANSIZE;
+	uzstep = ds_su.x * SPANSIZE;
+	vzstep = ds_sv.x * SPANSIZE;
 	//x1 = 0;
 	width++;
 
@@ -1536,9 +1532,9 @@ void R_DrawTiltedWaterSpan_NPO2_8(void)
 		else
 		{
 			double left = width;
-			iz += ds_szp->x * left;
-			uz += ds_sup->x * left;
-			vz += ds_svp->x * left;
+			iz += ds_sz.x * left;
+			uz += ds_su.x * left;
+			vz += ds_sv.x * left;
 
 			endz = 1.f/iz;
 			endu = uz*endz;
diff --git a/src/r_main.c b/src/r_main.c
index 0cfccab8c2fbcbd7cdc35c678c379cdfb942cbcc..125f80b7849cad91786cc394f1da8a958ad4f8cc 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -356,7 +356,7 @@ angle_t R_PointToAngle2(fixed_t pviewx, fixed_t pviewy, fixed_t x, fixed_t y)
 fixed_t R_PointToDist2(fixed_t px2, fixed_t py2, fixed_t px1, fixed_t py1)
 {
 	angle_t angle;
-	fixed_t dx, dy, dist;
+	ufixed_t dx, dy, dist;
 
 	dx = abs(px1 - px2);
 	dy = abs(py1 - py2);
@@ -957,16 +957,6 @@ void R_ExecuteSetViewSize(void)
 			dy = FixedMul(abs(dy), fovtan);
 			yslopetab[i] = FixedDiv(centerx*FRACUNIT, dy);
 		}
-
-		if (ds_su)
-			Z_Free(ds_su);
-		if (ds_sv)
-			Z_Free(ds_sv);
-		if (ds_sz)
-			Z_Free(ds_sz);
-
-		ds_su = ds_sv = ds_sz = NULL;
-		ds_sup = ds_svp = ds_szp = NULL;
 	}
 
 	memset(scalelight, 0xFF, sizeof(scalelight));
@@ -1012,9 +1002,6 @@ void R_Init(void)
 	R_InitViewBorder();
 	R_SetViewSize(); // setsizeneeded is set true
 
-	//I_OutputMsg("\nR_InitPlanes");
-	R_InitPlanes();
-
 	// this is now done by SCR_Recalc() at the first mode set
 	//I_OutputMsg("\nR_InitLightTables");
 	R_InitLightTables();
@@ -1027,6 +1014,34 @@ void R_Init(void)
 	framecount = 0;
 }
 
+//
+// R_IsPointInSector
+//
+boolean R_IsPointInSector(sector_t *sector, fixed_t x, fixed_t y)
+{
+	size_t i;
+	line_t *closest = NULL;
+	fixed_t closestdist = INT32_MAX;
+
+	for (i = 0; i < sector->linecount; i++)
+	{
+		vertex_t v;
+		fixed_t dist;
+
+		// find the line closest to the point we're looking for.
+		P_ClosestPointOnLine(x, y, sector->lines[i], &v);
+		dist = R_PointToDist2(0, 0, v.x - x, v.y - y);
+		if (dist < closestdist)
+		{
+			closest = sector->lines[i];
+			closestdist = dist;
+		}
+	}
+
+	// if the side of the closest line is in this sector, we're inside of it.
+	return P_PointOnLineSide(x, y, closest) == 0 ? closest->frontsector == sector : closest->backsector == sector;
+}
+
 //
 // R_PointInSubsector
 //
diff --git a/src/r_main.h b/src/r_main.h
index a6fb42ba2410ba42c8adc7ba84ef8fde21e0802f..02c640b51f55e36f219156d2957faec14c2bb333 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -79,6 +79,7 @@ fixed_t R_PointToDist(fixed_t x, fixed_t y);
 fixed_t R_PointToDist2(fixed_t px2, fixed_t py2, fixed_t px1, fixed_t py1);
 
 fixed_t R_ScaleFromGlobalAngle(angle_t visangle);
+boolean R_IsPointInSector(sector_t *sector, fixed_t x, fixed_t y);
 subsector_t *R_PointInSubsector(fixed_t x, fixed_t y);
 subsector_t *R_PointInSubsectorOrNull(fixed_t x, fixed_t y);
 
diff --git a/src/r_plane.c b/src/r_plane.c
index 29ce26b292e5ea529d937aded0028e41a8d294ba..08e147c89b8f8a7e290af44b4272630aa6109a98 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -83,22 +83,15 @@ static fixed_t planeheight;
 fixed_t yslopetab[MAXVIDHEIGHT*16];
 fixed_t *yslope;
 
-fixed_t cachedheight[MAXVIDHEIGHT];
-fixed_t cacheddistance[MAXVIDHEIGHT];
-fixed_t cachedxstep[MAXVIDHEIGHT];
-fixed_t cachedystep[MAXVIDHEIGHT];
-
 static fixed_t xoffs, yoffs;
-static floatv3_t ds_slope_origin, ds_slope_u, ds_slope_v;
+static floatv3_t slope_origin, slope_u, slope_v;
+static floatv3_t slope_lightu, slope_lightv;
 
-//
-// R_InitPlanes
-// Only at game startup.
-//
-void R_InitPlanes(void)
-{
-	// FIXME: unused
-}
+static void CalcSlopePlaneVectors(visplane_t *pl, fixed_t xoff, fixed_t yoff);
+static void CalcSlopeLightVectors(pslope_t *slope, fixed_t xpos, fixed_t ypos, fixed_t height, float ang, angle_t plangle);
+
+static void DoSlopeCrossProducts(void);
+static void DoSlopeLightCrossProduct(void);
 
 //
 // Water ripple effect
@@ -159,37 +152,28 @@ static void R_MapPlane(INT32 y, INT32 x1, INT32 x2)
 	planecos = FINECOSINE(angle);
 	planesin = FINESINE(angle);
 
-	if (planeheight != cachedheight[y])
-	{
-		cachedheight[y] = planeheight;
-		cacheddistance[y] = distance = FixedMul(planeheight, yslope[y]);
-		span = abs(centery - y);
-
-		if (span) // Don't divide by zero
-		{
-			ds_xstep = FixedMul(planesin, planeheight) / span;
-			ds_ystep = FixedMul(planecos, planeheight) / span;
-		}
-		else
-			ds_xstep = ds_ystep = FRACUNIT;
+	// [RH] Notice that I dumped the caching scheme used by Doom.
+	// It did not offer any appreciable speedup.
+	distance = FixedMul(planeheight, yslope[y]);
+	span = abs(centery - y);
 
-		cachedxstep[y] = ds_xstep;
-		cachedystep[y] = ds_ystep;
-	}
-	else
+	if (span) // Don't divide by zero
 	{
-		distance = cacheddistance[y];
-		ds_xstep = cachedxstep[y];
-		ds_ystep = cachedystep[y];
+		ds_xstep = FixedMul(planesin, planeheight) / span;
+		ds_ystep = FixedMul(planecos, planeheight) / span;
+		ds_xstep = FixedMul(currentplane->xscale, ds_xstep);
+		ds_ystep = FixedMul(currentplane->yscale, ds_ystep);
 	}
+	else
+		ds_xstep = ds_ystep = FRACUNIT;
 
 	// [RH] Instead of using the xtoviewangle array, I calculated the fractional values
 	// at the middle of the screen, then used the calculated ds_xstep and ds_ystep
 	// to step from those to the proper texture coordinate to start drawing at.
 	// That way, the texture coordinate is always calculated by its position
 	// on the screen and not by its position relative to the edge of the visplane.
-	ds_xfrac = xoffs + FixedMul(planecos, distance) + (x1 - centerx) * ds_xstep;
-	ds_yfrac = yoffs - FixedMul(planesin, distance) + (x1 - centerx) * ds_ystep;
+	ds_xfrac = xoffs + FixedMul(currentplane->xscale, FixedMul(planecos, distance)) + (x1 - centerx) * ds_xstep;
+	ds_yfrac = yoffs - FixedMul(currentplane->yscale, FixedMul(planesin, distance)) + (x1 - centerx) * ds_ystep;
 
 	// Water ripple effect
 	if (planeripple.active)
@@ -238,9 +222,9 @@ static void R_MapTiltedPlane(INT32 y, INT32 x1, INT32 x2)
 	{
 		ds_bgofs = R_CalculateRippleOffset(y);
 
-		ds_sup = &ds_su[y];
-		ds_svp = &ds_sv[y];
-		ds_szp = &ds_sz[y];
+		R_CalculatePlaneRipple(currentplane->viewangle + currentplane->plangle);
+
+		CalcSlopePlaneVectors(currentplane, (xoffs + planeripple.xfrac), (yoffs + planeripple.yfrac));
 
 		ds_bgofs >>= FRACBITS;
 
@@ -275,10 +259,7 @@ static void R_MapFogPlane(INT32 y, INT32 x1, INT32 x2)
 	if (x1 >= vid.width)
 		x1 = vid.width - 1;
 
-	if (planeheight != cachedheight[y])
-		distance = FixedMul(planeheight, yslope[y]);
-	else
-		distance = cacheddistance[y];
+	distance = FixedMul(planeheight, yslope[y]);
 
 	pindex = distance >> LIGHTZSHIFT;
 	if (pindex >= MAXLIGHTZ)
@@ -361,9 +342,6 @@ void R_ClearPlanes(void)
 	{
 		freehead = &(*freehead)->next;
 	}
-
-	// texture calculation
-	memset(cachedheight, 0, sizeof (cachedheight));
 }
 
 static visplane_t *new_visplane(unsigned hash)
@@ -391,7 +369,8 @@ static visplane_t *new_visplane(unsigned hash)
 //              If not, allocates another of them.
 //
 visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel,
-	fixed_t xoff, fixed_t yoff, angle_t plangle, extracolormap_t *planecolormap,
+	fixed_t xoff, fixed_t yoff, fixed_t xscale, fixed_t yscale,
+	angle_t plangle, extracolormap_t *planecolormap,
 	ffloor_t *pfloor, polyobj_t *polyobj, pslope_t *slope)
 {
 	visplane_t *check;
@@ -399,8 +378,9 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel,
 
 	if (!slope) // Don't mess with this right now if a slope is involved
 	{
-		xoff += viewx;
-		yoff -= viewy;
+		xoff += FixedMul(viewx, xscale);
+		yoff -= FixedMul(viewy, yscale);
+
 		if (plangle != 0)
 		{
 			// Add the view offset, rotated by the plane angle.
@@ -441,16 +421,16 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel,
 		hash = visplane_hash(picnum, lightlevel, height);
 		for (check = visplanes[hash]; check; check = check->next)
 		{
-			if (polyobj != check->polyobj)
-				continue;
 			if (height == check->height && picnum == check->picnum
 				&& lightlevel == check->lightlevel
 				&& xoff == check->xoffs && yoff == check->yoffs
+				&& xscale == check->xscale && yscale == check->yscale
 				&& planecolormap == check->extra_colormap
 				&& check->viewx == viewx && check->viewy == viewy && check->viewz == viewz
 				&& check->viewangle == viewangle
 				&& check->plangle == plangle
-				&& check->slope == slope)
+				&& check->slope == slope
+				&& check->polyobj == polyobj)
 			{
 				return check;
 			}
@@ -470,6 +450,8 @@ visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel,
 	check->maxx = -1;
 	check->xoffs = xoff;
 	check->yoffs = yoff;
+	check->xscale = xscale;
+	check->yscale = yscale;
 	check->extra_colormap = planecolormap;
 	check->ffloor = pfloor;
 	check->viewx = viewx;
@@ -546,6 +528,8 @@ visplane_t *R_CheckPlane(visplane_t *pl, INT32 start, INT32 stop)
 		new_pl->lightlevel = pl->lightlevel;
 		new_pl->xoffs = pl->xoffs;
 		new_pl->yoffs = pl->yoffs;
+		new_pl->xscale = pl->xscale;
+		new_pl->yscale = pl->yscale;
 		new_pl->extra_colormap = pl->extra_colormap;
 		new_pl->ffloor = pl->ffloor;
 		new_pl->viewx = pl->viewx;
@@ -686,8 +670,6 @@ static INT64 R_GetSlopeZAt(const pslope_t *slope, fixed_t x, fixed_t y)
 // Sets the texture origin vector of the sloped plane.
 static void R_SetSlopePlaneOrigin(pslope_t *slope, fixed_t xpos, fixed_t ypos, fixed_t zpos, fixed_t xoff, fixed_t yoff, fixed_t angle)
 {
-	floatv3_t *p = &ds_slope_origin;
-
 	INT64 vx = (INT64)xpos + (INT64)xoff;
 	INT64 vy = (INT64)ypos - (INT64)yoff;
 
@@ -695,125 +677,164 @@ static void R_SetSlopePlaneOrigin(pslope_t *slope, fixed_t xpos, fixed_t ypos, f
 	float vyf = vy / (float)FRACUNIT;
 	float ang = ANG2RAD(ANGLE_270 - angle);
 
-	// p is the texture origin in view space
+	// slope_origin is the texture origin in view space
 	// Don't add in the offsets at this stage, because doing so can result in
 	// errors if the flat is rotated.
-	p->x = vxf * cos(ang) - vyf * sin(ang);
-	p->z = vxf * sin(ang) + vyf * cos(ang);
-	p->y = (R_GetSlopeZAt(slope, -xoff, yoff) - zpos) / (float)FRACUNIT;
+	slope_origin.x = vxf * cos(ang) - vyf * sin(ang);
+	slope_origin.z = vxf * sin(ang) + vyf * cos(ang);
+	slope_origin.y = (R_GetSlopeZAt(slope, -xoff, yoff) - zpos) / (float)FRACUNIT;
 }
 
 // This function calculates all of the vectors necessary for drawing a sloped plane.
 void R_SetSlopePlane(pslope_t *slope, fixed_t xpos, fixed_t ypos, fixed_t zpos, fixed_t xoff, fixed_t yoff, angle_t angle, angle_t plangle)
 {
-	// Potentially override other stuff for now cus we're mean. :< But draw a slope plane!
 	// I copied ZDoom's code and adapted it to SRB2... -Red
-	floatv3_t *m = &ds_slope_v, *n = &ds_slope_u;
-	fixed_t height, temp;
+	fixed_t height, z_at_xy;
 	float ang;
 
 	R_SetSlopePlaneOrigin(slope, xpos, ypos, zpos, xoff, yoff, angle);
 	height = P_GetSlopeZAt(slope, xpos, ypos);
 	zeroheight = FixedToFloat(height - zpos);
 
-	// m is the v direction vector in view space
 	ang = ANG2RAD(ANGLE_180 - (angle + plangle));
-	m->x = cos(ang);
-	m->z = sin(ang);
 
-	// n is the u direction vector in view space
-	n->x = sin(ang);
-	n->z = -cos(ang);
+	CalcSlopeLightVectors(slope, xpos, ypos, height, ang, plangle);
+
+	if (ds_solidcolor || ds_fog)
+	{
+		DoSlopeLightCrossProduct();
+		return;
+	}
+
+	// slope_v is the v direction vector in view space
+	slope_v.x = cos(ang);
+	slope_v.z = sin(ang);
+
+	// slope_u is the u direction vector in view space
+	slope_u.x = sin(ang);
+	slope_u.z = -cos(ang);
 
 	plangle >>= ANGLETOFINESHIFT;
-	temp = P_GetSlopeZAt(slope, xpos + FINESINE(plangle), ypos + FINECOSINE(plangle));
-	m->y = FixedToFloat(temp - height);
-	temp = P_GetSlopeZAt(slope, xpos + FINECOSINE(plangle), ypos - FINESINE(plangle));
-	n->y = FixedToFloat(temp - height);
+	z_at_xy = P_GetSlopeZAt(slope, xpos + FINESINE(plangle), ypos + FINECOSINE(plangle));
+	slope_v.y = FixedToFloat(z_at_xy - height);
+	z_at_xy = P_GetSlopeZAt(slope, xpos + FINECOSINE(plangle), ypos - FINESINE(plangle));
+	slope_u.y = FixedToFloat(z_at_xy - height);
+
+	DoSlopeCrossProducts();
+	DoSlopeLightCrossProduct();
 }
 
 // This function calculates all of the vectors necessary for drawing a sloped and scaled plane.
 void R_SetScaledSlopePlane(pslope_t *slope, fixed_t xpos, fixed_t ypos, fixed_t zpos, fixed_t xs, fixed_t ys, fixed_t xoff, fixed_t yoff, angle_t angle, angle_t plangle)
 {
-	floatv3_t *m = &ds_slope_v, *n = &ds_slope_u;
-	fixed_t height, temp;
+	fixed_t height, z_at_xy;
 
-	float xscale = FixedToFloat(xs);
-	float yscale = FixedToFloat(ys);
 	float ang;
 
 	R_SetSlopePlaneOrigin(slope, xpos, ypos, zpos, xoff, yoff, angle);
 	height = P_GetSlopeZAt(slope, xpos, ypos);
 	zeroheight = FixedToFloat(height - zpos);
 
-	// m is the v direction vector in view space
 	ang = ANG2RAD(ANGLE_180 - (angle + plangle));
-	m->x = yscale * cos(ang);
-	m->z = yscale * sin(ang);
+
+	CalcSlopeLightVectors(slope, xpos, ypos, height, ang, plangle);
+
+	if (ds_solidcolor || ds_fog)
+	{
+		DoSlopeLightCrossProduct();
+		return;
+	}
+
+	float xscale = FixedToFloat(xs);
+	float yscale = FixedToFloat(ys);
+
+	// m is the v direction vector in view space
+	slope_v.x = yscale * cos(ang);
+	slope_v.z = yscale * sin(ang);
 
 	// n is the u direction vector in view space
-	n->x = xscale * sin(ang);
-	n->z = -xscale * cos(ang);
+	slope_u.x = xscale * sin(ang);
+	slope_u.z = -xscale * cos(ang);
 
 	ang = ANG2RAD(plangle);
-	temp = P_GetSlopeZAt(slope, xpos + FloatToFixed(yscale * sin(ang)), ypos + FloatToFixed(yscale * cos(ang)));
-	m->y = FixedToFloat(temp - height);
-	temp = P_GetSlopeZAt(slope, xpos + FloatToFixed(xscale * cos(ang)), ypos - FloatToFixed(xscale * sin(ang)));
-	n->y = FixedToFloat(temp - height);
+	z_at_xy = P_GetSlopeZAt(slope, xpos + FloatToFixed(yscale * sin(ang)), ypos + FloatToFixed(yscale * cos(ang)));
+	slope_v.y = FixedToFloat(z_at_xy - height);
+	z_at_xy = P_GetSlopeZAt(slope, xpos + FloatToFixed(xscale * cos(ang)), ypos - FloatToFixed(xscale * sin(ang)));
+	slope_u.y = FixedToFloat(z_at_xy - height);
+
+	DoSlopeCrossProducts();
+	DoSlopeLightCrossProduct();
 }
 
-void R_CalculateSlopeVectors(void)
+static void CalcSlopeLightVectors(pslope_t *slope, fixed_t xpos, fixed_t ypos, fixed_t height, float ang, angle_t plangle)
 {
-	float sfmult = 65536.f;
+	fixed_t z_at_xy;
 
-	// Eh. I tried making this stuff fixed-point and it exploded on me. Here's a macro for the only floating-point vector function I recall using.
+	slope_lightv.x = cos(ang);
+	slope_lightv.z = sin(ang);
+
+	slope_lightu.x = sin(ang);
+	slope_lightu.z = -cos(ang);
+
+	plangle >>= ANGLETOFINESHIFT;
+	z_at_xy = P_GetSlopeZAt(slope, xpos + FINESINE(plangle), ypos + FINECOSINE(plangle));
+	slope_lightv.y = FixedToFloat(z_at_xy - height);
+	z_at_xy = P_GetSlopeZAt(slope, xpos + FINECOSINE(plangle), ypos - FINESINE(plangle));
+	slope_lightu.y = FixedToFloat(z_at_xy - height);
+}
+
+// Eh. I tried making this stuff fixed-point and it exploded on me. Here's a macro for the only floating-point vector function I recall using.
 #define CROSS(d, v1, v2) \
-d->x = (v1.y * v2.z) - (v1.z * v2.y);\
-d->y = (v1.z * v2.x) - (v1.x * v2.z);\
-d->z = (v1.x * v2.y) - (v1.y * v2.x)
-	CROSS(ds_sup, ds_slope_origin, ds_slope_v);
-	CROSS(ds_svp, ds_slope_origin, ds_slope_u);
-	CROSS(ds_szp, ds_slope_v, ds_slope_u);
-#undef CROSS
+d.x = (v1.y * v2.z) - (v1.z * v2.y);\
+d.y = (v1.z * v2.x) - (v1.x * v2.z);\
+d.z = (v1.x * v2.y) - (v1.y * v2.x)
+
+static void DoSlopeCrossProducts(void)
+{
+	float sfmult = 65536.f;
 
-	ds_sup->z *= focallengthf;
-	ds_svp->z *= focallengthf;
-	ds_szp->z *= focallengthf;
+	CROSS(ds_su, slope_origin, slope_v);
+	CROSS(ds_sv, slope_origin, slope_u);
+	CROSS(ds_sz, slope_v, slope_u);
+
+	ds_su.z *= focallengthf;
+	ds_sv.z *= focallengthf;
+	ds_sz.z *= focallengthf;
 
 	if (ds_solidcolor)
 		return;
 
 	// Premultiply the texture vectors with the scale factors
 	if (ds_powersoftwo)
-		sfmult *= (1 << nflatshiftup);
-
-	ds_sup->x *= sfmult;
-	ds_sup->y *= sfmult;
-	ds_sup->z *= sfmult;
-	ds_svp->x *= sfmult;
-	ds_svp->y *= sfmult;
-	ds_svp->z *= sfmult;
+		sfmult *= 1 << nflatshiftup;
+
+	ds_su.x *= sfmult;
+	ds_su.y *= sfmult;
+	ds_su.z *= sfmult;
+	ds_sv.x *= sfmult;
+	ds_sv.y *= sfmult;
+	ds_sv.z *= sfmult;
 }
 
-void R_SetTiltedSpan(INT32 span)
+static void DoSlopeLightCrossProduct(void)
 {
-	if (ds_su == NULL)
-		ds_su = Z_Malloc(sizeof(*ds_su) * vid.height, PU_STATIC, NULL);
-	if (ds_sv == NULL)
-		ds_sv = Z_Malloc(sizeof(*ds_sv) * vid.height, PU_STATIC, NULL);
-	if (ds_sz == NULL)
-		ds_sz = Z_Malloc(sizeof(*ds_sz) * vid.height, PU_STATIC, NULL);
-
-	ds_sup = &ds_su[span];
-	ds_svp = &ds_sv[span];
-	ds_szp = &ds_sz[span];
+	CROSS(ds_slopelight, slope_lightv, slope_lightu);
+
+	ds_slopelight.z *= focallengthf;
 }
 
-static void R_SetSlopePlaneVectors(visplane_t *pl, INT32 y, fixed_t xoff, fixed_t yoff)
+#undef CROSS
+
+static void CalcSlopePlaneVectors(visplane_t *pl, fixed_t xoff, fixed_t yoff)
 {
-	R_SetTiltedSpan(y);
-	R_SetSlopePlane(pl->slope, pl->viewx, pl->viewy, pl->viewz, xoff, yoff, pl->viewangle, pl->plangle);
-	R_CalculateSlopeVectors();
+	if (!ds_fog && (pl->xscale != FRACUNIT || pl->yscale != FRACUNIT))
+	{
+		R_SetScaledSlopePlane(pl->slope, pl->viewx, pl->viewy, pl->viewz,
+			FixedDiv(FRACUNIT, pl->xscale), FixedDiv(FRACUNIT, pl->yscale),
+			FixedDiv(xoff, pl->xscale), FixedDiv(yoff, pl->yscale), pl->viewangle, pl->plangle);
+	}
+	else
+		R_SetSlopePlane(pl->slope, pl->viewx, pl->viewy, pl->viewz, xoff, yoff, pl->viewangle, pl->plangle);
 }
 
 static inline void R_AdjustSlopeCoordinates(vector3_t *origin)
@@ -850,7 +871,6 @@ void R_DrawSinglePlane(visplane_t *pl)
 	INT32 light = 0;
 	INT32 x, stop;
 	ffloor_t *rover;
-	boolean fog = false;
 	INT32 spanfunctype = BASEDRAWFUNC;
 	void (*mapfunc)(INT32, INT32, INT32);
 
@@ -864,6 +884,8 @@ void R_DrawSinglePlane(visplane_t *pl)
 		return;
 	}
 
+	ds_powersoftwo = ds_solidcolor = ds_fog = false;
+
 	planeripple.active = false;
 
 	if (pl->polyobj)
@@ -928,13 +950,13 @@ void R_DrawSinglePlane(visplane_t *pl)
 			}
 			else if (pl->ffloor->fofflags & FOF_FOG)
 			{
-				fog = true;
+				ds_fog = true;
 				spanfunctype = SPANDRAWFUNC_FOG;
 				light = (pl->lightlevel >> LIGHTSEGSHIFT);
 			}
 			else light = (pl->lightlevel >> LIGHTSEGSHIFT);
 
-			if (pl->ffloor->fofflags & FOF_RIPPLE && !fog)
+			if (pl->ffloor->fofflags & FOF_RIPPLE && !ds_fog)
 			{
 				planeripple.active = true;
 
@@ -962,9 +984,7 @@ void R_DrawSinglePlane(visplane_t *pl)
 			light = (pl->lightlevel >> LIGHTSEGSHIFT);
 	}
 
-	ds_powersoftwo = ds_solidcolor = false;
-
-	if (fog)
+	if (ds_fog)
 	{
 		// Since all fog planes do is apply a colormap, it's not required
 		// to know any information about their textures.
@@ -1000,13 +1020,6 @@ void R_DrawSinglePlane(visplane_t *pl)
 				}
 		}
 
-		// Don't mess with angle on slopes! We'll handle this ourselves later
-		if (!pl->slope && viewangle != pl->viewangle+pl->plangle)
-		{
-			memset(cachedheight, 0, sizeof (cachedheight));
-			viewangle = pl->viewangle+pl->plangle;
-		}
-
 		mapfunc = R_MapPlane;
 
 		if (ds_solidcolor)
@@ -1037,13 +1050,13 @@ void R_DrawSinglePlane(visplane_t *pl)
 
 	if (pl->slope)
 	{
-		if (fog)
+		if (ds_fog)
 			mapfunc = R_MapTiltedFogPlane;
 		else
 		{
 			mapfunc = R_MapTiltedPlane;
 
-			if (!pl->plangle && !ds_solidcolor)
+			if (!pl->plangle && !ds_solidcolor && pl->xscale == FRACUNIT && pl->yscale == FRACUNIT)
 			{
 				if (ds_powersoftwo)
 					R_AdjustSlopeCoordinates(&pl->slope->o);
@@ -1052,21 +1065,10 @@ void R_DrawSinglePlane(visplane_t *pl)
 			}
 		}
 
-		if (planeripple.active)
-		{
+		if (!ds_fog && planeripple.active)
 			planeheight = abs(P_GetSlopeZAt(pl->slope, pl->viewx, pl->viewy) - pl->viewz);
-
-			R_PlaneBounds(pl);
-
-			for (x = pl->high; x < pl->low; x++)
-			{
-				ds_bgofs = R_CalculateRippleOffset(x);
-				R_CalculatePlaneRipple(pl->viewangle + pl->plangle);
-				R_SetSlopePlaneVectors(pl, x, (xoffs + planeripple.xfrac), (yoffs + planeripple.yfrac));
-			}
-		}
 		else
-			R_SetSlopePlaneVectors(pl, 0, xoffs, yoffs);
+			CalcSlopePlaneVectors(pl, xoffs, yoffs);
 
 		switch (spanfunctype)
 		{
diff --git a/src/r_plane.h b/src/r_plane.h
index 917e8b041b75775016dbc2a1e10b8cc6ad7a3778..5ce49e3cc9f2789b113076b64677125f68aca2c1 100644
--- a/src/r_plane.h
+++ b/src/r_plane.h
@@ -49,6 +49,7 @@ typedef struct visplane_s
 	INT32 high, low; // R_PlaneBounds should set these.
 
 	fixed_t xoffs, yoffs; // Scrolling flats.
+	fixed_t xscale, yscale;
 
 	struct ffloor_s *ffloor;
 	polyobj_t *polyobj;
@@ -62,21 +63,16 @@ extern visplane_t *ceilingplane;
 // Visplane related.
 extern INT16 floorclip[MAXVIDWIDTH], ceilingclip[MAXVIDWIDTH];
 extern fixed_t frontscale[MAXVIDWIDTH], yslopetab[MAXVIDHEIGHT*16];
-extern fixed_t cachedheight[MAXVIDHEIGHT];
-extern fixed_t cacheddistance[MAXVIDHEIGHT];
-extern fixed_t cachedxstep[MAXVIDHEIGHT];
-extern fixed_t cachedystep[MAXVIDHEIGHT];
 
 extern fixed_t *yslope;
 extern lighttable_t **planezlight;
 
-void R_InitPlanes(void);
 void R_ClearPlanes(void);
 void R_ClearFFloorClips (void);
 
 void R_DrawPlanes(void);
-visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel, fixed_t xoff, fixed_t yoff, angle_t plangle,
-	extracolormap_t *planecolormap, ffloor_t *ffloor, polyobj_t *polyobj, pslope_t *slope);
+visplane_t *R_FindPlane(fixed_t height, INT32 picnum, INT32 lightlevel, fixed_t xoff, fixed_t yoff, fixed_t xscale, fixed_t yscale,
+	angle_t plangle, extracolormap_t *planecolormap, ffloor_t *ffloor, polyobj_t *polyobj, pslope_t *slope);
 visplane_t *R_CheckPlane(visplane_t *pl, INT32 start, INT32 stop);
 void R_ExpandPlane(visplane_t *pl, INT32 start, INT32 stop);
 void R_PlaneBounds(visplane_t *plane);
@@ -87,10 +83,6 @@ void R_DrawSinglePlane(visplane_t *pl);
 // Calculates the slope vectors needed for tilted span drawing.
 void R_SetSlopePlane(pslope_t *slope, fixed_t xpos, fixed_t ypos, fixed_t zpos, fixed_t xoff, fixed_t yoff, angle_t angle, angle_t plangle);
 void R_SetScaledSlopePlane(pslope_t *slope, fixed_t xpos, fixed_t ypos, fixed_t zpos, fixed_t xs, fixed_t ys, fixed_t xoff, fixed_t yoff, angle_t angle, angle_t plangle);
-void R_CalculateSlopeVectors(void);
-
-// Sets the slope vector pointers for the current tilted span.
-void R_SetTiltedSpan(INT32 span);
 
 typedef struct planemgr_s
 {
diff --git a/src/r_segs.c b/src/r_segs.c
index 019a0d5c6a9f7e316d984546e7cb7c7cd5c9bae5..ecc49fbc23ec80e7100c82dbd957fb453735f643 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -48,15 +48,18 @@ fixed_t rw_distance;
 //
 static INT32 rw_x, rw_stopx;
 static angle_t rw_centerangle;
-static fixed_t rw_offset;
-static fixed_t rw_offset_top, rw_offset_mid, rw_offset_bot;
-static fixed_t rw_offset2; // for splats
+static fixed_t rw_offset, rw_offsetx;
+static fixed_t rw_offset_top, rw_offset_mid, rw_offset_bottom;
 static fixed_t rw_scale, rw_scalestep;
 static fixed_t rw_midtexturemid, rw_toptexturemid, rw_bottomtexturemid;
 static INT32 worldtop, worldbottom, worldhigh, worldlow;
 static INT32 worldtopslope, worldbottomslope, worldhighslope, worldlowslope; // worldtop/bottom at end of slope
 static fixed_t rw_toptextureslide, rw_midtextureslide, rw_bottomtextureslide; // Defines how to adjust Y offsets along the wall for slopes
 static fixed_t rw_midtextureback, rw_midtexturebackslide; // Values for masked midtexture height calculation
+static fixed_t rw_midtexturescalex, rw_midtexturescaley;
+static fixed_t rw_toptexturescalex, rw_toptexturescaley;
+static fixed_t rw_bottomtexturescalex, rw_bottomtexturescaley;
+static fixed_t rw_invmidtexturescalex, rw_invtoptexturescalex, rw_invbottomtexturescalex;
 
 // Lactozilla: 3D floor clipping
 static boolean rw_floormarked = false;
@@ -71,8 +74,10 @@ static fixed_t topfrac, topstep;
 static fixed_t bottomfrac, bottomstep;
 
 static lighttable_t **walllights;
-static fixed_t *maskedtexturecol;
+static fixed_t *maskedtexturecol = NULL;
 static fixed_t *maskedtextureheight = NULL;
+static fixed_t *thicksidecol = NULL;
+static fixed_t *invscale = NULL;
 
 //SoM: 3/23/2000: Use boom opening limit removal
 static size_t numopenings;
@@ -162,7 +167,8 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 
 	frontsector = curline->frontsector;
 	backsector = curline->backsector;
-	texnum = R_GetTextureNum(curline->sidedef->midtexture);
+	sidedef = curline->sidedef;
+	texnum = R_GetTextureNum(sidedef->midtexture);
 	windowbottom = windowtop = sprbotscreen = INT32_MAX;
 
 	ldef = curline->linedef;
@@ -200,9 +206,13 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 		colfunc = colfuncs[COLDRAWFUNC_FUZZY];
 	}
 
+	fixed_t wall_scaley = sidedef->scaley_mid;
+	fixed_t scalestep = FixedDiv(ds->scalestep, wall_scaley);
+	fixed_t scale1 = FixedDiv(ds->scale1, wall_scaley);
+
 	range = max(ds->x2-ds->x1, 1);
-	rw_scalestep = ds->scalestep;
-	spryscale = ds->scale1 + (x1 - ds->x1)*rw_scalestep;
+	rw_scalestep = scalestep;
+	spryscale = scale1 + (x1 - ds->x1)*rw_scalestep;
 
 	// Texture must be cached before setting colfunc_2s,
 	// otherwise texture[texnum]->holes may be false when it shouldn't be
@@ -313,8 +323,8 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 	else
 		back = backsector;
 
-	if (ds->curline->sidedef->repeatcnt)
-		repeats = 1 + ds->curline->sidedef->repeatcnt;
+	if (sidedef->repeatcnt)
+		repeats = 1 + sidedef->repeatcnt;
 	else if (ldef->flags & ML_WRAPMIDTEX)
 	{
 		fixed_t high, low;
@@ -340,15 +350,14 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 	{
 		if (times > 0)
 		{
-			rw_scalestep = ds->scalestep;
-			spryscale = ds->scale1 + (x1 - ds->x1)*rw_scalestep;
-			if (dc_numlights)
-			{ // reset all lights to their starting heights
-				for (i = 0; i < dc_numlights; i++)
-				{
-					rlight = &dc_lightlist[i];
-					rlight->height = rlight->startheight;
-				}
+			rw_scalestep = scalestep;
+			spryscale = scale1 + (x1 - ds->x1)*rw_scalestep;
+
+			// reset all lights to their starting heights
+			for (i = 0; i < dc_numlights; i++)
+			{
+				rlight = &dc_lightlist[i];
+				rlight->height = rlight->startheight;
 			}
 		}
 
@@ -390,8 +399,10 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 				sprbotscreen = INT32_MAX;
 				sprtopscreen = windowtop = (centeryfrac - FixedMul(dc_texturemid, spryscale));
 
-				realbot = windowbottom = FixedMul(textureheight[texnum], spryscale) + sprtopscreen;
-				dc_iscale = 0xffffffffu / (unsigned)spryscale;
+				realbot = FixedMul(textureheight[texnum], spryscale) + sprtopscreen;
+				dc_iscale = FixedMul(ds->invscale[dc_x], wall_scaley);
+
+				windowbottom = realbot;
 
 				// draw the texture
 				col = (column_t *)((UINT8 *)R_GetColumn(texnum, (maskedtexturecol[dc_x] >> FRACBITS)) - 3);
@@ -466,7 +477,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 				dc_colormap = frontsector->extra_colormap->colormap + (dc_colormap - colormaps);
 
 			sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale);
-			dc_iscale = 0xffffffffu / (unsigned)spryscale;
+			dc_iscale = FixedMul(ds->invscale[dc_x], wall_scaley);
 
 			// draw the texture
 			col = (column_t *)((UINT8 *)R_GetColumn(texnum, (maskedtexturecol[dc_x] >> FRACBITS)) - 3);
@@ -525,7 +536,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 	INT32             i, p;
 	fixed_t         bottombounds = viewheight << FRACBITS;
 	fixed_t         topbounds = (con_clipviewtop - 1) << FRACBITS;
-	fixed_t         offsetvalue = 0;
+	fixed_t         offsetvalue;
 	lightlist_t     *light;
 	r_lightlist_t   *rlight;
 	INT32           range;
@@ -534,11 +545,13 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 	// NOTE: INT64 instead of fixed_t because overflow concerns
 	INT64         top_frac, top_step, bottom_frac, bottom_step;
 	// skew FOF walls with slopes?
-	boolean	      slopeskew = false;
 	fixed_t       ffloortextureslide = 0;
 	INT32         oldx = -1;
 	fixed_t       left_top, left_bottom; // needed here for slope skewing
 	pslope_t      *skewslope = NULL;
+	boolean do_texture_skew;
+	UINT32 lineflags;
+	fixed_t wall_scalex, wall_scaley;
 
 	void (*colfunc_2s) (column_t *);
 
@@ -550,7 +563,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 	curline = ds->curline;
 	backsector = pfloor->target;
 	frontsector = curline->frontsector == pfloor->target ? curline->backsector : curline->frontsector;
-	texnum = R_GetTextureNum(sides[pfloor->master->sidenum[0]].midtexture);
+	sidedef = &sides[pfloor->master->sidenum[0]];
 
 	colfunc = colfuncs[BASEDRAWFUNC];
 
@@ -558,8 +571,13 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 	{
 		size_t linenum = curline->linedef-backsector->lines[0];
 		newline = pfloor->master->frontsector->lines[0] + linenum;
-		texnum = R_GetTextureNum(sides[newline->sidenum[0]].midtexture);
+		sidedef = &sides[newline->sidenum[0]];
+		lineflags = newline->flags;
 	}
+	else
+		lineflags = pfloor->master->flags;
+
+	texnum = R_GetTextureNum(sidedef->midtexture);
 
 	if (pfloor->fofflags & FOF_TRANSLUCENT)
 	{
@@ -711,7 +729,13 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 			walllights = scalelight[lightnum];
 	}
 
-	maskedtexturecol = ds->thicksidecol;
+	wall_scalex = FixedDiv(FRACUNIT, sidedef->scalex_mid);
+	wall_scaley = sidedef->scaley_mid;
+
+	thicksidecol = ds->thicksidecol;
+
+	for (INT32 x = x1; x <= x2; x++)
+		thicksidecol[x] = FixedDiv(thicksidecol[x], wall_scalex) + ds->offsetx;
 
 	mfloorclip = ds->sprbottomclip;
 	mceilingclip = ds->sprtopclip;
@@ -721,51 +745,29 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 	left_top    = P_GetFFloorTopZAt   (pfloor, ds->leftpos.x, ds->leftpos.y) - viewz;
 	left_bottom = P_GetFFloorBottomZAt(pfloor, ds->leftpos.x, ds->leftpos.y) - viewz;
 
+	do_texture_skew = lineflags & ML_SKEWTD;
 	skewslope = *pfloor->t_slope; // skew using top slope by default
-	if (newline)
-	{
-		if (newline->flags & ML_SKEWTD)
-			slopeskew = true;
-	}
-	else if (pfloor->master->flags & ML_SKEWTD)
-		slopeskew = true;
 
-	if (slopeskew)
-		dc_texturemid = left_top;
+	if (do_texture_skew)
+		dc_texturemid = FixedMul(left_top, wall_scaley);
 	else
-		dc_texturemid = *pfloor->topheight - viewz;
+		dc_texturemid = FixedMul(*pfloor->topheight - viewz, wall_scaley);
 
-	if (newline)
-	{
-		offsetvalue = sides[newline->sidenum[0]].rowoffset + sides[newline->sidenum[0]].offsety_mid;
-		if (newline->flags & ML_DONTPEGBOTTOM)
-		{
-			skewslope = *pfloor->b_slope; // skew using bottom slope
-			if (slopeskew)
-				dc_texturemid = left_bottom;
-			else
-				offsetvalue -= *pfloor->topheight - *pfloor->bottomheight;
-		}
-	}
-	else
+	offsetvalue = sidedef->rowoffset + sidedef->offsety_mid;
+
+	if (lineflags & ML_DONTPEGBOTTOM)
 	{
-		offsetvalue = sides[pfloor->master->sidenum[0]].rowoffset + sides[pfloor->master->sidenum[0]].offsety_mid;
-		if (curline->linedef->flags & ML_DONTPEGBOTTOM)
-		{
-			skewslope = *pfloor->b_slope; // skew using bottom slope
-			if (slopeskew)
-				dc_texturemid = left_bottom;
-			else
-				offsetvalue -= *pfloor->topheight - *pfloor->bottomheight;
-		}
+		skewslope = *pfloor->b_slope; // skew using bottom slope
+		if (do_texture_skew)
+			dc_texturemid = FixedMul(left_bottom, wall_scaley);
+		else
+			offsetvalue -= FixedMul(*pfloor->topheight - *pfloor->bottomheight, wall_scaley);
 	}
 
-	if (slopeskew)
+	if (do_texture_skew && skewslope)
 	{
 		angle_t lineangle = R_PointToAngle2(curline->v1->x, curline->v1->y, curline->v2->x, curline->v2->y);
-
-		if (skewslope)
-			ffloortextureslide = FixedMul(skewslope->zdelta, FINECOSINE((lineangle-skewslope->xydirection)>>ANGLETOFINESHIFT));
+		ffloortextureslide = FixedMul(skewslope->zdelta, FINECOSINE((lineangle-skewslope->xydirection)>>ANGLETOFINESHIFT));
 	}
 
 	dc_texturemid += offsetvalue;
@@ -819,7 +821,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 		if (ffloortextureslide)
 		{
 			if (oldx != -1)
-				dc_texturemid += FixedMul(ffloortextureslide, maskedtexturecol[oldx]-maskedtexturecol[dc_x]);
+				dc_texturemid += FixedMul(ffloortextureslide, thicksidecol[oldx]-thicksidecol[dc_x]);
 			oldx = dc_x;
 		}
 
@@ -852,10 +854,10 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 			continue;
 		}
 
-		dc_iscale = 0xffffffffu / (unsigned)spryscale;
+		dc_iscale = FixedMul(0xffffffffu / (unsigned)spryscale, wall_scaley);
 
 		// Get data for the column
-		col = (column_t *)((UINT8 *)R_GetColumn(texnum, (maskedtexturecol[dc_x] >> FRACBITS)) - 3);
+		col = (column_t *)((UINT8 *)R_GetColumn(texnum, (thicksidecol[dc_x] >> FRACBITS)) - 3);
 
 		// SoM: New code does not rely on R_DrawColumnShadowed_8 which
 		// will (hopefully) put less strain on the stack.
@@ -1044,13 +1046,18 @@ UINT32 nombre = 100000;
 static void R_RenderSegLoop (void)
 {
 	angle_t angle;
+	fixed_t textureoffset;
 	size_t  pindex;
 	INT32     yl;
 	INT32     yh;
 
 	INT32     mid;
 	fixed_t texturecolumn = 0;
+	fixed_t toptexturecolumn = 0;
+	fixed_t bottomtexturecolumn = 0;
 	fixed_t oldtexturecolumn = -1;
+	fixed_t oldtexturecolumn_top = -1;
+	fixed_t oldtexturecolumn_bottom = -1;
 	INT32     top;
 	INT32     bottom;
 	INT32     i;
@@ -1217,17 +1224,8 @@ static void R_RenderSegLoop (void)
 		//SoM: Calculate offsets for Thick fake floors.
 		// calculate texture offset
 		angle = (rw_centerangle + xtoviewangle[rw_x])>>ANGLETOFINESHIFT;
-		texturecolumn = rw_offset-FixedMul(FINETANGENT(angle),rw_distance);
-
-		if (oldtexturecolumn != -1) {
-			rw_bottomtexturemid += FixedMul(rw_bottomtextureslide,  oldtexturecolumn-texturecolumn);
-			rw_midtexturemid    += FixedMul(rw_midtextureslide,     oldtexturecolumn-texturecolumn);
-			rw_toptexturemid    += FixedMul(rw_toptextureslide,     oldtexturecolumn-texturecolumn);
-			rw_midtextureback   += FixedMul(rw_midtexturebackslide, oldtexturecolumn-texturecolumn);
-		}
-		oldtexturecolumn = texturecolumn;
-
-		INT32 itexturecolumn = texturecolumn >> FRACBITS;
+		textureoffset = rw_offset - FixedMul(FINETANGENT(angle), rw_distance);
+		texturecolumn = FixedDiv(textureoffset, rw_invmidtexturescalex);
 
 		// texturecolumn and lighting are independent of wall tiers
 		if (segtextured)
@@ -1240,7 +1238,6 @@ static void R_RenderSegLoop (void)
 
 			dc_colormap = walllights[pindex];
 			dc_x = rw_x;
-			dc_iscale = 0xffffffffu / (unsigned)rw_scale;
 
 			if (frontsector->extra_colormap)
 				dc_colormap = frontsector->extra_colormap->colormap + (dc_colormap - colormaps);
@@ -1290,10 +1287,13 @@ static void R_RenderSegLoop (void)
 			// single sided line
 			if (yl <= yh && yh >= 0 && yl < viewheight)
 			{
+				fixed_t offset = texturecolumn + rw_offsetx;
+
 				dc_yl = yl;
 				dc_yh = yh;
 				dc_texturemid = rw_midtexturemid;
-				dc_source = R_GetColumn(midtexture, itexturecolumn + (rw_offset_mid>>FRACBITS));
+				dc_iscale = FixedMul(0xffffffffu / (unsigned)rw_scale, rw_midtexturescaley);
+				dc_source = R_GetColumn(midtexture, offset >> FRACBITS);
 				dc_texheight = textureheight[midtexture]>>FRACBITS;
 
 				//profile stuff ---------------------------------------------------------
@@ -1342,6 +1342,8 @@ static void R_RenderSegLoop (void)
 				if (mid >= floorclip[rw_x])
 					mid = floorclip[rw_x]-1;
 
+				toptexturecolumn = FixedDiv(textureoffset, rw_invtoptexturescalex);
+
 				if (mid >= yl) // back ceiling lower than front ceiling ?
 				{
 					if (yl >= viewheight) // entirely off bottom of screen
@@ -1351,10 +1353,16 @@ static void R_RenderSegLoop (void)
 					}
 					else if (mid >= 0) // safe to draw top texture
 					{
+						fixed_t offset = rw_offset_top;
+						if (rw_toptexturescalex < 0)
+							offset = -offset;
+						offset = toptexturecolumn + offset;
+
 						dc_yl = yl;
 						dc_yh = mid;
 						dc_texturemid = rw_toptexturemid;
-						dc_source = R_GetColumn(toptexture, itexturecolumn + (rw_offset_top>>FRACBITS));
+						dc_iscale = FixedMul(0xffffffffu / (unsigned)rw_scale, rw_toptexturescaley);
+						dc_source = R_GetColumn(toptexture, offset >> FRACBITS);
 						dc_texheight = textureheight[toptexture]>>FRACBITS;
 						colfunc();
 						ceilingclip[rw_x] = (INT16)mid;
@@ -1364,6 +1372,10 @@ static void R_RenderSegLoop (void)
 				}
 				else if (!rw_ceilingmarked)
 					ceilingclip[rw_x] = topclip;
+
+				if (oldtexturecolumn_top != -1)
+					rw_toptexturemid += FixedMul(rw_toptextureslide, oldtexturecolumn_top-toptexturecolumn);
+				oldtexturecolumn_top = toptexturecolumn;
 			}
 			else if (markceiling && (!rw_ceilingmarked)) // no top wall
 				ceilingclip[rw_x] = topclip;
@@ -1378,6 +1390,8 @@ static void R_RenderSegLoop (void)
 				if (mid <= ceilingclip[rw_x])
 					mid = ceilingclip[rw_x]+1;
 
+				bottomtexturecolumn = FixedDiv(textureoffset, rw_invbottomtexturescalex);
+
 				if (mid <= yh) // back floor higher than front floor ?
 				{
 					if (yh < 0) // entirely off top of screen
@@ -1387,10 +1401,16 @@ static void R_RenderSegLoop (void)
 					}
 					else if (mid < viewheight) // safe to draw bottom texture
 					{
+						fixed_t offset = rw_offset_bottom;
+						if (rw_bottomtexturescalex < 0)
+							offset = -offset;
+						offset = bottomtexturecolumn + offset;
+
 						dc_yl = mid;
 						dc_yh = yh;
 						dc_texturemid = rw_bottomtexturemid;
-						dc_source = R_GetColumn(bottomtexture, itexturecolumn + (rw_offset_bot>>FRACBITS));
+						dc_iscale = FixedMul(0xffffffffu / (unsigned)rw_scale, rw_bottomtexturescaley);
+						dc_source = R_GetColumn(bottomtexture, offset >> FRACBITS);
 						dc_texheight = textureheight[bottomtexture]>>FRACBITS;
 						colfunc();
 						floorclip[rw_x] = (INT16)mid;
@@ -1400,24 +1420,43 @@ static void R_RenderSegLoop (void)
 				}
 				else if (!rw_floormarked)
 					floorclip[rw_x] = bottomclip;
+
+				if (oldtexturecolumn_bottom != -1)
+					rw_bottomtexturemid += FixedMul(rw_bottomtextureslide, oldtexturecolumn_bottom-bottomtexturecolumn);
+				oldtexturecolumn_bottom = bottomtexturecolumn;
 			}
 			else if (markfloor && (!rw_floormarked)) // no bottom wall
 				floorclip[rw_x] = bottomclip;
 		}
 
-		if (maskedtexture || numthicksides)
+		if (maskedtexturecol)
+			maskedtexturecol[rw_x] = texturecolumn + rw_offsetx;
+
+		if (thicksidecol)
+			thicksidecol[rw_x] = textureoffset;
+
+		if (maskedtextureheight)
+		{
+			if (curline->linedef->flags & ML_MIDPEG)
+				maskedtextureheight[rw_x] = max(rw_midtexturemid, rw_midtextureback);
+			else
+				maskedtextureheight[rw_x] = min(rw_midtexturemid, rw_midtextureback);
+		}
+
+		if (midtexture || maskedtextureheight)
 		{
-			// save texturecol
-			//  for backdrawing of masked mid texture
-			maskedtexturecol[rw_x] = texturecolumn + rw_offset_mid;
-
-			if (maskedtextureheight != NULL) {
-				maskedtextureheight[rw_x] = (curline->linedef->flags & ML_MIDPEG) ?
-											max(rw_midtexturemid, rw_midtextureback) :
-											min(rw_midtexturemid, rw_midtextureback);
+			if (oldtexturecolumn != -1)
+			{
+				rw_midtexturemid += FixedMul(rw_midtextureslide, oldtexturecolumn-texturecolumn);
+				rw_midtextureback += FixedMul(rw_midtexturebackslide, oldtexturecolumn-texturecolumn);
 			}
+
+			oldtexturecolumn = texturecolumn;
 		}
 
+		if (invscale)
+			invscale[rw_x] = 0xffffffffu / (unsigned)rw_scale;
+
 		if (dc_numlights)
 		{
 			for (i = 0; i < dc_numlights; i++)
@@ -1506,10 +1545,9 @@ static void R_AllocClippingTables(size_t range)
 static void R_AllocTextureColumnTables(size_t range)
 {
 	size_t pos = curtexturecolumntable - texturecolumntable;
+	size_t need = range * 3;
 
-	// For both tables, we reserve exactly an amount of memory that's equivalent to
-	// how many columns the seg will take on the entire screen (think about it)
-	if (pos + range < texturecolumntablesize)
+	if (pos + need < texturecolumntablesize)
 		return;
 
 	fixed_t *oldtable = texturecolumntable;
@@ -1518,7 +1556,7 @@ static void R_AllocTextureColumnTables(size_t range)
 	if (texturecolumntablesize == 0)
 		texturecolumntablesize = 16384;
 
-	texturecolumntablesize += range;
+	texturecolumntablesize += need;
 	texturecolumntable = Z_Realloc(texturecolumntable, texturecolumntablesize * sizeof (*texturecolumntable), PU_STATIC, NULL);
 	curtexturecolumntable = texturecolumntable + pos;
 
@@ -1532,6 +1570,8 @@ static void R_AllocTextureColumnTables(size_t range)
 			ds->maskedtexturecol = (ds->maskedtexturecol - oldtable) + texturecolumntable;
 		if (ds->thicksidecol + ds->x1 >= oldtable && ds->thicksidecol + ds->x1 <= oldlast)
 			ds->thicksidecol = (ds->thicksidecol - oldtable) + texturecolumntable;
+		if (ds->invscale + ds->x1 >= oldtable && ds->invscale + ds->x1 <= oldlast)
+			ds->invscale = (ds->invscale - oldtable) + texturecolumntable;
 	}
 }
 
@@ -1555,7 +1595,11 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 	fixed_t ceilingfrontslide, floorfrontslide, ceilingbackslide, floorbackslide;
 	static size_t maxdrawsegs = 0;
 
+	maskedtexturecol = NULL;
 	maskedtextureheight = NULL;
+	thicksidecol = NULL;
+	invscale = NULL;
+
 	//initialize segleft and segright
 	memset(&segleft, 0x00, sizeof(segleft));
 	memset(&segright, 0x00, sizeof(segright));
@@ -1712,6 +1756,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 	ds_p->maskedtexturecol = NULL;
 	ds_p->numthicksides = numthicksides = 0;
 	ds_p->thicksidecol = NULL;
+	ds_p->invscale = NULL;
 	ds_p->tsilheight = 0;
 
 	numbackffloors = 0;
@@ -1751,32 +1796,67 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			ceilingbackslide = FixedMul(backsector->c_slope->zdelta, FINECOSINE((lineangle-backsector->c_slope->xydirection)>>ANGLETOFINESHIFT));
 	}
 
+	rw_midtexturescalex = sidedef->scalex_mid;
+	rw_midtexturescaley = sidedef->scaley_mid;
+	rw_invmidtexturescalex = FixedDiv(FRACUNIT, rw_midtexturescalex);
+
 	if (!backsector)
 	{
-		fixed_t texheight;
-		// single sided line
 		midtexture = R_GetTextureNum(sidedef->midtexture);
-		texheight = textureheight[midtexture];
+
 		// a single sided line is terminal, so it must mark ends
 		markfloor = markceiling = true;
-		if (linedef->flags & ML_NOSKEW) {
-			if (linedef->flags & ML_DONTPEGBOTTOM)
-				rw_midtexturemid = frontsector->floorheight + texheight - viewz;
-			else
-				rw_midtexturemid = frontsector->ceilingheight - viewz;
-		}
-		else if (linedef->flags & ML_DONTPEGBOTTOM)
+
+		fixed_t rowoffset = sidedef->rowoffset + sidedef->offsety_mid;
+		fixed_t texheight = textureheight[midtexture];
+
+		if (rw_midtexturescaley > 0)
 		{
-			rw_midtexturemid = worldbottom + texheight;
-			rw_midtextureslide = floorfrontslide;
+			if (linedef->flags & ML_NOSKEW)
+			{
+				if (linedef->flags & ML_DONTPEGBOTTOM)
+					rw_midtexturemid = FixedMul(frontsector->floorheight - viewz, rw_midtexturescaley) + texheight;
+				else
+					rw_midtexturemid = FixedMul(frontsector->ceilingheight - viewz, rw_midtexturescaley);
+			}
+			else if (linedef->flags & ML_DONTPEGBOTTOM)
+			{
+				rw_midtexturemid = FixedMul(worldbottom, rw_midtexturescaley) + texheight;
+				rw_midtextureslide = floorfrontslide;
+			}
+			else
+			{
+				// top of texture at top
+				rw_midtexturemid = FixedMul(worldtop, rw_midtexturescaley);
+				rw_midtextureslide = ceilingfrontslide;
+			}
 		}
 		else
 		{
-			// top of texture at top
-			rw_midtexturemid = worldtop;
-			rw_midtextureslide = ceilingfrontslide;
+			// Upside down
+			rowoffset = -rowoffset;
+
+			if (linedef->flags & ML_NOSKEW)
+			{
+				if (linedef->flags & ML_DONTPEGBOTTOM)
+					rw_midtexturemid = FixedMul(frontsector->floorheight - viewz, rw_midtexturescaley);
+				else
+					rw_midtexturemid = FixedMul(frontsector->ceilingheight - viewz, rw_midtexturescaley) + texheight;
+			}
+			else if (linedef->flags & ML_DONTPEGBOTTOM)
+			{
+				rw_midtexturemid = FixedMul(worldbottom, rw_midtexturescaley);
+				rw_midtextureslide = floorfrontslide;
+			}
+			else
+			{
+				// top of texture at top
+				rw_midtexturemid = FixedMul(worldtop, rw_midtexturescaley) + texheight;
+				rw_midtextureslide = ceilingfrontslide;
+			}
 		}
-		rw_midtexturemid += sidedef->rowoffset + sidedef->offsety_mid;
+
+		rw_midtexturemid += rowoffset;
 
 		ds_p->silhouette = SIL_BOTH;
 		ds_p->sprtopclip = screenheightarray;
@@ -1901,6 +1981,8 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			//SoM: 3/22/2000: Check floor x and y offsets.
 			|| backsector->floorxoffset != frontsector->floorxoffset
 			|| backsector->flooryoffset != frontsector->flooryoffset
+			|| backsector->floorxscale != frontsector->floorxscale
+			|| backsector->flooryscale != frontsector->flooryscale
 			|| backsector->floorangle != frontsector->floorangle
 			//SoM: 3/22/2000: Prevents bleeding.
 			|| (frontsector->heightsec != -1 && frontsector->floorpic != skyflatnum)
@@ -1934,6 +2016,8 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			//SoM: 3/22/2000: Check floor x and y offsets.
 			|| backsector->ceilingxoffset != frontsector->ceilingxoffset
 			|| backsector->ceilingyoffset != frontsector->ceilingyoffset
+			|| backsector->ceilingxscale != frontsector->ceilingxscale
+			|| backsector->ceilingyscale != frontsector->ceilingyscale
 			|| backsector->ceilingangle != frontsector->ceilingangle
 			//SoM: 3/22/2000: Prevents bleeding.
 			|| (frontsector->heightsec != -1 && frontsector->ceilingpic != skyflatnum)
@@ -1962,16 +2046,28 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			}
 		}
 
+		fixed_t toprowoffset = sidedef->rowoffset + sidedef->offsety_top;
+		fixed_t botrowoffset = sidedef->rowoffset + sidedef->offsety_bottom;
+
 		// check TOP TEXTURE
 		if (!bothceilingssky // never draw the top texture if on
 			&& (worldhigh < worldtop || worldhighslope < worldtopslope))
 		{
-			fixed_t texheight;
-			// top texture
 			toptexture = R_GetTextureNum(sidedef->toptexture);
-			texheight = textureheight[toptexture];
 
-			if (!(linedef->flags & ML_SKEWTD)) { // Ignore slopes for lower/upper textures unless flag is checked
+			rw_toptexturescalex = sidedef->scalex_top;
+			rw_toptexturescaley = sidedef->scaley_top;
+
+			rw_invtoptexturescalex = FixedDiv(FRACUNIT, rw_toptexturescalex);
+
+			if (rw_toptexturescaley < 0)
+				toprowoffset = -toprowoffset;
+
+			fixed_t texheight = textureheight[toptexture];
+
+			// Ignore slopes for lower/upper textures unless flag is checked
+			if (!(linedef->flags & ML_SKEWTD))
+			{
 				if (linedef->flags & ML_DONTPEGTOP)
 					rw_toptexturemid = frontsector->ceilingheight - viewz;
 				else
@@ -1988,7 +2084,10 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 				rw_toptexturemid = worldhigh + texheight;
 				rw_toptextureslide = ceilingbackslide;
 			}
+
+			rw_toptexturemid = FixedMul(rw_toptexturemid, rw_toptexturescaley);
 		}
+
 		// check BOTTOM TEXTURE
 		if (!bothfloorssky // never draw the bottom texture if on
 			&& (worldlow > worldbottom || worldlowslope > worldbottomslope)) // Only if VISIBLE!!!
@@ -1996,7 +2095,17 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			// bottom texture
 			bottomtexture = R_GetTextureNum(sidedef->bottomtexture);
 
-			if (!(linedef->flags & ML_SKEWTD)) { // Ignore slopes for lower/upper textures unless flag is checked
+			rw_bottomtexturescalex = sidedef->scalex_bottom;
+			rw_bottomtexturescaley = sidedef->scaley_bottom;
+
+			rw_invbottomtexturescalex = FixedDiv(FRACUNIT, rw_bottomtexturescalex);
+
+			if (rw_bottomtexturescaley < 0)
+				botrowoffset = -botrowoffset;
+
+			// Ignore slopes for lower/upper textures unless flag is checked
+			if (!(linedef->flags & ML_SKEWTD))
+			{
 				if (linedef->flags & ML_DONTPEGBOTTOM)
 					rw_bottomtexturemid = frontsector->floorheight - viewz;
 				else
@@ -2009,18 +2118,22 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 				rw_bottomtexturemid = worldbottom;
 				rw_bottomtextureslide = floorfrontslide;
 			}
-			else {   // top of texture at top
+			else
+			{
+				// top of texture at top
 				rw_bottomtexturemid = worldlow;
 				rw_bottomtextureslide = floorbackslide;
 			}
+
+			rw_bottomtexturemid = FixedMul(rw_bottomtexturemid, rw_bottomtexturescaley);
 		}
 
-		rw_toptexturemid += sidedef->rowoffset + sidedef->offsety_top;
-		rw_bottomtexturemid += sidedef->rowoffset + sidedef->offsety_bot;
+		rw_toptexturemid += toprowoffset;
+		rw_bottomtexturemid += botrowoffset;
 
+		// allocate space for masked texture tables
 		R_AllocTextureColumnTables(rw_stopx - start);
 
-		// allocate space for masked texture tables
 		if (frontsector && backsector && !Tag_Compare(&frontsector->tags, &backsector->tags) && (backsector->ffloors || frontsector->ffloors))
 		{
 			ffloor_t *rover;
@@ -2031,10 +2144,9 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			// Used for height comparisons and etc across FOFs and slopes
 			fixed_t high1, highslope1, low1, lowslope1, high2, highslope2, low2, lowslope2;
 
-			//markceiling = markfloor = true;
 			maskedtexture = true;
 
-			ds_p->thicksidecol = maskedtexturecol = curtexturecolumntable - rw_x;
+			ds_p->thicksidecol = thicksidecol = curtexturecolumntable - rw_x;
 			curtexturecolumntable += rw_stopx - rw_x;
 
 			lowcut = max(worldbottom, worldlow) + viewz;
@@ -2213,21 +2325,20 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 
 			ds_p->numthicksides = numthicksides = i;
 		}
+
+		// masked midtexture
 		if (sidedef->midtexture > 0 && sidedef->midtexture < numtextures)
 		{
-			// masked midtexture
-			if (!ds_p->thicksidecol)
-			{
-				ds_p->maskedtexturecol = maskedtexturecol = curtexturecolumntable - rw_x;
-				curtexturecolumntable += rw_stopx - rw_x;
-			}
-			else
-				ds_p->maskedtexturecol = ds_p->thicksidecol;
+			ds_p->maskedtexturecol = maskedtexturecol = curtexturecolumntable - rw_x;
+			curtexturecolumntable += rw_stopx - rw_x;
 
 			maskedtextureheight = ds_p->maskedtextureheight; // note to red, this == &(ds_p->maskedtextureheight[0])
 
+			maskedtexture = true;
+
 			if (curline->polyseg)
-			{ // use REAL front and back floors please, so midtexture rendering isn't mucked up
+			{
+				// use REAL front and back floors please, so midtexture rendering isn't mucked up
 				rw_midtextureslide = rw_midtexturebackslide = 0;
 				if (linedef->flags & ML_MIDPEG)
 					rw_midtexturemid = rw_midtextureback = max(curline->frontsector->floorheight, curline->backsector->floorheight) - viewz;
@@ -2238,7 +2349,8 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			{
 				// Set midtexture starting height
 				if (linedef->flags & ML_NOSKEW)
-				{ // Ignore slopes when texturing
+				{
+					// Ignore slopes when texturing
 					rw_midtextureslide = rw_midtexturebackslide = 0;
 					if (linedef->flags & ML_MIDPEG)
 						rw_midtexturemid = rw_midtextureback = max(frontsector->floorheight, backsector->floorheight) - viewz;
@@ -2261,6 +2373,10 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 					rw_midtexturebackslide = ceilingbackslide;
 				}
 			}
+
+			rw_midtexturemid = FixedMul(rw_midtexturemid, rw_midtexturescaley);
+			rw_midtextureback = FixedMul(rw_midtextureback, rw_midtexturescaley);
+
 			rw_midtexturemid += sidedef->rowoffset + sidedef->offsety_mid;
 			rw_midtextureback += sidedef->rowoffset + sidedef->offsety_mid;
 
@@ -2273,6 +2389,8 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 
 	if (segtextured)
 	{
+		fixed_t sideoffset = sidedef->textureoffset;
+
 		offsetangle = rw_normalangle-rw_angle1;
 
 		if (offsetangle > ANGLE_180)
@@ -2297,14 +2415,20 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		if (rw_normalangle-rw_angle1 < ANGLE_180)
 			rw_offset = -rw_offset;
 
-		/// don't use texture offset for splats
-		rw_offset2 = rw_offset + curline->offset;
-		rw_offset += sidedef->textureoffset + curline->offset;
-		rw_offset_top = sidedef->offsetx_top;
-		rw_offset_mid = sidedef->offsetx_mid;
-		rw_offset_bot = sidedef->offsetx_bot;
+		rw_offset += curline->offset;
 		rw_centerangle = ANGLE_90 + viewangle - rw_normalangle;
 
+		rw_offset_top = sideoffset + sidedef->offsetx_top;
+		rw_offset_mid = sideoffset + sidedef->offsetx_mid;
+		rw_offset_bottom = sideoffset + sidedef->offsetx_bottom;
+
+		rw_offsetx = rw_offset_mid;
+		if (rw_midtexturescalex < 0)
+			rw_offsetx = -rw_offsetx;
+
+		if (numthicksides)
+			ds_p->offsetx = rw_offsetx;
+
 		// calculate light table
 		//  use different light tables
 		//  for horizontal / vertical / diagonal
@@ -2324,6 +2448,12 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 			walllights = scalelight[lightnum];
 	}
 
+	if (maskedtexture)
+	{
+		ds_p->invscale = invscale = curtexturecolumntable - rw_x;
+		curtexturecolumntable += rw_stopx - rw_x;
+	}
+
 	// if a floor / ceiling plane is on the wrong side
 	//  of the view plane, it is definitely invisible
 	//  and doesn't need to be marked.
diff --git a/src/r_skins.c b/src/r_skins.c
index 02e2b8558bbc62ab0c306f7bb6a47014c815c6a7..1605ca6c8921364974c9616ed98327c21eca1d59 100644
--- a/src/r_skins.c
+++ b/src/r_skins.c
@@ -377,7 +377,7 @@ static void SetSkin(player_t *player, INT32 skinnum)
 		P_SetScale(player->mo, 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
+		P_SetMobjState(player->mo, player->mo->state-states); // Prevent visual errors when switching between skins with differing number of frames
 	}
 }
 
diff --git a/src/r_splats.c b/src/r_splats.c
index 0b482d7988cc0e3a32f719a0f84a272fdf63d2f3..e9665e84a35a4089e72cfa40120157cf418cf9e9 100644
--- a/src/r_splats.c
+++ b/src/r_splats.c
@@ -369,7 +369,7 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr
 	ds_flatwidth = pSplat->width;
 	ds_flatheight = pSplat->height;
 
-	ds_powersoftwo = ds_solidcolor = false;
+	ds_powersoftwo = ds_solidcolor = ds_fog = false;
 
 	if (R_CheckSolidColorFlat())
 		ds_solidcolor = true;
@@ -381,9 +381,7 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr
 
 	if (pSplat->slope)
 	{
-		R_SetTiltedSpan(0);
 		R_SetScaledSlopePlane(pSplat->slope, vis->viewpoint.x, vis->viewpoint.y, vis->viewpoint.z, pSplat->xscale, pSplat->yscale, -pSplat->verts[0].x, pSplat->verts[0].y, vis->viewpoint.angle, pSplat->angle);
-		R_CalculateSlopeVectors();
 	}
 	else if (!ds_solidcolor)
 	{
@@ -391,8 +389,6 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr
 
 		if (pSplat->angle)
 		{
-			memset(cachedheight, 0, sizeof(cachedheight));
-
 			// Add the view offset, rotated by the plane angle.
 			fixed_t a = -pSplat->verts[0].x + vis->viewpoint.x;
 			fixed_t b = -pSplat->verts[0].y + vis->viewpoint.y;
@@ -547,29 +543,18 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr
 			angle_t planecos = FINECOSINE(angle);
 			angle_t planesin = FINESINE(angle);
 
-			if (planeheight != cachedheight[y])
-			{
-				cachedheight[y] = planeheight;
-				distance = cacheddistance[y] = FixedMul(planeheight, yslope[y]);
-				span = abs(centery - y);
-
-				if (span) // Don't divide by zero
-				{
-					xstep = FixedMul(planesin, planeheight) / span;
-					ystep = FixedMul(planecos, planeheight) / span;
-				}
-				else
-					xstep = ystep = FRACUNIT;
+			// [RH] Notice that I dumped the caching scheme used by Doom.
+			// It did not offer any appreciable speedup.
+			distance = FixedMul(planeheight, yslope[y]);
+			span = abs(centery - y);
 
-				cachedxstep[y] = xstep;
-				cachedystep[y] = ystep;
-			}
-			else
+			if (span) // Don't divide by zero
 			{
-				distance = cacheddistance[y];
-				xstep = cachedxstep[y];
-				ystep = cachedystep[y];
+				xstep = FixedMul(planesin, planeheight) / span;
+				ystep = FixedMul(planecos, planeheight) / span;
 			}
+			else
+				xstep = ystep = FRACUNIT;
 
 			ds_xstep = FixedDiv(xstep, pSplat->xscale);
 			ds_ystep = FixedDiv(ystep, pSplat->yscale);
@@ -586,9 +571,6 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr
 		rastertab[y].minx = INT32_MAX;
 		rastertab[y].maxx = INT32_MIN;
 	}
-
-	if (!ds_solidcolor && pSplat->angle && !pSplat->slope)
-		memset(cachedheight, 0, sizeof(cachedheight));
 }
 
 static void prepare_rastertab(void)
diff --git a/src/r_things.c b/src/r_things.c
index dd0b5a1861e034ba23df0fc17b41d4f8dea057b0..9d2868d4cf2c66fc71038ddf953bdf8a34e5a77b 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -1627,6 +1627,8 @@ static void R_ProjectSprite(mobj_t *thing)
 	height = interp.height; // Ditto
 
 	// transform the origin point
+	if (thing->type == MT_OVERLAY) // Handle overlays
+		R_ThingOffsetOverlay(thing, &interp.x, &interp.y);
 	tr_x = interp.x - viewx;
 	tr_y = interp.y - viewy;
 
@@ -1972,6 +1974,8 @@ static void R_ProjectSprite(mobj_t *thing)
 		radius = tracer_interp.radius; // For drop shadows
 		height = tracer_interp.height; // Ditto
 
+		if (thing->type == MT_OVERLAY) // Handle overlays
+			R_ThingOffsetOverlay(thing, &tracer_interp.x, &tracer_interp.y);
 		tr_x = (tracer_interp.x + sort_x) - viewx;
 		tr_y = (tracer_interp.y + sort_y) - viewy;
 		tz = FixedMul(tr_x, viewcos) + FixedMul(tr_y, viewsin);
@@ -3566,6 +3570,50 @@ boolean R_ThingIsFullDark(mobj_t *thing)
 	return ((thing->frame & FF_BRIGHTMASK) == FF_FULLDARK || (thing->renderflags & RF_BRIGHTMASK) == RF_FULLDARK);
 }
 
+// Offsets MT_OVERLAY towards the camera at render-time - Works in splitscreen!
+// The &x and &y arguments should be pre-interpolated, and will be modified
+void R_ThingOffsetOverlay(mobj_t *thing, fixed_t *x, fixed_t *y)
+{
+	mobj_t *mobj = thing;
+	INT16 offset = 0; // Offset towards or away from the camera, and how much
+	fixed_t offsetscale = thing->scale; // Occasionally needs to be interpolated
+	angle_t viewingangle;
+	UINT8 looplimit = 255; // Prevent infinite loops - A chain of 255 connected overlays is enough for any sane use case
+
+#ifdef PARANOIA
+	if (P_MobjWasRemoved(mobj) || !x || !y)
+		I_Error("R_ThingOffsetOverlay: thing, x, or y is invalid");
+#endif
+
+	do // Get the overlay's offset
+	{
+		// Does the overlay use FF_ANIMATE? If not, if var1 is non-zero, it's an underlay instead of an overlay
+		if (!(mobj->state->frame & FF_ANIMATE) && mobj->state->var1)
+			offset += 1; // Underlay below the target, away from the camera
+		else
+			offset -= 1; // Overlay on top of the target, towards the camera
+
+		looplimit -= 1;
+		mobj = mobj->target;
+	} while (!P_MobjWasRemoved(mobj) && mobj->type == MT_OVERLAY && looplimit > 0); // Handle overlays following other overlays
+
+	// Does the offset scale need to be interpolated?
+	if (thing->scale != thing->old_scale && R_UsingFrameInterpolation() && !paused)
+	{
+		interpmobjstate_t interp = {0};
+		R_InterpolateMobjState(thing, rendertimefrac, &interp);
+		offsetscale = interp.scale;
+	}
+
+
+	// Get the angle from the camera to the X and Y coordinates
+	viewingangle = R_PointToAngle(*x, *y);
+
+	// Finally, offset the X and Y coordinates towards or away from the camera
+	*x += P_ReturnThrustX(thing, viewingangle, FixedMul(offset * (FRACUNIT/4), offsetscale));
+	*y += P_ReturnThrustY(thing, viewingangle, FixedMul(offset * (FRACUNIT/4), offsetscale));
+}
+
 //
 // R_DrawMasked
 //
diff --git a/src/r_things.h b/src/r_things.h
index 318234886bbbfa2b603f1883d50bbfea4c67d370..ed2156baab4b15784360c0664352a1b8b2461d19 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -88,6 +88,8 @@ boolean R_ThingIsFullBright (mobj_t *thing);
 boolean R_ThingIsSemiBright (mobj_t *thing);
 boolean R_ThingIsFullDark (mobj_t *thing);
 
+void R_ThingOffsetOverlay (mobj_t *thing, fixed_t *outx, fixed_t *outy);
+
 // --------------
 // MASKED DRAWING
 // --------------
diff --git a/src/screen.c b/src/screen.c
index 3c50ec67eadef1d4e43441498f4ea96df0542f8d..ca59b251dce1f6f1371af433b010a18b4304e621 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -98,14 +98,6 @@ UINT8 *scr_borderpatch; // flat used to fill the reduced view borders set at ST_
 //  Short and Tall sky drawer, for the current color mode
 void (*walldrawerfunc)(void);
 
-boolean R_486 = false;
-boolean R_586 = false;
-boolean R_MMX = false;
-boolean R_SSE = false;
-boolean R_3DNow = false;
-boolean R_MMXExt = false;
-boolean R_SSE2 = false;
-
 void SCR_SetDrawFuncs(void)
 {
 	//
@@ -225,48 +217,6 @@ void SCR_SetMode(void)
 //
 void SCR_Startup(void)
 {
-	const CPUInfoFlags *RCpuInfo = I_CPUInfo();
-	if (!M_CheckParm("-NOCPUID") && RCpuInfo)
-	{
-#if defined (__i386__) || defined (_M_IX86) || defined (__WATCOMC__)
-		R_486 = true;
-#endif
-		if (RCpuInfo->RDTSC)
-			R_586 = true;
-		if (RCpuInfo->MMX)
-			R_MMX = true;
-		if (RCpuInfo->AMD3DNow)
-			R_3DNow = true;
-		if (RCpuInfo->MMXExt)
-			R_MMXExt = true;
-		if (RCpuInfo->SSE)
-			R_SSE = true;
-		if (RCpuInfo->SSE2)
-			R_SSE2 = true;
-		CONS_Printf("CPU Info: 486: %i, 586: %i, MMX: %i, 3DNow: %i, MMXExt: %i, SSE2: %i\n", R_486, R_586, R_MMX, R_3DNow, R_MMXExt, R_SSE2);
-	}
-
-	if (M_CheckParm("-486"))
-		R_486 = true;
-	if (M_CheckParm("-586"))
-		R_586 = true;
-	if (M_CheckParm("-MMX"))
-		R_MMX = true;
-	if (M_CheckParm("-3DNow"))
-		R_3DNow = true;
-	if (M_CheckParm("-MMXExt"))
-		R_MMXExt = true;
-
-	if (M_CheckParm("-SSE"))
-		R_SSE = true;
-	if (M_CheckParm("-noSSE"))
-		R_SSE = false;
-
-	if (M_CheckParm("-SSE2"))
-		R_SSE2 = true;
-
-	M_SetupMemcpy();
-
 	if (dedicated)
 	{
 		V_Init();
diff --git a/src/screen.h b/src/screen.h
index 46c1b99c6ca962a18944e2e41fcd4c6d147d5327..e4c1006c35b79a7f9948f818c0a3b03edf84c558 100644
--- a/src/screen.h
+++ b/src/screen.h
@@ -172,17 +172,6 @@ extern void (*spanfunc)(void);
 extern void (*spanfuncs[SPANDRAWFUNC_MAX])(void);
 extern void (*spanfuncs_npo2[SPANDRAWFUNC_MAX])(void);
 
-// -----
-// CPUID
-// -----
-extern boolean R_ASM;
-extern boolean R_486;
-extern boolean R_586;
-extern boolean R_MMX;
-extern boolean R_3DNow;
-extern boolean R_MMXExt;
-extern boolean R_SSE2;
-
 // ----------------
 // screen variables
 // ----------------
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index 450237149b08c9225b7204a9d31c63e311a06c29..847806270f78175624fd2fabcdcf32a0b9e0e29e 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -41,6 +41,12 @@ typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T);
 #include <ntsecapi.h>
 #undef SystemFunction036
 
+// A little more than the minimum sleep duration on Windows.
+// May be incorrect for other platforms, but we don't currently have a way to
+// query the scheduler granularity. SDL will do what's needed to make this as
+// low as possible though.
+#define MIN_SLEEP_DURATION_MS 2.1
+
 #endif
 #include <stdio.h>
 #include <stdlib.h>
@@ -138,7 +144,9 @@ typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T);
 #endif
 
 #if defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON)
+#ifndef NOEXECINFO
 #include <execinfo.h>
+#endif
 #include <time.h>
 #define UNIXBACKTRACE
 #endif
@@ -269,13 +277,17 @@ UINT8 keyboard_started = false;
 static void write_backtrace(INT32 signal)
 {
 	int fd = -1;
+#ifndef NOEXECINFO
 	size_t size;
+#endif
 	time_t rawtime;
 	struct tm timeinfo;
 	ssize_t junk;
 
 	enum { BT_SIZE = 1024, STR_SIZE = 32 };
+#ifndef NOEXECINFO
 	void *array[BT_SIZE];
+#endif
 	char timestr[STR_SIZE];
 
 	const char *error = "An error occurred within SRB2! Send this stack trace to someone who can help!\n";
@@ -308,12 +320,14 @@ static void write_backtrace(INT32 signal)
 	CRASHLOG_WRITE(strsignal(signal));
 	CRASHLOG_WRITE("\n"); // Newline for the signal name
 
+#ifndef NOEXECINFO
 	CRASHLOG_STDERR_WRITE("\nBacktrace:\n");
 
 	// Flood the output and log with the backtrace
 	size = backtrace(array, BT_SIZE);
 	backtrace_symbols_fd(array, size, fd);
 	backtrace_symbols_fd(array, size, STDERR_FILENO);
+#endif
 
 	CRASHLOG_WRITE("\n"); // Write another newline to the log so it looks nice :)
 	(void)junk;
@@ -638,6 +652,7 @@ void I_GetConsoleEvents(void)
 	else if (tty_con.cursor < sizeof (tty_con.buffer))
 	{
 		// push regular character
+		ev.type = ev_text;
 		ev.key = tty_con.buffer[tty_con.cursor] = key;
 		tty_con.cursor++;
 		// print the current line (this is differential)
@@ -2266,6 +2281,52 @@ void I_Sleep(UINT32 ms)
 	SDL_Delay(ms);
 }
 
+void I_SleepDuration(precise_t duration)
+{
+#if defined(__linux__) || defined(__FreeBSD__)
+	UINT64 precision = I_GetPrecisePrecision();
+	struct timespec ts = {
+		.tv_sec = duration / precision,
+		.tv_nsec = duration * 1000000000 / precision % 1000000000,
+	};
+	int status;
+	do status = clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, &ts);
+	while (status == EINTR);
+#else
+	UINT64 precision = I_GetPrecisePrecision();
+	INT32 sleepvalue = cv_sleep.value;
+	UINT64 delaygranularity;
+	precise_t cur;
+	precise_t dest;
+
+	{
+		double gran = round(((double)(precision / 1000) * sleepvalue * MIN_SLEEP_DURATION_MS));
+		delaygranularity = (UINT64)gran;
+	}
+
+	cur = I_GetPreciseTime();
+	dest = cur + duration;
+
+	// the reason this is not dest > cur is because the precise counter may wrap
+	// two's complement arithmetic is our friend here, though!
+	// e.g. cur 0xFFFFFFFFFFFFFFFE = -2, dest 0x0000000000000001 = 1
+	// 0x0000000000000001 - 0xFFFFFFFFFFFFFFFE = 3
+	while ((INT64)(dest - cur) > 0)
+	{
+		// If our cv_sleep value exceeds the remaining sleep duration, use the
+		// hard sleep function.
+		if (sleepvalue > 0 && (dest - cur) > delaygranularity)
+		{
+			I_Sleep(sleepvalue);
+		}
+
+		// Otherwise, this is a spinloop.
+
+		cur = I_GetPreciseTime();
+	}
+#endif
+}
+
 #ifdef NEWSIGNALHANDLER
 ATTRNORETURN static FUNCNORETURN void newsignalhandler_Warn(const char *pr)
 {
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index 590d7d142a7a536678bfb96d3891c78264a19fba..d3a602c05803fcd5d6aced670321aad5c723ea74 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -382,10 +382,8 @@ static INT32 Impl_SDL_Scancode_To_Keycode(SDL_Scancode code)
 	return 0;
 }
 
-static boolean IgnoreMouse(void)
+static boolean ShouldIgnoreMouse(void)
 {
-	if (cv_alwaysgrabmouse.value)
-		return false;
 	if (menuactive)
 		return !M_MouseNeeded();
 	if (paused || con_destlines || chat_on)
@@ -393,11 +391,20 @@ static boolean IgnoreMouse(void)
 	if (gamestate != GS_LEVEL && gamestate != GS_INTERMISSION &&
 			gamestate != GS_CONTINUING && gamestate != GS_CUTSCENE)
 		return true;
-	if (!mousegrabbedbylua)
-		return true;
 	return false;
 }
 
+static boolean ShouldGrabMouse(void)
+{
+	if (cv_alwaysgrabmouse.value)
+		return true;
+	if (ShouldIgnoreMouse())
+		return false;
+	if (!mousegrabbedbylua)
+		return false;
+	return true;
+}
+
 static void SDLdoGrabMouse(void)
 {
 	SDL_ShowCursor(SDL_DISABLE);
@@ -424,7 +431,7 @@ void I_UpdateMouseGrab(void)
 {
 	if (SDL_WasInit(SDL_INIT_VIDEO) == SDL_INIT_VIDEO && window != NULL
 	&& SDL_GetMouseFocus() == window && SDL_GetKeyboardFocus() == window
-	&& USE_MOUSEINPUT && !IgnoreMouse())
+	&& USE_MOUSEINPUT && ShouldGrabMouse())
 		SDLdoGrabMouse();
 }
 
@@ -640,7 +647,7 @@ static void Impl_HandleWindowEvent(SDL_WindowEvent evt)
 		}
 		//else firsttimeonmouse = SDL_FALSE;
 
-		if (USE_MOUSEINPUT && !IgnoreMouse())
+		if (USE_MOUSEINPUT && ShouldGrabMouse())
 			SDLdoGrabMouse();
 	}
 	else if (!mousefocus && !kbfocus)
@@ -686,13 +693,26 @@ static void Impl_HandleKeyboardEvent(SDL_KeyboardEvent evt, Uint32 type)
 	if (event.key) D_PostEvent(&event);
 }
 
+static void Impl_HandleTextEvent(SDL_TextInputEvent evt)
+{
+	event_t event;
+	event.type = ev_text;
+	if (evt.text[1] != '\0')
+	{
+		// limit ourselves to ASCII for now, we can add UTF-8 support later
+		return;
+	}
+	event.key = evt.text[0];
+	D_PostEvent(&event);
+}
+
 static void Impl_HandleMouseMotionEvent(SDL_MouseMotionEvent evt)
 {
 	static boolean firstmove = true;
 
 	if (USE_MOUSEINPUT)
 	{
-		if ((SDL_GetMouseFocus() != window && SDL_GetKeyboardFocus() != window) || (IgnoreMouse() && !firstmove))
+		if ((SDL_GetMouseFocus() != window && SDL_GetKeyboardFocus() != window) || (!ShouldGrabMouse() && !firstmove))
 		{
 			SDLdoUngrabMouse();
 			firstmove = false;
@@ -745,7 +765,7 @@ static void Impl_HandleMouseButtonEvent(SDL_MouseButtonEvent evt, Uint32 type)
 	// this apparently makes a mouse button down event but not a mouse button up event,
 	// resulting in whatever key was pressed down getting "stuck" if we don't ignore it.
 	// -- Monster Iestyn (28/05/18)
-	if (SDL_GetMouseFocus() != window || IgnoreMouse())
+	if (SDL_GetMouseFocus() != window || ShouldIgnoreMouse())
 		return;
 
 	/// \todo inputEvent.button.which
@@ -934,6 +954,9 @@ void I_GetEvent(void)
 			case SDL_KEYDOWN:
 				Impl_HandleKeyboardEvent(evt.key, evt.type);
 				break;
+			case SDL_TEXTINPUT:
+				Impl_HandleTextEvent(evt.text);
+				break;
 			case SDL_MOUSEMOTION:
 				//if (!mouseMotionOnce)
 				Impl_HandleMouseMotionEvent(evt.motion);
@@ -1127,7 +1150,7 @@ void I_StartupMouse(void)
 	}
 	else
 		firsttimeonmouse = SDL_FALSE;
-	if (cv_usemouse.value && !IgnoreMouse())
+	if (cv_usemouse.value && ShouldGrabMouse())
 		SDLdoGrabMouse();
 	else
 		SDLdoUngrabMouse();
diff --git a/src/sdl/macosx/Srb2mac.pbproj/project.pbxproj b/src/sdl/macosx/Srb2mac.pbproj/project.pbxproj
index 909bb2ced72d7f3cc7a157d87fea0eac37dfd178..40f580be12967abc4e049372810215e89005ce8f 100644
--- a/src/sdl/macosx/Srb2mac.pbproj/project.pbxproj
+++ b/src/sdl/macosx/Srb2mac.pbproj/project.pbxproj
@@ -2133,7 +2133,7 @@
 				INSTALL_PATH = "$(HOME)/Applications";
 				JAVA_COMPILER_DEBUGGING_SYMBOLS = NO;
 				OPTIMIZATION_CFLAGS = "-O2";
-				OTHER_CFLAGS = "-DMAC_ALERT -DUNIXCOMMON -DSDLMAIN -DHAVE_MIXER -DHAVE_PNG -D_BIG_ENDIAN -DSTDC_HEADERS -DSDL -Wall -Winline -fno-strict-aliasing";
+				OTHER_CFLAGS = "-DMAC_ALERT -DUNIXCOMMON -DSDLMAIN -DHAVE_MIXER -DHAVE_PNG -D_BIG_ENDIAN -DSTDC_HEADERS -DSDL -Wall -Winline -fno-strict-aliasing -fwrapv";
 				OTHER_REZFLAGS = "";
 				PREBINDING = NO;
 				PRODUCT_NAME = Srb2;
diff --git a/src/taglist.c b/src/taglist.c
index e4e385b9eb7f57e9f0758418e6db2ecbe7748a34..7bd0046275e6d5c3bf463fa9a65c230391ae92ca 100644
--- a/src/taglist.c
+++ b/src/taglist.c
@@ -180,10 +180,10 @@ void Taggroup_Add (taggroup_t *garray[], const mtag_t tag, size_t id)
 	if (Taggroup_Find(group, id) != (size_t)-1)
 		return;
 
-	if (! in_bit_array(tags_available, tag))
+	if (! in_bit_array(tags_available, (UINT16)tag))
 	{
 		num_tags++;
-		set_bit_array(tags_available, tag);
+		set_bit_array(tags_available, (UINT16)tag);
 	}
 
 	// Create group if empty.
@@ -220,10 +220,10 @@ static void Taggroup_Add_Init(taggroup_t *garray[], const mtag_t tag, size_t id)
 
 	group = garray[(UINT16)tag];
 
-	if (! in_bit_array(tags_available, tag))
+	if (! in_bit_array(tags_available, (UINT16)tag))
 	{
 		num_tags++;
-		set_bit_array(tags_available, tag);
+		set_bit_array(tags_available, (UINT16)tag);
 	}
 
 	// Create group if empty.
@@ -271,7 +271,7 @@ void Taggroup_Remove (taggroup_t *garray[], const mtag_t tag, size_t id)
 	if (group->count == 1 && total_elements_with_tag(tag) == 1)
 	{
 		num_tags--;
-		unset_bit_array(tags_available, tag);
+		unset_bit_array(tags_available, (UINT16)tag);
 	}
 
 	// Strip away taggroup if no elements left.
diff --git a/src/v_video.c b/src/v_video.c
index dd62c16ae94071dc22dd7ef58c1e69c30ef26f93..9e1bac2e5ec417d585957d8fc788ceb58ec054c1 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -1163,12 +1163,32 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 {
 	UINT8 *dest;
 	const UINT8 *deststop;
+	UINT32 alphalevel = ((c & V_ALPHAMASK) >> V_ALPHASHIFT);
+	UINT32 blendmode = ((c & V_BLENDMASK) >> V_BLENDSHIFT);
 
 	UINT8 perplayershuffle = 0;
 
 	if (rendermode == render_none)
 		return;
 
+	v_translevel = NULL;
+	if (alphalevel || blendmode)
+	{
+		if (alphalevel == 10) // V_HUDTRANSHALF
+			alphalevel = hudminusalpha[st_translucency];
+		else if (alphalevel == 11) // V_HUDTRANS
+			alphalevel = 10 - st_translucency;
+		else if (alphalevel == 12) // V_HUDTRANSDOUBLE
+			alphalevel = hudplusalpha[st_translucency];
+
+		if (alphalevel >= 10)
+			return; // invis
+
+		if (alphalevel || blendmode)
+			v_translevel = R_GetBlendTable(blendmode+1, alphalevel);
+	}
+
+
 #ifdef HWRENDER
 	//if (rendermode != render_soft && !con_startup)		// Not this again
 	if (rendermode == render_opengl)
@@ -1178,6 +1198,8 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 	}
 #endif
 
+	
+
 	if (splitscreen && (c & V_PERPLAYER))
 	{
 		fixed_t adjusty = ((c & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)>>1;
@@ -1312,8 +1334,21 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 
 	c &= 255;
 
-	for (;(--h >= 0) && dest < deststop; dest += vid.width)
-		memset(dest, c, w * vid.bpp);
+	// borrowing this from jimitia's new hud drawing functions rq
+	if (alphalevel)
+	{
+		v_translevel += c<<8;
+		for (;(--h >= 0) && dest < deststop; dest += vid.width)
+		{
+			for (x = 0; x < w; x++)
+				dest[x] = v_translevel[dest[x]];
+		}
+	}
+	else
+	{
+		for (;(--h >= 0) && dest < deststop; dest += vid.width)
+			memset(dest, c, w * vid.bpp);
+	}
 }
 
 #ifdef HWRENDER
diff --git a/src/w_wad.c b/src/w_wad.c
index d012182f18208bd05435c9f7ee8e43d0f27256e2..051469ef57285b28003b43569de8e625856ce5e8 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -208,7 +208,7 @@ static void W_LoadDehackedLumpsPK3(UINT16 wadnum, boolean mainfile)
 	posStart = W_CheckNumForFullNamePK3("Init.lua", wadnum, 0);
 	if (posStart != INT16_MAX)
 	{
-		LUA_LoadLump(wadnum, posStart, true);
+		LUA_DoLump(wadnum, posStart, true);
 	}
 	else
 	{
@@ -217,7 +217,7 @@ static void W_LoadDehackedLumpsPK3(UINT16 wadnum, boolean mainfile)
 		{
 			posEnd = W_CheckNumForFolderEndPK3("Lua/", wadnum, posStart);
 			for (; posStart < posEnd; posStart++)
-				LUA_LoadLump(wadnum, posStart, true);
+				LUA_DoLump(wadnum, posStart, true);
 		}
 	}
 
@@ -250,7 +250,7 @@ static void W_LoadDehackedLumps(UINT16 wadnum, boolean mainfile)
 		lumpinfo_t *lump_p = wadfiles[wadnum]->lumpinfo;
 		for (lump = 0; lump < wadfiles[wadnum]->numlumps; lump++, lump_p++)
 			if (memcmp(lump_p->name,"LUA_",4)==0)
-				LUA_LoadLump(wadnum, lump, true);
+				LUA_DoLump(wadnum, lump, true);
 	}
 
 	{
@@ -993,7 +993,7 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
 		DEH_LoadDehackedLumpPwad(numwadfiles - 1, 0, mainfile);
 		break;
 	case RET_LUA:
-		LUA_LoadLump(numwadfiles - 1, 0, true);
+		LUA_DoLump(numwadfiles - 1, 0, true);
 		break;
 	default:
 		break;