diff --git a/.gitlab/ci/templates/srb2ci.yml b/.gitlab/ci/templates/srb2ci.yml
index 3716d9df921407cb29081af301a5b6a3d92db459..bdf8a3ed66d8126559c25e5563da432da41932e0 100644
--- a/.gitlab/ci/templates/srb2ci.yml
+++ b/.gitlab/ci/templates/srb2ci.yml
@@ -61,7 +61,7 @@
     - - |
           # apt_update
           echo -e "\e[0Ksection_start:`date +%s`:apt_update[collapsed=true]\r\e[0KUpdating APT listing"
-      - apt-get update
+      - timeout 2m apt-get update || timeout 2m apt-get update
       - |
           # apt_update
           echo -e "\e[0Ksection_end:`date +%s`:apt_update\r\e[0K"
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 20caecf7bd6a65a20631318799016da319d3f13d..fbd29cddda5e3caae29aaee01bbed579375730a5 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -172,6 +172,11 @@ if("${CMAKE_SYSTEM_NAME}" MATCHES "Linux")
 	endif()
 endif()
 
+if("${CMAKE_SYSTEM_NAME}" MATCHES "Haiku")
+	target_compile_definitions(SRB2SDL2 PRIVATE -DNOEXECINFO)
+	target_link_libraries(SRB2SDL2 PRIVATE network)
+endif()
+
 if("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin")
 	target_compile_definitions(SRB2SDL2 PRIVATE -DMACOSX)
 endif()
diff --git a/src/Makefile.d/detect.mk b/src/Makefile.d/detect.mk
index 9e27369462b00c407f02cb95dbda22ef284a91d1..853ad3d6df728d7c69ea64b204cf099547f59e45 100644
--- a/src/Makefile.d/detect.mk
+++ b/src/Makefile.d/detect.mk
@@ -4,7 +4,6 @@
 
 # Previously featured:\
 	PANDORA\
-	HAIKU\
 	DUMMY\
 	DJGPPDOS\
 	SOLARIS\
@@ -17,6 +16,7 @@ all_systems:=\
 	UNIX\
 	LINUX\
 	FREEBSD\
+	HAIKU\
 
 # check for user specified system
 ifeq (,$(filter $(all_systems),$(.VARIABLES)))
@@ -35,6 +35,8 @@ system:=$(shell uname -s)
 
 ifeq ($(system),Linux)
 new_system:=LINUX
+else ifeq ($(system),Haiku)
+new_system:=HAIKU
 else
 
 $(error \
diff --git a/src/Makefile.d/haiku.mk b/src/Makefile.d/haiku.mk
new file mode 100644
index 0000000000000000000000000000000000000000..6742ec75b58f992a916a99752c72ca827f4dab5e
--- /dev/null
+++ b/src/Makefile.d/haiku.mk
@@ -0,0 +1,71 @@
+#
+# Makefile options for Haiku
+#
+
+opts+=-DUNIXCOMMON -DLUA_USE_POSIX
+
+ifndef DEDICATED
+ifndef DUMMY
+SDL?=1
+DEDICATED?=0
+endif
+endif
+
+NOEXECINFO=1
+
+ifeq (${SDL},1)
+EXENAME?=srb2haiku
+else ifeq (${DEDICATED},1)
+EXENAME?=srb2haikud
+endif
+
+ifndef NONET
+libs+=-lnetwork
+endif
+
+define _set =
+$(1)_CFLAGS?=$($(1)_opts)
+$(1)_LDFLAGS?=$($(1)_libs)
+endef
+
+lib:=../libs/gme
+LIBGME_opts:=-I$(lib)/include
+LIBGME_libs:=-l:libgme.so.0
+$(eval $(call _set,LIBGME))
+
+lib:=../libs/libopenmpt
+LIBOPENMPT_opts:=-I$(lib)/inc
+LIBOPENMPT_libs:=-l:libopenmpt.so.0
+$(eval $(call _set,LIBOPENMPT))
+
+ifdef SDL
+lib:=../libs/SDL2_mixer
+mixer_opts:=-I$(lib)/include
+mixer_libs:=-l:libSDL2_mixer-2.0.so.0
+
+lib:=../libs/SDL2
+SDL_opts:=-I$(lib)/include $(mixer_opts)
+SDL_libs:=$(mixer_libs) -l:libSDL2-2.0.so.0
+$(eval $(call _set,SDL))
+endif
+
+lib:=../libs/zlib
+ZLIB_opts:=-I$(lib)
+ZLIB_libs:=-l:libz.so.1
+$(eval $(call _set,ZLIB))
+
+ifndef PNG_CONFIG
+PNG_opts:=
+PNG_libs:=-l:libpng16.so.16
+$(eval $(call _set,PNG))
+endif
+
+lib:=../libs/curl
+CURL_opts:=-I$(lib)/include
+CURL_libs:=-l:libcurl.so.4
+$(eval $(call _set,CURL))
+
+lib:=../libs/miniupnpc
+MINIUPNPC_opts:=-I$(lib)/include
+MINIUPNPC_libs:=-l:libminiupnpc.so.17
+$(eval $(call _set,MINIUPNPC))
diff --git a/src/Makefile.d/platform.mk b/src/Makefile.d/platform.mk
index d9a2954f60c54b30c121c0467022c1707f720fc0..d5423a397384922689260546b71948447846f3e1 100644
--- a/src/Makefile.d/platform.mk
+++ b/src/Makefile.d/platform.mk
@@ -35,6 +35,10 @@ endif
 else ifdef FREEBSD
 UNIX=1
 platform=freebsd
+else ifdef HAIKU
+# Give Haiku its own configuration, since it
+# isn't actually UNIX.
+include Makefile.d/haiku.mk
 else ifdef SOLARIS # FIXME: UNTESTED
 UNIX=1
 platform=solaris
diff --git a/src/Makefile.d/sdl.mk b/src/Makefile.d/sdl.mk
index a1bfa33038bbacebada85790a7565fceb9440985..d5e83989c2d1955146a8c228c15034d9f2b9e091 100644
--- a/src/Makefile.d/sdl.mk
+++ b/src/Makefile.d/sdl.mk
@@ -33,11 +33,13 @@ else
 opts+=-DHAVE_MIXER
 sources+=sdl/mixer_sound.c
 
-  ifdef HAVE_MIXERX
-  opts+=-DHAVE_MIXERX
-  libs+=-lSDL2_mixer_ext
-  else
-  libs+=-lSDL2_mixer
+  ifndef HAIKU # Haiku has a special import path
+    ifdef HAVE_MIXERX
+    opts+=-DHAVE_MIXERX
+    libs+=-lSDL2_mixer_ext
+    else
+    libs+=-lSDL2_mixer
+    endif
   endif
 endif
 
diff --git a/src/am_map.c b/src/am_map.c
index df3a45cfff504e046ddaa6ecc03cf9f9d548e45f..36b62f2f9b0157c58a2becd8b55624302d29ae0b 100644
--- a/src/am_map.c
+++ b/src/am_map.c
@@ -1076,6 +1076,9 @@ static inline void AM_drawPlayers(void)
 		if (!playeringame[i] || players[i].spectator)
 			continue;
 
+		if (!players[i].mo)
+			continue;
+
 		p = &players[i];
 		if (p->skincolor > 0)
 			color = R_GetTranslationColormap(TC_DEFAULT, p->skincolor, GTC_CACHE)[GREENS + 8];
diff --git a/src/command.c b/src/command.c
index 399714bd0d65758489634477a8bf84b2444ff659..8f9166361d35f9893160d6a2ca54f88e40b1147a 100644
--- a/src/command.c
+++ b/src/command.c
@@ -1988,7 +1988,7 @@ static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth)
 	if (!var->string)
 		I_Error("CV_Set: %s no string set!\n", var->name);
 #endif
-	if (!var || !var->string || !value || !stricmp(var->string, value))
+	if (!var || !var->string || !value || (var->can_change == NULL && !stricmp(var->string, value)))
 		return; // no changes
 
 	if (var->flags & CV_NETVAR)
diff --git a/src/d_main.c b/src/d_main.c
index a6a9385d29d9529374417aa2f37c8978fe1b3c0b..444671e5a35086ec448ae5dbc83b8c6fc52679e1 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -1600,7 +1600,7 @@ void D_SRB2Main(void)
 	{
 		if (!M_IsNextParm())
 			I_Error("usage: -room <room_id>\nCheck the Master Server's webpage for room ID numbers.\n");
-		ms_RoomId = atoi(M_GetNextParm());
+		CV_SetValue(&cv_masterserver_room_id, atoi(M_GetNextParm()));
 
 #ifdef UPDATE_ALERT
 		GetMODVersion_Console();
diff --git a/src/d_think.h b/src/d_think.h
index 58912458783a2f3b2187e50ebaf00a667a0e0a78..76c1bb5b8af11a5557bab32705897445e699ca5b 100644
--- a/src/d_think.h
+++ b/src/d_think.h
@@ -51,6 +51,8 @@ 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 removing;
 	boolean cachable;
 
 #ifdef PARANOIA
diff --git a/src/dedicated/i_system.c b/src/dedicated/i_system.c
index 23c0149ca789bcdd34bc464590b1edc67398ddbf..22d3d1200f78d3e2279ec44ee297589f5357025c 100644
--- a/src/dedicated/i_system.c
+++ b/src/dedicated/i_system.c
@@ -705,10 +705,9 @@ typedef struct
 
 static feild_t tty_con;
 
-// when printing general stuff to stdout stderr (Sys_Printf)
-//   we need to disable the tty console stuff
-// this increments so we can recursively disable
-static INT32 ttycon_hide = 0;
+// lock to prevent clearing partial lines, since not everything
+// printed ends on a newline.
+static boolean ttycon_ateol = true;
 // some key codes that the terminal may be using
 // TTimo NOTE: I'm not sure how relevant this is
 static INT32 tty_erase;
@@ -736,63 +735,31 @@ static inline void tty_FlushIn(void)
 // TTimo NOTE: it seems on some terminals just sending '\b' is not enough
 //   so for now, in any case we send "\b \b" .. yeah well ..
 //   (there may be a way to find out if '\b' alone would work though)
+// Hanicef NOTE: using \b this way is unreliable because of terminal state,
+//   it's better to use \r to reset the cursor to the beginning of the
+//   line and clear from there.
 static void tty_Back(void)
 {
-	char key;
-	ssize_t d;
-	key = '\b';
-	d = write(STDOUT_FILENO, &key, 1);
-	key = ' ';
-	d = write(STDOUT_FILENO, &key, 1);
-	key = '\b';
-	d = write(STDOUT_FILENO, &key, 1);
-	(void)d;
-}
-
-static void tty_Clear(void)
-{
-	size_t i;
+	write(STDOUT_FILENO, "\r", 1);
 	if (tty_con.cursor>0)
 	{
-		for (i=0; i<tty_con.cursor; i++)
-		{
-			tty_Back();
-		}
-	}
-
-}
-
-// clear the display of the line currently edited
-// bring cursor back to beginning of line
-static inline void tty_Hide(void)
-{
-	//I_Assert(consolevent);
-	if (ttycon_hide)
-	{
-		ttycon_hide++;
-		return;
+		write(STDOUT_FILENO, tty_con.buffer, tty_con.cursor);
 	}
-	tty_Clear();
-	ttycon_hide++;
+	write(STDOUT_FILENO, " \b", 2);
 }
 
-// show the current line
-// FIXME TTimo need to position the cursor if needed??
-static inline void tty_Show(void)
+static void tty_Clear(void)
 {
 	size_t i;
-	ssize_t d;
-	//I_Assert(consolevent);
-	I_Assert(ttycon_hide>0);
-	ttycon_hide--;
-	if (ttycon_hide == 0 && tty_con.cursor)
+	write(STDOUT_FILENO, "\r", 1);
+	if (tty_con.cursor>0)
 	{
 		for (i=0; i<tty_con.cursor; i++)
 		{
-			d = write(STDOUT_FILENO, tty_con.buffer+i, 1);
+			write(STDOUT_FILENO, " ", 1);
 		}
+		write(STDOUT_FILENO, "\r", 1);
 	}
-	(void)d;
 }
 
 // never exit without calling this, or your terminal will be left in a pretty bad state
@@ -900,6 +867,11 @@ static void I_GetConsoleEvents(void)
 				tty_con.cursor = 0;
 				ev.key = KEY_ENTER;
 			}
+			else if (key == 0x4) // ^D, aka EOF
+			{
+				// shut down, most unix programs behave this way
+				I_Quit();
+			}
 			else continue;
 		}
 		else if (tty_con.cursor < sizeof(tty_con.buffer))
@@ -1046,6 +1018,9 @@ void I_OutputMsg(const char *fmt, ...)
 	va_start(argptr,fmt);
 	len = vsnprintf(NULL, 0, fmt, argptr);
 	va_end(argptr);
+	if (len == 0)
+		return;
+
 	txt = malloc(len+1);
 	va_start(argptr,fmt);
 	vsprintf(txt, fmt, argptr);
@@ -1135,18 +1110,20 @@ void I_OutputMsg(const char *fmt, ...)
 	}
 #else
 #ifdef HAVE_TERMIOS
-	if (consolevent)
+	if (consolevent && ttycon_ateol)
 	{
-		tty_Hide();
+		tty_Clear();
+		ttycon_ateol = false;
 	}
 #endif
 
 	if (!framebuffer)
 		fprintf(stderr, "%s", txt);
 #ifdef HAVE_TERMIOS
-	if (consolevent)
+	if (consolevent && txt[len-1] == '\n')
 	{
-		tty_Show();
+		write(STDOUT_FILENO, tty_con.buffer, tty_con.cursor);
+		ttycon_ateol = true;
 	}
 #endif
 
