diff --git a/.gitlab/ci/templates/srb2ci.yml b/.gitlab/ci/templates/srb2ci.yml
index 3716d9df9..bdf8a3ed6 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/command.c b/src/command.c
index 399714bd0..8f9166361 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/deh_lua.c b/src/deh_lua.c
index e81a8c08e..e5b3b03de 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 2050a117f..63656753d 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -192,7 +192,10 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
 	INT32 i;
 
 	if (!deh_loaded)
+	{
 		initfreeslots();
+		deh_loaded = true;
+	}
 
 	deh_num_warning = 0;
 
@@ -605,14 +608,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/hardware/hw_batching.c b/src/hardware/hw_batching.c
index 7a57a7a12..5d1af10e2 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 f282ca891..b71bf7007 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;
 	}
 
-	// create hw lighttable if there isn't one
-	if (!colormap->gl_lighttable_id)
-	{
-		UINT8 *colormap_pointer;
+	UINT8 *colormap_pointer;
 
-		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);
+	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.data == NULL)
+	{
+		Z_Malloc(256 * 32 * sizeof(RGBA_t), PU_HWRLIGHTTABLEDATA, &colormap->gl_lighttable.data);
 	}
 
-	return colormap->gl_lighttable_id;
+	// 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);
+	}
+
+	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 694cc1b8c..a5fdc00a4 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 62fe8d0bd..bf9f7da3b 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 4741c6a46..da8965454 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/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 75a92c2fb..85a50edb0 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -3257,6 +3257,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/info.c b/src/info.c
index a138db8a4..715fe5d53 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_colorlib.c b/src/lua_colorlib.c
index 9e679eb40..2743635ed 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 90a11d05d..ce79cd1cb 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 ebe995ea8..6bdebf774 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/m_menu.c b/src/m_menu.c
index 2e4a42506..63a702474 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -3401,7 +3401,7 @@ boolean M_Responder(event_t *ev)
 	{
 		// 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);
@@ -12195,15 +12195,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/netcode/d_net.c b/src/netcode/d_net.c
index 031336852..4860d8688 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 64feeeda3..630999cd0 100644
--- a/src/netcode/d_netcmd.c
+++ b/src/netcode/d_netcmd.c
@@ -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/i_net.h b/src/netcode/i_net.h
index 4fd317994..a2039bd19 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 442ed9817..256d9992e 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,16 +1152,7 @@ static SINT8 SOCK_NetMakeNodewPort(const char *address, const char *port)
 		// test ip address of server
 		for (i = 0; i < mysocketses; ++i)
 		{
-#ifdef __HAIKU__
-			// Haiku doesn't appreciate empty packets, so just hope for the best.
 			if (runp->ai_addr->sa_family == myfamily[i])
-#else
-			/* 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)
-#endif
 			{
 				memcpy(&clientaddress[newnode], runp->ai_addr, runp->ai_addrlen);
 				break;
@@ -1250,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();
@@ -1278,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++;
@@ -1315,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;
@@ -1324,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/p_enemy.c b/src/p_enemy.c
index 2ae7276df..59ca95409 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -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 19ab5ead4..dc606a47e 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -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)
diff --git a/src/p_mobj.c b/src/p_mobj.c
index b377ff82f..fb9e7d78e 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -90,7 +90,7 @@ 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;
diff --git a/src/p_saveg.c b/src/p_saveg.c
index d6f8d23c5..e939cf8e3 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -5239,8 +5239,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 +5255,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 +5283,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)
 		{
diff --git a/src/p_setup.c b/src/p_setup.c
index 2524803ab..906195c18 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)
@@ -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);
@@ -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);
+		}
 	}
 }
 
@@ -8002,6 +8002,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 6f3543161..9b124f9af 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -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/r_data.c b/src/r_data.c
index e7200ecbd..32faa07a8 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 7c6ee19d5..64abf6d83 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 51fac21fa..eac3e2d1f 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 00032e44f..d5c5b4e25 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 220878f88..c720f454e 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 6213ecbc5..7fbb40d84 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 32e3138eb..ee05876da 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 380f39eab..7642a4dd6 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -1079,6 +1079,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_portal.c b/src/r_portal.c
index 4d042cae3..957d574b2 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 2485e45a7..5190885b7 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/screen.c b/src/screen.c
index a2c1f04da..781d22335 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 6d45560c4..3ce593a52 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 ca87fcc79..f56126601 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_video.c b/src/sdl/i_video.c
index 10c866a1e..36bfd380f 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -1901,6 +1901,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);
 
diff --git a/src/w_wad.c b/src/w_wad.c
index 4a1d44548..50ba622a9 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 5872ae50a..84aafa3a4 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 5750f8ae0..0852fabae 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 ce7af4a15..56c540c3b 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