@@ -1453,8 +1430,10 @@ const char *I_LocateWad(void)
 	{
 		// change to the directory where we found srb2.pk3
 #if defined (_WIN32)
+		waddir = _fullpath(NULL, waddir, MAX_PATH);
 		SetCurrentDirectoryA(waddir);
 #else
+		waddir = realpath(waddir, NULL);
 		if (chdir(waddir) == -1)
 			I_OutputMsg("Couldn't change working directory\n");
 #endif
diff --git a/src/deh_lua.c b/src/deh_lua.c
index e81a8c08e911c4ed0810379566953885998daa01..e5b3b03de4349dbf97d158471af20df7a18e00d3 100644
--- a/src/deh_lua.c
+++ b/src/deh_lua.c
@@ -28,6 +28,12 @@ static inline int lib_freeslot(lua_State *L)
 	if (!lua_lumploading)
 		return luaL_error(L, "This function cannot be called from within a hook or coroutine!");
 
+	if (!deh_loaded)
+	{
+		initfreeslots();
+		deh_loaded = true;
+	}
+
 	while (n-- > 0)
 	{
 		s = Z_StrDup(luaL_checkstring(L,1));
diff --git a/src/dehacked.c b/src/dehacked.c
index 473e77e55b6548e07189124a8cb79128347a2d8e..470e468a0ba01c2d6bdda441dad3b195111cab2c 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -193,7 +193,10 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
 	INT32 i;
 
 	if (!deh_loaded)
+	{
 		initfreeslots();
+		deh_loaded = true;
+	}
 
 	deh_num_warning = 0;
 
@@ -611,14 +614,10 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
 	if (deh_num_warning)
 	{
 		CONS_Printf(M_GetText("%d warning%s in the SOC lump\n"), deh_num_warning, deh_num_warning == 1 ? "" : "s");
-		if (devparm) {
+		if (devparm)
 			I_Error("%s%s",va(M_GetText("%d warning%s in the SOC lump\n"), deh_num_warning, deh_num_warning == 1 ? "" : "s"), M_GetText("See log.txt for details.\n"));
-			//while (!I_GetKey())
-				//I_OsPolling();
-		}
 	}
 
-	deh_loaded = true;
 	Z_Free(s);
 }
 
diff --git a/src/f_finale.c b/src/f_finale.c
index 37e4ba0703468d062be0762c52eb86b180fb6a7b..a992a0dfdfd1f49030840dac445a1d6f6d7864e2 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -3435,7 +3435,7 @@ void F_TitleScreenTicker(boolean run)
 		{
 			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+				if (th->removing)
 					continue;
 
 				mo2 = (mobj_t *)th;
diff --git a/src/g_demo.c b/src/g_demo.c
index b0e16addad0ad976faa46cf69b8743c47c3645dc..8315e716bfad9f80387d438d1128078aa3e2ca0f 100644
--- a/src/g_demo.c
+++ b/src/g_demo.c
@@ -614,7 +614,7 @@ void G_ConsGhostTic(void)
 				mobj = NULL;
 				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+					if (th->removing)
 						continue;
 					mobj = (mobj_t *)th;
 					if (mobj->type == (mobjtype_t)type && mobj->x == x && mobj->y == y && mobj->z == z)
@@ -2696,7 +2696,7 @@ void G_DoPlayMetal(void)
 	// find metal sonic
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo = (mobj_t *)th;
diff --git a/src/g_game.c b/src/g_game.c
index d409bb2cf721b0132f086113932c5402da289615..8940635851adbbf56b95e296c1fa908da642b828 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1384,7 +1384,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 	if (PLAYERINPUTDOWN(ssplayer, GC_SPIN) || (usejoystick && axis > 0))
 		cmd->buttons |= BT_SPIN;
 
-	if (gamestate != GS_LEVEL) // not in a level, don't build anything else
+	if (gamestate == GS_INTRO) // prevent crash in intro
 	{
 		cmd->angleturn = ticcmd_oldangleturn[forplayer];
 		cmd->aiming = G_ClipAimingPitch(myaiming);
@@ -1722,7 +1722,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 	// At this point, cmd doesn't contain the final angle yet,
 	// So we need to temporarily transform it so Lua scripters
 	// don't need to handle it differently than in other hooks.
-	if (addedtogame)
+	if (addedtogame && gamestate == GS_LEVEL)
 	{
 		INT16 extra = ticcmd_oldangleturn[forplayer] - player->oldrelangleturn;
 		INT16 origangle = cmd->angleturn;
@@ -3072,7 +3072,7 @@ void G_ChangePlayerReferences(mobj_t *oldmo, mobj_t *newmo)
 	// scan all thinkers
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo2 = (mobj_t *)th;
diff --git a/src/hardware/hw_batching.c b/src/hardware/hw_batching.c
index 7a57a7a120d348fa8cc3a4e00513ace2eae03e18..5d1af10e2a35e073472e9e4d6cbd90e31bdaec40 100644
--- a/src/hardware/hw_batching.c
+++ b/src/hardware/hw_batching.c
@@ -114,7 +114,7 @@ void HWR_ProcessPolygon(FSurfaceInfo *pSurf, FOutVector *pOutVerts, FUINT iNumPt
 		polygonArray[polygonArraySize].numVerts = iNumPts;
 		polygonArray[polygonArraySize].polyFlags = PolyFlags;
 		polygonArray[polygonArraySize].texture = current_texture;
-		polygonArray[polygonArraySize].shader = (shader_target != -1) ? HWR_GetShaderFromTarget(shader_target) : shader_target;
+		polygonArray[polygonArraySize].shader = (shader_target != SHADER_NONE) ? HWR_GetShaderFromTarget(shader_target) : shader_target;
 		polygonArray[polygonArraySize].horizonSpecial = horizonSpecial;
 		// default to polygonArraySize so we don't lose order on horizon lines
 		// (yes, it's supposed to be negative, since we're sorting in that direction)
@@ -311,7 +311,6 @@ void HWR_RenderBatches(void)
 			int nextIndex = polygonIndexArray[polygonReadPos];
 			if (polygonArray[index].hash != polygonArray[nextIndex].hash)
 			{
-				changeState = true;
 				nextShader = polygonArray[nextIndex].shader;
 				nextTexture = polygonArray[nextIndex].texture;
 				nextPolyFlags = polygonArray[nextIndex].polyFlags;
@@ -320,14 +319,17 @@ void HWR_RenderBatches(void)
 					nextTexture = 0;
 				if (currentShader != nextShader && cv_glshaders.value && gl_shadersavailable)
 				{
+					changeState = true;
 					changeShader = true;
 				}
 				if (currentTexture != nextTexture)
 				{
+					changeState = true;
 					changeTexture = true;
 				}
 				if (currentPolyFlags != nextPolyFlags)
 				{
+					changeState = true;
 					changePolyFlags = true;
 				}
 				if (cv_glshaders.value && gl_shadersavailable)
@@ -339,6 +341,7 @@ void HWR_RenderBatches(void)
 						currentSurfaceInfo.LightInfo.fade_start != nextSurfaceInfo.LightInfo.fade_start ||
 						currentSurfaceInfo.LightInfo.fade_end != nextSurfaceInfo.LightInfo.fade_end)
 					{
+						changeState = true;
 						changeSurfaceInfo = true;
 					}
 				}
@@ -346,6 +349,7 @@ void HWR_RenderBatches(void)
 				{
 					if (currentSurfaceInfo.PolyColor.rgba != nextSurfaceInfo.PolyColor.rgba)
 					{
+						changeState = true;
 						changeSurfaceInfo = true;
 					}
 				}
diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index f282ca89184b9d47fe000f47e126880c0467b602..b71bf7007e03ab2cdb7d229d84aa5a57deb8cc8c 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -1258,20 +1258,29 @@ void HWR_SetMapPalette(void)
 
 // Creates a hardware lighttable from the supplied lighttable.
 // Returns the id of the hw lighttable, usable in FSurfaceInfo.
-UINT32 HWR_CreateLightTable(UINT8 *lighttable)
+UINT32 HWR_CreateLightTable(UINT8 *lighttable, RGBA_t *hw_lighttable)
 {
-	UINT32 i, id;
+	UINT32 i;
 	RGBA_t *palette = HWR_GetTexturePalette();
-	RGBA_t *hw_lighttable = Z_Malloc(256 * 32 * sizeof(RGBA_t), PU_STATIC, NULL);
 
 	// To make the palette index -> RGBA mapping easier for the shader,
 	// the hardware lighttable is composed of RGBA colors instead of palette indices.
 	for (i = 0; i < 256 * 32; i++)
 		hw_lighttable[i] = palette[lighttable[i]];
 
-	id = HWD.pfnCreateLightTable(hw_lighttable);
-	Z_Free(hw_lighttable);
-	return id;
+	return HWD.pfnCreateLightTable(hw_lighttable);
+}
+
+// Updates a hardware lighttable of a given id from the supplied lighttable.
+void HWR_UpdateLightTable(UINT32 id, UINT8 *lighttable, RGBA_t *hw_lighttable)
+{
+	UINT32 i;
+	RGBA_t *palette = HWR_GetTexturePalette();
+
+	for (i = 0; i < 256 * 32; i++)
+		hw_lighttable[i] = palette[lighttable[i]];
+
+	HWD.pfnUpdateLightTable(id, hw_lighttable);
 }
 
 // get hwr lighttable id for colormap, create it if it doesn't already exist
@@ -1285,25 +1294,41 @@ UINT32 HWR_GetLightTableID(extracolormap_t *colormap)
 		default_colormap = true;
 	}
 
+	UINT8 *colormap_pointer;
+
+	if (default_colormap)
+		colormap_pointer = colormaps; // don't actually use the data from the "default colormap"
+	else
+		colormap_pointer = colormap->colormap;
+
 	// create hw lighttable if there isn't one
-	if (!colormap->gl_lighttable_id)
+	if (colormap->gl_lighttable.data == NULL)
 	{
-		UINT8 *colormap_pointer;
+		Z_Malloc(256 * 32 * sizeof(RGBA_t), PU_HWRLIGHTTABLEDATA, &colormap->gl_lighttable.data);
+	}
 
-		if (default_colormap)
-			colormap_pointer = colormaps; // don't actually use the data from the "default colormap"
-		else
-			colormap_pointer = colormap->colormap;
-		colormap->gl_lighttable_id = HWR_CreateLightTable(colormap_pointer);
+	// Generate the texture for this light table
+	if (!colormap->gl_lighttable.id)
+	{
+		colormap->gl_lighttable.id = HWR_CreateLightTable(colormap_pointer, colormap->gl_lighttable.data);
+	}
+	// Update the texture if it was directly changed by a script
+	else if (colormap->gl_lighttable.needs_update)
+	{
+		HWR_UpdateLightTable(colormap->gl_lighttable.id, colormap_pointer, colormap->gl_lighttable.data);
 	}
 
-	return colormap->gl_lighttable_id;
+	colormap->gl_lighttable.needs_update = false;
+
+	return colormap->gl_lighttable.id;
 }
 
 // Note: all hardware lighttable ids assigned before this
 // call become invalid and must not be used.
 void HWR_ClearLightTables(void)
 {
+	Z_FreeTag(PU_HWRLIGHTTABLEDATA);
+
 	if (vid.glstate == VID_GL_LIBRARY_LOADED)
 		HWD.pfnClearLightTables();
 }
diff --git a/src/hardware/hw_drv.h b/src/hardware/hw_drv.h
index 694cc1b8c0d5a16e1d40ae9d14575c562d868058..a5fdc00a47c17e6b5cbeba5d074554a66672cde7 100644
--- a/src/hardware/hw_drv.h
+++ b/src/hardware/hw_drv.h
@@ -71,6 +71,7 @@ EXPORT void HWRAPI(SetShaderInfo) (hwdshaderinfo_t info, INT32 value);
 
 EXPORT void HWRAPI(SetPaletteLookup)(UINT8 *lut);
 EXPORT UINT32 HWRAPI(CreateLightTable)(RGBA_t *hw_lighttable);
+EXPORT void HWRAPI(UpdateLightTable)(UINT32 id, RGBA_t *hw_lighttable);
 EXPORT void HWRAPI(ClearLightTables)(void);
 EXPORT void HWRAPI(SetScreenPalette)(RGBA_t *palette);
 
@@ -125,6 +126,7 @@ struct hwdriver_s
 
 	SetPaletteLookup    pfnSetPaletteLookup;
 	CreateLightTable    pfnCreateLightTable;
+	UpdateLightTable    pfnUpdateLightTable;
 	ClearLightTables    pfnClearLightTables;
 	SetScreenPalette    pfnSetScreenPalette;
 };
diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h
index 62fe8d0bda5b521f650a91f00f97e111d99e6eed..bf9f7da3b4c126154fed2faf81761f348fbeed63 100644
--- a/src/hardware/hw_glob.h
+++ b/src/hardware/hw_glob.h
@@ -133,7 +133,8 @@ void HWR_UnlockCachedPatch(GLPatch_t *gpatch);
 
 void HWR_SetPalette(RGBA_t *palette);
 void HWR_SetMapPalette(void);
-UINT32 HWR_CreateLightTable(UINT8 *lighttable);
+UINT32 HWR_CreateLightTable(UINT8 *lighttable, RGBA_t *hw_lighttable);
+void HWR_UpdateLightTable(UINT32 id, UINT8 *lighttable, RGBA_t *hw_lighttable);
 UINT32 HWR_GetLightTableID(extracolormap_t *colormap);
 void HWR_ClearLightTables(void);
 
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 4741c6a4697e645671d133f69f0433602fdcaab6..da8965454ae30f52e1c50762e0dff76f875d6780 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -954,6 +954,7 @@ static boolean HWR_BlendMidtextureSurface(FSurfaceInfo *pSurf)
 
 static void HWR_RenderMidtexture(INT32 gl_midtexture, float cliplow, float cliphigh, fixed_t worldtop, fixed_t worldbottom, fixed_t worldhigh, fixed_t worldlow, fixed_t worldtopslope, fixed_t worldbottomslope, fixed_t worldhighslope, fixed_t worldlowslope, UINT32 lightnum, FOutVector *inWallVerts)
 {
+	sector_t *front, *back;
 	FOutVector wallVerts[4];
 
 	FSurfaceInfo Surf;
@@ -963,6 +964,16 @@ static void HWR_RenderMidtexture(INT32 gl_midtexture, float cliplow, float cliph
 	if (!HWR_BlendMidtextureSurface(&Surf))
 		return;
 
+	if (gl_linedef->frontsector->heightsec != -1)
+		front = &sectors[gl_linedef->frontsector->heightsec];
+	else
+		front = gl_linedef->frontsector;
+
+	if (gl_linedef->backsector->heightsec != -1)
+		back = &sectors[gl_linedef->backsector->heightsec];
+	else
+		back = gl_linedef->backsector;
+
 	fixed_t texheight = FixedDiv(textureheight[gl_midtexture], abs(gl_sidedef->scaley_mid));
 	INT32 repeats;
 
@@ -972,15 +983,15 @@ static void HWR_RenderMidtexture(INT32 gl_midtexture, float cliplow, float cliph
 	{
 		fixed_t high, low;
 
-		if (gl_frontsector->ceilingheight > gl_backsector->ceilingheight)
-			high = gl_backsector->ceilingheight;
+		if (front->ceilingheight > back->ceilingheight)
+			high = back->ceilingheight;
 		else
-			high = gl_frontsector->ceilingheight;
+			high = front->ceilingheight;
 
-		if (gl_frontsector->floorheight > gl_backsector->floorheight)
-			low = gl_frontsector->floorheight;
+		if (front->floorheight > back->floorheight)
+			low = front->floorheight;
 		else
-			low = gl_backsector->floorheight;
+			low = back->floorheight;
 
 		repeats = (high - low) / texheight;
 		if ((high - low) % texheight)
@@ -1007,8 +1018,8 @@ static void HWR_RenderMidtexture(INT32 gl_midtexture, float cliplow, float cliph
 	if (gl_curline->polyseg)
 	{
 		// Change this when polyobjects support slopes
-		popentop = popentopslope = gl_curline->backsector->ceilingheight;
-		popenbottom = popenbottomslope = gl_curline->backsector->floorheight;
+		popentop = popentopslope = back->ceilingheight;
+		popenbottom = popenbottomslope = back->floorheight;
 	}
 	else
 	{
@@ -1681,7 +1692,7 @@ static void HWR_ProcessSeg(void)
 				{
 					blendmode = PF_Masked;
 
-					if ((rover->fofflags & FOF_TRANSLUCENT && !(rover->fofflags & FOF_SPLAT)) || rover->blend)
+					if ((rover->fofflags & FOF_TRANSLUCENT && !((rover->fofflags & FOF_SPLAT) && rover->alpha >= 255)) || rover->blend)
 					{
 						blendmode = rover->blend ? HWR_GetBlendModeFlag(rover->blend) : PF_Translucent;
 						Surf.PolyColor.s.alpha = max(0, min(rover->alpha, 255));
@@ -1838,7 +1849,7 @@ static void HWR_ProcessSeg(void)
 				{
 					blendmode = PF_Masked;
 
-					if ((rover->fofflags & FOF_TRANSLUCENT && !(rover->fofflags & FOF_SPLAT)) || rover->blend)
+					if ((rover->fofflags & FOF_TRANSLUCENT && !((rover->fofflags & FOF_SPLAT) && rover->alpha >= 255)) || rover->blend)
 					{
 						blendmode = rover->blend ? HWR_GetBlendModeFlag(rover->blend) : PF_Translucent;
 						Surf.PolyColor.s.alpha = max(0, min(rover->alpha, 255));
@@ -2492,7 +2503,7 @@ static void HWR_Subsector(size_t num)
 			
 			if (gl_frontsector->cullheight)
 			{
-				if (HWR_DoCulling(gl_frontsector->cullheight, viewsector->cullheight, gl_viewz, FIXED_TO_FLOAT(bottomCullHeight), FIXED_TO_FLOAT(topCullHeight)))
+				if (HWR_DoCulling(gl_frontsector->cullheight, viewsector->cullheight, gl_viewz, FIXED_TO_FLOAT(*rover->bottomheight), FIXED_TO_FLOAT(*rover->topheight)))
 					continue;
 			}
 
@@ -2519,7 +2530,7 @@ static void HWR_Subsector(size_t num)
 					                       alpha, rover->master->frontsector, PF_Fog|PF_NoTexture,
 										   true, false, rover->master->frontsector->extra_colormap);
 				}
-				else if ((rover->fofflags & FOF_TRANSLUCENT && !(rover->fofflags & FOF_SPLAT)) || rover->blend) // SoM: Flags are more efficient
+				else if ((rover->fofflags & FOF_TRANSLUCENT && !((rover->fofflags & FOF_SPLAT) && rover->alpha >= 255)) || rover->blend) // SoM: Flags are more efficient
 				{
 					light = R_GetPlaneLight(gl_frontsector, centerHeight, viewz < bottomCullHeight ? true : false);
 
@@ -2565,7 +2576,7 @@ static void HWR_Subsector(size_t num)
 					                       alpha, rover->master->frontsector, PF_Fog|PF_NoTexture,
 										   true, false, rover->master->frontsector->extra_colormap);
 				}
-				else if ((rover->fofflags & FOF_TRANSLUCENT && !(rover->fofflags & FOF_SPLAT)) || rover->blend)
+				else if ((rover->fofflags & FOF_TRANSLUCENT && !((rover->fofflags & FOF_SPLAT) && rover->alpha >= 255)) || rover->blend)
 				{
 					light = R_GetPlaneLight(gl_frontsector, centerHeight, viewz < topCullHeight ? true : false);
 
@@ -5594,7 +5605,7 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 // Can't have palette rendering if shaders are disabled.
 boolean HWR_ShouldUsePaletteRendering(void)
 {
-	return (cv_glpaletterendering.value && HWR_UseShader());
+	return (pMasterPalette != NULL && cv_glpaletterendering.value && HWR_UseShader());
 }
 
 // enable or disable palette rendering state depending on settings and availability
@@ -5711,7 +5722,7 @@ consvar_t cv_glbatching = CVAR_INIT ("gr_batching", "On", 0, CV_OnOff, NULL);
 
 static CV_PossibleValue_t glpalettedepth_cons_t[] = {{16, "16 bits"}, {24, "24 bits"}, {0, NULL}};
 
-consvar_t cv_glpaletterendering = CVAR_INIT ("gr_paletterendering", "Off", CV_SAVE|CV_CALL, CV_OnOff, CV_glpaletterendering_OnChange);
+consvar_t cv_glpaletterendering = CVAR_INIT ("gr_paletterendering", "On", CV_SAVE|CV_CALL, CV_OnOff, CV_glpaletterendering_OnChange);
 consvar_t cv_glpalettedepth = CVAR_INIT ("gr_palettedepth", "16 bits", CV_SAVE|CV_CALL, glpalettedepth_cons_t, CV_glpalettedepth_OnChange);
 
 #define ONLY_IF_GL_LOADED if (vid.glstate != VID_GL_LIBRARY_LOADED) return;
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 426c014d2d8a1c2f40db950b7dfb30c0be3cf028..011b478e58c38c95813dfe8170a7facaa2fa178d 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -1193,6 +1193,90 @@ static void adjustTextureCoords(model_t *model, patch_t *patch)
 	model->max_t = gpatch->max_t;
 }
 
+static INT32 GetAnimDuration(mobj_t *mobj) //part of p_mobj's setplayermobjstate logic, used to make sure that anim durations are actually correct when the speed gets adjusted on players
+{
+	player_t *player = mobj->player;
+	INT32 tics = mobj->state->tics;
+
+	if (!(mobj->frame & FF_ANIMATE) && mobj->anim_duration) //set manually by something through lua
+		return mobj->anim_duration;
+
+	if (!player && mobj->type == MT_TAILSOVERLAY && mobj->tracer) //so tails overlays interpolate properly
+		player = mobj->tracer->player;
+	if (player)
+	{
+		if (player->panim == PA_EDGE && (player->charflags & SF_FASTEDGE))
+			tics = 2;
+		else if (player->powers[pw_tailsfly] && (!(player->mo->eflags & MFE_UNDERWATER) || (mobj->type == MT_PLAYER))) //tailsoverlay does not get adjusted from these rules when underwater
+		{
+			if (player->fly1 > 0)
+				tics = 1;
+			else if (!(player->mo->eflags & MFE_UNDERWATER))
+				tics = 2;
+			else
+				tics = 4;
+		}
+		else if (!(disableSpeedAdjust || player->charflags & SF_NOSPEEDADJUST))
+		{
+			fixed_t speed;// = FixedDiv(player->speed, FixedMul(mobj->scale, player->mo->movefactor));
+			if (player->panim == PA_FALL)
+			{
+				speed = FixedDiv(abs(mobj->momz), mobj->scale);
+				if (speed < 10<<FRACBITS)
+					tics = 4;
+				else if (speed < 20<<FRACBITS)
+					tics = 3;
+				else if (speed < 30<<FRACBITS)
+					tics = 2;
+				else
+					tics = 1;
+			}
+			else if (player->panim == PA_ABILITY2 && player->charability2 == CA2_SPINDASH)
+			{
+				fixed_t step = (player->maxdash - player->mindash)/4;
+				speed = (player->dashspeed - player->mindash);
+				if (speed > 3*step)
+					tics = 1;
+				else if (speed > step)
+					tics = 2;
+				else
+					tics = 3;
+			}
+			else
+			{
+				speed = FixedDiv(player->speed, FixedMul(mobj->scale, player->mo->movefactor));
+				if (player->panim == PA_ROLL || player->panim == PA_JUMP)
+				{
+					if (speed > 16<<FRACBITS)
+						tics = 1;
+					else
+						tics = 2;
+				}
+				else if (P_IsObjectOnGround(mobj) || ((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super]) // Only if on the ground or superflying.
+				{
+					if (player->panim == PA_WALK)
+					{
+						if (speed > 12<<FRACBITS)
+							tics = 2;
+						else if (speed > 6<<FRACBITS)
+							tics = 3;
+						else
+							tics = 4;
+					}
+					else if ((player->panim == PA_RUN) || (player->panim == PA_DASH))
+					{
+						if (speed > 52<<FRACBITS)
+							tics = 1;
+						else
+							tics = 2;
+					}
+				}
+			}
+		}
+	}
+	return tics;
+}
+
 //
 // HWR_DrawModel
 //
@@ -1266,7 +1350,7 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 	{
 		patch_t *gpatch, *blendgpatch;
 		GLPatch_t *hwrPatch = NULL, *hwrBlendPatch = NULL;
-		float durs = (float)spr->mobj->state->tics;
+		float durs = GetAnimDuration(spr->mobj);
 		float tics = (float)spr->mobj->tics;
 		const boolean papersprite = (R_ThingIsPaperSprite(spr->mobj) && !R_ThingIsFloorSprite(spr->mobj));
 		const UINT8 flip = (UINT8)(!(spr->mobj->eflags & MFE_VERTICALFLIP) != !R_ThingVerticallyFlipped(spr->mobj));
@@ -1287,8 +1371,8 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 		}
 
 		// Apparently people don't like jump frames like that, so back it goes
-		//if (tics > durs)
-			//durs = tics;
+		if (tics > durs)
+			durs = tics;
 		
 		// Make linkdraw objects use their tracer's alpha value
 		fixed_t newalpha = spr->mobj->alpha;
@@ -1607,7 +1691,6 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 			HWD.pfnDrawModel(md2->model, frame, durs, tics, nextFrame, &p, md2->scale * xs, md2->scale * ys, flip, hflip, &Surf);
 		}
 	}
-
 	return true;
 }
 
diff --git a/src/hardware/r_opengl/ogl_win.c b/src/hardware/r_opengl/ogl_win.c
index c9bf601442a5531b35d0ac629730aa83978d17a6..ec8fc9bdb8b77f99f03b97af7df9cbdedea864f4 100644
--- a/src/hardware/r_opengl/ogl_win.c
+++ b/src/hardware/r_opengl/ogl_win.c
@@ -117,7 +117,7 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, // handle to DLL module
 #define pwglDeleteContext wglDeleteContext;
 #define pwglMakeCurrent wglMakeCurrent;
 #else
-static HMODULE OGL32, GLU32;
+static HMODULE OGL32;
 typedef void *(WINAPI *PFNwglGetProcAddress) (const char *);
 static PFNwglGetProcAddress pwglGetProcAddress;
 typedef HGLRC (WINAPI *PFNwglCreateContext) (HDC hdc);
@@ -132,13 +132,6 @@ static PFNwglMakeCurrent pwglMakeCurrent;
 void *GetGLFunc(const char *proc)
 {
 	void *func = NULL;
-	if (strncmp(proc, "glu", 3) == 0)
-	{
-		if (GLU32)
-			func = GetProcAddress(GLU32, proc);
-		else
-			return NULL;
-	}
 	if (pwglGetProcAddress)
 		func = pwglGetProcAddress(proc);
 	if (!func)
@@ -155,8 +148,6 @@ boolean LoadGL(void)
 	if (!OGL32)
 		return 0;
 
-	GLU32 = LoadLibrary("GLU32.DLL");
-
 	pwglGetProcAddress = GetGLFunc("wglGetProcAddress");
 	pwglCreateContext = GetGLFunc("wglCreateContext");
 	pwglDeleteContext = GetGLFunc("wglDeleteContext");
@@ -528,7 +519,6 @@ EXPORT void HWRAPI(Shutdown) (void)
 		ReleaseDC(hWnd, hDC);
 		hDC = NULL;
 	}
-	FreeLibrary(GLU32);
 	FreeLibrary(OGL32);
 	GL_DBG_Printf ("HWRAPI Shutdown(DONE)\n");
 }
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 75a92c2fb33ad542b0931786a9b4688a9f9dccbc..e11dd4f166cb8e068cc8fb99d2e2bd0661832755 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -96,6 +96,7 @@ static GLint min_filter = GL_LINEAR;
 static GLint mag_filter = GL_LINEAR;
 static GLint anisotropic_filter = 0;
 static boolean model_lighting = false;
+boolean supportMipMap = false;
 
 const GLubyte *gl_version = NULL;
 const GLubyte *gl_renderer = NULL;
@@ -417,9 +418,6 @@ static PFNglCopyTexImage2D pglCopyTexImage2D;
 typedef void (APIENTRY * PFNglCopyTexSubImage2D) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height);
 static PFNglCopyTexSubImage2D pglCopyTexSubImage2D;
 #endif
-/* GLU functions */
-typedef GLint (APIENTRY * PFNgluBuild2DMipmaps) (GLenum target, GLint internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *data);
-static PFNgluBuild2DMipmaps pgluBuild2DMipmaps;
 
 /* 1.2 functions for 3D textures */
 typedef void (APIENTRY * PFNglTexImage3D) (GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
@@ -708,9 +706,6 @@ void SetupGLFunc4(void)
 	pglUniform3fv = GetGLFunc("glUniform3fv");
 	pglGetUniformLocation = GetGLFunc("glGetUniformLocation");
 #endif
-
-	// GLU
-	pgluBuild2DMipmaps = GetGLFunc("gluBuild2DMipmaps");
 }
 
 EXPORT boolean HWRAPI(InitShaders) (void)
@@ -1617,7 +1612,8 @@ EXPORT void HWRAPI(UpdateTexture) (GLMipmap_t *pTexInfo)
 		//pglTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
 		if (MipMap)
 		{
-			pgluBuild2DMipmaps(GL_TEXTURE_2D, GL_LUMINANCE_ALPHA, w, h, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
+			pglTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
+			pglTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
 			pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0);
 			if (pTexInfo->flags & TF_TRANSPARENT)
 				pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 0); // No mippmaps on transparent stuff
@@ -1638,7 +1634,8 @@ EXPORT void HWRAPI(UpdateTexture) (GLMipmap_t *pTexInfo)
 		//pglTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
 		if (MipMap)
 		{
-			pgluBuild2DMipmaps(GL_TEXTURE_2D, GL_ALPHA, w, h, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
+			pglTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
+			pglTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
 			pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0);
 			if (pTexInfo->flags & TF_TRANSPARENT)
 				pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 0); // No mippmaps on transparent stuff
@@ -1658,7 +1655,8 @@ EXPORT void HWRAPI(UpdateTexture) (GLMipmap_t *pTexInfo)
 	{
 		if (MipMap)
 		{
-			pgluBuild2DMipmaps(GL_TEXTURE_2D, textureformatGL, w, h, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
+			pglTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
+			pglTexImage2D(GL_TEXTURE_2D, 0, textureformatGL, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
 			// Control the mipmap level of detail
 			pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0); // the lower the number, the higer the detail
 			if (pTexInfo->flags & TF_TRANSPARENT)
@@ -2241,7 +2239,7 @@ EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value)
 					mag_filter = GL_LINEAR;
 					min_filter = GL_NEAREST;
 			}
-			if (!pgluBuild2DMipmaps)
+			if (!supportMipMap)
 			{
 				MipMap = GL_FALSE;
 				min_filter = GL_LINEAR;
@@ -3257,6 +3255,24 @@ EXPORT UINT32 HWRAPI(CreateLightTable)(RGBA_t *hw_lighttable)
 	return item->id;
 }
 
+EXPORT void HWRAPI(UpdateLightTable)(UINT32 id, RGBA_t *hw_lighttable)
+{
+	LTListItem *item = LightTablesHead;
+	while (item && item->id != id)
+		item = item->next;
+
+	if (item)
+	{
+		pglBindTexture(GL_TEXTURE_2D, item->id);
+
+		// Just update it
+		pglTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 32, GL_RGBA, GL_UNSIGNED_BYTE, hw_lighttable);
+
+		// restore previously bound texture
+		pglBindTexture(GL_TEXTURE_2D, tex_downloaded);
+	}
+}
+
 // Delete light table textures, ids given before become invalid and must not be used.
 EXPORT void HWRAPI(ClearLightTables)(void)
 {
diff --git a/src/hardware/r_opengl/r_opengl.h b/src/hardware/r_opengl/r_opengl.h
index f7e33c46aa36b9c416a80dc9158c5c7321fb78d0..197f75ea8931735b3e10b80e68a6ae47a5a736f5 100644
--- a/src/hardware/r_opengl/r_opengl.h
+++ b/src/hardware/r_opengl/r_opengl.h
@@ -35,7 +35,6 @@
 
 #else
 #include <GL/gl.h>
-#include <GL/glu.h>
 
 #ifdef STATIC_OPENGL // Because of the 1.3 functions, you'll need GLext to compile it if static
 #define GL_GLEXT_PROTOTYPES
@@ -127,6 +126,7 @@ extern GLint			screen_width;
 extern GLint			screen_height;
 extern GLbyte			screen_depth;
 extern GLint			maximumAnisotropy;
+extern boolean 			supportMipMap;
 
 /**	\brief OpenGL flags for video driver
 */
diff --git a/src/info.c b/src/info.c
index a138db8a41d62644c99603bd2832c2c7e2e49af9..715fe5d5335ed2ed9de8bfcefc60f14727188186 100644
--- a/src/info.c
+++ b/src/info.c
@@ -1784,8 +1784,8 @@ state_t states[NUMSTATES] =
 	{SPR_RING, FF_ANIMATE|FF_GLOBALANIM, -1, {NULL}, 23, 1, S_RING, 0}, // S_RING
 
 	// Blue Sphere for special stages
-	{SPR_SPHR, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL, 0}, // S_BLUESPHERE
-	{SPR_SPHR, FF_FULLBRIGHT
+	{SPR_SPHR, FF_SEMIBRIGHT, -1, {NULL}, 0, 0, S_NULL, 0}, // S_BLUESPHERE
+	{SPR_SPHR, FF_SEMIBRIGHT
 #ifdef MANIASPHERES
 							|FF_ANIMATE|FF_RANDOMANIM
 #endif
@@ -1794,13 +1794,13 @@ state_t states[NUMSTATES] =
 
 	// Bomb Sphere
 	{SPR_SPHR, FF_FULLBRIGHT|3, 2, {NULL}, 0, 0, S_BOMBSPHERE2, 0}, // S_BOMBSPHERE1
-	{SPR_SPHR, FF_FULLBRIGHT|4, 1, {NULL}, 0, 0, S_BOMBSPHERE3, 0}, // S_BOMBSPHERE2
-	{SPR_SPHR, FF_FULLBRIGHT|5, 2, {NULL}, 0, 0, S_BOMBSPHERE4, 0}, // S_BOMBSPHERE3
-	{SPR_SPHR, FF_FULLBRIGHT|4, 1, {NULL}, 0, 0, S_BOMBSPHERE1, 0}, // S_BOMBSPHERE4
+	{SPR_SPHR, FF_SEMIBRIGHT|4, 1, {NULL}, 0, 0, S_BOMBSPHERE3, 0}, // S_BOMBSPHERE2
+	{SPR_SPHR, FF_SEMIBRIGHT|5, 2, {NULL}, 0, 0, S_BOMBSPHERE4, 0}, // S_BOMBSPHERE3
+	{SPR_SPHR, FF_SEMIBRIGHT|4, 1, {NULL}, 0, 0, S_BOMBSPHERE1, 0}, // S_BOMBSPHERE4
 
 	// NiGHTS Chip
-	{SPR_NCHP, FF_FULLBRIGHT|FF_ANIMATE,    -1, {NULL}, 15, 2, S_NULL, 0}, // S_NIGHTSCHIP
-	{SPR_NCHP, FF_FULLBRIGHT|FF_ANIMATE|16, -1, {NULL}, 15, 2, S_NULL, 0}, // S_NIGHTSCHIPBONUS
+	{SPR_NCHP, FF_SEMIBRIGHT|FF_ANIMATE,    -1, {NULL}, 15, 2, S_NULL, 0}, // S_NIGHTSCHIP
+	{SPR_NCHP, FF_SEMIBRIGHT|FF_ANIMATE|16, -1, {NULL}, 15, 2, S_NULL, 0}, // S_NIGHTSCHIPBONUS
 
 	// NiGHTS Star
 	{SPR_NSTR, FF_ANIMATE, -1, {NULL}, 14, 2, S_NULL, 0}, // S_NIGHTSSTAR
@@ -1858,9 +1858,9 @@ state_t states[NUMSTATES] =
 	{SPR_CEMG, FF_FULLBRIGHT|6, -1, {NULL}, 0, 0, S_NULL, 0}, // S_CEMG7
 
 	// Emerald hunt shards
-	{SPR_SHRD, 0, -1, {NULL}, 0, 0, S_NULL, 0}, // S_SHRD1
-	{SPR_SHRD, 1, -1, {NULL}, 0, 0, S_NULL, 0}, // S_SHRD2
-	{SPR_SHRD, 2, -1, {NULL}, 0, 0, S_NULL, 0}, // S_SHRD3
+	{SPR_SHRD, FF_SEMIBRIGHT,   -1, {NULL}, 0, 0, S_NULL, 0}, // S_SHRD1
+	{SPR_SHRD, FF_SEMIBRIGHT|1, -1, {NULL}, 0, 0, S_NULL, 0}, // S_SHRD2
+	{SPR_SHRD, FF_SEMIBRIGHT|2, -1, {NULL}, 0, 0, S_NULL, 0}, // S_SHRD3
 
 	// Bubble Source
 	{SPR_BBLS, 0, 8, {A_BubbleSpawn}, 2048, 0, S_BUBBLES2, 0}, // S_BUBBLES1
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 19775eb8a20cfa0e38b4ffe79ded3efe694923de..18ae53405d08ba8a0223d987225285e225b15ecb 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -3181,17 +3181,25 @@ static int lib_rTextureNumForName(lua_State *L)
 
 static int lib_rCheckTextureNameForNum(lua_State *L)
 {
+	char s[9];
 	INT32 num = (INT32)luaL_checkinteger(L, 1);
 	//HUDSAFE
-	lua_pushstring(L, R_CheckTextureNameForNum(num));
+
+	M_Memcpy(s, R_CheckTextureNameForNum(num), 8);
+	s[8] = '\0';
+	lua_pushstring(L, s);
 	return 1;
 }
 
 static int lib_rTextureNameForNum(lua_State *L)
 {
+	char s[9];
 	INT32 num = (INT32)luaL_checkinteger(L, 1);
 	//HUDSAFE
-	lua_pushstring(L, R_TextureNameForNum(num));
+
+	M_Memcpy(s, R_TextureNameForNum(num), 8);
+	s[8] = '\0';
+	lua_pushstring(L, s);
 	return 1;
 }
 
diff --git a/src/lua_blockmaplib.c b/src/lua_blockmaplib.c
index c29eadecc625214615c2546abd2216c9b6630254..f570c229b97b138d6d10e714c4c2cdd8175b1596 100644
--- a/src/lua_blockmaplib.c
+++ b/src/lua_blockmaplib.c
@@ -254,11 +254,10 @@ static int lib_searchBlockmap(lua_State *L)
 	}
 	else // mobj and function only - search around mobj's radius by default
 	{
-		fixed_t radius = mobj->radius + MAXRADIUS;
-		x1 = mobj->x - radius;
-		x2 = mobj->x + radius;
-		y1 = mobj->y - radius;
-		y2 = mobj->y + radius;
+		x1 = mobj->x - mobj->radius;
+		x2 = mobj->x + mobj->radius;
+		y1 = mobj->y - mobj->radius;
+		y2 = mobj->y + mobj->radius;
 	}
 	lua_settop(L, 2); // pop everything except function, mobj
 
diff --git a/src/lua_colorlib.c b/src/lua_colorlib.c
index 9e679eb402f8e6e99eb4957c93aee8dbe9915bdb..2743635edfefe88348a93dc45aa26f77d4702067 100644
--- a/src/lua_colorlib.c
+++ b/src/lua_colorlib.c
@@ -593,7 +593,7 @@ static int extracolormap_set(lua_State *L)
 		|| exc->fadergba != old_fade_rgba
 		|| exc->fadestart != old_fade_start
 		|| exc->fadeend != old_fade_end)
-	R_GenerateLightTable(exc, true);
+	R_UpdateLightTable(exc, true);
 
 	return 0;
 }
diff --git a/src/lua_hook.h b/src/lua_hook.h
index 90a11d05d66dbf89a6fabd30123b63aae965235f..ce79cd1cb70a2e81f9d531ed15094e7cf42b1e75 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -72,6 +72,7 @@ automatically.
 	X (MusicChange),\
 	X (PlayerHeight),/* override player height */\
 	X (PlayerCanEnterSpinGaps),\
+	X (AddonLoaded),\
 	X (KeyDown),\
 	X (KeyUp),\
 
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index ebe995ea8cf17a9ed52341a7c2feb18d3a04a9a2..6bdebf774df50d0527e3f5713043e008f5c3e33e 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -198,12 +198,12 @@ static int mobj_get(lua_State *L)
 	enum mobj_e field = Lua_optoption(L, 2, -1, mobj_fields_ref);
 	lua_settop(L, 2);
 
-	if (!mo || !ISINLEVEL) {
+	if (P_MobjWasRemoved(mo) || !ISINLEVEL) {
 		if (field == mobj_valid) {
 			lua_pushboolean(L, 0);
 			return 1;
 		}
-		if (!mo) {
+		if (P_MobjWasRemoved(mo)) {
 			return LUA_ErrInvalid(L, "mobj_t");
 		} else
 			return luaL_error(L, "Do not access an mobj_t field outside a level!");
diff --git a/src/lua_script.c b/src/lua_script.c
index 623d8867351590cec65d0c656e37a5b03f0a58cc..686555a16d6b09b98d839cefec0ac0fc876181ae 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -1779,7 +1779,7 @@ void LUA_Archive(save_t *save_p)
 
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		// archive function will determine when to skip mobjs,
@@ -1817,7 +1817,7 @@ void LUA_UnArchive(save_t *save_p)
 		mobjnum = P_ReadUINT32(save_p); // read a mobjnum
 		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			if (th->removing)
 				continue;
 			if (((mobj_t *)th)->mobjnum != mobjnum) // find matching mobj
 				continue;
diff --git a/src/m_cheat.c b/src/m_cheat.c
index 2b32253fa5bc04e84b7e3f03c1cb3401cb393ca4..4be071bb20254f062bfbcb87dbf625abac6ea4ec 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -562,7 +562,7 @@ void Command_Teleport_f(void)
 
 			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+				if (th->removing)
 					continue;
 
 				mo2 = (mobj_t *)th;
@@ -1072,7 +1072,7 @@ static mapthing_t *OP_CreateNewMapThing(player_t *player, UINT16 type, boolean c
 
 		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			if (th->removing)
 				continue;
 
 			mo = (mobj_t *)th;
diff --git a/src/m_menu.c b/src/m_menu.c
index 2e4a42506afb865e70e5984ec141eb175f05f27d..2ac6ac7790aa4605c8bec74a2e785392c3410a52 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -3318,7 +3318,7 @@ boolean M_Responder(event_t *ev)
 
 	if (ch == -1)
 		return false;
-	else if (ch == gamecontrol[GC_SYSTEMMENU][0] || ch == gamecontrol[GC_SYSTEMMENU][1]) // allow remappable ESC key
+	else if (ev->type != ev_text && (ch == gamecontrol[GC_SYSTEMMENU][0] || ch == gamecontrol[GC_SYSTEMMENU][1])) // allow remappable ESC key
 		ch = KEY_ESCAPE;
 
 	// F-Keys
@@ -3399,9 +3399,16 @@ boolean M_Responder(event_t *ev)
 	// Handle menuitems which need a specific key handling
 	if (routine && (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_KEYHANDLER)
 	{
+		// block text input if ctrl is held, to allow using ctrl+c ctrl+v and ctrl+x
+		if (ctrldown)
+		{
+			routine(ch);
+			return true;
+		}
+
 		// 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)
+		if (ev->type == ev_keydown && ((ch >= 32 && ch <= 127) || (ch >= KEY_KEYPAD7 && ch <= KEY_KPADDEL)))
 			return true;
 
 		routine(ch);
@@ -3429,7 +3436,7 @@ boolean M_Responder(event_t *ev)
 		{
 			// dirty hack: for customising controls, I want only buttons/keys, not moves
 			if (ev->type == ev_mouse || ev->type == ev_mouse2 || ev->type == ev_joystick
-				|| ev->type == ev_joystick2)
+				|| ev->type == ev_joystick2 || ev->type == ev_text)
 				return true;
 			if (routine)
 			{
@@ -8511,6 +8518,7 @@ static void M_StartTutorial(INT32 choice)
 	gamecomplete = 0;
 	cursaveslot = 0;
 	maplistoption = 0;
+	CV_StealthSet(&cv_skin, DEFAULTSKIN); // tutorial accounts for sonic only
 	G_DeferedInitNew(false, G_BuildMapName(tutorialmap), 0, false, false);
 }
 
@@ -11775,10 +11783,10 @@ static void M_ChooseRoom(INT32 choice)
 #endif
 
 	if (choice == 0)
-		ms_RoomId = -1;
+		CV_SetValue(&cv_masterserver_room_id, 0);
 	else
 	{
-		ms_RoomId = roomIds[choice-1];
+		CV_SetValue(&cv_masterserver_room_id, roomIds[choice-1]);
 		menuRoomIndex = choice - 1;
 	}
 
@@ -12115,8 +12123,7 @@ static void M_HandleConnectIP(INT32 choice)
 
 			if ( ctrldown ) {
 				switch (choice) {
-					case 'v':
-					case 'V': // ctrl+v, pasting
+					case 'v': // ctrl+v, pasting
 					{
 						const char *paste = I_ClipboardPaste();
 
@@ -12129,8 +12136,7 @@ static void M_HandleConnectIP(INT32 choice)
 						break;
 					}
 					case KEY_INS:
-					case 'c':
-					case 'C': // ctrl+c, ctrl+insert, copying
+					case 'c': // ctrl+c, ctrl+insert, copying
 						if (l != 0) // Don't replace the clipboard without any text
 						{
 							I_ClipboardCopy(setupm_ip, l);
@@ -12138,8 +12144,7 @@ static void M_HandleConnectIP(INT32 choice)
 						}
 						break;
 
-					case 'x':
-					case 'X': // ctrl+x, cutting
+					case 'x': // ctrl+x, cutting
 						if (l != 0) // Don't replace the clipboard without any text
 						{
 							I_ClipboardCopy(setupm_ip, l);
@@ -12195,15 +12200,6 @@ static void M_HandleConnectIP(INT32 choice)
 				setupm_ip[l] = (char)choice;
 				setupm_ip[l+1] = 0;
 			}
-			else if (choice >= 199 && choice <= 211 && choice != 202 && choice != 206) //numpad too!
-			{
-				char keypad_translation[] = {'7','8','9','-','4','5','6','+','1','2','3','0','.'};
-				choice = keypad_translation[choice - 199];
-				S_StartSound(NULL,sfx_menu1); // Tails
-				setupm_ip[l] = (char)choice;
-				setupm_ip[l+1] = 0;
-			}
-
 			break;
 	}
 
diff --git a/src/m_perfstats.c b/src/m_perfstats.c
index b9948bdc0c3284a3473e7e6e8b95b2e3bd8fb568..33a774acfbb4ca9781ff62a177b93f9bffca28f8 100644
--- a/src/m_perfstats.c
+++ b/src/m_perfstats.c
@@ -453,7 +453,7 @@ static int PS_DrawPerfRows(int x, int y, int color, perfstatrow_t *rows)
 	return draw_y;
 }
 
-static void PS_UpdateMetricHistory(ps_metric_t *metric, boolean time_metric, boolean frame_metric, boolean set_user)
+static void PS_UpdateMetricHistory(ps_metric_t *metric, boolean time_metric, boolean frame_metric)
 {
 	int index = frame_metric ? ps_frame_index : ps_tick_index;
 
@@ -461,7 +461,7 @@ static void PS_UpdateMetricHistory(ps_metric_t *metric, boolean time_metric, boo
 	{
 		// allocate history table
 		int value_size = time_metric ? sizeof(precise_t) : sizeof(INT32);
-		void** memory_user = set_user ? &metric->history : NULL;
+		void** memory_user = &metric->history;
 
 		metric->history = Z_Calloc(value_size * cv_ps_samplesize.value, PU_PERFSTATS,
 				memory_user);
@@ -491,7 +491,7 @@ static void PS_UpdateRowHistories(perfstatrow_t *rows, boolean frame_metric)
 	for (row = rows; row->lores_label; row++)
 	{
 		if (PS_IsRowValid(row))
-			PS_UpdateMetricHistory(row->metric, !!(row->flags & PS_TIME), frame_metric, true);
+			PS_UpdateMetricHistory(row->metric, !!(row->flags & PS_TIME), frame_metric);
 	}
 }
 
@@ -584,7 +584,7 @@ static void PS_CountThinkers(void)
 		for (thinker = thlist[i].next; thinker != &thlist[i]; thinker = thinker->next)
 		{
 			ps_thinkercount.value.i++;
-			if (thinker->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			if (thinker->removing)
 				ps_removecount.value.i++;
 			else if (i == THINK_POLYOBJ)
 				ps_polythcount.value.i++;
@@ -649,17 +649,17 @@ void PS_UpdateTickStats(void)
 			if (cv_perfstats.value == 3)
 			{
 				for (i = 0; i < thinkframe_hooks_length; i++)
-					PS_UpdateMetricHistory(&thinkframe_hooks[i].time_taken, true, false, false);
+					PS_UpdateMetricHistory(&thinkframe_hooks[i].time_taken, true, 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);
+					PS_UpdateMetricHistory(&prethinkframe_hooks[i].time_taken, true, 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);
+					PS_UpdateMetricHistory(&postthinkframe_hooks[i].time_taken, true, false);
 			}
 		}
 		if (cv_perfstats.value)
diff --git a/src/netcode/client_connection.c b/src/netcode/client_connection.c
index 917e32b598e03d86f8540b997329069f279817fb..c740d53a6a3845539bfe0637771e78827ae8b055 100644
--- a/src/netcode/client_connection.c
+++ b/src/netcode/client_connection.c
@@ -546,6 +546,7 @@ static void AbortConnection(void)
 {
 	Snake_Free(&snake);
 
+	CURLAbortFile();
 	D_QuitNetGame();
 	CL_Reset();
 	D_StartTitle();
@@ -1062,10 +1063,6 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic
 				}
 			}
 
-			// Rusty TODO: multithread
-			if (filedownload.http_running)
-				CURLGetFile();
-
 			if (waitmore)
 				break; // exit the case
 
diff --git a/src/netcode/d_clisrv.c b/src/netcode/d_clisrv.c
index d34dbc4e085e4d4f4a0c658ed376b37532ab214e..5919235716cd7b186030eb30b59149dfe6343cf8 100644
--- a/src/netcode/d_clisrv.c
+++ b/src/netcode/d_clisrv.c
@@ -1814,7 +1814,7 @@ INT16 Consistancy(void)
 	{
 		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			if (th->removing)
 				continue;
 
 			mo = (mobj_t *)th;
diff --git a/src/netcode/d_net.c b/src/netcode/d_net.c
index 0313368525224ab647ff35703ee6f5ce14ad8a24..4860d8688b4a8b998b59523c6cf52502c69d10ca 100644
--- a/src/netcode/d_net.c
+++ b/src/netcode/d_net.c
@@ -67,8 +67,6 @@ INT16 hardware_MAXPACKETLENGTH;
 
 boolean (*I_NetGet)(void) = NULL;
 void (*I_NetSend)(void) = NULL;
-boolean (*I_NetCanSend)(void) = NULL;
-boolean (*I_NetCanGet)(void) = NULL;
 void (*I_NetCloseSocket)(void) = NULL;
 void (*I_NetFreeNodenum)(INT32 nodenum) = NULL;
 SINT8 (*I_NetMakeNodewPort)(const char *address, const char* port) = NULL;
@@ -993,15 +991,7 @@ boolean HSendPacket(INT32 node, boolean reliable, UINT8 acknum, size_t packetlen
 		netbuffer->ackreturn = 0;
 	if (reliable)
 	{
-		if (I_NetCanSend && !I_NetCanSend())
-		{
-			if (netbuffer->packettype < PT_CANFAIL)
-				GetFreeAcknum(&netbuffer->ack, true);
-
-			DEBFILE("HSendPacket: Out of bandwidth\n");
-			return false;
-		}
-		else if (!GetFreeAcknum(&netbuffer->ack, false))
+		if (!GetFreeAcknum(&netbuffer->ack, false))
 			return false;
 	}
 	else
@@ -1153,7 +1143,7 @@ static void Internal_FreeNodenum(INT32 nodenum)
 
 char *I_NetSplitAddress(char *host, char **port)
 {
-	boolean v4 = (strchr(host, '.') != NULL);
+	boolean v4 = (host[0] != '[');
 
 	host = strtok(host, v4 ? ":" : "[]");
 
@@ -1205,7 +1195,6 @@ boolean D_CheckNetGame(void)
 
 	I_NetGet = Internal_Get;
 	I_NetSend = Internal_Send;
-	I_NetCanSend = NULL;
 	I_NetCloseSocket = NULL;
 	I_NetFreeNodenum = Internal_FreeNodenum;
 	I_NetMakeNodewPort = NULL;
@@ -1375,7 +1364,6 @@ void D_CloseConnection(void)
 
 		I_NetGet = Internal_Get;
 		I_NetSend = Internal_Send;
-		I_NetCanSend = NULL;
 		I_NetCloseSocket = NULL;
 		I_NetFreeNodenum = Internal_FreeNodenum;
 		I_NetMakeNodewPort = NULL;
diff --git a/src/netcode/d_netcmd.c b/src/netcode/d_netcmd.c
index 64feeeda314e6423354fbca9635fa30d6769db51..8376f26b9b4e853197f7d4fe393e7fb52cf322fd 100644
--- a/src/netcode/d_netcmd.c
+++ b/src/netcode/d_netcmd.c
@@ -391,7 +391,7 @@ static CV_PossibleValue_t perfstats_cons_t[] = {
 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}};
-consvar_t cv_ps_samplesize = CVAR_INIT ("ps_samplesize", "1", CV_CALL, ps_samplesize_cons_t, PS_SampleSize_OnChange);
+consvar_t cv_ps_samplesize = CVAR_INIT ("ps_samplesize", "175", CV_CALL, ps_samplesize_cons_t, PS_SampleSize_OnChange);
 static CV_PossibleValue_t ps_descriptor_cons_t[] = {
 	{1, "Average"}, {2, "SD"}, {3, "Minimum"}, {4, "Maximum"}, {0, NULL}};
 consvar_t cv_ps_descriptor = CVAR_INIT ("ps_descriptor", "Average", 0, ps_descriptor_cons_t, NULL);
@@ -4910,11 +4910,13 @@ static void Name2_OnChange(void)
 
 static boolean Skin_CanChange(const char *valstr)
 {
-	(void)valstr;
-
 	if (!Playing())
 		return true; // do whatever you want
 
+	// You already are that skin.
+	if (stricmp(skins[players[consoleplayer].skin]->name, valstr) == 0)
+		return false;
+
 	if (!(multiplayer || netgame)) // In single player.
 		return true;
 
@@ -4929,11 +4931,13 @@ static boolean Skin_CanChange(const char *valstr)
 
 static boolean Skin2_CanChange(const char *valstr)
 {
-	(void)valstr;
-
 	if (!Playing() || !splitscreen)
 		return true; // do whatever you want
 
+	// You already are that skin.
+	if (stricmp(skins[players[secondarydisplayplayer].skin]->name, valstr) == 0)
+		return false;
+
 	if (CanChangeSkin(secondarydisplayplayer) && !P_PlayerMoving(secondarydisplayplayer))
 		return true;
 	else
diff --git a/src/netcode/d_netfil.c b/src/netcode/d_netfil.c
index adec1a0e47ecd4a2996a7edcf0016eb9c4529936..313905f438e0203e4d757a1c41b6db596fe48941 100644
--- a/src/netcode/d_netfil.c
+++ b/src/netcode/d_netfil.c
@@ -95,6 +95,7 @@ static filetran_t transfer[MAXNETNODES];
 INT32 fileneedednum; // Number of files needed to join the server
 fileneeded_t *fileneeded; // List of needed files
 static tic_t lasttimeackpacketsent = 0;
+static I_mutex downloadmutex;
 char downloaddir[512] = "DOWNLOAD";
 
 // For resuming failed downloads
@@ -606,7 +607,7 @@ void AddLuaFileTransfer(const char *filename, const char *mode)
 		prevnext = &((*prevnext)->next);
 
 	// Allocate file transfer information and append it to the transfer list
-	filetransfer = malloc(sizeof(luafiletransfer_t));
+	filetransfer = calloc(1, sizeof(luafiletransfer_t));
 	if (!filetransfer)
 		I_Error("AddLuaFileTransfer: Out of memory\n");
 	*prevnext = filetransfer;
@@ -1609,11 +1610,13 @@ boolean CURLPrepareFile(const char* url, int dfilenum)
 		I_Error("Attempted to download files in -nodownload mode");
 #endif
 
-	curl_global_init(CURL_GLOBAL_ALL);
+	if (!multi_handle)
+	{
+		curl_global_init(CURL_GLOBAL_ALL);
+		multi_handle = curl_multi_init();
+	}
 
 	http_handle = curl_easy_init();
-	multi_handle = curl_multi_init();
-
 	if (http_handle && multi_handle)
 	{
 		I_mkdir(downloaddir, 0755);
@@ -1672,6 +1675,8 @@ boolean CURLPrepareFile(const char* url, int dfilenum)
 		filedownload.current = dfilenum;
 		filedownload.http_running = true;
 
+		I_spawn_thread("http-download", (I_thread_fn)CURLGetFile, NULL);
+
 		return true;
 	}
 
@@ -1680,103 +1685,119 @@ boolean CURLPrepareFile(const char* url, int dfilenum)
 	return false;
 }
 
+void CURLAbortFile(void)
+{
+	filedownload.http_running = false;
+
+	// lock and unlock to wait for the download thread to exit
+	I_lock_mutex(&downloadmutex);
+	I_unlock_mutex(downloadmutex);
+}
+
 void CURLGetFile(void)
 {
+	I_lock_mutex(&downloadmutex);
 	CURLMcode mc; /* return code used by curl_multi_wait() */
 	CURLcode easyres; /* Return from easy interface */
-	int numfds;
 	CURLMsg *m; /* for picking up messages with the transfer status */
 	CURL *e;
 	int msgs_left; /* how many messages are left */
 	const char *easy_handle_error;
+	boolean running = true;
 
-	if (curl_runninghandles)
+	while (running && filedownload.http_running)
 	{
-		curl_multi_perform(multi_handle, &curl_runninghandles);
+		if (curl_runninghandles)
+		{
+			curl_multi_perform(multi_handle, &curl_runninghandles);
 
-		/* wait for activity, timeout or "nothing" */
-		mc = curl_multi_wait(multi_handle, NULL, 0, 1000, &numfds);
+			/* wait for activity, timeout or "nothing" */
+			mc = curl_multi_wait(multi_handle, NULL, 0, 1000, NULL);
 
-		if (mc != CURLM_OK)
-		{
-			CONS_Alert(CONS_WARNING, "curl_multi_wait() failed, code %d.\n", mc);
-			return;
+			if (mc != CURLM_OK)
+			{
+				CONS_Alert(CONS_WARNING, "curl_multi_wait() failed, code %d.\n", mc);
+				continue;
+			}
+			curl_curfile->currentsize = curl_dlnow;
+			curl_curfile->totalsize = curl_dltotal;
 		}
-		curl_curfile->currentsize = curl_dlnow;
-		curl_curfile->totalsize = curl_dltotal;
-	}
 
-	/* See how the transfers went */
-	while ((m = curl_multi_info_read(multi_handle, &msgs_left)))
-	{
-		if (m && (m->msg == CURLMSG_DONE))
+		/* See how the transfers went */
+		while ((m = curl_multi_info_read(multi_handle, &msgs_left)))
 		{
-			e = m->easy_handle;
-			easyres = m->data.result;
-
-			char *filename = Z_StrDup(curl_realname);
-			nameonly(filename);
-
-			if (easyres != CURLE_OK)
+			if (m && (m->msg == CURLMSG_DONE))
 			{
-				long response_code = 0;
+				running = false;
+				e = m->easy_handle;
+				easyres = m->data.result;
 
-				if (easyres == CURLE_HTTP_RETURNED_ERROR)
-					curl_easy_getinfo(e, CURLINFO_RESPONSE_CODE, &response_code);
+				char *filename = Z_StrDup(curl_realname);
+				nameonly(filename);
 
-				if (response_code == 404)
-					curl_curfile->failed = FDOWNLOAD_FAIL_NOTFOUND;
-				else
-					curl_curfile->failed = FDOWNLOAD_FAIL_OTHER;
-
-				easy_handle_error = (response_code) ? va("HTTP response code %ld", response_code) : curl_easy_strerror(easyres);
-				curl_curfile->status = FS_FALLBACK;
-				curl_curfile->currentsize = curl_origfilesize;
-				curl_curfile->totalsize = curl_origtotalfilesize;
-				filedownload.http_failed = true;
-				fclose(curl_curfile->file);
-				remove(curl_curfile->filename);
-				CONS_Alert(CONS_ERROR, M_GetText("Failed to download addon \"%s\" (%s)\n"), filename, easy_handle_error);
-			}
-			else
-			{
-				fclose(curl_curfile->file);
+				if (easyres != CURLE_OK)
+				{
+					long response_code = 0;
 
-				CONS_Printf(M_GetText("Finished download of \"%s\"\n"), filename);
+					if (easyres == CURLE_HTTP_RETURNED_ERROR)
+						curl_easy_getinfo(e, CURLINFO_RESPONSE_CODE, &response_code);
 
-				if (checkfilemd5(curl_curfile->filename, curl_curfile->md5sum) == FS_MD5SUMBAD)
-				{
-					CONS_Alert(CONS_WARNING, M_GetText("File \"%s\" does not match the version used by the server\n"), filename);
+					if (response_code == 404)
+						curl_curfile->failed = FDOWNLOAD_FAIL_NOTFOUND;
+					else
+						curl_curfile->failed = FDOWNLOAD_FAIL_OTHER;
+
+					easy_handle_error = (response_code) ? va("HTTP response code %ld", response_code) : curl_easy_strerror(easyres);
 					curl_curfile->status = FS_FALLBACK;
-					curl_curfile->failed = FDOWNLOAD_FAIL_MD5SUMBAD;
+					curl_curfile->currentsize = curl_origfilesize;
+					curl_curfile->totalsize = curl_origtotalfilesize;
 					filedownload.http_failed = true;
+					fclose(curl_curfile->file);
+					remove(curl_curfile->filename);
+					CONS_Alert(CONS_ERROR, M_GetText("Failed to download addon \"%s\" (%s)\n"), filename, easy_handle_error);
 				}
 				else
 				{
-					filedownload.completednum++;
-					filedownload.completedsize += curl_curfile->totalsize;
-					curl_curfile->status = FS_FOUND;
+					fclose(curl_curfile->file);
+
+					CONS_Printf(M_GetText("Finished download of \"%s\"\n"), filename);
+
+					if (checkfilemd5(curl_curfile->filename, curl_curfile->md5sum) == FS_MD5SUMBAD)
+					{
+						CONS_Alert(CONS_WARNING, M_GetText("File \"%s\" does not match the version used by the server\n"), filename);
+						curl_curfile->status = FS_FALLBACK;
+						curl_curfile->failed = FDOWNLOAD_FAIL_MD5SUMBAD;
+						filedownload.http_failed = true;
+					}
+					else
+					{
+						filedownload.completednum++;
+						filedownload.completedsize += curl_curfile->totalsize;
+						curl_curfile->status = FS_FOUND;
+					}
 				}
-			}
 
-			Z_Free(filename);
+				Z_Free(filename);
 
-			curl_curfile->file = NULL;
-			filedownload.http_running = false;
-			filedownload.remaining--;
-			curl_multi_remove_handle(multi_handle, e);
-			curl_easy_cleanup(e);
+				curl_curfile->file = NULL;
+				filedownload.remaining--;
+				curl_multi_remove_handle(multi_handle, e);
+				curl_easy_cleanup(e);
 
-			if (!filedownload.remaining)
-				break;
+				if (!filedownload.remaining)
+					break;
+			}
 		}
 	}
 
-	if (!filedownload.remaining)
+	if (!filedownload.remaining || !filedownload.http_running)
 	{
 		curl_multi_cleanup(multi_handle);
 		curl_global_cleanup();
+		multi_handle = NULL;
 	}
+	filedownload.http_running = false;
+	I_unlock_mutex(downloadmutex);
 }
 
 HTTP_login *
diff --git a/src/netcode/d_netfil.h b/src/netcode/d_netfil.h
index 4039b5e2d5cc4b0d81fbdc04e6d7c7bef46f724a..9f29d18bb79ff39dda69fffd68e2756f8d4f0db9 100644
--- a/src/netcode/d_netfil.h
+++ b/src/netcode/d_netfil.h
@@ -140,6 +140,7 @@ boolean CL_SendFileRequest(void);
 void PT_RequestFile(SINT8 node);
 
 boolean CURLPrepareFile(const char* url, int dfilenum);
+void CURLAbortFile(void);
 void CURLGetFile(void);
 HTTP_login * CURLGetLogin (const char *url, HTTP_login ***return_prev_next);
 
diff --git a/src/netcode/i_net.h b/src/netcode/i_net.h
index 4fd31799463291a92005b1345f66160aaeb9966f..a2039bd1902197d1c812f7490fad3e1493f60588 100644
--- a/src/netcode/i_net.h
+++ b/src/netcode/i_net.h
@@ -67,18 +67,10 @@ extern doomcom_t *doomcom;
 */
 extern boolean (*I_NetGet)(void);
 
-/**	\brief ask to driver if there is data waiting
-*/
-extern boolean (*I_NetCanGet)(void);
-
 /**	\brief send packet within doomcom struct
 */
 extern void (*I_NetSend)(void);
 
-/**	\brief ask to driver if all is ok to send data now
-*/
-extern boolean (*I_NetCanSend)(void);
-
 /**	\brief	close a connection
 
 	\param	nodenum	node to be closed
diff --git a/src/netcode/i_tcp.c b/src/netcode/i_tcp.c
index ed1f5b4e749d3734e3876b0826d82591235218e1..256d9992eff6acdd614ac9bf99428e459884fe2a 100644
--- a/src/netcode/i_tcp.c
+++ b/src/netcode/i_tcp.c
@@ -143,7 +143,6 @@ typedef union
 #endif
 
 #include "i_addrinfo.h"
-#define SELECTTEST
 #define DEFAULTPORT "5029"
 
 #ifdef USE_WINSOCK
@@ -631,56 +630,6 @@ static boolean SOCK_Get(void)
 	return false;
 }
 
-// check if we can send (do not go over the buffer)
-
-static fd_set masterset;
-
-#ifdef SELECTTEST
-static boolean FD_CPY(fd_set *src, fd_set *dst, SOCKET_TYPE *fd, size_t len)
-{
-	boolean testset = false;
-	FD_ZERO(dst);
-	for (size_t i = 0; i < len;i++)
-	{
-		if(fd[i] != (SOCKET_TYPE)ERRSOCKET &&
-		   FD_ISSET(fd[i], src) && !FD_ISSET(fd[i], dst)) // no checking for dups
-		{
-			FD_SET(fd[i], dst);
-			testset = true;
-		}
-	}
-	return testset;
-}
-
-static boolean SOCK_CanSend(void)
-{
-	struct timeval timeval_for_select = {0, 0};
-	fd_set tset;
-	int wselect;
-
-	if(!FD_CPY(&masterset, &tset, mysockets, mysocketses))
-		return false;
-	wselect = select(255, NULL, &tset, NULL, &timeval_for_select);
-	if (wselect >= 1)
-		return true;
-	return false;
-}
-
-static boolean SOCK_CanGet(void)
-{
-	struct timeval timeval_for_select = {0, 0};
-	fd_set tset;
-	int rselect;
-
-	if(!FD_CPY(&masterset, &tset, mysockets, mysocketses))
-		return false;
-	rselect = select(255, &tset, NULL, NULL, &timeval_for_select);
-	if (rselect >= 1)
-		return true;
-	return false;
-}
-#endif
-
 static inline ssize_t SOCK_SendToAddr(SOCKET_TYPE socket, mysockaddr_t *sockaddr)
 {
 	socklen_t d4 = (socklen_t)sizeof(struct sockaddr_in);
@@ -922,7 +871,6 @@ static boolean UDP_Socket(void)
 		mysockets[s] = ERRSOCKET;
 	for (s = 0; s < MAXNETNODES+1; s++)
 		nodesocket[s] = ERRSOCKET;
-	FD_ZERO(&masterset);
 	s = 0;
 
 	memset(&hints, 0x00, sizeof (hints));
@@ -949,7 +897,6 @@ static boolean UDP_Socket(void)
 					mysockets[s] = UDP_Bind(runp->ai_family, runp->ai_addr, (socklen_t)runp->ai_addrlen);
 					if (mysockets[s] != (SOCKET_TYPE)ERRSOCKET)
 					{
-						FD_SET(mysockets[s], &masterset);
 						myfamily[s] = hints.ai_family;
 						s++;
 					}
@@ -970,7 +917,6 @@ static boolean UDP_Socket(void)
 				mysockets[s] = UDP_Bind(runp->ai_family, runp->ai_addr, (socklen_t)runp->ai_addrlen);
 				if (mysockets[s] != (SOCKET_TYPE)ERRSOCKET)
 				{
-					FD_SET(mysockets[s], &masterset);
 					myfamily[s] = hints.ai_family;
 					s++;
 #ifdef HAVE_MINIUPNPC
@@ -1003,7 +949,6 @@ static boolean UDP_Socket(void)
 						mysockets[s] = UDP_Bind(runp->ai_family, runp->ai_addr, (socklen_t)runp->ai_addrlen);
 						if (mysockets[s] != (SOCKET_TYPE)ERRSOCKET)
 						{
-							FD_SET(mysockets[s], &masterset);
 							myfamily[s] = hints.ai_family;
 							s++;
 						}
@@ -1024,7 +969,6 @@ static boolean UDP_Socket(void)
 					mysockets[s] = UDP_Bind(runp->ai_family, runp->ai_addr, (socklen_t)runp->ai_addrlen);
 					if (mysockets[s] != (SOCKET_TYPE)ERRSOCKET)
 					{
-						FD_SET(mysockets[s], &masterset);
 						myfamily[s] = hints.ai_family;
 						s++;
 					}
@@ -1150,16 +1094,13 @@ boolean I_InitTcpDriver(void)
 
 static void SOCK_CloseSocket(void)
 {
-	for (size_t i=0; i < MAXNETNODES+1; i++)
+	for (size_t i=0; i < mysocketses; i++)
 	{
-		if (mysockets[i] != (SOCKET_TYPE)ERRSOCKET
-		 && FD_ISSET(mysockets[i], &masterset))
-		{
-			FD_CLR(mysockets[i], &masterset);
+		if (mysockets[i] != (SOCKET_TYPE)ERRSOCKET)
 			close(mysockets[i]);
-		}
 		mysockets[i] = ERRSOCKET;
 	}
+	mysocketses = 0;
 }
 
 void I_ShutdownTcpDriver(void)
@@ -1211,11 +1152,7 @@ static SINT8 SOCK_NetMakeNodewPort(const char *address, const char *port)
 		// test ip address of server
 		for (i = 0; i < mysocketses; ++i)
 		{
-			/* sendto tests that there is a network to this
-				address */
-			if (runp->ai_addr->sa_family == myfamily[i] &&
-					sendto(mysockets[i], NULL, 0, 0,
-						runp->ai_addr, runp->ai_addrlen) == 0)
+			if (runp->ai_addr->sa_family == myfamily[i])
 			{
 				memcpy(&clientaddress[newnode], runp->ai_addr, runp->ai_addrlen);
 				break;
@@ -1245,12 +1182,6 @@ static boolean SOCK_OpenSocket(void)
 	I_NetFreeNodenum = SOCK_FreeNodenum;
 	I_NetMakeNodewPort = SOCK_NetMakeNodewPort;
 
-#ifdef SELECTTEST
-	// seem like not work with libsocket : (
-	I_NetCanSend = SOCK_CanSend;
-	I_NetCanGet = SOCK_CanGet;
-#endif
-
 	// build the socket but close it first
 	SOCK_CloseSocket();
 	return UDP_Socket();
@@ -1273,7 +1204,7 @@ static boolean SOCK_Ban(INT32 node)
 	else if (banned[numbans].any.sa_family == AF_INET6)
 	{
 		banned[numbans].ip6.sin6_port = 0;
-		bannedmask[numbans] = 128;
+		bannedmask[numbans] = 64;
 	}
 #endif
 	numbans++;
@@ -1310,7 +1241,7 @@ static boolean SOCK_SetBanAddress(const char *address, const char *mask)
 			bannedmask[numbans] = (UINT8)atoi(mask);
 #ifdef HAVE_IPV6
 		else if (runp->ai_family == AF_INET6)
-			bannedmask[numbans] = 128;
+			bannedmask[numbans] = 64;
 #endif
 		else
 			bannedmask[numbans] = 32;
@@ -1319,7 +1250,7 @@ static boolean SOCK_SetBanAddress(const char *address, const char *mask)
 			bannedmask[numbans] = 32;
 #ifdef HAVE_IPV6
 		else if (bannedmask[numbans] > 128 && runp->ai_family == AF_INET6)
-			bannedmask[numbans] = 128;
+			bannedmask[numbans] = 64;
 #endif
 		numbans++;
 		runp = runp->ai_next;
diff --git a/src/netcode/mserv.c b/src/netcode/mserv.c
index 78a395344994e2c3ba9552649e414fd7d07d839f..fba36a3babddc507dd9d3c7601682ac50baa7a45 100644
--- a/src/netcode/mserv.c
+++ b/src/netcode/mserv.c
@@ -55,6 +55,7 @@ static boolean ServerName_CanChange (const char*);
 static void Update_parameters (void);
 
 static void MasterServer_OnChange(void);
+static void RoomId_OnChange(void);
 
 static CV_PossibleValue_t masterserver_update_rate_cons_t[] = {
 	{2,  "MIN"},
@@ -66,8 +67,9 @@ consvar_t cv_masterserver = CVAR_INIT ("masterserver", "https://ds.ms.srb2.org/M
 consvar_t cv_servername = CVAR_INIT_WITH_CALLBACKS ("servername", "SRB2 server", CV_SAVE|CV_NETVAR|CV_CALL|CV_NOINIT|CV_ALLOWLUA, NULL, Update_parameters, ServerName_CanChange);
 
 consvar_t cv_masterserver_update_rate = CVAR_INIT ("masterserver_update_rate", "15", CV_SAVE|CV_CALL|CV_NOINIT, masterserver_update_rate_cons_t, Update_parameters);
+consvar_t cv_masterserver_room_id = CVAR_INIT ("masterserver_room_id", "0", CV_CALL, CV_Unsigned, RoomId_OnChange);
 
-INT16 ms_RoomId = -1;
+INT16 ms_RoomId = 0;
 
 #if defined (MASTERSERVER) && defined (HAVE_THREADS)
 int           ms_QueryId;
@@ -92,6 +94,7 @@ void AddMServCommands(void)
 {
 	CV_RegisterVar(&cv_masterserver);
 	CV_RegisterVar(&cv_masterserver_update_rate);
+	CV_RegisterVar(&cv_masterserver_room_id);
 	CV_RegisterVar(&cv_masterserver_timeout);
 	CV_RegisterVar(&cv_masterserver_debug);
 	CV_RegisterVar(&cv_masterserver_token);
@@ -534,6 +537,17 @@ Update_parameters (void)
 #endif/*MASTERSERVER*/
 }
 
+static void RoomId_OnChange(void)
+{
+	if (ms_RoomId != cv_masterserver_room_id.value)
+	{
+		UnregisterServer();
+		ms_RoomId = cv_masterserver_room_id.value;
+		if (Online())
+			RegisterServer();
+	}
+}
+
 static void MasterServer_OnChange(void)
 {
 #ifdef MASTERSERVER
diff --git a/src/netcode/mserv.h b/src/netcode/mserv.h
index 0bc8c8e6b878a415887e2a9613188d3ceac30956..419c11a89409d7491249d159f669285c344fdee5 100644
--- a/src/netcode/mserv.h
+++ b/src/netcode/mserv.h
@@ -66,6 +66,7 @@ typedef struct
 
 extern consvar_t cv_masterserver, cv_servername;
 extern consvar_t cv_masterserver_update_rate;
+extern consvar_t cv_masterserver_room_id;
 extern consvar_t cv_masterserver_timeout;
 extern consvar_t cv_masterserver_debug;
 extern consvar_t cv_masterserver_token;
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 2ae7276dfcca0518f74712964f6b1c1cd7265a8f..60cffebfc4b09dda07f6c84e65ff66a0869cb0aa 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -3744,7 +3744,7 @@ static void P_DoBossVictory(mobj_t *mo)
 	// scan the remaining thinkers to see if all bosses are dead
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -6449,7 +6449,7 @@ void A_RingExplode(mobj_t *actor)
 
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -8756,7 +8756,7 @@ void A_FindTarget(mobj_t *actor)
 	// scan the thinkers
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -8820,7 +8820,7 @@ void A_FindTracer(mobj_t *actor)
 	// scan the thinkers
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -9498,7 +9498,7 @@ void A_RemoteAction(mobj_t *actor)
 		// scan the thinkers
 		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			if (th->removing)
 				continue;
 
 			mo2 = (mobj_t *)th;
@@ -9761,7 +9761,7 @@ void A_SetObjectTypeState(mobj_t *actor)
 
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -10391,7 +10391,7 @@ void A_CheckThingCount(mobj_t *actor)
 
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -14395,7 +14395,7 @@ void A_LavafallLava(mobj_t *actor)
 	if (LUA_CallAction(A_LAVAFALLLAVA, actor))
 		return;
 
-	if ((40 - actor->fuse) % (2*(actor->scale >> FRACBITS)))
+	if ((40 - actor->fuse) % max(2*(actor->scale >> FRACBITS), 1)) // avoid crashes if actor->scale < FRACUNIT
 		return;
 
 	// Don't spawn lava unless a player is nearby.
diff --git a/src/p_inter.c b/src/p_inter.c
index 19ab5ead4b8cbb50fef7ace0b6055cca9b89c58a..0e63fea1b9500cb8b9d22642bfb9f0d9c7fd2b20 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -101,7 +101,7 @@ void P_ClearStarPost(INT32 postnum)
 	// scan the thinkers
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -130,7 +130,7 @@ void P_ResetStarposts(void)
 
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		post = (mobj_t *)th;
@@ -836,7 +836,8 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				{
 					clientGamedata->collected[special->health-1] = true;
 					M_UpdateUnlockablesAndExtraEmblems(clientGamedata);
-					G_SaveGameData(clientGamedata);
+					if (!prevCollected) // don't thrash the disk and wreak performance.
+						G_SaveGameData(clientGamedata);
 				}
 
 				if (netgame)
@@ -1002,7 +1003,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 						// scan the thinkers to find the corresponding anchorpoint
 						for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 						{
-							if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+							if (th->removing)
 								continue;
 
 							mo2 = (mobj_t *)th;
@@ -1096,7 +1097,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				// scan the remaining thinkers
 				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+					if (th->removing)
 						continue;
 
 					mo2 = (mobj_t *)th;
@@ -1146,7 +1147,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				// in from the paraloop. Isn't this just so efficient?
 				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+					if (th->removing)
 						continue;
 
 					mo2 = (mobj_t *)th;
@@ -1521,7 +1522,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				// scan the remaining thinkers to find koopa
 				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+					if (th->removing)
 						continue;
 
 					mo2 = (mobj_t *)th;
@@ -2019,7 +2020,7 @@ void P_TouchStarPost(mobj_t *post, player_t *player, boolean snaptopost)
 
 		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			if (th->removing)
 				continue;
 
 			mo2 = (mobj_t *)th;
@@ -2869,7 +2870,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 				// scan the thinkers to make sure all the old pinch dummies are gone on death
 				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 				{
-					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+					if (th->removing)
 						continue;
 
 					mo = (mobj_t *)th;
diff --git a/src/p_local.h b/src/p_local.h
index 39856bffbcc5d7d04272afdb143974af18b95d80..3253ef0b68f0b208ef3b8d8574d62c51fd6d47fc 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -39,11 +39,6 @@
 // Convenience macro to fix issue with collision along bottom/left edges of blockmap -Red
 #define BMBOUNDFIX(xl, xh, yl, yh) {if (xl > xh) xl = 0; if (yl > yh) yl = 0;}
 
-// MAXRADIUS is for precalculated sector block boxes
-// the spider demon is larger,
-// but we do not have any moving sectors nearby
-#define MAXRADIUS (32*FRACUNIT)
-
 // max Z move up or down without jumping
 // above this, a height difference is considered as a 'dropoff'
 #define MAXSTEPMOVE (24*FRACUNIT)
diff --git a/src/p_map.c b/src/p_map.c
index 1116ae06ad0a9dc792ffc444ca456803b22e8bf8..fce17f8c4bb2357eb9d789a46261119e67f9e946 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -36,6 +36,9 @@
 
 #include "m_perfstats.h" // ps_checkposition_calls
 
+// Formerly called MAXRADIUS
+#define MAXTRYMOVE (32*FRACUNIT)
+
 fixed_t tmbbox[4];
 mobj_t *tmthing;
 static INT32 tmflags;
@@ -2165,15 +2168,10 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 		}
 	}
 
-	// The bounding box is extended by MAXRADIUS
-	// because mobj_ts are grouped into mapblocks
-	// based on their origin point, and can overlap
-	// into adjacent blocks by up to MAXRADIUS units.
-
-	xl = (unsigned)(tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
-	xh = (unsigned)(tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
-	yl = (unsigned)(tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
-	yh = (unsigned)(tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
+	xl = (unsigned)(tmbbox[BOXLEFT] - bmaporgx)>>MAPBLOCKSHIFT;
+	xh = (unsigned)(tmbbox[BOXRIGHT] - bmaporgx)>>MAPBLOCKSHIFT;
+	yl = (unsigned)(tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT;
+	yh = (unsigned)(tmbbox[BOXTOP] - bmaporgy)>>MAPBLOCKSHIFT;
 
 	BMBOUNDFIX(xl, xh, yl, yh);
 
@@ -2393,11 +2391,6 @@ boolean P_CheckCameraPosition(fixed_t x, fixed_t y, camera_t *thiscam)
 		}
 	}
 
-	// The bounding box is extended by MAXRADIUS
-	// because mobj_ts are grouped into mapblocks
-	// based on their origin point, and can overlap
-	// into adjacent blocks by up to MAXRADIUS units.
-
 	xl = (unsigned)(tmbbox[BOXLEFT] - bmaporgx)>>MAPBLOCKSHIFT;
 	xh = (unsigned)(tmbbox[BOXRIGHT] - bmaporgx)>>MAPBLOCKSHIFT;
 	yl = (unsigned)(tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT;
@@ -2528,16 +2521,16 @@ boolean P_TryCameraMove(fixed_t x, fixed_t y, camera_t *thiscam)
 		}
 
 		do {
-			if (x-tryx > MAXRADIUS)
-				tryx += MAXRADIUS;
-			else if (x-tryx < -MAXRADIUS)
-				tryx -= MAXRADIUS;
+			if (x-tryx > MAXTRYMOVE)
+				tryx += MAXTRYMOVE;
+			else if (x-tryx < -MAXTRYMOVE)
+				tryx -= MAXTRYMOVE;
 			else
 				tryx = x;
-			if (y-tryy > MAXRADIUS)
-				tryy += MAXRADIUS;
-			else if (y-tryy < -MAXRADIUS)
-				tryy -= MAXRADIUS;
+			if (y-tryy > MAXTRYMOVE)
+				tryy += MAXTRYMOVE;
+			else if (y-tryy < -MAXTRYMOVE)
+				tryy -= MAXTRYMOVE;
 			else
 				tryy = y;
 
@@ -2683,7 +2676,7 @@ increment_move
 	floatok = false;
 
 	// This makes sure that there are no freezes from computing extremely small movements.
-	// Originally was MAXRADIUS/2, but that can cause some bad inconsistencies for small players.
+	// Originally was MAXTRYMOVE/2, but that can cause some bad inconsistencies for small players.
 	radius = max(radius, thing->scale);
 
 	// And we also have to prevent Big Large (tm) movements, as those can skip too far
@@ -2872,10 +2865,10 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 	{
 		INT32 xl, xh, yl, yh;
 
-		yh = (unsigned)(thing->y + MAXRADIUS - bmaporgy)>>MAPBLOCKSHIFT;
-		yl = (unsigned)(thing->y - MAXRADIUS - bmaporgy)>>MAPBLOCKSHIFT;
-		xh = (unsigned)(thing->x + MAXRADIUS - bmaporgx)>>MAPBLOCKSHIFT;
-		xl = (unsigned)(thing->x - MAXRADIUS - bmaporgx)>>MAPBLOCKSHIFT;
+		yh = (unsigned)(thing->y + thing->radius - bmaporgy)>>MAPBLOCKSHIFT;
+		yl = (unsigned)(thing->y - thing->radius - bmaporgy)>>MAPBLOCKSHIFT;
+		xh = (unsigned)(thing->x + thing->radius - bmaporgx)>>MAPBLOCKSHIFT;
+		xl = (unsigned)(thing->x - thing->radius - bmaporgx)>>MAPBLOCKSHIFT;
 
 		BMBOUNDFIX(xl, xh, yl, yh);
 
@@ -2947,16 +2940,16 @@ boolean P_SceneryTryMove(mobj_t *thing, fixed_t x, fixed_t y)
 	tryx = thing->x;
 	tryy = thing->y;
 	do {
-		if (x-tryx > MAXRADIUS)
-			tryx += MAXRADIUS;
-		else if (x-tryx < -MAXRADIUS)
-			tryx -= MAXRADIUS;
+		if (x-tryx > MAXTRYMOVE)
+			tryx += MAXTRYMOVE;
+		else if (x-tryx < -MAXTRYMOVE)
+			tryx -= MAXTRYMOVE;
 		else
 			tryx = x;
-		if (y-tryy > MAXRADIUS)
-			tryy += MAXRADIUS;
-		else if (y-tryy < -MAXRADIUS)
-			tryy -= MAXRADIUS;
+		if (y-tryy > MAXTRYMOVE)
+			tryy += MAXTRYMOVE;
+		else if (y-tryy < -MAXTRYMOVE)
+			tryy -= MAXTRYMOVE;
 		else
 			tryy = y;
 
@@ -4215,7 +4208,8 @@ void P_RadiusAttack(mobj_t *spot, mobj_t *source, fixed_t damagedist, UINT8 dama
 	INT32 xl, xh, yl, yh;
 	fixed_t dist;
 
-	dist = FixedMul(damagedist, spot->scale) + MAXRADIUS;
+	dist = FixedMul(damagedist, spot->scale);
+
 	yh = (unsigned)(spot->y + dist - bmaporgy)>>MAPBLOCKSHIFT;
 	yl = (unsigned)(spot->y - dist - bmaporgy)>>MAPBLOCKSHIFT;
 	xh = (unsigned)(spot->x + dist - bmaporgx)>>MAPBLOCKSHIFT;
@@ -4385,15 +4379,15 @@ static boolean P_CheckSectorPolyObjects(sector_t *sector, boolean realcrush, boo
 			{
 				mobj_t *mo;
 				blocknode_t *block;
+				blocknode_t *next = NULL;
 
 				if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
 					continue;
 
-				block = blocklinks[y * bmapwidth + x];
-
-				for (; block; block = block->mnext)
+				for (block = blocklinks[y * bmapwidth + x]; block != NULL; block = next)
 				{
 					mo = block->mobj;
+					next = block->mnext;
 
 					// Monster Iestyn: do we need to check if a mobj has already been checked? ...probably not I suspect
 					if (!P_MobjInsidePolyobj(po, mo))
diff --git a/src/p_maputl.c b/src/p_maputl.c
index f10a396a3f2d2b74946a22f90c1f13a2c3f6bfc5..5398fd7a4c584022e8f33bb49312a176207e74b8 100644
--- a/src/p_maputl.c
+++ b/src/p_maputl.c
@@ -1052,35 +1052,23 @@ boolean P_BlockLinesIterator(INT32 x, INT32 y, boolean (*func)(line_t *))
 //
 boolean P_BlockThingsIterator(INT32 x, INT32 y, boolean (*func)(mobj_t *))
 {
-	mobj_t *bnext = NULL;
 	blocknode_t *block, *next = NULL;
 
 	if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
 		return true;
 
 	// Check interaction with the objects in the blockmap.
-	for (block = blocklinks[y*bmapwidth + x]; block; block = next)
+	for (block = blocklinks[y*bmapwidth + x]; block != NULL; block = next)
 	{
-		next = block->mnext;
-		if (next)
-			P_SetTarget(&bnext, next->mobj); // We want to note our reference to bnext here in case it is MF_NOTHINK and gets removed!
+		next = block->mnext; // We want to note our reference to mnext here!
 
 		if (!func(block->mobj))
-		{
-			P_SetTarget(&bnext, NULL);
 			return false;
-		}
 
-		if (P_MobjWasRemoved(tmthing) // func just popped our tmthing, cannot continue.
-		|| (bnext && P_MobjWasRemoved(bnext))) // func just broke blockmap chain, cannot continue.
-		{
-			P_SetTarget(&bnext, NULL);
+		if (P_MobjWasRemoved(tmthing)) // func just popped our tmthing, cannot continue.
 			return true;
-		}
 	}
 
-	P_SetTarget(&bnext, NULL);
-
 	return true;
 }
 
diff --git a/src/p_mobj.c b/src/p_mobj.c
index b377ff82f4a57bb253857b284f50b49893a373f8..22e2a38292bd1f9db3fb0fdb422e5790459cc23d 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -90,17 +90,21 @@ static void P_SetupStateAnimation(mobj_t *mobj, state_t *st)
 	if (mobj->sprite == SPR_PLAY && mobj->skin)
 	{
 		spritedef_t *spritedef = P_GetSkinSpritedef(mobj->skin, mobj->sprite2);
-		animlength = (INT32)(spritedef->numframes);
+		animlength = (INT32)(spritedef->numframes) - 1;
 	}
 	else
 		animlength = st->var1;
 
 	if (!(st->frame & FF_ANIMATE))
+	{
+		mobj->anim_duration = 0;
 		return;
+	}
 
 	if (animlength <= 0 || st->var2 == 0)
 	{
 		mobj->frame &= ~FF_ANIMATE;
+		mobj->anim_duration = 0;
 		return; // Crash/stupidity prevention
 	}
 
@@ -765,7 +769,7 @@ void P_EmeraldManager(void)
 
 	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (think->removing)
 			continue;
 
 		mo = (mobj_t *)think;
@@ -3455,7 +3459,7 @@ void P_DestroyRobots(void)
 
 	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (think->removing)
 			continue;
 
 		mo = (mobj_t *)think;
@@ -4254,7 +4258,7 @@ static void P_Boss3Thinker(mobj_t *mobj)
 			// this can happen if the boss was hurt earlier than expected
 			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+				if (th->removing)
 					continue;
 
 				mo2 = (mobj_t *)th;
@@ -5343,7 +5347,7 @@ static void P_Boss9Thinker(mobj_t *mobj)
 		// Build a hoop linked list of 'em!
 		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			if (th->removing)
 				continue;
 
 			mo2 = (mobj_t *)th;
@@ -6045,7 +6049,7 @@ mobj_t *P_GetClosestAxis(mobj_t *source)
 	// scan the thinkers to find the closest axis point
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -9337,10 +9341,10 @@ static void P_PointPushThink(mobj_t *mobj)
 	radius = mobj->spawnpoint->args[0] << FRACBITS;
 
 	pushmobj = mobj;
-	xl = (unsigned)(mobj->x - radius - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
-	xh = (unsigned)(mobj->x + radius - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
-	yl = (unsigned)(mobj->y - radius - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
-	yh = (unsigned)(mobj->y + radius - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
+	xl = (unsigned)(mobj->x - radius - bmaporgx)>>MAPBLOCKSHIFT;
+	xh = (unsigned)(mobj->x + radius - bmaporgx)>>MAPBLOCKSHIFT;
+	yl = (unsigned)(mobj->y - radius - bmaporgy)>>MAPBLOCKSHIFT;
+	yh = (unsigned)(mobj->y + radius - bmaporgy)>>MAPBLOCKSHIFT;
 
 	P_DoBlockThingsIterate(xl, yl, xh, yh, PIT_PushThing);
 }
@@ -11183,17 +11187,16 @@ tic_t itemrespawntime[ITEMQUESIZE];
 size_t iquehead, iquetail;
 
 #ifdef PARANOIA
-#define SCRAMBLE_REMOVED // Force debug build to crash when Removed mobj is accessed
+#define SCRAMBLE_REMOVED // Force debug build to crash when a removed mobj is accessed
 #endif
 void P_RemoveMobj(mobj_t *mobj)
 {
 	I_Assert(mobj != NULL);
-	if (P_MobjWasRemoved(mobj))
-		return; // something already removing this mobj.
+	if (P_MobjWasRemoved(mobj) || mobj->thinker.removing)
+		return; // Something already removed or is removing this mobj.
 
-	mobj->thinker.function.acp1 = (actionf_p1)P_RemoveThinkerDelayed; // shh. no recursing.
+	mobj->thinker.removing = true; // Set earlier to avoid recursion.
 	LUA_HookMobj(mobj, MOBJ_HOOK(MobjRemoved));
-	mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker; // needed for P_UnsetThingPosition, etc. to work.
 
 	// Rings only, please!
 	if (mobj->spawnpoint &&
@@ -12863,7 +12866,7 @@ static boolean P_MapAlreadyHasStarPost(mobj_t *mobj)
 
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo2 = (mobj_t *)th;
diff --git a/src/p_polyobj.c b/src/p_polyobj.c
index 168fca61f331ce96772f6124270b0be8ab66f2df..3d1a38d36ff9d9b8f7324df0b18029e35519870d 100644
--- a/src/p_polyobj.c
+++ b/src/p_polyobj.c
@@ -878,15 +878,15 @@ static void Polyobj_carryThings(polyobj_t *po, fixed_t dx, fixed_t dy)
 		{
 			mobj_t *mo;
 			blocknode_t *block;
+			blocknode_t *next = NULL;
 
 			if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
 				continue;
 
-			block = blocklinks[y * bmapwidth + x];
-
-			for (; block; block = block->mnext)
+			for (block = blocklinks[y * bmapwidth + x]; block != NULL; block = next)
 			{
 				mo = block->mobj;
+				next = block->mnext;
 
 				if (mo->lastlook == pomovecount)
 					continue;
@@ -927,11 +927,11 @@ static INT32 Polyobj_clipThings(polyobj_t *po, line_t *line)
 	if (!(po->flags & POF_SOLID))
 		return hitflags;
 
-	// adjust linedef bounding box to blockmap, extend by MAXRADIUS
-	linebox[BOXLEFT]   = (unsigned)(line->bbox[BOXLEFT]   - bmaporgx - MAXRADIUS) >> MAPBLOCKSHIFT;
-	linebox[BOXRIGHT]  = (unsigned)(line->bbox[BOXRIGHT]  - bmaporgx + MAXRADIUS) >> MAPBLOCKSHIFT;
-	linebox[BOXBOTTOM] = (unsigned)(line->bbox[BOXBOTTOM] - bmaporgy - MAXRADIUS) >> MAPBLOCKSHIFT;
-	linebox[BOXTOP]    = (unsigned)(line->bbox[BOXTOP]    - bmaporgy + MAXRADIUS) >> MAPBLOCKSHIFT;
+	// adjust linedef bounding box to blockmap
+	linebox[BOXLEFT]   = (unsigned)(line->bbox[BOXLEFT]   - bmaporgx) >> MAPBLOCKSHIFT;
+	linebox[BOXRIGHT]  = (unsigned)(line->bbox[BOXRIGHT]  - bmaporgx) >> MAPBLOCKSHIFT;
+	linebox[BOXBOTTOM] = (unsigned)(line->bbox[BOXBOTTOM] - bmaporgy) >> MAPBLOCKSHIFT;
+	linebox[BOXTOP]    = (unsigned)(line->bbox[BOXTOP]    - bmaporgy) >> MAPBLOCKSHIFT;
 
 	// check all mobj blockmap cells the line contacts
 	for (y = linebox[BOXBOTTOM]; y <= linebox[BOXTOP]; ++y)
@@ -942,9 +942,11 @@ static INT32 Polyobj_clipThings(polyobj_t *po, line_t *line)
 			{
 				mobj_t *mo = NULL;
 				blocknode_t *block = blocklinks[y * bmapwidth + x];
+				blocknode_t *next = NULL;
 
-				for (; block; block = block->mnext)
+				for (; block != NULL; block = next)
 				{
+					next = block->mnext;
 					mo = block->mobj;
 
 					// Don't scroll objects that aren't affected by gravity
@@ -1115,15 +1117,15 @@ static void Polyobj_rotateThings(polyobj_t *po, vector2_t origin, angle_t delta,
 		{
 			mobj_t *mo;
 			blocknode_t *block;
+			blocknode_t *next = NULL;
 
 			if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
 				continue;
 
-			block = blocklinks[y * bmapwidth + x];
-
-			for (; block; block = block->mnext)
+			for (block = blocklinks[y * bmapwidth + x]; block != NULL; block = next)
 			{
 				mo = block->mobj;
+				next = block->mnext;
 
 				if (mo->lastlook == pomovecount)
 					continue;
@@ -1316,7 +1318,7 @@ void Polyobj_InitLevel(void)
 	// the mobj_t pointers on a queue for use below.
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo = (mobj_t *)th;
diff --git a/src/p_saveg.c b/src/p_saveg.c
index d6f8d23c57dca656dd5d133ccd07c911a7b62bd8..650622f59f8130a896257748eb56a4116e963f7e 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -2972,8 +2972,7 @@ static void P_NetArchiveThinkers(save_t *save_p)
 		// save off the current thinkers
 		for (th = thlist[i].next; th != &thlist[i]; th = th->next)
 		{
-			if (!(th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed
-			 || th->function.acp1 == (actionf_p1)P_NullPrecipThinker))
+			if (!(th->removing || th->function.acp1 == (actionf_p1)P_NullPrecipThinker))
 				numsaved++;
 
 			if (th->function.acp1 == (actionf_p1)P_MobjThinker)
@@ -3186,7 +3185,7 @@ static void P_NetArchiveThinkers(save_t *save_p)
 			}
 #ifdef PARANOIA
 			else
-				I_Assert(th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed); // wait garbage collection
+				I_Assert(th->removing); // wait garbage collection
 #endif
 		}
 
@@ -3207,7 +3206,7 @@ mobj_t *P_FindNewPosition(UINT32 oldposition)
 
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mobj = (mobj_t *)th;
@@ -4528,7 +4527,7 @@ static inline void P_FinishMobjs(void)
 	for (currentthinker = thlist[THINK_MOBJ].next; currentthinker != &thlist[THINK_MOBJ];
 		currentthinker = currentthinker->next)
 	{
-		if (currentthinker->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (currentthinker->removing)
 			continue;
 
 		mobj = (mobj_t *)currentthinker;
@@ -4546,7 +4545,7 @@ static void P_RelinkPointers(void)
 	for (currentthinker = thlist[THINK_MOBJ].next; currentthinker != &thlist[THINK_MOBJ];
 		currentthinker = currentthinker->next)
 	{
-		if (currentthinker->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (currentthinker->removing)
 			continue;
 
 		mobj = (mobj_t *)currentthinker;
@@ -5239,8 +5238,8 @@ static void P_NetArchiveSectorPortals(save_t *save_p)
 		UINT8 type = secportals[i].type;
 
 		P_WriteUINT8(save_p, type);
-		P_WriteFixed(save_p, secportals[i].origin.x);
-		P_WriteFixed(save_p, secportals[i].origin.y);
+		P_WriteUINT8(save_p, secportals[i].ceiling ? 1 : 0);
+		P_WriteUINT32(save_p, SaveSector(secportals[i].target));
 
 		switch (type)
 		{
@@ -5255,8 +5254,8 @@ static void P_NetArchiveSectorPortals(save_t *save_p)
 			P_WriteUINT32(save_p, SaveSector(secportals[i].sector));
 			break;
 		case SECPORTAL_OBJECT:
-			if (secportals[i].mobj && !P_MobjWasRemoved(secportals[i].mobj))
-				SaveMobjnum(secportals[i].mobj);
+			if (!P_MobjWasRemoved(secportals[i].mobj))
+				P_WriteUINT32(save_p, SaveMobjnum(secportals[i].mobj));
 			else
 				P_WriteUINT32(save_p, 0);
 			break;
@@ -5283,8 +5282,8 @@ static void P_NetUnArchiveSectorPortals(save_t *save_p)
 		sectorportal_t *secportal = &secportals[id];
 
 		secportal->type = P_ReadUINT8(save_p);
-		secportal->origin.x = P_ReadFixed(save_p);
-		secportal->origin.y = P_ReadFixed(save_p);
+		secportal->ceiling = (P_ReadUINT8(save_p) != 0) ? true : false;
+		secportal->target = LoadSector(P_ReadUINT32(save_p));
 
 		switch (secportal->type)
 		{
@@ -5376,7 +5375,7 @@ void P_SaveNetGame(save_t *save_p, boolean resending)
 	// Assign the mobjnumber for pointer tracking
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mobj = (mobj_t *)th;
diff --git a/src/p_setup.c b/src/p_setup.c
index 2524803ab5c62b679e2dc22a1032589fe8291853..a62e287ac19b8bf4608c8361a40b87e26d5b8a1a 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -530,7 +530,7 @@ UINT32 P_GetScoreForGradeOverall(INT16 map, UINT8 grade)
 void P_AddNiGHTSTimes(INT16 i, char *gtext)
 {
 	char *spos = gtext;
-	
+
 	for (UINT8 n = 0; n < 8; n++)
 	{
 		if (spos != NULL)
@@ -692,7 +692,7 @@ void P_ReloadRings(void)
 	// scan the thinkers to find rings/spheres/hoops to unset
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo = (mobj_t *)th;
@@ -750,7 +750,7 @@ void P_SwitchSpheresBonusMode(boolean bonustime)
 	// scan the thinkers to find spheres to switch
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo = (mobj_t *)th;
@@ -2995,7 +2995,7 @@ static void P_LoadTextmap(void)
 	side_t     *sd;
 	mapthing_t *mt;
 
-	CONS_Alert(CONS_NOTICE, "UDMF support is still a work-in-progress; its specs and features are prone to change until it is fully implemented.\n");
+	//CONS_Alert(CONS_NOTICE, "UDMF support is still a work-in-progress; its specs and features are prone to change until it is fully implemented.\n");
 
 	/// Given the UDMF specs, some fields are given a default value.
 	/// If an element's field has a default value set, it is omitted
@@ -3075,7 +3075,7 @@ static void P_LoadTextmap(void)
 			// TODO: remove this limitation in a backwards-compatible way (UDMF versioning?)
 			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);
@@ -7333,7 +7333,7 @@ void P_RespawnThings(void)
 
 	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (think->removing)
 			continue;
 		P_RemoveMobj((mobj_t *)think);
 	}
@@ -7645,20 +7645,20 @@ static void P_InitCamera(void)
 			CV_SetValue(&cv_analog[1], 0);
 
 		displayplayer = consoleplayer; // Start with your OWN view, please!
-	}
 
-	if (twodlevel)
-	{
-		CV_SetValue(&cv_analog[0], false);
-		CV_SetValue(&cv_analog[1], false);
-	}
-	else
-	{
-		if (cv_useranalog[0].value)
-			CV_SetValue(&cv_analog[0], true);
+		if (twodlevel)
+		{
+			CV_SetValue(&cv_analog[0], false);
+			CV_SetValue(&cv_analog[1], false);
+		}
+		else
+		{
+			if (cv_useranalog[0].value)
+				CV_SetValue(&cv_analog[0], true);
 
-		if ((splitscreen && cv_useranalog[1].value) || botingame)
-			CV_SetValue(&cv_analog[1], true);
+			if ((splitscreen && cv_useranalog[1].value) || botingame)
+				CV_SetValue(&cv_analog[1], true);
+		}
 	}
 }
 
@@ -7853,6 +7853,10 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
 	maptol = mapheaderinfo[gamemap-1]->typeoflevel;
 	gametyperules = gametypedefaultrules[gametype];
 
+	// clear the target on map change, since the object will be invalidated
+	P_SetTarget(&ticcmd_ztargetfocus[0], NULL);
+	P_SetTarget(&ticcmd_ztargetfocus[1], NULL);
+
 	CON_Drawer(); // let the user know what we are going to do
 	I_FinishUpdate(); // page flip or blit buffer
 
@@ -8002,6 +8006,9 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
 	// Free GPU textures before freeing patches.
 	if (rendermode == render_opengl && (vid.glstate == VID_GL_LIBRARY_LOADED))
 		HWR_ClearAllTextures();
+
+	// Delete light table textures
+	HWR_ClearLightTables();
 #endif
 
 	Patch_FreeTag(PU_PATCH_LOWPRIORITY);
diff --git a/src/p_spec.c b/src/p_spec.c
index 6f3543161d3248046bf3acd3472a74509917597e..d375d3e2f23e7c65ad990b7b66a76965894be06a 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -3642,7 +3642,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					if (mo2->type != MT_EGGTRAP)
 						continue;
 
-					if (mo2->thinker.function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+					if (mo2->thinker.removing)
 						continue;
 
 					P_KillMobj(mo2, NULL, mo, 0);
@@ -3854,7 +3854,7 @@ void P_SetupSignExit(player_t *player)
 	// spin all signposts in the level then.
 	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (think->removing)
 			continue;
 
 		thing = (mobj_t *)think;
@@ -3892,7 +3892,7 @@ boolean P_IsFlagAtBase(mobjtype_t flag)
 
 	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (think->removing)
 			continue;
 
 		mo = (mobj_t *)think;
@@ -4395,7 +4395,7 @@ static void P_ProcessEggCapsule(player_t *player, sector_t *sector)
 	// The chimps are my friends.. heeheeheheehehee..... - LouisJM
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 		mo2 = (mobj_t *)th;
 		if (mo2->type != MT_EGGTRAP)
@@ -6236,7 +6236,7 @@ static void P_DoPortalCopyFromLine(sector_t *dest_sector, int plane_type, int ta
 	}
 }
 
-static sectorportal_t *P_SectorGetPortalOrCreate(sector_t *sector, UINT32 *num, UINT32 *result)
+static sectorportal_t *P_SectorGetPortalOrCreate(sector_t *sector, UINT32 *num, UINT32 *result, boolean ceiling)
 {
 	sectorportal_t *secportal = NULL;
 
@@ -6244,8 +6244,8 @@ static sectorportal_t *P_SectorGetPortalOrCreate(sector_t *sector, UINT32 *num,
 	{
 		*num = P_NewSectorPortal();
 		secportal = &secportals[*num];
-		secportal->origin.x = sector->soundorg.x;
-		secportal->origin.y = sector->soundorg.y;
+		secportal->target = sector;
+		secportal->ceiling = ceiling;
 		*result = *num;
 	}
 	else
@@ -6259,12 +6259,12 @@ static sectorportal_t *P_SectorGetPortalOrCreate(sector_t *sector, UINT32 *num,
 
 static sectorportal_t *P_SectorGetFloorPortalOrCreate(sector_t *sector, UINT32 *result)
 {
-	return P_SectorGetPortalOrCreate(sector, &sector->portal_floor, result);
+	return P_SectorGetPortalOrCreate(sector, &sector->portal_floor, result, false);
 }
 
 static sectorportal_t *P_SectorGetCeilingPortalOrCreate(sector_t *sector, UINT32 *result)
 {
-	return P_SectorGetPortalOrCreate(sector, &sector->portal_ceiling, result);
+	return P_SectorGetPortalOrCreate(sector, &sector->portal_ceiling, result, true);
 }
 
 static void P_CopySectorPortalToLines(UINT32 portal_num, int sector_tag)
diff --git a/src/p_tick.c b/src/p_tick.c
index 68de091382d6a2ab76619e9bd4ccda36a6d693f5..db8688484ac3dbcf56fafc293af6bc995190e0f9 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -162,7 +162,7 @@ void Command_CountMobjs_f(void)
 
 			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+				if (th->removing)
 					continue;
 
 				if (((mobj_t *)th)->type == i)
@@ -182,7 +182,7 @@ void Command_CountMobjs_f(void)
 
 		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			if (th->removing)
 				continue;
 
 			if (((mobj_t *)th)->type == i)
@@ -348,6 +348,7 @@ void P_RemoveThinkerDelayed(thinker_t *thinker)
 void P_RemoveThinker(thinker_t *thinker)
 {
 	LUA_InvalidateUserdata(thinker);
+	thinker->removing = true;
 	thinker->function.acp1 = (actionf_p1)P_RemoveThinkerDelayed;
 }
 
diff --git a/src/p_user.c b/src/p_user.c
index a2bae228ba54d115ec94c9753a25fe30d54edc4a..3301987a2ac6ed576b762ccfc28a2adecd88e255 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -435,7 +435,7 @@ UINT8 P_FindLowestMare(void)
 	// to find the egg capsule with the lowest mare
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -488,7 +488,7 @@ boolean P_TransferToNextMare(player_t *player)
 	// to find the closest axis point
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -539,7 +539,7 @@ static mobj_t *P_FindAxis(INT32 mare, INT32 axisnum)
 	// to find the closest axis point
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -574,7 +574,7 @@ static mobj_t *P_FindAxisTransfer(INT32 mare, INT32 axisnum, mobjtype_t type)
 	// to find the closest axis point
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -615,7 +615,7 @@ void P_TransferToAxis(player_t *player, INT32 axisnum)
 	// to find the closest axis point
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -716,7 +716,7 @@ static void P_DeNightserizePlayer(player_t *player)
 	// Check to see if the player should be killed.
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -1899,7 +1899,7 @@ void P_SpawnShieldOrb(player_t *player)
 	// blaze through the thinkers to see if an orb already exists!
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		shieldobj = (mobj_t *)th;
@@ -5112,7 +5112,7 @@ void P_Telekinesis(player_t *player, fixed_t thrust, fixed_t range)
 
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -6479,7 +6479,7 @@ static void P_NightsTransferPoints(player_t *player, fixed_t xspeed, fixed_t rad
 		// Find next waypoint
 		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			if (th->removing)
 				continue;
 
 			mo2 = (mobj_t *)th;
@@ -6515,7 +6515,7 @@ static void P_NightsTransferPoints(player_t *player, fixed_t xspeed, fixed_t rad
 		{
 			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+				if (th->removing)
 					continue;
 
 				mo2 = (mobj_t *)th;
@@ -6544,7 +6544,7 @@ static void P_NightsTransferPoints(player_t *player, fixed_t xspeed, fixed_t rad
 		{
 			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+				if (th->removing)
 					continue;
 
 				mo2 = (mobj_t *)th;
@@ -7276,7 +7276,7 @@ static void P_NiGHTSMovement(player_t *player)
 		// to find the closest axis point
 		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			if (th->removing)
 				continue;
 
 			mo2 = (mobj_t *)th;
@@ -8176,7 +8176,7 @@ void P_MovePlayer(player_t *player)
 
 			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 			{
-				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+				if (th->removing)
 					continue;
 
 				mo2 = (mobj_t *)th;
@@ -9161,7 +9161,7 @@ void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius)
 
 	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (think->removing)
 			continue;
 
 		mo = (mobj_t *)think;
@@ -9259,7 +9259,7 @@ mobj_t *P_LookForFocusTarget(player_t *player, mobj_t *exclude, SINT8 direction,
 
 	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (think->removing)
 			continue;
 
 		mo = (mobj_t *)think;
@@ -9378,7 +9378,7 @@ mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet)
 
 	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
 	{
-		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (think->removing)
 			continue;
 
 		mo = (mobj_t *)think;
@@ -9524,7 +9524,7 @@ void P_FindEmerald(void)
 	// to find all emeralds
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo2 = (mobj_t *)th;
@@ -10925,7 +10925,7 @@ static mobj_t *P_GetAxis(INT32 num)
 
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mobj = (mobj_t *)th;
@@ -12007,7 +12007,7 @@ void P_PlayerThink(player_t *player)
 
 		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 		{
-			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			if (th->removing)
 				continue;
 
 			mo2 = (mobj_t *)th;
diff --git a/src/r_data.c b/src/r_data.c
index e7200ecbd600c85cc6b3ca89c5c84d511b8aface..32faa07a8fb40370008218f2c4fa9ec3a4d3737b 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -426,9 +426,6 @@ void R_ClearColormaps(void)
 {
 	// Purged by PU_LEVEL, just overwrite the pointer
 	extra_colormaps = R_CreateDefaultColormap(true);
-#ifdef HWRENDER
-	HWR_ClearLightTables();
-#endif
 }
 
 //
@@ -849,6 +846,15 @@ void R_GenerateLightTable(extracolormap_t *extra_colormap, boolean uselookup)
 	}
 }
 
+void R_UpdateLightTable(extracolormap_t *extra_colormap, boolean uselookup)
+{
+	R_GenerateLightTable(extra_colormap, uselookup);
+
+#ifdef HWRENDER
+	extra_colormap->gl_lighttable.needs_update = true;
+#endif
+}
+
 extracolormap_t *R_CreateColormapFromLinedef(char *p1, char *p2, char *p3)
 {
 	// default values
diff --git a/src/r_data.h b/src/r_data.h
index 7c6ee19d50c4b8b9b081691541b4d35fff5388c4..64abf6d83637ed8f54c7449f621ac52eb6aa9113 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -90,6 +90,7 @@ typedef enum
 } textmapcolormapflags_t;
 
 void R_GenerateLightTable(extracolormap_t *extra_colormap, boolean uselookup);
+void R_UpdateLightTable(extracolormap_t *extra_colormap, boolean uselookup);
 lighttable_t *R_CreateLightTable(extracolormap_t *extra_colormap);
 extracolormap_t * R_CreateColormapFromLinedef(char *p1, char *p2, char *p3);
 extracolormap_t* R_CreateColormap(INT32 rgba, INT32 fadergba, UINT8 fadestart, UINT8 fadeend, UINT8 flags);
diff --git a/src/r_defs.h b/src/r_defs.h
index 51fac21fae4ed2e1c83929d6a121d09286e2a271..eac3e2d1f38752f9b3b2002f1a7ab3e3fe15975e 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -76,8 +76,11 @@ typedef struct extracolormap_s
 	lighttable_t *colormap;
 
 #ifdef HWRENDER
-	// The id of the hardware lighttable. Zero means it does not exist yet.
-	UINT32 gl_lighttable_id;
+	struct {
+		UINT32 id; // The id of the hardware lighttable. Zero means it does not exist yet.
+		RGBA_t *data; // The texture data of the hardware lighttable.
+		boolean needs_update; // If the colormap changed recently or not.
+	} gl_lighttable;
 #endif
 
 #ifdef EXTRACOLORMAPLUMPS
@@ -242,9 +245,8 @@ typedef struct sectorportal_s
 		struct sector_s *sector;
 		struct mobj_s *mobj;
 	};
-	struct {
-		fixed_t x, y;
-	} origin;
+	struct sector_s *target;
+	boolean ceiling;
 } sectorportal_t;
 
 typedef struct ffloor_s
diff --git a/src/r_draw.h b/src/r_draw.h
index 00032e44f51d713b42b89cd9c2dda5344e945abb..d5c5b4e25d36ba375d365a2d13f0ba5fab19e3f3 100644
--- a/src/r_draw.h
+++ b/src/r_draw.h
@@ -173,6 +173,7 @@ void R_DrawTiltedTranslucentSpan_8(void);
 void R_DrawSplat_8(void);
 void R_DrawTranslucentSplat_8(void);
 void R_DrawTiltedSplat_8(void);
+void R_DrawTiltedTranslucentSplat_8(void);
 
 void R_DrawFloorSprite_8(void);
 void R_DrawTranslucentFloorSprite_8(void);
@@ -194,6 +195,7 @@ void R_DrawTiltedTranslucentSpan_NPO2_8(void);
 void R_DrawSplat_NPO2_8(void);
 void R_DrawTranslucentSplat_NPO2_8(void);
 void R_DrawTiltedSplat_NPO2_8(void);
+void R_DrawTiltedTranslucentSplat_NPO2_8(void);
 
 void R_DrawFloorSprite_NPO2_8(void);
 void R_DrawTranslucentFloorSprite_NPO2_8(void);
diff --git a/src/r_draw8.c b/src/r_draw8.c
index 220878f88e4d34ac0cfd99ef7246a12aec73d4e4..c720f454e078d1f7857227eae01caa866d56d136 100644
--- a/src/r_draw8.c
+++ b/src/r_draw8.c
@@ -1301,6 +1301,136 @@ void R_DrawTiltedSplat_8(void)
 #endif
 }
 
+void R_DrawTiltedTranslucentSplat_8(void)
+{
+	// x1, x2 = ds_x1, ds_x2
+	int width = ds_x2 - ds_x1;
+	double iz, uz, vz;
+	UINT32 u, v;
+	int i;
+
+	UINT8 *source;
+	UINT8 *colormap;
+	UINT8 *dest;
+
+	UINT8 val;
+
+	double startz, startu, startv;
+	double izstep, uzstep, vzstep;
+	double endz, endu, endv;
+	UINT32 stepu, stepv;
+
+	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);
+
+	R_CalcSlopeLight();
+
+	dest = &topleft[ds_y*vid.width + ds_x1];
+	source = ds_source;
+	//colormap = ds_colormap;
+
+#if 0	// The "perfect" reference version of this routine. Pretty slow.
+		// Use it only to see how things are supposed to look.
+	i = 0;
+	do
+	{
+		double z = 1.f/iz;
+		u = (INT64)(uz*z);
+		v = (INT64)(vz*z);
+
+		colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
+
+		val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+		if (val != TRANSPARENTPIXEL)
+			*dest = *(ds_transmap + (colormap[val] << 8) + *dest);
+
+		dest++;
+		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_sz.x * SPANSIZE;
+	uzstep = ds_su.x * SPANSIZE;
+	vzstep = ds_sv.x * SPANSIZE;
+	//x1 = 0;
+	width++;
+
+	while (width >= SPANSIZE)
+	{
+		iz += izstep;
+		uz += uzstep;
+		vz += vzstep;
+
+		endz = 1.f/iz;
+		endu = uz*endz;
+		endv = vz*endz;
+		stepu = (INT64)((endu - startu) * INVSPAN);
+		stepv = (INT64)((endv - startv) * INVSPAN);
+		u = (INT64)(startu);
+		v = (INT64)(startv);
+
+		for (i = SPANSIZE-1; i >= 0; i--)
+		{
+			colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
+			val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+			if (val != TRANSPARENTPIXEL)
+				*dest = *(ds_transmap + (colormap[val] << 8) + *dest);
+			dest++;
+			u += stepu;
+			v += stepv;
+		}
+		startu = endu;
+		startv = endv;
+		width -= SPANSIZE;
+	}
+	if (width > 0)
+	{
+		if (width == 1)
+		{
+			u = (INT64)(startu);
+			v = (INT64)(startv);
+			colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
+			val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+			if (val != TRANSPARENTPIXEL)
+				*dest = *(ds_transmap + (colormap[val] << 8) + *dest);
+		}
+		else
+		{
+			double left = width;
+			iz += ds_sz.x * left;
+			uz += ds_su.x * left;
+			vz += ds_sv.x * left;
+
+			endz = 1.f/iz;
+			endu = uz*endz;
+			endv = vz*endz;
+			left = 1.f/left;
+			stepu = (INT64)((endu - startu) * left);
+			stepv = (INT64)((endv - startv) * left);
+			u = (INT64)(startu);
+			v = (INT64)(startv);
+
+			for (; width != 0; width--)
+			{
+				colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
+				val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+				if (val != TRANSPARENTPIXEL)
+					*dest = *(ds_transmap + (colormap[val] << 8) + *dest);
+				dest++;
+				u += stepu;
+				v += stepv;
+			}
+		}
+	}
+#endif
+}
+
 /**	\brief The R_DrawSplat_8 function
 	Just like R_DrawSpan_8, but skips transparent pixels.
 */
diff --git a/src/r_draw8_npo2.c b/src/r_draw8_npo2.c
index 6213ecbc5f17af1cfa37b78c4d9d454f0ab09c9a..7fbb40d84956d342f1d675876f7dbcc9b2cd539b 100644
--- a/src/r_draw8_npo2.c
+++ b/src/r_draw8_npo2.c
@@ -666,6 +666,204 @@ void R_DrawTiltedSplat_NPO2_8(void)
 #endif
 }
 
+void R_DrawTiltedTranslucentSplat_NPO2_8(void)
+{
+	// x1, x2 = ds_x1, ds_x2
+	int width = ds_x2 - ds_x1;
+	double iz, uz, vz;
+	UINT32 u, v;
+	int i;
+
+	UINT8 *source;
+	UINT8 *colormap;
+	UINT8 *dest;
+
+	UINT8 val;
+
+	double startz, startu, startv;
+	double izstep, uzstep, vzstep;
+	double endz, endu, endv;
+	UINT32 stepu, stepv;
+
+	struct libdivide_u32_t x_divider = libdivide_u32_gen(ds_flatwidth);
+	struct libdivide_u32_t y_divider = libdivide_u32_gen(ds_flatheight);
+
+	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);
+
+	R_CalcSlopeLight();
+
+	dest = &topleft[ds_y*vid.width + ds_x1];
+	source = ds_source;
+	//colormap = ds_colormap;
+
+#if 0	// The "perfect" reference version of this routine. Pretty slow.
+		// Use it only to see how things are supposed to look.
+	i = 0;
+	do
+	{
+		double z = 1.f/iz;
+		u = (INT64)(uz*z);
+		v = (INT64)(vz*z);
+
+		colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
+
+		// Lactozilla: Non-powers-of-two
+		{
+			fixed_t x = (((fixed_t)u) >> FRACBITS);
+			fixed_t y = (((fixed_t)v) >> FRACBITS);
+
+			// Carefully align all of my Friends.
+			if (x < 0)
+				x += (libdivide_u32_do((UINT32)(-x-1), &x_divider) + 1) * ds_flatwidth;
+			else
+				x -= libdivide_u32_do((UINT32)x, &x_divider) * ds_flatwidth;
+			if (y < 0)
+				y += (libdivide_u32_do((UINT32)(-y-1), &y_divider) + 1) * ds_flatheight;
+			else
+				y -= libdivide_u32_do((UINT32)y, &y_divider) * ds_flatheight;
+
+			val = source[((y * ds_flatwidth) + x)];
+		}
+
+		if (val != TRANSPARENTPIXEL)
+			*dest = *(ds_transmap + (colormap[val] << 8) + *dest);
+
+		dest++;
+		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_sz.x * SPANSIZE;
+	uzstep = ds_su.x * SPANSIZE;
+	vzstep = ds_sv.x * SPANSIZE;
+	//x1 = 0;
+	width++;
+
+	while (width >= SPANSIZE)
+	{
+		iz += izstep;
+		uz += uzstep;
+		vz += vzstep;
+
+		endz = 1.f/iz;
+		endu = uz*endz;
+		endv = vz*endz;
+		stepu = (INT64)((endu - startu) * INVSPAN);
+		stepv = (INT64)((endv - startv) * INVSPAN);
+		u = (INT64)(startu);
+		v = (INT64)(startv);
+
+		for (i = SPANSIZE-1; i >= 0; i--)
+		{
+			colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
+			// Lactozilla: Non-powers-of-two
+			{
+				fixed_t x = (((fixed_t)u) >> FRACBITS);
+				fixed_t y = (((fixed_t)v) >> FRACBITS);
+
+				// Carefully align all of my Friends.
+				if (x < 0)
+					x += (libdivide_u32_do((UINT32)(-x-1), &x_divider) + 1) * ds_flatwidth;
+				else
+					x -= libdivide_u32_do((UINT32)x, &x_divider) * ds_flatwidth;
+				if (y < 0)
+					y += (libdivide_u32_do((UINT32)(-y-1), &y_divider) + 1) * ds_flatheight;
+				else
+					y -= libdivide_u32_do((UINT32)y, &y_divider) * ds_flatheight;
+
+				val = source[((y * ds_flatwidth) + x)];
+			}
+			if (val != TRANSPARENTPIXEL)
+				*dest = *(ds_transmap + (colormap[val] << 8) + *dest);
+			dest++;
+			u += stepu;
+			v += stepv;
+		}
+		startu = endu;
+		startv = endv;
+		width -= SPANSIZE;
+	}
+	if (width > 0)
+	{
+		if (width == 1)
+		{
+			u = (INT64)(startu);
+			v = (INT64)(startv);
+			colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
+			// Lactozilla: Non-powers-of-two
+			{
+				fixed_t x = (((fixed_t)u) >> FRACBITS);
+				fixed_t y = (((fixed_t)v) >> FRACBITS);
+
+				// Carefully align all of my Friends.
+				if (x < 0)
+					x += (libdivide_u32_do((UINT32)(-x-1), &x_divider) + 1) * ds_flatwidth;
+				else
+					x -= libdivide_u32_do((UINT32)x, &x_divider) * ds_flatwidth;
+				if (y < 0)
+					y += (libdivide_u32_do((UINT32)(-y-1), &y_divider) + 1) * ds_flatheight;
+				else
+					y -= libdivide_u32_do((UINT32)y, &y_divider) * ds_flatheight;
+
+				val = source[((y * ds_flatwidth) + x)];
+			}
+			if (val != TRANSPARENTPIXEL)
+				*dest = *(ds_transmap + (colormap[val] << 8) + *dest);
+		}
+		else
+		{
+			double left = width;
+			iz += ds_sz.x * left;
+			uz += ds_su.x * left;
+			vz += ds_sv.x * left;
+
+			endz = 1.f/iz;
+			endu = uz*endz;
+			endv = vz*endz;
+			left = 1.f/left;
+			stepu = (INT64)((endu - startu) * left);
+			stepv = (INT64)((endv - startv) * left);
+			u = (INT64)(startu);
+			v = (INT64)(startv);
+
+			for (; width != 0; width--)
+			{
+				colormap = planezlight[tiltlighting[ds_x1++]] + (ds_colormap - colormaps);
+				// Lactozilla: Non-powers-of-two
+				{
+					fixed_t x = (((fixed_t)u) >> FRACBITS);
+					fixed_t y = (((fixed_t)v) >> FRACBITS);
+
+					// Carefully align all of my Friends.
+					if (x < 0)
+						x += (libdivide_u32_do((UINT32)(-x-1), &x_divider) + 1) * ds_flatwidth;
+					else
+						x -= libdivide_u32_do((UINT32)x, &x_divider) * ds_flatwidth;
+					if (y < 0)
+						y += (libdivide_u32_do((UINT32)(-y-1), &y_divider) + 1) * ds_flatheight;
+					else
+						y -= libdivide_u32_do((UINT32)y, &y_divider) * ds_flatheight;
+
+					val = source[((y * ds_flatwidth) + x)];
+				}
+				if (val != TRANSPARENTPIXEL)
+					*dest = *(ds_transmap + (colormap[val] << 8) + *dest);
+				dest++;
+				u += stepu;
+				v += stepv;
+			}
+		}
+	}
+#endif
+}
+
 /**	\brief The R_DrawSplat_NPO2_8 function
 	Just like R_DrawSpan_NPO2_8, but skips transparent pixels.
 */
diff --git a/src/r_main.c b/src/r_main.c
index 32e3138eb07fbe1abc736c382cc66de389084b64..ee05876da1395f4375515457e552b36cffcfd486 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -1434,6 +1434,9 @@ static void R_PortalFrame(portal_t *portal)
 	viewsin = FINESINE(viewangle>>ANGLETOFINESHIFT);
 	viewcos = FINECOSINE(viewangle>>ANGLETOFINESHIFT);
 
+	if (!P_MobjWasRemoved(portal->viewmobj))
+		r_viewmobj = portal->viewmobj;
+
 	portalclipstart = portal->start;
 	portalclipend = portal->end;
 
diff --git a/src/r_plane.c b/src/r_plane.c
index 380f39eabca224a402044d7be1504a3b60356044..a0de048ea8c6fe7eac8265d1cdfd3559d9bba1d6 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -83,7 +83,7 @@ static fixed_t planeheight;
 fixed_t yslopetab[MAXVIDHEIGHT*16];
 fixed_t *yslope;
 
-static fixed_t xoffs, yoffs;
+static INT64 xoffs, yoffs;
 static dvector3_t slope_origin, slope_u, slope_v;
 static dvector3_t slope_lightu, slope_lightv;
 
@@ -376,19 +376,25 @@ visplane_t *R_FindPlane(sector_t *sector, fixed_t height, INT32 picnum, INT32 li
 	visplane_t *check;
 	unsigned hash;
 
+	float offset_xd = FixedToFloat(xoff) / FixedToFloat(xscale ? xscale : 1);
+	float offset_yd = FixedToFloat(yoff) / FixedToFloat(yscale ? yscale : 1);
+
+	INT64 offset_x = offset_xd * FRACUNIT;
+	INT64 offset_y = offset_yd * FRACUNIT;
+
 	if (!slope) // Don't mess with this right now if a slope is involved
 	{
-		xoff += FixedMul(viewx, xscale);
-		yoff -= FixedMul(viewy, yscale);
+		offset_x += viewx;
+		offset_y -= viewy;
 
 		if (plangle != 0)
 		{
 			// Add the view offset, rotated by the plane angle.
 			float ang = ANG2RAD(plangle);
-			float x = FixedToFloat(xoff);
-			float y = FixedToFloat(yoff);
-			xoff = FloatToFixed(x * cos(ang) + y * sin(ang));
-			yoff = FloatToFixed(-x * sin(ang) + y * cos(ang));
+			float x = offset_x / (float)FRACUNIT;
+			float y = offset_y / (float)FRACUNIT;
+			offset_x = (x * cos(ang) + y * sin(ang)) * FRACUNIT;
+			offset_y = (-x * sin(ang) + y * cos(ang)) * FRACUNIT;
 		}
 	}
 
@@ -399,16 +405,19 @@ visplane_t *R_FindPlane(sector_t *sector, fixed_t height, INT32 picnum, INT32 li
 			float ang = ANG2RAD(polyobj->angle);
 			float x = FixedToFloat(polyobj->centerPt.x);
 			float y = FixedToFloat(polyobj->centerPt.y);
-			xoff -= FloatToFixed(x * cos(ang) + y * sin(ang));
-			yoff -= FloatToFixed(x * sin(ang) - y * cos(ang));
+			offset_x -= (x * cos(ang) + y * sin(ang)) * FRACUNIT;
+			offset_y -= (x * sin(ang) - y * cos(ang)) * FRACUNIT;
 		}
 		else
 		{
-			xoff -= polyobj->centerPt.x;
-			yoff += polyobj->centerPt.y;
+			offset_x -= polyobj->centerPt.x;
+			offset_y += polyobj->centerPt.y;
 		}
 	}
 
+	offset_x = ((INT64)offset_x * xscale) / FRACUNIT;
+	offset_y = ((INT64)offset_y * yscale) / FRACUNIT;
+
 	// This appears to fix the Nimbus Ruins sky bug.
 	if (picnum == skyflatnum && pfloor)
 	{
@@ -423,7 +432,7 @@ visplane_t *R_FindPlane(sector_t *sector, fixed_t height, INT32 picnum, INT32 li
 		{
 			if (height == check->height && picnum == check->picnum
 				&& lightlevel == check->lightlevel
-				&& xoff == check->xoffs && yoff == check->yoffs
+				&& offset_x == check->xoffs && offset_y == check->yoffs
 				&& xscale == check->xscale && yscale == check->yscale
 				&& planecolormap == check->extra_colormap
 				&& check->viewx == viewx && check->viewy == viewy && check->viewz == viewz
@@ -449,8 +458,8 @@ visplane_t *R_FindPlane(sector_t *sector, fixed_t height, INT32 picnum, INT32 li
 	check->lightlevel = lightlevel;
 	check->minx = vid.width;
 	check->maxx = -1;
-	check->xoffs = xoff;
-	check->yoffs = yoff;
+	check->xoffs = offset_x;
+	check->yoffs = offset_y;
 	check->xscale = xscale;
 	check->yscale = yscale;
 	check->extra_colormap = planecolormap;
@@ -658,13 +667,13 @@ static void R_DrawSkyPlane(visplane_t *pl)
 }
 
 // Returns the height of the sloped plane at (x, y) as a double
-static double R_GetSlopeZAt(const pslope_t *slope, fixed_t x, fixed_t y)
+static double R_GetSlopeZAt(const pslope_t *slope, INT64 x, INT64 y)
 {
 	// If you want to reimplement this using just the equation constants, use this instead:
 	// (d + a*x + b*y) * -(1.0 / c)
 
-	double px = FixedToDouble(x) - slope->dorigin.x;
-	double py = FixedToDouble(y) - slope->dorigin.y;
+	double px = (x / (double)FRACUNIT) - slope->dorigin.x;
+	double py = (y / (double)FRACUNIT) - slope->dorigin.y;
 
 	double dist = (px * slope->dnormdir.x) + (py * slope->dnormdir.y);
 
@@ -672,10 +681,10 @@ static double 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)
+static void R_SetSlopePlaneOrigin(pslope_t *slope, fixed_t xpos, fixed_t ypos, fixed_t zpos, INT64 xoff, INT64 yoff, fixed_t angle)
 {
-	INT64 vx = (INT64)xpos + (INT64)xoff;
-	INT64 vy = (INT64)ypos - (INT64)yoff;
+	INT64 vx = (INT64)xpos + xoff;
+	INT64 vy = (INT64)ypos - yoff;
 
 	float vxf = vx / (float)FRACUNIT;
 	float vyf = vy / (float)FRACUNIT;
@@ -702,7 +711,7 @@ void R_SetSlopePlane(pslope_t *slope, fixed_t xpos, fixed_t ypos, fixed_t zpos,
 		slope->moved = false;
 	}
 
-	R_SetSlopePlaneOrigin(slope, xpos, ypos, zpos, xoff, yoff, angle);
+	R_SetSlopePlaneOrigin(slope, xpos, ypos, zpos, (INT64)xoff, (INT64)yoff, angle);
 	height = R_GetSlopeZAt(slope, xpos, ypos);
 	zeroheight = height - FixedToDouble(zpos);
 
@@ -735,7 +744,7 @@ void R_SetSlopePlane(pslope_t *slope, fixed_t xpos, fixed_t ypos, fixed_t zpos,
 }
 
 // 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)
+void R_SetScaledSlopePlane(pslope_t *slope, fixed_t xpos, fixed_t ypos, fixed_t zpos, fixed_t xs, fixed_t ys, INT64 xoff, INT64 yoff, angle_t angle, angle_t plangle)
 {
 	double height, z_at_xy;
 	float ang;
@@ -836,9 +845,11 @@ static void CalcSlopePlaneVectors(visplane_t *pl, fixed_t xoff, fixed_t yoff)
 {
 	if (!ds_fog && (pl->xscale != FRACUNIT || pl->yscale != FRACUNIT))
 	{
+		float offset_x = FixedToFloat(xoff) / FixedToFloat(pl->xscale ? pl->xscale : 1);
+		float offset_y = FixedToFloat(yoff) / FixedToFloat(pl->yscale ? pl->yscale : 1);
 		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);
+			(INT64)(offset_x * FRACUNIT), (INT64)(offset_y * FRACUNIT), pl->viewangle, pl->plangle);
 	}
 	else
 		R_SetSlopePlane(pl->slope, pl->viewx, pl->viewy, pl->viewz, xoff, yoff, pl->viewangle, pl->plangle);
@@ -1079,6 +1090,9 @@ void R_DrawSinglePlane(visplane_t *pl)
 			case SPANDRAWFUNC_SPLAT:
 				spanfunctype = SPANDRAWFUNC_TILTEDSPLAT;
 				break;
+			case SPANDRAWFUNC_TRANSSPLAT:
+				spanfunctype = SPANDRAWFUNC_TILTEDTRANSSPLAT;
+				break;
 			case SPANDRAWFUNC_SOLID:
 				spanfunctype = SPANDRAWFUNC_TILTEDSOLID;
 				break;
diff --git a/src/r_plane.h b/src/r_plane.h
index 69620f25e07cadefee8242f23ad3a26a57ffbc9d..cd947750119b8446e0683bd4d62ae7b88fa9c1a7 100644
--- a/src/r_plane.h
+++ b/src/r_plane.h
@@ -48,7 +48,7 @@ typedef struct visplane_s
 	UINT16 padbottomstart, bottom[MAXVIDWIDTH], padbottomend;
 	INT32 high, low; // R_PlaneBounds should set these.
 
-	fixed_t xoffs, yoffs; // Scrolling flats.
+	INT64 xoffs, yoffs; // Scrolling flats.
 	fixed_t xscale, yscale;
 
 	sector_t *sector;
@@ -85,7 +85,7 @@ 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_SetScaledSlopePlane(pslope_t *slope, fixed_t xpos, fixed_t ypos, fixed_t zpos, fixed_t xs, fixed_t ys, INT64 xoff, INT64 yoff, angle_t angle, angle_t plangle);
 
 typedef struct planemgr_s
 {
diff --git a/src/r_portal.c b/src/r_portal.c
index 4d042cae38574bd54165d5b5850e8e18c5f31f33..957d574b2e9ebc3505a459adfee51c08270066ac 100644
--- a/src/r_portal.c
+++ b/src/r_portal.c
@@ -101,7 +101,7 @@ void Portal_ClipApply (const portal_t* portal)
 
 static portal_t* Portal_Add (const INT16 x1, const INT16 x2)
 {
-	portal_t *portal		= Z_Malloc(sizeof(portal_t), PU_LEVEL, NULL);
+	portal_t *portal		= Z_Calloc(sizeof(portal_t), PU_LEVEL, NULL);
 	INT16 *ceilingclipsave	= Z_Malloc(sizeof(INT16)*(x2-x1 + 1), PU_LEVEL, NULL);
 	INT16 *floorclipsave	= Z_Malloc(sizeof(INT16)*(x2-x1 + 1), PU_LEVEL, NULL);
 	fixed_t *frontscalesave	= Z_Malloc(sizeof(fixed_t)*(x2-x1 + 1), PU_LEVEL, NULL);
@@ -117,7 +117,7 @@ static portal_t* Portal_Add (const INT16 x1, const INT16 x2)
 		portal_cap->next = portal;
 		portal_cap = portal;
 	}
-	portal->next = NULL;
+	portal->clipline = -1;
 
 	// Store clipping values so they can be restored once the portal is rendered.
 	portal->ceilingclip	= ceilingclipsave;
@@ -142,11 +142,9 @@ void Portal_Remove (portal_t* portal)
 	Z_Free(portal);
 }
 
-static void Portal_GetViewpointForLine(portal_t *portal, line_t *start, line_t *dest)
+static void Portal_GetViewpointForLine(portal_t *portal, line_t *start, line_t *dest, angle_t dangle)
 {
 	// Offset the portal view by the linedef centers
-	angle_t dangle = R_PointToAngle2(0,0,dest->dx,dest->dy) - R_PointToAngle2(start->dx,start->dy,0,0);
-
 	fixed_t disttopoint;
 	angle_t angtopoint;
 
@@ -168,7 +166,6 @@ static void Portal_GetViewpointForLine(portal_t *portal, line_t *start, line_t *
 
 	portal->viewx = dest_c.x + FixedMul(FINECOSINE(angtopoint>>ANGLETOFINESHIFT), disttopoint);
 	portal->viewy = dest_c.y + FixedMul(FINESINE(angtopoint>>ANGLETOFINESHIFT), disttopoint);
-	portal->viewz = viewz + dest->frontsector->floorheight - start->frontsector->floorheight;
 	portal->viewangle = viewangle + dangle;
 }
 
@@ -189,12 +186,13 @@ void Portal_Add2Lines (const INT32 line1, const INT32 line2, const INT32 x1, con
 	line_t* start	= &lines[line1];
 	line_t* dest	= &lines[line2];
 
-	Portal_GetViewpointForLine(portal, start, dest);
+	angle_t dangle = R_PointToAngle2(0,0,dest->dx,dest->dy) - R_PointToAngle2(start->dx,start->dy,0,0);
+
+	Portal_GetViewpointForLine(portal, start, dest, dangle);
+
+	portal->viewz = viewz + dest->frontsector->floorheight - start->frontsector->floorheight;
 
 	portal->clipline = line2;
-	portal->is_skybox = false;
-	portal->is_horizon = false;
-	portal->horizon_sector = NULL;
 
 	Portal_ClipRange(portal);
 
@@ -317,10 +315,7 @@ static boolean Portal_AddSkybox (const visplane_t* plane)
 
 	Portal_ClipVisplane(plane, portal);
 
-	portal->clipline = -1;
 	portal->is_skybox = true;
-	portal->is_horizon = false;
-	portal->horizon_sector = NULL;
 
 	Portal_GetViewpointForSkybox(portal);
 
@@ -332,14 +327,28 @@ static void Portal_GetViewpointForSecPortal(portal_t *portal, sectorportal_t *se
 	fixed_t x, y, z;
 	angle_t angle;
 
+	sector_t *target = secportal->target;
+
+	fixed_t target_x = target->soundorg.x;
+	fixed_t target_y = target->soundorg.y;
+	fixed_t target_z;
+
+	if (secportal->ceiling)
+		target_z = P_GetSectorCeilingZAt(target, target_x, target_y);
+	else
+		target_z = P_GetSectorFloorZAt(target, target_x, target_y);
+
 	switch (secportal->type)
 	{
 	case SECPORTAL_LINE:
-		Portal_GetViewpointForLine(portal, secportal->line.start, secportal->line.dest);
+		angle = secportal->line.dest->angle - secportal->line.start->angle;
+		Portal_GetViewpointForLine(portal, secportal->line.start, secportal->line.dest, angle);
+		portal->viewz = viewz; // Apparently it just works like that. Not going to question it.
 		return;
 	case SECPORTAL_OBJECT:
-		if (!secportal->mobj || P_MobjWasRemoved(secportal->mobj))
+		if (P_MobjWasRemoved(secportal->mobj))
 			return;
+		portal->viewmobj = secportal->mobj;
 		x = secportal->mobj->x;
 		y = secportal->mobj->y;
 		z = secportal->mobj->z;
@@ -373,8 +382,9 @@ static void Portal_GetViewpointForSecPortal(portal_t *portal, sectorportal_t *se
 		return;
 	}
 
-	fixed_t refx = secportal->origin.x - viewx;
-	fixed_t refy = secportal->origin.y - viewy;
+	fixed_t refx = target_x - viewx;
+	fixed_t refy = target_y - viewy;
+	fixed_t refz = target_z - viewz;
 
 	// Rotate the X/Y to match the target angle
 	if (angle != 0)
@@ -387,7 +397,7 @@ static void Portal_GetViewpointForSecPortal(portal_t *portal, sectorportal_t *se
 
 	portal->viewx = x - refx;
 	portal->viewy = y - refy;
-	portal->viewz = z + viewz;
+	portal->viewz = z - refz;
 	portal->viewangle = angle + viewangle;
 }
 
@@ -413,11 +423,6 @@ static boolean Portal_AddSectorPortal (const visplane_t* plane)
 
 	Portal_ClipVisplane(plane, portal);
 
-	portal->clipline = -1;
-	portal->is_horizon = false;
-	portal->is_skybox = false;
-	portal->horizon_sector = NULL;
-
 	Portal_GetViewpointForSecPortal(portal, secportal);
 
 	return true;
@@ -425,7 +430,7 @@ static boolean Portal_AddSectorPortal (const visplane_t* plane)
 
 /** Creates a transferred sector portal.
  */
-void Portal_AddTransferred (UINT32 secportalnum, const INT32 x1, const INT32 x2)
+void Portal_AddTransferred (const UINT32 secportalnum, const INT32 x1, const INT32 x2)
 {
 	if (secportalnum >= secportalcount)
 		return;
@@ -435,9 +440,6 @@ void Portal_AddTransferred (UINT32 secportalnum, const INT32 x1, const INT32 x2)
 		return;
 
 	portal_t* portal = Portal_Add(x1, x2);
-	portal->is_skybox = false;
-	portal->is_horizon = false;
-	portal->horizon_sector = NULL;
 
 	if (secportal->type == SECPORTAL_SKYBOX)
 		Portal_GetViewpointForSkybox(portal);
diff --git a/src/r_portal.h b/src/r_portal.h
index 2485e45a71afb5bc189525f140527c5bb03e2a21..5190885b7189c38b1b7b1b7b13f0519f1d455b0c 100644
--- a/src/r_portal.h
+++ b/src/r_portal.h
@@ -36,6 +36,8 @@ typedef struct portal_s
 
 	boolean is_skybox;
 
+	mobj_t *viewmobj;
+
 	UINT8 pass;			/**< Keeps track of the portal's recursion depth. */
 	INT32 clipline;		/**< Optional clipline for line-based portals. */
 
@@ -58,7 +60,7 @@ extern INT32 portalclipstart, portalclipend;
 void Portal_InitList		(void);
 void Portal_Remove			(portal_t* portal);
 void Portal_Add2Lines		(const INT32 line1, const INT32 line2, const INT32 x1, const INT32 x2);
-void Portal_AddTransferred	(UINT32 secportalnum, const INT32 x1, const INT32 x2);
+void Portal_AddTransferred	(const UINT32 secportalnum, const INT32 x1, const INT32 x2);
 
 void Portal_ClipRange (portal_t* portal);
 void Portal_ClipApply (const portal_t* portal);
diff --git a/src/r_splats.c b/src/r_splats.c
index 813c4b2430b53941c449450c9b5926e2e922ec80..ce35a35b4c3ff24d1045a170a67d421310f837af 100644
--- a/src/r_splats.c
+++ b/src/r_splats.c
@@ -381,7 +381,7 @@ static void R_RasterizeFloorSplat(floorsplat_t *pSplat, vector2_t *verts, visspr
 
 	if (pSplat->slope)
 	{
-		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_SetScaledSlopePlane(pSplat->slope, vis->viewpoint.x, vis->viewpoint.y, vis->viewpoint.z, (INT64)pSplat->xscale, (INT64)pSplat->yscale, -pSplat->verts[0].x, pSplat->verts[0].y, vis->viewpoint.angle, pSplat->angle);
 	}
 	else if (!ds_solidcolor)
 	{
diff --git a/src/screen.c b/src/screen.c
index a2c1f04daa32ee977119f2729e57b87e8d6a2811..781d22335adde3d90a349f8a064e5ab4059d54bc 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -124,6 +124,7 @@ void SCR_SetDrawFuncs(void)
 		spanfuncs[SPANDRAWFUNC_SPLAT] = R_DrawSplat_8;
 		spanfuncs[SPANDRAWFUNC_TRANSSPLAT] = R_DrawTranslucentSplat_8;
 		spanfuncs[SPANDRAWFUNC_TILTEDSPLAT] = R_DrawTiltedSplat_8;
+		spanfuncs[SPANDRAWFUNC_TILTEDTRANSSPLAT] = R_DrawTiltedTranslucentSplat_8;
 		spanfuncs[SPANDRAWFUNC_SPRITE] = R_DrawFloorSprite_8;
 		spanfuncs[SPANDRAWFUNC_TRANSSPRITE] = R_DrawTranslucentFloorSprite_8;
 		spanfuncs[SPANDRAWFUNC_TILTEDSPRITE] = R_DrawTiltedFloorSprite_8;
@@ -146,6 +147,7 @@ void SCR_SetDrawFuncs(void)
 		spanfuncs_npo2[SPANDRAWFUNC_SPLAT] = R_DrawSplat_NPO2_8;
 		spanfuncs_npo2[SPANDRAWFUNC_TRANSSPLAT] = R_DrawTranslucentSplat_NPO2_8;
 		spanfuncs_npo2[SPANDRAWFUNC_TILTEDSPLAT] = R_DrawTiltedSplat_NPO2_8;
+		spanfuncs_npo2[SPANDRAWFUNC_TILTEDTRANSSPLAT] = R_DrawTiltedTranslucentSplat_NPO2_8;
 		spanfuncs_npo2[SPANDRAWFUNC_SPRITE] = R_DrawFloorSprite_NPO2_8;
 		spanfuncs_npo2[SPANDRAWFUNC_TRANSSPRITE] = R_DrawTranslucentFloorSprite_NPO2_8;
 		spanfuncs_npo2[SPANDRAWFUNC_TILTEDSPRITE] = R_DrawTiltedFloorSprite_NPO2_8;
diff --git a/src/screen.h b/src/screen.h
index 6d45560c4582164e84c881004cbda002a7177d47..3ce593a52e8595e7412eb9c20c29c91ba9c057bb 100644
--- a/src/screen.h
+++ b/src/screen.h
@@ -115,6 +115,7 @@ enum
 	SPANDRAWFUNC_SPLAT,
 	SPANDRAWFUNC_TRANSSPLAT,
 	SPANDRAWFUNC_TILTEDSPLAT,
+	SPANDRAWFUNC_TILTEDTRANSSPLAT,
 
 	SPANDRAWFUNC_SPRITE,
 	SPANDRAWFUNC_TRANSSPRITE,
diff --git a/src/sdl/hwsym_sdl.c b/src/sdl/hwsym_sdl.c
index ca87fcc7951758e9a5de7ca1a405f88b1f202204..f561266017dd9fcf20b467b3c5e9323be7f5dbe2 100644
--- a/src/sdl/hwsym_sdl.c
+++ b/src/sdl/hwsym_sdl.c
@@ -112,6 +112,7 @@ void *hwSym(const char *funcName,void *handle)
 
 	GETFUNC(SetPaletteLookup);
 	GETFUNC(CreateLightTable);
+	GETFUNC(UpdateLightTable);
 	GETFUNC(ClearLightTables);
 	GETFUNC(SetScreenPalette);
 
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index 9fe50a6a2347583f0fe705ff5c6f6a0466ea51a9..7c2e6b69d32208f15959738b07b855421c7d43ba 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -477,10 +477,9 @@ typedef struct
 
 feild_t tty_con;
 
-// when printing general stuff to stdout stderr (Sys_Printf)
-//   we need to disable the tty console stuff
-// this increments so we can recursively disable
-static INT32 ttycon_hide = 0;
+// lock to prevent clearing partial lines, since not everything
+// printed ends on a newline.
+static boolean ttycon_ateol = true;
 // some key codes that the terminal may be using
 // TTimo NOTE: I'm not sure how relevant this is
 static INT32 tty_erase;
@@ -508,63 +507,31 @@ static inline void tty_FlushIn(void)
 // TTimo NOTE: it seems on some terminals just sending '\b' is not enough
 //   so for now, in any case we send "\b \b" .. yeah well ..
 //   (there may be a way to find out if '\b' alone would work though)
+// Hanicef NOTE: using \b this way is unreliable because of terminal state,
+//   it's better to use \r to reset the cursor to the beginning of the
+//   line and clear from there.
 static void tty_Back(void)
 {
-	char key;
-	ssize_t d;
-	key = '\b';
-	d = write(STDOUT_FILENO, &key, 1);
-	key = ' ';
-	d = write(STDOUT_FILENO, &key, 1);
-	key = '\b';
-	d = write(STDOUT_FILENO, &key, 1);
-	(void)d;
-}
-
-static void tty_Clear(void)
-{
-	size_t i;
+	write(STDOUT_FILENO, "\r", 1);
 	if (tty_con.cursor>0)
 	{
-		for (i=0; i<tty_con.cursor; i++)
-		{
-			tty_Back();
-		}
-	}
-
-}
-
-// clear the display of the line currently edited
-// bring cursor back to beginning of line
-static inline void tty_Hide(void)
-{
-	//I_Assert(consolevent);
-	if (ttycon_hide)
-	{
-		ttycon_hide++;
-		return;
+		write(STDOUT_FILENO, tty_con.buffer, tty_con.cursor);
 	}
-	tty_Clear();
-	ttycon_hide++;
+	write(STDOUT_FILENO, " \b", 2);
 }
 
-// show the current line
-// FIXME TTimo need to position the cursor if needed??
-static inline void tty_Show(void)
+static void tty_Clear(void)
 {
 	size_t i;
-	ssize_t d;
-	//I_Assert(consolevent);
-	I_Assert(ttycon_hide>0);
-	ttycon_hide--;
-	if (ttycon_hide == 0 && tty_con.cursor)
+	write(STDOUT_FILENO, "\r", 1);
+	if (tty_con.cursor>0)
 	{
 		for (i=0; i<tty_con.cursor; i++)
 		{
-			d = write(STDOUT_FILENO, tty_con.buffer+i, 1);
+			write(STDOUT_FILENO, " ", 1);
 		}
+		write(STDOUT_FILENO, "\r", 1);
 	}
-	(void)d;
 }
 
 // never exit without calling this, or your terminal will be left in a pretty bad state
@@ -672,6 +639,11 @@ void I_GetConsoleEvents(void)
 				tty_con.cursor = 0;
 				ev.key = KEY_ENTER;
 			}
+			else if (key == 0x4) // ^D, aka EOF
+			{
+				// shut down, most unix programs behave this way
+				I_Quit();
+			}
 			else continue;
 		}
 		else if (tty_con.cursor < sizeof (tty_con.buffer))
@@ -879,9 +851,16 @@ static void I_RegisterChildSignals(void)
 void I_OutputMsg(const char *fmt, ...)
 {
 	size_t len;
-	char txt[8192];
+	char *txt;
 	va_list  argptr;
 
+	va_start(argptr,fmt);
+	len = vsnprintf(NULL, 0, fmt, argptr);
+	va_end(argptr);
+	if (len == 0)
+		return;
+
+	txt = malloc(len+1);
 	va_start(argptr,fmt);
 	vsprintf(txt, fmt, argptr);
 	va_end(argptr);
@@ -915,7 +894,10 @@ void I_OutputMsg(const char *fmt, ...)
 		DWORD bytesWritten;
 
 		if (co == INVALID_HANDLE_VALUE)
+		{
+			free(txt);
 			return;
+		}
 
 		if (GetFileType(co) == FILE_TYPE_CHAR && GetConsoleMode(co, &bytesWritten))
 		{
@@ -931,11 +913,16 @@ void I_OutputMsg(const char *fmt, ...)
 			if (oldLength > 0)
 			{
 				LPVOID blank = malloc(oldLength);
-				if (!blank) return;
+				if (!blank)
+				{
+					free(txt);
+					return;
+				}
 				memset(blank, ' ', oldLength); // Blank out.
 				oldLines = malloc(oldLength*sizeof(TCHAR));
 				if (!oldLines)
 				{
+					free(txt);
 					free(blank);
 					return;
 				}
@@ -970,18 +957,20 @@ void I_OutputMsg(const char *fmt, ...)
 	}
 #else
 #ifdef HAVE_TERMIOS
-	if (consolevent)
+	if (consolevent && ttycon_ateol)
 	{
-		tty_Hide();
+		tty_Clear();
+		ttycon_ateol = false;
 	}
 #endif
 
 	if (!framebuffer)
 		fprintf(stderr, "%s", txt);
 #ifdef HAVE_TERMIOS
-	if (consolevent)
+	if (consolevent && txt[len-1] == '\n')
 	{
-		tty_Show();
+		write(STDOUT_FILENO, tty_con.buffer, tty_con.cursor);
+		ttycon_ateol = true;
 	}
 #endif
 
@@ -990,6 +979,7 @@ void I_OutputMsg(const char *fmt, ...)
 		fflush(stderr);
 
 #endif
+	free(txt);
 }
 
 //
@@ -2307,7 +2297,7 @@ void I_Sleep(UINT32 ms)
 
 void I_SleepDuration(precise_t duration)
 {
-#if defined(__linux__) || defined(__FreeBSD__)
+#if defined(__linux__) || defined(__FreeBSD__) || defined(__HAIKU__)
 	UINT64 precision = I_GetPrecisePrecision();
 	struct timespec ts = {
 		.tv_sec = duration / precision,
@@ -3087,8 +3077,10 @@ const char *I_LocateWad(void)
 	{
 		// change to the directory where we found srb2.pk3
 #if defined (_WIN32)
+		waddir = _fullpath(NULL, waddir, MAX_PATH);
 		SetCurrentDirectoryA(waddir);
 #else
+		waddir = realpath(waddir, NULL);
 		if (chdir(waddir) == -1)
 			I_OutputMsg("Couldn't change working directory\n");
 #endif
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index 10c866a1e32ff6dd3c8ac248fb50df2a4e3a30f6..82583ccb4651ff8cbfc42be4a0f40ee54bd31d7d 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -87,7 +87,7 @@
 #endif
 
 // maximum number of windowed modes (see windowedModes[][])
-#define MAXWINMODES (18)
+#define MAXWINMODES (21)
 
 /**	\brief
 */
@@ -157,7 +157,9 @@ static INT32 windowedModes[MAXWINMODES][2] =
 	{1920,1080}, // 1.66
 	{1680,1050}, // 1.60,5.25
 	{1600,1200}, // 1.33
+	{1600,1000}, // 1.60,5.00
 	{1600, 900}, // 1.66
+	{1536, 864}, // 1.66,4.80
 	{1366, 768}, // 1.66
 	{1440, 900}, // 1.60,4.50
 	{1280,1024}, // 1.33?
@@ -166,6 +168,7 @@ static INT32 windowedModes[MAXWINMODES][2] =
 	{1280, 720}, // 1.66
 	{1152, 864}, // 1.33,3.60
 	{1024, 768}, // 1.33,3.20
+	{ 960, 600}, // 1.60,3.00
 	{ 800, 600}, // 1.33,2.50
 	{ 640, 480}, // 1.33,2.00
 	{ 640, 400}, // 1.60,2.00
@@ -1901,6 +1904,7 @@ void VID_StartupOpenGL(void)
 
 		HWD.pfnSetPaletteLookup = hwSym("SetPaletteLookup",NULL);
 		HWD.pfnCreateLightTable = hwSym("CreateLightTable",NULL);
+		HWD.pfnUpdateLightTable = hwSym("UpdateLightTable",NULL);
 		HWD.pfnClearLightTables = hwSym("ClearLightTables",NULL);
 		HWD.pfnSetScreenPalette = hwSym("SetScreenPalette",NULL);
 
@@ -1945,8 +1949,6 @@ void I_ShutdownGraphics(void)
 	I_OutputMsg("shut down\n");
 
 #ifdef HWRENDER
-	if (GLUhandle)
-		hwClose(GLUhandle);
 	if (sdlglcontext)
 	{
 		SDL_GL_DeleteContext(sdlglcontext);
diff --git a/src/sdl/ogl_sdl.c b/src/sdl/ogl_sdl.c
index e7347547e224b43f2fcdf66e527c8adc2681e243..c78b43ec4097cafad8420ceb7497e2883220a67e 100644
--- a/src/sdl/ogl_sdl.c
+++ b/src/sdl/ogl_sdl.c
@@ -70,18 +70,10 @@ PFNglGetString pglGetString;
 /**	\brief SDL video display surface
 */
 INT32 oglflags = 0;
-void *GLUhandle = NULL;
 SDL_GLContext sdlglcontext = 0;
 
 void *GetGLFunc(const char *proc)
 {
-	if (strncmp(proc, "glu", 3) == 0)
-	{
-		if (GLUhandle)
-			return hwSym(proc, GLUhandle);
-		else
-			return NULL;
-	}
 	return SDL_GL_GetProcAddress(proc);
 }
 
@@ -89,7 +81,6 @@ boolean LoadGL(void)
 {
 #ifndef STATIC_OPENGL
 	const char *OGLLibname = NULL;
-	const char *GLULibname = NULL;
 
 	if (M_CheckParm("-OGLlib") && M_IsNextParm())
 		OGLLibname = M_GetNextParm();
@@ -102,43 +93,6 @@ boolean LoadGL(void)
 			CONS_Printf("If you know what is the OpenGL library's name, use -OGLlib\n");
 		return 0;
 	}
-
-#if 0
-	GLULibname = "/proc/self/exe";
-#elif defined (_WIN32)
-	GLULibname = "GLU32.DLL";
-#elif defined (__MACH__)
-	GLULibname = "/System/Library/Frameworks/OpenGL.framework/Libraries/libGLU.dylib";
-#elif defined (macintos)
-	GLULibname = "OpenGLLibrary";
-#elif defined (__unix__)
-	GLULibname = "libGLU.so.1";
-#elif defined (__HAIKU__)
-	GLULibname = "libGLU.so";
-#else
-	GLULibname = NULL;
-#endif
-
-	if (M_CheckParm("-GLUlib") && M_IsNextParm())
-		GLULibname = M_GetNextParm();
-
-	if (GLULibname)
-	{
-		GLUhandle = hwOpen(GLULibname);
-		if (GLUhandle)
-			return SetupGLfunc();
-		else
-		{
-			CONS_Alert(CONS_ERROR, "Could not load GLU Library: %s\n", GLULibname);
-			if (!M_CheckParm ("-GLUlib"))
-				CONS_Alert(CONS_ERROR, "If you know what is the GLU library's name, use -GLUlib\n");
-		}
-	}
-	else
-	{
-		CONS_Alert(CONS_ERROR, "Could not load GLU Library\n");
-		CONS_Alert(CONS_ERROR, "If you know what is the GLU library's name, use -GLUlib\n");
-	}
 #endif
 	return SetupGLfunc();
 }
@@ -155,6 +109,7 @@ boolean OglSdlSurface(INT32 w, INT32 h)
 {
 	INT32 cbpp = cv_scr_depth.value < 16 ? 16 : cv_scr_depth.value;
 	static boolean first_init = false;
+	static int majorGL = 0, minorGL = 0;
 
 	oglflags = 0;
 
@@ -189,6 +144,12 @@ boolean OglSdlSurface(INT32 w, INT32 h)
 	else
 		maximumAnisotropy = 1;
 
+	if (sscanf((const char*)gl_version, "%d.%d", &majorGL, &minorGL)
+		&& (!(majorGL == 1 && minorGL <= 3)))
+		supportMipMap = true;
+	else
+		supportMipMap = false;
+
 	SetupGLFunc4();
 
 	glanisotropicmode_cons_t[1].value = maximumAnisotropy;
diff --git a/src/sdl/ogl_sdl.h b/src/sdl/ogl_sdl.h
index bd1d699ff44db27a4599bfb81d4f65e34ac7f858..87df5e5e6115334dd8705658d70f9f6e5b6706f8 100644
--- a/src/sdl/ogl_sdl.h
+++ b/src/sdl/ogl_sdl.h
@@ -19,8 +19,6 @@
 
 #include "../v_video.h"
 
-extern void *GLUhandle;
-
 boolean OglSdlSurface(INT32 w, INT32 h);
 
 void OglSdlFinishUpdate(boolean vidwait);
diff --git a/src/st_stuff.c b/src/st_stuff.c
index a44771e4b6041dc1390c3967a7ee6497d8a6f1cb..5bb3aa98c88bdb4303d8d23bd6570f24bbb1f5e9 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -2655,7 +2655,7 @@ static boolean ST_doItemFinderIconsAndSound(void)
 	// Scan thinkers to find emblem mobj with these ids
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+		if (th->removing)
 			continue;
 
 		mo2 = (mobj_t *)th;
diff --git a/src/w_wad.c b/src/w_wad.c
index 4a1d44548b75b801343cd60a81f8c80bec7bcf17..50ba622a9b32f538b18d50dcdf29fbfd457f7355 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -65,6 +65,7 @@
 #include "i_video.h" // rendermode
 #include "md5.h"
 #include "lua_script.h"
+#include "lua_hook.h"
 #ifdef SCANTHINGS
 #include "p_setup.h" // P_ScanThings
 #endif
@@ -307,12 +308,10 @@ static void W_LoadDehackedLumps(UINT16 wadnum, boolean mainfile)
   * \param resblock resulting MD5 checksum
   * \return 0 if MD5 checksum was made, and is at resblock, 1 if error was found
   */
+
+#ifndef NOMD5
 static INT32 W_MakeFileMD5(const char *filename, void *resblock)
 {
-#ifdef NOMD5
-	(void)filename;
-	memset(resblock, 0x00, 16);
-#else
 	FILE *fhandle;
 
 	if ((fhandle = fopen(filename, "rb")) != NULL)
@@ -329,9 +328,9 @@ static INT32 W_MakeFileMD5(const char *filename, void *resblock)
 		fclose(fhandle);
 		return 0;
 	}
-#endif
 	return 1;
 }
+#endif
 
 // Invalidates the cache of lump numbers. Call this whenever a wad is added.
 static void W_InvalidateLumpnumCache(void)
@@ -1011,6 +1010,10 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
 		break;
 	}
 
+	lua_lumploading++;
+	LUA_HookVoid(HOOK(AddonLoaded));
+	lua_lumploading--;
+
 	W_InvalidateLumpnumCache();
 	return wadfile->numlumps;
 }
@@ -1171,6 +1174,11 @@ UINT16 W_InitFolder(const char *path, boolean mainfile, boolean startup)
 	W_ReadFileShaders(wadfile);
 	W_LoadTrnslateLumps(numwadfiles - 1);
 	W_LoadDehackedLumpsPK3(numwadfiles - 1, mainfile);
+
+	lua_lumploading++;
+	LUA_HookVoid(HOOK(AddonLoaded));
+	lua_lumploading--;
+
 	W_InvalidateLumpnumCache();
 
 	return wadfile->numlumps;
@@ -1350,17 +1358,6 @@ UINT16 W_CheckNumForFolderEndPK3(const char *name, UINT16 wad, UINT16 startlump)
 	return i;
 }
 
-// Returns 0 if the folder is not empty, 1 if it is empty, -1 if it doesn't exist
-INT32 W_IsFolderEmpty(const char *name, UINT16 wad)
-{
-	UINT16 start = W_CheckNumForFolderStartPK3(name, wad, 0);
-	if (start == INT16_MAX)
-		return -1;
-
-	// Unlike W_CheckNumForFolderStartPK3, W_CheckNumForFolderEndPK3 doesn't return INT16_MAX.
-	return W_CheckNumForFolderEndPK3(name, wad, start) <= start;
-}
-
 char *W_GetLumpFolderPathPK3(UINT16 wad, UINT16 lump)
 {
 	const char *fullname = wadfiles[wad]->lumpinfo[lump].fullname;
@@ -1725,10 +1722,14 @@ static UINT16 W_CheckNumForPatchNamePwad(const char *name, UINT16 wad, boolean l
 	// TODO: cache namespace lump IDs
 	if (W_FileHasFolders(wadfiles[wad]))
 	{
-		if (!W_IsFolderEmpty("Flats/", wad))
+		start = W_CheckNumForFolderStartPK3("Flats/", wad, 0);
+		end = W_CheckNumForFolderEndPK3("Flats/", wad, start);
+
+		// if the start and end is the same, the folder is empty
+		if (end <= start)
 		{
-			start = W_CheckNumForFolderStartPK3("Flats/", wad, 0);
-			end = W_CheckNumForFolderEndPK3("Flats/", wad, start);
+			start = INT16_MAX;
+			end = INT16_MAX;
 		}
 	}
 	else
diff --git a/src/w_wad.h b/src/w_wad.h
index 5872ae50a98c017961cf6964ab203d71151d8f4d..84aafa3a40e2e48f662c723d5a5854138806b5b0 100644
--- a/src/w_wad.h
+++ b/src/w_wad.h
@@ -180,7 +180,6 @@ UINT16 W_CheckNumForMarkerStartPwad(const char *name, UINT16 wad, UINT16 startlu
 UINT16 W_CheckNumForFullNamePK3(const char *name, UINT16 wad, UINT16 startlump);
 UINT16 W_CheckNumForFolderStartPK3(const char *name, UINT16 wad, UINT16 startlump);
 UINT16 W_CheckNumForFolderEndPK3(const char *name, UINT16 wad, UINT16 startlump);
-INT32 W_IsFolderEmpty(const char *name, UINT16 wad);
 char *W_GetLumpFolderPathPK3(UINT16 wad, UINT16 lump);
 char *W_GetLumpFolderNamePK3(UINT16 wad, UINT16 lump);
 
diff --git a/src/z_zone.c b/src/z_zone.c
index 5750f8ae016becd9c9548b690b0e7fd5b32ec7cb..0852fabaeac03f3751d33063cb14f63105678537 100644
--- a/src/z_zone.c
+++ b/src/z_zone.c
@@ -671,6 +671,7 @@ static void Command_Memfree_f(void)
 		CONS_Printf(M_GetText("Cached textures        : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRCACHE)>>10));
 		CONS_Printf(M_GetText("Texture colormaps      : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRPATCHCOLMIPMAP)>>10));
 		CONS_Printf(M_GetText("Model textures         : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRMODELTEXTURE)>>10));
+		CONS_Printf(M_GetText("Light table textures   : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRLIGHTTABLEDATA)>>10));
 		CONS_Printf(M_GetText("Plane polygons         : %7s KB\n"), sizeu1(Z_TagUsage(PU_HWRPLANE)>>10));
 		CONS_Printf(M_GetText("All GPU textures       : %7d KB\n"), HWR_GetTextureUsed()>>10);
 	}
diff --git a/src/z_zone.h b/src/z_zone.h
index ce7af4a159555e3a6c2be83e8d0eadcf8a7a69eb..56c540c3bb31a4a84e5f2403519455f3830583ee 100644
--- a/src/z_zone.h
+++ b/src/z_zone.h
@@ -55,6 +55,7 @@ enum
 	PU_HWRPATCHINFO          = 21, // Hardware GLPatch_t struct for OpenGL texture cache
 	PU_HWRPATCHCOLMIPMAP     = 22, // Hardware GLMipmap_t struct colormap variation of patch
 	PU_HWRMODELTEXTURE       = 23, // Hardware model texture
+	PU_HWRLIGHTTABLEDATA     = 24, // Hardware light table data
 
 	PU_HWRCACHE              = 48, // static until unlocked
 	PU_CACHE                 = 49, // static until unlocked