diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a1f752ed7d540d72ac280aeff28dd4f9c5c0210e..fa09c83246eeb09c7ac68dbac7cece3a1892ae21 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -130,6 +130,8 @@ set(SRB2_CORE_RENDER_SOURCES
 	r_splats.c
 	r_things.c
 	r_textures.c
+	r_patch.c
+	r_patchrotation.c
 	r_picformats.c
 	r_portal.c
 
@@ -147,6 +149,8 @@ set(SRB2_CORE_RENDER_SOURCES
 	r_state.h
 	r_things.h
 	r_textures.h
+	r_patch.h
+	r_patchrotation.h
 	r_picformats.h
 	r_portal.h
 )
@@ -169,6 +173,7 @@ set(SRB2_CORE_GAME_SOURCES
 	p_telept.c
 	p_tick.c
 	p_user.c
+	taglist.c
 
 	p_local.h
 	p_maputl.h
@@ -180,6 +185,7 @@ set(SRB2_CORE_GAME_SOURCES
 	p_slopes.h
 	p_spec.h
 	p_tick.h
+	taglist.h
 )
 
 if(NOT (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
diff --git a/src/Makefile b/src/Makefile
index da918e2057146f2031fce4eec742e85d46e79c3f..fe17846753acfd1017c2f40004d9144d41f9601e 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -519,9 +519,12 @@ OBJS:=$(i_main_o) \
 		$(OBJDIR)/r_splats.o \
 		$(OBJDIR)/r_things.o \
 		$(OBJDIR)/r_textures.o \
+		$(OBJDIR)/r_patch.o \
+		$(OBJDIR)/r_patchrotation.o \
 		$(OBJDIR)/r_picformats.o \
 		$(OBJDIR)/r_portal.o \
 		$(OBJDIR)/screen.o   \
+		$(OBJDIR)/taglist.o  \
 		$(OBJDIR)/v_video.o  \
 		$(OBJDIR)/s_sound.o  \
 		$(OBJDIR)/sounds.o   \
diff --git a/src/android/i_video.c b/src/android/i_video.c
index 18f92955a7e3cba7f6b31ef8f929664a3b700770..bf0decb74118385ff2b776d8d470e5ea3a03a2ba 100644
--- a/src/android/i_video.c
+++ b/src/android/i_video.c
@@ -9,6 +9,7 @@
 #include "utils/Log.h"
 
 rendermode_t rendermode = render_soft;
+rendermode_t chosenrendermode = render_none;
 
 boolean highcolor = false;
 
@@ -52,8 +53,15 @@ INT32 VID_SetMode(INT32 modenum)
   return 0;
 }
 
-void VID_CheckRenderer(void) {}
-void VID_CheckGLLoaded(rendermode_t oldrender) {}
+boolean VID_CheckRenderer(void)
+{
+	return false;
+}
+
+void VID_CheckGLLoaded(rendermode_t oldrender)
+{
+	(void)oldrender;
+}
 
 const char *VID_GetModeName(INT32 modenum)
 {
diff --git a/src/console.c b/src/console.c
index 3eee67bb843e2479c0b86bcd3734288601cb0417..29794d0170e4a578b93a0cc208600ed17a5416c8 100644
--- a/src/console.c
+++ b/src/console.c
@@ -56,7 +56,8 @@ I_mutex con_mutex;
 #endif/*HAVE_THREADS*/
 
 static boolean con_started = false; // console has been initialised
-       boolean con_startup = false; // true at game startup, screen need refreshing
+       boolean con_startup = false; // true at game startup
+       boolean con_refresh = false; // screen needs refreshing
 static boolean con_forcepic = true; // at startup toggle console translucency when first off
        boolean con_recalc;          // set true when screen size has changed
 
@@ -439,7 +440,8 @@ void CON_Init(void)
 		Lock_state();
 
 		con_started = true;
-		con_startup = true; // need explicit screen refresh until we are in Doom loop
+		con_startup = true;
+		con_refresh = true; // needs explicit screen refresh until we are in the main game loop
 		consoletoggle = false;
 
 		Unlock_state();
@@ -457,7 +459,8 @@ void CON_Init(void)
 		Lock_state();
 
 		con_started = true;
-		con_startup = false; // need explicit screen refresh until we are in Doom loop
+		con_startup = false;
+		con_refresh = false; // disable explicit screen refresh
 		consoletoggle = true;
 
 		Unlock_state();
@@ -1438,7 +1441,7 @@ void CONS_Printf(const char *fmt, ...)
 {
 	va_list argptr;
 	static char *txt = NULL;
-	boolean startup;
+	boolean refresh;
 
 	if (txt == NULL)
 		txt = malloc(8192);
@@ -1454,32 +1457,21 @@ void CONS_Printf(const char *fmt, ...)
 	if (con_started)
 		CON_Print(txt);
 
-	CON_LogMessage(txt);	
+	CON_LogMessage(txt);
 
 	Lock_state();
 
 	// make sure new text is visible
 	con_scrollup = 0;
-	startup = con_startup;
+	refresh = con_refresh;
 
 	Unlock_state();
 
 	// if not in display loop, force screen update
-	if (startup && (!setrenderneeded))
+	if (refresh)
 	{
-#ifdef _WINDOWS
-		patch_t *con_backpic = W_CachePatchName("CONSBACK", PU_PATCH);
-
-		// Jimita: CON_DrawBackpic just called V_DrawScaledPatch
-		V_DrawScaledPatch(0, 0, 0, con_backpic);
-
-		W_UnlockCachedPatch(con_backpic);
-		I_LoadingScreen(txt);				// Win32/OS2 only
-#else
-		// here we display the console text
-		CON_Drawer();
+		CON_Drawer(); // here we display the console text
 		I_FinishUpdate(); // page flip or blit buffer
-#endif
 	}
 }
 
@@ -1541,7 +1533,7 @@ void CONS_Debug(INT32 debugflags, const char *fmt, ...)
 //
 void CONS_Error(const char *msg)
 {
-#ifdef RPC_NO_WINDOWS_H
+#if defined(RPC_NO_WINDOWS_H) && defined(_WINDOWS)
 	if (!graphics_started)
 	{
 		MessageBoxA(vid.WndParent, msg, "SRB2 Warning", MB_OK);
@@ -1719,8 +1711,8 @@ static void CON_DrawBackpic(void)
 	if (piclump == LUMPERROR)
 		piclump = W_GetNumForName("MISSING");
 
-	// Cache the Software patch.
-	con_backpic = W_CacheSoftwarePatchNum(piclump, PU_PATCH);
+	// Cache the patch.
+	con_backpic = W_CachePatchNum(piclump, PU_PATCH);
 
 	// Center the backpic, and draw a vertically cropped patch.
 	w = (con_backpic->width * vid.dupx);
@@ -1731,7 +1723,7 @@ static void CON_DrawBackpic(void)
 	// then fill the sides with a solid color.
 	if (x > 0)
 	{
-		column_t *column = (column_t *)((UINT8 *)(con_backpic) + LONG(con_backpic->columnofs[0]));
+		column_t *column = (column_t *)((UINT8 *)(con_backpic->columns) + (con_backpic->columnofs[0]));
 		if (!column->topdelta)
 		{
 			UINT8 *source = (UINT8 *)(column) + 3;
@@ -1743,8 +1735,7 @@ static void CON_DrawBackpic(void)
 		}
 	}
 
-	// Cache the patch normally.
-	con_backpic = W_CachePatchNum(piclump, PU_PATCH);
+	// Draw the patch.
 	V_DrawCroppedPatch(x << FRACBITS, 0, FRACUNIT, V_NOSCALESTART, con_backpic,
 			0, ( BASEVIDHEIGHT - h ), BASEVIDWIDTH, h);
 
@@ -1829,9 +1820,6 @@ void CON_Drawer(void)
 		return;
 	}
 
-	if (needpatchrecache)
-		HU_LoadGraphics();
-
 	if (con_recalc)
 	{
 		CON_RecalcSize();
diff --git a/src/console.h b/src/console.h
index c8dd9e3de4485b3ce75eb5b1ecef05237e889f95..0296f4f6e658e82a01d78a2ae05f636d90e411ed 100644
--- a/src/console.h
+++ b/src/console.h
@@ -25,8 +25,12 @@ extern I_mutex con_mutex;
 // set true when screen size has changed, to adapt console
 extern boolean con_recalc;
 
+// console being displayed at game startup
 extern boolean con_startup;
 
+// needs explicit screen refresh until we are in the main game loop
+extern boolean con_refresh;
+
 // top clip value for view render: do not draw part of view hidden by console
 extern INT32 con_clipviewtop;
 
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index e314d419f46c3250e3436dbca2739a189115f2ad..b198011a0eeff38f0e3936cc7a28b1ce3281554e 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -84,6 +84,8 @@ char playeraddress[MAXPLAYERS][64];
 // The actual timeout will be longer depending on the savegame length
 tic_t jointimeout = (10*TICRATE);
 static boolean sendingsavegame[MAXNETNODES]; // Are we sending the savegame?
+static boolean resendingsavegame[MAXNETNODES]; // Are we resending the savegame?
+static tic_t savegameresendcooldown[MAXNETNODES]; // How long before we can resend again?
 static tic_t freezetimeout[MAXNETNODES]; // Until when can this node freeze the server before getting a timeout?
 
 // Incremented by cv_joindelay when a client joins, decremented each tic.
@@ -107,15 +109,8 @@ static tic_t maketic;
 
 static INT16 consistancy[BACKUPTICS];
 
-// Resynching shit!
-static UINT32 resynch_score[MAXNETNODES]; // "score" for kicking -- if this gets too high then cfail kick
-static UINT16 resynch_delay[MAXNETNODES]; // delay time before the player can be considered to have desynched
-static UINT32 resynch_status[MAXNETNODES]; // 0 bit means synched for that player, 1 means possibly desynched
-static UINT8 resynch_sent[MAXNETNODES][MAXPLAYERS]; // what synch packets have we attempted to send to the player
-static UINT8 resynch_inprogress[MAXNETNODES];
-static UINT8 resynch_local_inprogress = false; // WE are desynched and getting packets to fix it.
 static UINT8 player_joining = false;
-UINT8 hu_resynching = 0;
+UINT8 hu_redownloadinggamestate = 0;
 
 UINT8 adminpassmd5[16];
 boolean adminpasswordset = false;
@@ -126,6 +121,7 @@ static ticcmd_t localcmds2;
 static boolean cl_packetmissed;
 // here it is for the secondary local player (splitscreen)
 static UINT8 mynode; // my address pointofview server
+static boolean cl_redownloadinggamestate = false;
 
 static UINT8 localtextcmd[MAXTEXTCMD];
 static UINT8 localtextcmd2[MAXTEXTCMD]; // splitscreen
@@ -509,603 +505,6 @@ void ReadLmpExtraData(UINT8 **demo_pointer, INT32 playernum)
 // end extra data function for lmps
 // -----------------------------------------------------------------
 
-// -----------------------------------------------------------------
-// resynch player data
-// -----------------------------------------------------------------
-static inline void resynch_write_player(resynch_pak *rsp, const size_t i)
-{
-	size_t j;
-
-	rsp->playernum = (UINT8)i;
-
-	// Do not send anything visual related.
-	// Only send data that we need to know for physics.
-	rsp->playerstate = (UINT8)players[i].playerstate; //playerstate_t
-	rsp->pflags = (UINT32)LONG(players[i].pflags); //pflags_t
-	rsp->panim  = (UINT8)players[i].panim; //panim_t
-
-	rsp->angleturn = (INT16)SHORT(players[i].angleturn);
-	rsp->oldrelangleturn = (INT16)SHORT(players[i].oldrelangleturn);
-
-	rsp->aiming = (angle_t)LONG(players[i].aiming);
-	rsp->currentweapon = LONG(players[i].currentweapon);
-	rsp->ringweapons = LONG(players[i].ringweapons);
-
-	rsp->ammoremoval = (UINT16)SHORT(players[i].ammoremoval);
-	rsp->ammoremovaltimer = (tic_t)LONG(players[i].ammoremovaltimer);
-	rsp->ammoremovalweapon = LONG(players[i].ammoremovalweapon);
-
-	for (j = 0; j < NUMPOWERS; ++j)
-		rsp->powers[j] = (UINT16)SHORT(players[i].powers[j]);
-
-	// Score is resynched in the rspfirm resync packet
-	rsp->rings = SHORT(players[i].rings);
-	rsp->spheres = SHORT(players[i].spheres);
-	rsp->lives = players[i].lives;
-	rsp->continues = players[i].continues;
-	rsp->scoreadd = players[i].scoreadd;
-	rsp->xtralife = players[i].xtralife;
-	rsp->pity = players[i].pity;
-
-	rsp->skincolor = players[i].skincolor;
-	rsp->skin = LONG(players[i].skin);
-	rsp->availabilities = LONG(players[i].availabilities);
-	// Just in case Lua does something like
-	// modify these at runtime
-	rsp->camerascale = (fixed_t)LONG(players[i].camerascale);
-	rsp->shieldscale = (fixed_t)LONG(players[i].shieldscale);
-	rsp->normalspeed = (fixed_t)LONG(players[i].normalspeed);
-	rsp->runspeed = (fixed_t)LONG(players[i].runspeed);
-	rsp->thrustfactor = players[i].thrustfactor;
-	rsp->accelstart = players[i].accelstart;
-	rsp->acceleration = players[i].acceleration;
-	rsp->charability = players[i].charability;
-	rsp->charability2 = players[i].charability2;
-	rsp->charflags = (UINT32)LONG(players[i].charflags);
-	rsp->thokitem = (UINT32)LONG(players[i].thokitem); //mobjtype_t
-	rsp->spinitem = (UINT32)LONG(players[i].spinitem); //mobjtype_t
-	rsp->revitem = (UINT32)LONG(players[i].revitem); //mobjtype_t
-	rsp->followitem = (UINT32)LONG(players[i].followitem); //mobjtype_t
-	rsp->actionspd = (fixed_t)LONG(players[i].actionspd);
-	rsp->mindash = (fixed_t)LONG(players[i].mindash);
-	rsp->maxdash = (fixed_t)LONG(players[i].maxdash);
-	rsp->jumpfactor = (fixed_t)LONG(players[i].jumpfactor);
-	rsp->playerheight = (fixed_t)LONG(players[i].height);
-	rsp->playerspinheight = (fixed_t)LONG(players[i].spinheight);
-
-	rsp->speed = (fixed_t)LONG(players[i].speed);
-	rsp->secondjump = players[i].secondjump;
-	rsp->fly1 = players[i].fly1;
-	rsp->glidetime = (tic_t)LONG(players[i].glidetime);
-	rsp->climbing = players[i].climbing;
-	rsp->deadtimer = players[i].deadtimer;
-	rsp->exiting = (tic_t)LONG(players[i].exiting);
-	rsp->homing = players[i].homing;
-	rsp->dashmode = (tic_t)LONG(players[i].dashmode);
-	rsp->skidtime = (tic_t)LONG(players[i].skidtime);
-	rsp->cmomx = (fixed_t)LONG(players[i].cmomx);
-	rsp->cmomy = (fixed_t)LONG(players[i].cmomy);
-	rsp->rmomx = (fixed_t)LONG(players[i].rmomx);
-	rsp->rmomy = (fixed_t)LONG(players[i].rmomy);
-
-	rsp->weapondelay = LONG(players[i].weapondelay);
-	rsp->tossdelay = LONG(players[i].tossdelay);
-
-	rsp->starpostx = SHORT(players[i].starpostx);
-	rsp->starposty = SHORT(players[i].starposty);
-	rsp->starpostz = SHORT(players[i].starpostz);
-	rsp->starpostnum = LONG(players[i].starpostnum);
-	rsp->starposttime = (tic_t)LONG(players[i].starposttime);
-	rsp->starpostangle = (angle_t)LONG(players[i].starpostangle);
-	rsp->starpostscale = (fixed_t)LONG(players[i].starpostscale);
-
-	rsp->maxlink = LONG(players[i].maxlink);
-	rsp->dashspeed = (fixed_t)LONG(players[i].dashspeed);
-	rsp->angle_pos = (angle_t)LONG(players[i].angle_pos);
-	rsp->old_angle_pos = (angle_t)LONG(players[i].old_angle_pos);
-	rsp->bumpertime = (tic_t)LONG(players[i].bumpertime);
-	rsp->flyangle = LONG(players[i].flyangle);
-	rsp->drilltimer = (tic_t)LONG(players[i].drilltimer);
-	rsp->linkcount = LONG(players[i].linkcount);
-	rsp->linktimer = (tic_t)LONG(players[i].linktimer);
-	rsp->anotherflyangle = LONG(players[i].anotherflyangle);
-	rsp->nightstime = (tic_t)LONG(players[i].nightstime);
-	rsp->drillmeter = LONG(players[i].drillmeter);
-	rsp->drilldelay = players[i].drilldelay;
-	rsp->bonustime = players[i].bonustime;
-	rsp->mare = players[i].mare;
-	rsp->lastsidehit = SHORT(players[i].lastsidehit);
-	rsp->lastlinehit = SHORT(players[i].lastlinehit);
-
-	rsp->losstime = (tic_t)LONG(players[i].losstime);
-	rsp->timeshit = players[i].timeshit;
-	rsp->onconveyor = LONG(players[i].onconveyor);
-
-	rsp->hasmo = false;
-	//Transfer important mo information if the player has a body.
-	//This lets us resync players even if they are dead.
-	if (!players[i].mo)
-		return;
-	rsp->hasmo = true;
-
-	rsp->health = LONG(players[i].mo->health);
-	rsp->angle = (angle_t)LONG(players[i].mo->angle);
-	rsp->rollangle = (angle_t)LONG(players[i].mo->rollangle);
-	rsp->x = LONG(players[i].mo->x);
-	rsp->y = LONG(players[i].mo->y);
-	rsp->z = LONG(players[i].mo->z);
-	rsp->momx = LONG(players[i].mo->momx);
-	rsp->momy = LONG(players[i].mo->momy);
-	rsp->momz = LONG(players[i].mo->momz);
-	rsp->friction = LONG(players[i].mo->friction);
-	rsp->movefactor = LONG(players[i].mo->movefactor);
-
-	rsp->sprite = (spritenum_t)LONG(players[i].mo->sprite);
-	rsp->frame = LONG(players[i].mo->frame);
-	rsp->sprite2 = players[i].mo->sprite2;
-	rsp->anim_duration = SHORT(players[i].mo->anim_duration);
-	rsp->tics = LONG(players[i].mo->tics);
-	rsp->statenum = (statenum_t)LONG(players[i].mo->state-states); // :(
-	rsp->eflags = (UINT16)SHORT(players[i].mo->eflags);
-	rsp->flags = LONG(players[i].mo->flags);
-	rsp->flags2 = LONG(players[i].mo->flags2);
-
-	rsp->radius = LONG(players[i].mo->radius);
-	rsp->height = LONG(players[i].mo->height);
-	rsp->scale = LONG(players[i].mo->scale);
-	rsp->destscale = LONG(players[i].mo->destscale);
-	rsp->scalespeed = LONG(players[i].mo->scalespeed);
-}
-
-static void resynch_read_player(resynch_pak *rsp)
-{
-	INT32 i = rsp->playernum, j;
-	mobj_t *savedmo = players[i].mo;
-
-	// Do not send anything visual related.
-	// Only send data that we need to know for physics.
-	players[i].playerstate = (UINT8)rsp->playerstate; //playerstate_t
-	players[i].pflags = (UINT32)LONG(rsp->pflags); //pflags_t
-	players[i].panim  = (UINT8)rsp->panim; //panim_t
-
-	players[i].angleturn = (INT16)SHORT(rsp->angleturn);
-	players[i].oldrelangleturn = (INT16)SHORT(rsp->oldrelangleturn);
-
-	players[i].aiming = (angle_t)LONG(rsp->aiming);
-	players[i].currentweapon = LONG(rsp->currentweapon);
-	players[i].ringweapons = LONG(rsp->ringweapons);
-
-	players[i].ammoremoval = (UINT16)SHORT(rsp->ammoremoval);
-	players[i].ammoremovaltimer = (tic_t)LONG(rsp->ammoremovaltimer);
-	players[i].ammoremovalweapon = LONG(rsp->ammoremovalweapon);
-
-	for (j = 0; j < NUMPOWERS; ++j)
-		players[i].powers[j] = (UINT16)SHORT(rsp->powers[j]);
-
-	// Score is resynched in the rspfirm resync packet
-	players[i].rings = SHORT(rsp->rings);
-	players[i].spheres = SHORT(rsp->spheres);
-	players[i].lives = rsp->lives;
-	players[i].continues = rsp->continues;
-	players[i].scoreadd = rsp->scoreadd;
-	players[i].xtralife = rsp->xtralife;
-	players[i].pity = rsp->pity;
-
-	players[i].skincolor = rsp->skincolor;
-	players[i].skin = LONG(rsp->skin);
-	players[i].availabilities = LONG(rsp->availabilities);
-	// Just in case Lua does something like
-	// modify these at runtime
-	players[i].camerascale = (fixed_t)LONG(rsp->camerascale);
-	players[i].shieldscale = (fixed_t)LONG(rsp->shieldscale);
-	players[i].normalspeed = (fixed_t)LONG(rsp->normalspeed);
-	players[i].runspeed = (fixed_t)LONG(rsp->runspeed);
-	players[i].thrustfactor = rsp->thrustfactor;
-	players[i].accelstart = rsp->accelstart;
-	players[i].acceleration = rsp->acceleration;
-	players[i].charability = rsp->charability;
-	players[i].charability2 = rsp->charability2;
-	players[i].charflags = (UINT32)LONG(rsp->charflags);
-	players[i].thokitem = (UINT32)LONG(rsp->thokitem); //mobjtype_t
-	players[i].spinitem = (UINT32)LONG(rsp->spinitem); //mobjtype_t
-	players[i].revitem = (UINT32)LONG(rsp->revitem); //mobjtype_t
-	players[i].followitem = (UINT32)LONG(rsp->followitem); //mobjtype_t
-	players[i].actionspd = (fixed_t)LONG(rsp->actionspd);
-	players[i].mindash = (fixed_t)LONG(rsp->mindash);
-	players[i].maxdash = (fixed_t)LONG(rsp->maxdash);
-	players[i].jumpfactor = (fixed_t)LONG(rsp->jumpfactor);
-	players[i].height = (fixed_t)LONG(rsp->playerheight);
-	players[i].spinheight = (fixed_t)LONG(rsp->playerspinheight);
-
-	players[i].speed = (fixed_t)LONG(rsp->speed);
-	players[i].secondjump = rsp->secondjump;
-	players[i].fly1 = rsp->fly1;
-	players[i].glidetime = (tic_t)LONG(rsp->glidetime);
-	players[i].climbing = rsp->climbing;
-	players[i].deadtimer = rsp->deadtimer;
-	players[i].exiting = (tic_t)LONG(rsp->exiting);
-	players[i].homing = rsp->homing;
-	players[i].dashmode = (tic_t)LONG(rsp->dashmode);
-	players[i].skidtime = (tic_t)LONG(rsp->skidtime);
-	players[i].cmomx = (fixed_t)LONG(rsp->cmomx);
-	players[i].cmomy = (fixed_t)LONG(rsp->cmomy);
-	players[i].rmomx = (fixed_t)LONG(rsp->rmomx);
-	players[i].rmomy = (fixed_t)LONG(rsp->rmomy);
-
-	players[i].weapondelay = LONG(rsp->weapondelay);
-	players[i].tossdelay = LONG(rsp->tossdelay);
-
-	players[i].starpostx = SHORT(rsp->starpostx);
-	players[i].starposty = SHORT(rsp->starposty);
-	players[i].starpostz = SHORT(rsp->starpostz);
-	players[i].starpostnum = LONG(rsp->starpostnum);
-	players[i].starposttime = (tic_t)LONG(rsp->starposttime);
-	players[i].starpostangle = (angle_t)LONG(rsp->starpostangle);
-	players[i].starpostscale = (fixed_t)LONG(rsp->starpostscale);
-
-	players[i].maxlink = LONG(rsp->maxlink);
-	players[i].dashspeed = (fixed_t)LONG(rsp->dashspeed);
-	players[i].angle_pos = (angle_t)LONG(rsp->angle_pos);
-	players[i].old_angle_pos = (angle_t)LONG(rsp->old_angle_pos);
-	players[i].bumpertime = (tic_t)LONG(rsp->bumpertime);
-	players[i].flyangle = LONG(rsp->flyangle);
-	players[i].drilltimer = (tic_t)LONG(rsp->drilltimer);
-	players[i].linkcount = LONG(rsp->linkcount);
-	players[i].linktimer = (tic_t)LONG(rsp->linktimer);
-	players[i].anotherflyangle = LONG(rsp->anotherflyangle);
-	players[i].nightstime = (tic_t)LONG(rsp->nightstime);
-	players[i].drillmeter = LONG(rsp->drillmeter);
-	players[i].drilldelay = rsp->drilldelay;
-	players[i].bonustime = rsp->bonustime;
-	players[i].mare = rsp->mare;
-	players[i].lastsidehit = SHORT(rsp->lastsidehit);
-	players[i].lastlinehit = SHORT(rsp->lastlinehit);
-
-	players[i].losstime = (tic_t)LONG(rsp->losstime);
-	players[i].timeshit = rsp->timeshit;
-	players[i].onconveyor = LONG(rsp->onconveyor);
-
-	//We get a packet for each player in game.
-	if (!playeringame[i])
-		return;
-
-	//...but keep old mo even if it is corrupt or null!
-	players[i].mo = savedmo;
-
-	//Transfer important mo information if they have a valid mo.
-	if (!rsp->hasmo)
-		return;
-
-	//server thinks player has a body.
-	//Give them a new body that can be then manipulated by the server's info.
-	if (!players[i].mo) //client thinks it has no body.
-		P_SpawnPlayer(i);
-
-	//At this point, the player should have a body, whether they were respawned or not.
-	P_UnsetThingPosition(players[i].mo);
-	players[i].mo->angle = (angle_t)LONG(rsp->angle);
-	players[i].mo->rollangle = (angle_t)LONG(rsp->rollangle);
-	players[i].mo->eflags = (UINT16)SHORT(rsp->eflags);
-	players[i].mo->flags = LONG(rsp->flags);
-	players[i].mo->flags2 = LONG(rsp->flags2);
-	players[i].mo->friction = LONG(rsp->friction);
-	players[i].mo->health = LONG(rsp->health);
-	players[i].mo->momx = LONG(rsp->momx);
-	players[i].mo->momy = LONG(rsp->momy);
-	players[i].mo->momz = LONG(rsp->momz);
-	players[i].mo->movefactor = LONG(rsp->movefactor);
-
-	// Don't use P_SetMobjStateNF to restore state, write/read all the values manually!
-	// This should stop those stupid console errors, hopefully.
-	// -- Monster Iestyn
-	players[i].mo->sprite = (spritenum_t)LONG(rsp->sprite);
-	players[i].mo->frame = LONG(rsp->frame);
-	players[i].mo->sprite2 = rsp->sprite2;
-	players[i].mo->anim_duration = SHORT(rsp->anim_duration);
-	players[i].mo->tics = LONG(rsp->tics);
-	players[i].mo->state = &states[LONG(rsp->statenum)];
-
-	players[i].mo->x = LONG(rsp->x);
-	players[i].mo->y = LONG(rsp->y);
-	players[i].mo->z = LONG(rsp->z);
-	players[i].mo->radius = LONG(rsp->radius);
-	players[i].mo->height = LONG(rsp->height);
-	// P_SetScale is redundant for this, as all related variables are already restored properly.
-	players[i].mo->scale = LONG(rsp->scale);
-	players[i].mo->destscale = LONG(rsp->destscale);
-	players[i].mo->scalespeed = LONG(rsp->scalespeed);
-
-	// And finally, SET THE MOBJ SKIN damn it.
-	if ((players[i].powers[pw_carry] == CR_NIGHTSMODE) && (skins[players[i].skin].sprites[SPR2_NFLY].numframes == 0))
-	{
-		players[i].mo->skin = &skins[DEFAULTNIGHTSSKIN];
-		players[i].mo->color = skins[DEFAULTNIGHTSSKIN].prefcolor; // this will be corrected by thinker to super flash
-	}
-	else
-	{
-		players[i].mo->skin = &skins[players[i].skin];
-		players[i].mo->color = players[i].skincolor; // this will be corrected by thinker to super flash/mario star
-	}
-
-	P_SetThingPosition(players[i].mo);
-}
-
-static inline void resynch_write_ctf(resynchend_pak *rst)
-{
-	mobj_t *mflag;
-	UINT8 i, j;
-
-	for (i = 0, mflag = redflag; i < 2; ++i, mflag = blueflag)
-	{
-		rst->flagx[i] = rst->flagy[i] = rst->flagz[i] = 0;
-		rst->flagloose[i] = rst->flagflags[i] = 0;
-		rst->flagplayer[i] = -1;
-
-		if (!mflag)
-		{
-			// Should be held by a player
-			for (j = 0; j < MAXPLAYERS; ++j)
-			{
-				// GF_REDFLAG is 1, GF_BLUEFLAG is 2
-				// redflag handling is i=0, blueflag is i=1
-				// so check for gotflag == (i+1)
-				if (!playeringame[j] || players[j].gotflag != (i+1))
-					continue;
-				rst->flagplayer[i] = (SINT8)j;
-				break;
-			}
-			if (j == MAXPLAYERS) // fine, no I_Error
-			{
-				CONS_Alert(CONS_ERROR, "One of the flags has gone completely missing...\n");
-				rst->flagplayer[i] = -2;
-			}
-			continue;
-		}
-
-		rst->flagx[i] = (fixed_t)LONG(mflag->x);
-		rst->flagy[i] = (fixed_t)LONG(mflag->y);
-		rst->flagz[i] = (fixed_t)LONG(mflag->z);
-		rst->flagflags[i] = LONG(mflag->flags2);
-		rst->flagloose[i] = LONG(mflag->fuse); // Dropped or not?
-	}
-}
-
-static inline void resynch_read_ctf(resynchend_pak *p)
-{
-	UINT8 i;
-
-	for (i = 0; i < MAXPLAYERS; ++i)
-		players[i].gotflag = 0;
-
-	// Red flag
-	if (p->flagplayer[0] == -2)
-		; // The server doesn't even know what happened to it...
-	else if (p->flagplayer[0] != -1) // Held by a player
-	{
-		if (!playeringame[p->flagplayer[0]])
-			 I_Error("Invalid red flag player %d who isn't in the game!", (INT32)p->flagplayer[0]);
-		players[p->flagplayer[0]].gotflag = GF_REDFLAG;
-		if (redflag)
-		{
-			P_RemoveMobj(redflag);
-			redflag = NULL;
-		}
-	}
-	else
-	{
-		if (!redflag)
-			redflag = P_SpawnMobj(0,0,0,MT_REDFLAG);
-
-		P_UnsetThingPosition(redflag);
-		redflag->x = (fixed_t)LONG(p->flagx[0]);
-		redflag->y = (fixed_t)LONG(p->flagy[0]);
-		redflag->z = (fixed_t)LONG(p->flagz[0]);
-		redflag->flags2 = LONG(p->flagflags[0]);
-		redflag->fuse = LONG(p->flagloose[0]);
-		P_SetThingPosition(redflag);
-	}
-
-	// Blue flag
-	if (p->flagplayer[1] == -2)
-		; // The server doesn't even know what happened to it...
-	else if (p->flagplayer[1] != -1) // Held by a player
-	{
-		if (!playeringame[p->flagplayer[1]])
-			 I_Error("Invalid blue flag player %d who isn't in the game!", (INT32)p->flagplayer[1]);
-		players[p->flagplayer[1]].gotflag = GF_BLUEFLAG;
-		if (blueflag)
-		{
-			P_RemoveMobj(blueflag);
-			blueflag = NULL;
-		}
-	}
-	else
-	{
-		if (!blueflag)
-			blueflag = P_SpawnMobj(0,0,0,MT_BLUEFLAG);
-
-		P_UnsetThingPosition(blueflag);
-		blueflag->x = (fixed_t)LONG(p->flagx[1]);
-		blueflag->y = (fixed_t)LONG(p->flagy[1]);
-		blueflag->z = (fixed_t)LONG(p->flagz[1]);
-		blueflag->flags2 = LONG(p->flagflags[1]);
-		blueflag->fuse = LONG(p->flagloose[1]);
-		P_SetThingPosition(blueflag);
-	}
-}
-
-static inline void resynch_write_others(resynchend_pak *rst)
-{
-	UINT8 i;
-
-	rst->ingame = 0;
-	rst->outofcoop = 0;
-
-	for (i = 0; i < MAXPLAYERS; ++i)
-	{
-		if (!playeringame[i])
-		{
-			rst->ctfteam[i] = 0;
-			rst->score[i] = 0;
-			rst->numboxes[i] = 0;
-			rst->totalring[i] = 0;
-			rst->realtime[i] = 0;
-			rst->laps[i] = 0;
-			continue;
-		}
-
-		if (!players[i].spectator)
-			rst->ingame |= (1<<i);
-		if (players[i].outofcoop)
-			rst->outofcoop |= (1<<i);
-		rst->ctfteam[i] = (INT32)LONG(players[i].ctfteam);
-		rst->score[i] = (UINT32)LONG(players[i].score);
-		rst->numboxes[i] = SHORT(players[i].numboxes);
-		rst->totalring[i] = SHORT(players[i].totalring);
-		rst->realtime[i] = (tic_t)LONG(players[i].realtime);
-		rst->laps[i] = players[i].laps;
-	}
-
-	// endian safeness
-	rst->ingame = (UINT32)LONG(rst->ingame);
-}
-
-static inline void resynch_read_others(resynchend_pak *p)
-{
-	UINT8 i;
-	UINT32 loc_ingame = (UINT32)LONG(p->ingame);
-	UINT32 loc_outofcoop = (UINT32)LONG(p->outofcoop);
-
-	for (i = 0; i < MAXPLAYERS; ++i)
-	{
-		// We don't care if they're in the game or not, just write all the data.
-		players[i].spectator = !(loc_ingame & (1<<i));
-		players[i].outofcoop = (loc_outofcoop & (1<<i));
-		players[i].ctfteam = (INT32)LONG(p->ctfteam[i]); // no, 0 does not mean spectator, at least not in Match
-		players[i].score = (UINT32)LONG(p->score[i]);
-		players[i].numboxes = SHORT(p->numboxes[i]);
-		players[i].totalring = SHORT(p->totalring[i]);
-		players[i].realtime = (tic_t)LONG(p->realtime[i]);
-		players[i].laps = p->laps[i];
-	}
-}
-
-static void SV_InitResynchVars(INT32 node)
-{
-	resynch_delay[node] = TICRATE; // initial one second delay
-	resynch_score[node] = 0; // clean slate
-	resynch_status[node] = 0x00;
-	resynch_inprogress[node] = false;
-	memset(resynch_sent[node], 0, MAXPLAYERS);
-}
-
-static void SV_RequireResynch(INT32 node)
-{
-	INT32 i;
-
-	resynch_delay[node] = 10; // Delay before you can fail sync again
-	resynch_score[node] += 200; // Add score for initial desynch
-	resynch_status[node] = 0xFFFFFFFF; // No players assumed synched
-	resynch_inprogress[node] = true; // so we know to send a PT_RESYNCHEND after sync
-
-	// Initial setup
-	memset(resynch_sent[node], 0, MAXPLAYERS);
-	for (i = 0; i < MAXPLAYERS; ++i)
-	{
-		if (!playeringame[i]) // Player not in game so just drop it from required synch
-			resynch_status[node] &= ~(1<<i);
-		else if (playernode[i] == node); // instantly update THEIR position
-		else // Send at random times based on num players
-			resynch_sent[node][i] = M_RandomKey(D_NumPlayers()>>1)+1;
-	}
-}
-
-static void SV_SendResynch(INT32 node)
-{
-	INT32 i, j;
-
-	if (!nodeingame[node])
-	{
-		// player left during resynch
-		// so obviously we don't need to do any of this anymore
-		resynch_inprogress[node] = false;
-		return;
-	}
-
-	// resynched?
-	if (!resynch_status[node])
-	{
-		// you are now synched
-		resynch_inprogress[node] = false;
-
-		netbuffer->packettype = PT_RESYNCHEND;
-
-		netbuffer->u.resynchend.randomseed = P_GetRandSeed();
-		if (gametyperules & GTR_TEAMFLAGS)
-			resynch_write_ctf(&netbuffer->u.resynchend);
-		resynch_write_others(&netbuffer->u.resynchend);
-
-		HSendPacket(node, true, 0, (sizeof(resynchend_pak)));
-		return;
-	}
-
-	netbuffer->packettype = PT_RESYNCHING;
-	for (i = 0, j = 0; i < MAXPLAYERS; ++i)
-	{
-		// if already synched don't bother
-		if (!(resynch_status[node] & 1<<i))
-			continue;
-
-		// waiting for a reply or just waiting in general
-		if (resynch_sent[node][i])
-		{
-			--resynch_sent[node][i];
-			continue;
-		}
-
-		resynch_write_player(&netbuffer->u.resynchpak, i);
-		HSendPacket(node, false, 0, (sizeof(resynch_pak)));
-
-		resynch_sent[node][i] = TICRATE;
-		resynch_score[node] += 2; // penalty for send
-
-		if (++j > 3)
-			break;
-	}
-
-	if (resynch_score[node] > (unsigned)cv_resynchattempts.value*250)
-	{
-		SendKick(nodetoplayer[node], KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
-		resynch_score[node] = 0;
-	}
-}
-
-static void CL_AcknowledgeResynch(resynch_pak *rsp)
-{
-	resynch_read_player(rsp);
-
-	netbuffer->packettype = PT_RESYNCHGET;
-	netbuffer->u.resynchgot = rsp->playernum;
-	HSendPacket(servernode, true, 0, sizeof(UINT8));
-}
-
-static void SV_AcknowledgeResynchAck(INT32 node, UINT8 rsg)
-{
-	if (rsg >= MAXPLAYERS)
-		resynch_score[node] += 16384; // lol.
-	else
-	{
-		resynch_status[node] &= ~(1<<rsg);
-		--resynch_score[node]; // unpenalize
-	}
-
-	// Don't let resynch cause a timeout
-	freezetimeout[node] = I_GetTime() + connectiontimeout;
-}
-// -----------------------------------------------------------------
-// end resynch
-// -----------------------------------------------------------------
-
 static INT16 Consistancy(void);
 
 typedef enum
@@ -1123,40 +522,6 @@ static void GetPackets(void);
 
 static cl_mode_t cl_mode = CL_SEARCHING;
 
-// Player name send/load
-
-static void CV_SavePlayerNames(UINT8 **p)
-{
-	INT32 i = 0;
-	// Players in game only.
-	for (; i < MAXPLAYERS; ++i)
-	{
-		if (!playeringame[i])
-		{
-			WRITEUINT8(*p, 0);
-			continue;
-		}
-		WRITESTRING(*p, player_names[i]);
-	}
-}
-
-static void CV_LoadPlayerNames(UINT8 **p)
-{
-	INT32 i = 0;
-	char tmp_name[MAXPLAYERNAME+1];
-	tmp_name[MAXPLAYERNAME] = 0;
-
-	for (; i < MAXPLAYERS; ++i)
-	{
-		READSTRING(*p, tmp_name);
-		if (tmp_name[0] == 0)
-			continue;
-		if (tmp_name[MAXPLAYERNAME]) // overflow detected
-			I_Error("Received bad server config packet when trying to join");
-		memcpy(player_names[i], tmp_name, MAXPLAYERNAME+1);
-	}
-}
-
 #ifndef NONET
 #define SNAKE_SPEED 5
 
@@ -1975,8 +1340,6 @@ static void SV_SendPlayerInfo(INT32 node)
   */
 static boolean SV_SendServerConfig(INT32 node)
 {
-	INT32 i;
-	UINT8 *p, *op;
 	boolean waspacketsent;
 
 	netbuffer->packettype = PT_SERVERCFG;
@@ -1992,32 +1355,10 @@ static boolean SV_SendServerConfig(INT32 node)
 	netbuffer->u.servercfg.gametype = (UINT8)gametype;
 	netbuffer->u.servercfg.modifiedgame = (UINT8)modifiedgame;
 
-	// we fill these structs with FFs so that any players not in game get sent as 0xFFFF
-	// which is nice and easy for us to detect
-	memset(netbuffer->u.servercfg.playerskins, 0xFF, sizeof(netbuffer->u.servercfg.playerskins));
-	memset(netbuffer->u.servercfg.playercolor, 0xFF, sizeof(netbuffer->u.servercfg.playercolor));
-	memset(netbuffer->u.servercfg.playeravailabilities, 0xFF, sizeof(netbuffer->u.servercfg.playeravailabilities));
-
-	memset(netbuffer->u.servercfg.adminplayers, -1, sizeof(netbuffer->u.servercfg.adminplayers));
-
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		netbuffer->u.servercfg.adminplayers[i] = (SINT8)adminplayers[i];
-
-		if (!playeringame[i])
-			continue;
-		netbuffer->u.servercfg.playerskins[i] = (UINT8)players[i].skin;
-		netbuffer->u.servercfg.playercolor[i] = (UINT16)players[i].skincolor;
-		netbuffer->u.servercfg.playeravailabilities[i] = (UINT32)LONG(players[i].availabilities);
-	}
-
 	memcpy(netbuffer->u.servercfg.server_context, server_context, 8);
-	op = p = netbuffer->u.servercfg.varlengthinputs;
 
-	CV_SavePlayerNames(&p);
-	CV_SaveNetVars(&p);
 	{
-		const size_t len = sizeof (serverconfig_pak) + (size_t)(p - op);
+		const size_t len = sizeof (serverconfig_pak);
 
 #ifdef DEBUGFILE
 		if (debugfile)
@@ -2050,7 +1391,17 @@ static boolean SV_SendServerConfig(INT32 node)
 #ifndef NONET
 #define SAVEGAMESIZE (768*1024)
 
-static void SV_SendSaveGame(INT32 node)
+static boolean SV_ResendingSavegameToAnyone(void)
+{
+	INT32 i;
+
+	for (i = 0; i < MAXNETNODES; i++)
+		if (resendingsavegame[i])
+			return true;
+	return false;
+}
+
+static void SV_SendSaveGame(INT32 node, boolean resending)
 {
 	size_t length, compressedlen;
 	UINT8 *savebuffer;
@@ -2068,7 +1419,7 @@ static void SV_SendSaveGame(INT32 node)
 	// Leave room for the uncompressed length.
 	save_p = savebuffer + sizeof(UINT32);
 
-	P_SaveNetGame();
+	P_SaveNetGame(resending);
 
 	length = save_p - savebuffer;
 	if (length > SAVEGAMESIZE)
@@ -2141,7 +1492,7 @@ static void SV_SavedGame(void)
 		return;
 	}
 
-	P_SaveNetGame();
+	P_SaveNetGame(false);
 
 	length = save_p - savebuffer;
 	if (length > SAVEGAMESIZE)
@@ -2164,7 +1515,7 @@ static void SV_SavedGame(void)
 #define TMPSAVENAME "$$$.sav"
 
 
-static void CL_LoadReceivedSavegame(void)
+static void CL_LoadReceivedSavegame(boolean reloading)
 {
 	UINT8 *savebuffer = NULL;
 	size_t length, decompressedlen;
@@ -2200,7 +1551,7 @@ static void CL_LoadReceivedSavegame(void)
 	automapactive = false;
 
 	// load a base level
-	if (P_LoadNetGame())
+	if (P_LoadNetGame(reloading))
 	{
 		const UINT8 actnum = mapheaderinfo[gamemap-1]->actnum;
 		CONS_Printf(M_GetText("Map is now \"%s"), G_BuildMapName(gamemap));
@@ -2231,6 +1582,45 @@ static void CL_LoadReceivedSavegame(void)
 		CONS_Alert(CONS_ERROR, M_GetText("Can't delete %s\n"), tmpsave);
 	consistancy[gametic%BACKUPTICS] = Consistancy();
 	CON_ToggleOff();
+
+	// Tell the server we have received and reloaded the gamestate
+	// so they know they can resume the game
+	netbuffer->packettype = PT_RECEIVEDGAMESTATE;
+	HSendPacket(servernode, true, 0, 0);
+}
+
+static void CL_ReloadReceivedSavegame(void)
+{
+	INT32 i;
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+#ifdef HAVE_BLUA
+		LUA_InvalidatePlayer(&players[i]);
+#endif
+		sprintf(player_names[i], "Player %d", i + 1);
+	}
+
+	CL_LoadReceivedSavegame(true);
+
+	if (neededtic < gametic)
+		neededtic = gametic;
+	maketic = neededtic;
+
+	ticcmd_oldangleturn[0] = players[consoleplayer].oldrelangleturn;
+	P_ForceLocalAngle(&players[consoleplayer], (angle_t)(players[consoleplayer].angleturn << 16));
+	if (splitscreen)
+	{
+		ticcmd_oldangleturn[1] = players[secondarydisplayplayer].oldrelangleturn;
+		P_ForceLocalAngle(&players[secondarydisplayplayer], (angle_t)(players[secondarydisplayplayer].angleturn << 16));
+	}
+
+	camera.subsector = R_PointInSubsector(camera.x, camera.y);
+	camera2.subsector = R_PointInSubsector(camera2.x, camera2.y);
+
+	cl_redownloadinggamestate = false;
+
+	CONS_Printf(M_GetText("Game state reloaded\n"));
 }
 #endif
 
@@ -2648,7 +2038,7 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic
 			if (fileneeded[0].status == FS_FOUND)
 			{
 				// Gamestate is now handled within CL_LoadReceivedSavegame()
-				CL_LoadReceivedSavegame();
+				CL_LoadReceivedSavegame(false);
 				cl_mode = CL_CONNECTED;
 			} // don't break case continue to CL_CONNECTED
 			else
@@ -3111,10 +2501,14 @@ static void CL_RemovePlayer(INT32 playernum, kickreason_t reason)
 		}
 
 		count--;
-		spheres = players[playernum].spheres;
-		rings = players[playernum].rings;
-		sincrement = spheres/count;
-		rincrement = rings/count;
+		sincrement = spheres = players[playernum].spheres;
+		rincrement = rings = players[playernum].rings;
+
+		if (count)
+		{
+			sincrement /= count;
+			rincrement /= count;
+		}
 
 		for (i = 0; i < MAXPLAYERS; i++)
 		{
@@ -3204,7 +2598,6 @@ void CL_Reset(void)
 	multiplayer = false;
 	servernode = 0;
 	server = true;
-	resynch_local_inprogress = false;
 	doomcom->numnodes = 1;
 	doomcom->numslots = 1;
 	SV_StopServer();
@@ -3676,6 +3069,34 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 		CL_RemovePlayer(pnum, kickreason);
 }
 
+static void Command_ResendGamestate(void)
+{
+	SINT8 playernum;
+
+	if (COM_Argc() == 1)
+	{
+		CONS_Printf(M_GetText("resendgamestate <playername/playernum>: resend the game state to a player\n"));
+		return;
+	}
+	else if (client)
+	{
+		CONS_Printf(M_GetText("Only the server can use this.\n"));
+		return;
+	}
+
+	playernum = nametonum(COM_Argv(1));
+	if (playernum == -1 || playernum == 0)
+		return;
+
+	// Send a PT_WILLRESENDGAMESTATE packet to the client so they know what's going on
+	netbuffer->packettype = PT_WILLRESENDGAMESTATE;
+	if (!HSendPacket(playernode[playernum], true, 0, 0))
+	{
+		CONS_Alert(CONS_ERROR, M_GetText("A problem occured, please try again.\n"));
+		return;
+	}
+}
+
 static CV_PossibleValue_t netticbuffer_cons_t[] = {{0, "MIN"}, {3, "MAX"}, {0, NULL}};
 consvar_t cv_netticbuffer = CVAR_INIT ("netticbuffer", "1", CV_SAVE, netticbuffer_cons_t, NULL);
 
@@ -3719,6 +3140,7 @@ void D_ClientServerInit(void)
 	COM_AddCommand("reloadbans", Command_ReloadBan);
 	COM_AddCommand("connect", Command_connect);
 	COM_AddCommand("nodes", Command_Nodes);
+	COM_AddCommand("resendgamestate", Command_ResendGamestate);
 #ifdef PACKETDROP
 	COM_AddCommand("drop", Command_Drop);
 	COM_AddCommand("droprate", Command_Droprate);
@@ -3750,14 +3172,18 @@ void D_ClientServerInit(void)
 static void ResetNode(INT32 node)
 {
 	nodeingame[node] = false;
-	nodetoplayer[node] = -1;
-	nodetoplayer2[node] = -1;
+	nodewaiting[node] = 0;
+
 	nettics[node] = gametic;
 	supposedtics[node] = gametic;
-	nodewaiting[node] = 0;
+
+	nodetoplayer[node] = -1;
+	nodetoplayer2[node] = -1;
 	playerpernode[node] = 0;
+
 	sendingsavegame[node] = false;
-	SV_InitResynchVars(node);
+	resendingsavegame[node] = false;
+	savegameresendcooldown[node] = 0;
 }
 
 void SV_ResetServer(void)
@@ -3791,6 +3217,7 @@ void SV_ResetServer(void)
 
 	mynode = 0;
 	cl_packetmissed = false;
+	cl_redownloadinggamestate = false;
 
 	if (dedicated)
 	{
@@ -4260,12 +3687,6 @@ static void HandleConnect(SINT8 node)
 #endif
 			SV_AddNode(node);
 
-			/// \note Wait what???
-			///       What if the gamestate takes more than one second to get downloaded?
-			///       Or if a lagspike happens?
-			// you get a free second before desynch checks. use it wisely.
-			SV_InitResynchVars(node);
-
 			if (cv_joinnextround.value && gameaction == ga_nothing)
 				G_SetGamestate(GS_WAITINGPLAYERS);
 			if (!SV_SendServerConfig(node))
@@ -4287,7 +3708,7 @@ static void HandleConnect(SINT8 node)
 		{
 			if ((gamestate == GS_LEVEL || gamestate == GS_INTERMISSION) && newnode)
 			{
-				SV_SendSaveGame(node); // send a complete game state
+				SV_SendSaveGame(node, false); // send a complete game state
 				DEBFILE("send savegame\n");
 			}
 			SV_AddWaitingPlayers(names[0], names[1]);
@@ -4354,6 +3775,43 @@ static void HandleServerInfo(SINT8 node)
 }
 #endif
 
+static void PT_WillResendGamestate(void)
+{
+	char tmpsave[256];
+
+	if (server || cl_redownloadinggamestate)
+		return;
+
+	// Send back a PT_CANRECEIVEGAMESTATE packet to the server
+	// so they know they can start sending the game state
+	netbuffer->packettype = PT_CANRECEIVEGAMESTATE;
+	if (!HSendPacket(servernode, true, 0, 0))
+		return;
+
+	CONS_Printf(M_GetText("Reloading game state...\n"));
+
+	sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home);
+
+	// Don't get a corrupt savegame error because tmpsave already exists
+	if (FIL_FileExists(tmpsave) && unlink(tmpsave) == -1)
+		I_Error("Can't delete %s\n", tmpsave);
+
+	CL_PrepareDownloadSaveGame(tmpsave);
+
+	cl_redownloadinggamestate = true;
+}
+
+static void PT_CanReceiveGamestate(SINT8 node)
+{
+	if (client || sendingsavegame[node])
+		return;
+
+	CONS_Printf(M_GetText("Resending game state to %s...\n"), player_names[nodetoplayer[node]]);
+
+	SV_SendSaveGame(node, true); // Resend a complete game state
+	resendingsavegame[node] = true;
+}
+
 /** Handles a packet received from a node that isn't in game
   *
   * \param node The packet sender
@@ -4444,9 +3902,6 @@ static void HandlePacketFromAwayNode(SINT8 node)
 
 		case PT_SERVERCFG: // Positive response of client join request
 		{
-			INT32 j;
-			UINT8 *scp;
-
 			if (server && serverrunning && node != servernode)
 			{ // but wait I thought I'm the server?
 				Net_CloseConnection(node);
@@ -4462,8 +3917,6 @@ static void HandlePacketFromAwayNode(SINT8 node)
 				maketic = gametic = neededtic = (tic_t)LONG(netbuffer->u.servercfg.gametic);
 				G_SetGametype(netbuffer->u.servercfg.gametype);
 				modifiedgame = netbuffer->u.servercfg.modifiedgame;
-				for (j = 0; j < MAXPLAYERS; j++)
-					adminplayers[j] = netbuffer->u.servercfg.adminplayers[j];
 				memcpy(server_context, netbuffer->u.servercfg.server_context, 8);
 			}
 
@@ -4482,23 +3935,6 @@ static void HandlePacketFromAwayNode(SINT8 node)
 #endif
 			DEBFILE(va("Server accept join gametic=%u mynode=%d\n", gametic, mynode));
 
-			memset(playeringame, 0, sizeof(playeringame));
-			for (j = 0; j < MAXPLAYERS; j++)
-			{
-				if (netbuffer->u.servercfg.playerskins[j] == 0xFF
-				 && netbuffer->u.servercfg.playercolor[j] == 0xFFFF
-				 && netbuffer->u.servercfg.playeravailabilities[j] == 0xFFFFFFFF)
-					continue; // not in game
-
-				playeringame[j] = true;
-				players[j].availabilities = (UINT32)LONG(netbuffer->u.servercfg.playeravailabilities[j]);
-				SetPlayerSkinByNum(j, (INT32)netbuffer->u.servercfg.playerskins[j]);
-				players[j].skincolor = netbuffer->u.servercfg.playercolor[j];
-			}
-
-			scp = netbuffer->u.servercfg.varlengthinputs;
-			CV_LoadPlayerNames(&scp);
-			CV_LoadNetVars(&scp);
 #ifndef NONET
 			/// \note Wait. What if a Lua script uses some global custom variables synched with the NetVars hook?
 			///       Shouldn't them be downloaded even at intermission time?
@@ -4598,11 +4034,6 @@ static void HandlePacketFromPlayer(SINT8 node)
 	switch (netbuffer->packettype)
 	{
 // -------------------------------------------- SERVER RECEIVE ----------
-		case PT_RESYNCHGET:
-			if (client)
-				break;
-			SV_AcknowledgeResynchAck(node, netbuffer->u.resynchgot);
-			break;
 		case PT_CLIENTCMD:
 		case PT_CLIENT2CMD:
 		case PT_CLIENTMIS:
@@ -4612,10 +4043,6 @@ static void HandlePacketFromPlayer(SINT8 node)
 			if (client)
 				break;
 
-			// Ignore tics from those not synched
-			if (resynch_inprogress[node] && nettics[node] == gametic)
-				break;
-
 			// To save bytes, only the low byte of tic numbers are sent
 			// Use ExpandTics to figure out what the rest of the bytes are
 			realstart = ExpandTics(netbuffer->u.clientpak.client_tic, node);
@@ -4642,9 +4069,6 @@ static void HandlePacketFromPlayer(SINT8 node)
 				|| netbuffer->packettype == PT_NODEKEEPALIVEMIS)
 				break;
 
-			// If a client sends a ticcmd it should mean they are done receiving the savegame
-			sendingsavegame[node] = false;
-
 			// As long as clients send valid ticcmds, the server can keep running, so reset the timeout
 			/// \todo Use a separate cvar for that kind of timeout?
 			freezetimeout[node] = I_GetTime() + connectiontimeout;
@@ -4669,21 +4093,20 @@ static void HandlePacketFromPlayer(SINT8 node)
 				G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][(UINT8)nodetoplayer2[node]],
 					&netbuffer->u.client2pak.cmd2, 1);
 
-			// A delay before we check resynching
-			// Used on join or just after a synch fail
-			if (resynch_delay[node])
-			{
-				--resynch_delay[node];
-				break;
-			}
 			// Check player consistancy during the level
-			if (realstart <= gametic && realstart > gametic - BACKUPTICS+1 && gamestate == GS_LEVEL
-				&& consistancy[realstart%BACKUPTICS] != SHORT(netbuffer->u.clientpak.consistancy))
+			if (realstart <= gametic && realstart + BACKUPTICS - 1 > gametic && gamestate == GS_LEVEL
+				&& consistancy[realstart%BACKUPTICS] != SHORT(netbuffer->u.clientpak.consistancy)
+				&& !resendingsavegame[node] && savegameresendcooldown[node] <= I_GetTime()
+				&& !SV_ResendingSavegameToAnyone())
 			{
-				SV_RequireResynch(node);
-
-				if (cv_resynchattempts.value && resynch_score[node] <= (unsigned)cv_resynchattempts.value*250)
+				if (cv_resynchattempts.value)
 				{
+					// Tell the client we are about to resend them the gamestate
+					netbuffer->packettype = PT_WILLRESENDGAMESTATE;
+					HSendPacket(node, true, 0, 0);
+
+					resendingsavegame[node] = true;
+
 					if (cv_blamecfail.value)
 						CONS_Printf(M_GetText("Synch failure for player %d (%s); expected %hd, got %hd\n"),
 							netconsole+1, player_names[netconsole],
@@ -4703,8 +4126,6 @@ static void HandlePacketFromPlayer(SINT8 node)
 					break;
 				}
 			}
-			else if (resynch_score[node])
-				--resynch_score[node];
 			break;
 		case PT_TEXTCMD2: // splitscreen special
 			netconsole = nodetoplayer2[node];
@@ -4830,6 +4251,9 @@ static void HandlePacketFromPlayer(SINT8 node)
 			Net_CloseConnection(node);
 			nodeingame[node] = false;
 			break;
+		case PT_CANRECEIVEGAMESTATE:
+			PT_CanReceiveGamestate(node);
+			break;
 		case PT_ASKLUAFILE:
 			if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_ASKED)
 				AddLuaFileToSendQueue(node, luafiletransfers->realfilename);
@@ -4838,25 +4262,12 @@ static void HandlePacketFromPlayer(SINT8 node)
 			if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_SENDING)
 				SV_HandleLuaFileSent(node);
 			break;
-// -------------------------------------------- CLIENT RECEIVE ----------
-		case PT_RESYNCHEND:
-			// Only accept PT_RESYNCHEND from the server.
-			if (node != servernode)
-			{
-				CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_RESYNCHEND", node);
-				if (server)
-					SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
-				break;
-			}
-			resynch_local_inprogress = false;
-
-			P_SetRandSeed(netbuffer->u.resynchend.randomseed);
-
-			if (gametyperules & GTR_TEAMFLAGS)
-				resynch_read_ctf(&netbuffer->u.resynchend);
-			resynch_read_others(&netbuffer->u.resynchend);
-
+		case PT_RECEIVEDGAMESTATE:
+			sendingsavegame[node] = false;
+			resendingsavegame[node] = false;
+			savegameresendcooldown[node] = I_GetTime() + 15 * TICRATE;
 			break;
+// -------------------------------------------- CLIENT RECEIVE ----------
 		case PT_SERVERTICS:
 			// Only accept PT_SERVERTICS from the server.
 			if (node != servernode)
@@ -4917,18 +4328,6 @@ static void HandlePacketFromPlayer(SINT8 node)
 							"IRC or Discord so it can be fixed.\n", (INT32)realstart, (INT32)realend, (INT32)neededtic);*/
 			}
 			break;
-		case PT_RESYNCHING:
-			// Only accept PT_RESYNCHING from the server.
-			if (node != servernode)
-			{
-				CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_RESYNCHING", node);
-				if (server)
-					SendKick(netconsole, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
-				break;
-			}
-			resynch_local_inprogress = true;
-			CL_AcknowledgeResynch(&netbuffer->u.resynchpak);
-			break;
 		case PT_PING:
 			// Only accept PT_PING from the server.
 			if (node != servernode)
@@ -4973,6 +4372,9 @@ static void HandlePacketFromPlayer(SINT8 node)
 			if (server)
 				PT_FileReceived();
 			break;
+		case PT_WILLRESENDGAMESTATE:
+			PT_WillResendGamestate();
+			break;
 		case PT_SENDINGLUAFILE:
 			if (client)
 				CL_PrepareDownloadLuaFile();
@@ -5430,7 +4832,7 @@ void TryRunTics(tic_t realtics)
 	if (player_joining)
 		return;
 
-	if (neededtic > gametic && !resynch_local_inprogress)
+	if (neededtic > gametic)
 	{
 		if (advancedemo)
 		{
@@ -5596,9 +4998,12 @@ void NetUpdate(void)
 
 	if (client)
 	{
-		if (!resynch_local_inprogress)
-			CL_SendClientCmd(); // Send tic cmd
-		hu_resynching = resynch_local_inprogress;
+		// If the client just finished redownloading the game state, load it
+		if (cl_redownloadinggamestate && fileneeded[0].status == FS_FOUND)
+			CL_ReloadReceivedSavegame();
+
+		CL_SendClientCmd(); // Send tic cmd
+		hu_redownloadinggamestate = cl_redownloadinggamestate;
 	}
 	else
 	{
@@ -5606,7 +5011,7 @@ void NetUpdate(void)
 		{
 			INT32 counts;
 
-			hu_resynching = false;
+			hu_redownloadinggamestate = false;
 
 			firstticstosend = gametic;
 			for (i = 0; i < MAXNETNODES; i++)
@@ -5621,36 +5026,18 @@ void NetUpdate(void)
 			// Don't erase tics not acknowledged
 			counts = realtics;
 
-			for (i = 0; i < MAXNETNODES; ++i)
-				if (resynch_inprogress[i])
-				{
-					if (!nodeingame[i] || nettics[i] == gametic)
-					{
-						SV_SendResynch(i);
-						counts = -666;
-					}
-					else
-						counts = 0; // Let the client catch up with the server
-				}
+			if (maketic + counts >= firstticstosend + BACKUPTICS)
+				counts = firstticstosend+BACKUPTICS-maketic-1;
 
-			// Do not make tics while resynching
-			if (counts != -666)
-			{
-				if (maketic + counts >= firstticstosend + BACKUPTICS)
-					counts = firstticstosend+BACKUPTICS-maketic-1;
-
-				for (i = 0; i < counts; i++)
-					SV_Maketic(); // Create missed tics and increment maketic
+			for (i = 0; i < counts; i++)
+				SV_Maketic(); // Create missed tics and increment maketic
 
-				for (; tictoclear < firstticstosend; tictoclear++) // Clear only when acknowledged
-					D_Clearticcmd(tictoclear);                    // Clear the maketic the new tic
+			for (; tictoclear < firstticstosend; tictoclear++) // Clear only when acknowledged
+				D_Clearticcmd(tictoclear);                    // Clear the maketic the new tic
 
-				SV_SendTics();
+			SV_SendTics();
 
-				neededtic = maketic; // The server is a client too
-			}
-			else
-				hu_resynching = true;
+			neededtic = maketic; // The server is a client too
 		}
 	}
 
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index adc8a7cc9b090cf4bf5d5881f08070bf34ba1093..3d67525dacc65dd6c79d18c544cb7ff9fdffebac 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -64,8 +64,10 @@ typedef enum
 	PT_REQUESTFILE,   // Client requests a file transfer
 	PT_ASKINFOVIAMS,  // Packet from the MS requesting info be sent to new client.
 	                  // If this ID changes, update masterserver definition.
-	PT_RESYNCHEND,    // Player is now resynched and is being requested to remake the gametic
-	PT_RESYNCHGET,    // Player got resynch packet
+
+	PT_WILLRESENDGAMESTATE, // Hey Client, I am about to resend you the gamestate!
+	PT_CANRECEIVEGAMESTATE, // Okay Server, I'm ready to receive it, you can go ahead.
+	PT_RECEIVEDGAMESTATE,   // Thank you Server, I am ready to play again!
 
 	PT_SENDINGLUAFILE, // Server telling a client Lua needs to open a file
 	PT_ASKLUAFILE,     // Client telling the server they don't have the file
@@ -85,8 +87,6 @@ typedef enum
 	PT_TEXTCMD2,      // Splitscreen text commands.
 	PT_CLIENTJOIN,    // Client wants to join; used in start game.
 	PT_NODETIMEOUT,   // Packet sent to self if the connection times out.
-	PT_RESYNCHING,    // Packet sent to resync players.
-	                  // Blocks game advance until synched.
 
 	PT_LOGIN,         // Login attempt from the client.
 
@@ -139,168 +139,6 @@ typedef struct
 	ticcmd_t cmds[45]; // Normally [BACKUPTIC][MAXPLAYERS] but too large
 } ATTRPACK servertics_pak;
 
-// Sent to client when all consistency data
-// for players has been restored
-typedef struct
-{
-	UINT32 randomseed;
-
-	// CTF flag stuff
-	SINT8 flagplayer[2];
-	INT32 flagloose[2];
-	INT32 flagflags[2];
-	fixed_t flagx[2];
-	fixed_t flagy[2];
-	fixed_t flagz[2];
-
-	UINT32 ingame;  // Spectator bit for each player
-	UINT32 outofcoop;  // outofcoop bit for each player
-	INT32 ctfteam[MAXPLAYERS]; // Which team? (can't be 1 bit, since in regular Match there are no teams)
-
-	// Resynch game scores and the like all at once
-	UINT32 score[MAXPLAYERS]; // Everyone's score
-	INT16 numboxes[MAXPLAYERS];
-	INT16 totalring[MAXPLAYERS];
-	tic_t realtime[MAXPLAYERS];
-	UINT8 laps[MAXPLAYERS];
-} ATTRPACK resynchend_pak;
-
-typedef struct
-{
-	// Player stuff
-	UINT8 playernum;
-
-	// Do not send anything visual related.
-	// Only send data that we need to know for physics.
-	UINT8 playerstate; // playerstate_t
-	UINT32 pflags; // pflags_t
-	UINT8 panim; // panim_t
-
-	INT16 angleturn;
-	INT16 oldrelangleturn;
-
-	angle_t aiming;
-	INT32 currentweapon;
-	INT32 ringweapons;
-	UINT16 ammoremoval;
-	tic_t ammoremovaltimer;
-	INT32 ammoremovalweapon;
-	UINT16 powers[NUMPOWERS];
-
-	// Score is resynched in the confirm resync packet
-	INT16 rings;
-	INT16 spheres;
-	SINT8 lives;
-	SINT8 continues;
-	UINT8 scoreadd;
-	SINT8 xtralife;
-	SINT8 pity;
-
-	UINT16 skincolor;
-	INT32 skin;
-	UINT32 availabilities;
-	// Just in case Lua does something like
-	// modify these at runtime
-	fixed_t camerascale;
-	fixed_t shieldscale;
-	fixed_t normalspeed;
-	fixed_t runspeed;
-	UINT8 thrustfactor;
-	UINT8 accelstart;
-	UINT8 acceleration;
-	UINT8 charability;
-	UINT8 charability2;
-	UINT32 charflags;
-	UINT32 thokitem; // mobjtype_t
-	UINT32 spinitem; // mobjtype_t
-	UINT32 revitem; // mobjtype_t
-	UINT32 followitem; // mobjtype_t
-	fixed_t actionspd;
-	fixed_t mindash;
-	fixed_t maxdash;
-	fixed_t jumpfactor;
-	fixed_t playerheight;
-	fixed_t playerspinheight;
-
-	fixed_t speed;
-	UINT8 secondjump;
-	UINT8 fly1;
-	tic_t glidetime;
-	UINT8 climbing;
-	INT32 deadtimer;
-	tic_t exiting;
-	UINT8 homing;
-	tic_t dashmode;
-	tic_t skidtime;
-	fixed_t cmomx;
-	fixed_t cmomy;
-	fixed_t rmomx;
-	fixed_t rmomy;
-
-	INT32 weapondelay;
-	INT32 tossdelay;
-
-	INT16 starpostx;
-	INT16 starposty;
-	INT16 starpostz;
-	INT32 starpostnum;
-	tic_t starposttime;
-	angle_t starpostangle;
-	fixed_t starpostscale;
-
-	INT32 maxlink;
-	fixed_t dashspeed;
-	angle_t angle_pos;
-	angle_t old_angle_pos;
-	tic_t bumpertime;
-	INT32 flyangle;
-	tic_t drilltimer;
-	INT32 linkcount;
-	tic_t linktimer;
-	INT32 anotherflyangle;
-	tic_t nightstime;
-	INT32 drillmeter;
-	UINT8 drilldelay;
-	UINT8 bonustime;
-	UINT8 mare;
-	INT16 lastsidehit, lastlinehit;
-
-	tic_t losstime;
-	UINT8 timeshit;
-	INT32 onconveyor;
-
-	//player->mo stuff
-	UINT8 hasmo; // Boolean
-
-	INT32 health;
-	angle_t angle;
-	angle_t rollangle;
-	fixed_t x;
-	fixed_t y;
-	fixed_t z;
-	fixed_t momx;
-	fixed_t momy;
-	fixed_t momz;
-	fixed_t friction;
-	fixed_t movefactor;
-
-	spritenum_t sprite;
-	UINT32 frame;
-	UINT8 sprite2;
-	UINT16 anim_duration;
-	INT32 tics;
-	statenum_t statenum;
-	UINT32 flags;
-	UINT32 flags2;
-	UINT16 eflags;
-
-	fixed_t radius;
-	fixed_t height;
-	fixed_t scale;
-	fixed_t destscale;
-	fixed_t scalespeed;
-} ATTRPACK resynch_pak;
-
 typedef struct
 {
 	UINT8 version; // Different versions don't work
@@ -314,18 +152,10 @@ typedef struct
 	UINT8 clientnode;
 	UINT8 gamestate;
 
-	// 0xFF == not in game; else player skin num
-	UINT8 playerskins[MAXPLAYERS];
-	UINT16 playercolor[MAXPLAYERS];
-	UINT32 playeravailabilities[MAXPLAYERS];
-
 	UINT8 gametype;
 	UINT8 modifiedgame;
-	SINT8 adminplayers[MAXPLAYERS]; // Needs to be signed
 
 	char server_context[8]; // Unique context id, generated at server startup.
-
-	UINT8 varlengthinputs[0]; // Playernames and netvars
 } ATTRPACK serverconfig_pak;
 
 typedef struct
@@ -462,9 +292,6 @@ typedef struct
 		client2cmd_pak client2pak;          //         200 bytes
 		servertics_pak serverpak;           //      132495 bytes (more around 360, no?)
 		serverconfig_pak servercfg;         //         773 bytes
-		resynchend_pak resynchend;          //
-		resynch_pak resynchpak;             //
-		UINT8 resynchgot;                   //
 		UINT8 textcmd[MAXTEXTCMD+1];        //       66049 bytes (wut??? 64k??? More like 257 bytes...)
 		filetx_pak filetxpak;               //         139 bytes
 		fileack_pak fileack;
@@ -606,7 +433,7 @@ UINT8 GetFreeXCmdSize(void);
 
 void D_MD5PasswordPass(const UINT8 *buffer, size_t len, const char *salt, void *dest);
 
-extern UINT8 hu_resynching;
+extern UINT8 hu_redownloadinggamestate;
 
 extern UINT8 adminpassmd5[16];
 extern boolean adminpasswordset;
diff --git a/src/d_main.c b/src/d_main.c
index ce1331fe3abf12eb1f0a9dfb01d01777ecaf7959..53798d4466ac56ef9e083e50fe78074541e242b3 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -107,8 +107,6 @@ boolean devparm = false; // started game with -devparm
 boolean singletics = false; // timedemo
 boolean lastdraw = false;
 
-static void D_CheckRendererState(void);
-
 postimg_t postimgtype = postimg_none;
 INT32 postimgparam;
 postimg_t postimgtype2 = postimg_none;
@@ -237,7 +235,6 @@ INT16 wipetypepost = -1;
 
 static void D_Display(void)
 {
-	INT32 setrenderstillneeded = 0;
 	boolean forcerefresh = false;
 	static boolean wipe = false;
 	INT32 wipedefindex = 0;
@@ -260,48 +257,28 @@ static void D_Display(void)
 	//    create plane polygons, if necessary.
 	// 3. Functions related to switching video
 	//    modes (resolution) are called.
-	// 4. Patch data is freed from memory,
-	//    and recached if necessary.
-	// 5. The frame is ready to be drawn!
-
-	// stop movie if needs to change renderer
-	if (setrenderneeded && (moviemode == MM_APNG))
-		M_StopMovie();
+	// 4. The frame is ready to be drawn!
 
-	// check for change of renderer or screen size (video mode)
+	// Check for change of renderer or screen size (video mode)
 	if ((setrenderneeded || setmodeneeded) && !wipe)
-	{
-		if (setrenderneeded)
-		{
-			CONS_Debug(DBG_RENDER, "setrenderneeded set (%d)\n", setrenderneeded);
-			setrenderstillneeded = setrenderneeded;
-		}
 		SCR_SetMode(); // change video mode
-	}
 
-	if (vid.recalc || setrenderstillneeded)
-	{
+	// Recalc the screen
+	if (vid.recalc)
 		SCR_Recalc(); // NOTE! setsizeneeded is set by SCR_Recalc()
-#ifdef HWRENDER
-		// Shoot! The screen texture was flushed!
-		if ((rendermode == render_opengl) && (gamestate == GS_INTERMISSION))
-			usebuffer = false;
-#endif
-	}
 
+	// View morph
 	if (rendermode == render_soft && !splitscreen)
 		R_CheckViewMorph();
 
-	// change the view size if needed
-	if (setsizeneeded || setrenderstillneeded)
+	// Change the view size if needed
+	// Set by changing video mode or renderer
+	if (setsizeneeded)
 	{
 		R_ExecuteSetViewSize();
 		forcerefresh = true; // force background redraw
 	}
 
-	// Lactozilla: Renderer switching
-	D_CheckRendererState();
-
 	// draw buffered stuff to screen
 	// Used only by linux GGI version
 	I_UpdateNoBlit();
@@ -642,26 +619,6 @@ static void D_Display(void)
 		I_FinishUpdate(); // page flip or blit buffer
 		ps_swaptime = I_GetTimeMicros() - ps_swaptime;
 	}
-
-	needpatchflush = false;
-	needpatchrecache = false;
-}
-
-// Check the renderer's state
-// after a possible renderer switch.
-void D_CheckRendererState(void)
-{
-	// flush all patches from memory
-	if (needpatchflush)
-	{
-		Z_FlushCachedPatches();
-		needpatchflush = false;
-	}
-
-	// some patches have been freed,
-	// so cache them again
-	if (needpatchrecache)
-		R_ReloadHUDGraphics();
 }
 
 // =========================================================================
@@ -688,6 +645,7 @@ void D_SRB2Loop(void)
 	oldentertics = I_GetTime();
 
 	// end of loading screen: CONS_Printf() will no more call FinishUpdate()
+	con_refresh = false;
 	con_startup = false;
 
 	// make sure to do a d_display to init mode _before_ load a level
@@ -1338,22 +1296,17 @@ void D_SRB2Main(void)
 	// set user default mode or mode set at cmdline
 	SCR_CheckDefaultMode();
 
-	// Lactozilla: Does the render mode need to change?
-	if ((setrenderneeded != 0) && (setrenderneeded != rendermode))
+	// Lactozilla: Check if the render mode needs to change.
+	if (setrenderneeded)
 	{
 		CONS_Printf(M_GetText("Switching the renderer...\n"));
-		Z_PreparePatchFlush();
 
-		// set needpatchflush / needpatchrecache true for D_CheckRendererState
-		needpatchflush = true;
-		needpatchrecache = true;
+		// Switch the renderer in the interface
+		if (VID_CheckRenderer())
+			con_refresh = true; // Allow explicit screen refresh again
 
 		// Set cv_renderer to the new render mode
-		VID_CheckRenderer();
-		SCR_ChangeRendererCVars(rendermode);
-
-		// check the renderer's state
-		D_CheckRendererState();
+		CV_StealthSetValue(&cv_renderer, rendermode);
 	}
 
 	wipegamestate = gamestate;
@@ -1598,7 +1551,7 @@ void D_SRB2Main(void)
 	{
 		levelstarttic = gametic;
 		G_SetGamestate(GS_LEVEL);
-		if (!P_LoadLevel(false))
+		if (!P_LoadLevel(false, false))
 			I_Quit(); // fail so reset game stuff
 	}
 }
diff --git a/src/d_net.c b/src/d_net.c
index 2823ce2191ebcde53dc1c2a21d1a8ff9e03bef08..d534b1b081360da6f7274f33e14cb178c1d2f632 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -798,8 +798,9 @@ static const char *packettypename[NUMPACKETTYPE] =
 	"REQUESTFILE",
 	"ASKINFOVIAMS",
 
-	"RESYNCHEND",
-	"RESYNCHGET",
+	"WILLRESENDGAMESTATE",
+	"CANRECEIVEGAMESTATE",
+	"RECEIVEDGAMESTATE",
 
 	"SENDINGLUAFILE",
 	"ASKLUAFILE",
@@ -813,7 +814,6 @@ static const char *packettypename[NUMPACKETTYPE] =
 	"TEXTCMD2",
 	"CLIENTJOIN",
 	"NODETIMEOUT",
-	"RESYNCHING",
 	"LOGIN",
 	"PING"
 };
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 87abd596a1520b1b6088eacb476af290abcefa84..31c10f58a8e0dbe7f709c0ffd98e63772fe175d6 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -374,6 +374,7 @@ consvar_t cv_sleep = CVAR_INIT ("cpusleep", "1", CV_SAVE, sleeping_cons_t, NULL)
 static CV_PossibleValue_t perfstats_cons_t[] = {
 	{0, "Off"}, {1, "Rendering"}, {2, "Logic"}, {3, "ThinkFrame"}, {0, NULL}};
 consvar_t cv_perfstats = CVAR_INIT ("perfstats", "Off", 0, perfstats_cons_t, NULL);
+consvar_t cv_freedemocamera = CVAR_INIT("freedemocamera", "Off", CV_SAVE, CV_OnOff, NULL);
 
 char timedemo_name[256];
 boolean timedemo_csv;
@@ -675,10 +676,6 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_gif_dynamicdelay);
 	CV_RegisterVar(&cv_gif_localcolortable);
 
-#ifdef WALLSPLATS
-	CV_RegisterVar(&cv_splats);
-#endif
-
 	// register these so it is saved to config
 	CV_RegisterVar(&cv_playername);
 	CV_RegisterVar(&cv_playercolor);
@@ -880,6 +877,8 @@ void D_RegisterClientCommands(void)
 //	CV_RegisterVar(&cv_grid);
 //	CV_RegisterVar(&cv_snapto);
 
+	CV_RegisterVar(&cv_freedemocamera);
+	
 	// add cheat commands
 	COM_AddCommand("noclip", Command_CheatNoClip_f);
 	COM_AddCommand("god", Command_CheatGod_f);
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index 841f71acd6692341af29928b117b36699e295080..98d8f142576e46d18dd84a2820a1812b3289d7d6 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -75,9 +75,6 @@ extern consvar_t cv_teamscramble;
 extern consvar_t cv_scrambleonchange;
 
 extern consvar_t cv_netstat;
-#ifdef WALLSPLATS
-extern consvar_t cv_splats;
-#endif
 
 extern consvar_t cv_countdowntime;
 extern consvar_t cv_runscripts;
@@ -121,6 +118,8 @@ extern boolean timedemo_csv;
 extern char timedemo_csv_id[256];
 extern boolean timedemo_quit;
 
+extern consvar_t cv_freedemocamera;
+
 typedef enum
 {
 	XD_NAMEANDCOLOR = 1,
diff --git a/src/dehacked.c b/src/dehacked.c
index 0380cc30eb86a5d6a543e02227fefb98c5259592..eb8951569600628c05a03333b2b15b9de09fa40c 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -26,11 +26,13 @@
 #include "dehacked.h"
 #include "st_stuff.h"
 #include "i_system.h"
+#include "i_sound.h" // musictype_t (for lua)
 #include "p_local.h" // for var1 and var2, and some constants
 #include "p_setup.h"
 #include "r_data.h"
 #include "r_textures.h"
 #include "r_draw.h"
+#include "r_patch.h"
 #include "r_picformats.h"
 #include "r_things.h" // R_Char2Frame
 #include "r_sky.h"
@@ -1040,11 +1042,6 @@ static void readspriteinfo(MYFILE *f, INT32 num, boolean sprite2)
 	spriteinfo_t *info = Z_Calloc(sizeof(spriteinfo_t), PU_STATIC, NULL);
 	info->available = true;
 
-#ifdef ROTSPRITE
-	if ((sprites != NULL) && (!sprite2))
-		R_FreeSingleRotSprite(&sprites[num]);
-#endif
-
 	do
 	{
 		lastline = f->curpos;
@@ -1173,9 +1170,6 @@ static void readspriteinfo(MYFILE *f, INT32 num, boolean sprite2)
 						size_t skinnum = skinnumbers[i];
 						skin_t *skin = &skins[skinnum];
 						spriteinfo_t *sprinfo = skin->sprinfo;
-#ifdef ROTSPRITE
-						R_FreeSkinRotSprite(skinnum);
-#endif
 						M_Memcpy(&sprinfo[num], info, sizeof(spriteinfo_t));
 					}
 				}
@@ -9044,6 +9038,7 @@ static const char *const MOBJFLAG2_LIST[] = {
 	"AMBUSH",         // Alternate behaviour typically set by MTF_AMBUSH
 	"LINKDRAW",       // Draw vissprite of mobj immediately before/after tracer's vissprite (dependent on dispoffset and position)
 	"SHIELD",         // Thinker calls P_AddShield/P_ShieldLook (must be partnered with MF_SCENERY to use)
+	"SPLAT",          // Object is a splat
 	NULL
 };
 
@@ -9588,6 +9583,36 @@ struct {
 	{"tr_trans90",tr_trans90},
 	{"NUMTRANSMAPS",NUMTRANSMAPS},
 
+	// Alpha styles (blend modes)
+	{"AST_COPY",AST_COPY},
+	{"AST_TRANSLUCENT",AST_TRANSLUCENT},
+	{"AST_ADD",AST_ADD},
+	{"AST_SUBTRACT",AST_SUBTRACT},
+	{"AST_REVERSESUBTRACT",AST_REVERSESUBTRACT},
+	{"AST_MODULATE",AST_MODULATE},
+	{"AST_OVERLAY",AST_OVERLAY},
+
+	// Render flags
+	{"RF_HORIZONTALFLIP",RF_HORIZONTALFLIP},
+	{"RF_VERTICALFLIP",RF_VERTICALFLIP},
+	{"RF_ABSOLUTEOFFSETS",RF_ABSOLUTEOFFSETS},
+	{"RF_FLIPOFFSETS",RF_FLIPOFFSETS},
+	{"RF_SPLATMASK",RF_SLOPESPLAT},
+	{"RF_SLOPESPLAT",RF_SLOPESPLAT},
+	{"RF_OBJECTSLOPESPLAT",RF_OBJECTSLOPESPLAT},
+	{"RF_NOSPLATBILLBOARD",RF_NOSPLATBILLBOARD},
+	{"RF_NOSPLATROLLANGLE",RF_NOSPLATROLLANGLE},
+	{"RF_BLENDMASK",RF_BLENDMASK},
+	{"RF_FULLBRIGHT",RF_FULLBRIGHT},
+	{"RF_FULLDARK",RF_FULLDARK},
+	{"RF_NOCOLORMAPS",RF_NOCOLORMAPS},
+	{"RF_SPRITETYPEMASK",RF_SPRITETYPEMASK},
+	{"RF_PAPERSPRITE",RF_PAPERSPRITE},
+	{"RF_FLOORSPRITE",RF_FLOORSPRITE},
+	{"RF_SHADOWDRAW",RF_SHADOWDRAW},
+	{"RF_SHADOWEFFECTS",RF_SHADOWEFFECTS},
+	{"RF_DROPSHADOW",RF_DROPSHADOW},
+
 	// Level flags
 	{"LF_SCRIPTISFILE",LF_SCRIPTISFILE},
 	{"LF_SPEEDMUSIC",LF_SPEEDMUSIC},
@@ -10108,6 +10133,18 @@ struct {
 	{"MA_NOCUTSCENES",MA_NOCUTSCENES},
 	{"MA_INGAME",MA_INGAME},
 
+	// music types
+	{"MU_NONE", MU_NONE},
+	{"MU_WAV", MU_WAV},
+	{"MU_MOD", MU_MOD},
+	{"MU_MID", MU_MID},
+	{"MU_OGG", MU_OGG},
+	{"MU_MP3", MU_MP3},
+	{"MU_FLAC", MU_FLAC},
+	{"MU_GME", MU_GME},
+	{"MU_MOD_EX", MU_MOD_EX},
+	{"MU_MID_EX", MU_MID_EX},
+
 	// gamestates
 	{"GS_NULL",GS_NULL},
 	{"GS_LEVEL",GS_LEVEL},
diff --git a/src/doomdata.h b/src/doomdata.h
index f3890c846536dc438573104f98398bace13f7415..b3f7f5c4dbcc4ea5ce9ee8ff1cdf34a1845cdafa 100644
--- a/src/doomdata.h
+++ b/src/doomdata.h
@@ -23,6 +23,7 @@
 // Some global defines, that configure the game.
 #include "doomdef.h"
 
+#include "taglist.h"
 #include "m_fixed.h" // See the mapthing_t scale.
 
 //
@@ -208,11 +209,10 @@ typedef struct
 	UINT16 options;
 	INT16 z;
 	UINT8 extrainfo;
+	taglist_t tags;
 	fixed_t scale;
-	INT16 tag;
 	INT32 args[NUMMAPTHINGARGS];
 	char *stringargs[NUMMAPTHINGSTRINGARGS];
-
 	struct mobj_s *mobj;
 } mapthing_t;
 
diff --git a/src/doomdef.h b/src/doomdef.h
index b9ee1ce5f59cb7bd3c66beab532256533550687b..d0b7ea0c2391334c703051d02e0dae693dfdfe19 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -628,9 +628,6 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 /// \note   Required for proper collision with moving sloped surfaces that have sector specials on them.
 #define SECTORSPECIALSAFTERTHINK
 
-/// Cache patches in Lua in a way that renderer switching will work flawlessly.
-//#define LUA_PATCH_SAFETY
-
 /// Sprite rotation
 #define ROTSPRITE
 #define ROTANGLES 72 // Needs to be a divisor of 360 (45, 60, 90, 120...)
diff --git a/src/dummy/i_video.c b/src/dummy/i_video.c
index 2b0478220eac8119ec36dff4b0b8a05cf92158fc..3b0a12a328df587e1cd20d0312cf96ce6c8df847 100644
--- a/src/dummy/i_video.c
+++ b/src/dummy/i_video.c
@@ -3,6 +3,7 @@
 #include "../i_video.h"
 
 rendermode_t rendermode = render_none;
+rendermode_t chosenrendermode = render_none;
 
 boolean highcolor = false;
 
@@ -40,8 +41,15 @@ INT32 VID_SetMode(INT32 modenum)
 	return 0;
 }
 
-void VID_CheckRenderer(void) {}
-void VID_CheckGLLoaded(rendermode_t oldrender) {}
+boolean VID_CheckRenderer(void)
+{
+	return false;
+}
+
+void VID_CheckGLLoaded(rendermode_t oldrender)
+{
+	(void)oldrender;
+}
 
 const char *VID_GetModeName(INT32 modenum)
 {
diff --git a/src/f_finale.c b/src/f_finale.c
index d7f81b9df0e7f3f4f31d56e12d84368a457b149b..268bc79f5fd99e685bdf12b75b2e94ff31cab3c2 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -533,78 +533,78 @@ static void F_IntroDrawScene(void)
 			bgxoffs = 28;
 			break;
 		case 1:
-			background = W_CachePatchName("INTRO1", PU_PATCH);
+			background = W_CachePatchName("INTRO1", PU_PATCH_LOWPRIORITY);
 			break;
 		case 2:
-			background = W_CachePatchName("INTRO2", PU_PATCH);
+			background = W_CachePatchName("INTRO2", PU_PATCH_LOWPRIORITY);
 			break;
 		case 3:
-			background = W_CachePatchName("INTRO3", PU_PATCH);
+			background = W_CachePatchName("INTRO3", PU_PATCH_LOWPRIORITY);
 			break;
 		case 4:
-			background = W_CachePatchName("INTRO4", PU_PATCH);
+			background = W_CachePatchName("INTRO4", PU_PATCH_LOWPRIORITY);
 			break;
 		case 5:
 			if (intro_curtime >= 5*TICRATE)
-				background = W_CachePatchName("RADAR", PU_PATCH);
+				background = W_CachePatchName("RADAR", PU_PATCH_LOWPRIORITY);
 			else
-				background = W_CachePatchName("DRAT", PU_PATCH);
+				background = W_CachePatchName("DRAT", PU_PATCH_LOWPRIORITY);
 			break;
 		case 6:
-			background = W_CachePatchName("INTRO6", PU_PATCH);
+			background = W_CachePatchName("INTRO6", PU_PATCH_LOWPRIORITY);
 			cx = 180;
 			cy = 8;
 			break;
 		case 7:
 		{
 			if (intro_curtime >= 7*TICRATE + ((TICRATE/7)*2))
-				background = W_CachePatchName("SGRASS5", PU_PATCH);
+				background = W_CachePatchName("SGRASS5", PU_PATCH_LOWPRIORITY);
 			else if (intro_curtime >= 7*TICRATE + (TICRATE/7))
-				background = W_CachePatchName("SGRASS4", PU_PATCH);
+				background = W_CachePatchName("SGRASS4", PU_PATCH_LOWPRIORITY);
 			else if (intro_curtime >= 7*TICRATE)
-				background = W_CachePatchName("SGRASS3", PU_PATCH);
+				background = W_CachePatchName("SGRASS3", PU_PATCH_LOWPRIORITY);
 			else if (intro_curtime >= 6*TICRATE)
-				background = W_CachePatchName("SGRASS2", PU_PATCH);
+				background = W_CachePatchName("SGRASS2", PU_PATCH_LOWPRIORITY);
 			else
-				background = W_CachePatchName("SGRASS1", PU_PATCH);
+				background = W_CachePatchName("SGRASS1", PU_PATCH_LOWPRIORITY);
 			break;
 		}
 		case 8:
-			background = W_CachePatchName("WATCHING", PU_PATCH);
+			background = W_CachePatchName("WATCHING", PU_PATCH_LOWPRIORITY);
 			break;
 		case 9:
-			background = W_CachePatchName("ZOOMING", PU_PATCH);
+			background = W_CachePatchName("ZOOMING", PU_PATCH_LOWPRIORITY);
 			break;
 		case 10:
 			break;
 		case 11:
-			background = W_CachePatchName("INTRO5", PU_PATCH);
+			background = W_CachePatchName("INTRO5", PU_PATCH_LOWPRIORITY);
 			break;
 		case 12:
-			background = W_CachePatchName("REVENGE", PU_PATCH);
+			background = W_CachePatchName("REVENGE", PU_PATCH_LOWPRIORITY);
 			cx = 208;
 			cy = 8;
 			break;
 		case 13:
-			background = W_CachePatchName("CONFRONT", PU_PATCH);
+			background = W_CachePatchName("CONFRONT", PU_PATCH_LOWPRIORITY);
 			cy += 48;
 			break;
 		case 14:
-			background = W_CachePatchName("TAILSSAD", PU_PATCH);
+			background = W_CachePatchName("TAILSSAD", PU_PATCH_LOWPRIORITY);
 			bgxoffs = 144;
 			cx = 8;
 			cy = 8;
 			break;
 		case 15:
 			if (intro_curtime >= 7*TICRATE)
-				background = W_CachePatchName("SONICDO2", PU_PATCH);
+				background = W_CachePatchName("SONICDO2", PU_PATCH_LOWPRIORITY);
 			else
-				background = W_CachePatchName("SONICDO1", PU_PATCH);
+				background = W_CachePatchName("SONICDO1", PU_PATCH_LOWPRIORITY);
 			cx = 224;
 			cy = 8;
 			break;
 		case 16:
-			background = W_CachePatchName("INTRO7", PU_PATCH);
+			background = W_CachePatchName("INTRO7", PU_PATCH_LOWPRIORITY);
 			break;
 		default:
 			break;
@@ -635,7 +635,7 @@ static void F_IntroDrawScene(void)
 				strncpy(stjrintro, "STJRI029", 9);
 				S_ChangeMusicInternal("_stjr", false);
 
-				background = W_CachePatchName(stjrintro, PU_PATCH);
+				background = W_CachePatchName(stjrintro, PU_PATCH_LOWPRIORITY);
 				wipestyleflags = WSF_FADEIN;
 				F_WipeStartScreen();
 				F_TryColormapFade(31);
@@ -646,7 +646,7 @@ static void F_IntroDrawScene(void)
 
 			if (!WipeInAction) // Draw the patch if not in a wipe
 			{
-				background = W_CachePatchName(stjrintro, PU_PATCH);
+				background = W_CachePatchName(stjrintro, PU_PATCH_LOWPRIORITY);
 				V_DrawSmallScaledPatch(bgxoffs, 84, 0, background);
 			}
 		}
@@ -656,27 +656,27 @@ static void F_IntroDrawScene(void)
 		if (timetonext > 5*TICRATE && timetonext < 6*TICRATE)
 		{
 			if (!(finalecount & 3))
-				background = W_CachePatchName("BRITEGG1", PU_PATCH);
+				background = W_CachePatchName("BRITEGG1", PU_PATCH_LOWPRIORITY);
 			else
-				background = W_CachePatchName("DARKEGG1", PU_PATCH);
+				background = W_CachePatchName("DARKEGG1", PU_PATCH_LOWPRIORITY);
 
 			V_DrawSmallScaledPatch(0, 0, 0, background);
 		}
 		else if (timetonext > 3*TICRATE && timetonext < 4*TICRATE)
 		{
 			if (!(finalecount & 3))
-				background = W_CachePatchName("BRITEGG2", PU_PATCH);
+				background = W_CachePatchName("BRITEGG2", PU_PATCH_LOWPRIORITY);
 			else
-				background = W_CachePatchName("DARKEGG2", PU_PATCH);
+				background = W_CachePatchName("DARKEGG2", PU_PATCH_LOWPRIORITY);
 
 			V_DrawSmallScaledPatch(0, 0, 0, background);
 		}
 		else if (timetonext > 1*TICRATE && timetonext < 2*TICRATE)
 		{
 			if (!(finalecount & 3))
-				background = W_CachePatchName("BRITEGG3", PU_PATCH);
+				background = W_CachePatchName("BRITEGG3", PU_PATCH_LOWPRIORITY);
 			else
-				background = W_CachePatchName("DARKEGG3", PU_PATCH);
+				background = W_CachePatchName("DARKEGG3", PU_PATCH_LOWPRIORITY);
 
 			V_DrawSmallScaledPatch(0, 0, 0, background);
 		}
@@ -708,79 +708,79 @@ static void F_IntroDrawScene(void)
 			knucklesx += sonicx;
 			sonicx += P_ReturnThrustX(NULL, finalecount * ANG10, 3);
 
-			V_DrawSmallScaledPatch(skyx, 0, 0, (patch = W_CachePatchName("INTROSKY", PU_PATCH)));
+			V_DrawSmallScaledPatch(skyx, 0, 0, (patch = W_CachePatchName("INTROSKY", PU_PATCH_LOWPRIORITY)));
 			V_DrawSmallScaledPatch(skyx - 320, 0, 0, patch);
 			W_UnlockCachedPatch(patch);
-			V_DrawSmallScaledPatch(grassx, 0, 0, (patch = W_CachePatchName("INTROGRS", PU_PATCH)));
+			V_DrawSmallScaledPatch(grassx, 0, 0, (patch = W_CachePatchName("INTROGRS", PU_PATCH_LOWPRIORITY)));
 			V_DrawSmallScaledPatch(grassx - 320, 0, 0, patch);
 			W_UnlockCachedPatch(patch);
 
 			if (finalecount & 1)
 			{
 				// Sonic
-				V_DrawSmallScaledPatch(sonicx, 54, 0, (patch = W_CachePatchName("RUN2", PU_PATCH)));
+				V_DrawSmallScaledPatch(sonicx, 54, 0, (patch = W_CachePatchName("RUN2", PU_PATCH_LOWPRIORITY)));
 				W_UnlockCachedPatch(patch);
 
 				// Appendages
 				if (finalecount & 2)
 				{
 					// Sonic's feet
-					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT4", PU_PATCH)));
+					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT4", PU_PATCH_LOWPRIORITY)));
 					W_UnlockCachedPatch(patch);
 					// Tails' tails
-					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP2", PU_PATCH)));
+					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP2", PU_PATCH_LOWPRIORITY)));
 					W_UnlockCachedPatch(patch);
 				}
 				else
 				{
 					// Sonic's feet
-					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT2", PU_PATCH)));
+					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT2", PU_PATCH_LOWPRIORITY)));
 					W_UnlockCachedPatch(patch);
 					// Tails' tails
-					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP1", PU_PATCH)));
+					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP1", PU_PATCH_LOWPRIORITY)));
 					W_UnlockCachedPatch(patch);
 				}
 
 				// Tails
-				V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("FLY2", PU_PATCH)));
+				V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("FLY2", PU_PATCH_LOWPRIORITY)));
 				W_UnlockCachedPatch(patch);
 
 				// Knuckles
-				V_DrawSmallScaledPatch(knucklesx, knucklesy, 0, (patch = W_CachePatchName("GLIDE2", PU_PATCH)));
+				V_DrawSmallScaledPatch(knucklesx, knucklesy, 0, (patch = W_CachePatchName("GLIDE2", PU_PATCH_LOWPRIORITY)));
 				W_UnlockCachedPatch(patch);
 			}
 			else
 			{
 				// Sonic
-				V_DrawSmallScaledPatch(sonicx, 54, 0, (patch = W_CachePatchName("RUN1", PU_PATCH)));
+				V_DrawSmallScaledPatch(sonicx, 54, 0, (patch = W_CachePatchName("RUN1", PU_PATCH_LOWPRIORITY)));
 				W_UnlockCachedPatch(patch);
 
 				// Appendages
 				if (finalecount & 2)
 				{
 					// Sonic's feet
-					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT3", PU_PATCH)));
+					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT3", PU_PATCH_LOWPRIORITY)));
 					W_UnlockCachedPatch(patch);
 					// Tails' tails
-					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP2", PU_PATCH)));
+					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP2", PU_PATCH_LOWPRIORITY)));
 					W_UnlockCachedPatch(patch);
 				}
 				else
 				{
 					// Sonic's feet
-					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT1", PU_PATCH)));
+					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT1", PU_PATCH_LOWPRIORITY)));
 					W_UnlockCachedPatch(patch);
 					// Tails' tails
-					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP1", PU_PATCH)));
+					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP1", PU_PATCH_LOWPRIORITY)));
 					W_UnlockCachedPatch(patch);
 				}
 
 				// Tails
-				V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("FLY1", PU_PATCH)));
+				V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("FLY1", PU_PATCH_LOWPRIORITY)));
 				W_UnlockCachedPatch(patch);
 
 				// Knuckles
-				V_DrawSmallScaledPatch(knucklesx, knucklesy, 0, (patch = W_CachePatchName("GLIDE1", PU_PATCH)));
+				V_DrawSmallScaledPatch(knucklesx, knucklesy, 0, (patch = W_CachePatchName("GLIDE1", PU_PATCH_LOWPRIORITY)));
 				W_UnlockCachedPatch(patch);
 			}
 
@@ -813,8 +813,8 @@ static void F_IntroDrawScene(void)
 				y += (30*(FRACUNIT-scale));
 			}
 
-			rockpat = W_CachePatchName(va("ROID00%.2d", 34 - (worktics % 35)), PU_PATCH);
-			glow = W_CachePatchName(va("ENDGLOW%.1d", 2+(worktics & 1)), PU_PATCH);
+			rockpat = W_CachePatchName(va("ROID00%.2d", 34 - (worktics % 35)), PU_PATCH_LOWPRIORITY);
+			glow = W_CachePatchName(va("ENDGLOW%.1d", 2+(worktics & 1)), PU_PATCH_LOWPRIORITY);
 
 			if (worktics >= 5)
 				trans = (worktics-5)>>1;
@@ -934,7 +934,7 @@ void F_IntroDrawer(void)
 	{
 		if (intro_scenenum == 5 && intro_curtime == 5*TICRATE)
 		{
-			patch_t *radar = W_CachePatchName("RADAR", PU_PATCH);
+			patch_t *radar = W_CachePatchName("RADAR", PU_PATCH_LOWPRIORITY);
 
 			F_WipeStartScreen();
 			F_WipeColorFill(31);
@@ -947,7 +947,7 @@ void F_IntroDrawer(void)
 		}
 		else if (intro_scenenum == 7 && intro_curtime == 6*TICRATE) // Force a wipe here
 		{
-			patch_t *grass = W_CachePatchName("SGRASS2", PU_PATCH);
+			patch_t *grass = W_CachePatchName("SGRASS2", PU_PATCH_LOWPRIORITY);
 
 			F_WipeStartScreen();
 			F_WipeColorFill(31);
@@ -960,7 +960,7 @@ void F_IntroDrawer(void)
 		}
 		/*else if (intro_scenenum == 11 && intro_curtime == 7*TICRATE)
 		{
-			patch_t *confront = W_CachePatchName("CONFRONT", PU_PATCH);
+			patch_t *confront = W_CachePatchName("CONFRONT", PU_PATCH_LOWPRIORITY);
 
 			F_WipeStartScreen();
 			F_WipeColorFill(31);
@@ -973,7 +973,7 @@ void F_IntroDrawer(void)
 		}*/
 		if (intro_scenenum == 15 && intro_curtime == 7*TICRATE)
 		{
-			patch_t *sdo = W_CachePatchName("SONICDO2", PU_PATCH);
+			patch_t *sdo = W_CachePatchName("SONICDO2", PU_PATCH_LOWPRIORITY);
 
 			F_WipeStartScreen();
 			F_WipeColorFill(31);
@@ -1303,14 +1303,14 @@ void F_CreditDrawer(void)
 	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
 
 	// Zig Zagz
-	V_DrawScaledPatch(-16,               zagpos,       V_SNAPTOLEFT,         W_CachePatchName("LTZIGZAG", PU_PATCH));
-	V_DrawScaledPatch(-16,               zagpos - 320, V_SNAPTOLEFT,         W_CachePatchName("LTZIGZAG", PU_PATCH));
-	V_DrawScaledPatch(BASEVIDWIDTH + 16, zagpos,       V_SNAPTORIGHT|V_FLIP, W_CachePatchName("LTZIGZAG", PU_PATCH));
-	V_DrawScaledPatch(BASEVIDWIDTH + 16, zagpos - 320, V_SNAPTORIGHT|V_FLIP, W_CachePatchName("LTZIGZAG", PU_PATCH));
+	V_DrawScaledPatch(-16,               zagpos,       V_SNAPTOLEFT,         W_CachePatchName("LTZIGZAG", PU_PATCH_LOWPRIORITY));
+	V_DrawScaledPatch(-16,               zagpos - 320, V_SNAPTOLEFT,         W_CachePatchName("LTZIGZAG", PU_PATCH_LOWPRIORITY));
+	V_DrawScaledPatch(BASEVIDWIDTH + 16, zagpos,       V_SNAPTORIGHT|V_FLIP, W_CachePatchName("LTZIGZAG", PU_PATCH_LOWPRIORITY));
+	V_DrawScaledPatch(BASEVIDWIDTH + 16, zagpos - 320, V_SNAPTORIGHT|V_FLIP, W_CachePatchName("LTZIGZAG", PU_PATCH_LOWPRIORITY));
 
 	// Draw background pictures first
 	for (i = 0; credits_pics[i].patch; i++)
-		V_DrawSciencePatch(credits_pics[i].x<<FRACBITS, (280<<FRACBITS) + (((i*credits_height)<<FRACBITS)/(credits_numpics)) - 4*(animtimer<<FRACBITS)/5, 0, W_CachePatchName(credits_pics[i].patch, PU_PATCH), FRACUNIT>>1);
+		V_DrawSciencePatch(credits_pics[i].x<<FRACBITS, (280<<FRACBITS) + (((i*credits_height)<<FRACBITS)/(credits_numpics)) - 4*(animtimer<<FRACBITS)/5, 0, W_CachePatchName(credits_pics[i].patch, PU_PATCH_LOWPRIORITY), FRACUNIT>>1);
 
 	// Dim the background
 	V_DrawFadeScreen(0xFF00, 16);
@@ -1519,14 +1519,14 @@ void F_GameEvaluationDrawer(void)
 
 		if (goodending)
 		{
-			rockpat = W_CachePatchName(va("ROID00%.2d", 34 - (finalecount % 35)), PU_PATCH);
-			glow = W_CachePatchName(va("ENDGLOW%.1d", 2+(finalecount & 1)), PU_PATCH);
+			rockpat = W_CachePatchName(va("ROID00%.2d", 34 - (finalecount % 35)), PU_PATCH_LOWPRIORITY);
+			glow = W_CachePatchName(va("ENDGLOW%.1d", 2+(finalecount & 1)), PU_PATCH_LOWPRIORITY);
 			x -= FRACUNIT;
 		}
 		else
 		{
-			rockpat = W_CachePatchName("ROID0000", PU_LEVEL);
-			glow = W_CachePatchName(va("ENDGLOW%.1d", (finalecount & 1)), PU_PATCH);
+			rockpat = W_CachePatchName("ROID0000", PU_PATCH_LOWPRIORITY);
+			glow = W_CachePatchName(va("ENDGLOW%.1d", (finalecount & 1)), PU_PATCH_LOWPRIORITY);
 		}
 
 		if (finalecount >= 5)
@@ -1558,20 +1558,20 @@ void F_GameEvaluationDrawer(void)
 					// if j == 0 - alternate between 0 and 1
 					//         1 -                   1 and 2
 					//         2 -                   2 and not rendered
-					V_DrawFixedPatch(x+sparkloffs[j-1][0], y+sparkloffs[j-1][1], FRACUNIT, 0, W_CachePatchName(va("ENDSPKL%.1d", (j - ((sparklloop & 1) ? 0 : 1))), PU_PATCH), R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_AQUA, GTC_CACHE));
+					V_DrawFixedPatch(x+sparkloffs[j-1][0], y+sparkloffs[j-1][1], FRACUNIT, 0, W_CachePatchName(va("ENDSPKL%.1d", (j - ((sparklloop & 1) ? 0 : 1))), PU_PATCH_LOWPRIORITY), R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_AQUA, GTC_CACHE));
 				}
 				j--;
 			}
 		}
 		else
 		{
-			patch_t *eggrock = W_CachePatchName("ENDEGRK5", PU_PATCH);
+			patch_t *eggrock = W_CachePatchName("ENDEGRK5", PU_PATCH_LOWPRIORITY);
 			V_DrawFixedPatch(x, y, scale, 0, eggrock, colormap[0]);
 			if (trans < 10)
 				V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, eggrock, colormap[1]);
 			else if (sparklloop)
 				V_DrawFixedPatch(x, y, scale, (10-sparklloop)<<V_ALPHASHIFT,
-					W_CachePatchName("ENDEGRK0", PU_PATCH), colormap[1]);
+					W_CachePatchName("ENDEGRK0", PU_PATCH_LOWPRIORITY), colormap[1]);
 		}
 	}
 
@@ -1585,7 +1585,7 @@ void F_GameEvaluationDrawer(void)
 		eemeralds_cur += (360<<FRACBITS)/7;
 
 		patchname[4] = 'A'+(char)i;
-		V_DrawFixedPatch(x, y, FRACUNIT, ((emeralds & (1<<i)) ? 0 : V_80TRANS), W_CachePatchName(patchname, PU_PATCH), NULL);
+		V_DrawFixedPatch(x, y, FRACUNIT, ((emeralds & (1<<i)) ? 0 : V_80TRANS), W_CachePatchName(patchname, PU_PATCH_LOWPRIORITY), NULL);
 	}
 
 	V_DrawCreditString((BASEVIDWIDTH - V_CreditStringWidth(endingtext))<<(FRACBITS-1), (BASEVIDHEIGHT-100)<<(FRACBITS-1), 0, endingtext);
@@ -1714,32 +1714,32 @@ void F_GameEvaluationTicker(void)
 
 static void F_CacheEnding(void)
 {
-	endbrdr[1] = W_CachePatchName("ENDBRDR1", PU_PATCH);
+	endbrdr[1] = W_CachePatchName("ENDBRDR1", PU_PATCH_LOWPRIORITY);
 
-	endegrk[0] = W_CachePatchName("ENDEGRK0", PU_PATCH);
-	endegrk[1] = W_CachePatchName("ENDEGRK1", PU_PATCH);
+	endegrk[0] = W_CachePatchName("ENDEGRK0", PU_PATCH_LOWPRIORITY);
+	endegrk[1] = W_CachePatchName("ENDEGRK1", PU_PATCH_LOWPRIORITY);
 
-	endglow[0] = W_CachePatchName("ENDGLOW0", PU_PATCH);
-	endglow[1] = W_CachePatchName("ENDGLOW1", PU_PATCH);
+	endglow[0] = W_CachePatchName("ENDGLOW0", PU_PATCH_LOWPRIORITY);
+	endglow[1] = W_CachePatchName("ENDGLOW1", PU_PATCH_LOWPRIORITY);
 
-	endbgsp[0] = W_CachePatchName("ENDBGSP0", PU_PATCH);
-	endbgsp[1] = W_CachePatchName("ENDBGSP1", PU_PATCH);
-	endbgsp[2] = W_CachePatchName("ENDBGSP2", PU_PATCH);
+	endbgsp[0] = W_CachePatchName("ENDBGSP0", PU_PATCH_LOWPRIORITY);
+	endbgsp[1] = W_CachePatchName("ENDBGSP1", PU_PATCH_LOWPRIORITY);
+	endbgsp[2] = W_CachePatchName("ENDBGSP2", PU_PATCH_LOWPRIORITY);
 
-	endspkl[0] = W_CachePatchName("ENDSPKL0", PU_PATCH);
-	endspkl[1] = W_CachePatchName("ENDSPKL1", PU_PATCH);
-	endspkl[2] = W_CachePatchName("ENDSPKL2", PU_PATCH);
+	endspkl[0] = W_CachePatchName("ENDSPKL0", PU_PATCH_LOWPRIORITY);
+	endspkl[1] = W_CachePatchName("ENDSPKL1", PU_PATCH_LOWPRIORITY);
+	endspkl[2] = W_CachePatchName("ENDSPKL2", PU_PATCH_LOWPRIORITY);
 
-	endxpld[0] = W_CachePatchName("ENDXPLD0", PU_PATCH);
-	endxpld[1] = W_CachePatchName("ENDXPLD1", PU_PATCH);
-	endxpld[2] = W_CachePatchName("ENDXPLD2", PU_PATCH);
-	endxpld[3] = W_CachePatchName("ENDXPLD3", PU_PATCH);
+	endxpld[0] = W_CachePatchName("ENDXPLD0", PU_PATCH_LOWPRIORITY);
+	endxpld[1] = W_CachePatchName("ENDXPLD1", PU_PATCH_LOWPRIORITY);
+	endxpld[2] = W_CachePatchName("ENDXPLD2", PU_PATCH_LOWPRIORITY);
+	endxpld[3] = W_CachePatchName("ENDXPLD3", PU_PATCH_LOWPRIORITY);
 
-	endescp[0] = W_CachePatchName("ENDESCP0", PU_PATCH);
-	endescp[1] = W_CachePatchName("ENDESCP1", PU_PATCH);
-	endescp[2] = W_CachePatchName("ENDESCP2", PU_PATCH);
-	endescp[3] = W_CachePatchName("ENDESCP3", PU_PATCH);
-	endescp[4] = W_CachePatchName("ENDESCP4", PU_PATCH);
+	endescp[0] = W_CachePatchName("ENDESCP0", PU_PATCH_LOWPRIORITY);
+	endescp[1] = W_CachePatchName("ENDESCP1", PU_PATCH_LOWPRIORITY);
+	endescp[2] = W_CachePatchName("ENDESCP2", PU_PATCH_LOWPRIORITY);
+	endescp[3] = W_CachePatchName("ENDESCP3", PU_PATCH_LOWPRIORITY);
+	endescp[4] = W_CachePatchName("ENDESCP4", PU_PATCH_LOWPRIORITY);
 
 	// so we only need to check once
 	if ((goodending = ALL7EMERALDS(emeralds)))
@@ -1752,41 +1752,41 @@ static void F_CacheEnding(void)
 			sprdef = &skins[skinnum].sprites[SPR2_XTRA];
 			// character head, skin specific
 			sprframe = &sprdef->spriteframes[XTRA_ENDING];
-			endfwrk[0] = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
+			endfwrk[0] = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH_LOWPRIORITY);
 			sprframe = &sprdef->spriteframes[XTRA_ENDING+1];
-			endfwrk[1] = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
+			endfwrk[1] = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH_LOWPRIORITY);
 			sprframe = &sprdef->spriteframes[XTRA_ENDING+2];
-			endfwrk[2] = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH);
+			endfwrk[2] = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH_LOWPRIORITY);
 		}
 		else // Show a star if your character doesn't have an ending firework display. (Basically the MISSINGs for this)
 		{
-			endfwrk[0] = W_CachePatchName("ENDFWRK3", PU_PATCH);
-			endfwrk[1] = W_CachePatchName("ENDFWRK4", PU_PATCH);
-			endfwrk[2] = W_CachePatchName("ENDFWRK5", PU_PATCH);
+			endfwrk[0] = W_CachePatchName("ENDFWRK3", PU_PATCH_LOWPRIORITY);
+			endfwrk[1] = W_CachePatchName("ENDFWRK4", PU_PATCH_LOWPRIORITY);
+			endfwrk[2] = W_CachePatchName("ENDFWRK5", PU_PATCH_LOWPRIORITY);
 		}
 
-		endbrdr[0] = W_CachePatchName("ENDBRDR2", PU_PATCH);
+		endbrdr[0] = W_CachePatchName("ENDBRDR2", PU_PATCH_LOWPRIORITY);
 	}
 	else
 	{
 		// eggman, skin nonspecific
-		endfwrk[0] = W_CachePatchName("ENDFWRK0", PU_PATCH);
-		endfwrk[1] = W_CachePatchName("ENDFWRK1", PU_PATCH);
-		endfwrk[2] = W_CachePatchName("ENDFWRK2", PU_PATCH);
+		endfwrk[0] = W_CachePatchName("ENDFWRK0", PU_PATCH_LOWPRIORITY);
+		endfwrk[1] = W_CachePatchName("ENDFWRK1", PU_PATCH_LOWPRIORITY);
+		endfwrk[2] = W_CachePatchName("ENDFWRK2", PU_PATCH_LOWPRIORITY);
 
-		endbrdr[0] = W_CachePatchName("ENDBRDR0", PU_LEVEL);
+		endbrdr[0] = W_CachePatchName("ENDBRDR0", PU_PATCH_LOWPRIORITY);
 	}
 }
 
 static void F_CacheGoodEnding(void)
 {
-	endegrk[0] = W_CachePatchName("ENDEGRK2", PU_PATCH);
-	endegrk[1] = W_CachePatchName("ENDEGRK3", PU_PATCH);
+	endegrk[0] = W_CachePatchName("ENDEGRK2", PU_PATCH_LOWPRIORITY);
+	endegrk[1] = W_CachePatchName("ENDEGRK3", PU_PATCH_LOWPRIORITY);
 
-	endglow[0] = W_CachePatchName("ENDGLOW2", PU_PATCH);
-	endglow[1] = W_CachePatchName("ENDGLOW3", PU_PATCH);
+	endglow[0] = W_CachePatchName("ENDGLOW2", PU_PATCH_LOWPRIORITY);
+	endglow[1] = W_CachePatchName("ENDGLOW3", PU_PATCH_LOWPRIORITY);
 
-	endxpld[0] = W_CachePatchName("ENDEGRK4", PU_PATCH);
+	endxpld[0] = W_CachePatchName("ENDEGRK4", PU_PATCH_LOWPRIORITY);
 }
 
 void F_StartEnding(void)
@@ -1843,17 +1843,10 @@ void F_EndingDrawer(void)
 	INT32 x, y, i, j, parallaxticker;
 	patch_t *rockpat;
 
-	if (needpatchrecache)
-	{
-		F_CacheEnding();
-		if (goodending && finalecount >= INFLECTIONPOINT) // time to swap some assets
-			F_CacheGoodEnding();
-	}
-
 	if (!goodending || finalecount < INFLECTIONPOINT)
-		rockpat = W_CachePatchName("ROID0000", PU_PATCH);
+		rockpat = W_CachePatchName("ROID0000", PU_PATCH_LOWPRIORITY);
 	else
-		rockpat = W_CachePatchName(va("ROID00%.2d", 34 - ((finalecount - INFLECTIONPOINT) % 35)), PU_PATCH);
+		rockpat = W_CachePatchName(va("ROID00%.2d", 34 - ((finalecount - INFLECTIONPOINT) % 35)), PU_PATCH_LOWPRIORITY);
 
 	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
 
@@ -2190,7 +2183,7 @@ void F_EndingDrawer(void)
 				eemeralds_cur[0] += (360<<FRACBITS)/7;
 
 				patchname[4] = 'A'+(char)i;
-				V_DrawFixedPatch(x, y, FRACUNIT, 0, W_CachePatchName(patchname, PU_LEVEL), NULL);
+				V_DrawFixedPatch(x, y, FRACUNIT, 0, W_CachePatchName(patchname, PU_PATCH_LOWPRIORITY), NULL);
 			}
 		} // if (goodending...
 	} // (finalecount > 20)
@@ -2337,11 +2330,11 @@ void F_SkyScroll(INT32 scrollxspeed, INT32 scrollyspeed, const char *patchname)
 
 	if (!scrollxspeed && !scrollyspeed)
 	{
-		V_DrawPatchFill(W_CachePatchName(patchname, PU_PATCH));
+		V_DrawPatchFill(W_CachePatchName(patchname, PU_PATCH_LOWPRIORITY));
 		return;
 	}
 
-	pat = W_CachePatchName(patchname, PU_PATCH);
+	pat = W_CachePatchName(patchname, PU_PATCH_LOWPRIORITY);
 
 	patwidth = SHORT(pat->width);
 	patheight = SHORT(pat->height);
@@ -2380,7 +2373,7 @@ void F_SkyScroll(INT32 scrollxspeed, INT32 scrollyspeed, const char *patchname)
 lumpnum = W_CheckNumForName(name); \
 if (lumpnum != LUMPERROR) \
 { \
-	arr[0] = W_CachePatchName(name, PU_LEVEL); \
+	arr[0] = W_CachePatchName(name, PU_PATCH_LOWPRIORITY); \
 	arr[min(1, maxf-1)] = 0; \
 } \
 else if (strlen(name) <= 6) \
@@ -2393,7 +2386,7 @@ else if (strlen(name) <= 6) \
 		lumpname[8] = 0; \
 		lumpnum = W_CheckNumForName(lumpname); \
 		if (lumpnum != LUMPERROR) \
-			arr[i] = W_CachePatchName(lumpname, PU_LEVEL); \
+			arr[i] = W_CachePatchName(lumpname, PU_PATCH_LOWPRIORITY); \
 		else \
 			break; \
 	} \
@@ -2408,21 +2401,21 @@ static void F_CacheTitleScreen(void)
 	{
 		case TTMODE_OLD:
 		case TTMODE_NONE:
-			ttbanner = W_CachePatchName("TTBANNER", PU_LEVEL);
-			ttwing = W_CachePatchName("TTWING", PU_LEVEL);
-			ttsonic = W_CachePatchName("TTSONIC", PU_LEVEL);
-			ttswave1 = W_CachePatchName("TTSWAVE1", PU_LEVEL);
-			ttswave2 = W_CachePatchName("TTSWAVE2", PU_LEVEL);
-			ttswip1 = W_CachePatchName("TTSWIP1", PU_LEVEL);
-			ttsprep1 = W_CachePatchName("TTSPREP1", PU_LEVEL);
-			ttsprep2 = W_CachePatchName("TTSPREP2", PU_LEVEL);
-			ttspop1 = W_CachePatchName("TTSPOP1", PU_LEVEL);
-			ttspop2 = W_CachePatchName("TTSPOP2", PU_LEVEL);
-			ttspop3 = W_CachePatchName("TTSPOP3", PU_LEVEL);
-			ttspop4 = W_CachePatchName("TTSPOP4", PU_LEVEL);
-			ttspop5 = W_CachePatchName("TTSPOP5", PU_LEVEL);
-			ttspop6 = W_CachePatchName("TTSPOP6", PU_LEVEL);
-			ttspop7 = W_CachePatchName("TTSPOP7", PU_LEVEL);
+			ttbanner = W_CachePatchName("TTBANNER", PU_PATCH_LOWPRIORITY);
+			ttwing = W_CachePatchName("TTWING", PU_PATCH_LOWPRIORITY);
+			ttsonic = W_CachePatchName("TTSONIC", PU_PATCH_LOWPRIORITY);
+			ttswave1 = W_CachePatchName("TTSWAVE1", PU_PATCH_LOWPRIORITY);
+			ttswave2 = W_CachePatchName("TTSWAVE2", PU_PATCH_LOWPRIORITY);
+			ttswip1 = W_CachePatchName("TTSWIP1", PU_PATCH_LOWPRIORITY);
+			ttsprep1 = W_CachePatchName("TTSPREP1", PU_PATCH_LOWPRIORITY);
+			ttsprep2 = W_CachePatchName("TTSPREP2", PU_PATCH_LOWPRIORITY);
+			ttspop1 = W_CachePatchName("TTSPOP1", PU_PATCH_LOWPRIORITY);
+			ttspop2 = W_CachePatchName("TTSPOP2", PU_PATCH_LOWPRIORITY);
+			ttspop3 = W_CachePatchName("TTSPOP3", PU_PATCH_LOWPRIORITY);
+			ttspop4 = W_CachePatchName("TTSPOP4", PU_PATCH_LOWPRIORITY);
+			ttspop5 = W_CachePatchName("TTSPOP5", PU_PATCH_LOWPRIORITY);
+			ttspop6 = W_CachePatchName("TTSPOP6", PU_PATCH_LOWPRIORITY);
+			ttspop7 = W_CachePatchName("TTSPOP7", PU_PATCH_LOWPRIORITY);
 			break;
 
 		// don't load alacroix gfx yet; we do that upon first draw.
@@ -2542,7 +2535,7 @@ void F_StartTitleScreen(void)
 
 static void F_UnloadAlacroixGraphics(SINT8 oldttscale)
 {
-	// This all gets freed by PU_LEVEL when exiting the menus.
+	// This all gets freed by PU_PATCH_LOWPRIORITY when exiting the menus.
 	// When re-visiting the menus (e.g., from exiting in-game), the gfx are force-reloaded.
 	// So leftover addresses here should not be a problem.
 
@@ -2648,17 +2641,12 @@ static void F_FigureActiveTtScale(void)
 	SINT8 newttscale = max(1, min(6, vid.dupx));
 	SINT8 oldttscale = activettscale;
 
-	if (needpatchrecache)
-		ttloaded[0] = ttloaded[1] = ttloaded[2] = ttloaded[3] = ttloaded[4] = ttloaded[5] = 0;
-	else
-	{
-		if (newttscale == testttscale)
-			return;
+	if (newttscale == testttscale)
+		return;
 
-		// We have a new ttscale, so load gfx
-		if(oldttscale > 0)
-			F_UnloadAlacroixGraphics(oldttscale);
-	}
+	// We have a new ttscale, so load gfx
+	if(oldttscale > 0)
+		F_UnloadAlacroixGraphics(oldttscale);
 
 	testttscale = newttscale;
 
@@ -2692,9 +2680,6 @@ void F_TitleScreenDrawer(void)
 	if (modeattacking)
 		return; // We likely came here from retrying. Don't do a damn thing.
 
-	if (needpatchrecache && (curttmode != TTMODE_ALACROIX))
-		F_CacheTitleScreen();
-
 	// Draw that sky!
 	if (curbgcolor >= 0)
 		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
@@ -3658,7 +3643,7 @@ void F_ContinueDrawer(void)
 	V_DrawLevelTitle(x - (V_LevelNameWidth("Continue?")>>1), 16, 0, "Continue?");
 
 	// Two stars...
-	patch = W_CachePatchName("CONTSTAR", PU_PATCH);
+	patch = W_CachePatchName("CONTSTAR", PU_PATCH_LOWPRIORITY);
 	V_DrawScaledPatch(x-32, 160, 0, patch);
 	V_DrawScaledPatch(x+32, 160, 0, patch);
 
@@ -3666,14 +3651,14 @@ void F_ContinueDrawer(void)
 	if (timeleft > 9)
 	{
 		numbuf[7] = '1';
-		V_DrawScaledPatch(x - 10, 160, 0, W_CachePatchName(numbuf, PU_PATCH));
+		V_DrawScaledPatch(x - 10, 160, 0, W_CachePatchName(numbuf, PU_PATCH_LOWPRIORITY));
 		numbuf[7] = '0';
-		V_DrawScaledPatch(x + 10, 160, 0, W_CachePatchName(numbuf, PU_PATCH));
+		V_DrawScaledPatch(x + 10, 160, 0, W_CachePatchName(numbuf, PU_PATCH_LOWPRIORITY));
 	}
 	else
 	{
 		numbuf[7] = '0'+timeleft;
-		V_DrawScaledPatch(x, 160, 0, W_CachePatchName(numbuf, PU_PATCH));
+		V_DrawScaledPatch(x, 160, 0, W_CachePatchName(numbuf, PU_PATCH_LOWPRIORITY));
 	}
 
 	// Draw the continue markers! Show continues.
@@ -3702,7 +3687,7 @@ void F_ContinueDrawer(void)
 	}
 
 	// Spotlight
-	V_DrawScaledPatch(x, 140, 0, W_CachePatchName("CONTSPOT", PU_PATCH));
+	V_DrawScaledPatch(x, 140, 0, W_CachePatchName("CONTSPOT", PU_PATCH_LOWPRIORITY));
 
 	// warping laser
 	if (continuetime)
@@ -3739,7 +3724,7 @@ void F_ContinueDrawer(void)
 #define drawchar(dx, dy, n)	{\
 								sprdef = &contskins[n]->sprites[cont_spr2[n][0]];\
 								sprframe = &sprdef->spriteframes[cont_spr2[n][1]];\
-								patch = W_CachePatchNum(sprframe->lumppat[cont_spr2[n][2]], PU_PATCH);\
+								patch = W_CachePatchNum(sprframe->lumppat[cont_spr2[n][2]], PU_PATCH_LOWPRIORITY);\
 								V_DrawFixedPatch((dx), (dy), contskins[n]->highresscale, (sprframe->flip & (1<<cont_spr2[n][2])) ? V_FLIP : 0, patch, contcolormaps[n]);\
 							}
 
@@ -4004,10 +3989,10 @@ void F_CutsceneDrawer(void)
 	{
 		if (cutscenes[cutnum]->scene[scenenum].pichires[picnum])
 			V_DrawSmallScaledPatch(picxpos, picypos, 0,
-				W_CachePatchName(cutscenes[cutnum]->scene[scenenum].picname[picnum], PU_PATCH));
+				W_CachePatchName(cutscenes[cutnum]->scene[scenenum].picname[picnum], PU_PATCH_LOWPRIORITY));
 		else
 			V_DrawScaledPatch(picxpos,picypos, 0,
-				W_CachePatchName(cutscenes[cutnum]->scene[scenenum].picname[picnum], PU_PATCH));
+				W_CachePatchName(cutscenes[cutnum]->scene[scenenum].picname[picnum], PU_PATCH_LOWPRIORITY));
 	}
 
 	if (dofadenow && rendermode != render_none)
@@ -4493,10 +4478,10 @@ void F_TextPromptDrawer(void)
 	{
 		if (textprompts[cutnum]->page[scenenum].pichires[picnum])
 			V_DrawSmallScaledPatch(picxpos, picypos, 0,
-				W_CachePatchName(textprompts[cutnum]->page[scenenum].picname[picnum], PU_PATCH));
+				W_CachePatchName(textprompts[cutnum]->page[scenenum].picname[picnum], PU_PATCH_LOWPRIORITY));
 		else
 			V_DrawScaledPatch(picxpos,picypos, 0,
-				W_CachePatchName(textprompts[cutnum]->page[scenenum].picname[picnum], PU_PATCH));
+				W_CachePatchName(textprompts[cutnum]->page[scenenum].picname[picnum], PU_PATCH_LOWPRIORITY));
 	}
 
 	// Draw background
@@ -4506,7 +4491,7 @@ void F_TextPromptDrawer(void)
 	if (iconlump != LUMPERROR)
 	{
 		INT32 iconx, icony, scale, scaledsize;
-		patch = W_CachePatchName(textprompts[cutnum]->page[scenenum].iconname, PU_PATCH);
+		patch = W_CachePatchName(textprompts[cutnum]->page[scenenum].iconname, PU_PATCH_LOWPRIORITY);
 
 		// scale and center
 		if (patch->width > patch->height)
diff --git a/src/f_wipe.c b/src/f_wipe.c
index f5b9bd72230ae7f5763b7fdc97cbbca9398d0ff5..6afb8a6a7934709c90b1428fcd5f5a6f55d54c20 100644
--- a/src/f_wipe.c
+++ b/src/f_wipe.c
@@ -293,7 +293,7 @@ static void F_DoWipe(fademask_t *fademask)
 			else
 			{
 				// pointer to transtable that this mask would use
-				transtbl = transtables + ((9 - *mask)<<FF_TRANSSHIFT);
+				transtbl = R_GetTranslucencyTable((9 - *mask) + 1);
 
 				// DRAWING LOOP
 				while (draw_linestogo--)
diff --git a/src/g_demo.c b/src/g_demo.c
index 0f72ad1094e443523f6be3fdb6b5fa345fa48539..9d3b8601584385d06bbd14e5ff8a2ec4ea63ca3c 100644
--- a/src/g_demo.c
+++ b/src/g_demo.c
@@ -453,8 +453,7 @@ void G_WriteGhostTic(mobj_t *ghost)
 			WRITEUINT16(demo_p,oldghost.sprite);
 		if (ghostext.flags & EZT_HEIGHT)
 		{
-			height >>= FRACBITS;
-			WRITEINT16(demo_p, height);
+			WRITEFIXED(demo_p, height);
 		}
 		ghostext.flags = 0;
 	}
@@ -610,7 +609,7 @@ void G_ConsGhostTic(void)
 		if (xziptic & EZT_SPRITE)
 			demo_p += sizeof(UINT16);
 		if (xziptic & EZT_HEIGHT)
-			demo_p += sizeof(INT16);
+			demo_p += (demoversion < 0x000e) ? sizeof(INT16) : sizeof(fixed_t);
 	}
 
 	if (ziptic & GZT_FOLLOW)
@@ -842,7 +841,7 @@ void G_GhostTicker(void)
 				g->mo->sprite = READUINT16(g->p);
 			if (xziptic & EZT_HEIGHT)
 			{
-				fixed_t temp = READINT16(g->p)<<FRACBITS;
+				fixed_t temp = (g->version < 0x000e) ? READINT16(g->p)<<FRACBITS : READFIXED(g->p);
 				g->mo->height = FixedMul(temp, g->mo->scale);
 			}
 		}
@@ -1106,7 +1105,7 @@ void G_ReadMetalTic(mobj_t *metal)
 			metal->sprite = READUINT16(metal_p);
 		if (xziptic & EZT_HEIGHT)
 		{
-			fixed_t temp = READINT16(metal_p)<<FRACBITS;
+			fixed_t temp = (metalversion < 0x000e) ? READINT16(metal_p)<<FRACBITS : READFIXED(metal_p);
 			metal->height = FixedMul(temp, metal->scale);
 		}
 	}
@@ -1293,8 +1292,7 @@ void G_WriteMetalTic(mobj_t *metal)
 			WRITEUINT16(demo_p,oldmetal.sprite);
 		if (ghostext.flags & EZT_HEIGHT)
 		{
-			height >>= FRACBITS;
-			WRITEINT16(demo_p, height);
+			WRITEFIXED(demo_p, height);
 		}
 		ghostext.flags = 0;
 	}
@@ -1474,8 +1472,8 @@ void G_BeginRecording(void)
 	WRITEUINT8(demo_p,player->thrustfactor);
 	WRITEUINT8(demo_p,player->accelstart);
 	WRITEUINT8(demo_p,player->acceleration);
-	WRITEUINT8(demo_p,player->height>>FRACBITS);
-	WRITEUINT8(demo_p,player->spinheight>>FRACBITS);
+	WRITEFIXED(demo_p,player->height);
+	WRITEFIXED(demo_p,player->spinheight);
 	WRITEUINT8(demo_p,player->camerascale>>FRACBITS);
 	WRITEUINT8(demo_p,player->shieldscale>>FRACBITS);
 
@@ -1901,8 +1899,8 @@ void G_DoPlayDemo(char *defdemoname)
 	thrustfactor = READUINT8(demo_p);
 	accelstart = READUINT8(demo_p);
 	acceleration = READUINT8(demo_p);
-	height = (fixed_t)READUINT8(demo_p)<<FRACBITS;
-	spinheight = (fixed_t)READUINT8(demo_p)<<FRACBITS;
+	height = (demoversion < 0x000e) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
+	spinheight = (demoversion < 0x000e) ? (fixed_t)READUINT8(demo_p)<<FRACBITS : READFIXED(demo_p);
 	camerascale = (fixed_t)READUINT8(demo_p)<<FRACBITS;
 	shieldscale = (fixed_t)READUINT8(demo_p)<<FRACBITS;
 	jumpfactor = READFIXED(demo_p);
@@ -2150,8 +2148,7 @@ void G_AddGhost(char *defdemoname)
 	p++; // thrustfactor
 	p++; // accelstart
 	p++; // acceleration
-	p++; // height
-	p++; // spinheight
+	p += (ghostversion < 0x000e) ? 2 : 2 * sizeof(fixed_t); // height and spinheight
 	p++; // camerascale
 	p++; // shieldscale
 	p += 4; // jumpfactor
diff --git a/src/g_game.c b/src/g_game.c
index 1ea7cd0809a4cfdf59242812421636b79cfc6bb9..228295b629539da319ee874798a26c91fcca0aa7 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1829,7 +1829,7 @@ void G_DoLoadLevel(boolean resetplayer)
 	}
 
 	// Setup the level.
-	if (!P_LoadLevel(false)) // this never returns false?
+	if (!P_LoadLevel(false, false)) // this never returns false?
 	{
 		// fail so reset game stuff
 		Command_ExitGame_f();
diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index 8c85c5112b09aa38ebd619b13c7c7b07a147e92f..85dabbcec317be2ae1a9ac1dfd8bdd7354ccd431 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -25,6 +25,7 @@
 #include "../z_zone.h"
 #include "../v_video.h"
 #include "../r_draw.h"
+#include "../r_patch.h"
 #include "../r_picformats.h"
 #include "../p_setup.h"
 
@@ -306,7 +307,7 @@ static void HWR_DrawPatchInCache(GLMipmap_t *mipmap,
 	// Draw each column to the block cache
 	for (; ncols--; block += bpp, xfrac += xfracstep)
 	{
-		patchcol = (const column_t *)((const UINT8 *)realpatch + LONG(realpatch->columnofs[xfrac>>FRACBITS]));
+		patchcol = (const column_t *)((const UINT8 *)realpatch->columns + (realpatch->columnofs[xfrac>>FRACBITS]));
 
 		HWR_DrawColumnInCache(patchcol, block, mipmap,
 								pblockheight, blockmodulo,
@@ -320,7 +321,7 @@ static void HWR_DrawPatchInCache(GLMipmap_t *mipmap,
 static void HWR_DrawTexturePatchInCache(GLMipmap_t *mipmap,
 	INT32 pblockwidth, INT32 pblockheight,
 	texture_t *texture, texpatch_t *patch,
-	const patch_t *realpatch)
+	const softwarepatch_t *realpatch)
 {
 	INT32 x, x1, x2;
 	INT32 col, ncols;
@@ -391,7 +392,7 @@ static void HWR_DrawTexturePatchInCache(GLMipmap_t *mipmap,
 	bpp = format2bpp(mipmap->format);
 
 	if (bpp < 1 || bpp > 4)
-		I_Error("HWR_DrawPatchInCache: no drawer defined for this bpp (%d)\n",bpp);
+		I_Error("HWR_DrawTexturePatchInCache: no drawer defined for this bpp (%d)\n",bpp);
 
 	// NOTE: should this actually be pblockwidth*bpp?
 	blockmodulo = pblockwidth*bpp;
@@ -446,7 +447,7 @@ static void HWR_GenerateTexture(INT32 texnum, GLMapTexture_t *grtex)
 	UINT8 *block;
 	texture_t *texture;
 	texpatch_t *patch;
-	patch_t *realpatch;
+	softwarepatch_t *realpatch;
 	UINT8 *pdata;
 	INT32 blockwidth, blockheight, blocksize;
 
@@ -502,16 +503,16 @@ static void HWR_GenerateTexture(INT32 texnum, GLMapTexture_t *grtex)
 		boolean dealloc = true;
 		size_t lumplength = W_LumpLengthPwad(patch->wad, patch->lump);
 		pdata = W_CacheLumpNumPwad(patch->wad, patch->lump, PU_CACHE);
-		realpatch = (patch_t *)pdata;
+		realpatch = (softwarepatch_t *)pdata;
 
 #ifndef NO_PNG_LUMPS
 		if (Picture_IsLumpPNG((UINT8 *)realpatch, lumplength))
-			realpatch = (patch_t *)Picture_PNGConvert(pdata, PICFMT_PATCH, NULL, NULL, NULL, NULL, lumplength, NULL, 0);
+			realpatch = (softwarepatch_t *)Picture_PNGConvert(pdata, PICFMT_DOOMPATCH, NULL, NULL, NULL, NULL, lumplength, NULL, 0);
 		else
 #endif
 #ifdef WALLFLATS
 		if (texture->type == TEXTURETYPE_FLAT)
-			realpatch = (patch_t *)Picture_Convert(PICFMT_FLAT, pdata, PICFMT_PATCH, 0, NULL, texture->width, texture->height, 0, 0, 0);
+			realpatch = (softwarepatch_t *)Picture_Convert(PICFMT_FLAT, pdata, PICFMT_DOOMPATCH, 0, NULL, texture->width, texture->height, 0, 0, 0);
 		else
 #endif
 		{
@@ -544,36 +545,20 @@ static void HWR_GenerateTexture(INT32 texnum, GLMapTexture_t *grtex)
 // patch may be NULL if grMipmap has been initialised already and makebitmap is false
 void HWR_MakePatch (const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipmap, boolean makebitmap)
 {
-#ifndef NO_PNG_LUMPS
-	// lump is a png so convert it
-	size_t len = W_LumpLengthPwad(grPatch->wadnum, grPatch->lumpnum);
-	if ((patch != NULL) && Picture_IsLumpPNG((const UINT8 *)patch, len))
-		patch = (patch_t *)Picture_PNGConvert((const UINT8 *)patch, PICFMT_PATCH, NULL, NULL, NULL, NULL, len, NULL, 0);
-#endif
-
-	// don't do it twice (like a cache)
 	if (grMipmap->width == 0)
 	{
-		// save the original patch header so that the GLPatch can be casted
-		// into a standard patch_t struct and the existing code can get the
-		// orginal patch dimensions and offsets.
-		grPatch->width = SHORT(patch->width);
-		grPatch->height = SHORT(patch->height);
-		grPatch->leftoffset = SHORT(patch->leftoffset);
-		grPatch->topoffset = SHORT(patch->topoffset);
-
 		grMipmap->width = grMipmap->height = 1;
-		while (grMipmap->width < grPatch->width) grMipmap->width <<= 1;
-		while (grMipmap->height < grPatch->height) grMipmap->height <<= 1;
+		while (grMipmap->width < patch->width) grMipmap->width <<= 1;
+		while (grMipmap->height < patch->height) grMipmap->height <<= 1;
 
 		// no wrap around, no chroma key
 		grMipmap->flags = 0;
+
 		// setup the texture info
 		grMipmap->format = patchformat;
 
-		//grPatch->max_s = grPatch->max_t = 1.0f;
-		grPatch->max_s = (float)grPatch->width / (float)grMipmap->width;
-		grPatch->max_t = (float)grPatch->height / (float)grMipmap->height;
+		grPatch->max_s = (float)patch->width / (float)grMipmap->width;
+		grPatch->max_t = (float)patch->height / (float)grMipmap->height;
 	}
 
 	Z_Free(grMipmap->data);
@@ -585,7 +570,7 @@ void HWR_MakePatch (const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipm
 
 		HWR_DrawPatchInCache(grMipmap,
 			grMipmap->width, grMipmap->height,
-			grPatch->width, grPatch->height,
+			patch->width, patch->height,
 			patch);
 	}
 }
@@ -598,20 +583,44 @@ void HWR_MakePatch (const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipm
 static size_t gl_numtextures = 0; // Texture count
 static GLMapTexture_t *gl_textures; // For all textures
 static GLMapTexture_t *gl_flats; // For all (texture) flats, as normal flats don't need to be cached
+boolean gl_maptexturesloaded = false;
 
-void HWR_InitTextureCache(void)
+void HWR_FreeTexture(patch_t *patch)
 {
-	gl_textures = NULL;
-	gl_flats = NULL;
+	if (!patch)
+		return;
+
+	if (patch->hardware)
+	{
+		GLPatch_t *grPatch = patch->hardware;
+
+		HWR_FreeTextureColormaps(patch);
+
+		if (grPatch->mipmap)
+		{
+			if (vid.glstate == VID_GL_LIBRARY_LOADED)
+				HWD.pfnDeleteTexture(grPatch->mipmap);
+			if (grPatch->mipmap->data)
+				Z_Free(grPatch->mipmap->data);
+			Z_Free(grPatch->mipmap);
+		}
+
+		Z_Free(patch->hardware);
+	}
+
+	patch->hardware = NULL;
 }
 
-// Callback function for HWR_FreeTextureCache.
-static void FreeMipmapColormap(INT32 patchnum, void *patch)
+// Called by HWR_FreePatchCache.
+void HWR_FreeTextureColormaps(patch_t *patch)
 {
-	GLPatch_t* const pat = patch;
-	(void)patchnum; //unused
+	GLPatch_t *pat;
 
 	// The patch must be valid, obviously
+	if (!patch)
+		return;
+
+	pat = patch->hardware;
 	if (!pat)
 		return;
 
@@ -639,34 +648,65 @@ static void FreeMipmapColormap(INT32 patchnum, void *patch)
 		if (next->data)
 			Z_Free(next->data);
 		next->data = NULL;
+		HWD.pfnDeleteTexture(next);
 
 		// Free the old colormap mipmap from memory.
 		free(next);
 	}
 }
 
-void HWR_FreeMipmapCache(void)
+static void HWR_FreePatchCache(boolean freeall)
 {
 	INT32 i;
 
+	for (i = 0; i < numwadfiles; i++)
+	{
+		INT32 j = 0;
+		for (; j < wadfiles[i]->numlumps; j++)
+			(freeall ? HWR_FreeTexture : HWR_FreeTextureColormaps)(wadfiles[i]->patchcache[j]);
+	}
+}
+
+void HWR_ClearAllTextures(void)
+{
+	HWR_FreeMapTextures();
+
 	// free references to the textures
 	HWD.pfnClearMipMapCache();
 
-	// free all hardware-converted graphics cached in the heap
-	// our gool is only the textures since user of the texture is the texture cache
-	Z_FreeTag(PU_HWRCACHE);
-	Z_FreeTag(PU_HWRCACHE_UNLOCKED);
-
 	// Alam: free the Z_Blocks before freeing it's users
-	// free all patch colormaps after each level: must be done after ClearMipMapCache!
-	for (i = 0; i < numwadfiles; i++)
-		M_AATreeIterate(wadfiles[i]->hwrcache, FreeMipmapColormap);
+	HWR_FreePatchCache(true);
 }
 
-void HWR_FreeTextureCache(void)
+// free all patch colormaps after each level: must be done after ClearMipMapCache!
+void HWR_FreeColormapCache(void)
 {
-	// free references to the textures
-	HWR_FreeMipmapCache();
+	HWR_FreePatchCache(false);
+}
+
+void HWR_InitMapTextures(void)
+{
+	gl_textures = NULL;
+	gl_flats = NULL;
+	gl_maptexturesloaded = false;
+}
+
+static void FreeMapTexture(GLMapTexture_t *tex)
+{
+	HWD.pfnDeleteTexture(&tex->mipmap);
+	if (tex->mipmap.data)
+		Z_Free(tex->mipmap.data);
+}
+
+void HWR_FreeMapTextures(void)
+{
+	size_t i;
+
+	for (i = 0; i < gl_numtextures; i++)
+	{
+		FreeMapTexture(&gl_textures[i]);
+		FreeMapTexture(&gl_flats[i]);
+	}
 
 	// now the heap don't have any 'user' pointing to our
 	// texturecache info, we can free it
@@ -677,12 +717,13 @@ void HWR_FreeTextureCache(void)
 	gl_textures = NULL;
 	gl_flats = NULL;
 	gl_numtextures = 0;
+	gl_maptexturesloaded = false;
 }
 
-void HWR_LoadTextures(size_t pnumtextures)
+void HWR_LoadMapTextures(size_t pnumtextures)
 {
 	// we must free it since numtextures changed
-	HWR_FreeTextureCache();
+	HWR_FreeMapTextures();
 
 	// Why not Z_Malloc?
 	gl_numtextures = pnumtextures;
@@ -692,7 +733,9 @@ void HWR_LoadTextures(size_t pnumtextures)
 	// Doesn't tell you which it _is_, but hopefully
 	// should never ever happen (right?!)
 	if ((gl_textures == NULL) || (gl_flats == NULL))
-		I_Error("HWR_LoadTextures: ran out of memory for OpenGL textures. Sad!");
+		I_Error("HWR_LoadMapTextures: ran out of memory for OpenGL textures. Sad!");
+
+	gl_maptexturesloaded = true;
 }
 
 void HWR_SetPalette(RGBA_t *palette)
@@ -730,7 +773,6 @@ GLMapTexture_t *HWR_GetTexture(INT32 tex)
 	// If hardware does not have the texture, then call pfnSetTexture to upload it
 	if (!grtex->mipmap.downloaded)
 		HWD.pfnSetTexture(&grtex->mipmap);
-
 	HWR_SetCurrentTexture(&grtex->mipmap);
 
 	// The system-memory data can be purged now.
@@ -806,17 +848,19 @@ static void HWR_CacheTextureAsFlat(GLMipmap_t *grMipmap, INT32 texturenum)
 void HWR_LiterallyGetFlat(lumpnum_t flatlumpnum)
 {
 	GLMipmap_t *grmip;
+	patch_t *patch;
+
 	if (flatlumpnum == LUMPERROR)
 		return;
 
-	grmip = HWR_GetCachedGLPatch(flatlumpnum)->mipmap;
+	patch = HWR_GetCachedGLPatch(flatlumpnum);
+	grmip = ((GLPatch_t *)Patch_AllocateHardwarePatch(patch))->mipmap;
 	if (!grmip->downloaded && !grmip->data)
 		HWR_CacheFlat(grmip, flatlumpnum);
 
 	// If hardware does not have the texture, then call pfnSetTexture to upload it
 	if (!grmip->downloaded)
 		HWD.pfnSetTexture(grmip);
-
 	HWR_SetCurrentTexture(grmip);
 
 	// The system-memory data can be purged now.
@@ -854,7 +898,6 @@ void HWR_GetLevelFlat(levelflat_t *levelflat)
 		// If hardware does not have the texture, then call pfnSetTexture to upload it
 		if (!grtex->mipmap.downloaded)
 			HWD.pfnSetTexture(&grtex->mipmap);
-
 		HWR_SetCurrentTexture(&grtex->mipmap);
 
 		// The system-memory data can be purged now.
@@ -862,9 +905,9 @@ void HWR_GetLevelFlat(levelflat_t *levelflat)
 	}
 	else if (levelflat->type == LEVELFLAT_PATCH)
 	{
-		GLPatch_t *patch = W_CachePatchNum(levelflat->u.flat.lumpnum, PU_CACHE);
-		levelflat->width = (UINT16)SHORT(patch->width);
-		levelflat->height = (UINT16)SHORT(patch->height);
+		patch_t *patch = W_CachePatchNum(levelflat->u.flat.lumpnum, PU_CACHE);
+		levelflat->width = (UINT16)(patch->width);
+		levelflat->height = (UINT16)(patch->height);
 		HWR_GetPatch(patch);
 	}
 #ifndef NO_PNG_LUMPS
@@ -911,89 +954,61 @@ void HWR_GetLevelFlat(levelflat_t *levelflat)
 		HWR_SetCurrentTexture(NULL);
 }
 
-//
-// HWR_LoadMappedPatch(): replace the skin color of the sprite in cache
-//                          : load it first in doom cache if not already
-//
-static void HWR_LoadMappedPatch(GLMipmap_t *grmip, GLPatch_t *gpatch)
+// --------------------+
+// HWR_LoadPatchMipmap : Generates a patch into a mipmap, usually the mipmap inside the patch itself
+// --------------------+
+static void HWR_LoadPatchMipmap(patch_t *patch, GLMipmap_t *grMipmap)
 {
-	if (!grmip->downloaded && !grmip->data)
-	{
-		patch_t *patch = gpatch->rawpatch;
-		if (!patch)
-			patch = W_CacheLumpNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
-		HWR_MakePatch(patch, gpatch, grmip, true);
-
-		// You can't free rawpatch for some reason?
-		// (Obviously I can't, sprite rotation needs that...)
-		if (!gpatch->rawpatch)
-			Z_Free(patch);
-	}
+	GLPatch_t *grPatch = patch->hardware;
+	if (!grMipmap->downloaded && !grMipmap->data)
+		HWR_MakePatch(patch, grPatch, grMipmap, true);
 
 	// If hardware does not have the texture, then call pfnSetTexture to upload it
-	if (!grmip->downloaded)
-		HWD.pfnSetTexture(grmip);
-
-	HWR_SetCurrentTexture(grmip);
+	if (!grMipmap->downloaded)
+		HWD.pfnSetTexture(grMipmap);
+	HWR_SetCurrentTexture(grMipmap);
 
 	// The system-memory data can be purged now.
-	Z_ChangeTag(grmip->data, PU_HWRCACHE_UNLOCKED);
+	Z_ChangeTag(grMipmap->data, PU_HWRCACHE_UNLOCKED);
 }
 
 // -----------------+
 // HWR_GetPatch     : Download a patch to the hardware cache and make it ready for use
 // -----------------+
-void HWR_GetPatch(GLPatch_t *gpatch)
+void HWR_GetPatch(patch_t *patch)
 {
-	// is it in hardware cache
-	if (!gpatch->mipmap->downloaded && !gpatch->mipmap->data)
-	{
-		// load the software patch, PU_STATIC or the Z_Malloc for hardware patch will
-		// flush the software patch before the conversion! oh yeah I suffered
-		patch_t *ptr = gpatch->rawpatch;
-		if (!ptr)
-			ptr = W_CacheLumpNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
-		HWR_MakePatch(ptr, gpatch, gpatch->mipmap, true);
-
-		// this is inefficient.. but the hardware patch in heap is purgeable so it should
-		// not fragment memory, and besides the REAL cache here is the hardware memory
-		if (!gpatch->rawpatch)
-			Z_Free(ptr);
-	}
-
-	// If hardware does not have the texture, then call pfnSetTexture to upload it
-	if (!gpatch->mipmap->downloaded)
-		HWD.pfnSetTexture(gpatch->mipmap);
-
-	HWR_SetCurrentTexture(gpatch->mipmap);
-
-	// The system-memory patch data can be purged now.
-	Z_ChangeTag(gpatch->mipmap->data, PU_HWRCACHE_UNLOCKED);
+	if (!patch->hardware)
+		Patch_CreateGL(patch);
+	HWR_LoadPatchMipmap(patch, ((GLPatch_t *)patch->hardware)->mipmap);
 }
 
-
 // -------------------+
 // HWR_GetMappedPatch : Same as HWR_GetPatch for sprite color
 // -------------------+
-void HWR_GetMappedPatch(GLPatch_t *gpatch, const UINT8 *colormap)
+void HWR_GetMappedPatch(patch_t *patch, const UINT8 *colormap)
 {
-	GLMipmap_t *grmip, *newmip;
+	GLPatch_t *grPatch;
+	GLMipmap_t *grMipmap, *newMipmap;
+
+	if (!patch->hardware)
+		Patch_CreateGL(patch);
+	grPatch = patch->hardware;
 
 	if (colormap == colormaps || colormap == NULL)
 	{
-		// Load the default (green) color in doom cache (temporary?) AND hardware cache
-		HWR_GetPatch(gpatch);
+		// Load the default (green) color in hardware cache
+		HWR_GetPatch(patch);
 		return;
 	}
 
 	// search for the mimmap
 	// skip the first (no colormap translated)
-	for (grmip = gpatch->mipmap; grmip->nextcolormap; )
+	for (grMipmap = grPatch->mipmap; grMipmap->nextcolormap; )
 	{
-		grmip = grmip->nextcolormap;
-		if (grmip->colormap == colormap)
+		grMipmap = grMipmap->nextcolormap;
+		if (grMipmap->colormap == colormap)
 		{
-			HWR_LoadMappedPatch(grmip, gpatch);
+			HWR_LoadPatchMipmap(patch, grMipmap);
 			return;
 		}
 	}
@@ -1002,15 +1017,15 @@ void HWR_GetMappedPatch(GLPatch_t *gpatch, const UINT8 *colormap)
 
 	//BP: WARNING: don't free it manually without clearing the cache of harware renderer
 	//              (it have a liste of mipmap)
-	//    this malloc is cleared in HWR_FreeTextureCache
+	//    this malloc is cleared in HWR_FreeColormapCache
 	//    (...) unfortunately z_malloc fragment alot the memory :(so malloc is better
-	newmip = calloc(1, sizeof (*newmip));
-	if (newmip == NULL)
+	newMipmap = calloc(1, sizeof (*newMipmap));
+	if (newMipmap == NULL)
 		I_Error("%s: Out of memory", "HWR_GetMappedPatch");
-	grmip->nextcolormap = newmip;
+	grMipmap->nextcolormap = newMipmap;
 
-	newmip->colormap = colormap;
-	HWR_LoadMappedPatch(newmip, gpatch);
+	newMipmap->colormap = colormap;
+	HWR_LoadPatchMipmap(patch, newMipmap);
 }
 
 void HWR_UnlockCachedPatch(GLPatch_t *gpatch)
@@ -1100,79 +1115,73 @@ static void HWR_DrawPicInCache(UINT8 *block, INT32 pblockwidth, INT32 pblockheig
 // HWR_GetPic       : Download a Doom pic (raw row encoded with no 'holes')
 // Returns          :
 // -----------------+
-GLPatch_t *HWR_GetPic(lumpnum_t lumpnum)
+patch_t *HWR_GetPic(lumpnum_t lumpnum)
 {
-	GLPatch_t *grpatch = HWR_GetCachedGLPatch(lumpnum);
-	if (!grpatch->mipmap->downloaded && !grpatch->mipmap->data)
+	patch_t *patch = HWR_GetCachedGLPatch(lumpnum);
+	GLPatch_t *grPatch = (GLPatch_t *)(patch->hardware);
+
+	if (!grPatch->mipmap->downloaded && !grPatch->mipmap->data)
 	{
 		pic_t *pic;
 		UINT8 *block;
 		size_t len;
 
 		pic = W_CacheLumpNum(lumpnum, PU_CACHE);
-		grpatch->width = SHORT(pic->width);
-		grpatch->height = SHORT(pic->height);
+		patch->width = SHORT(pic->width);
+		patch->height = SHORT(pic->height);
 		len = W_LumpLength(lumpnum) - sizeof (pic_t);
 
-		grpatch->leftoffset = 0;
-		grpatch->topoffset = 0;
-
-		grpatch->mipmap->width = (UINT16)grpatch->width;
-		grpatch->mipmap->height = (UINT16)grpatch->height;
+		grPatch->mipmap->width = (UINT16)patch->width;
+		grPatch->mipmap->height = (UINT16)patch->height;
 
 		if (pic->mode == PALETTE)
-			grpatch->mipmap->format = textureformat; // can be set by driver
+			grPatch->mipmap->format = textureformat; // can be set by driver
 		else
-			grpatch->mipmap->format = picmode2GR[pic->mode];
+			grPatch->mipmap->format = picmode2GR[pic->mode];
 
-		Z_Free(grpatch->mipmap->data);
+		Z_Free(grPatch->mipmap->data);
 
 		// allocate block
-		block = MakeBlock(grpatch->mipmap);
+		block = MakeBlock(grPatch->mipmap);
 
-		if (grpatch->width  == SHORT(pic->width) &&
-			grpatch->height == SHORT(pic->height) &&
-			format2bpp(grpatch->mipmap->format) == format2bpp(picmode2GR[pic->mode]))
+		if (patch->width  == SHORT(pic->width) &&
+			patch->height == SHORT(pic->height) &&
+			format2bpp(grPatch->mipmap->format) == format2bpp(picmode2GR[pic->mode]))
 		{
 			// no conversion needed
-			M_Memcpy(grpatch->mipmap->data, pic->data,len);
+			M_Memcpy(grPatch->mipmap->data, pic->data,len);
 		}
 		else
 			HWR_DrawPicInCache(block, SHORT(pic->width), SHORT(pic->height),
-			                   SHORT(pic->width)*format2bpp(grpatch->mipmap->format),
+			                   SHORT(pic->width)*format2bpp(grPatch->mipmap->format),
 			                   pic,
-			                   format2bpp(grpatch->mipmap->format));
+			                   format2bpp(grPatch->mipmap->format));
 
 		Z_Unlock(pic);
 		Z_ChangeTag(block, PU_HWRCACHE_UNLOCKED);
 
-		grpatch->mipmap->flags = 0;
-		grpatch->max_s = grpatch->max_t = 1.0f;
+		grPatch->mipmap->flags = 0;
+		grPatch->max_s = grPatch->max_t = 1.0f;
 	}
-	HWD.pfnSetTexture(grpatch->mipmap);
-	//CONS_Debug(DBG_RENDER, "picloaded at %x as texture %d\n",grpatch->mipmap.data, grpatch->mipmap.downloaded);
+	HWD.pfnSetTexture(grPatch->mipmap);
+	//CONS_Debug(DBG_RENDER, "picloaded at %x as texture %d\n",grPatch->mipmap->data, grPatch->mipmap->downloaded);
 
-	return grpatch;
+	return patch;
 }
 
-GLPatch_t *HWR_GetCachedGLPatchPwad(UINT16 wadnum, UINT16 lumpnum)
+patch_t *HWR_GetCachedGLPatchPwad(UINT16 wadnum, UINT16 lumpnum)
 {
-	aatree_t *hwrcache = wadfiles[wadnum]->hwrcache;
-	GLPatch_t *grpatch;
-
-	if (!(grpatch = M_AATreeGet(hwrcache, lumpnum)))
+	lumpcache_t *lumpcache = wadfiles[wadnum]->patchcache;
+	if (!lumpcache[lumpnum])
 	{
-		grpatch = Z_Calloc(sizeof(GLPatch_t), PU_HWRPATCHINFO, NULL);
-		grpatch->wadnum = wadnum;
-		grpatch->lumpnum = lumpnum;
-		grpatch->mipmap = Z_Calloc(sizeof(GLMipmap_t), PU_HWRPATCHINFO, NULL);
-		M_AATreeSet(hwrcache, lumpnum, grpatch);
+		void *ptr = Z_Calloc(sizeof(patch_t), PU_PATCH, &lumpcache[lumpnum]);
+		Patch_Create(NULL, 0, ptr);
+		Patch_AllocateHardwarePatch(ptr);
 	}
-
-	return grpatch;
+	return (patch_t *)(lumpcache[lumpnum]);
 }
 
-GLPatch_t *HWR_GetCachedGLPatch(lumpnum_t lumpnum)
+patch_t *HWR_GetCachedGLPatch(lumpnum_t lumpnum)
 {
 	return HWR_GetCachedGLPatchPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum));
 }
@@ -1265,7 +1274,8 @@ static void HWR_CacheFadeMask(GLMipmap_t *grMipmap, lumpnum_t fademasklumpnum)
 
 void HWR_GetFadeMask(lumpnum_t fademasklumpnum)
 {
-	GLMipmap_t *grmip = HWR_GetCachedGLPatch(fademasklumpnum)->mipmap;
+	patch_t *patch = HWR_GetCachedGLPatch(fademasklumpnum);
+	GLMipmap_t *grmip = ((GLPatch_t *)Patch_AllocateHardwarePatch(patch))->mipmap;
 	if (!grmip->downloaded && !grmip->data)
 		HWR_CacheFadeMask(grmip, fademasklumpnum);
 
diff --git a/src/hardware/hw_data.h b/src/hardware/hw_data.h
index e5477d7292b3701a4bb8966c19715c0cd508d881..6a872d25876b159562d45452095f9dfd4da5c974 100644
--- a/src/hardware/hw_data.h
+++ b/src/hardware/hw_data.h
@@ -73,23 +73,10 @@ struct GLMapTexture_s
 typedef struct GLMapTexture_s GLMapTexture_t;
 
 
-// a cached patch as converted to hardware format, holding the original patch_t
-// header so that the existing code can retrieve ->width, ->height as usual
-// This is returned by W_CachePatchNum()/W_CachePatchName(), when rendermode
-// is 'render_opengl'. Else it returns the normal patch_t data.
-
+// a cached patch as converted to hardware format
 struct GLPatch_s
 {
-	// the 4 first fields come right away from the original patch_t
-	INT16               width;
-	INT16               height;
-	INT16               leftoffset;     // pixels to the left of origin
-	INT16               topoffset;      // pixels below the origin
-	//
 	float               max_s,max_t;
-	UINT16              wadnum;      // the software patch lump num for when the hardware patch
-	UINT16              lumpnum;     // was flushed, and we need to re-create it
-	void                *rawpatch;   // :^)
 	GLMipmap_t          *mipmap;
 } ATTRPACK;
 typedef struct GLPatch_s GLPatch_t;
diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h
index 607d21ef52dd6863f5bafc72f736cc27f2a475b5..a782762a38c46dbb4161468b43b3041d215e8d2e 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -212,35 +212,32 @@ typedef struct
 // You pass a combination of these flags to DrawPolygon()
 enum EPolyFlags
 {
-		// the first 5 are mutually exclusive
-
-	PF_Masked           = 0x00000001,   // Poly is alpha scaled and 0 alpha pels are discarded (holes in texture)
+	// Mutually exclusive blend flags
+	PF_Masked           = 0x00000001,   // Poly is alpha scaled and 0 alpha pixels are discarded (holes in texture)
 	PF_Translucent      = 0x00000002,   // Poly is transparent, alpha = level of transparency
-	PF_Additive         = 0x00000004,   // Poly is added to the frame buffer
-	PF_Environment      = 0x00000008,   // Poly should be drawn environment mapped.
-	                                    // Hurdler: used for text drawing
-	PF_Substractive     = 0x00000010,   // for splat
-	PF_NoAlphaTest      = 0x00000020,   // hiden param
-	PF_Fog              = 0x00000040,   // Fog blocks
-	PF_Blending         = (PF_Environment|PF_Additive|PF_Translucent|PF_Masked|PF_Substractive|PF_Fog)&~PF_NoAlphaTest,
-
-		// other flag bits
-
-	PF_Occlude          = 0x00000100,   // Update the depth buffer
-	PF_NoDepthTest      = 0x00000200,   // Disable the depth test mode
-	PF_Invisible        = 0x00000400,   // Disable write to color buffer
-	PF_Decal            = 0x00000800,   // Enable polygon offset
+	PF_Environment      = 0x00000004,   // Poly should be drawn environment mapped. (Hurdler: used for text drawing)
+	PF_Additive         = 0x00000008,   // Additive color blending
+	PF_AdditiveSource   = 0x00000010,   // Source blending factor is additive. This is the opposite of regular additive blending.
+	PF_Subtractive      = 0x00000020,   // Subtractive color blending
+	PF_ReverseSubtract  = 0x00000040,   // Reverse subtract, used in wall splats (decals)
+	PF_Multiplicative   = 0x00000080,   // Multiplicative color blending
+	PF_Fog              = 0x20000000,   // Fog blocks
+	PF_NoAlphaTest      = 0x40000000,   // Disables alpha testing
+	PF_Blending         = (PF_Masked|PF_Translucent|PF_Environment|PF_Additive|PF_AdditiveSource|PF_Subtractive|PF_ReverseSubtract|PF_Multiplicative|PF_Fog) & ~PF_NoAlphaTest,
+
+	// other flag bits
+	PF_Occlude          = 0x00000100,   // Updates the depth buffer
+	PF_NoDepthTest      = 0x00000200,   // Disables the depth test mode
+	PF_Invisible        = 0x00000400,   // Disables write to color buffer
+	PF_Decal            = 0x00000800,   // Enables polygon offset
 	PF_Modulated        = 0x00001000,   // Modulation (multiply output with constant ARGB)
 	                                    // When set, pass the color constant into the FSurfaceInfo -> PolyColor
-	PF_NoTexture        = 0x00002000,   // Use the small white texture
-	PF_Corona           = 0x00004000,   // Tell the rendrer we are drawing a corona
-	PF_Ripple           = 0x00008000,   // Water shader effect
-	PF_RemoveYWrap      = 0x00010000,   // Force clamp texture on Y
-	PF_ForceWrapX       = 0x00020000,   // Force repeat texture on X
-	PF_ForceWrapY       = 0x00040000,   // Force repeat texture on Y
-	PF_Clip             = 0x40000000,   // clip to frustum and nearz plane (glide only, automatic in opengl)
-	PF_NoZClip          = 0x20000000,   // in conjonction with PF_Clip
-	PF_Debug            = 0x80000000    // print debug message in driver :)
+	PF_NoTexture        = 0x00002000,   // Disables texturing
+	PF_Corona           = 0x00004000,   // Tells the renderer we are drawing a corona
+	PF_Ripple           = 0x00008000,   // Water effect shader
+	PF_RemoveYWrap      = 0x00010000,   // Forces clamp texture on Y
+	PF_ForceWrapX       = 0x00020000,   // Forces repeat texture on X
+	PF_ForceWrapY       = 0x00040000    // Forces repeat texture on Y
 };
 
 
@@ -295,6 +292,16 @@ enum hwdsetspecialstate
 
 typedef enum hwdsetspecialstate hwdspecialstate_t;
 
+// Lactozilla: Shader options
+enum hwdshaderoption
+{
+	HWD_SHADEROPTION_OFF,
+	HWD_SHADEROPTION_ON,
+	HWD_SHADEROPTION_NOCUSTOM,
+};
+
+typedef enum hwdshaderoption hwdshaderoption_t;
+
 // Lactozilla: Shader info
 // Generally set at the start of the frame.
 enum hwdshaderinfo
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index f5a984d5d3a23ed3bd80d4d7e690bcba8d42422c..faf7a9f8c6d65f65cda61f890b04f332b8f319b7 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -68,10 +68,11 @@ static UINT8 softwaretranstogl_lo[11] = {  0, 12, 24, 36, 48, 60, 71, 83, 95,111
 // Notes            : x,y : positions relative to the original Doom resolution
 //                  : textes(console+score) + menus + status bar
 // -----------------+
-void HWR_DrawPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option)
+void HWR_DrawPatch(patch_t *gpatch, INT32 x, INT32 y, INT32 option)
 {
 	FOutVector v[4];
 	FBITFIELD flags;
+	GLPatch_t *hwrPatch;
 
 //  3--2
 //  | /|
@@ -84,6 +85,7 @@ void HWR_DrawPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option)
 
 	// make patch ready in hardware cache
 	HWR_GetPatch(gpatch);
+	hwrPatch = ((GLPatch_t *)gpatch->hardware);
 
 	switch (option & V_SCALEPATCHMASK)
 	{
@@ -103,17 +105,17 @@ void HWR_DrawPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option)
 	if (option & V_NOSCALESTART)
 		sdupx = sdupy = 2.0f;
 
-	v[0].x = v[3].x = (x*sdupx-SHORT(gpatch->leftoffset)*pdupx)/vid.width - 1;
-	v[2].x = v[1].x = (x*sdupx+(SHORT(gpatch->width)-SHORT(gpatch->leftoffset))*pdupx)/vid.width - 1;
-	v[0].y = v[1].y = 1-(y*sdupy-SHORT(gpatch->topoffset)*pdupy)/vid.height;
-	v[2].y = v[3].y = 1-(y*sdupy+(SHORT(gpatch->height)-SHORT(gpatch->topoffset))*pdupy)/vid.height;
+	v[0].x = v[3].x = (x*sdupx-(gpatch->leftoffset)*pdupx)/vid.width - 1;
+	v[2].x = v[1].x = (x*sdupx+(gpatch->width-gpatch->leftoffset)*pdupx)/vid.width - 1;
+	v[0].y = v[1].y = 1-(y*sdupy-(gpatch->topoffset)*pdupy)/vid.height;
+	v[2].y = v[3].y = 1-(y*sdupy+(gpatch->height-gpatch->topoffset)*pdupy)/vid.height;
 
 	v[0].z = v[1].z = v[2].z = v[3].z = 1.0f;
 
 	v[0].s = v[3].s = 0.0f;
-	v[2].s = v[1].s = gpatch->max_s;
+	v[2].s = v[1].s = hwrPatch->max_s;
 	v[0].t = v[1].t = 0.0f;
-	v[2].t = v[3].t = gpatch->max_t;
+	v[2].t = v[3].t = hwrPatch->max_t;
 
 	flags = PF_Translucent|PF_NoDepthTest;
 
@@ -126,13 +128,14 @@ void HWR_DrawPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option)
 	HWD.pfnDrawPolygon(NULL, v, 4, flags);
 }
 
-void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 option, const UINT8 *colormap)
+void HWR_DrawStretchyFixedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 option, const UINT8 *colormap)
 {
 	FOutVector v[4];
 	FBITFIELD flags;
 	float cx = FIXED_TO_FLOAT(x);
 	float cy = FIXED_TO_FLOAT(y);
 	UINT8 alphalevel = ((option & V_ALPHAMASK) >> V_ALPHASHIFT);
+	GLPatch_t *hwrPatch;
 
 //  3--2
 //  | /|
@@ -151,6 +154,8 @@ void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t
 	else
 		HWR_GetMappedPatch(gpatch, colormap);
 
+	hwrPatch = ((GLPatch_t *)gpatch->hardware);
+
 	dupx = (float)vid.dupx;
 	dupy = (float)vid.dupy;
 
@@ -181,13 +186,13 @@ void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t
 
 		// left offset
 		if (option & V_FLIP)
-			offsetx = (float)(SHORT(gpatch->width) - SHORT(gpatch->leftoffset)) * fscalew;
+			offsetx = (float)(gpatch->width - gpatch->leftoffset) * fscalew;
 		else
-			offsetx = (float)SHORT(gpatch->leftoffset) * fscalew;
+			offsetx = (float)(gpatch->leftoffset) * fscalew;
 
 		// top offset
 		// TODO: make some kind of vertical version of V_FLIP, maybe by deprecating V_OFFSET in future?!?
-		offsety = (float)SHORT(gpatch->topoffset) * fscaleh;
+		offsety = (float)(gpatch->topoffset) * fscaleh;
 
 		if ((option & (V_NOSCALESTART|V_OFFSET)) == (V_NOSCALESTART|V_OFFSET)) // Multiply by dupx/dupy for crosshairs
 		{
@@ -277,17 +282,14 @@ void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t
 			// if it's meant to cover the whole screen, black out the rest (ONLY IF TOP LEFT ISN'T TRANSPARENT)
 			// cx and cy are possibly *slightly* off from float maths
 			// This is done before here compared to software because we directly alter cx and cy to centre
-			if (cx >= -0.1f && cx <= 0.1f && SHORT(gpatch->width) == BASEVIDWIDTH && cy >= -0.1f && cy <= 0.1f && SHORT(gpatch->height) == BASEVIDHEIGHT)
+			if (cx >= -0.1f && cx <= 0.1f && (gpatch->width) == BASEVIDWIDTH && cy >= -0.1f && cy <= 0.1f && SHORT(gpatch->height) == BASEVIDHEIGHT)
 			{
-				// Need to temporarily cache the real patch to get the colour of the top left pixel
-				patch_t *realpatch = W_CacheSoftwarePatchNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
-				const column_t *column = (const column_t *)((const UINT8 *)(realpatch) + LONG((realpatch)->columnofs[0]));
+				const column_t *column = (const column_t *)((const UINT8 *)(gpatch->columns) + (gpatch->columnofs[0]));
 				if (!column->topdelta)
 				{
 					const UINT8 *source = (const UINT8 *)(column) + 3;
 					HWR_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
 				}
-				Z_Free(realpatch);
 			}
 			// centre screen
 			if (fabsf((float)vid.width - (float)BASEVIDWIDTH * dupx) > 1.0E-36f)
@@ -317,13 +319,13 @@ void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t
 
 	if (pscale != FRACUNIT || (splitscreen && option & V_PERPLAYER))
 	{
-		fwidth = (float)SHORT(gpatch->width) * fscalew * dupx;
-		fheight = (float)SHORT(gpatch->height) * fscaleh * dupy;
+		fwidth = (float)(gpatch->width) * fscalew * dupx;
+		fheight = (float)(gpatch->height) * fscaleh * dupy;
 	}
 	else
 	{
-		fwidth = (float)SHORT(gpatch->width) * dupx;
-		fheight = (float)SHORT(gpatch->height) * dupy;
+		fwidth = (float)(gpatch->width) * dupx;
+		fheight = (float)(gpatch->height) * dupy;
 	}
 
 	// positions of the cx, cy, are between 0 and vid.width/vid.height now, we need them to be between -1 and 1
@@ -345,17 +347,17 @@ void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t
 
 	if (option & V_FLIP)
 	{
-		v[0].s = v[3].s = gpatch->max_s;
+		v[0].s = v[3].s = hwrPatch->max_s;
 		v[2].s = v[1].s = 0.0f;
 	}
 	else
 	{
 		v[0].s = v[3].s = 0.0f;
-		v[2].s = v[1].s = gpatch->max_s;
+		v[2].s = v[1].s = hwrPatch->max_s;
 	}
 
 	v[0].t = v[1].t = 0.0f;
-	v[2].t = v[3].t = gpatch->max_t;
+	v[2].t = v[3].t = hwrPatch->max_t;
 
 	flags = PF_Translucent|PF_NoDepthTest;
 
@@ -380,13 +382,14 @@ void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t
 		HWD.pfnDrawPolygon(NULL, v, 4, flags);
 }
 
-void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, INT32 option, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h)
+void HWR_DrawCroppedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, INT32 option, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h)
 {
 	FOutVector v[4];
 	FBITFIELD flags;
 	float cx = FIXED_TO_FLOAT(x);
 	float cy = FIXED_TO_FLOAT(y);
 	UINT8 alphalevel = ((option & V_ALPHAMASK) >> V_ALPHASHIFT);
+	GLPatch_t *hwrPatch;
 
 //  3--2
 //  | /|
@@ -399,6 +402,7 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscal
 
 	// make patch ready in hardware cache
 	HWR_GetPatch(gpatch);
+	hwrPatch = ((GLPatch_t *)gpatch->hardware);
 
 	dupx = (float)vid.dupx;
 	dupy = (float)vid.dupy;
@@ -438,15 +442,12 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscal
 			// This is done before here compared to software because we directly alter cx and cy to centre
 			if (cx >= -0.1f && cx <= 0.1f && SHORT(gpatch->width) == BASEVIDWIDTH && cy >= -0.1f && cy <= 0.1f && SHORT(gpatch->height) == BASEVIDHEIGHT)
 			{
-				// Need to temporarily cache the real patch to get the colour of the top left pixel
-				patch_t *realpatch = W_CacheSoftwarePatchNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
-				const column_t *column = (const column_t *)((const UINT8 *)(realpatch) + LONG((realpatch)->columnofs[0]));
+				const column_t *column = (const column_t *)((const UINT8 *)(gpatch->columns) + (gpatch->columnofs[0]));
 				if (!column->topdelta)
 				{
 					const UINT8 *source = (const UINT8 *)(column) + 3;
 					HWR_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
 				}
-				Z_Free(realpatch);
 			}
 			// centre screen
 			if (fabsf((float)vid.width - (float)BASEVIDWIDTH * dupx) > 1.0E-36f)
@@ -469,11 +470,11 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscal
 	fwidth = w;
 	fheight = h;
 
-	if (fwidth > SHORT(gpatch->width))
-		fwidth = SHORT(gpatch->width);
+	if (fwidth > gpatch->width)
+		fwidth = gpatch->width;
 
-	if (fheight > SHORT(gpatch->height))
-		fheight = SHORT(gpatch->height);
+	if (fheight > gpatch->height)
+		fheight = gpatch->height;
 
 	if (pscale != FRACUNIT)
 	{
@@ -503,17 +504,17 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscal
 
 	v[0].z = v[1].z = v[2].z = v[3].z = 1.0f;
 
-	v[0].s = v[3].s = ((sx  )/(float)SHORT(gpatch->width) )*gpatch->max_s;
-	if (sx + w > SHORT(gpatch->width))
-		v[2].s = v[1].s = gpatch->max_s;
+	v[0].s = v[3].s = ((sx)/(float)(gpatch->width))*hwrPatch->max_s;
+	if (sx + w > gpatch->width)
+		v[2].s = v[1].s = hwrPatch->max_s;
 	else
-		v[2].s = v[1].s = ((sx+w)/(float)SHORT(gpatch->width) )*gpatch->max_s;
+		v[2].s = v[1].s = ((sx+w)/(float)(gpatch->width))*hwrPatch->max_s;
 
-	v[0].t = v[1].t = ((sy  )/(float)SHORT(gpatch->height))*gpatch->max_t;
-	if (sy + h > SHORT(gpatch->height))
-		v[2].t = v[3].t = gpatch->max_t;
+	v[0].t = v[1].t = ((sy)/(float)(gpatch->height))*hwrPatch->max_t;
+	if (sy + h > gpatch->height)
+		v[2].t = v[3].t = hwrPatch->max_t;
 	else
-		v[2].t = v[3].t = ((sy+h)/(float)SHORT(gpatch->height))*gpatch->max_t;
+		v[2].t = v[3].t = ((sy+h)/(float)(gpatch->height))*hwrPatch->max_t;
 
 	flags = PF_Translucent|PF_NoDepthTest;
 
@@ -541,7 +542,7 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscal
 void HWR_DrawPic(INT32 x, INT32 y, lumpnum_t lumpnum)
 {
 	FOutVector      v[4];
-	const GLPatch_t    *patch;
+	const patch_t    *patch;
 
 	// make pic ready in hardware cache
 	patch = HWR_GetPic(lumpnum);
@@ -558,10 +559,10 @@ void HWR_DrawPic(INT32 x, INT32 y, lumpnum_t lumpnum)
 
 	v[0].z = v[1].z = v[2].z = v[3].z = 1.0f;
 
-	v[0].s = v[3].s =  0;
-	v[2].s = v[1].s =  patch->max_s;
-	v[0].t = v[1].t =  0;
-	v[2].t = v[3].t =  patch->max_t;
+	v[0].s = v[3].s = 0;
+	v[2].s = v[1].s = ((GLPatch_t *)patch->hardware)->max_s;
+	v[0].t = v[1].t = 0;
+	v[2].t = v[3].t = ((GLPatch_t *)patch->hardware)->max_t;
 
 
 	//Hurdler: Boris, the same comment as above... but maybe for pics
@@ -570,7 +571,7 @@ void HWR_DrawPic(INT32 x, INT32 y, lumpnum_t lumpnum)
 	// But then, the question is: why not 0 instead of PF_Masked ?
 	// or maybe PF_Environment ??? (like what I said above)
 	// BP: PF_Environment don't change anything ! and 0 is undifined
-	HWD.pfnDrawPolygon(NULL, v, 4, PF_Translucent | PF_NoDepthTest | PF_Clip | PF_NoZClip);
+	HWD.pfnDrawPolygon(NULL, v, 4, PF_Translucent | PF_NoDepthTest);
 }
 
 // ==========================================================================
@@ -934,7 +935,7 @@ void HWR_DrawViewBorder(INT32 clearlines)
 	INT32 top, side;
 	INT32 baseviewwidth, baseviewheight;
 	INT32 basewindowx, basewindowy;
-	GLPatch_t *patch;
+	patch_t *patch;
 
 //    if (gl_viewwidth == vid.width)
 //        return;
diff --git a/src/hardware/hw_drv.h b/src/hardware/hw_drv.h
index aaa41e86f4a2f0a6daa9d4378c8fc1edc717f736..de17f97d255509d6b335fd26ac378e577ab1649e 100644
--- a/src/hardware/hw_drv.h
+++ b/src/hardware/hw_drv.h
@@ -42,6 +42,7 @@ EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags);
 EXPORT void HWRAPI(ClearBuffer) (FBOOLEAN ColorMask, FBOOLEAN DepthMask, FRGBAFloat *ClearColor);
 EXPORT void HWRAPI(SetTexture) (FTextureInfo *TexInfo);
 EXPORT void HWRAPI(UpdateTexture) (FTextureInfo *TexInfo);
+EXPORT void HWRAPI(DeleteTexture) (FTextureInfo *TexInfo);
 EXPORT void HWRAPI(ReadRect) (INT32 x, INT32 y, INT32 width, INT32 height, INT32 dst_stride, UINT16 *dst_data);
 EXPORT void HWRAPI(GClipRect) (INT32 minx, INT32 miny, INT32 maxx, INT32 maxy, float nearclip);
 EXPORT void HWRAPI(ClearMipMapCache) (void);
@@ -70,7 +71,7 @@ EXPORT void HWRAPI(PostImgRedraw) (float points[SCREENVERTS][SCREENVERTS][2]);
 // jimita
 EXPORT boolean HWRAPI(CompileShaders) (void);
 EXPORT void HWRAPI(CleanShaders) (void);
-EXPORT void HWRAPI(SetShader) (int shader);
+EXPORT void HWRAPI(SetShader) (int type);
 EXPORT void HWRAPI(UnSetShader) (void);
 
 EXPORT void HWRAPI(SetShaderInfo) (hwdshaderinfo_t info, INT32 value);
@@ -95,6 +96,7 @@ struct hwdriver_s
 	ClearBuffer         pfnClearBuffer;
 	SetTexture          pfnSetTexture;
 	UpdateTexture       pfnUpdateTexture;
+	DeleteTexture       pfnDeleteTexture;
 	ReadRect            pfnReadRect;
 	GClipRect           pfnGClipRect;
 	ClearMipMapCache    pfnClearMipMapCache;
diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h
index 6ede8448bf88cc6ee2b9a7083546e2b2e059e904..112b241ef2a04375012821711bbd9fe7d5714806 100644
--- a/src/hardware/hw_glob.h
+++ b/src/hardware/hw_glob.h
@@ -62,19 +62,32 @@ typedef struct
 typedef struct gl_vissprite_s
 {
 	float x1, x2;
-	float tz, ty;
+	float z1, z2;
+	float gz, gzt;
+
+	float tz;
 	float tracertz; // for MF2_LINKDRAW sprites, this contains tracer's tz for use in sorting
-	//lumpnum_t patchlumpnum;
-	GLPatch_t *gpatch;
-	boolean flip;
-	UINT8 translucency;       //alpha level 0-255
-	mobj_t *mobj; // NOTE: This is a precipmobj_t if precip is true !!! Watch out.
+
+	float scale;
+	float shadowheight, shadowscale;
+
+	float spritexscale, spriteyscale;
+	float spritexoffset, spriteyoffset;
+
+	UINT32 renderflags;
+	UINT8 rotateflags;
+
+	boolean flip, vflip;
 	boolean precip; // Tails 08-25-2002
-	boolean vflip;
-   //Hurdler: 25/04/2000: now support colormap in hardware mode
+	boolean rotated;
+	UINT8 translucency;       //alpha level 0-255
+
+	//Hurdler: 25/04/2000: now support colormap in hardware mode
 	UINT8 *colormap;
 	INT32 dispoffset; // copy of info->dispoffset, affects ordering but not drawing
-	float z1, z2;
+
+	patch_t *gpatch;
+	mobj_t *mobj; // NOTE: This is a precipmobj_t if precip is true !!! Watch out.
 } gl_vissprite_t;
 
 // --------
@@ -86,25 +99,35 @@ extern size_t addsubsector;
 void HWR_InitPolyPool(void);
 void HWR_FreePolyPool(void);
 
+void HWR_FreeExtraSubsectors(void);
+
 // --------
 // hw_cache.c
 // --------
-void HWR_InitTextureCache(void);
-void HWR_FreeTextureCache(void);
-void HWR_FreeMipmapCache(void);
-void HWR_FreeExtraSubsectors(void);
+void HWR_InitMapTextures(void);
+void HWR_LoadMapTextures(size_t pnumtextures);
+void HWR_FreeMapTextures(void);
+
+patch_t *HWR_GetCachedGLPatchPwad(UINT16 wad, UINT16 lump);
+patch_t *HWR_GetCachedGLPatch(lumpnum_t lumpnum);
+
+void HWR_GetPatch(patch_t *patch);
+void HWR_GetMappedPatch(patch_t *patch, const UINT8 *colormap);
+void HWR_GetFadeMask(lumpnum_t fademasklumpnum);
+patch_t *HWR_GetPic(lumpnum_t lumpnum);
 
+GLMapTexture_t *HWR_GetTexture(INT32 tex);
 void HWR_GetLevelFlat(levelflat_t *levelflat);
 void HWR_LiterallyGetFlat(lumpnum_t flatlumpnum);
-GLMapTexture_t *HWR_GetTexture(INT32 tex);
-void HWR_GetPatch(GLPatch_t *gpatch);
-void HWR_GetMappedPatch(GLPatch_t *gpatch, const UINT8 *colormap);
+
+void HWR_FreeTexture(patch_t *patch);
+void HWR_FreeTextureColormaps(patch_t *patch);
+void HWR_ClearAllTextures(void);
+void HWR_FreeColormapCache(void);
 void HWR_UnlockCachedPatch(GLPatch_t *gpatch);
-GLPatch_t *HWR_GetPic(lumpnum_t lumpnum);
+
 void HWR_SetPalette(RGBA_t *palette);
-GLPatch_t *HWR_GetCachedGLPatchPwad(UINT16 wad, UINT16 lump);
-GLPatch_t *HWR_GetCachedGLPatch(lumpnum_t lumpnum);
-void HWR_GetFadeMask(lumpnum_t fademasklumpnum);
+
 
 // --------
 // hw_draw.c
diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c
index 32c2d550d4355b02b039bb2a8ee708b9afd17c51..987d70c69e22b293bb8d07cce5ce10520b5d387e 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -35,8 +35,7 @@
 
 #define DL_HIGH_QUALITY
 //#define STATICLIGHT  //Hurdler: TODO!
-//#define LIGHTMAPFLAGS  (PF_Masked|PF_Clip|PF_NoAlphaTest)  // debug see overdraw
-#define LIGHTMAPFLAGS (PF_Modulated|PF_Additive|PF_Clip)
+#define LIGHTMAPFLAGS (PF_Modulated|PF_AdditiveSource)
 
 #ifdef ALAM_LIGHTING
 static dynlights_t view_dynlights[2]; // 2 players in splitscreen mode
@@ -1056,7 +1055,7 @@ void HWR_DoCoronasLighting(FOutVector *outVerts, gl_vissprite_t *spr)
 
 		HWR_GetPic(coronalumpnum);  /// \todo use different coronas
 
-		HWD.pfnDrawPolygon (&Surf, light, 4, PF_Modulated | PF_Additive | PF_Clip | PF_Corona | PF_NoDepthTest);
+		HWD.pfnDrawPolygon (&Surf, light, 4, PF_Modulated | PF_AdditiveSource | PF_Corona | PF_NoDepthTest);
 	}
 }
 #endif
@@ -1144,7 +1143,7 @@ void HWR_DrawCoronas(void)
 		light[3].y = cy+size*1.33f;
 		light[3].s = 0.0f;   light[3].t = 1.0f;
 
-		HWD.pfnDrawPolygon (&Surf, light, 4, PF_Modulated | PF_Additive | PF_Clip | PF_NoDepthTest | PF_Corona);
+		HWD.pfnDrawPolygon (&Surf, light, 4, PF_Modulated | PF_AdditiveSource | PF_NoDepthTest | PF_Corona);
 	}
 }
 #endif
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 4268556e3b02508e8ee25ce04caad0ac78824e7a..2a694b95f2670e9c2054702eaf933472796a2cad 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -25,6 +25,7 @@
 #include "../p_local.h"
 #include "../p_setup.h"
 #include "../r_local.h"
+#include "../r_patch.h"
 #include "../r_picformats.h"
 #include "../r_bsp.h"
 #include "../d_clisrv.h"
@@ -163,9 +164,11 @@ int ps_hw_numcolors = 0;
 int ps_hw_batchsorttime = 0;
 int ps_hw_batchdrawtime = 0;
 
+boolean gl_init = false;
+boolean gl_maploaded = false;
+boolean gl_sessioncommandsadded = false;
 boolean gl_shadersavailable = true;
 
-
 // ==========================================================================
 // Lighting
 // ==========================================================================
@@ -693,101 +696,73 @@ static void HWR_RenderSkyPlane(extrasubsector_t *xsub, fixed_t fixedheight)
 		v3d->z = pv->y;
 	}
 
-	HWD.pfnDrawPolygon(NULL, planeVerts, nrPlaneVerts,
-	 PF_Clip|PF_Invisible|PF_NoTexture|PF_Occlude);
+	HWD.pfnDrawPolygon(NULL, planeVerts, nrPlaneVerts, PF_Invisible|PF_NoTexture|PF_Occlude);
 }
 #endif //polysky
 
 #endif //doplanes
 
-/*
-   wallVerts order is :
-		3--2
-		| /|
-		|/ |
-		0--1
-*/
-#ifdef WALLSPLATS
-static void HWR_DrawSegsSplats(FSurfaceInfo * pSurf)
+FBITFIELD HWR_GetBlendModeFlag(INT32 ast)
 {
-	FOutVector wallVerts[4];
-	wallsplat_t *splat;
-	GLPatch_t *gpatch;
-	fixed_t i;
-	// seg bbox
-	fixed_t segbbox[4];
-
-	M_ClearBox(segbbox);
-	M_AddToBox(segbbox,
-		FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv1)->x),
-		FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv1)->y));
-	M_AddToBox(segbbox,
-		FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv2)->x),
-		FLOAT_TO_FIXED(((polyvertex_t *)gl_curline->pv2)->y));
-
-	splat = (wallsplat_t *)gl_curline->linedef->splats;
-	for (; splat; splat = splat->next)
-	{
-		//BP: don't draw splat extern to this seg
-		//    this is quick fix best is explain in logboris.txt at 12-4-2000
-		if (!M_PointInBox(segbbox,splat->v1.x,splat->v1.y) && !M_PointInBox(segbbox,splat->v2.x,splat->v2.y))
-			continue;
-
-		gpatch = W_CachePatchNum(splat->patch, PU_PATCH);
-		HWR_GetPatch(gpatch);
-
-		wallVerts[0].x = wallVerts[3].x = FIXED_TO_FLOAT(splat->v1.x);
-		wallVerts[0].z = wallVerts[3].z = FIXED_TO_FLOAT(splat->v1.y);
-		wallVerts[2].x = wallVerts[1].x = FIXED_TO_FLOAT(splat->v2.x);
-		wallVerts[2].z = wallVerts[1].z = FIXED_TO_FLOAT(splat->v2.y);
+	switch (ast)
+	{
+		case AST_ADD:
+			return PF_Additive;
+		case AST_SUBTRACT:
+			return PF_Subtractive;
+		case AST_REVERSESUBTRACT:
+			return PF_ReverseSubtract;
+		case AST_MODULATE:
+			return PF_Multiplicative;
+		default:
+			return PF_Translucent;
+	}
 
-		i = splat->top;
-		if (splat->yoffset)
-			i += *splat->yoffset;
+	return 0;
+}
 
-		wallVerts[2].y = wallVerts[3].y = FIXED_TO_FLOAT(i)+(gpatch->height>>1);
-		wallVerts[0].y = wallVerts[1].y = FIXED_TO_FLOAT(i)-(gpatch->height>>1);
+UINT8 HWR_GetTranstableAlpha(INT32 transtablenum)
+{
+	transtablenum = max(min(transtablenum, tr_trans90), 0);
 
-		wallVerts[3].s = wallVerts[3].t = wallVerts[2].s = wallVerts[0].t = 0.0f;
-		wallVerts[1].s = wallVerts[1].t = wallVerts[2].t = wallVerts[0].s = 1.0f;
+	switch (transtablenum)
+	{
+		case 0          : return 0xff;
+		case tr_trans10 : return 0xe6;
+		case tr_trans20 : return 0xcc;
+		case tr_trans30 : return 0xb3;
+		case tr_trans40 : return 0x99;
+		case tr_trans50 : return 0x80;
+		case tr_trans60 : return 0x66;
+		case tr_trans70 : return 0x4c;
+		case tr_trans80 : return 0x33;
+		case tr_trans90 : return 0x19;
+	}
 
-		switch (splat->flags & SPLATDRAWMODE_MASK)
-		{
-			case SPLATDRAWMODE_OPAQUE :
-				pSurf.PolyColor.s.alpha = 0xff;
-				i = PF_Translucent;
-				break;
-			case SPLATDRAWMODE_TRANS :
-				pSurf.PolyColor.s.alpha = 128;
-				i = PF_Translucent;
-				break;
-			case SPLATDRAWMODE_SHADE :
-				pSurf.PolyColor.s.alpha = 0xff;
-				i = PF_Substractive;
-				break;
-		}
+	return 0xff;
+}
 
-		HWD.pfnSetShader(SHADER_WALL);	// wall shader
-		HWD.pfnDrawPolygon(&pSurf, wallVerts, 4, i|PF_Modulated|PF_Decal);
+FBITFIELD HWR_SurfaceBlend(INT32 style, INT32 transtablenum, FSurfaceInfo *pSurf)
+{
+	if (!transtablenum)
+	{
+		pSurf->PolyColor.s.alpha = 0xff;
+		return PF_Masked;
 	}
+
+	pSurf->PolyColor.s.alpha = HWR_GetTranstableAlpha(transtablenum);
+	return HWR_GetBlendModeFlag(style);
 }
-#endif
 
 FBITFIELD HWR_TranstableToAlpha(INT32 transtablenum, FSurfaceInfo *pSurf)
 {
-	switch (transtablenum)
+	if (!transtablenum)
 	{
-		case 0          : pSurf->PolyColor.s.alpha = 0x00;return  PF_Masked;
-		case tr_trans10 : pSurf->PolyColor.s.alpha = 0xe6;return  PF_Translucent;
-		case tr_trans20 : pSurf->PolyColor.s.alpha = 0xcc;return  PF_Translucent;
-		case tr_trans30 : pSurf->PolyColor.s.alpha = 0xb3;return  PF_Translucent;
-		case tr_trans40 : pSurf->PolyColor.s.alpha = 0x99;return  PF_Translucent;
-		case tr_trans50 : pSurf->PolyColor.s.alpha = 0x80;return  PF_Translucent;
-		case tr_trans60 : pSurf->PolyColor.s.alpha = 0x66;return  PF_Translucent;
-		case tr_trans70 : pSurf->PolyColor.s.alpha = 0x4c;return  PF_Translucent;
-		case tr_trans80 : pSurf->PolyColor.s.alpha = 0x33;return  PF_Translucent;
-		case tr_trans90 : pSurf->PolyColor.s.alpha = 0x19;return  PF_Translucent;
+		pSurf->PolyColor.s.alpha = 0x00;
+		return PF_Masked;
 	}
+
+	pSurf->PolyColor.s.alpha = HWR_GetTranstableAlpha(transtablenum);
 	return PF_Translucent;
 }
 
@@ -797,19 +772,21 @@ static void HWR_AddTransparentWall(FOutVector *wallVerts, FSurfaceInfo *pSurf, I
 // Wall generation from subsector segs
 // ==========================================================================
 
+/*
+   wallVerts order is :
+		3--2
+		| /|
+		|/ |
+		0--1
+*/
+
 //
 // HWR_ProjectWall
 //
 static void HWR_ProjectWall(FOutVector *wallVerts, FSurfaceInfo *pSurf, FBITFIELD blendmode, INT32 lightlevel, extracolormap_t *wallcolormap)
 {
 	HWR_Lighting(pSurf, lightlevel, wallcolormap);
-
 	HWR_ProcessPolygon(pSurf, wallVerts, 4, blendmode|PF_Modulated|PF_Occlude, SHADER_WALL, false); // wall shader
-
-#ifdef WALLSPLATS
-	if (gl_curline->linedef->splats && cv_splats.value)
-		HWR_DrawSegsSplats(pSurf);
-#endif
 }
 
 // ==========================================================================
@@ -1608,7 +1585,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 
 
 	//Hurdler: 3d-floors test
-	if (gl_frontsector && gl_backsector && gl_frontsector->tag != gl_backsector->tag && (gl_backsector->ffloors || gl_frontsector->ffloors))
+	if (gl_frontsector && gl_backsector && !Tag_Compare(&gl_frontsector->tags, &gl_backsector->tags) && (gl_backsector->ffloors || gl_frontsector->ffloors))
 	{
 		ffloor_t * rover;
 		fixed_t    highcut = 0, lowcut = 0;
@@ -1625,6 +1602,18 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 		{
 			for (rover = gl_backsector->ffloors; rover; rover = rover->next)
 			{
+				boolean bothsides = false;
+				// Skip if it exists on both sectors.
+				ffloor_t * r2;
+				for (r2 = gl_frontsector->ffloors; r2; r2 = r2->next)
+					if (rover->master == r2->master)
+					{
+						bothsides = true;
+						break;
+					}
+
+				if (bothsides) continue;
+
 				if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERSIDES))
 					continue;
 				if (!(rover->flags & FF_ALLSIDES) && rover->flags & FF_INVERTSIDES)
@@ -1759,6 +1748,18 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom
 		{
 			for (rover = gl_frontsector->ffloors; rover; rover = rover->next)
 			{
+				boolean bothsides = false;
+				// Skip if it exists on both sectors.
+				ffloor_t * r2;
+				for (r2 = gl_backsector->ffloors; r2; r2 = r2->next)
+					if (rover->master == r2->master)
+					{
+						bothsides = true;
+						break;
+					}
+
+				if (bothsides) continue;
+
 				if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERSIDES))
 					continue;
 				if (!(rover->flags & FF_ALLSIDES || rover->flags & FF_INVERTSIDES))
@@ -2384,7 +2385,7 @@ static void HWR_AddLine(seg_t * line)
 			if (!line->polyseg &&
 				!line->sidedef->midtexture
 				&& ((!gl_frontsector->ffloors && !gl_backsector->ffloors)
-					|| (gl_frontsector->tag == gl_backsector->tag)))
+					|| Tag_Compare(&gl_frontsector->tags, &gl_backsector->tags)))
 				return; // line is empty, don't even bother
 			// treat like wide open window instead
 			HWR_ProcessSeg(); // Doesn't need arguments because they're defined globally :D
@@ -2423,7 +2424,7 @@ static void HWR_AddLine(seg_t * line)
 		if (!line->polyseg &&
 			!line->sidedef->midtexture
 			&& ((!gl_frontsector->ffloors && !gl_backsector->ffloors)
-				|| (gl_frontsector->tag == gl_backsector->tag)))
+				|| Tag_Compare(&gl_frontsector->tags, &gl_backsector->tags)))
 			return; // line is empty, don't even bother
 
 		goto clippass; // treat like wide open window instead
@@ -2839,10 +2840,10 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 	if (blendmode & PF_Translucent)
 	{
 		Surf.PolyColor.s.alpha = (UINT8)alpha;
-		blendmode |= PF_Modulated|PF_Occlude|PF_Clip;
+		blendmode |= PF_Modulated|PF_Occlude;
 	}
 	else
-		blendmode |= PF_Masked|PF_Modulated|PF_Clip;
+		blendmode |= PF_Masked|PF_Modulated;
 
 	HWR_ProcessPolygon(&Surf, planeVerts, nrPlaneVerts, blendmode, SHADER_FLOOR, false); // floor shader
 }
@@ -3512,7 +3513,7 @@ static void HWR_LinkDrawHackFinish(void)
 	{
 		// draw sprite shape, only to z-buffer
 		HWR_GetPatch(linkdrawlist[i].spr->gpatch);
-		HWR_ProcessPolygon(&surf, linkdrawlist[i].verts, 4, PF_Translucent|PF_Occlude|PF_Invisible|PF_Clip, 0, false);
+		HWR_ProcessPolygon(&surf, linkdrawlist[i].verts, 4, PF_Translucent|PF_Occlude|PF_Invisible, 0, false);
 	}
 	// reset list
 	linkdrawcount = 0;
@@ -3560,7 +3561,7 @@ static boolean HWR_DoCulling(line_t *cullheight, line_t *viewcullheight, float v
 
 static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale)
 {
-	GLPatch_t *gpatch;
+	patch_t *gpatch;
 	FOutVector shadowVerts[4];
 	FSurfaceInfo sSurf;
 	float fscale; float fx; float fy; float offset;
@@ -3586,12 +3587,12 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale)
 	if (alpha >= 255) return;
 	alpha = 255 - alpha;
 
-	gpatch = (GLPatch_t *)W_CachePatchName("DSHADOW", PU_CACHE);
-	if (!(gpatch && gpatch->mipmap->format)) return;
+	gpatch = (patch_t *)W_CachePatchName("DSHADOW", PU_SPRITE);
+	if (!(gpatch && ((GLPatch_t *)gpatch->hardware)->mipmap->format)) return;
 	HWR_GetPatch(gpatch);
 
 	scalemul = FixedMul(FRACUNIT - floordiff/640, scale);
-	scalemul = FixedMul(scalemul, (thing->radius*2) / SHORT(gpatch->height));
+	scalemul = FixedMul(scalemul, (thing->radius*2) / gpatch->height);
 
 	fscale = FIXED_TO_FLOAT(scalemul);
 	fx = FIXED_TO_FLOAT(thing->x);
@@ -3603,9 +3604,9 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale)
 	//  0--1
 
 	if (thing && fabsf(fscale - 1.0f) > 1.0E-36f)
-		offset = (SHORT(gpatch->height)/2) * fscale;
+		offset = ((gpatch->height)/2) * fscale;
 	else
-		offset = (float)(SHORT(gpatch->height)/2);
+		offset = (float)((gpatch->height)/2);
 
 	shadowVerts[2].x = shadowVerts[3].x = fx + offset;
 	shadowVerts[1].x = shadowVerts[0].x = fx - offset;
@@ -3635,28 +3636,29 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale)
 	}
 
 	shadowVerts[0].s = shadowVerts[3].s = 0;
-	shadowVerts[2].s = shadowVerts[1].s = gpatch->max_s;
+	shadowVerts[2].s = shadowVerts[1].s = ((GLPatch_t *)gpatch->hardware)->max_s;
 
 	shadowVerts[3].t = shadowVerts[2].t = 0;
-	shadowVerts[0].t = shadowVerts[1].t = gpatch->max_t;
+	shadowVerts[0].t = shadowVerts[1].t = ((GLPatch_t *)gpatch->hardware)->max_t;
 
-	if (thing->subsector->sector->numlights)
+	if (!(thing->renderflags & RF_NOCOLORMAPS))
 	{
-		light = R_GetPlaneLight(thing->subsector->sector, groundz, false); // Always use the light at the top instead of whatever I was doing before
+		if (thing->subsector->sector->numlights)
+		{
+			// Always use the light at the top instead of whatever I was doing before
+			light = R_GetPlaneLight(thing->subsector->sector, groundz, false);
 
-		if (*thing->subsector->sector->lightlist[light].extra_colormap)
-			colormap = *thing->subsector->sector->lightlist[light].extra_colormap;
-	}
-	else
-	{
-		if (thing->subsector->sector->extra_colormap)
+			if (*thing->subsector->sector->lightlist[light].extra_colormap)
+				colormap = *thing->subsector->sector->lightlist[light].extra_colormap;
+		}
+		else if (thing->subsector->sector->extra_colormap)
 			colormap = thing->subsector->sector->extra_colormap;
 	}
 
 	HWR_Lighting(&sSurf, 0, colormap);
 	sSurf.PolyColor.s.alpha = alpha;
 
-	HWR_ProcessPolygon(&sSurf, shadowVerts, 4, PF_Translucent|PF_Modulated|PF_Clip, SHADER_SPRITE, false); // sprite shader
+	HWR_ProcessPolygon(&sSurf, shadowVerts, 4, PF_Translucent|PF_Modulated, SHADER_SPRITE, false); // sprite shader
 }
 
 // This is expecting a pointer to an array containing 4 wallVerts for a sprite
@@ -3676,17 +3678,17 @@ static void HWR_RotateSpritePolyToAim(gl_vissprite_t *spr, FOutVector *wallVerts
 		// X, Y, AND Z need to be manipulated for the polys to rotate around the
 		// origin, because of how the origin setting works I believe that should
 		// be mobj->z or mobj->z + mobj->height
-		wallVerts[2].y = wallVerts[3].y = (spr->ty - basey) * gl_viewludsin + basey;
+		wallVerts[2].y = wallVerts[3].y = (spr->gzt - basey) * gl_viewludsin + basey;
 		wallVerts[0].y = wallVerts[1].y = (lowy - basey) * gl_viewludsin + basey;
 		// translate back to be around 0 before translating back
-		wallVerts[3].x += ((spr->ty - basey) * gl_viewludcos) * gl_viewcos;
-		wallVerts[2].x += ((spr->ty - basey) * gl_viewludcos) * gl_viewcos;
+		wallVerts[3].x += ((spr->gzt - basey) * gl_viewludcos) * gl_viewcos;
+		wallVerts[2].x += ((spr->gzt - basey) * gl_viewludcos) * gl_viewcos;
 
 		wallVerts[0].x += ((lowy - basey) * gl_viewludcos) * gl_viewcos;
 		wallVerts[1].x += ((lowy - basey) * gl_viewludcos) * gl_viewcos;
 
-		wallVerts[3].z += ((spr->ty - basey) * gl_viewludcos) * gl_viewsin;
-		wallVerts[2].z += ((spr->ty - basey) * gl_viewludcos) * gl_viewsin;
+		wallVerts[3].z += ((spr->gzt - basey) * gl_viewludcos) * gl_viewsin;
+		wallVerts[2].z += ((spr->gzt - basey) * gl_viewludcos) * gl_viewsin;
 
 		wallVerts[0].z += ((lowy - basey) * gl_viewludcos) * gl_viewsin;
 		wallVerts[1].z += ((lowy - basey) * gl_viewludcos) * gl_viewsin;
@@ -3695,17 +3697,17 @@ static void HWR_RotateSpritePolyToAim(gl_vissprite_t *spr, FOutVector *wallVerts
 
 static void HWR_SplitSprite(gl_vissprite_t *spr)
 {
-	float this_scale = 1.0f;
 	FOutVector wallVerts[4];
 	FOutVector baseWallVerts[4]; // This is what the verts should end up as
-	GLPatch_t *gpatch;
+	patch_t *gpatch;
 	FSurfaceInfo Surf;
-	const boolean hires = (spr->mobj && spr->mobj->skin && ((skin_t *)spr->mobj->skin)->flags & SF_HIRES);
-	extracolormap_t *colormap;
+	extracolormap_t *colormap = NULL;
 	FUINT lightlevel;
+	boolean lightset = true;
 	FBITFIELD blend = 0;
 	FBITFIELD occlusion;
 	boolean use_linkdraw_hack = false;
+	boolean splat = R_ThingIsFloorSprite(spr->mobj);
 	UINT8 alpha;
 
 	INT32 i;
@@ -3721,12 +3723,7 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 	fixed_t temp;
 	fixed_t v1x, v1y, v2x, v2y;
 
-	this_scale = FIXED_TO_FLOAT(spr->mobj->scale);
-
-	if (hires)
-		this_scale = this_scale * FIXED_TO_FLOAT(((skin_t *)spr->mobj->skin)->highresscale);
-
-	gpatch = spr->gpatch; //W_CachePatchNum(spr->patchlumpnum, PU_CACHE);
+	gpatch = spr->gpatch;
 
 	// cache the patch in the graphics card memory
 	//12/12/99: Hurdler: same comment as above (for md2)
@@ -3738,11 +3735,8 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 	baseWallVerts[0].z = baseWallVerts[3].z = spr->z1;
 	baseWallVerts[1].z = baseWallVerts[2].z = spr->z2;
 
-	baseWallVerts[2].y = baseWallVerts[3].y = spr->ty;
-	if (spr->mobj && fabsf(this_scale - 1.0f) > 1.0E-36f)
-		baseWallVerts[0].y = baseWallVerts[1].y = spr->ty - gpatch->height * this_scale;
-	else
-		baseWallVerts[0].y = baseWallVerts[1].y = spr->ty - gpatch->height;
+	baseWallVerts[2].y = baseWallVerts[3].y = spr->gzt;
+	baseWallVerts[0].y = baseWallVerts[1].y = spr->gz;
 
 	v1x = FLOAT_TO_FIXED(spr->x1);
 	v1y = FLOAT_TO_FIXED(spr->z1);
@@ -3751,39 +3745,42 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 
 	if (spr->flip)
 	{
-		baseWallVerts[0].s = baseWallVerts[3].s = gpatch->max_s;
+		baseWallVerts[0].s = baseWallVerts[3].s = ((GLPatch_t *)gpatch->hardware)->max_s;
 		baseWallVerts[2].s = baseWallVerts[1].s = 0;
 	}
 	else
 	{
 		baseWallVerts[0].s = baseWallVerts[3].s = 0;
-		baseWallVerts[2].s = baseWallVerts[1].s = gpatch->max_s;
+		baseWallVerts[2].s = baseWallVerts[1].s = ((GLPatch_t *)gpatch->hardware)->max_s;
 	}
 
 	// flip the texture coords (look familiar?)
 	if (spr->vflip)
 	{
-		baseWallVerts[3].t = baseWallVerts[2].t = gpatch->max_t;
+		baseWallVerts[3].t = baseWallVerts[2].t = ((GLPatch_t *)gpatch->hardware)->max_t;
 		baseWallVerts[0].t = baseWallVerts[1].t = 0;
 	}
 	else
 	{
 		baseWallVerts[3].t = baseWallVerts[2].t = 0;
-		baseWallVerts[0].t = baseWallVerts[1].t = gpatch->max_t;
+		baseWallVerts[0].t = baseWallVerts[1].t = ((GLPatch_t *)gpatch->hardware)->max_t;
 	}
 
-	// if it has a dispoffset, push it a little towards the camera
-	if (spr->dispoffset) {
-		float co = -gl_viewcos*(0.05f*spr->dispoffset);
-		float si = -gl_viewsin*(0.05f*spr->dispoffset);
-		baseWallVerts[0].z = baseWallVerts[3].z = baseWallVerts[0].z+si;
-		baseWallVerts[1].z = baseWallVerts[2].z = baseWallVerts[1].z+si;
-		baseWallVerts[0].x = baseWallVerts[3].x = baseWallVerts[0].x+co;
-		baseWallVerts[1].x = baseWallVerts[2].x = baseWallVerts[1].x+co;
-	}
+	if (!splat)
+	{
+		// if it has a dispoffset, push it a little towards the camera
+		if (spr->dispoffset) {
+			float co = -gl_viewcos*(0.05f*spr->dispoffset);
+			float si = -gl_viewsin*(0.05f*spr->dispoffset);
+			baseWallVerts[0].z = baseWallVerts[3].z = baseWallVerts[0].z+si;
+			baseWallVerts[1].z = baseWallVerts[2].z = baseWallVerts[1].z+si;
+			baseWallVerts[0].x = baseWallVerts[3].x = baseWallVerts[0].x+co;
+			baseWallVerts[1].x = baseWallVerts[2].x = baseWallVerts[1].x+co;
+		}
 
-	// Let dispoffset work first since this adjust each vertex
-	HWR_RotateSpritePolyToAim(spr, baseWallVerts, false);
+		// Let dispoffset work first since this adjust each vertex
+		HWR_RotateSpritePolyToAim(spr, baseWallVerts, false);
+	}
 
 	realtop = top = baseWallVerts[3].y;
 	realbot = bot = baseWallVerts[0].y;
@@ -3815,10 +3812,15 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 	else if (spr->mobj->flags2 & MF2_SHADOW)
 	{
 		Surf.PolyColor.s.alpha = 0x40;
-		blend = PF_Translucent;
+		blend = HWR_GetBlendModeFlag(spr->mobj->blendmode);
 	}
 	else if (spr->mobj->frame & FF_TRANSMASK)
-		blend = HWR_TranstableToAlpha((spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT, &Surf);
+	{
+		INT32 trans = (spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT;
+		if (spr->mobj->blendmode == AST_TRANSLUCENT && trans >= NUMTRANSMAPS)
+			return;
+		blend = HWR_SurfaceBlend(spr->mobj->blendmode, trans, &Surf);
+	}
 	else
 	{
 		// BP: i agree that is little better in environement but it don't
@@ -3826,7 +3828,7 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 		// Hurdler: PF_Environement would be cool, but we need to fix
 		//          the issue with the fog before
 		Surf.PolyColor.s.alpha = 0xFF;
-		blend = PF_Translucent|occlusion;
+		blend = HWR_GetBlendModeFlag(spr->mobj->blendmode)|occlusion;
 		if (!occlusion) use_linkdraw_hack = true;
 	}
 
@@ -3834,21 +3836,28 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 
 	// Start with the lightlevel and colormap from the top of the sprite
 	lightlevel = *list[sector->numlights - 1].lightlevel;
-	colormap = *list[sector->numlights - 1].extra_colormap;
+	if (!(spr->mobj->renderflags & RF_NOCOLORMAPS))
+		colormap = *list[sector->numlights - 1].extra_colormap;
+
 	i = 0;
 	temp = FLOAT_TO_FIXED(realtop);
 
-	if (spr->mobj->frame & FF_FULLBRIGHT)
+	if (R_ThingIsFullBright(spr->mobj))
 		lightlevel = 255;
+	else if (R_ThingIsFullDark(spr->mobj))
+		lightlevel = 0;
+	else
+		lightset = false;
 
 	for (i = 1; i < sector->numlights; i++)
 	{
 		fixed_t h = P_GetLightZAt(&sector->lightlist[i], spr->mobj->x, spr->mobj->y);
 		if (h <= temp)
 		{
-			if (!(spr->mobj->frame & FF_FULLBRIGHT))
+			if (!lightset)
 				lightlevel = *list[i-1].lightlevel > 255 ? 255 : *list[i-1].lightlevel;
-			colormap = *list[i-1].extra_colormap;
+			if (!(spr->mobj->renderflags & RF_NOCOLORMAPS))
+				colormap = *list[i-1].extra_colormap;
 			break;
 		}
 	}
@@ -3861,9 +3870,10 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 		// even if we aren't changing colormap or lightlevel, we still need to continue drawing down the sprite
 		if (!(list[i].flags & FF_NOSHADE) && (list[i].flags & FF_CUTSPRITES))
 		{
-			if (!(spr->mobj->frame & FF_FULLBRIGHT))
+			if (!lightset)
 				lightlevel = *list[i].lightlevel > 255 ? 255 : *list[i].lightlevel;
-			colormap = *list[i].extra_colormap;
+			if (!(spr->mobj->renderflags & RF_NOCOLORMAPS))
+				colormap = *list[i].extra_colormap;
 		}
 
 		if (i + 1 < sector->numlights)
@@ -3930,7 +3940,7 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 
 		Surf.PolyColor.s.alpha = alpha;
 
-		HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated|PF_Clip, SHADER_SPRITE, false); // sprite shader
+		HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated, SHADER_SPRITE, false); // sprite shader
 
 		if (use_linkdraw_hack)
 			HWR_LinkDrawHackAdd(wallVerts, spr);
@@ -3959,7 +3969,7 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 
 	Surf.PolyColor.s.alpha = alpha;
 
-	HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated|PF_Clip, SHADER_SPRITE, false); // sprite shader
+	HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated, SHADER_SPRITE, false); // sprite shader
 
 	if (use_linkdraw_hack)
 		HWR_LinkDrawHackAdd(wallVerts, spr);
@@ -3972,16 +3982,10 @@ static void HWR_SplitSprite(gl_vissprite_t *spr)
 // -----------------+
 static void HWR_DrawSprite(gl_vissprite_t *spr)
 {
-	float this_scale = 1.0f;
 	FOutVector wallVerts[4];
-	GLPatch_t *gpatch; // sprite patch converted to hardware
+	patch_t *gpatch; // sprite patch converted to hardware
 	FSurfaceInfo Surf;
-	const boolean hires = (spr->mobj && spr->mobj->skin && ((skin_t *)spr->mobj->skin)->flags & SF_HIRES);
-	//const boolean papersprite = (spr->mobj && (spr->mobj->frame & FF_PAPERSPRITE));
-	if (spr->mobj)
-		this_scale = FIXED_TO_FLOAT(spr->mobj->scale);
-	if (hires)
-		this_scale = this_scale * FIXED_TO_FLOAT(((skin_t *)spr->mobj->skin)->highresscale);
+	const boolean splat = R_ThingIsFloorSprite(spr->mobj);
 
 	if (!spr->mobj)
 		return;
@@ -3989,7 +3993,7 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 	if (!spr->mobj->subsector)
 		return;
 
-	if (spr->mobj->subsector->sector->numlights)
+	if (spr->mobj->subsector->sector->numlights && !splat)
 	{
 		HWR_SplitSprite(spr);
 		return;
@@ -4001,7 +4005,7 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 	//          sure to do it the right way. So actually, we keep normal sprite
 	//          in memory and we add the md2 model if it exists for that sprite
 
-	gpatch = spr->gpatch; //W_CachePatchNum(spr->patchlumpnum, PU_CACHE);
+	gpatch = spr->gpatch;
 
 #ifdef ALAM_LIGHTING
 	if (!(spr->mobj->flags2 & MF2_DEBRIS) && (spr->mobj->sprite != SPR_PLAY ||
@@ -4016,37 +4020,144 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 	//  |/ |
 	//  0--1
 
-	// these were already scaled in HWR_ProjectSprite
-	wallVerts[0].x = wallVerts[3].x = spr->x1;
-	wallVerts[2].x = wallVerts[1].x = spr->x2;
-	wallVerts[2].y = wallVerts[3].y = spr->ty;
-	if (spr->mobj && fabsf(this_scale - 1.0f) > 1.0E-36f)
-		wallVerts[0].y = wallVerts[1].y = spr->ty - gpatch->height * this_scale;
+	if (splat)
+	{
+		F2DCoord verts[4];
+		F2DCoord rotated[4];
+
+		angle_t angle;
+		float ca, sa;
+		float w, h;
+		float xscale, yscale;
+		float xoffset, yoffset;
+		float leftoffset, topoffset;
+		float scale = spr->scale;
+		float zoffset = (P_MobjFlip(spr->mobj) * 0.05f);
+		pslope_t *splatslope = NULL;
+		INT32 i;
+
+		renderflags_t renderflags = spr->renderflags;
+		if (renderflags & RF_SHADOWEFFECTS)
+			scale *= spr->shadowscale;
+
+		if (spr->rotateflags & SRF_3D || renderflags & RF_NOSPLATBILLBOARD)
+			angle = spr->mobj->angle;
+		else
+			angle = viewangle;
+
+		if (!spr->rotated)
+			angle += spr->mobj->rollangle;
+
+		angle = -angle;
+		angle += ANGLE_90;
+
+		topoffset = spr->spriteyoffset;
+		leftoffset = spr->spritexoffset;
+		if (spr->flip)
+			leftoffset = ((float)gpatch->width - leftoffset);
+
+		xscale = spr->scale * spr->spritexscale;
+		yscale = spr->scale * spr->spriteyscale;
+
+		xoffset = leftoffset * xscale;
+		yoffset = topoffset * yscale;
+
+		w = (float)gpatch->width * xscale;
+		h = (float)gpatch->height * yscale;
+
+		// Set positions
+
+		// 3--2
+		// |  |
+		// 0--1
+
+		verts[3].x = -xoffset;
+		verts[3].y = yoffset;
+
+		verts[2].x = w - xoffset;
+		verts[2].y = yoffset;
+
+		verts[1].x = w - xoffset;
+		verts[1].y = -h + yoffset;
+
+		verts[0].x = -xoffset;
+		verts[0].y = -h + yoffset;
+
+		ca = FIXED_TO_FLOAT(FINECOSINE((-angle)>>ANGLETOFINESHIFT));
+		sa = FIXED_TO_FLOAT(FINESINE((-angle)>>ANGLETOFINESHIFT));
+
+		// Rotate
+		for (i = 0; i < 4; i++)
+		{
+			rotated[i].x = (verts[i].x * ca) - (verts[i].y * sa);
+			rotated[i].y = (verts[i].x * sa) + (verts[i].y * ca);
+		}
+
+		// Translate
+		for (i = 0; i < 4; i++)
+		{
+			wallVerts[i].x = rotated[i].x + FIXED_TO_FLOAT(spr->mobj->x);
+			wallVerts[i].z = rotated[i].y + FIXED_TO_FLOAT(spr->mobj->y);
+		}
+
+		if (renderflags & (RF_SLOPESPLAT | RF_OBJECTSLOPESPLAT))
+		{
+			pslope_t *standingslope = spr->mobj->standingslope; // The slope that the object is standing on.
+
+			// The slope that was defined for the sprite.
+			if (renderflags & RF_SLOPESPLAT)
+				splatslope = spr->mobj->floorspriteslope;
+
+			if (standingslope && (renderflags & RF_OBJECTSLOPESPLAT))
+				splatslope = standingslope;
+		}
+
+		// Set vertical position
+		if (splatslope)
+		{
+			for (i = 0; i < 4; i++)
+			{
+				fixed_t slopez = P_GetSlopeZAt(splatslope, FLOAT_TO_FIXED(wallVerts[i].x), FLOAT_TO_FIXED(wallVerts[i].z));
+				wallVerts[i].y = FIXED_TO_FLOAT(slopez) + zoffset;
+			}
+		}
+		else
+		{
+			for (i = 0; i < 4; i++)
+				wallVerts[i].y = FIXED_TO_FLOAT(spr->mobj->z) + zoffset;
+		}
+	}
 	else
-		wallVerts[0].y = wallVerts[1].y = spr->ty - gpatch->height;
+	{
+		// these were already scaled in HWR_ProjectSprite
+		wallVerts[0].x = wallVerts[3].x = spr->x1;
+		wallVerts[2].x = wallVerts[1].x = spr->x2;
+		wallVerts[2].y = wallVerts[3].y = spr->gzt;
+		wallVerts[0].y = wallVerts[1].y = spr->gz;
 
-	// make a wall polygon (with 2 triangles), using the floor/ceiling heights,
-	// and the 2d map coords of start/end vertices
-	wallVerts[0].z = wallVerts[3].z = spr->z1;
-	wallVerts[1].z = wallVerts[2].z = spr->z2;
+		// make a wall polygon (with 2 triangles), using the floor/ceiling heights,
+		// and the 2d map coords of start/end vertices
+		wallVerts[0].z = wallVerts[3].z = spr->z1;
+		wallVerts[1].z = wallVerts[2].z = spr->z2;
+	}
 
 	if (spr->flip)
 	{
-		wallVerts[0].s = wallVerts[3].s = gpatch->max_s;
+		wallVerts[0].s = wallVerts[3].s = ((GLPatch_t *)gpatch->hardware)->max_s;
 		wallVerts[2].s = wallVerts[1].s = 0;
 	}else{
 		wallVerts[0].s = wallVerts[3].s = 0;
-		wallVerts[2].s = wallVerts[1].s = gpatch->max_s;
+		wallVerts[2].s = wallVerts[1].s = ((GLPatch_t *)gpatch->hardware)->max_s;
 	}
 
 	// flip the texture coords (look familiar?)
 	if (spr->vflip)
 	{
-		wallVerts[3].t = wallVerts[2].t = gpatch->max_t;
+		wallVerts[3].t = wallVerts[2].t = ((GLPatch_t *)gpatch->hardware)->max_t;
 		wallVerts[0].t = wallVerts[1].t = 0;
 	}else{
 		wallVerts[3].t = wallVerts[2].t = 0;
-		wallVerts[0].t = wallVerts[1].t = gpatch->max_t;
+		wallVerts[0].t = wallVerts[1].t = ((GLPatch_t *)gpatch->hardware)->max_t;
 	}
 
 	// cache the patch in the graphics card memory
@@ -4054,18 +4165,21 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 	//Hurdler: 25/04/2000: now support colormap in hardware mode
 	HWR_GetMappedPatch(gpatch, spr->colormap);
 
-	// if it has a dispoffset, push it a little towards the camera
-	if (spr->dispoffset) {
-		float co = -gl_viewcos*(0.05f*spr->dispoffset);
-		float si = -gl_viewsin*(0.05f*spr->dispoffset);
-		wallVerts[0].z = wallVerts[3].z = wallVerts[0].z+si;
-		wallVerts[1].z = wallVerts[2].z = wallVerts[1].z+si;
-		wallVerts[0].x = wallVerts[3].x = wallVerts[0].x+co;
-		wallVerts[1].x = wallVerts[2].x = wallVerts[1].x+co;
-	}
+	if (!splat)
+	{
+		// if it has a dispoffset, push it a little towards the camera
+		if (spr->dispoffset) {
+			float co = -gl_viewcos*(0.05f*spr->dispoffset);
+			float si = -gl_viewsin*(0.05f*spr->dispoffset);
+			wallVerts[0].z = wallVerts[3].z = wallVerts[0].z+si;
+			wallVerts[1].z = wallVerts[2].z = wallVerts[1].z+si;
+			wallVerts[0].x = wallVerts[3].x = wallVerts[0].x+co;
+			wallVerts[1].x = wallVerts[2].x = wallVerts[1].x+co;
+		}
 
-	// Let dispoffset work first since this adjust each vertex
-	HWR_RotateSpritePolyToAim(spr, wallVerts, false);
+		// Let dispoffset work first since this adjust each vertex
+		HWR_RotateSpritePolyToAim(spr, wallVerts, false);
+	}
 
 	// This needs to be AFTER the shadows so that the regular sprites aren't drawn completely black.
 	// sprite lighting by modulating the RGB components
@@ -4074,10 +4188,31 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 	// colormap test
 	{
 		sector_t *sector = spr->mobj->subsector->sector;
-		UINT8 lightlevel = 255;
-		extracolormap_t *colormap = sector->extra_colormap;
+		UINT8 lightlevel = 0;
+		boolean lightset = true;
+		extracolormap_t *colormap = NULL;
+
+		if (R_ThingIsFullBright(spr->mobj))
+			lightlevel = 255;
+		else if (R_ThingIsFullDark(spr->mobj))
+			lightlevel = 0;
+		else
+			lightset = false;
+
+		if (!(spr->mobj->renderflags & RF_NOCOLORMAPS))
+			colormap = sector->extra_colormap;
+
+		if (splat && sector->numlights)
+		{
+			INT32 light = R_GetPlaneLight(sector, spr->mobj->z, false);
 
-		if (!(spr->mobj->frame & FF_FULLBRIGHT))
+			if (!lightset)
+				lightlevel = *sector->lightlist[light].lightlevel > 255 ? 255 : *sector->lightlist[light].lightlevel;
+
+			if (*sector->lightlist[light].extra_colormap && !(spr->mobj->renderflags & RF_NOCOLORMAPS))
+				colormap = *sector->lightlist[light].extra_colormap;
+		}
+		else if (!lightset)
 			lightlevel = sector->lightlevel > 255 ? 255 : sector->lightlevel;
 
 		HWR_Lighting(&Surf, lightlevel, colormap);
@@ -4104,10 +4239,15 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 		else if (spr->mobj->flags2 & MF2_SHADOW)
 		{
 			Surf.PolyColor.s.alpha = 0x40;
-			blend = PF_Translucent;
+			blend = HWR_GetBlendModeFlag(spr->mobj->blendmode);
 		}
 		else if (spr->mobj->frame & FF_TRANSMASK)
-			blend = HWR_TranstableToAlpha((spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT, &Surf);
+		{
+			INT32 trans = (spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT;
+			if (spr->mobj->blendmode == AST_TRANSLUCENT && trans >= NUMTRANSMAPS)
+				return;
+			blend = HWR_SurfaceBlend(spr->mobj->blendmode, trans, &Surf);
+		}
 		else
 		{
 			// BP: i agree that is little better in environement but it don't
@@ -4115,11 +4255,23 @@ static void HWR_DrawSprite(gl_vissprite_t *spr)
 			// Hurdler: PF_Environement would be cool, but we need to fix
 			//          the issue with the fog before
 			Surf.PolyColor.s.alpha = 0xFF;
+			blend = HWR_GetBlendModeFlag(spr->mobj->blendmode)|occlusion;
+			if (!occlusion) use_linkdraw_hack = true;
+		}
+
+		if (spr->renderflags & RF_SHADOWEFFECTS)
+		{
+			INT32 alpha = Surf.PolyColor.s.alpha;
+			alpha -= ((INT32)(spr->shadowheight / 4.0f)) + 75;
+			if (alpha < 1)
+				return;
+
+			Surf.PolyColor.s.alpha = (UINT8)(alpha);
 			blend = PF_Translucent|occlusion;
 			if (!occlusion) use_linkdraw_hack = true;
 		}
 
-		HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated|PF_Clip, SHADER_SPRITE, false); // sprite shader
+		HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated, SHADER_SPRITE, false); // sprite shader
 
 		if (use_linkdraw_hack)
 			HWR_LinkDrawHackAdd(wallVerts, spr);
@@ -4132,7 +4284,7 @@ static inline void HWR_DrawPrecipitationSprite(gl_vissprite_t *spr)
 {
 	FBITFIELD blend = 0;
 	FOutVector wallVerts[4];
-	GLPatch_t *gpatch; // sprite patch converted to hardware
+	patch_t *gpatch; // sprite patch converted to hardware
 	FSurfaceInfo Surf;
 
 	if (!spr->mobj)
@@ -4142,7 +4294,7 @@ static inline void HWR_DrawPrecipitationSprite(gl_vissprite_t *spr)
 		return;
 
 	// cache sprite graphics
-	gpatch = spr->gpatch; //W_CachePatchNum(spr->patchlumpnum, PU_CACHE);
+	gpatch = spr->gpatch;
 
 	// create the sprite billboard
 	//
@@ -4152,8 +4304,8 @@ static inline void HWR_DrawPrecipitationSprite(gl_vissprite_t *spr)
 	//  0--1
 	wallVerts[0].x = wallVerts[3].x = spr->x1;
 	wallVerts[2].x = wallVerts[1].x = spr->x2;
-	wallVerts[2].y = wallVerts[3].y = spr->ty;
-	wallVerts[0].y = wallVerts[1].y = spr->ty - gpatch->height;
+	wallVerts[2].y = wallVerts[3].y = spr->gzt;
+	wallVerts[0].y = wallVerts[1].y = spr->gz;
 
 	// make a wall polygon (with 2 triangles), using the floor/ceiling heights,
 	// and the 2d map coords of start/end vertices
@@ -4164,10 +4316,10 @@ static inline void HWR_DrawPrecipitationSprite(gl_vissprite_t *spr)
 	HWR_RotateSpritePolyToAim(spr, wallVerts, true);
 
 	wallVerts[0].s = wallVerts[3].s = 0;
-	wallVerts[2].s = wallVerts[1].s = gpatch->max_s;
+	wallVerts[2].s = wallVerts[1].s = ((GLPatch_t *)gpatch->hardware)->max_s;
 
 	wallVerts[3].t = wallVerts[2].t = 0;
-	wallVerts[0].t = wallVerts[1].t = gpatch->max_t;
+	wallVerts[0].t = wallVerts[1].t = ((GLPatch_t *)gpatch->hardware)->max_t;
 
 	// cache the patch in the graphics card memory
 	//12/12/99: Hurdler: same comment as above (for md2)
@@ -4182,9 +4334,8 @@ static inline void HWR_DrawPrecipitationSprite(gl_vissprite_t *spr)
 
 		if (sector->numlights)
 		{
-			INT32 light;
-
-			light = R_GetPlaneLight(sector, spr->mobj->z + spr->mobj->height, false); // Always use the light at the top instead of whatever I was doing before
+			// Always use the light at the top instead of whatever I was doing before
+			INT32 light = R_GetPlaneLight(sector, spr->mobj->z + spr->mobj->height, false);
 
 			if (!(spr->mobj->frame & FF_FULLBRIGHT))
 				lightlevel = *sector->lightlist[light].lightlevel > 255 ? 255 : *sector->lightlist[light].lightlevel;
@@ -4204,13 +4355,13 @@ static inline void HWR_DrawPrecipitationSprite(gl_vissprite_t *spr)
 		HWR_Lighting(&Surf, lightlevel, colormap);
 	}
 
-	if (spr->mobj->flags2 & MF2_SHADOW)
+	if (spr->mobj->frame & FF_TRANSMASK)
 	{
-		Surf.PolyColor.s.alpha = 0x40;
-		blend = PF_Translucent;
+		INT32 trans = (spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT;
+		if (spr->mobj->blendmode == AST_TRANSLUCENT && trans >= NUMTRANSMAPS)
+			return;
+		blend = HWR_SurfaceBlend(spr->mobj->blendmode, trans, &Surf);
 	}
-	else if (spr->mobj->frame & FF_TRANSMASK)
-		blend = HWR_TranstableToAlpha((spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT, &Surf);
 	else
 	{
 		// BP: i agree that is little better in environement but it don't
@@ -4218,10 +4369,10 @@ static inline void HWR_DrawPrecipitationSprite(gl_vissprite_t *spr)
 		// Hurdler: PF_Environement would be cool, but we need to fix
 		//          the issue with the fog before
 		Surf.PolyColor.s.alpha = 0xFF;
-		blend = PF_Translucent|PF_Occlude;
+		blend = HWR_GetBlendModeFlag(spr->mobj->blendmode)|PF_Occlude;
 	}
 
-	HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated|PF_Clip, SHADER_SPRITE, false); // sprite shader
+	HWR_ProcessPolygon(&Surf, wallVerts, 4, blend|PF_Modulated, SHADER_SPRITE, false); // sprite shader
 }
 #endif
 
@@ -4685,7 +4836,7 @@ static void HWR_DrawSprites(void)
 	// (Other states probably don't matter. Here I left them same as in LinkDrawHackFinish)
 	// Without this workaround the rest of the draw calls in this frame (including UI, screen texture)
 	// can get drawn using an incorrect glBlendFunc, resulting in a occasional black screen.
-	HWD.pfnSetBlend(PF_Translucent|PF_Occlude|PF_Clip|PF_Masked);
+	HWD.pfnSetBlend(PF_Translucent|PF_Occlude|PF_Masked);
 }
 
 // --------------------------------------------------------------------------
@@ -4750,23 +4901,28 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	float tracertz = 0.0f;
 	float x1, x2;
 	float rightsin, rightcos;
-	float this_scale;
+	float this_scale, this_xscale, this_yscale;
+	float spritexscale, spriteyscale;
+	float shadowheight = 1.0f, shadowscale = 1.0f;
 	float gz, gzt;
 	spritedef_t *sprdef;
 	spriteframe_t *sprframe;
+#ifdef ROTSPRITE
 	spriteinfo_t *sprinfo;
+#endif
 	md2_t *md2;
 	size_t lumpoff;
 	unsigned rot;
 	UINT16 flip;
-	boolean vflip = (!(thing->eflags & MFE_VERTICALFLIP) != !(thing->frame & FF_VERTICALFLIP));
+	boolean vflip = (!(thing->eflags & MFE_VERTICALFLIP) != !R_ThingVerticallyFlipped(thing));
 	boolean mirrored = thing->mirrored;
-	boolean hflip = (!(thing->frame & FF_HORIZONTALFLIP) != !mirrored);
+	boolean hflip = (!R_ThingHorizontallyFlipped(thing) != !mirrored);
 	INT32 dispoffset;
 
 	angle_t ang;
 	INT32 heightsec, phs;
-	const boolean papersprite = (thing->frame & FF_PAPERSPRITE);
+	const boolean papersprite = R_ThingIsPaperSprite(thing);
+	const boolean splat = R_ThingIsFloorSprite(thing);
 	angle_t mobjangle = (thing->player ? thing->player->drawangle : thing->angle);
 	float z1, z2;
 
@@ -4780,9 +4936,14 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	if (!thing)
 		return;
 
+	if (thing->spritexscale < 1 || thing->spriteyscale < 1)
+		return;
+
 	dispoffset = thing->info->dispoffset;
 
 	this_scale = FIXED_TO_FLOAT(thing->scale);
+	spritexscale = FIXED_TO_FLOAT(thing->spritexscale);
+	spriteyscale = FIXED_TO_FLOAT(thing->spriteyscale);
 
 	// transform the origin point
 	tr_x = FIXED_TO_FLOAT(thing->x) - gl_viewx;
@@ -4792,7 +4953,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	tz = (tr_x * gl_viewcos) + (tr_y * gl_viewsin);
 
 	// thing is behind view plane?
-	if (tz < ZCLIP_PLANE && !papersprite)
+	if (tz < ZCLIP_PLANE && !(papersprite || splat))
 	{
 		if (cv_glmodels.value) //Yellow: Only MD2's dont disappear
 		{
@@ -4824,12 +4985,16 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	if (thing->skin && thing->sprite == SPR_PLAY)
 	{
 		sprdef = &((skin_t *)thing->skin)->sprites[thing->sprite2];
+#ifdef ROTSPRITE
 		sprinfo = &((skin_t *)thing->skin)->sprinfo[thing->sprite2];
+#endif
 	}
 	else
 	{
 		sprdef = &sprites[thing->sprite];
-		sprinfo = NULL;
+#ifdef ROTSPRITE
+		sprinfo = &spriteinfo[thing->sprite];
+#endif
 	}
 
 	if (rot >= sprdef->numframes)
@@ -4839,7 +5004,9 @@ static void HWR_ProjectSprite(mobj_t *thing)
 		thing->sprite = states[S_UNKNOWN].sprite;
 		thing->frame = states[S_UNKNOWN].frame;
 		sprdef = &sprites[thing->sprite];
-		sprinfo = NULL;
+#ifdef ROTSPRITE
+		sprinfo = &spriteinfo[thing->sprite];
+#endif
 		rot = thing->frame&FF_FRAMEMASK;
 		thing->state->sprite = thing->sprite;
 		thing->state->frame = thing->frame;
@@ -4890,7 +5057,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	}
 
 	if (thing->skin && ((skin_t *)thing->skin)->flags & SF_HIRES)
-		this_scale = this_scale * FIXED_TO_FLOAT(((skin_t *)thing->skin)->highresscale);
+		this_scale *= FIXED_TO_FLOAT(((skin_t *)thing->skin)->highresscale);
 
 	spr_width = spritecachedinfo[lumpoff].width;
 	spr_height = spritecachedinfo[lumpoff].height;
@@ -4898,24 +5065,42 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	spr_topoffset = spritecachedinfo[lumpoff].topoffset;
 
 #ifdef ROTSPRITE
-	if (thing->rollangle)
+	if (thing->rollangle
+	&& !(splat && !(thing->renderflags & RF_NOSPLATROLLANGLE)))
 	{
 		rollangle = R_GetRollAngle(thing->rollangle);
-		if (!(sprframe->rotsprite.cached & (1<<rot)))
-			R_CacheRotSprite(thing->sprite, (thing->frame & FF_FRAMEMASK), sprinfo, sprframe, rot, flip);
-		rotsprite = sprframe->rotsprite.patch[rot][rollangle];
+		rotsprite = Patch_GetRotatedSprite(sprframe, (thing->frame & FF_FRAMEMASK), rot, flip, false, sprinfo, rollangle);
+
 		if (rotsprite != NULL)
 		{
-			spr_width = SHORT(rotsprite->width) << FRACBITS;
-			spr_height = SHORT(rotsprite->height) << FRACBITS;
-			spr_offset = SHORT(rotsprite->leftoffset) << FRACBITS;
-			spr_topoffset = SHORT(rotsprite->topoffset) << FRACBITS;
+			spr_width = rotsprite->width << FRACBITS;
+			spr_height = rotsprite->height << FRACBITS;
+			spr_offset = rotsprite->leftoffset << FRACBITS;
+			spr_topoffset = rotsprite->topoffset << FRACBITS;
+			spr_topoffset += FEETADJUST;
+
 			// flip -> rotate, not rotate -> flip
 			flip = 0;
 		}
 	}
 #endif
 
+	if (thing->renderflags & RF_ABSOLUTEOFFSETS)
+	{
+		spr_offset = thing->spritexoffset;
+		spr_topoffset = thing->spriteyoffset;
+	}
+	else
+	{
+		SINT8 flipoffset = 1;
+
+		if ((thing->renderflags & RF_FLIPOFFSETS) && flip)
+			flipoffset = -1;
+
+		spr_offset += thing->spritexoffset * flipoffset;
+		spr_topoffset += thing->spriteyoffset * flipoffset;
+	}
+
 	if (papersprite)
 	{
 		rightsin = FIXED_TO_FLOAT(FINESINE((mobjangle)>>ANGLETOFINESHIFT));
@@ -4929,15 +5114,36 @@ static void HWR_ProjectSprite(mobj_t *thing)
 
 	flip = !flip != !hflip;
 
+	if (thing->renderflags & RF_SHADOWEFFECTS)
+	{
+		mobj_t *caster = thing->target;
+
+		if (caster && !P_MobjWasRemoved(caster))
+		{
+			fixed_t groundz = R_GetShadowZ(thing, NULL);
+			fixed_t floordiff = abs(((thing->eflags & MFE_VERTICALFLIP) ? caster->height : 0) + caster->z - groundz);
+
+			shadowheight = FIXED_TO_FLOAT(floordiff);
+			shadowscale = FIXED_TO_FLOAT(FixedMul(FRACUNIT - floordiff/640, caster->scale));
+
+			if (splat)
+				spritexscale *= shadowscale;
+			spriteyscale *= shadowscale;
+		}
+	}
+
+	this_xscale = spritexscale * this_scale;
+	this_yscale = spriteyscale * this_scale;
+
 	if (flip)
 	{
-		x1 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_scale);
-		x2 = (FIXED_TO_FLOAT(spr_offset) * this_scale);
+		x1 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_xscale);
+		x2 = (FIXED_TO_FLOAT(spr_offset) * this_xscale);
 	}
 	else
 	{
-		x1 = (FIXED_TO_FLOAT(spr_offset) * this_scale);
-		x2 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_scale);
+		x1 = (FIXED_TO_FLOAT(spr_offset) * this_xscale);
+		x2 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_xscale);
 	}
 
 	// test if too close
@@ -4959,13 +5165,13 @@ static void HWR_ProjectSprite(mobj_t *thing)
 
 	if (vflip)
 	{
-		gz = FIXED_TO_FLOAT(thing->z+thing->height) - FIXED_TO_FLOAT(spr_topoffset) * this_scale;
-		gzt = gz + FIXED_TO_FLOAT(spr_height) * this_scale;
+		gz = FIXED_TO_FLOAT(thing->z + thing->height) - (FIXED_TO_FLOAT(spr_topoffset) * this_yscale);
+		gzt = gz + (FIXED_TO_FLOAT(spr_height) * this_yscale);
 	}
 	else
 	{
-		gzt = FIXED_TO_FLOAT(thing->z) + FIXED_TO_FLOAT(spr_topoffset) * this_scale;
-		gz = gzt - FIXED_TO_FLOAT(spr_height) * this_scale;
+		gzt = FIXED_TO_FLOAT(thing->z) + (FIXED_TO_FLOAT(spr_topoffset) * this_yscale);
+		gz = gzt - (FIXED_TO_FLOAT(spr_height) * this_yscale);
 	}
 
 	if (thing->subsector->sector->cullheight)
@@ -5022,20 +5228,39 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	vis = HWR_NewVisSprite();
 	vis->x1 = x1;
 	vis->x2 = x2;
+	vis->z1 = z1;
+	vis->z2 = z2;
+
 	vis->tz = tz; // Keep tz for the simple sprite sorting that happens
 	vis->tracertz = tracertz;
+
+	vis->renderflags = thing->renderflags;
+	vis->rotateflags = sprframe->rotate;
+
+	vis->shadowheight = shadowheight;
+	vis->shadowscale = shadowscale;
 	vis->dispoffset = dispoffset; // Monster Iestyn: 23/11/15: HARDWARE SUPPORT AT LAST
-	//vis->patchlumpnum = sprframe->lumppat[rot];
+	vis->flip = flip;
+
+	vis->scale = this_scale;
+	vis->spritexscale = spritexscale;
+	vis->spriteyscale = spriteyscale;
+	vis->spritexoffset = FIXED_TO_FLOAT(spr_offset);
+	vis->spriteyoffset = FIXED_TO_FLOAT(spr_topoffset);
+
+	vis->rotated = false;
+
 #ifdef ROTSPRITE
 	if (rotsprite)
-		vis->gpatch = (GLPatch_t *)rotsprite;
+	{
+		vis->gpatch = (patch_t *)rotsprite;
+		vis->rotated = true;
+	}
 	else
 #endif
-		vis->gpatch = (GLPatch_t *)W_CachePatchNum(sprframe->lumppat[rot], PU_CACHE);
-	vis->flip = flip;
+		vis->gpatch = (patch_t *)W_CachePatchNum(sprframe->lumppat[rot], PU_SPRITE);
+
 	vis->mobj = thing;
-	vis->z1 = z1;
-	vis->z2 = z2;
 
 	//Hurdler: 25/04/2000: now support colormap in hardware mode
 	if ((vis->mobj->flags & (MF_ENEMY|MF_BOSS)) && (vis->mobj->flags2 & MF2_FRET) && !(vis->mobj->flags & MF_GRENADEBOUNCE) && (leveltime & 1)) // Bosses "flash"
@@ -5073,7 +5298,8 @@ static void HWR_ProjectSprite(mobj_t *thing)
 		vis->colormap = colormaps;
 
 	// set top/bottom coords
-	vis->ty = gzt;
+	vis->gzt = gzt;
+	vis->gz = gz;
 
 	//CONS_Debug(DBG_RENDER, "------------------\nH: sprite  : %d\nH: frame   : %x\nH: type    : %d\nH: sname   : %s\n\n",
 	//            thing->sprite, thing->frame, thing->type, sprnames[thing->sprite]);
@@ -5166,15 +5392,15 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 	vis->z2 = z2;
 	vis->tz = tz;
 	vis->dispoffset = 0; // Monster Iestyn: 23/11/15: HARDWARE SUPPORT AT LAST
-	//vis->patchlumpnum = sprframe->lumppat[rot];
-	vis->gpatch = (GLPatch_t *)W_CachePatchNum(sprframe->lumppat[rot], PU_CACHE);
+	vis->gpatch = (patch_t *)W_CachePatchNum(sprframe->lumppat[rot], PU_SPRITE);
 	vis->flip = flip;
 	vis->mobj = (mobj_t *)thing;
 
 	vis->colormap = colormaps;
 
 	// set top/bottom coords
-	vis->ty = FIXED_TO_FLOAT(thing->z + spritecachedinfo[lumpoff].topoffset);
+	vis->gzt = FIXED_TO_FLOAT(thing->z + spritecachedinfo[lumpoff].topoffset);
+	vis->gz = vis->gzt - FIXED_TO_FLOAT(spritecachedinfo[lumpoff].height);
 
 	vis->precip = true;
 
@@ -5556,6 +5782,20 @@ static void HWR_SetTransformAiming(FTransform *trans, player_t *player, boolean
 	trans->anglex = (float)(gl_aimingangle>>ANGLETOFINESHIFT)*(360.0f/(float)FINEANGLES);
 }
 
+//
+// Sets the shader state.
+//
+static void HWR_SetShaderState(void)
+{
+	hwdshaderoption_t state = cv_glshaders.value;
+
+	if (!cv_glallowshaders.value)
+		state = (cv_glshaders.value == HWD_SHADEROPTION_ON ? HWD_SHADEROPTION_NOCUSTOM : cv_glshaders.value);
+
+	HWD.pfnSetSpecialState(HWD_SET_SHADERS, (INT32)state);
+	HWD.pfnSetShader(SHADER_DEFAULT);
+}
+
 // ==========================================================================
 // Same as rendering the player view, but from the skybox object
 // ==========================================================================
@@ -5674,8 +5914,7 @@ void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
 	HWD.pfnSetTransform(&atransform);
 
 	// Reset the shader state.
-	HWD.pfnSetSpecialState(HWD_SET_SHADERS, cv_glshaders.value);
-	HWD.pfnSetShader(SHADER_DEFAULT);
+	HWR_SetShaderState();
 
 	validcount++;
 
@@ -5889,8 +6128,7 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	HWD.pfnSetTransform(&atransform);
 
 	// Reset the shader state.
-	HWD.pfnSetSpecialState(HWD_SET_SHADERS, cv_glshaders.value);
-	HWD.pfnSetShader(SHADER_DEFAULT);
+	HWR_SetShaderState();
 
 	ps_numbspcalls = 0;
 	ps_numpolyobjects = 0;
@@ -5981,13 +6219,41 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	HWD.pfnGClipRect(0, 0, vid.width, vid.height, NZCLIP_PLANE);
 }
 
+void HWR_LoadLevel(void)
+{
+	// Lactozilla (December 8, 2019)
+	// Level setup used to free EVERY mipmap from memory.
+	// Even mipmaps that aren't related to level textures.
+	// Presumably, the hardware render code used to store textures as level data.
+	// Meaning, they had memory allocated and marked with the PU_LEVEL tag.
+	// Level textures are only reloaded after R_LoadTextures, which is
+	// when the texture list is loaded.
+
+	// Sal: Unfortunately, NOT freeing them causes the dreaded Color Bug.
+	HWR_FreeColormapCache();
+
+#ifdef ALAM_LIGHTING
+	// BP: reset light between levels (we draw preview frame lights on current frame)
+	HWR_ResetLights();
+#endif
+
+	HWR_CreatePlanePolygons((INT32)numnodes - 1);
+
+	// Build the sky dome
+	HWR_ClearSkyDome();
+	HWR_BuildSkyDome();
+
+	gl_maploaded = true;
+}
+
 // ==========================================================================
 //                                                         3D ENGINE COMMANDS
 // ==========================================================================
 
-static CV_PossibleValue_t grmodelinterpolation_cons_t[] = {{0, "Off"}, {1, "Sometimes"}, {2, "Always"}, {0, NULL}};
-static CV_PossibleValue_t grfakecontrast_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Smooth"}, {0, NULL}};
-static CV_PossibleValue_t grshearing_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Third-person"}, {0, NULL}};
+static CV_PossibleValue_t glshaders_cons_t[] = {{HWD_SHADEROPTION_OFF, "Off"}, {HWD_SHADEROPTION_ON, "On"}, {HWD_SHADEROPTION_NOCUSTOM, "Ignore custom shaders"}, {0, NULL}};
+static CV_PossibleValue_t glmodelinterpolation_cons_t[] = {{0, "Off"}, {1, "Sometimes"}, {2, "Always"}, {0, NULL}};
+static CV_PossibleValue_t glfakecontrast_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Smooth"}, {0, NULL}};
+static CV_PossibleValue_t glshearing_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Third-person"}, {0, NULL}};
 
 static void CV_glfiltermode_OnChange(void);
 static void CV_glanisotropic_OnChange(void);
@@ -5998,9 +6264,10 @@ static CV_PossibleValue_t glfiltermode_cons_t[]= {{HWD_SET_TEXTUREFILTER_POINTSA
 	{HWD_SET_TEXTUREFILTER_MIXED2, "Nearest_Linear"},
 	{HWD_SET_TEXTUREFILTER_MIXED3, "Nearest_Mipmap"},
 	{0, NULL}};
-CV_PossibleValue_t granisotropicmode_cons_t[] = {{1, "MIN"}, {16, "MAX"}, {0, NULL}};
+CV_PossibleValue_t glanisotropicmode_cons_t[] = {{1, "MIN"}, {16, "MAX"}, {0, NULL}};
 
-consvar_t cv_glshaders = CVAR_INIT ("gr_shaders", "On", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_glshaders = CVAR_INIT ("gr_shaders", "On", CV_SAVE, glshaders_cons_t, NULL);
+consvar_t cv_glallowshaders = CVAR_INIT ("gr_allowclientshaders", "On", CV_NETVAR, CV_OnOff, NULL);
 consvar_t cv_fovchange = CVAR_INIT ("gr_fovchange", "Off", CV_SAVE, CV_OnOff, NULL);
 
 #ifdef ALAM_LIGHTING
@@ -6011,17 +6278,17 @@ consvar_t cv_glcoronasize = CVAR_INIT ("gr_coronasize", "1", CV_SAVE|CV_FLOAT, 0
 #endif
 
 consvar_t cv_glmodels = CVAR_INIT ("gr_models", "Off", CV_SAVE, CV_OnOff, NULL);
-consvar_t cv_glmodelinterpolation = CVAR_INIT ("gr_modelinterpolation", "Sometimes", CV_SAVE, grmodelinterpolation_cons_t, NULL);
+consvar_t cv_glmodelinterpolation = CVAR_INIT ("gr_modelinterpolation", "Sometimes", CV_SAVE, glmodelinterpolation_cons_t, NULL);
 consvar_t cv_glmodellighting = CVAR_INIT ("gr_modellighting", "Off", CV_SAVE, CV_OnOff, NULL);
 
-consvar_t cv_glshearing = CVAR_INIT ("gr_shearing", "Off", CV_SAVE, grshearing_cons_t, NULL);
+consvar_t cv_glshearing = CVAR_INIT ("gr_shearing", "Off", CV_SAVE, glshearing_cons_t, NULL);
 consvar_t cv_glspritebillboarding = CVAR_INIT ("gr_spritebillboarding", "Off", CV_SAVE, CV_OnOff, NULL);
 consvar_t cv_glskydome = CVAR_INIT ("gr_skydome", "On", CV_SAVE, CV_OnOff, NULL);
-consvar_t cv_glfakecontrast = CVAR_INIT ("gr_fakecontrast", "Smooth", CV_SAVE, grfakecontrast_cons_t, NULL);
+consvar_t cv_glfakecontrast = CVAR_INIT ("gr_fakecontrast", "Smooth", CV_SAVE, glfakecontrast_cons_t, NULL);
 consvar_t cv_glslopecontrast = CVAR_INIT ("gr_slopecontrast", "Off", CV_SAVE, CV_OnOff, NULL);
 
 consvar_t cv_glfiltermode = CVAR_INIT ("gr_filtermode", "Nearest", CV_SAVE|CV_CALL, glfiltermode_cons_t, CV_glfiltermode_OnChange);
-consvar_t cv_glanisotropicmode = CVAR_INIT ("gr_anisotropicmode", "1", CV_CALL, granisotropicmode_cons_t, CV_glanisotropic_OnChange);
+consvar_t cv_glanisotropicmode = CVAR_INIT ("gr_anisotropicmode", "1", CV_CALL, glanisotropicmode_cons_t, CV_glanisotropic_OnChange);
 
 consvar_t cv_glsolvetjoin = CVAR_INIT ("gr_solvetjoin", "On", 0, CV_OnOff, NULL);
 
@@ -6060,6 +6327,7 @@ void HWR_AddCommands(void)
 	CV_RegisterVar(&cv_glfakecontrast);
 	CV_RegisterVar(&cv_glshearing);
 	CV_RegisterVar(&cv_glshaders);
+	CV_RegisterVar(&cv_glallowshaders);
 
 	CV_RegisterVar(&cv_glfiltermode);
 	CV_RegisterVar(&cv_glsolvetjoin);
@@ -6073,13 +6341,10 @@ void HWR_AddCommands(void)
 
 void HWR_AddSessionCommands(void)
 {
-	static boolean alreadycalled = false;
-	if (alreadycalled)
+	if (gl_sessioncommandsadded)
 		return;
-
 	CV_RegisterVar(&cv_glanisotropicmode);
-
-	alreadycalled = true;
+	gl_sessioncommandsadded = true;
 }
 
 // --------------------------------------------------------------------------
@@ -6087,16 +6352,13 @@ void HWR_AddSessionCommands(void)
 // --------------------------------------------------------------------------
 void HWR_Startup(void)
 {
-	static boolean startupdone = false;
-
-	// do this once
-	if (!startupdone)
+	if (!gl_init)
 	{
 		CONS_Printf("HWR_Startup()...\n");
 
 		HWR_InitPolyPool();
 		HWR_AddSessionCommands();
-		HWR_InitTextureCache();
+		HWR_InitMapTextures();
 		HWR_InitModels();
 #ifdef ALAM_LIGHTING
 		HWR_InitLight();
@@ -6110,7 +6372,7 @@ void HWR_Startup(void)
 	if (rendermode == render_opengl)
 		textureformat = patchformat = GL_TEXFMT_RGBA;
 
-	startupdone = true;
+	gl_init = true;
 }
 
 // --------------------------------------------------------------------------
@@ -6118,9 +6380,21 @@ void HWR_Startup(void)
 // --------------------------------------------------------------------------
 void HWR_Switch(void)
 {
+	// Add session commands
+	if (!gl_sessioncommandsadded)
+		HWR_AddSessionCommands();
+
 	// Set special states from CVARs
 	HWD.pfnSetSpecialState(HWD_SET_TEXTUREFILTERMODE, cv_glfiltermode.value);
 	HWD.pfnSetSpecialState(HWD_SET_TEXTUREANISOTROPICMODE, cv_glanisotropicmode.value);
+
+	// Load textures
+	if (!gl_maptexturesloaded)
+		HWR_LoadMapTextures(numtextures);
+
+	// Create plane polygons
+	if (!gl_maploaded && (gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
+		HWR_LoadLevel();
 }
 
 // --------------------------------------------------------------------------
@@ -6131,7 +6405,7 @@ void HWR_Shutdown(void)
 	CONS_Printf("HWR_Shutdown()\n");
 	HWR_FreeExtraSubsectors();
 	HWR_FreePolyPool();
-	HWR_FreeTextureCache();
+	HWR_FreeMapTextures();
 	HWD.pfnFlushScreenTextures();
 }
 
@@ -6207,13 +6481,7 @@ void HWR_RenderWall(FOutVector *wallVerts, FSurfaceInfo *pSurf, FBITFIELD blend,
 	}
 
 	blendmode |= PF_Modulated;	// No PF_Occlude means overlapping (incorrect) transparency
-
 	HWR_ProcessPolygon(pSurf, wallVerts, 4, blendmode, shader, false);
-
-#ifdef WALLSPLATS
-	if (gl_curline->linedef->splats && cv_splats.value)
-		HWR_DrawSegsSplats(pSurf);
-#endif
 }
 
 INT32 HWR_GetTextureUsed(void)
@@ -6254,7 +6522,7 @@ void HWR_DoPostProcessor(player_t *player)
 
 		Surf.PolyColor.s.alpha = 0xc0; // match software mode
 
-		HWD.pfnDrawPolygon(&Surf, v, 4, PF_Modulated|PF_Additive|PF_NoTexture|PF_NoDepthTest|PF_Clip|PF_NoZClip);
+		HWD.pfnDrawPolygon(&Surf, v, 4, PF_Modulated|PF_AdditiveSource|PF_NoTexture|PF_NoDepthTest);
 	}
 
 	// Capture the screen for intermission and screen waving
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index 85072dfd9c483456206d0315f248d606cd18f380..2ce918408b041780b3a5e0294a9ce059a5326381 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -37,13 +37,12 @@ void HWR_DrawViewBorder(INT32 clearlines);
 void HWR_DrawFlatFill(INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatlumpnum);
 void HWR_InitTextureMapping(void);
 void HWR_SetViewSize(void);
-void HWR_DrawPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option);
-void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 option, const UINT8 *colormap);
-void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t scale, INT32 option, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h);
+void HWR_DrawPatch(patch_t *gpatch, INT32 x, INT32 y, INT32 option);
+void HWR_DrawStretchyFixedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 option, const UINT8 *colormap);
+void HWR_DrawCroppedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t scale, INT32 option, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h);
 void HWR_MakePatch(const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipmap, boolean makebitmap);
 void HWR_CreatePlanePolygons(INT32 bspnum);
 void HWR_CreateStaticLightmaps(INT32 bspnum);
-void HWR_LoadTextures(size_t pnumtextures);
 void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color);
 void HWR_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color, UINT16 actualcolor, UINT8 strength);
 void HWR_DrawConsoleFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color, UINT32 actualcolor);	// Lat: separate flags from color since color needs to be an uint to work right.
@@ -55,7 +54,6 @@ boolean HWR_Screenshot(const char *pathname);
 void HWR_AddCommands(void);
 void HWR_AddSessionCommands(void);
 void transform(float *cx, float *cy, float *cz);
-FBITFIELD HWR_TranstableToAlpha(INT32 transtablenum, FSurfaceInfo *pSurf);
 INT32 HWR_GetTextureUsed(void);
 void HWR_DoPostProcessor(player_t *player);
 void HWR_StartScreenWipe(void);
@@ -66,10 +64,15 @@ void HWR_DoTintedWipe(UINT8 wipenum, UINT8 scrnnum);
 void HWR_MakeScreenFinalTexture(void);
 void HWR_DrawScreenFinalTexture(int width, int height);
 
-// This stuff is put here so MD2's can use them
+// This stuff is put here so models can use them
 void HWR_Lighting(FSurfaceInfo *Surface, INT32 light_level, extracolormap_t *colormap);
 UINT8 HWR_FogBlockAlpha(INT32 light, extracolormap_t *colormap); // Let's see if this can work
 
+UINT8 HWR_GetTranstableAlpha(INT32 transtablenum);
+FBITFIELD HWR_GetBlendModeFlag(INT32 ast);
+FBITFIELD HWR_SurfaceBlend(INT32 style, INT32 transtablenum, FSurfaceInfo *pSurf);
+FBITFIELD HWR_TranstableToAlpha(INT32 transtablenum, FSurfaceInfo *pSurf);
+
 boolean HWR_CompileShaders(void);
 
 void HWR_LoadAllCustomShaders(void);
@@ -78,7 +81,7 @@ const char *HWR_GetShaderName(INT32 shader);
 
 extern customshaderxlat_t shaderxlat[];
 
-extern CV_PossibleValue_t granisotropicmode_cons_t[];
+extern CV_PossibleValue_t glanisotropicmode_cons_t[];
 
 #ifdef ALAM_LIGHTING
 extern consvar_t cv_gldynamiclighting;
@@ -87,7 +90,7 @@ extern consvar_t cv_glcoronas;
 extern consvar_t cv_glcoronasize;
 #endif
 
-extern consvar_t cv_glshaders;
+extern consvar_t cv_glshaders, cv_glallowshaders;
 extern consvar_t cv_glmodels;
 extern consvar_t cv_glmodelinterpolation;
 extern consvar_t cv_glmodellighting;
@@ -130,6 +133,10 @@ extern int ps_hw_numcolors;
 extern int ps_hw_batchsorttime;
 extern int ps_hw_batchdrawtime;
 
+extern boolean gl_init;
+extern boolean gl_maploaded;
+extern boolean gl_maptexturesloaded;
+extern boolean gl_sessioncommandsadded;
 extern boolean gl_shadersavailable;
 
 #endif
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 0c1b14b20879f1136a33b7ccd982c7436fbbd0bb..670a405a1e311e67bfa4135b3fd5a3ea7926ab2e 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -364,48 +364,53 @@ static GLTextureFormat_t PCX_Load(const char *filename, int *w, int *h,
 // -----------------+
 static void md2_loadTexture(md2_t *model)
 {
-	GLPatch_t *grpatch;
+	patch_t *patch;
+	GLPatch_t *grPatch = NULL;
 	const char *filename = model->filename;
 
 	if (model->grpatch)
 	{
-		grpatch = model->grpatch;
-		Z_Free(grpatch->mipmap->data);
+		patch = model->grpatch;
+		grPatch = (GLPatch_t *)(patch->hardware);
+		if (grPatch)
+			Z_Free(grPatch->mipmap->data);
 	}
 	else
-	{
-		grpatch = Z_Calloc(sizeof *grpatch, PU_HWRPATCHINFO,
-		                   &(model->grpatch));
-		grpatch->mipmap = Z_Calloc(sizeof (GLMipmap_t), PU_HWRPATCHINFO, NULL);
-	}
+		model->grpatch = patch = Patch_Create(NULL, 0, NULL);
 
-	if (!grpatch->mipmap->downloaded && !grpatch->mipmap->data)
+	if (!patch->hardware)
+		Patch_AllocateHardwarePatch(patch);
+
+	if (grPatch == NULL)
+		grPatch = (GLPatch_t *)(patch->hardware);
+
+	if (!grPatch->mipmap->downloaded && !grPatch->mipmap->data)
 	{
 		int w = 0, h = 0;
 		UINT32 size;
 		RGBA_t *image;
 
 #ifdef HAVE_PNG
-		grpatch->mipmap->format = PNG_Load(filename, &w, &h, grpatch);
-		if (grpatch->mipmap->format == 0)
+		grPatch->mipmap->format = PNG_Load(filename, &w, &h, grPatch);
+		if (grPatch->mipmap->format == 0)
 #endif
-		grpatch->mipmap->format = PCX_Load(filename, &w, &h, grpatch);
-		if (grpatch->mipmap->format == 0)
+		grPatch->mipmap->format = PCX_Load(filename, &w, &h, grPatch);
+		if (grPatch->mipmap->format == 0)
 		{
 			model->notexturefile = true; // mark it so its not searched for again repeatedly
 			return;
 		}
 
-		grpatch->mipmap->downloaded = 0;
-		grpatch->mipmap->flags = 0;
+		grPatch->mipmap->downloaded = 0;
+		grPatch->mipmap->flags = 0;
 
-		grpatch->width = (INT16)w;
-		grpatch->height = (INT16)h;
-		grpatch->mipmap->width = (UINT16)w;
-		grpatch->mipmap->height = (UINT16)h;
+		patch->width = (INT16)w;
+		patch->height = (INT16)h;
+		grPatch->mipmap->width = (UINT16)w;
+		grPatch->mipmap->height = (UINT16)h;
 
 		// Lactozilla: Apply colour cube
-		image = grpatch->mipmap->data;
+		image = grPatch->mipmap->data;
 		size = w*h;
 		while (size--)
 		{
@@ -413,7 +418,7 @@ static void md2_loadTexture(md2_t *model)
 			image++;
 		}
 	}
-	HWD.pfnSetTexture(grpatch->mipmap);
+	HWD.pfnSetTexture(grPatch->mipmap);
 }
 
 // -----------------+
@@ -421,48 +426,53 @@ static void md2_loadTexture(md2_t *model)
 // -----------------+
 static void md2_loadBlendTexture(md2_t *model)
 {
-	GLPatch_t *grpatch;
+	patch_t *patch;
+	GLPatch_t *grPatch = NULL;
 	char *filename = Z_Malloc(strlen(model->filename)+7, PU_STATIC, NULL);
-	strcpy(filename, model->filename);
 
+	strcpy(filename, model->filename);
 	FIL_ForceExtension(filename, "_blend.png");
 
 	if (model->blendgrpatch)
 	{
-		grpatch = model->blendgrpatch;
-		Z_Free(grpatch->mipmap->data);
+		patch = model->blendgrpatch;
+		grPatch = (GLPatch_t *)(patch->hardware);
+		if (grPatch)
+			Z_Free(grPatch->mipmap->data);
 	}
 	else
-	{
-		grpatch = Z_Calloc(sizeof *grpatch, PU_HWRPATCHINFO,
-		                   &(model->blendgrpatch));
-		grpatch->mipmap = Z_Calloc(sizeof (GLMipmap_t), PU_HWRPATCHINFO, NULL);
-	}
+		model->blendgrpatch = patch = Patch_Create(NULL, 0, NULL);
+
+	if (!patch->hardware)
+		Patch_AllocateHardwarePatch(patch);
 
-	if (!grpatch->mipmap->downloaded && !grpatch->mipmap->data)
+	if (grPatch == NULL)
+		grPatch = (GLPatch_t *)(patch->hardware);
+
+	if (!grPatch->mipmap->downloaded && !grPatch->mipmap->data)
 	{
 		int w = 0, h = 0;
 #ifdef HAVE_PNG
-		grpatch->mipmap->format = PNG_Load(filename, &w, &h, grpatch);
-		if (grpatch->mipmap->format == 0)
+		grPatch->mipmap->format = PNG_Load(filename, &w, &h, grPatch);
+		if (grPatch->mipmap->format == 0)
 #endif
-		grpatch->mipmap->format = PCX_Load(filename, &w, &h, grpatch);
-		if (grpatch->mipmap->format == 0)
+		grPatch->mipmap->format = PCX_Load(filename, &w, &h, grPatch);
+		if (grPatch->mipmap->format == 0)
 		{
 			model->noblendfile = true; // mark it so its not searched for again repeatedly
 			Z_Free(filename);
 			return;
 		}
 
-		grpatch->mipmap->downloaded = 0;
-		grpatch->mipmap->flags = 0;
+		grPatch->mipmap->downloaded = 0;
+		grPatch->mipmap->flags = 0;
 
-		grpatch->width = (INT16)w;
-		grpatch->height = (INT16)h;
-		grpatch->mipmap->width = (UINT16)w;
-		grpatch->mipmap->height = (UINT16)h;
+		patch->width = (INT16)w;
+		patch->height = (INT16)h;
+		grPatch->mipmap->width = (UINT16)w;
+		grPatch->mipmap->height = (UINT16)h;
 	}
-	HWD.pfnSetTexture(grpatch->mipmap); // We do need to do this so that it can be cleared and knows to recreate it when necessary
+	HWD.pfnSetTexture(grPatch->mipmap); // We do need to do this so that it can be cleared and knows to recreate it when necessary
 
 	Z_Free(filename);
 }
@@ -692,8 +702,10 @@ spritemodelfound:
 #define SETBRIGHTNESS(brightness,r,g,b) \
 	brightness = (UINT8)(((1063*(UINT16)(r))/5000) + ((3576*(UINT16)(g))/5000) + ((361*(UINT16)(b))/5000))
 
-static void HWR_CreateBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, GLMipmap_t *grmip, INT32 skinnum, skincolornum_t color)
+static void HWR_CreateBlendedTexture(patch_t *gpatch, patch_t *blendgpatch, GLMipmap_t *grMipmap, INT32 skinnum, skincolornum_t color)
 {
+	GLPatch_t *hwrPatch = gpatch->hardware;
+	GLPatch_t *hwrBlendPatch = blendgpatch->hardware;
 	UINT16 w = gpatch->width, h = gpatch->height;
 	UINT32 size = w*h;
 	RGBA_t *image, *blendimage, *cur, blendcolor;
@@ -706,28 +718,29 @@ static void HWR_CreateBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch,
 	memset(translation, 0, sizeof(translation));
 	memset(cutoff, 0, sizeof(cutoff));
 
-	if (grmip->width == 0)
+	if (grMipmap->width == 0)
 	{
-		grmip->width = gpatch->width;
-		grmip->height = gpatch->height;
+		grMipmap->width = gpatch->width;
+		grMipmap->height = gpatch->height;
 
 		// no wrap around, no chroma key
-		grmip->flags = 0;
+		grMipmap->flags = 0;
+
 		// setup the texture info
-		grmip->format = GL_TEXFMT_RGBA;
+		grMipmap->format = GL_TEXFMT_RGBA;
 	}
 
-	if (grmip->data)
+	if (grMipmap->data)
 	{
-		Z_Free(grmip->data);
-		grmip->data = NULL;
+		Z_Free(grMipmap->data);
+		grMipmap->data = NULL;
 	}
 
-	cur = Z_Malloc(size*4, PU_HWRMODELTEXTURE, &grmip->data);
+	cur = Z_Malloc(size*4, PU_HWRMODELTEXTURE, &grMipmap->data);
 	memset(cur, 0x00, size*4);
 
-	image = gpatch->mipmap->data;
-	blendimage = blendgpatch->mipmap->data;
+	image = hwrPatch->mipmap->data;
+	blendimage = hwrBlendPatch->mipmap->data;
 
 	// TC_METALSONIC includes an actual skincolor translation, on top of its flashing.
 	if (skinnum == TC_METALSONIC)
@@ -1066,37 +1079,39 @@ skippixel:
 
 #undef SETBRIGHTNESS
 
-static void HWR_GetBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, INT32 skinnum, const UINT8 *colormap, skincolornum_t color)
+static void HWR_GetBlendedTexture(patch_t *patch, patch_t *blendpatch, INT32 skinnum, const UINT8 *colormap, skincolornum_t color)
 {
 	// mostly copied from HWR_GetMappedPatch, hence the similarities and comment
-	GLMipmap_t *grmip, *newmip;
+	GLPatch_t *grPatch = patch->hardware;
+	GLPatch_t *grBlendPatch = NULL;
+	GLMipmap_t *grMipmap, *newMipmap;
 
-	if (colormap == colormaps || colormap == NULL)
+	if (blendpatch == NULL || colormap == colormaps || colormap == NULL)
 	{
 		// Don't do any blending
-		HWD.pfnSetTexture(gpatch->mipmap);
+		HWD.pfnSetTexture(grPatch->mipmap);
 		return;
 	}
 
-	if ((blendgpatch && blendgpatch->mipmap->format)
-		&& (gpatch->width != blendgpatch->width || gpatch->height != blendgpatch->height))
+	if ((blendpatch && (grBlendPatch = blendpatch->hardware) && grBlendPatch->mipmap->format)
+		&& (patch->width != blendpatch->width || patch->height != blendpatch->height))
 	{
 		// Blend image exists, but it's bad.
-		HWD.pfnSetTexture(gpatch->mipmap);
+		HWD.pfnSetTexture(grPatch->mipmap);
 		return;
 	}
 
 	// search for the mipmap
 	// skip the first (no colormap translated)
-	for (grmip = gpatch->mipmap; grmip->nextcolormap; )
+	for (grMipmap = grPatch->mipmap; grMipmap->nextcolormap; )
 	{
-		grmip = grmip->nextcolormap;
-		if (grmip->colormap == colormap)
+		grMipmap = grMipmap->nextcolormap;
+		if (grMipmap->colormap == colormap)
 		{
-			if (grmip->downloaded && grmip->data)
+			if (grMipmap->downloaded && grMipmap->data)
 			{
-				HWD.pfnSetTexture(grmip); // found the colormap, set it to the correct texture
-				Z_ChangeTag(grmip->data, PU_HWRMODELTEXTURE_UNLOCKED);
+				HWD.pfnSetTexture(grMipmap); // found the colormap, set it to the correct texture
+				Z_ChangeTag(grMipmap->data, PU_HWRMODELTEXTURE_UNLOCKED);
 				return;
 			}
 		}
@@ -1107,18 +1122,18 @@ static void HWR_GetBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, INT
 
 	//BP: WARNING: don't free it manually without clearing the cache of harware renderer
 	//              (it have a liste of mipmap)
-	//    this malloc is cleared in HWR_FreeTextureCache
+	//    this malloc is cleared in HWR_FreeColormapCache
 	//    (...) unfortunately z_malloc fragment alot the memory :(so malloc is better
-	newmip = calloc(1, sizeof (*newmip));
-	if (newmip == NULL)
+	newMipmap = calloc(1, sizeof (*newMipmap));
+	if (newMipmap == NULL)
 		I_Error("%s: Out of memory", "HWR_GetBlendedTexture");
-	grmip->nextcolormap = newmip;
-	newmip->colormap = colormap;
+	grMipmap->nextcolormap = newMipmap;
+	newMipmap->colormap = colormap;
 
-	HWR_CreateBlendedTexture(gpatch, blendgpatch, newmip, skinnum, color);
+	HWR_CreateBlendedTexture(patch, blendpatch, newMipmap, skinnum, color);
 
-	HWD.pfnSetTexture(newmip);
-	Z_ChangeTag(newmip->data, PU_HWRMODELTEXTURE_UNLOCKED);
+	HWD.pfnSetTexture(newMipmap);
+	Z_ChangeTag(newMipmap->data, PU_HWRMODELTEXTURE_UNLOCKED);
 }
 
 #define NORMALFOG 0x00000000
@@ -1206,9 +1221,11 @@ static UINT8 HWR_GetModelSprite2(md2_t *md2, skin_t *skin, UINT8 spr2, player_t
 }
 
 // Adjust texture coords of model to fit into a patch's max_s and max_t
-static void adjustTextureCoords(model_t *model, GLPatch_t *gpatch)
+static void adjustTextureCoords(model_t *model, patch_t *patch)
 {
 	int i;
+	GLPatch_t *gpatch = ((GLPatch_t *)patch->hardware);
+
 	for (i = 0; i < model->numMeshes; i++)
 	{
 		int j;
@@ -1308,7 +1325,8 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 
 	// Look at HWR_ProjectSprite for more
 	{
-		GLPatch_t *gpatch;
+		patch_t *gpatch, *blendgpatch;
+		GLPatch_t *hwrPatch = NULL, *hwrBlendPatch = NULL;
 		INT32 durs = spr->mobj->state->tics;
 		INT32 tics = spr->mobj->tics;
 		//mdlframe_t *next = NULL;
@@ -1326,15 +1344,16 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 		//if (tics > durs)
 			//durs = tics;
 
-		if (spr->mobj->flags2 & MF2_SHADOW)
-			Surf.PolyColor.s.alpha = 0x40;
-		else if (spr->mobj->frame & FF_TRANSMASK)
-			HWR_TranstableToAlpha((spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT, &Surf);
+		if (spr->mobj->frame & FF_TRANSMASK)
+			Surf.PolyFlags = HWR_SurfaceBlend(spr->mobj->blendmode, (spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT, &Surf);
 		else
-			Surf.PolyColor.s.alpha = 0xFF;
+		{
+			Surf.PolyColor.s.alpha = (spr->mobj->flags2 & MF2_SHADOW) ? 0x40 : 0xff;
+			Surf.PolyFlags = HWR_GetBlendModeFlag(spr->mobj->blendmode);
+		}
 
-		// dont forget to enabled the depth test because we can't do this like
-		// before: polygons models are not sorted
+		// don't forget to enable the depth test because we can't do this
+		// like before: model polygons are not sorted
 
 		// 1. load model+texture if not already loaded
 		// 2. draw model with correct position, rotation,...
@@ -1353,14 +1372,26 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 		// texture loading before model init, so it knows if sprite graphics are used, which
 		// means that texture coordinates have to be adjusted
 		gpatch = md2->grpatch;
-		if (!gpatch || ((!gpatch->mipmap->format || !gpatch->mipmap->downloaded) && !md2->notexturefile))
+		if (gpatch)
+			hwrPatch = ((GLPatch_t *)gpatch->hardware);
+
+		if (!gpatch || !hwrPatch
+		|| ((!hwrPatch->mipmap->format || !hwrPatch->mipmap->downloaded) && !md2->notexturefile))
 			md2_loadTexture(md2);
-		gpatch = md2->grpatch; // Load it again, because it isn't being loaded into gpatch after md2_loadtexture...
 
-		if ((gpatch && gpatch->mipmap->format) // don't load the blend texture if the base texture isn't available
-			&& (!md2->blendgrpatch
-			|| ((!((GLPatch_t *)md2->blendgrpatch)->mipmap->format || !((GLPatch_t *)md2->blendgrpatch)->mipmap->downloaded)
-			&& !md2->noblendfile)))
+		// Load it again, because it isn't being loaded into gpatch after md2_loadtexture...
+		gpatch = md2->grpatch;
+		if (gpatch)
+			hwrPatch = ((GLPatch_t *)gpatch->hardware);
+
+		// Load blend texture
+		blendgpatch = md2->blendgrpatch;
+		if (blendgpatch)
+			hwrBlendPatch = ((GLPatch_t *)blendgpatch->hardware);
+
+		if ((gpatch && hwrPatch && hwrPatch->mipmap->format) // don't load the blend texture if the base texture isn't available
+			&& (!blendgpatch || !hwrBlendPatch
+			|| ((!hwrBlendPatch->mipmap->format || !hwrBlendPatch->mipmap->downloaded) && !md2->noblendfile)))
 			md2_loadBlendTexture(md2);
 
 		if (md2->error)
@@ -1376,7 +1407,7 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 				md2_printModelInfo(md2->model);
 				// If model uses sprite patch as texture, then
 				// adjust texture coordinates to take power of two textures into account
-				if (!gpatch || !gpatch->mipmap->format)
+				if (!gpatch || !hwrPatch->mipmap->format)
 					adjustTextureCoords(md2->model, spr->gpatch);
 				// note down the max_s and max_t that end up in the VBO
 				md2->model->vbo_max_s = md2->model->max_s;
@@ -1395,7 +1426,7 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 		finalscale = md2->scale;
 		//Hurdler: arf, I don't like that implementation at all... too much crappy
 
-		if (gpatch && gpatch->mipmap->format) // else if meant that if a texture couldn't be loaded, it would just end up using something else's texture
+		if (gpatch && hwrPatch && hwrPatch->mipmap->format) // else if meant that if a texture couldn't be loaded, it would just end up using something else's texture
 		{
 			INT32 skinnum = TC_DEFAULT;
 
@@ -1428,21 +1459,19 @@ boolean HWR_DrawModel(gl_vissprite_t *spr)
 			}
 
 			// Translation or skin number found
-			HWR_GetBlendedTexture(gpatch, (GLPatch_t *)md2->blendgrpatch, skinnum, spr->colormap, (skincolornum_t)spr->mobj->color);
+			HWR_GetBlendedTexture(gpatch, blendgpatch, skinnum, spr->colormap, (skincolornum_t)spr->mobj->color);
 		}
-		else
+		else // Sprite
 		{
-			// Sprite
-			gpatch = spr->gpatch; //W_CachePatchNum(spr->patchlumpnum, PU_CACHE);
 			// Check if sprite dimensions are different from previously used sprite.
 			// If so, uvs need to be readjusted.
 			// Comparing floats with the != operator here should be okay because they
 			// are just copies of glpatches' max_s and max_t values.
 			// Instead of the != operator, memcmp is used to avoid a compiler warning.
-			if (memcmp(&(gpatch->max_s), &(md2->model->max_s), sizeof(md2->model->max_s)) != 0 ||
-				memcmp(&(gpatch->max_t), &(md2->model->max_t), sizeof(md2->model->max_t)) != 0)
+			if (memcmp(&(hwrPatch->max_s), &(md2->model->max_s), sizeof(md2->model->max_s)) != 0 ||
+				memcmp(&(hwrPatch->max_t), &(md2->model->max_t), sizeof(md2->model->max_t)) != 0)
 				adjustTextureCoords(md2->model, gpatch);
-			HWR_GetMappedPatch(gpatch, spr->colormap);
+			HWR_GetMappedPatch(spr->gpatch, spr->colormap);
 		}
 
 		if (spr->mobj->frame & FF_ANIMATE)
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index db3c6a17dfe6f8cf6b4787a8d009c96779916c76..39552dc1cfc9d4da74329a0b22b6b34017ab406e 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -91,13 +91,6 @@ static GLuint startScreenWipe = 0;
 static GLuint endScreenWipe = 0;
 static GLuint finalScreenTexture = 0;
 
-// Lactozilla: Shader functions
-static void *Shader_Load(FSurfaceInfo *Surface, GLRGBAFloat *poly, GLRGBAFloat *tint, GLRGBAFloat *fade);
-static void Shader_SetUniforms(FSurfaceInfo *Surface, GLRGBAFloat *poly, GLRGBAFloat *tint, GLRGBAFloat *fade);
-static void Shader_CompileError(const char *message, GLuint program, INT32 shadernum);
-
-static GLRGBAFloat shader_defaultcolor = {1.0f, 1.0f, 1.0f, 1.0f};
-
 // shortcut for ((float)1/i)
 static const GLfloat byte2float[256] = {
 	0.000000f, 0.003922f, 0.007843f, 0.011765f, 0.015686f, 0.019608f, 0.023529f, 0.027451f,
@@ -426,6 +419,10 @@ static PFNglBufferData pglBufferData;
 typedef void (APIENTRY * PFNglDeleteBuffers) (GLsizei n, const GLuint *buffers);
 static PFNglDeleteBuffers pglDeleteBuffers;
 
+/* 2.0 functions */
+typedef void (APIENTRY * PFNglBlendEquation) (GLenum mode);
+static PFNglBlendEquation pglBlendEquation;
+
 
 /* 1.2 Parms */
 /* GL_CLAMP_TO_EDGE_EXT */
@@ -533,8 +530,8 @@ boolean SetupGLfunc(void)
 	return true;
 }
 
-static boolean gl_allowshaders = false;
 static boolean gl_shadersenabled = false;
+static hwdshaderoption_t gl_allowshaders = HWD_SHADEROPTION_OFF;
 
 #ifdef GL_SHADERS
 typedef GLuint 	(APIENTRY *PFNglCreateShader)		(GLenum);
@@ -544,6 +541,7 @@ typedef void 	(APIENTRY *PFNglGetShaderiv)		(GLuint, GLenum, GLint*);
 typedef void 	(APIENTRY *PFNglGetShaderInfoLog)	(GLuint, GLsizei, GLsizei*, GLchar*);
 typedef void 	(APIENTRY *PFNglDeleteShader)		(GLuint);
 typedef GLuint 	(APIENTRY *PFNglCreateProgram)		(void);
+typedef void  	(APIENTRY *PFNglDeleteProgram)		(GLuint);
 typedef void 	(APIENTRY *PFNglAttachShader)		(GLuint, GLuint);
 typedef void 	(APIENTRY *PFNglLinkProgram)		(GLuint);
 typedef void 	(APIENTRY *PFNglGetProgramiv)		(GLuint, GLenum, GLint*);
@@ -565,6 +563,7 @@ static PFNglGetShaderiv pglGetShaderiv;
 static PFNglGetShaderInfoLog pglGetShaderInfoLog;
 static PFNglDeleteShader pglDeleteShader;
 static PFNglCreateProgram pglCreateProgram;
+static PFNglDeleteProgram pglDeleteProgram;
 static PFNglAttachShader pglAttachShader;
 static PFNglLinkProgram pglLinkProgram;
 static PFNglGetProgramiv pglGetProgramiv;
@@ -579,12 +578,6 @@ static PFNglUniform2fv pglUniform2fv;
 static PFNglUniform3fv pglUniform3fv;
 static PFNglGetUniformLocation pglGetUniformLocation;
 
-// 18032019
-static GLuint gl_currentshaderprogram = 0;
-static boolean gl_shaderprogramchanged = true;
-
-static shadersource_t gl_customshaders[HWR_MAXSHADERS];
-
 // 13062019
 typedef enum
 {
@@ -602,17 +595,37 @@ typedef enum
 	gluniform_max,
 } gluniform_t;
 
-typedef struct gl_shaderprogram_s
+typedef struct gl_shader_s
 {
 	GLuint program;
-	boolean custom;
 	GLint uniforms[gluniform_max+1];
-} gl_shaderprogram_t;
-static gl_shaderprogram_t gl_shaderprograms[HWR_MAXSHADERS];
+	boolean custom;
+} gl_shader_t;
+
+static gl_shader_t gl_shaders[HWR_MAXSHADERS];
+static gl_shader_t gl_usershaders[HWR_MAXSHADERS];
+static shadersource_t gl_customshaders[HWR_MAXSHADERS];
+
+// 09102020
+typedef struct gl_shaderstate_s
+{
+	gl_shader_t *current;
+	GLuint type;
+	GLuint program;
+	boolean changed;
+} gl_shaderstate_t;
+static gl_shaderstate_t gl_shaderstate;
 
 // Shader info
 static INT32 shader_leveltime = 0;
 
+// Lactozilla: Shader functions
+static boolean Shader_CompileProgram(gl_shader_t *shader, GLint i, const GLchar *vert_shader, const GLchar *frag_shader);
+static void Shader_CompileError(const char *message, GLuint program, INT32 shadernum);
+static void Shader_SetUniforms(FSurfaceInfo *Surface, GLRGBAFloat *poly, GLRGBAFloat *tint, GLRGBAFloat *fade);
+
+static GLRGBAFloat shader_defaultcolor = {1.0f, 1.0f, 1.0f, 1.0f};
+
 // ================
 //  Vertex shaders
 // ================
@@ -865,6 +878,9 @@ void SetupGLFunc4(void)
 	pglBufferData = GetGLFunc("glBufferData");
 	pglDeleteBuffers = GetGLFunc("glDeleteBuffers");
 
+	/* 2.0 funcs */
+	pglBlendEquation = GetGLFunc("glBlendEquation");
+
 #ifdef GL_SHADERS
 	pglCreateShader = GetGLFunc("glCreateShader");
 	pglShaderSource = GetGLFunc("glShaderSource");
@@ -873,6 +889,7 @@ void SetupGLFunc4(void)
 	pglGetShaderInfoLog = GetGLFunc("glGetShaderInfoLog");
 	pglDeleteShader = GetGLFunc("glDeleteShader");
 	pglCreateProgram = GetGLFunc("glCreateProgram");
+	pglDeleteProgram = GetGLFunc("glDeleteProgram");
 	pglAttachShader = GetGLFunc("glAttachShader");
 	pglLinkProgram = GetGLFunc("glLinkProgram");
 	pglGetProgramiv = GetGLFunc("glGetProgramiv");
@@ -896,113 +913,56 @@ void SetupGLFunc4(void)
 EXPORT boolean HWRAPI(CompileShaders) (void)
 {
 #ifdef GL_SHADERS
-	GLuint gl_vertShader, gl_fragShader;
-	GLint i, result;
+	GLint i;
 
-	if (!pglUseProgram) return false;
+	if (!pglUseProgram)
+		return false;
 
-	gl_customshaders[0].vertex = NULL;
-	gl_customshaders[0].fragment = NULL;
+	gl_customshaders[SHADER_DEFAULT].vertex = NULL;
+	gl_customshaders[SHADER_DEFAULT].fragment = NULL;
 
 	for (i = 0; gl_shadersources[i].vertex && gl_shadersources[i].fragment; i++)
 	{
-		gl_shaderprogram_t *shader;
+		gl_shader_t *shader, *usershader;
 		const GLchar *vert_shader = gl_shadersources[i].vertex;
 		const GLchar *frag_shader = gl_shadersources[i].fragment;
-		boolean custom = ((gl_customshaders[i].vertex || gl_customshaders[i].fragment) && (i > 0));
-
-		// 18032019
-		if (gl_customshaders[i].vertex)
-			vert_shader = gl_customshaders[i].vertex;
-		if (gl_customshaders[i].fragment)
-			frag_shader = gl_customshaders[i].fragment;
 
 		if (i >= HWR_MAXSHADERS)
 			break;
 
-		shader = &gl_shaderprograms[i];
-		shader->program = 0;
-		shader->custom = custom;
+		shader = &gl_shaders[i];
+		usershader = &gl_usershaders[i];
 
-		//
-		// Load and compile vertex shader
-		//
-		gl_vertShader = pglCreateShader(GL_VERTEX_SHADER);
-		if (!gl_vertShader)
-		{
-			GL_MSG_Error("CompileShaders: Error creating vertex shader %s\n", HWR_GetShaderName(i));
-			continue;
-		}
-
-		pglShaderSource(gl_vertShader, 1, &vert_shader, NULL);
-		pglCompileShader(gl_vertShader);
-
-		// check for compile errors
-		pglGetShaderiv(gl_vertShader, GL_COMPILE_STATUS, &result);
-		if (result == GL_FALSE)
-		{
-			Shader_CompileError("Error compiling vertex shader", gl_vertShader, i);
-			continue;
-		}
+		if (shader->program)
+			pglDeleteProgram(shader->program);
+		if (usershader->program)
+			pglDeleteProgram(usershader->program);
 
-		//
-		// Load and compile fragment shader
-		//
-		gl_fragShader = pglCreateShader(GL_FRAGMENT_SHADER);
-		if (!gl_fragShader)
-		{
-			GL_MSG_Error("CompileShaders: Error creating fragment shader %s\n", HWR_GetShaderName(i));
-			continue;
-		}
+		shader->program = 0;
+		usershader->program = 0;
 
-		pglShaderSource(gl_fragShader, 1, &frag_shader, NULL);
-		pglCompileShader(gl_fragShader);
+		if (!Shader_CompileProgram(shader, i, vert_shader, frag_shader))
+			shader->program = 0;
 
-		// check for compile errors
-		pglGetShaderiv(gl_fragShader, GL_COMPILE_STATUS, &result);
-		if (result == GL_FALSE)
-		{
-			Shader_CompileError("Error compiling fragment shader", gl_fragShader, i);
+		// Compile custom shader
+		if ((i == SHADER_DEFAULT) || !(gl_customshaders[i].vertex || gl_customshaders[i].fragment))
 			continue;
-		}
-
-		shader->program = pglCreateProgram();
-		pglAttachShader(shader->program, gl_vertShader);
-		pglAttachShader(shader->program, gl_fragShader);
-		pglLinkProgram(shader->program);
 
-		// check link status
-		pglGetProgramiv(shader->program, GL_LINK_STATUS, &result);
-
-		// delete the shader objects
-		pglDeleteShader(gl_vertShader);
-		pglDeleteShader(gl_fragShader);
+		// 18032019
+		if (gl_customshaders[i].vertex)
+			vert_shader = gl_customshaders[i].vertex;
+		if (gl_customshaders[i].fragment)
+			frag_shader = gl_customshaders[i].fragment;
 
-		// couldn't link?
-		if (result != GL_TRUE)
+		if (!Shader_CompileProgram(usershader, i, vert_shader, frag_shader))
 		{
-			shader->program = 0;
-			shader->custom = false;
-			GL_MSG_Error("CompileShaders: Error linking shader program %s\n", HWR_GetShaderName(i));
-			continue;
+			GL_MSG_Warning("CompileShaders: Could not compile custom shader program for %s\n", HWR_GetShaderName(i));
+			usershader->program = 0;
 		}
+	}
 
-		// 13062019
-#define GETUNI(uniform) pglGetUniformLocation(shader->program, uniform);
-
-		// lighting
-		shader->uniforms[gluniform_poly_color] = GETUNI("poly_color");
-		shader->uniforms[gluniform_tint_color] = GETUNI("tint_color");
-		shader->uniforms[gluniform_fade_color] = GETUNI("fade_color");
-		shader->uniforms[gluniform_lighting] = GETUNI("lighting");
-		shader->uniforms[gluniform_fade_start] = GETUNI("fade_start");
-		shader->uniforms[gluniform_fade_end] = GETUNI("fade_end");
-
-		// misc. (custom shaders)
-		shader->uniforms[gluniform_leveltime] = GETUNI("leveltime");
+	SetShader(SHADER_DEFAULT);
 
-#undef GETUNI
-	}
 	return true;
 #else
 	return false;
@@ -1070,26 +1030,45 @@ EXPORT void HWRAPI(LoadCustomShader) (int number, char *code, size_t size, boole
 #endif
 }
 
-EXPORT void HWRAPI(SetShader) (int shader)
+EXPORT void HWRAPI(SetShader) (int type)
 {
 #ifdef GL_SHADERS
-	if (gl_allowshaders)
+	if (gl_allowshaders != HWD_SHADEROPTION_OFF)
 	{
+		gl_shader_t *shader = gl_shaderstate.current;
+
 		// If using model lighting, set the appropriate shader.
 		// However don't override a custom shader.
-		if (shader == SHADER_MODEL && model_lighting
-		&& !(gl_shaderprograms[SHADER_MODEL].custom && !gl_shaderprograms[SHADER_MODEL_LIGHTING].custom))
-			shader = SHADER_MODEL_LIGHTING;
-		if ((GLuint)shader != gl_currentshaderprogram)
+		if (type == SHADER_MODEL && model_lighting
+		&& !(gl_shaders[SHADER_MODEL].custom && !gl_shaders[SHADER_MODEL_LIGHTING].custom))
+			type = SHADER_MODEL_LIGHTING;
+
+		if ((shader == NULL) || (GLuint)type != gl_shaderstate.type)
+		{
+			gl_shader_t *baseshader = &gl_shaders[type];
+			gl_shader_t *usershader = &gl_usershaders[type];
+
+			if (usershader->program)
+				shader = (gl_allowshaders == HWD_SHADEROPTION_NOCUSTOM) ? baseshader : usershader;
+			else
+				shader = baseshader;
+
+			gl_shaderstate.current = shader;
+			gl_shaderstate.type = type;
+			gl_shaderstate.changed = true;
+		}
+
+		if (gl_shaderstate.program != shader->program)
 		{
-			gl_currentshaderprogram = shader;
-			gl_shaderprogramchanged = true;
+			gl_shaderstate.program = shader->program;
+			gl_shaderstate.changed = true;
 		}
-		gl_shadersenabled = true;
+
+		gl_shadersenabled = (shader->program != 0);
 		return;
 	}
 #else
-	(void)shader;
+	(void)type;
 #endif
 	gl_shadersenabled = false;
 }
@@ -1097,11 +1076,15 @@ EXPORT void HWRAPI(SetShader) (int shader)
 EXPORT void HWRAPI(UnSetShader) (void)
 {
 #ifdef GL_SHADERS
-	gl_shadersenabled = false;
-	gl_currentshaderprogram = 0;
-	if (!pglUseProgram) return;
-	pglUseProgram(0);
+	gl_shaderstate.current = NULL;
+	gl_shaderstate.type = 0;
+	gl_shaderstate.program = 0;
+
+	if (pglUseProgram)
+		pglUseProgram(0);
 #endif
+
+	gl_shadersenabled = false;
 }
 
 EXPORT void HWRAPI(CleanShaders) (void)
@@ -1258,6 +1241,7 @@ void SetStates(void)
 
 	pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
 
+	pglEnable(GL_ALPHA_TEST);
 	pglAlphaFunc(GL_NOTEQUAL, 0.0f);
 
 	//pglBlendFunc(GL_ONE, GL_ZERO); // copy pixel to frame buffer (opaque)
@@ -1300,6 +1284,17 @@ void SetStates(void)
 }
 
 
+// -----------------+
+// DeleteTexture    : Deletes a texture from the GPU and frees its data
+// -----------------+
+EXPORT void HWRAPI(DeleteTexture) (FTextureInfo *pTexInfo)
+{
+	if (pTexInfo->downloaded)
+		pglDeleteTextures(1, (GLuint *)&pTexInfo->downloaded);
+	pTexInfo->downloaded = 0;
+}
+
+
 // -----------------+
 // Flush            : flush OpenGL textures
 //                  : Clear list of downloaded mipmaps
@@ -1310,9 +1305,7 @@ void Flush(void)
 
 	while (gl_cachehead)
 	{
-		if (gl_cachehead->downloaded)
-			pglDeleteTextures(1, (GLuint *)&gl_cachehead->downloaded);
-		gl_cachehead->downloaded = 0;
+		DeleteTexture(gl_cachehead);
 		gl_cachehead = gl_cachehead->nextmipmap;
 	}
 	gl_cachetail = gl_cachehead = NULL; //Hurdler: well, gl_cachehead is already NULL
@@ -1517,64 +1510,110 @@ EXPORT void HWRAPI(Draw2DLine) (F2DCoord * v1,
 	pglEnable(GL_TEXTURE_2D);
 }
 
+
+// -----------------+
+// SetBlend         : Set render mode
+// -----------------+
+// PF_Masked - we could use an ALPHA_TEST of GL_EQUAL, and alpha ref of 0,
+//             is it faster when pixels are discarded ?
+
 static void Clamp2D(GLenum pname)
 {
 	pglTexParameteri(GL_TEXTURE_2D, pname, GL_CLAMP); // fallback clamp
 	pglTexParameteri(GL_TEXTURE_2D, pname, GL_CLAMP_TO_EDGE);
 }
 
+static void SetBlendEquation(GLenum mode)
+{
+	if (pglBlendEquation)
+		pglBlendEquation(mode);
+}
+
+static void SetBlendMode(FBITFIELD flags)
+{
+	// Set blending function
+	switch (flags)
+	{
+		case PF_Translucent & PF_Blending:
+			pglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // alpha = level of transparency
+			break;
+		case PF_Masked & PF_Blending:
+			// Hurdler: does that mean lighting is only made by alpha src?
+			// it sounds ok, but not for polygonsmooth
+			pglBlendFunc(GL_SRC_ALPHA, GL_ZERO);                // 0 alpha = holes in texture
+			break;
+		case PF_Additive & PF_Blending:
+		case PF_Subtractive & PF_Blending:
+		case PF_ReverseSubtract & PF_Blending:
+		case PF_Environment & PF_Blending:
+			pglBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+			break;
+		case PF_AdditiveSource & PF_Blending:
+			pglBlendFunc(GL_SRC_ALPHA, GL_ONE); // src * alpha + dest
+			break;
+		case PF_Multiplicative & PF_Blending:
+			pglBlendFunc(GL_DST_COLOR, GL_ZERO);
+			break;
+		case PF_Fog & PF_Fog:
+			// Sryder: Fog
+			// multiplies input colour by input alpha, and destination colour by input colour, then adds them
+			pglBlendFunc(GL_SRC_ALPHA, GL_SRC_COLOR);
+			break;
+		default: // must be 0, otherwise it's an error
+			// No blending
+			pglBlendFunc(GL_ONE, GL_ZERO);   // the same as no blending
+			break;
+	}
+
+	// Set blending equation
+	switch (flags)
+	{
+		case PF_Subtractive & PF_Blending:
+			SetBlendEquation(GL_FUNC_SUBTRACT);
+			break;
+		case PF_ReverseSubtract & PF_Blending:
+			// good for shadow
+			// not really but what else ?
+			SetBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
+			break;
+		default:
+			SetBlendEquation(GL_FUNC_ADD);
+			break;
+	}
+
+	// Alpha test
+	switch (flags)
+	{
+		case PF_Masked & PF_Blending:
+			pglAlphaFunc(GL_GREATER, 0.5f);
+			break;
+		case PF_Translucent & PF_Blending:
+		case PF_Additive & PF_Blending:
+		case PF_AdditiveSource & PF_Blending:
+		case PF_Subtractive & PF_Blending:
+		case PF_ReverseSubtract & PF_Blending:
+		case PF_Environment & PF_Blending:
+		case PF_Multiplicative & PF_Blending:
+			pglAlphaFunc(GL_NOTEQUAL, 0.0f);
+			break;
+		case PF_Fog & PF_Fog:
+			pglAlphaFunc(GL_ALWAYS, 0.0f); // Don't discard zero alpha fragments
+			break;
+		default:
+			pglAlphaFunc(GL_GREATER, 0.5f);
+			break;
+	}
+}
 
-// -----------------+
-// SetBlend         : Set render mode
-// -----------------+
-// PF_Masked - we could use an ALPHA_TEST of GL_EQUAL, and alpha ref of 0,
-//             is it faster when pixels are discarded ?
 EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags)
 {
 	FBITFIELD Xor;
 	Xor = CurrentPolyFlags^PolyFlags;
-	if (Xor & (PF_Blending|PF_RemoveYWrap|PF_ForceWrapX|PF_ForceWrapY|PF_Occlude|PF_NoTexture|PF_Modulated|PF_NoDepthTest|PF_Decal|PF_Invisible|PF_NoAlphaTest))
+	if (Xor & (PF_Blending|PF_RemoveYWrap|PF_ForceWrapX|PF_ForceWrapY|PF_Occlude|PF_NoTexture|PF_Modulated|PF_NoDepthTest|PF_Decal|PF_Invisible))
 	{
-		if (Xor&(PF_Blending)) // if blending mode must be changed
-		{
-			switch (PolyFlags & PF_Blending) {
-				case PF_Translucent & PF_Blending:
-					pglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // alpha = level of transparency
-					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
-					break;
-				case PF_Masked & PF_Blending:
-					// Hurdler: does that mean lighting is only made by alpha src?
-					// it sounds ok, but not for polygonsmooth
-					pglBlendFunc(GL_SRC_ALPHA, GL_ZERO);                // 0 alpha = holes in texture
-					pglAlphaFunc(GL_GREATER, 0.5f);
-					break;
-				case PF_Additive & PF_Blending:
-					pglBlendFunc(GL_SRC_ALPHA, GL_ONE);                 // src * alpha + dest
-					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
-					break;
-				case PF_Environment & PF_Blending:
-					pglBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
-					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
-					break;
-				case PF_Substractive & PF_Blending:
-					// good for shadow
-					// not really but what else ?
-					pglBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
-					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
-					break;
-				case PF_Fog & PF_Fog:
-					// Sryder: Fog
-					// multiplies input colour by input alpha, and destination colour by input colour, then adds them
-					pglBlendFunc(GL_SRC_ALPHA, GL_SRC_COLOR);
-					pglAlphaFunc(GL_ALWAYS, 0.0f); // Don't discard zero alpha fragments
-					break;
-				default : // must be 0, otherwise it's an error
-					// No blending
-					pglBlendFunc(GL_ONE, GL_ZERO);   // the same as no blending
-					pglAlphaFunc(GL_GREATER, 0.5f);
-					break;
-			}
-		}
+		if (Xor & PF_Blending) // if blending mode must be changed
+			SetBlendMode(PolyFlags & PF_Blending);
+
 		if (Xor & PF_NoAlphaTest)
 		{
 			if (PolyFlags & PF_NoAlphaTest)
@@ -1591,7 +1630,7 @@ EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags)
 				pglDisable(GL_POLYGON_OFFSET_FILL);
 		}
 
-		if (Xor&PF_NoDepthTest)
+		if (Xor & PF_NoDepthTest)
 		{
 			if (PolyFlags & PF_NoDepthTest)
 				pglDepthFunc(GL_ALWAYS); //pglDisable(GL_DEPTH_TEST);
@@ -1599,25 +1638,25 @@ EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags)
 				pglDepthFunc(GL_LEQUAL); //pglEnable(GL_DEPTH_TEST);
 		}
 
-		if (Xor&PF_RemoveYWrap)
+		if (Xor & PF_RemoveYWrap)
 		{
 			if (PolyFlags & PF_RemoveYWrap)
 				Clamp2D(GL_TEXTURE_WRAP_T);
 		}
 
-		if (Xor&PF_ForceWrapX)
+		if (Xor & PF_ForceWrapX)
 		{
 			if (PolyFlags & PF_ForceWrapX)
 				pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
 		}
 
-		if (Xor&PF_ForceWrapY)
+		if (Xor & PF_ForceWrapY)
 		{
 			if (PolyFlags & PF_ForceWrapY)
 				pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
 		}
 
-		if (Xor&PF_Modulated)
+		if (Xor & PF_Modulated)
 		{
 #if defined (__unix__) || defined (UNIXCOMMON)
 			if (oglflags & GLF_NOTEXENV)
@@ -1901,42 +1940,24 @@ EXPORT void HWRAPI(SetTexture) (FTextureInfo *pTexInfo)
 	}
 }
 
-static void *Shader_Load(FSurfaceInfo *Surface, GLRGBAFloat *poly, GLRGBAFloat *tint, GLRGBAFloat *fade)
-{
-#ifdef GL_SHADERS
-	if (gl_shadersenabled && pglUseProgram)
-	{
-		gl_shaderprogram_t *shader = &gl_shaderprograms[gl_currentshaderprogram];
-		if (shader->program)
-		{
-			if (gl_shaderprogramchanged)
-			{
-				pglUseProgram(gl_shaderprograms[gl_currentshaderprogram].program);
-				gl_shaderprogramchanged = false;
-			}
-			Shader_SetUniforms(Surface, poly, tint, fade);
-			return shader;
-		}
-		else
-			pglUseProgram(0);
-	}
-#else
-	(void)Surface;
-	(void)poly;
-	(void)tint;
-	(void)fade;
-#endif
-	return NULL;
-}
-
 static void Shader_SetUniforms(FSurfaceInfo *Surface, GLRGBAFloat *poly, GLRGBAFloat *tint, GLRGBAFloat *fade)
 {
 #ifdef GL_SHADERS
-	if (gl_shadersenabled)
+	gl_shader_t *shader = gl_shaderstate.current;
+
+	if (gl_shadersenabled && (shader != NULL) && pglUseProgram)
 	{
-		gl_shaderprogram_t *shader = &gl_shaderprograms[gl_currentshaderprogram];
 		if (!shader->program)
+		{
+			pglUseProgram(0);
 			return;
+		}
+
+		if (gl_shaderstate.changed)
+		{
+			pglUseProgram(shader->program);
+			gl_shaderstate.changed = false;
+		}
 
 		// Color uniforms can be left NULL and will be set to white (1.0f, 1.0f, 1.0f, 1.0f)
 		if (poly == NULL)
@@ -1989,6 +2010,97 @@ static void Shader_SetUniforms(FSurfaceInfo *Surface, GLRGBAFloat *poly, GLRGBAF
 #endif
 }
 
+static boolean Shader_CompileProgram(gl_shader_t *shader, GLint i, const GLchar *vert_shader, const GLchar *frag_shader)
+{
+	GLuint gl_vertShader, gl_fragShader;
+	GLint result;
+
+	//
+	// Load and compile vertex shader
+	//
+	gl_vertShader = pglCreateShader(GL_VERTEX_SHADER);
+	if (!gl_vertShader)
+	{
+		GL_MSG_Error("Shader_CompileProgram: Error creating vertex shader %s\n", HWR_GetShaderName(i));
+		return false;
+	}
+
+	pglShaderSource(gl_vertShader, 1, &vert_shader, NULL);
+	pglCompileShader(gl_vertShader);
+
+	// check for compile errors
+	pglGetShaderiv(gl_vertShader, GL_COMPILE_STATUS, &result);
+	if (result == GL_FALSE)
+	{
+		Shader_CompileError("Error compiling vertex shader", gl_vertShader, i);
+		pglDeleteShader(gl_vertShader);
+		return false;
+	}
+
+	//
+	// Load and compile fragment shader
+	//
+	gl_fragShader = pglCreateShader(GL_FRAGMENT_SHADER);
+	if (!gl_fragShader)
+	{
+		GL_MSG_Error("Shader_CompileProgram: Error creating fragment shader %s\n", HWR_GetShaderName(i));
+		pglDeleteShader(gl_vertShader);
+		pglDeleteShader(gl_fragShader);
+		return false;
+	}
+
+	pglShaderSource(gl_fragShader, 1, &frag_shader, NULL);
+	pglCompileShader(gl_fragShader);
+
+	// check for compile errors
+	pglGetShaderiv(gl_fragShader, GL_COMPILE_STATUS, &result);
+	if (result == GL_FALSE)
+	{
+		Shader_CompileError("Error compiling fragment shader", gl_fragShader, i);
+		pglDeleteShader(gl_vertShader);
+		pglDeleteShader(gl_fragShader);
+		return false;
+	}
+
+	shader->program = pglCreateProgram();
+	pglAttachShader(shader->program, gl_vertShader);
+	pglAttachShader(shader->program, gl_fragShader);
+	pglLinkProgram(shader->program);
+
+	// check link status
+	pglGetProgramiv(shader->program, GL_LINK_STATUS, &result);
+
+	// delete the shader objects
+	pglDeleteShader(gl_vertShader);
+	pglDeleteShader(gl_fragShader);
+
+	// couldn't link?
+	if (result != GL_TRUE)
+	{
+		GL_MSG_Error("Shader_CompileProgram: Error linking shader program %s\n", HWR_GetShaderName(i));
+		pglDeleteProgram(shader->program);
+		return false;
+	}
+
+	// 13062019
+#define GETUNI(uniform) pglGetUniformLocation(shader->program, uniform);
+
+	// lighting
+	shader->uniforms[gluniform_poly_color] = GETUNI("poly_color");
+	shader->uniforms[gluniform_tint_color] = GETUNI("tint_color");
+	shader->uniforms[gluniform_fade_color] = GETUNI("fade_color");
+	shader->uniforms[gluniform_lighting] = GETUNI("lighting");
+	shader->uniforms[gluniform_fade_start] = GETUNI("fade_start");
+	shader->uniforms[gluniform_fade_end] = GETUNI("fade_end");
+
+	// misc. (custom shaders)
+	shader->uniforms[gluniform_leveltime] = GETUNI("leveltime");
+
+#undef GETUNI
+
+	return true;
+}
+
 static void Shader_CompileError(const char *message, GLuint program, INT32 shadernum)
 {
 	GLchar *infoLog = NULL;
@@ -2002,7 +2114,7 @@ static void Shader_CompileError(const char *message, GLuint program, INT32 shade
 		pglGetShaderInfoLog(program, logLength, NULL, infoLog);
 	}
 
-	GL_MSG_Error("CompileShaders: %s (%s)\n%s", message, HWR_GetShaderName(shadernum), (infoLog ? infoLog : ""));
+	GL_MSG_Error("Shader_CompileProgram: %s (%s)\n%s", message, HWR_GetShaderName(shadernum), (infoLog ? infoLog : ""));
 
 	if (infoLog)
 		free(infoLog);
@@ -2112,7 +2224,7 @@ static void PreparePolygon(FSurfaceInfo *pSurf, FOutVector *pOutVerts, FBITFIELD
 		pglColor4ubv(c);
 	}
 
-	Shader_Load(pSurf, &poly, &tint, &fade);
+	Shader_SetUniforms(pSurf, &poly, &tint, &fade);
 }
 
 // -----------------+
@@ -2158,7 +2270,7 @@ EXPORT void HWRAPI(RenderSkyDome) (gl_sky_t *sky)
 {
 	int i, j;
 
-	Shader_Load(NULL, NULL, NULL, NULL);
+	Shader_SetUniforms(NULL, NULL, NULL, NULL);
 
 	// Build the sky dome! Yes!
 	if (sky->rebuild)
@@ -2250,15 +2362,7 @@ EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value)
 			break;
 
 		case HWD_SET_SHADERS:
-			switch (Value)
-			{
-				case 1:
-					gl_allowshaders = true;
-					break;
-				default:
-					gl_allowshaders = false;
-					break;
-			}
+			gl_allowshaders = (hwdshaderoption_t)Value;
 			break;
 
 		case HWD_SET_TEXTUREFILTERMODE:
@@ -2528,6 +2632,7 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 
 	boolean useVBO = true;
 
+	FBITFIELD flags;
 	int i;
 
 	// Because otherwise, scaling the screen negatively vertically breaks the lighting
@@ -2595,8 +2700,6 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 	else
 		pglColor4ubv((GLubyte*)&Surface->PolyColor.s);
 
-	SetBlend((poly.alpha < 1 ? PF_Translucent : (PF_Masked|PF_Occlude))|PF_Modulated);
-
 	tint.red   = byte2float[Surface->TintColor.s.red];
 	tint.green = byte2float[Surface->TintColor.s.green];
 	tint.blue  = byte2float[Surface->TintColor.s.blue];
@@ -2607,7 +2710,14 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 	fade.blue  = byte2float[Surface->FadeColor.s.blue];
 	fade.alpha = byte2float[Surface->FadeColor.s.alpha];
 
-	Shader_Load(Surface, &poly, &tint, &fade);
+	flags = (Surface->PolyFlags | PF_Modulated);
+	if (Surface->PolyFlags & (PF_Additive|PF_AdditiveSource|PF_Subtractive|PF_ReverseSubtract|PF_Multiplicative))
+		flags |= PF_Occlude;
+	else if (Surface->PolyColor.s.alpha == 0xFF)
+		flags |= (PF_Occlude | PF_Masked);
+
+	SetBlend(flags);
+	Shader_SetUniforms(Surface, &poly, &tint, &fade);
 
 	pglEnable(GL_CULL_FACE);
 	pglEnable(GL_NORMALIZE);
@@ -3176,7 +3286,7 @@ EXPORT void HWRAPI(DoScreenWipe)(void)
 
 	pglClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
 
-	SetBlend(PF_Modulated|PF_NoDepthTest|PF_Clip|PF_NoZClip);
+	SetBlend(PF_Modulated|PF_NoDepthTest);
 	pglEnable(GL_TEXTURE_2D);
 
 	// Draw the original screen
@@ -3186,7 +3296,7 @@ EXPORT void HWRAPI(DoScreenWipe)(void)
 	pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
 	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
-	SetBlend(PF_Modulated|PF_Translucent|PF_NoDepthTest|PF_Clip|PF_NoZClip);
+	SetBlend(PF_Modulated|PF_Translucent|PF_NoDepthTest);
 
 	// Draw the end screen that fades in
 	pglActiveTexture(GL_TEXTURE0);
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 72fb9272d441135456286f908da9ba7e2ff9dc98..604a509e0d447420ed835e4d22e2def744af78de 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -2120,7 +2120,7 @@ void HU_Drawer(void)
 		HU_DrawCrosshair2();
 
 	// draw desynch text
-	if (hu_resynching)
+	if (hu_redownloadinggamestate)
 	{
 		static UINT32 resynch_ticker = 0;
 		char resynch_text[14];
diff --git a/src/i_sound.h b/src/i_sound.h
index a2249a10273fa48c95650a75fa692b84d4de37d8..d45c0b323ef4ca34ea936e49b8e598471eb9d290 100644
--- a/src/i_sound.h
+++ b/src/i_sound.h
@@ -21,15 +21,12 @@
 // copied from SDL mixer, plus GME
 typedef enum {
 	MU_NONE,
-	MU_CMD,
 	MU_WAV,
 	MU_MOD,
 	MU_MID,
 	MU_OGG,
 	MU_MP3,
-	MU_MP3_MAD_UNUSED, // use MU_MP3 instead
 	MU_FLAC,
-	MU_MODPLUG_UNUSED, // use MU_MOD instead
 	MU_GME,
 	MU_MOD_EX, // libopenmpt
 	MU_MID_EX // Non-native MIDI
diff --git a/src/i_video.h b/src/i_video.h
index 98ed7f38a18822d8038ec0973ef3c680572c2d89..ab48881d4405036b515ff65988a81bab89e7236a 100644
--- a/src/i_video.h
+++ b/src/i_video.h
@@ -36,10 +36,9 @@ typedef enum
 */
 extern rendermode_t rendermode;
 
-/**	\brief OpenGL state
-	0 = never loaded, 1 = loaded successfully, -1 = failed loading
+/**	\brief render mode set by command line arguments
 */
-extern INT32 vid_opengl_state;
+extern rendermode_t chosenrendermode;
 
 /**	\brief use highcolor modes if true
 */
@@ -90,8 +89,9 @@ INT32 VID_GetModeForSize(INT32 w, INT32 h);
 INT32 VID_SetMode(INT32 modenum);
 
 /**	\brief Checks the render state
+	\return	true if the renderer changed
 */
-void VID_CheckRenderer(void);
+boolean VID_CheckRenderer(void);
 
 /**	\brief Load OpenGL mode
 */
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index 132ebc1a8bda0fcd370a81ef38f8597a7743df5f..4667fdbf4a7549ae226075ff8d5f09feeb9cc05f 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -32,6 +32,7 @@
 #include "lua_script.h"
 #include "lua_libs.h"
 #include "lua_hud.h" // hud_running errors
+#include "taglist.h" // P_FindSpecialLineFromTag
 #include "lua_hook.h" // hook_cmd_running errors
 
 #define NOHUD if (hud_running)\
@@ -246,6 +247,56 @@ static int lib_userdataType(lua_State *L)
 		return luaL_typerror(L, 1, "userdata");
 }
 
+// Takes a metatable as first and only argument
+// Only callable during script loading
+static int lib_registerMetatable(lua_State *L)
+{
+	static UINT16 nextid = 1;
+
+	if (!lua_lumploading)
+		return luaL_error(L, "This function cannot be called from within a hook or coroutine!");
+	luaL_checktype(L, 1, LUA_TTABLE);
+
+	if (nextid == 0)
+		return luaL_error(L, "Too many metatables registered?! Please consider rewriting your script once you are sober again.\n");
+
+	lua_getfield(L, LUA_REGISTRYINDEX, LREG_METATABLES); // 2
+		// registry.metatables[metatable] = nextid
+		lua_pushvalue(L, 1); // 3
+			lua_pushnumber(L, nextid); // 4
+		lua_settable(L, 2);
+
+		// registry.metatables[nextid] = metatable
+		lua_pushnumber(L, nextid); // 3
+			lua_pushvalue(L, 1); // 4
+		lua_settable(L, 2);
+	lua_pop(L, 1);
+
+	nextid++;
+
+	return 0;
+}
+
+// Takes a string as only argument and returns the metatable
+// associated to the userdata type this string refers to
+// Returns nil if the string does not refer to a valid userdata type
+static int lib_userdataMetatable(lua_State *L)
+{
+	UINT32 i;
+	const char *udname = luaL_checkstring(L, 1);
+
+	// Find internal metatable name
+	for (i = 0; meta2utype[i].meta; i++)
+		if (!strcmp(udname, meta2utype[i].utype))
+		{
+			luaL_getmetatable(L, meta2utype[i].meta);
+			return 1;
+		}
+
+	lua_pushnil(L);
+	return 1;
+}
+
 static int lib_isPlayerAdmin(lua_State *L)
 {
 	player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
@@ -915,6 +966,28 @@ static int lib_pMaceRotate(lua_State *L)
 	return 0;
 }
 
+static int lib_pCreateFloorSpriteSlope(lua_State *L)
+{
+	mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	NOHUD
+	INLEVEL
+	if (!mobj)
+		return LUA_ErrInvalid(L, "mobj_t");
+	LUA_PushUserdata(L, (pslope_t *)P_CreateFloorSpriteSlope(mobj), META_SLOPE);
+	return 1;
+}
+
+static int lib_pRemoveFloorSpriteSlope(lua_State *L)
+{
+	mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
+	NOHUD
+	INLEVEL
+	if (!mobj)
+		return LUA_ErrInvalid(L, "mobj_t");
+	P_RemoveFloorSpriteSlope(mobj);
+	return 1;
+}
+
 static int lib_pRailThinker(lua_State *L)
 {
 	mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
@@ -3006,6 +3079,185 @@ static int lib_sStartMusicCaption(lua_State *L)
 	return 0;
 }
 
+static int lib_sMusicType(lua_State *L)
+{
+	player_t *player = NULL;
+	NOHUD
+	if (!lua_isnone(L, 1) && lua_isuserdata(L, 1))
+	{
+		player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+	}
+	if (!player || P_IsLocalPlayer(player))
+		lua_pushinteger(L, S_MusicType());
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
+static int lib_sMusicPlaying(lua_State *L)
+{
+	player_t *player = NULL;
+	NOHUD
+	if (!lua_isnone(L, 1) && lua_isuserdata(L, 1))
+	{
+		player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+	}
+	if (!player || P_IsLocalPlayer(player))
+		lua_pushboolean(L, S_MusicPlaying());
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
+static int lib_sMusicPaused(lua_State *L)
+{
+	player_t *player = NULL;
+	NOHUD
+	if (!lua_isnone(L, 1) && lua_isuserdata(L, 1))
+	{
+		player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+	}
+	if (!player || P_IsLocalPlayer(player))
+		lua_pushboolean(L, S_MusicPaused());
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
+static int lib_sMusicName(lua_State *L)
+{
+	player_t *player = NULL;
+	NOHUD
+	if (!lua_isnone(L, 1) && lua_isuserdata(L, 1))
+	{
+		player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+	}
+	if (!player || P_IsLocalPlayer(player))
+		lua_pushstring(L, S_MusicName());
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
+static int lib_sMusicExists(lua_State *L)
+{
+	boolean checkMIDI = lua_opttrueboolean(L, 2);
+	boolean checkDigi = lua_opttrueboolean(L, 3);
+#ifdef MUSICSLOT_COMPATIBILITY
+	const char *music_name;
+	UINT32 music_num;
+	char music_compat_name[7];
+	UINT16 music_flags = 0;
+	NOHUD
+	if (lua_isnumber(L, 1))
+	{
+		music_num = (UINT32)luaL_checkinteger(L, 1);
+		music_flags = (UINT16)(music_num & 0x0000FFFF);
+		if (music_flags && music_flags <= 1035)
+			snprintf(music_compat_name, 7, "%sM", G_BuildMapName((INT32)music_flags));
+		else if (music_flags && music_flags <= 1050)
+			strncpy(music_compat_name, compat_special_music_slots[music_flags - 1036], 7);
+		else
+			music_compat_name[0] = 0; // becomes empty string
+		music_compat_name[6] = 0;
+		music_name = (const char *)&music_compat_name;
+	}
+	else
+	{
+		music_num = 0;
+		music_name = luaL_checkstring(L, 1);
+	}
+#else
+	const char *music_name = luaL_checkstring(L, 1);
+#endif
+	NOHUD
+	lua_pushboolean(L, S_MusicExists(music_name, checkMIDI, checkDigi));
+	return 1;
+}
+
+static int lib_sSetMusicLoopPoint(lua_State *L)
+{
+	UINT32 looppoint = (UINT32)luaL_checkinteger(L, 1);
+	player_t *player = NULL;
+	NOHUD
+	if (!lua_isnone(L, 2) && lua_isuserdata(L, 2))
+	{
+		player = *((player_t **)luaL_checkudata(L, 2, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+	}
+	if (!player || P_IsLocalPlayer(player))
+		lua_pushboolean(L, S_SetMusicLoopPoint(looppoint));
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
+static int lib_sGetMusicLoopPoint(lua_State *L)
+{
+	player_t *player = NULL;
+	NOHUD
+	if (!lua_isnone(L, 1) && lua_isuserdata(L, 1))
+	{
+		player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+	}
+	if (!player || P_IsLocalPlayer(player))
+		lua_pushinteger(L, (int)S_GetMusicLoopPoint());
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
+static int lib_sPauseMusic(lua_State *L)
+{
+	player_t *player = NULL;
+	NOHUD
+	if (!lua_isnone(L, 1) && lua_isuserdata(L, 1))
+	{
+		player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+	}
+	if (!player || P_IsLocalPlayer(player))
+	{
+		S_PauseAudio();
+		lua_pushboolean(L, true);
+	}
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
+static int lib_sResumeMusic(lua_State *L)
+{
+	player_t *player = NULL;
+	NOHUD
+	if (!lua_isnone(L, 1) && lua_isuserdata(L, 1))
+	{
+		player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
+		if (!player)
+			return LUA_ErrInvalid(L, "player_t");
+	}
+	if (!player || P_IsLocalPlayer(player))
+	{
+		S_ResumeAudio();
+		lua_pushboolean(L, true);
+	}
+	else
+		lua_pushnil(L);
+	return 1;
+}
+
 // G_GAME
 ////////////
 
@@ -3494,6 +3746,8 @@ static luaL_Reg lib[] = {
 	{"chatprint", lib_chatprint},
 	{"chatprintf", lib_chatprintf},
 	{"userdataType", lib_userdataType},
+	{"registerMetatable", lib_registerMetatable},
+	{"userdataMetatable", lib_userdataMetatable},
 	{"IsPlayerAdmin", lib_isPlayerAdmin},
 	{"reserveLuabanks", lib_reserveLuabanks},
 
@@ -3553,6 +3807,8 @@ static luaL_Reg lib[] = {
 	{"P_CheckSolidLava",lib_pCheckSolidLava},
 	{"P_CanRunOnWater",lib_pCanRunOnWater},
 	{"P_MaceRotate",lib_pMaceRotate},
+	{"P_CreateFloorSpriteSlope",lib_pCreateFloorSpriteSlope},
+	{"P_RemoveFloorSpriteSlope",lib_pRemoveFloorSpriteSlope},
 	{"P_RailThinker",lib_pRailThinker},
 	{"P_XYMovement",lib_pXYMovement},
 	{"P_RingXYMovement",lib_pRingXYMovement},
@@ -3712,6 +3968,15 @@ static luaL_Reg lib[] = {
 	{"S_IdPlaying",lib_sIdPlaying},
 	{"S_SoundPlaying",lib_sSoundPlaying},
 	{"S_StartMusicCaption", lib_sStartMusicCaption},
+	{"S_MusicType",lib_sMusicType},
+	{"S_MusicPlaying",lib_sMusicPlaying},
+	{"S_MusicPaused",lib_sMusicPaused},
+	{"S_MusicName",lib_sMusicName},
+	{"S_MusicExists",lib_sMusicExists},
+	{"S_SetMusicLoopPoint",lib_sSetMusicLoopPoint},
+	{"S_GetMusicLoopPoint",lib_sGetMusicLoopPoint},
+	{"S_PauseMusic",lib_sPauseMusic},
+	{"S_ResumeMusic", lib_sResumeMusic},
 
 	// g_game
 	{"G_AddGametype", lib_gAddGametype},
diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c
index c6b082930385d3edd4762d846532028be1c9ade1..32d64b5b5333729b5dc4cb6220f93c20393815e8 100644
--- a/src/lua_consolelib.c
+++ b/src/lua_consolelib.c
@@ -440,6 +440,9 @@ static int CVarSetFunction
 ){
 	consvar_t *cvar = (consvar_t *)luaL_checkudata(L, 1, META_CVAR);
 
+	if (cvar->flags & CV_NOLUA)
+		return luaL_error(L, "Variable %s cannot be set from Lua.", cvar->name);
+
 	switch (lua_type(L, 2))
 	{
 		case LUA_TSTRING:
@@ -468,7 +471,12 @@ static int lib_cvStealthSet(lua_State *L)
 static int lib_cvAddValue(lua_State *L)
 {
 	consvar_t *cvar = (consvar_t *)luaL_checkudata(L, 1, META_CVAR);
+
+	if (cvar->flags & CV_NOLUA)
+		return luaL_error(L, "Variable %s cannot be set from Lua.", cvar->name);
+
 	CV_AddValue(cvar, (INT32)luaL_checknumber(L, 2));
+
 	return 0;
 }
 
diff --git a/src/lua_hook.h b/src/lua_hook.h
index 47850812f059c5825e3f53428c4bbecda4e7a5ef..796f3a9d287dcf0e3997d6963949b33111dceb0d 100644
--- a/src/lua_hook.h
+++ b/src/lua_hook.h
@@ -60,6 +60,7 @@ enum hook {
 	hook_ShouldJingleContinue,
 	hook_GameQuit,
 	hook_PlayerCmd,
+	hook_MusicChange,
 
 	hook_MAX // last hook
 };
@@ -118,3 +119,4 @@ boolean LUAh_SeenPlayer(player_t *player, player_t *seenfriend); // Hook for MT_
 boolean LUAh_ShouldJingleContinue(player_t *player, const char *musname); // Hook for whether a jingle of the given music should continue playing
 void LUAh_GameQuit(void); // Hook for game quitting
 boolean LUAh_PlayerCmd(player_t *player, ticcmd_t *cmd); // Hook for building player's ticcmd struct (Ported from SRB2Kart)
+boolean LUAh_MusicChange(const char *oldname, char *newname, UINT16 *mflags, boolean *looping, UINT32 *position, UINT32 *prefadems, UINT32 *fadeinms); // Hook for music changes
\ No newline at end of file
diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c
index 65d483dc1a9f1518fab22a70adde2bc78aa20ad3..117aa48a303e7ba7945f2b970cb20d0292cced2a 100644
--- a/src/lua_hooklib.c
+++ b/src/lua_hooklib.c
@@ -76,6 +76,7 @@ const char *const hookNames[hook_MAX+1] = {
 	"ShouldJingleContinue",
 	"GameQuit",
 	"PlayerCmd",
+	"MusicChange",
 	NULL
 };
 
@@ -1912,3 +1913,62 @@ boolean LUAh_PlayerCmd(player_t *player, ticcmd_t *cmd)
 	hook_cmd_running = false;
 	return hooked;
 }
+
+// Hook for music changes
+boolean LUAh_MusicChange(const char *oldname, char *newname, UINT16 *mflags, boolean *looping,
+	UINT32 *position, UINT32 *prefadems, UINT32 *fadeinms)
+{
+	hook_p hookp;
+	boolean hooked = false;
+
+	if (!gL || !(hooksAvailable[hook_MusicChange/8] & (1<<(hook_MusicChange%8))))
+		return false;
+
+	lua_settop(gL, 0);
+	lua_pushcfunction(gL, LUA_GetErrorMessage);
+
+	for (hookp = roothook; hookp; hookp = hookp->next)
+		if (hookp->type == hook_MusicChange)
+		{
+			PushHook(gL, hookp);
+			lua_pushstring(gL, oldname);
+			lua_pushstring(gL, newname);
+			lua_pushinteger(gL, *mflags);
+			lua_pushboolean(gL, *looping);
+			lua_pushinteger(gL, *position);
+			lua_pushinteger(gL, *prefadems);
+			lua_pushinteger(gL, *fadeinms);
+			if (lua_pcall(gL, 7, 6, 1)) {
+				CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
+				lua_pop(gL, 1);
+				continue;
+			}
+
+			// output 1: true, false, or string musicname override
+			if (lua_isboolean(gL, -6) && lua_toboolean(gL, -6))
+				hooked = true;
+			else if (lua_isstring(gL, -6))
+				strncpy(newname, lua_tostring(gL, -6), 7);
+			// output 2: mflags override
+			if (lua_isnumber(gL, -5))
+				*mflags = lua_tonumber(gL, -5);
+			// output 3: looping override
+			if (lua_isboolean(gL, -4))
+				*looping = lua_toboolean(gL, -4);
+			// output 4: position override
+			if (lua_isboolean(gL, -3))
+				*position = lua_tonumber(gL, -3);
+			// output 5: prefadems override
+			if (lua_isboolean(gL, -2))
+				*prefadems = lua_tonumber(gL, -2);
+			// output 6: fadeinms override
+			if (lua_isboolean(gL, -1))
+				*fadeinms = lua_tonumber(gL, -1);
+
+			lua_pop(gL, 7);  // Pop returned values and error handler
+		}
+
+	lua_settop(gL, 0);
+	newname[6] = 0;
+	return hooked;
+}
\ No newline at end of file
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 6b87dc93034060d5af15ff1d7a5687cb3fcb8b12..7b290bf3ff3a87d8e9d80f14c9c1c84c29a91e15 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -35,11 +35,6 @@ static UINT8 hud_enabled[(hud_MAX/8)+1];
 
 static UINT8 hudAvailable; // hud hooks field
 
-#ifdef LUA_PATCH_SAFETY
-static patchinfo_t *patchinfo, *patchinfohead;
-static int numluapatches;
-#endif
-
 // must match enum hud in lua_hud.h
 static const char *const hud_disable_options[] = {
 	"stagetitle",
@@ -292,11 +287,7 @@ static int colormap_get(lua_State *L)
 
 static int patch_get(lua_State *L)
 {
-#ifdef LUA_PATCH_SAFETY
 	patch_t *patch = *((patch_t **)luaL_checkudata(L, 1, META_PATCH));
-#else
-	patchinfo_t *patch = *((patchinfo_t **)luaL_checkudata(L, 1, META_PATCH));
-#endif
 	enum patch field = luaL_checkoption(L, 2, NULL, patch_opt);
 
 	// patches are invalidated when switching renderers
@@ -403,59 +394,8 @@ static int libd_patchExists(lua_State *L)
 
 static int libd_cachePatch(lua_State *L)
 {
-#ifdef LUA_PATCH_SAFETY
-	int i;
-	lumpnum_t lumpnum;
-	patchinfo_t *luapat;
-	patch_t *realpatch;
-
-	HUDONLY
-
-	luapat = patchinfohead;
-	lumpnum = W_CheckNumForLongName(luaL_checkstring(L, 1));
-	if (lumpnum == LUMPERROR)
-		lumpnum = W_GetNumForLongName("MISSING");
-
-	for (i = 0; i < numluapatches; i++)
-	{
-		// check if already cached
-		if (luapat->wadnum == WADFILENUM(lumpnum) && luapat->lumpnum == LUMPNUM(lumpnum))
-		{
-			LUA_PushUserdata(L, luapat, META_PATCH);
-			return 1;
-		}
-		luapat = luapat->next;
-		if (!luapat)
-			break;
-	}
-
-	if (numluapatches > 0)
-	{
-		patchinfo->next = Z_Malloc(sizeof(patchinfo_t), PU_STATIC, NULL);
-		patchinfo = patchinfo->next;
-	}
-	else
-	{
-		patchinfo = Z_Malloc(sizeof(patchinfo_t), PU_STATIC, NULL);
-		patchinfohead = patchinfo;
-	}
-
-	realpatch = W_CachePatchNum(lumpnum, PU_PATCH);
-
-	patchinfo->width = realpatch->width;
-	patchinfo->height = realpatch->height;
-	patchinfo->leftoffset = realpatch->leftoffset;
-	patchinfo->topoffset = realpatch->topoffset;
-
-	patchinfo->wadnum = WADFILENUM(lumpnum);
-	patchinfo->lumpnum = LUMPNUM(lumpnum);
-
-	LUA_PushUserdata(L, patchinfo, META_PATCH);
-	numluapatches++;
-#else
 	HUDONLY
 	LUA_PushUserdata(L, W_CachePatchLongName(luaL_checkstring(L, 1), PU_PATCH), META_PATCH);
-#endif
 	return 1;
 }
 
@@ -518,9 +458,8 @@ static int libd_getSpritePatch(lua_State *L)
 		INT32 rot = R_GetRollAngle(rollangle);
 
 		if (rot) {
-			if (!(sprframe->rotsprite.cached & (1<<angle)))
-				R_CacheRotSprite(i, frame, NULL, sprframe, angle, sprframe->flip & (1<<angle));
-			LUA_PushUserdata(L, sprframe->rotsprite.patch[angle][rot], META_PATCH);
+			patch_t *rotsprite = Patch_GetRotatedSprite(sprframe, frame, angle, sprframe->flip & (1<<angle), true, &spriteinfo[i], rot);
+			LUA_PushUserdata(L, rotsprite, META_PATCH);
 			lua_pushboolean(L, false);
 			lua_pushboolean(L, true);
 			return 3;
@@ -529,7 +468,7 @@ static int libd_getSpritePatch(lua_State *L)
 #endif
 
 	// push both the patch and it's "flip" value
-	LUA_PushUserdata(L, W_CachePatchNum(sprframe->lumppat[angle], PU_PATCH), META_PATCH);
+	LUA_PushUserdata(L, W_CachePatchNum(sprframe->lumppat[angle], PU_SPRITE), META_PATCH);
 	lua_pushboolean(L, (sprframe->flip & (1<<angle)) != 0);
 	return 2;
 }
@@ -631,9 +570,8 @@ static int libd_getSprite2Patch(lua_State *L)
 		INT32 rot = R_GetRollAngle(rollangle);
 
 		if (rot) {
-			if (!(sprframe->rotsprite.cached & (1<<angle)))
-				R_CacheRotSprite(SPR_PLAY, frame, &skins[i].sprinfo[j], sprframe, angle, sprframe->flip & (1<<angle));
-			LUA_PushUserdata(L, sprframe->rotsprite.patch[angle][rot], META_PATCH);
+			patch_t *rotsprite = Patch_GetRotatedSprite(sprframe, frame, angle, sprframe->flip & (1<<angle), true, &skins[i].sprinfo[j], rot);
+			LUA_PushUserdata(L, rotsprite, META_PATCH);
 			lua_pushboolean(L, false);
 			lua_pushboolean(L, true);
 			return 3;
@@ -642,7 +580,7 @@ static int libd_getSprite2Patch(lua_State *L)
 #endif
 
 	// push both the patch and it's "flip" value
-	LUA_PushUserdata(L, W_CachePatchNum(sprframe->lumppat[angle], PU_PATCH), META_PATCH);
+	LUA_PushUserdata(L, W_CachePatchNum(sprframe->lumppat[angle], PU_SPRITE), META_PATCH);
 	lua_pushboolean(L, (sprframe->flip & (1<<angle)) != 0);
 	return 2;
 }
@@ -651,22 +589,14 @@ static int libd_draw(lua_State *L)
 {
 	INT32 x, y, flags;
 	patch_t *patch;
-#ifdef LUA_PATCH_SAFETY
-	patchinfo_t *luapat;
-#endif
 	const UINT8 *colormap = NULL;
 
 	HUDONLY
 	x = luaL_checkinteger(L, 1);
 	y = luaL_checkinteger(L, 2);
-#ifdef LUA_PATCH_SAFETY
-	luapat = *((patchinfo_t **)luaL_checkudata(L, 3, META_PATCH));
-	patch = W_CachePatchNum((luapat->wadnum<<16)+luapat->lumpnum, PU_PATCH);
-#else
 	patch = *((patch_t **)luaL_checkudata(L, 3, META_PATCH));
 	if (!patch)
 		return LUA_ErrInvalid(L, "patch_t");
-#endif
 	flags = luaL_optinteger(L, 4, 0);
 	if (!lua_isnoneornil(L, 5))
 		colormap = *((UINT8 **)luaL_checkudata(L, 5, META_COLORMAP));
@@ -682,9 +612,6 @@ static int libd_drawScaled(lua_State *L)
 	fixed_t x, y, scale;
 	INT32 flags;
 	patch_t *patch;
-#ifdef LUA_PATCH_SAFETY
-	patchinfo_t *luapat;
-#endif
 	const UINT8 *colormap = NULL;
 
 	HUDONLY
@@ -693,14 +620,9 @@ static int libd_drawScaled(lua_State *L)
 	scale = luaL_checkinteger(L, 3);
 	if (scale < 0)
 		return luaL_error(L, "negative scale");
-#ifdef LUA_PATCH_SAFETY
-	luapat = *((patchinfo_t **)luaL_checkudata(L, 4, META_PATCH));
-	patch = W_CachePatchNum((luapat->wadnum<<16)+luapat->lumpnum, PU_PATCH);
-#else
 	patch = *((patch_t **)luaL_checkudata(L, 4, META_PATCH));
 	if (!patch)
 		return LUA_ErrInvalid(L, "patch_t");
-#endif
 	flags = luaL_optinteger(L, 5, 0);
 	if (!lua_isnoneornil(L, 6))
 		colormap = *((UINT8 **)luaL_checkudata(L, 6, META_COLORMAP));
@@ -1261,10 +1183,6 @@ int LUA_HudLib(lua_State *L)
 {
 	memset(hud_enabled, 0xff, (hud_MAX/8)+1);
 
-#ifdef LUA_PATCH_SAFETY
-	numluapatches = 0;
-#endif
-
 	lua_newtable(L); // HUD registry table
 		lua_newtable(L);
 		luaL_register(L, NULL, lib_draw);
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 5e5a1dbc4029ba27f338c525340570e2b9141c7a..18e51995117717fd5ca47a0182876ecdea7eac1f 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -17,6 +17,7 @@
 #include "p_mobj.h"
 #include "p_local.h"
 #include "z_zone.h"
+#include "r_patch.h"
 #include "r_picformats.h"
 #include "r_things.h"
 #include "r_draw.h" // R_GetColorByName
@@ -382,10 +383,6 @@ static int lib_setSpriteInfo(lua_State *L)
 		UINT32 i = luaL_checkinteger(L, 1);
 		if (i == 0 || i >= NUMSPRITES)
 			return luaL_error(L, "spriteinfo[] index %d out of range (1 - %d)", i, NUMSPRITES-1);
-#ifdef ROTSPRITE
-		if (sprites != NULL)
-			R_FreeSingleRotSprite(&sprites[i]);
-#endif
 		info = &spriteinfo[i]; // get the spriteinfo to assign to.
 	}
 	luaL_checktype(L, 2, LUA_TTABLE); // check that we've been passed a table.
@@ -469,11 +466,6 @@ static int spriteinfo_set(lua_State *L)
 	lua_remove(L, 1); // remove field
 	lua_settop(L, 1); // leave only one value
 
-#ifdef ROTSPRITE
-	if (sprites != NULL)
-		R_FreeSingleRotSprite(&sprites[sprinfo-spriteinfo]);
-#endif
-
 	if (fastcmp(field, "pivot"))
 	{
 		// pivot[] is a table
diff --git a/src/lua_libs.h b/src/lua_libs.h
index 03bd99cd29a53f531ed5c6412e101ede97a79893..062a3fe5009fb2beda2c2a5545243ab350a2cdf5 100644
--- a/src/lua_libs.h
+++ b/src/lua_libs.h
@@ -16,6 +16,7 @@ extern lua_State *gL;
 #define LREG_EXTVARS "LUA_VARS"
 #define LREG_STATEACTION "STATE_ACTION"
 #define LREG_ACTIONS "MOBJ_ACTION"
+#define LREG_METATABLES "METATABLES"
 
 #define META_STATE "STATE_T*"
 #define META_MOBJINFO "MOBJINFO_T*"
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 5f6dbc4d6480c0d92e942ded4d01f0524207ae70..95cc8c1019e88de52213a0fac5ebff9d42ffa8d0 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -99,8 +99,6 @@ enum line_e {
 	line_slopetype,
 	line_frontsector,
 	line_backsector,
-	line_firsttag,
-	line_nexttag,
 	line_polyobj,
 	line_text,
 	line_callcount
@@ -125,8 +123,6 @@ static const char *const line_opt[] = {
 	"slopetype",
 	"frontsector",
 	"backsector",
-	"firsttag",
-	"nexttag",
 	"polyobj",
 	"text",
 	"callcount",
@@ -583,7 +579,7 @@ static int sector_get(lua_State *L)
 		lua_pushinteger(L, sector->special);
 		return 1;
 	case sector_tag:
-		lua_pushinteger(L, sector->tag);
+		lua_pushinteger(L, Tag_FGet(&sector->tags));
 		return 1;
 	case sector_thinglist: // thinglist
 		lua_pushcfunction(L, lib_iterateSectorThinglist);
@@ -684,7 +680,7 @@ static int sector_set(lua_State *L)
 		sector->special = (INT16)luaL_checkinteger(L, 3);
 		break;
 	case sector_tag:
-		P_ChangeSectorTag((UINT32)(sector - sectors), (INT16)luaL_checkinteger(L, 3));
+		Tag_SectorFSet((UINT32)(sector - sectors), (INT16)luaL_checkinteger(L, 3));
 		break;
 	}
 	return 0;
@@ -823,7 +819,7 @@ static int line_get(lua_State *L)
 		lua_pushinteger(L, line->special);
 		return 1;
 	case line_tag:
-		lua_pushinteger(L, line->tag);
+		lua_pushinteger(L, Tag_FGet(&line->tags));
 		return 1;
 	case line_args:
 		LUA_PushUserdata(L, line->args, META_LINEARGS);
@@ -871,12 +867,6 @@ static int line_get(lua_State *L)
 	case line_backsector:
 		LUA_PushUserdata(L, line->backsector, META_SECTOR);
 		return 1;
-	case line_firsttag:
-		lua_pushinteger(L, line->firsttag);
-		return 1;
-	case line_nexttag:
-		lua_pushinteger(L, line->nexttag);
-		return 1;
 	case line_polyobj:
 		LUA_PushUserdata(L, line->polyobj, META_POLYOBJ);
 		return 1;
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index a1a3e96c93afd9d036ad6f8120e6a1c0bf82b9ac..7aae18c90a31f43dd43fefc6e33c6085003d20af 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -39,6 +39,11 @@ enum mobj_e {
 	mobj_frame,
 	mobj_sprite2,
 	mobj_anim_duration,
+	mobj_spritexscale,
+	mobj_spriteyscale,
+	mobj_spritexoffset,
+	mobj_spriteyoffset,
+	mobj_floorspriteslope,
 	mobj_touching_sectorlist,
 	mobj_subsector,
 	mobj_floorz,
@@ -56,8 +61,10 @@ enum mobj_e {
 	mobj_flags,
 	mobj_flags2,
 	mobj_eflags,
+	mobj_renderflags,
 	mobj_skin,
 	mobj_color,
+	mobj_blendmode,
 	mobj_bnext,
 	mobj_bprev,
 	mobj_hnext,
@@ -108,6 +115,11 @@ static const char *const mobj_opt[] = {
 	"frame",
 	"sprite2",
 	"anim_duration",
+	"spritexscale",
+	"spriteyscale",
+	"spritexoffset",
+	"spriteyoffset",
+	"floorspriteslope",
 	"touching_sectorlist",
 	"subsector",
 	"floorz",
@@ -125,8 +137,10 @@ static const char *const mobj_opt[] = {
 	"flags",
 	"flags2",
 	"eflags",
+	"renderflags",
 	"skin",
 	"color",
+	"blendmode",
 	"bnext",
 	"bprev",
 	"hnext",
@@ -227,6 +241,21 @@ static int mobj_get(lua_State *L)
 	case mobj_anim_duration:
 		lua_pushinteger(L, mo->anim_duration);
 		break;
+	case mobj_spritexscale:
+		lua_pushfixed(L, mo->spritexscale);
+		break;
+	case mobj_spriteyscale:
+		lua_pushfixed(L, mo->spriteyscale);
+		break;
+	case mobj_spritexoffset:
+		lua_pushfixed(L, mo->spritexoffset);
+		break;
+	case mobj_spriteyoffset:
+		lua_pushfixed(L, mo->spriteyoffset);
+		break;
+	case mobj_floorspriteslope:
+		LUA_PushUserdata(L, mo->floorspriteslope, META_SLOPE);
+		break;
 	case mobj_touching_sectorlist:
 		return UNIMPLEMENTED;
 	case mobj_subsector:
@@ -277,6 +306,9 @@ static int mobj_get(lua_State *L)
 	case mobj_eflags:
 		lua_pushinteger(L, mo->eflags);
 		break;
+	case mobj_renderflags:
+		lua_pushinteger(L, mo->renderflags);
+		break;
 	case mobj_skin: // skin name or nil, not struct
 		if (!mo->skin)
 			return 0;
@@ -285,6 +317,9 @@ static int mobj_get(lua_State *L)
 	case mobj_color:
 		lua_pushinteger(L, mo->color);
 		break;
+	case mobj_blendmode:
+		lua_pushinteger(L, mo->blendmode);
+		break;
 	case mobj_bnext:
 		LUA_PushUserdata(L, mo->bnext, META_MOBJ);
 		break;
@@ -492,6 +527,20 @@ static int mobj_set(lua_State *L)
 	case mobj_anim_duration:
 		mo->anim_duration = (UINT16)luaL_checkinteger(L, 3);
 		break;
+	case mobj_spritexscale:
+		mo->spritexscale = luaL_checkfixed(L, 3);
+		break;
+	case mobj_spriteyscale:
+		mo->spriteyscale = luaL_checkfixed(L, 3);
+		break;
+	case mobj_spritexoffset:
+		mo->spritexoffset = luaL_checkfixed(L, 3);
+		break;
+	case mobj_spriteyoffset:
+		mo->spriteyoffset = luaL_checkfixed(L, 3);
+		break;
+	case mobj_floorspriteslope:
+		return NOSET;
 	case mobj_touching_sectorlist:
 		return UNIMPLEMENTED;
 	case mobj_subsector:
@@ -580,6 +629,9 @@ static int mobj_set(lua_State *L)
 	case mobj_eflags:
 		mo->eflags = (UINT32)luaL_checkinteger(L, 3);
 		break;
+	case mobj_renderflags:
+		mo->renderflags = (UINT32)luaL_checkinteger(L, 3);
+		break;
 	case mobj_skin: // set skin by name
 	{
 		INT32 i;
@@ -603,6 +655,9 @@ static int mobj_set(lua_State *L)
 		mo->color = newcolor;
 		break;
 	}
+	case mobj_blendmode:
+		mo->blendmode = (INT32)luaL_checkinteger(L, 3);
+		break;
 	case mobj_bnext:
 		return NOSETPOS;
 	case mobj_bprev:
@@ -848,7 +903,7 @@ static int mapthing_get(lua_State *L)
 	else if(fastcmp(field,"extrainfo"))
 		number = mt->extrainfo;
 	else if(fastcmp(field,"tag"))
-		number = mt->tag;
+		number = Tag_FGet(&mt->tags);
 	else if(fastcmp(field,"args"))
 	{
 		LUA_PushUserdata(L, mt->args, META_THINGARGS);
@@ -910,7 +965,7 @@ static int mapthing_set(lua_State *L)
 		mt->extrainfo = (UINT8)extrainfo;
 	}
 	else if (fastcmp(field,"tag"))
-		mt->tag = (INT16)luaL_checkinteger(L, 3);
+		Tag_FSet(&mt->tags, (INT16)luaL_checkinteger(L, 3));
 	else if(fastcmp(field,"mobj"))
 		mt->mobj = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ));
 	else
diff --git a/src/lua_script.c b/src/lua_script.c
index 6e40cb785444d15b39f7aacdd571e6d1cb393ab4..bb022f9ce1b4d52c92b0ebbb11dbf5469ce0ce53 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -443,6 +443,10 @@ static void LUA_ClearState(void)
 	lua_newtable(L);
 	lua_setfield(L, LUA_REGISTRYINDEX, LREG_VALID);
 
+	// make LREG_METATABLES table for all registered metatables
+	lua_newtable(L);
+	lua_setfield(L, LUA_REGISTRYINDEX, LREG_METATABLES);
+
 	// open srb2 libraries
 	for(i = 0; liblist[i]; i++) {
 		lua_pushcfunction(L, liblist[i]);
@@ -980,8 +984,17 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
 			lua_pop(gL, 1);
 		}
 		if (!found)
+		{
 			t++;
 
+			if (t == 0)
+			{
+				CONS_Alert(CONS_ERROR, "Too many tables to archive!\n");
+				WRITEUINT8(save_p, ARCH_NULL);
+				return 0;
+			}
+		}
+
 		WRITEUINT8(save_p, ARCH_TABLE);
 		WRITEUINT16(save_p, t);
 
@@ -1294,8 +1307,22 @@ static void ArchiveTables(void)
 
 			lua_pop(gL, 1);
 		}
-		lua_pop(gL, 1);
 		WRITEUINT8(save_p, ARCH_TEND);
+
+		// Write metatable ID
+		if (lua_getmetatable(gL, -1))
+		{
+			// registry.metatables[metatable]
+			lua_getfield(gL, LUA_REGISTRYINDEX, LREG_METATABLES);
+			lua_pushvalue(gL, -2);
+			lua_gettable(gL, -2);
+			WRITEUINT16(save_p, lua_isnil(gL, -1) ? 0 : lua_tointeger(gL, -1));
+			lua_pop(gL, 3);
+		}
+		else
+			WRITEUINT16(save_p, 0);
+
+		lua_pop(gL, 1);
 	}
 }
 
@@ -1466,6 +1493,7 @@ static void UnArchiveTables(void)
 {
 	int TABLESINDEX;
 	UINT16 i, n;
+	UINT16 metatableid;
 
 	if (!gL)
 		return;
@@ -1490,6 +1518,19 @@ static void UnArchiveTables(void)
 			else
 				lua_rawset(gL, -3);
 		}
+
+		metatableid = READUINT16(save_p);
+		if (metatableid)
+		{
+			// setmetatable(table, registry.metatables[metatableid])
+			lua_getfield(gL, LUA_REGISTRYINDEX, LREG_METATABLES);
+				lua_rawgeti(gL, -1, metatableid);
+				if (lua_isnil(gL, -1))
+					I_Error("Unknown metatable ID %d\n", metatableid);
+				lua_setmetatable(gL, -3);
+			lua_pop(gL, 1);
+		}
+
 		lua_pop(gL, 1);
 	}
 }
diff --git a/src/m_anigif.c b/src/m_anigif.c
index 85118790bf6f72b28a81069d3a59117a5bb57cbf..dbc8d3422366f4c8cf255cde5e66ea55cb9cbddf 100644
--- a/src/m_anigif.c
+++ b/src/m_anigif.c
@@ -29,15 +29,21 @@
 // GIFs are always little-endian
 #include "byteptr.h"
 
+CV_PossibleValue_t gif_dynamicdelay_cons_t[] = {
+	{0, "Off"},
+	{1, "On"},
+	{2, "Accurate, experimental"},
+{0, NULL}};
+
 consvar_t cv_gif_optimize = CVAR_INIT ("gif_optimize", "On", CV_SAVE, CV_OnOff, NULL);
 consvar_t cv_gif_downscale =  CVAR_INIT ("gif_downscale", "On", CV_SAVE, CV_OnOff, NULL);
-consvar_t cv_gif_dynamicdelay = CVAR_INIT ("gif_dynamicdelay", "On", CV_SAVE, CV_OnOff, NULL);
+consvar_t cv_gif_dynamicdelay = CVAR_INIT ("gif_dynamicdelay", "On", CV_SAVE, gif_dynamicdelay_cons_t, NULL);
 consvar_t cv_gif_localcolortable =  CVAR_INIT ("gif_localcolortable", "On", CV_SAVE, CV_OnOff, NULL);
 
 #ifdef HAVE_ANIGIF
 static boolean gif_optimize = false; // So nobody can do something dumb
 static boolean gif_downscale = false; // like changing cvars mid output
-static boolean gif_dynamicdelay = false; // and messing something up
+static UINT8 gif_dynamicdelay = (UINT8)0; // and messing something up
 
 // Palette handling
 static boolean gif_localcolortable = false;
@@ -47,7 +53,8 @@ static RGBA_t *gif_framepalette = NULL;
 
 static FILE *gif_out = NULL;
 static INT32 gif_frames = 0;
-static UINT32 gif_prevframems = 0;
+static UINT32 gif_prevframeus = 0; // "us" is microseconds
+static UINT32 gif_delayus = 0;
 static UINT8 gif_writeover = 0;
 
 
@@ -594,16 +601,30 @@ static void GIF_framewrite(void)
 
 	// screen regions are handled in GIF_lzw
 	{
-		UINT16 delay;
+		UINT16 delay = 0;
 		INT32 startline;
 
-		if (gif_dynamicdelay) {
+		if (gif_dynamicdelay ==(UINT8) 2)
+		{
 			// golden's attempt at creating a "dynamic delay"
+			UINT16 mingifdelay = 10; // minimum gif delay in milliseconds (keep at 10 because gifs can't get more precise).
+			gif_delayus += (I_GetTimeMicros() - gif_prevframeus); // increase delay by how much time was spent between last measurement
+
+			if (gif_delayus/1000 >= mingifdelay) // delay is big enough to be able to effect gif frame delay?
+			{
+				int frames = (gif_delayus/1000) / mingifdelay; // get amount of frames to delay.
+				delay = frames; // set the delay to delay that amount of frames.
+				gif_delayus -= frames*(mingifdelay*1000); // remove frames by the amount of milliseconds they take. don't reset to 0, the microseconds help consistency.
+			}
+		}
+		else if (gif_dynamicdelay ==(UINT8) 1)
+		{
 			float delayf = ceil(100.0f/NEWTICRATE);
 
-			delay = (UINT16)((I_GetTimeMicros() - gif_prevframems)/10/1000);
-			if (delay < (int)(delayf))
-				delay = (int)(delayf);
+			delay = (UINT16)((I_GetTimeMicros() - gif_prevframeus)/10/1000);
+
+			if (delay < (UINT16)(delayf))
+				delay = (UINT16)(delayf);
 		}
 		else
 		{
@@ -690,7 +711,7 @@ static void GIF_framewrite(void)
 	}
 	fwrite(gifframe_data, 1, (p - gifframe_data), gif_out);
 	++gif_frames;
-	gif_prevframems = I_GetTimeMicros();
+	gif_prevframeus = I_GetTimeMicros();
 }
 
 
@@ -711,14 +732,15 @@ INT32 GIF_open(const char *filename)
 
 	gif_optimize = (!!cv_gif_optimize.value);
 	gif_downscale = (!!cv_gif_downscale.value);
-	gif_dynamicdelay = (!!cv_gif_dynamicdelay.value);
+	gif_dynamicdelay = (UINT8)cv_gif_dynamicdelay.value;
 	gif_localcolortable = (!!cv_gif_localcolortable.value);
 	gif_colorprofile = (!!cv_screenshot_colorprofile.value);
 	gif_headerpalette = GIF_getpalette(0);
 
 	GIF_headwrite();
 	gif_frames = 0;
-	gif_prevframems = I_GetTimeMicros();
+	gif_prevframeus = I_GetTimeMicros();
+	gif_delayus = 0;
 	return 1;
 }
 
diff --git a/src/m_cheat.c b/src/m_cheat.c
index 8e9cd9f51d9020e63b47f7f2ac66274c22cc9d13..6e0fb8c5c0784f66be508981aa33d75f9a661d89 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -1108,7 +1108,6 @@ static mapthing_t *OP_CreateNewMapThing(player_t *player, UINT16 type, boolean c
 
 	mt->options = (mt->z << ZSHIFT) | (UINT16)cv_opflags.value;
 	mt->scale = player->mo->scale;
-	mt->tag = 0;
 	memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args));
 	memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs));
 	mt->pitch = mt->roll = 0;
@@ -1435,7 +1434,7 @@ void Command_Writethings_f(void)
 	REQUIRE_SINGLEPLAYER;
 	REQUIRE_OBJECTPLACE;
 
-	P_WriteThings(W_GetNumForName(G_BuildMapName(gamemap)) + ML_THINGS);
+	P_WriteThings();
 }
 
 void Command_ObjectPlace_f(void)
diff --git a/src/m_menu.c b/src/m_menu.c
index 5860f00ca1a1f4dc5399982f06ccebc21c3d469b..d31e2154ce099671f1c9abcf740fb8ff42219454 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1483,7 +1483,7 @@ static menuitem_t OP_SoundOptionsMenu[] =
 
 	{IT_STRING | IT_CVAR,  NULL,  "MIDI Music", &cv_gamemidimusic, 36},
 	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "MIDI Music Volume", &cv_midimusicvolume, 41},
-	
+
 	{IT_STRING | IT_CVAR,  NULL,  "Music Preference", &cv_musicpref, 51},
 
 	{IT_HEADER, NULL, "Miscellaneous", NULL, 61},
@@ -2145,15 +2145,20 @@ menu_t OP_PlaystyleDef = {
 static void M_VideoOptions(INT32 choice)
 {
 	(void)choice;
+
+	OP_VideoOptionsMenu[op_video_renderer].status = (IT_TRANSTEXT | IT_PAIR);
+	OP_VideoOptionsMenu[op_video_renderer].patch = "Renderer";
+	OP_VideoOptionsMenu[op_video_renderer].text = "Software";
+
 #ifdef HWRENDER
-	if (vid_opengl_state == -1)
+	if (vid.glstate != VID_GL_LIBRARY_ERROR)
 	{
-		OP_VideoOptionsMenu[op_video_renderer].status = (IT_TRANSTEXT | IT_PAIR);
-		OP_VideoOptionsMenu[op_video_renderer].patch = "Renderer";
-		OP_VideoOptionsMenu[op_video_renderer].text = "Software";
+		OP_VideoOptionsMenu[op_video_renderer].status = (IT_STRING | IT_CVAR);
+		OP_VideoOptionsMenu[op_video_renderer].patch = NULL;
+		OP_VideoOptionsMenu[op_video_renderer].text = "Renderer";
 	}
-
 #endif
+
 	M_SetupNextMenu(&OP_VideoOptionsDef);
 }
 
@@ -5585,9 +5590,6 @@ static void M_DrawLevelPlatterWideMap(UINT8 row, UINT8 col, INT32 x, INT32 y, bo
 	if (map <= 0)
 		return;
 
-	if (needpatchrecache)
-		M_CacheLevelPlatter();
-
 	//  A 564x100 image of the level as entry MAPxxW
 	if (!(levelselect.rows[row].mapavailable[col]))
 	{
@@ -5619,9 +5621,6 @@ static void M_DrawLevelPlatterMap(UINT8 row, UINT8 col, INT32 x, INT32 y, boolea
 	if (map <= 0)
 		return;
 
-	if (needpatchrecache)
-		M_CacheLevelPlatter();
-
 	//  A 160x100 image of the level as entry MAPxxP
 	if (!(levelselect.rows[row].mapavailable[col]))
 	{
@@ -5831,8 +5830,6 @@ static void M_DrawNightsAttackSuperSonic(void)
 	const UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_YELLOW, GTC_CACHE);
 	INT32 timer = (ntsatkdrawtimer/4) % 2;
 	angle_t fa = (FixedAngle(((ntsatkdrawtimer * 4) % 360)<<FRACBITS)>>ANGLETOFINESHIFT) & FINEMASK;
-	ntssupersonic[0] = W_CachePatchName("NTSSONC1", PU_PATCH);
-	ntssupersonic[1] = W_CachePatchName("NTSSONC2", PU_PATCH);
 	V_DrawFixedPatch(235<<FRACBITS, (120<<FRACBITS) - (8*FINESINE(fa)), FRACUNIT, 0, ntssupersonic[timer], colormap);
 }
 
@@ -6176,7 +6173,7 @@ static void M_StopMessage(INT32 choice)
 static void M_DrawImageDef(void)
 {
 	// Grr.  Need to autodetect for pic_ts.
-	pic_t *pictest = (pic_t *)W_CachePatchName(currentMenu->menuitems[itemOn].text,PU_CACHE);
+	pic_t *pictest = (pic_t *)W_CacheLumpName(currentMenu->menuitems[itemOn].text,PU_CACHE);
 	if (!pictest->zero)
 		V_DrawScaledPic(0,0,0,W_GetNumForName(currentMenu->menuitems[itemOn].text));
 	else
@@ -6444,10 +6441,6 @@ static void M_DrawAddons(void)
 		return;
 	}
 
-	// Lactozilla: Load addons menu patches.
-	if (needpatchrecache)
-		M_LoadAddonsPatches();
-
 	if (Playing())
 		V_DrawCenteredString(BASEVIDWIDTH/2, 5, warningflags, "Adding files mid-game may cause problems.");
 	else
@@ -7602,9 +7595,6 @@ static void M_DrawSoundTest(void)
 	fixed_t hscale = FRACUNIT/2, vscale = FRACUNIT/2, bounce = 0;
 	UINT8 frame[4] = {0, 0, -1, SKINCOLOR_RUBY};
 
-	if (needpatchrecache)
-		M_CacheSoundTest();
-
 	// let's handle the ticker first. ideally we'd tick this somewhere else, BUT...
 	if (curplaying)
 	{
@@ -8252,9 +8242,6 @@ static void M_DrawLoadGameData(void)
 	if (vid.width != BASEVIDWIDTH*vid.dupx)
 		hsep = (hsep*vid.width)/(BASEVIDWIDTH*vid.dupx);
 
-	if (needpatchrecache)
-		M_CacheLoadGameData();
-
 	for (i = 2; prev_i; i = -(i + ((UINT32)i >> 31))) // draws from outwards in; 2, -2, 1, -1, 0
 	{
 		prev_i = i;
@@ -8958,6 +8945,7 @@ void M_ForceSaveSlotSelected(INT32 sslot)
 // ================
 // CHARACTER SELECT
 // ================
+
 static void M_CacheCharacterSelectEntry(INT32 i, INT32 skinnum)
 {
 	if (!(description[i].picname[0]))
@@ -8978,22 +8966,6 @@ static void M_CacheCharacterSelectEntry(INT32 i, INT32 skinnum)
 		description[i].namepic = W_CachePatchName(description[i].nametag, PU_PATCH);
 }
 
-static void M_CacheCharacterSelect(void)
-{
-	INT32 i, skinnum;
-
-	for (i = 0; i < MAXSKINS; i++)
-	{
-		if (!description[i].used)
-			continue;
-
-		// Already set in M_SetupChoosePlayer
-		skinnum = description[i].skinnum[0];
-		if ((skinnum != -1) && (R_SkinUsable(-1, skinnum)))
-			M_CacheCharacterSelectEntry(i, skinnum);
-	}
-}
-
 static UINT8 M_SetupChoosePlayerDirect(INT32 choice)
 {
 	INT32 skinnum;
@@ -9200,9 +9172,6 @@ static void M_DrawSetupChoosePlayerMenu(void)
 	INT32 x, y;
 	INT32 w = (vid.width/vid.dupx);
 
-	if (needpatchrecache)
-		M_CacheCharacterSelect();
-
 	if (abs(char_scroll) > FRACUNIT)
 		char_scroll -= (char_scroll>>2);
 	else // close enough.
@@ -10173,6 +10142,9 @@ static void M_NightsAttack(INT32 choice)
 	// This is really just to make sure Sonic is the played character, just in case
 	M_PatchSkinNameTable();
 
+	ntssupersonic[0] = W_CachePatchName("NTSSONC1", PU_PATCH);
+	ntssupersonic[1] = W_CachePatchName("NTSSONC2", PU_PATCH);
+
 	G_SetGamestate(GS_TIMEATTACK); // do this before M_SetupNextMenu so that menu meta state knows that we're switching
 	titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please
 	M_SetupNextMenu(&SP_NightsAttackDef);
@@ -10574,10 +10546,6 @@ void M_DrawMarathon(void)
 	angle_t fa;
 	INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy), xspan = (vid.width/dupz), yspan = (vid.height/dupz), diffx = (xspan - BASEVIDWIDTH)/2, diffy = (yspan - BASEVIDHEIGHT)/2, maxy = BASEVIDHEIGHT + diffy;
 
-	// lactozilla: the renderer changed so recache patches
-	if (needpatchrecache)
-		M_CacheCharacterSelect();
-
 	curbgxspeed = 0;
 	curbgyspeed = 18;
 
diff --git a/src/p_ceilng.c b/src/p_ceilng.c
index 3c3c507cd168e46a50ccdce3a503c8570c028797..f12499d5ce6315e1a8baf70b43f454443ae0bef8 100644
--- a/src/p_ceilng.c
+++ b/src/p_ceilng.c
@@ -394,8 +394,10 @@ INT32 EV_DoCeiling(line_t *line, ceiling_e type)
 	INT32 secnum = -1;
 	sector_t *sec;
 	ceiling_t *ceiling;
+	mtag_t tag = Tag_FGet(&line->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
-	while ((secnum = P_FindSectorFromTag(line->tag,secnum)) >= 0)
+	TAG_ITER_SECTORS(0, tag, secnum)
 	{
 		sec = &sectors[secnum];
 
@@ -593,7 +595,7 @@ INT32 EV_DoCeiling(line_t *line, ceiling_e type)
 
 		}
 
-		ceiling->tag = sec->tag;
+		ceiling->tag = tag;
 		ceiling->type = type;
 		firstone = 0;
 	}
@@ -614,8 +616,10 @@ INT32 EV_DoCrush(line_t *line, ceiling_e type)
 	INT32 secnum = -1;
 	sector_t *sec;
 	ceiling_t *ceiling;
+	mtag_t tag = Tag_FGet(&line->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
-	while ((secnum = P_FindSectorFromTag(line->tag,secnum)) >= 0)
+	TAG_ITER_SECTORS(0, tag, secnum)
 	{
 		sec = &sectors[secnum];
 
@@ -670,7 +674,7 @@ INT32 EV_DoCrush(line_t *line, ceiling_e type)
 				break;
 		}
 
-		ceiling->tag = sec->tag;
+		ceiling->tag = tag;
 		ceiling->type = type;
 	}
 	return rtn;
diff --git a/src/p_enemy.c b/src/p_enemy.c
index ddb01b63ba4fdb2a0fa7ea1cc524a4f531f1b5d8..f9baf1813c7d7d894f2707d14f6f19a75682c6f7 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -3925,11 +3925,12 @@ void A_BossDeath(mobj_t *mo)
 	else
 	{
 		// Bring the egg trap up to the surface
-		junk.tag = LE_CAPSULE0;
+		// Incredibly shitty code ahead
+		Tag_FSet(&junk.tags, LE_CAPSULE0);
 		EV_DoElevator(&junk, elevateHighest, false);
-		junk.tag = LE_CAPSULE1;
+		Tag_FSet(&junk.tags, LE_CAPSULE1);
 		EV_DoElevator(&junk, elevateUp, false);
-		junk.tag = LE_CAPSULE2;
+		Tag_FSet(&junk.tags, LE_CAPSULE2);
 		EV_DoElevator(&junk, elevateHighest, false);
 
 		if (mapheaderinfo[gamemap-1]->muspostbossname[0] &&
@@ -4052,7 +4053,7 @@ bossjustdie:
 		}
 		case MT_KOOPA:
 		{
-			junk.tag = LE_KOOPA;
+			Tag_FSet(&junk.tags, LE_KOOPA);
 			EV_DoCeiling(&junk, raiseToHighest);
 			return;
 		}
@@ -6157,7 +6158,7 @@ void A_RockSpawn(mobj_t *actor)
 {
 	mobj_t *mo;
 	mobjtype_t type;
-	INT32 i = P_FindSpecialLineFromTag(12, (INT16)actor->threshold, -1);
+	INT32 i = Tag_FindLineSpecial(12, (INT16)actor->threshold);
 	line_t *line;
 	fixed_t dist;
 	fixed_t randomoomph;
diff --git a/src/p_floor.c b/src/p_floor.c
index a0f7edd9ca0e68617b1eede0441c5a14a2dbe549..ed49b03a38ef8b4381ad968a4540c09952d1d018 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -632,8 +632,10 @@ void T_BounceCheese(bouncecheese_t *bouncer)
 	fixed_t waterheight;
 	fixed_t floorheight;
 	sector_t *actionsector;
-	INT32 i;
 	boolean remove;
+	INT32 i;
+	mtag_t tag = Tag_FGet(&bouncer->sourceline->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
 	if (bouncer->sector->crumblestate == CRUMBLE_RESTORE || bouncer->sector->crumblestate == CRUMBLE_WAIT
 		|| bouncer->sector->crumblestate == CRUMBLE_ACTIVATED) // Oops! Crumbler says to remove yourself!
@@ -648,7 +650,7 @@ void T_BounceCheese(bouncecheese_t *bouncer)
 	}
 
 	// You can use multiple target sectors, but at your own risk!!!
-	for (i = -1; (i = P_FindSectorFromTag(bouncer->sourceline->tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, tag, i)
 	{
 		actionsector = &sectors[i];
 		actionsector->moved = true;
@@ -772,6 +774,8 @@ void T_StartCrumble(crumble_t *crumble)
 	ffloor_t *rover;
 	sector_t *sector;
 	INT32 i;
+	mtag_t tag = Tag_FGet(&crumble->sourceline->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
 	// Once done, the no-return thinker just sits there,
 	// constantly 'returning'... kind of an oxymoron, isn't it?
@@ -800,7 +804,7 @@ void T_StartCrumble(crumble_t *crumble)
 		}
 		else if (++crumble->timer == 0) // Reposition back to original spot
 		{
-			for (i = -1; (i = P_FindSectorFromTag(crumble->sourceline->tag, i)) >= 0 ;)
+			TAG_ITER_SECTORS(0, tag, i)
 			{
 				sector = &sectors[i];
 
@@ -836,7 +840,7 @@ void T_StartCrumble(crumble_t *crumble)
 		// Flash to indicate that the platform is about to return.
 		if (crumble->timer > -224 && (leveltime % ((abs(crumble->timer)/8) + 1) == 0))
 		{
-			for (i = -1; (i = P_FindSectorFromTag(crumble->sourceline->tag, i)) >= 0 ;)
+			TAG_ITER_SECTORS(0, tag, i)
 			{
 				sector = &sectors[i];
 
@@ -928,7 +932,7 @@ void T_StartCrumble(crumble_t *crumble)
 		P_RemoveThinker(&crumble->thinker);
 	}
 
-	for (i = -1; (i = P_FindSectorFromTag(crumble->sourceline->tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, tag, i)
 	{
 		sector = &sectors[i];
 		sector->moved = true;
@@ -944,6 +948,7 @@ void T_StartCrumble(crumble_t *crumble)
 void T_MarioBlock(mariothink_t *block)
 {
 	INT32 i;
+	TAG_ITER_DECLARECOUNTER(0);
 
 	T_MovePlane
 	(
@@ -978,8 +983,7 @@ void T_MarioBlock(mariothink_t *block)
 		block->sector->ceilspeed = 0;
 		block->direction = 0;
 	}
-
-	for (i = -1; (i = P_FindSectorFromTag(block->tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, (INT16)block->tag, i)
 		P_RecalcPrecipInSector(&sectors[i]);
 }
 
@@ -992,8 +996,7 @@ void T_FloatSector(floatthink_t *floater)
 
 	// Just find the first sector with the tag.
 	// Doesn't work with multiple sectors that have different floor/ceiling heights.
-	secnum = P_FindSectorFromTag(floater->tag, -1);
-	if (secnum <= 0)
+	if ((secnum = Tag_Iterate_Sectors((INT16)floater->tag, 0)) < 0)
 		return;
 	actionsector = &sectors[secnum];
 
@@ -1131,10 +1134,8 @@ void T_ThwompSector(thwomp_t *thwomp)
 
 	// Just find the first sector with the tag.
 	// Doesn't work with multiple sectors that have different floor/ceiling heights.
-	secnum = P_FindSectorFromTag(thwomp->tag, -1);
-
-	if (secnum <= 0)
-		return; // Bad bad bad!
+	if ((secnum = Tag_Iterate_Sectors((INT16)thwomp->tag, 0)) < 0)
+		return;
 
 	actionsector = &sectors[secnum];
 
@@ -1293,8 +1294,10 @@ void T_NoEnemiesSector(noenemies_t *nobaddies)
 	sector_t *sec = NULL;
 	INT32 secnum = -1;
 	boolean FOFsector = false;
+	mtag_t tag = Tag_FGet(&nobaddies->sourceline->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
-	while ((secnum = P_FindSectorFromTag(nobaddies->sourceline->tag, secnum)) >= 0)
+	TAG_ITER_SECTORS(0, tag, secnum)
 	{
 		sec = &sectors[secnum];
 
@@ -1304,13 +1307,15 @@ void T_NoEnemiesSector(noenemies_t *nobaddies)
 		for (i = 0; i < sec->linecount; i++)
 		{
 			INT32 targetsecnum = -1;
+			mtag_t tag2 = Tag_FGet(&sec->lines[i]->tags);
+			TAG_ITER_DECLARECOUNTER(1);
 
 			if (sec->lines[i]->special < 100 || sec->lines[i]->special >= 300)
 				continue;
 
 			FOFsector = true;
 
-			while ((targetsecnum = P_FindSectorFromTag(sec->lines[i]->tag, targetsecnum)) >= 0)
+			TAG_ITER_SECTORS(1, tag2, targetsecnum)
 			{
 				if (T_SectorHasEnemies(&sectors[targetsecnum]))
 					return;
@@ -1321,7 +1326,7 @@ void T_NoEnemiesSector(noenemies_t *nobaddies)
 			return;
 	}
 
-	CONS_Debug(DBG_GAMELOGIC, "Running no-more-enemies exec with tag of %d\n", nobaddies->sourceline->tag);
+	CONS_Debug(DBG_GAMELOGIC, "Running no-more-enemies exec with tag of %d\n", tag);
 
 	// No enemies found, run the linedef exec and terminate this thinker
 	P_RunTriggerLinedef(nobaddies->sourceline, NULL, NULL);
@@ -1396,6 +1401,8 @@ void T_EachTimeThinker(eachtime_t *eachtime)
 	boolean floortouch = false;
 	fixed_t bottomheight, topheight;
 	ffloor_t *rover;
+	mtag_t tag = Tag_FGet(&eachtime->sourceline->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
@@ -1405,7 +1412,7 @@ void T_EachTimeThinker(eachtime_t *eachtime)
 		eachtime->playersOnArea[i] = false;
 	}
 
-	while ((secnum = P_FindSectorFromTag(eachtime->sourceline->tag, secnum)) >= 0)
+	TAG_ITER_SECTORS(0, tag, secnum)
 	{
 		sec = &sectors[secnum];
 
@@ -1422,13 +1429,15 @@ void T_EachTimeThinker(eachtime_t *eachtime)
 		for (i = 0; i < sec->linecount; i++)
 		{
 			INT32 targetsecnum = -1;
+			mtag_t tag2 = Tag_FGet(&sec->lines[i]->tags);
+			TAG_ITER_DECLARECOUNTER(1);
 
 			if (sec->lines[i]->special < 100 || sec->lines[i]->special >= 300)
 				continue;
 
 			FOFsector = true;
 
-			while ((targetsecnum = P_FindSectorFromTag(sec->lines[i]->tag, targetsecnum)) >= 0)
+			TAG_ITER_SECTORS(1, tag2, targetsecnum)
 			{
 				targetsec = &sectors[targetsecnum];
 
@@ -1530,7 +1539,7 @@ void T_EachTimeThinker(eachtime_t *eachtime)
 		if (!playersArea[i] && (!eachtime->triggerOnExit || !P_IsPlayerValid(i)))
 			continue;
 
-		CONS_Debug(DBG_GAMELOGIC, "Trying to activate each time executor with tag %d\n", eachtime->sourceline->tag);
+		CONS_Debug(DBG_GAMELOGIC, "Trying to activate each time executor with tag %d\n", tag);
 
 		// 03/08/14 -Monster Iestyn
 		// No more stupid hacks involving changing eachtime->sourceline's tag or special or whatever!
@@ -1562,11 +1571,13 @@ void T_RaiseSector(raise_t *raise)
 	fixed_t distToNearestEndpoint;
 	INT32 direction;
 	result_e res = 0;
+	mtag_t tag = raise->tag;
+	TAG_ITER_DECLARECOUNTER(0);
 
 	if (raise->sector->crumblestate >= CRUMBLE_FALL || raise->sector->ceilingdata)
 		return;
 
-	for (i = -1; (i = P_FindSectorFromTag(raise->tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, tag, i)
 	{
 		sector = &sectors[i];
 
@@ -1693,7 +1704,7 @@ void T_RaiseSector(raise_t *raise)
 	raise->sector->ceilspeed = 42;
 	raise->sector->floorspeed = speed*direction;
 
-	for (i = -1; (i = P_FindSectorFromTag(raise->tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, tag, i)
 		P_RecalcPrecipInSector(&sectors[i]);
 }
 
@@ -1810,8 +1821,10 @@ void EV_DoFloor(line_t *line, floor_e floortype)
 	INT32 secnum = -1;
 	sector_t *sec;
 	floormove_t *dofloor;
+	mtag_t tag = Tag_FGet(&line->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
-	while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+	TAG_ITER_SECTORS(0, tag, secnum)
 	{
 		sec = &sectors[secnum];
 
@@ -2025,9 +2038,11 @@ void EV_DoElevator(line_t *line, elevator_e elevtype, boolean customspeed)
 	INT32 secnum = -1;
 	sector_t *sec;
 	elevator_t *elevator;
+	mtag_t tag = Tag_FGet(&line->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
 	// act on all sectors with the same tag as the triggering linedef
-	while ((secnum = P_FindSectorFromTag(line->tag,secnum)) >= 0)
+	TAG_ITER_SECTORS(0, tag, secnum)
 	{
 		sec = &sectors[secnum];
 
@@ -2148,6 +2163,7 @@ void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
 	INT16 flags;
 
 	sector_t *controlsec = rover->master->frontsector;
+	mtag_t tag = Tag_FGet(&controlsec->tags);
 
 	if (sec == NULL)
 	{
@@ -2176,9 +2192,9 @@ void EV_CrumbleChain(sector_t *sec, ffloor_t *rover)
 	lifetime = 3*TICRATE;
 	flags = 0;
 
-	if (controlsec->tag != 0)
+	if (tag != 0)
 	{
-		INT32 tagline = P_FindSpecialLineFromTag(14, controlsec->tag, -1);
+		INT32 tagline = Tag_FindLineSpecial(14, tag);
 		if (tagline != -1)
 		{
 			if (sides[lines[tagline].sidenum[0]].toptexture)
@@ -2322,6 +2338,8 @@ INT32 EV_StartCrumble(sector_t *sec, ffloor_t *rover, boolean floating,
 	crumble_t *crumble;
 	sector_t *foundsec;
 	INT32 i;
+	mtag_t tag = Tag_FGet(&rover->master->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
 	// If floor is already activated, skip it
 	if (sec->floordata)
@@ -2364,7 +2382,7 @@ INT32 EV_StartCrumble(sector_t *sec, ffloor_t *rover, boolean floating,
 
 	crumble->sector->crumblestate = CRUMBLE_ACTIVATED;
 
-	for (i = -1; (i = P_FindSectorFromTag(crumble->sourceline->tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, tag, i)
 	{
 		foundsec = &sectors[i];
 
@@ -2413,7 +2431,7 @@ void EV_MarioBlock(ffloor_t *rover, sector_t *sector, mobj_t *puncher)
 		block->direction = 1;
 		block->floorstartheight = block->sector->floorheight;
 		block->ceilingstartheight = block->sector->ceilingheight;
-		block->tag = (INT16)sector->tag;
+		block->tag = (INT16)Tag_FGet(&sector->tags);
 
 		if (itsamonitor)
 		{
diff --git a/src/p_inter.c b/src/p_inter.c
index bd044f32a1672f81eacb28c1b9a284643d796591..415c679e4922b1d55920bc5f937c39fee1927e30 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -1388,7 +1388,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				if (player->bot)
 					return;
 
-				junk.tag = LE_AXE;
+				Tag_FSet(&junk.tags, LE_AXE);
 				EV_DoElevator(&junk, bridgeFall, false);
 
 				// scan the remaining thinkers to find koopa
diff --git a/src/p_lights.c b/src/p_lights.c
index 371077a302d39b903b88658baf492d38680dcbb7..d396e92d3dedff6ac56f40901335ec701d6625e9 100644
--- a/src/p_lights.c
+++ b/src/p_lights.c
@@ -374,8 +374,10 @@ void P_FadeLightBySector(sector_t *sector, INT32 destvalue, INT32 speed, boolean
 void P_FadeLight(INT16 tag, INT32 destvalue, INT32 speed, boolean ticbased, boolean force)
 {
 	INT32 i;
+	TAG_ITER_DECLARECOUNTER(0);
+
 	// search all sectors for ones with tag
-	for (i = -1; (i = P_FindSectorFromTag(tag, i)) >= 0 ;)
+	TAG_ITER_SECTORS(0, tag, i)
 	{
 		if (!force && ticbased // always let speed fader execute
 			&& sectors[i].lightingdata
diff --git a/src/p_map.c b/src/p_map.c
index 0a9107ee546239a47309d9600421ef4d1a6e29c7..922c0d9ec06c8a7f6c74d73b209ab961cc8ec571 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -2735,7 +2735,7 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 			// Step up
 			if (thing->z < tmfloorz)
 			{
-				if (tmfloorz - thing->z <= maxstep)
+				if (maxstep > 0 && tmfloorz - thing->z <= maxstep)
 				{
 					thing->z = thing->floorz = tmfloorz;
 					thing->floorrover = tmfloorrover;
@@ -2748,7 +2748,7 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 			}
 			else if (tmceilingz < thingtop)
 			{
-				if (thingtop - tmceilingz <= maxstep)
+				if (maxstep > 0 && thingtop - tmceilingz <= maxstep)
 				{
 					thing->z = ( thing->ceilingz = tmceilingz ) - thing->height;
 					thing->ceilingrover = tmceilingrover;
@@ -3107,7 +3107,7 @@ static void P_HitSlideLine(line_t *ld)
 	lineangle >>= ANGLETOFINESHIFT;
 	deltaangle >>= ANGLETOFINESHIFT;
 
-	movelen = P_AproxDistance(tmxmove, tmymove);
+	movelen = R_PointToDist2(0, 0, tmxmove, tmymove);
 	newlen = FixedMul(movelen, FINECOSINE(deltaangle));
 
 	tmxmove = FixedMul(newlen, FINECOSINE(lineangle));
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 414d0435c7246fea7e2669f826bf66cc25cf5bda..7ba6d1fad979ff68c563f00693c1ba94b5fbdf93 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -38,10 +38,6 @@
 static CV_PossibleValue_t CV_BobSpeed[] = {{0, "MIN"}, {4*FRACUNIT, "MAX"}, {0, NULL}};
 consvar_t cv_movebob = CVAR_INIT ("movebob", "1.0", CV_FLOAT|CV_SAVE, CV_BobSpeed, NULL);
 
-#ifdef WALLSPLATS
-consvar_t cv_splats = CVAR_INIT ("splats", "On", CV_SAVE, CV_OnOff, NULL);
-#endif
-
 actioncache_t actioncachehead;
 
 static mobj_t *overlaycap = NULL;
@@ -1961,29 +1957,6 @@ void P_XYMovement(mobj_t *mo)
 				return;
 			}
 
-			// draw damage on wall
-			//SPLAT TEST ----------------------------------------------------------
-#ifdef WALLSPLATS
-			if (blockingline && mo->type != MT_REDRING && mo->type != MT_FIREBALL
-			&& !(mo->flags2 & (MF2_AUTOMATIC|MF2_RAILRING|MF2_BOUNCERING|MF2_EXPLOSION|MF2_SCATTER)))
-				// set by last P_TryMove() that failed
-			{
-				divline_t divl;
-				divline_t misl;
-				fixed_t frac;
-
-				P_MakeDivline(blockingline, &divl);
-				misl.x = mo->x;
-				misl.y = mo->y;
-				misl.dx = mo->momx;
-				misl.dy = mo->momy;
-				frac = P_InterceptVector(&divl, &misl);
-				R_AddWallSplat(blockingline, P_PointOnLineSide(mo->x,mo->y,blockingline),
-					"A_DMG3", mo->z, frac, SPLATDRAWMODE_SHADE);
-			}
-#endif
-			// --------------------------------------------------------- SPLAT TEST
-
 			P_ExplodeMissile(mo);
 			return;
 		}
@@ -3536,16 +3509,19 @@ static boolean P_CameraCheckHeat(camera_t *thiscam)
 {
 	sector_t *sector;
 	fixed_t halfheight = thiscam->z + (thiscam->height >> 1);
+	size_t i;
 
 	// see if we are in water
 	sector = thiscam->subsector->sector;
 
-	if (P_FindSpecialLineFromTag(13, sector->tag, -1) != -1)
-		return true;
+	for (i = 0; i < sector->tags.count; i++)
+		if (Tag_FindLineSpecial(13, sector->tags.tags[i]) != -1)
+			return true;
 
 	if (sector->ffloors)
 	{
 		ffloor_t *rover;
+		size_t j;
 
 		for (rover = sector->ffloors; rover; rover = rover->next)
 		{
@@ -3557,7 +3533,8 @@ static boolean P_CameraCheckHeat(camera_t *thiscam)
 			if (halfheight <= P_GetFFloorBottomZAt(rover, thiscam->x, thiscam->y))
 				continue;
 
-			if (P_FindSpecialLineFromTag(13, rover->master->frontsector->tag, -1) != -1)
+			for (j = 0; j < rover->master->frontsector->tags.count; j++)
+			if (Tag_FindLineSpecial(13, rover->master->frontsector->tags.tags[j]) != -1)
 				return true;
 		}
 	}
@@ -4626,16 +4603,18 @@ static boolean P_Boss4MoveCage(mobj_t *mobj, fixed_t delta)
 	const UINT16 tag = 65534 + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0);
 	INT32 snum;
 	sector_t *sector;
-	for (snum = sectors[tag%numsectors].firsttag; snum != -1; snum = sector->nexttag)
+	boolean gotcage = false;
+	TAG_ITER_DECLARECOUNTER(0);
+
+	TAG_ITER_SECTORS(0, tag, snum)
 	{
 		sector = &sectors[snum];
-		if (sector->tag != tag)
-			continue;
 		sector->floorheight += delta;
 		sector->ceilingheight += delta;
 		P_CheckSector(sector, true);
+		gotcage = true;
 	}
-	return sectors[tag%numsectors].firsttag != -1;
+	return gotcage;
 }
 
 // Move Boss4's arms to angle
@@ -4707,26 +4686,16 @@ static void P_Boss4PinchSpikeballs(mobj_t *mobj, angle_t angle, fixed_t dz)
 static void P_Boss4DestroyCage(mobj_t *mobj)
 {
 	const UINT16 tag = 65534 + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0);
-	INT32 snum, next;
+	INT32 snum;
 	size_t a;
 	sector_t *sector, *rsec;
 	ffloor_t *rover;
+	TAG_ITER_DECLARECOUNTER(0);
 
-	// This will be the final iteration of sector tag.
-	// We'll destroy the tag list as we go.
-	next = sectors[tag%numsectors].firsttag;
-	sectors[tag%numsectors].firsttag = -1;
-
-	for (snum = next; snum != -1; snum = next)
+	TAG_ITER_SECTORS(0, tag, snum)
 	{
 		sector = &sectors[snum];
 
-		next = sector->nexttag;
-		sector->nexttag = -1;
-		if (sector->tag != tag)
-			continue;
-		sector->tag = 0;
-
 		// Destroy the FOFs.
 		for (a = 0; a < sector->numattached; a++)
 		{
@@ -9614,12 +9583,6 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
 			mobj->fuse = 1; // Return to base.
 		break;
 	}
-	case MT_CANNONBALL:
-#ifdef FLOORSPLATS
-		R_AddFloorSplat(mobj->tracer->subsector, mobj->tracer, "TARGET", mobj->tracer->x,
-			mobj->tracer->y, mobj->tracer->floorz, SPLATDRAWMODE_SHADE);
-#endif
-		break;
 	case MT_SPINDUST: // Spindash dust
 		mobj->momx = FixedMul(mobj->momx, (3*FRACUNIT)/4); // originally 50000
 		mobj->momy = FixedMul(mobj->momy, (3*FRACUNIT)/4); // same
@@ -10046,11 +10009,12 @@ void P_MobjThinker(mobj_t *mobj)
 	// Sector special (2,8) allows ANY mobj to trigger a linedef exec
 	if (mobj->subsector && GETSECSPECIAL(mobj->subsector->sector->special, 2) == 8)
 	{
-		sector_t *sec2;
-
-		sec2 = P_ThingOnSpecial3DFloor(mobj);
+		sector_t *sec2 = P_ThingOnSpecial3DFloor(mobj);
 		if (sec2 && GETSECSPECIAL(sec2->special, 2) == 1)
-			P_LinedefExecute(sec2->tag, mobj, sec2);
+		{
+			mtag_t tag = Tag_FGet(&sec2->tags);
+			P_LinedefExecute(tag, mobj, sec2);
+		}
 	}
 
 	if (mobj->scale != mobj->destscale)
@@ -10274,14 +10238,19 @@ void P_PushableThinker(mobj_t *mobj)
 	sec = mobj->subsector->sector;
 
 	if (GETSECSPECIAL(sec->special, 2) == 1 && mobj->z == sec->floorheight)
-		P_LinedefExecute(sec->tag, mobj, sec);
-//	else if (GETSECSPECIAL(sec->special, 2) == 8)
 	{
-		sector_t *sec2;
+		mtag_t tag = Tag_FGet(&sec->tags);
+		P_LinedefExecute(tag, mobj, sec);
+	}
 
-		sec2 = P_ThingOnSpecial3DFloor(mobj);
+//	else if (GETSECSPECIAL(sec->special, 2) == 8)
+	{
+		sector_t *sec2 = P_ThingOnSpecial3DFloor(mobj);
 		if (sec2 && GETSECSPECIAL(sec2->special, 2) == 1)
-			P_LinedefExecute(sec2->tag, mobj, sec2);
+		{
+			mtag_t tag = Tag_FGet(&sec2->tags);
+			P_LinedefExecute(tag, mobj, sec2);
+		}
 	}
 
 	// it has to be pushable RIGHT NOW for this part to happen
@@ -10502,6 +10471,12 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 	if ((maptol & TOL_ERZ3) && !(mobj->type == MT_BLACKEGGMAN))
 		mobj->destscale = FRACUNIT/2;
 
+	// Sprite rendering
+	mobj->blendmode = AST_TRANSLUCENT;
+	mobj->spritexscale = mobj->spriteyscale = mobj->scale;
+	mobj->spritexoffset = mobj->spriteyoffset = 0;
+	mobj->floorspriteslope = NULL;
+
 	// set subsector and/or block links
 	P_SetThingPosition(mobj);
 	I_Assert(mobj->subsector != NULL);
@@ -10902,6 +10877,22 @@ static inline precipmobj_t *P_SpawnSnowMobj(fixed_t x, fixed_t y, fixed_t z, mob
 	return mo;
 }
 
+void *P_CreateFloorSpriteSlope(mobj_t *mobj)
+{
+	if (mobj->floorspriteslope)
+		Z_Free(mobj->floorspriteslope);
+	mobj->floorspriteslope = Z_Calloc(sizeof(pslope_t), PU_LEVEL, NULL);
+	mobj->floorspriteslope->normal.z = FRACUNIT;
+	return (void *)mobj->floorspriteslope;
+}
+
+void P_RemoveFloorSpriteSlope(mobj_t *mobj)
+{
+	if (mobj->floorspriteslope)
+		Z_Free(mobj->floorspriteslope);
+	mobj->floorspriteslope = NULL;
+}
+
 //
 // P_RemoveMobj
 //
@@ -10958,11 +10949,14 @@ void P_RemoveMobj(mobj_t *mobj)
 		P_DelSeclist(sector_list);
 		sector_list = NULL;
 	}
+
 	mobj->flags |= MF_NOSECTOR|MF_NOBLOCKMAP;
 	mobj->subsector = NULL;
 	mobj->state = NULL;
 	mobj->player = NULL;
 
+	P_RemoveFloorSpriteSlope(mobj);
+
 	// stop any playing sound
 	S_StopSound(mobj);
 
@@ -12030,8 +12024,7 @@ static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
 	const size_t mthingi = (size_t)(mthing - mapthings);
 
 	// Find the corresponding linedef special, using angle as tag
-	// P_FindSpecialLineFromTag works here now =D
-	line = P_FindSpecialLineFromTag(9, mthing->angle, -1);
+	line = Tag_FindLineSpecial(9, mthing->angle);
 
 	if (line == -1)
 	{
@@ -12341,7 +12334,7 @@ static boolean P_SetupParticleGen(mapthing_t *mthing, mobj_t *mobj)
 	const size_t mthingi = (size_t)(mthing - mapthings);
 
 	// Find the corresponding linedef special, using angle as tag
-	line = P_FindSpecialLineFromTag(15, mthing->angle, -1);
+	line = Tag_FindLineSpecial(15, mthing->angle);
 
 	if (line == -1)
 	{
@@ -12580,17 +12573,20 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
 		break;
 	}
 	case MT_SKYBOX:
-		if (mthing->tag < 0 || mthing->tag > 15)
+	{
+		mtag_t tag = Tag_FGet(&mthing->tags);
+		if (tag < 0 || tag > 15)
 		{
-			CONS_Debug(DBG_GAMELOGIC, "P_SetupSpawnedMapThing: Skybox ID %d of mapthing %s is not between 0 and 15!\n", mthing->tag, sizeu1((size_t)(mthing - mapthings)));
+			CONS_Debug(DBG_GAMELOGIC, "P_SetupSpawnedMapThing: Skybox ID %d of mapthing %s is not between 0 and 15!\n", tag, sizeu1((size_t)(mthing - mapthings)));
 			break;
 		}
 
 		if (mthing->options & MTF_OBJECTSPECIAL)
-			skyboxcenterpnts[mthing->tag] = mobj;
+			skyboxcenterpnts[tag] = mobj;
 		else
-			skyboxviewpnts[mthing->tag] = mobj;
+			skyboxviewpnts[tag] = mobj;
 		break;
+	}
 	case MT_EGGSTATUE:
 		if (mthing->options & MTF_EXTRA)
 		{
@@ -13077,8 +13073,8 @@ static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y,
 	mobj = P_SpawnMobj(x, y, z, i);
 	mobj->spawnpoint = mthing;
 
-	P_SetScale(mobj, mthing->scale);
-	mobj->destscale = mthing->scale;
+	P_SetScale(mobj, FixedMul(mobj->scale, mthing->scale));
+	mobj->destscale = FixedMul(mobj->destscale, mthing->scale);
 
 	if (!P_SetupSpawnedMapThing(mthing, mobj, &doangle))
 		return mobj;
diff --git a/src/p_mobj.h b/src/p_mobj.h
index 27a6ef4f05d3a2f8a5a9cf57b8fe495fe4325ff0..5bb7c908e85463020507f1e02dbb7059d904ddf6 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -194,6 +194,7 @@ typedef enum
 	MF2_AMBUSH         = 1<<27, // Alternate behaviour typically set by MTF_AMBUSH
 	MF2_LINKDRAW       = 1<<28, // Draw vissprite of mobj immediately before/after tracer's vissprite (dependent on dispoffset and position)
 	MF2_SHIELD         = 1<<29, // Thinker calls P_AddShield/P_ShieldLook (must be partnered with MF_SCENERY to use)
+	MF2_SPLAT          = 1<<30, // Renders as a splat
 	// free: to and including 1<<31
 } mobjflag2_t;
 
@@ -264,6 +265,7 @@ typedef enum {
 	// Ran the thinker this tic.
 	PCF_THUNK = 32,
 } precipflag_t;
+
 // Map Object definition.
 typedef struct mobj_s
 {
@@ -285,6 +287,12 @@ typedef struct mobj_s
 	UINT8 sprite2; // player sprites
 	UINT16 anim_duration; // for FF_ANIMATE states
 
+	UINT32 renderflags; // render flags
+	INT32 blendmode; // blend mode
+	fixed_t spritexscale, spriteyscale;
+	fixed_t spritexoffset, spriteyoffset;
+	struct pslope_s *floorspriteslope; // The slope that the floorsprite is rotated by
+
 	struct msecnode_s *touching_sectorlist; // a linked list of sectors where this object appears
 
 	struct subsector_s *subsector; // Subsector the mobj resides in.
@@ -399,13 +407,19 @@ typedef struct precipmobj_s
 	struct precipmobj_s **sprev; // killough 8/11/98: change to ptr-to-ptr
 
 	// More drawing info: to determine current sprite.
-	angle_t angle, pitch, roll;  // orientation
+	angle_t angle, pitch, roll; // orientation
 	angle_t rollangle;
 	spritenum_t sprite; // used to find patch_t and flip value
 	UINT32 frame; // frame number, plus bits see p_pspr.h
 	UINT8 sprite2; // player sprites
 	UINT16 anim_duration; // for FF_ANIMATE states
 
+	UINT32 renderflags; // render flags
+	INT32 blendmode; // blend mode
+	fixed_t spritexscale, spriteyscale;
+	fixed_t spritexoffset, spriteyoffset;
+	struct pslope_s *floorspriteslope; // The slope that the floorsprite is rotated by
+
 	struct mprecipsecnode_s *touching_sectorlist; // a linked list of sectors where this object appears
 
 	struct subsector_s *subsector; // Subsector the mobj resides in.
@@ -462,6 +476,8 @@ void P_SpawnItemPattern(mapthing_t *mthing, boolean bonustime);
 void P_SpawnHoopOfSomething(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, angle_t rotangle);
 void P_SpawnPrecipitation(void);
 void P_SpawnParaloop(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, statenum_t nstate, angle_t rotangle, boolean spawncenter);
+void *P_CreateFloorSpriteSlope(mobj_t *mobj);
+void P_RemoveFloorSpriteSlope(mobj_t *mobj);
 boolean P_BossTargetPlayer(mobj_t *actor, boolean closest);
 boolean P_SupermanLook4Players(mobj_t *actor);
 void P_DestroyRobots(void);
diff --git a/src/p_polyobj.c b/src/p_polyobj.c
index 63d062c224bf2dbcbaa733215cfb653f711fd60b..874edbd50cfb60d6ff0308748dd149c96f8480c3 100644
--- a/src/p_polyobj.c
+++ b/src/p_polyobj.c
@@ -556,10 +556,11 @@ static void Polyobj_moveToSpawnSpot(mapthing_t *anchor)
 	polyobj_t *po;
 	vertex_t  dist, sspot;
 	size_t i;
+	mtag_t tag = Tag_FGet(&anchor->tags);
 
-	if (!(po = Polyobj_GetForNum(anchor->tag)))
+	if (!(po = Polyobj_GetForNum(tag)))
 	{
-		CONS_Debug(DBG_POLYOBJ, "Bad polyobject %d for anchor point\n", anchor->tag);
+		CONS_Debug(DBG_POLYOBJ, "Bad polyobject %d for anchor point\n", tag);
 		return;
 	}
 
@@ -1342,7 +1343,7 @@ void Polyobj_InitLevel(void)
 		{
 			qitem = (mobjqitem_t *)M_QueueIterator(&spawnqueue);
 
-			Polyobj_spawnPolyObj(i, qitem->mo, qitem->mo->spawnpoint->tag);
+			Polyobj_spawnPolyObj(i, qitem->mo, Tag_FGet(&qitem->mo->spawnpoint->tags));
 		}
 
 		// move polyobjects to spawn points
@@ -2444,10 +2445,11 @@ boolean EV_DoPolyObjFlag(polyflagdata_t *pfdata)
 	polymove_t *th;
 	size_t i;
 	INT32 start;
+	mtag_t tag = pfdata->polyObjNum;
 
-	if (!(po = Polyobj_GetForNum(pfdata->polyObjNum)))
+	if (!(po = Polyobj_GetForNum(tag)))
 	{
-		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyFlag: bad polyobj %d\n", pfdata->polyObjNum);
+		CONS_Debug(DBG_POLYOBJ, "EV_DoPolyFlag: bad polyobj %d\n", tag);
 		return false;
 	}
 
@@ -2470,7 +2472,7 @@ boolean EV_DoPolyObjFlag(polyflagdata_t *pfdata)
 	po->thinker = &th->thinker;
 
 	// set fields
-	th->polyObjNum = pfdata->polyObjNum;
+	th->polyObjNum = tag;
 	th->distance   = 0;
 	th->speed      = pfdata->speed;
 	th->angle      = pfdata->angle;
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 4f6f31803998d8cf8c19472a21d4090d779b2119..30a0c9c893088e412545c81217156deb864c4352 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -98,13 +98,16 @@ static void P_NetArchivePlayers(void)
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
+		WRITESINT8(save_p, (SINT8)adminplayers[i]);
+
 		if (!playeringame[i])
 			continue;
 
 		flags = 0;
 
-		// no longer send ticcmds, player name, skin, or color
+		// no longer send ticcmds
 
+		WRITESTRINGN(save_p, player_names[i], MAXPLAYERNAME);
 		WRITEINT16(save_p, players[i].angleturn);
 		WRITEINT16(save_p, players[i].oldrelangleturn);
 		WRITEANGLE(save_p, players[i].aiming);
@@ -134,6 +137,9 @@ static void P_NetArchivePlayers(void)
 		WRITEUINT16(save_p, players[i].flashpal);
 		WRITEUINT16(save_p, players[i].flashcount);
 
+		WRITEUINT8(save_p, players[i].skincolor);
+		WRITEINT32(save_p, players[i].skin);
+		WRITEUINT32(save_p, players[i].availabilities);
 		WRITEUINT32(save_p, players[i].score);
 		WRITEFIXED(save_p, players[i].dashspeed);
 		WRITESINT8(save_p, players[i].lives);
@@ -305,6 +311,8 @@ static void P_NetUnArchivePlayers(void)
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
+		adminplayers[i] = (INT32)READSINT8(save_p);
+
 		// Do NOT memset player struct to 0
 		// other areas may initialize data elsewhere
 		//memset(&players[i], 0, sizeof (player_t));
@@ -312,9 +320,8 @@ static void P_NetUnArchivePlayers(void)
 			continue;
 
 		// NOTE: sending tics should (hopefully) no longer be necessary
-		// sending player names, skin and color should not be necessary at all!
-		// (that data is handled in the server config now)
 
+		READSTRINGN(save_p, player_names[i], MAXPLAYERNAME);
 		players[i].angleturn = READINT16(save_p);
 		players[i].oldrelangleturn = READINT16(save_p);
 		players[i].aiming = READANGLE(save_p);
@@ -344,6 +351,9 @@ static void P_NetUnArchivePlayers(void)
 		players[i].flashpal = READUINT16(save_p);
 		players[i].flashcount = READUINT16(save_p);
 
+		players[i].skincolor = READUINT8(save_p);
+		players[i].skin = READINT32(save_p);
+		players[i].availabilities = READUINT32(save_p);
 		players[i].score = READUINT32(save_p);
 		players[i].dashspeed = READFIXED(save_p); // dashing speed
 		players[i].lives = READSINT8(save_p);
@@ -913,7 +923,7 @@ static void UnArchiveFFloors(const sector_t *ss)
 
 static void ArchiveSectors(void)
 {
-	size_t i;
+	size_t i, j;
 	const sector_t *ss = sectors;
 	const sector_t *spawnss = spawnsectors;
 	UINT8 diff, diff2, diff3;
@@ -951,10 +961,8 @@ static void ArchiveSectors(void)
 		if (ss->ceilingpic_angle != spawnss->ceilingpic_angle)
 			diff2 |= SD_CEILANG;
 
-		if (ss->tag != spawnss->tag)
+		if (!Tag_Compare(&ss->tags, &spawnss->tags))
 			diff2 |= SD_TAG;
-		if (ss->nexttag != spawnss->nexttag || ss->firsttag != spawnss->firsttag)
-			diff3 |= SD_TAGLIST;
 
 		if (ss->extra_colormap != spawnss->extra_colormap)
 			diff3 |= SD_COLORMAP;
@@ -1002,12 +1010,11 @@ static void ArchiveSectors(void)
 				WRITEANGLE(save_p, ss->floorpic_angle);
 			if (diff2 & SD_CEILANG)
 				WRITEANGLE(save_p, ss->ceilingpic_angle);
-			if (diff2 & SD_TAG) // save only the tag
-				WRITEINT16(save_p, ss->tag);
-			if (diff3 & SD_TAGLIST) // save both firsttag and nexttag
-			{ // either of these could be changed even if tag isn't
-				WRITEINT32(save_p, ss->firsttag);
-				WRITEINT32(save_p, ss->nexttag);
+			if (diff2 & SD_TAG)
+			{
+				WRITEUINT32(save_p, ss->tags.count);
+				for (j = 0; j < ss->tags.count; j++)
+					WRITEINT16(save_p, ss->tags.tags[j]);
 			}
 
 			if (diff3 & SD_COLORMAP)
@@ -1025,7 +1032,7 @@ static void ArchiveSectors(void)
 
 static void UnArchiveSectors(void)
 {
-	UINT16 i;
+	UINT16 i, j;
 	UINT8 diff, diff2, diff3;
 	for (;;)
 	{
@@ -1079,13 +1086,29 @@ static void UnArchiveSectors(void)
 		if (diff2 & SD_CEILANG)
 			sectors[i].ceilingpic_angle = READANGLE(save_p);
 		if (diff2 & SD_TAG)
-			sectors[i].tag = READINT16(save_p); // DON'T use P_ChangeSectorTag
-		if (diff3 & SD_TAGLIST)
 		{
-			sectors[i].firsttag = READINT32(save_p);
-			sectors[i].nexttag = READINT32(save_p);
+			size_t ncount = READUINT32(save_p);
+
+			// Remove entries from global lists.
+			for (j = 0; j < sectors[i].tags.count; j++)
+				Taggroup_Remove(tags_sectors, sectors[i].tags.tags[j], i);
+
+			// Reallocate if size differs.
+			if (ncount != sectors[i].tags.count)
+			{
+				sectors[i].tags.count = ncount;
+				sectors[i].tags.tags = Z_Realloc(sectors[i].tags.tags, ncount*sizeof(mtag_t), PU_LEVEL, NULL);
+			}
+
+			for (j = 0; j < ncount; j++)
+				sectors[i].tags.tags[j] = READINT16(save_p);
+
+			// Add new entries.
+			for (j = 0; j < sectors[i].tags.count; j++)
+				Taggroup_Remove(tags_sectors, sectors[i].tags.tags[j], i);
 		}
 
+
 		if (diff3 & SD_COLORMAP)
 			sectors[i].extra_colormap = GetNetColormapFromList(READUINT32(save_p));
 		if (diff3 & SD_CRUMBLESTATE)
@@ -1394,6 +1417,13 @@ typedef enum
 	MD2_MIRRORED     = 1<<13,
 	MD2_ROLLANGLE    = 1<<14,
 	MD2_SHADOWSCALE  = 1<<15,
+	MD2_RENDERFLAGS  = 1<<16,
+	MD2_BLENDMODE    = 1<<17,
+	MD2_SPRITEXSCALE = 1<<18,
+	MD2_SPRITEYSCALE = 1<<19,
+	MD2_SPRITEXOFFSET = 1<<20,
+	MD2_SPRITEYOFFSET = 1<<21,
+	MD2_FLOORSPRITESLOPE = 1<<22,
 } mobj_diff2_t;
 
 typedef enum
@@ -1606,6 +1636,27 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 		diff2 |= MD2_ROLLANGLE;
 	if (mobj->shadowscale)
 		diff2 |= MD2_SHADOWSCALE;
+	if (mobj->renderflags)
+		diff2 |= MD2_RENDERFLAGS;
+	if (mobj->renderflags)
+		diff2 |= MD2_BLENDMODE;
+	if (mobj->spritexscale != FRACUNIT)
+		diff2 |= MD2_SPRITEXSCALE;
+	if (mobj->spriteyscale != FRACUNIT)
+		diff2 |= MD2_SPRITEYSCALE;
+	if (mobj->spritexoffset)
+		diff2 |= MD2_SPRITEXOFFSET;
+	if (mobj->floorspriteslope)
+	{
+		pslope_t *slope = mobj->floorspriteslope;
+		if (slope->zangle || slope->zdelta || slope->xydirection
+		|| slope->o.x || slope->o.y || slope->o.z
+		|| slope->d.x || slope->d.y
+		|| slope->normal.x || slope->normal.y
+		|| (slope->normal.z != FRACUNIT))
+			diff2 |= MD2_FLOORSPRITESLOPE;
+	}
+
 	if (diff2 != 0)
 		diff |= MD_MORE;
 
@@ -1748,6 +1799,37 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 		WRITEANGLE(save_p, mobj->rollangle);
 	if (diff2 & MD2_SHADOWSCALE)
 		WRITEFIXED(save_p, mobj->shadowscale);
+	if (diff2 & MD2_RENDERFLAGS)
+		WRITEUINT32(save_p, mobj->renderflags);
+	if (diff2 & MD2_BLENDMODE)
+		WRITEINT32(save_p, mobj->blendmode);
+	if (diff2 & MD2_SPRITEXSCALE)
+		WRITEFIXED(save_p, mobj->spritexscale);
+	if (diff2 & MD2_SPRITEYSCALE)
+		WRITEFIXED(save_p, mobj->spriteyscale);
+	if (diff2 & MD2_SPRITEXOFFSET)
+		WRITEFIXED(save_p, mobj->spritexoffset);
+	if (diff2 & MD2_SPRITEYOFFSET)
+		WRITEFIXED(save_p, mobj->spriteyoffset);
+	if (diff2 & MD2_FLOORSPRITESLOPE)
+	{
+		pslope_t *slope = mobj->floorspriteslope;
+
+		WRITEFIXED(save_p, slope->zdelta);
+		WRITEANGLE(save_p, slope->zangle);
+		WRITEANGLE(save_p, slope->xydirection);
+
+		WRITEFIXED(save_p, slope->o.x);
+		WRITEFIXED(save_p, slope->o.y);
+		WRITEFIXED(save_p, slope->o.z);
+
+		WRITEFIXED(save_p, slope->d.x);
+		WRITEFIXED(save_p, slope->d.y);
+
+		WRITEFIXED(save_p, slope->normal.x);
+		WRITEFIXED(save_p, slope->normal.y);
+		WRITEFIXED(save_p, slope->normal.z);
+	}
 
 	WRITEUINT32(save_p, mobj->mobjnum);
 }
@@ -2757,6 +2839,37 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 		mobj->rollangle = READANGLE(save_p);
 	if (diff2 & MD2_SHADOWSCALE)
 		mobj->shadowscale = READFIXED(save_p);
+	if (diff2 & MD2_RENDERFLAGS)
+		mobj->renderflags = READUINT32(save_p);
+	if (diff2 & MD2_BLENDMODE)
+		mobj->blendmode = READINT32(save_p);
+	if (diff2 & MD2_SPRITEXSCALE)
+		mobj->spritexscale = READFIXED(save_p);
+	if (diff2 & MD2_SPRITEYSCALE)
+		mobj->spriteyscale = READFIXED(save_p);
+	if (diff2 & MD2_SPRITEXOFFSET)
+		mobj->spritexoffset = READFIXED(save_p);
+	if (diff2 & MD2_SPRITEYOFFSET)
+		mobj->spriteyoffset = READFIXED(save_p);
+	if (diff2 & MD2_FLOORSPRITESLOPE)
+	{
+		pslope_t *slope = (pslope_t *)P_CreateFloorSpriteSlope(mobj);
+
+		slope->zdelta = READFIXED(save_p);
+		slope->zangle = READANGLE(save_p);
+		slope->xydirection = READANGLE(save_p);
+
+		slope->o.x = READFIXED(save_p);
+		slope->o.y = READFIXED(save_p);
+		slope->o.z = READFIXED(save_p);
+
+		slope->d.x = READFIXED(save_p);
+		slope->d.y = READFIXED(save_p);
+
+		slope->normal.x = READFIXED(save_p);
+		slope->normal.y = READFIXED(save_p);
+		slope->normal.z = READFIXED(save_p);
+	}
 
 	if (diff & MD_REDFLAG)
 	{
@@ -3951,14 +4064,17 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride)
 	playeringame[consoleplayer] = true;
 }
 
-static void P_NetArchiveMisc(void)
+static void P_NetArchiveMisc(boolean resending)
 {
 	INT32 i;
 
 	WRITEUINT32(save_p, ARCHIVEBLOCK_MISC);
 
+	if (resending)
+		WRITEUINT32(save_p, gametic);
 	WRITEINT16(save_p, gamemap);
 	WRITEINT16(save_p, gamestate);
+	WRITEINT16(save_p, gametype);
 
 	{
 		UINT32 pig = 0;
@@ -4021,13 +4137,16 @@ static void P_NetArchiveMisc(void)
 		WRITEUINT8(save_p, 0x2e);
 }
 
-static inline boolean P_NetUnArchiveMisc(void)
+static inline boolean P_NetUnArchiveMisc(boolean reloading)
 {
 	INT32 i;
 
 	if (READUINT32(save_p) != ARCHIVEBLOCK_MISC)
 		I_Error("Bad $$$.sav at archive block Misc");
 
+	if (reloading)
+		gametic = READUINT32(save_p);
+
 	gamemap = READINT16(save_p);
 
 	// gamemap changed; we assume that its map header is always valid,
@@ -4041,6 +4160,8 @@ static inline boolean P_NetUnArchiveMisc(void)
 
 	G_SetGamestate(READINT16(save_p));
 
+	gametype = READINT16(save_p);
+
 	{
 		UINT32 pig = READUINT32(save_p);
 		for (i = 0; i < MAXPLAYERS; i++)
@@ -4054,7 +4175,7 @@ static inline boolean P_NetUnArchiveMisc(void)
 
 	tokenlist = READUINT32(save_p);
 
-	if (!P_LoadLevel(true))
+	if (!P_LoadLevel(true, reloading))
 		return false;
 
 	// get the time
@@ -4153,14 +4274,14 @@ void P_SaveGame(INT16 mapnum)
 	P_ArchiveLuabanksAndConsistency();
 }
 
-void P_SaveNetGame(void)
+void P_SaveNetGame(boolean resending)
 {
 	thinker_t *th;
 	mobj_t *mobj;
 	INT32 i = 1; // don't start from 0, it'd be confused with a blank pointer otherwise
 
 	CV_SaveNetVars(&save_p);
-	P_NetArchiveMisc();
+	P_NetArchiveMisc(resending);
 
 	// Assign the mobjnumber for pointer tracking
 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
@@ -4208,10 +4329,10 @@ boolean P_LoadGame(INT16 mapoverride)
 	return true;
 }
 
-boolean P_LoadNetGame(void)
+boolean P_LoadNetGame(boolean reloading)
 {
 	CV_LoadNetVars(&save_p);
-	if (!P_NetUnArchiveMisc())
+	if (!P_NetUnArchiveMisc(reloading))
 		return false;
 	P_NetUnArchivePlayers();
 	if (gamestate == GS_LEVEL)
diff --git a/src/p_saveg.h b/src/p_saveg.h
index d8756a7a9b955e4520e46849c3d76313329b1623..be98953eb232198e88fb757840f9e0021fa48f2d 100644
--- a/src/p_saveg.h
+++ b/src/p_saveg.h
@@ -22,9 +22,9 @@
 // These are the load / save game routines.
 
 void P_SaveGame(INT16 mapnum);
-void P_SaveNetGame(void);
+void P_SaveNetGame(boolean resending);
 boolean P_LoadGame(INT16 mapoverride);
-boolean P_LoadNetGame(void);
+boolean P_LoadNetGame(boolean reloading);
 
 mobj_t *P_FindNewPosition(UINT32 oldposition);
 
diff --git a/src/p_setup.c b/src/p_setup.c
index 8e09c34df9fa97fda3dea95b99b6abf05440d761..cfee0500971ad334d8ee7ade1a482687305718d4 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -29,6 +29,7 @@
 #include "r_data.h"
 #include "r_things.h" // for R_AddSpriteDefs
 #include "r_textures.h"
+#include "r_patch.h"
 #include "r_picformats.h"
 #include "r_sky.h"
 #include "r_draw.h"
@@ -82,6 +83,8 @@
 
 #include "fastcmp.h" // textmap parsing
 
+#include "taglist.h"
+
 //
 // Map MD5, calculated on level load.
 // Sent to clients in PT_SERVERINFO.
@@ -548,7 +551,7 @@ Ploadflat (levelflat_t *levelflat, const char *flatname, boolean resize)
 
 	lumpnum_t    flatnum;
 	int       texturenum;
-	patch_t   *flatpatch;
+	UINT8     *flatpatch;
 	size_t    lumplength;
 
 	size_t i;
@@ -608,7 +611,7 @@ flatfound:
 		/* This could be a flat, patch, or PNG. */
 		flatpatch = W_CacheLumpNum(flatnum, PU_CACHE);
 		lumplength = W_LumpLength(flatnum);
-		if (Picture_CheckIfPatch(flatpatch, lumplength))
+		if (Picture_CheckIfDoomPatch((softwarepatch_t *)flatpatch, lumplength))
 			levelflat->type = LEVELFLAT_PATCH;
 		else
 		{
@@ -904,16 +907,13 @@ static void P_SpawnMapThings(boolean spawnemblems)
 }
 
 // Experimental groovy write function!
-void P_WriteThings(lumpnum_t lumpnum)
+void P_WriteThings(void)
 {
 	size_t i, length;
 	mapthing_t *mt;
-	UINT8 *data;
 	UINT8 *savebuffer, *savebuf_p;
 	INT16 temp;
 
-	data = W_CacheLumpNum(lumpnum, PU_LEVEL);
-
 	savebuf_p = savebuffer = (UINT8 *)malloc(nummapthings * sizeof (mapthing_t));
 
 	if (!savebuf_p)
@@ -935,8 +935,6 @@ void P_WriteThings(lumpnum_t lumpnum)
 		WRITEUINT16(savebuf_p, mt->options);
 	}
 
-	Z_Free(data);
-
 	length = savebuf_p - savebuffer;
 
 	FIL_WriteFile(va("newthings%d.lmp", gamemap), savebuffer, length);
@@ -968,8 +966,6 @@ static void P_LoadVertices(UINT8 *data)
 
 static void P_InitializeSector(sector_t *ss)
 {
-	ss->nexttag = ss->firsttag = -1;
-
 	memset(&ss->soundorg, 0, sizeof(ss->soundorg));
 
 	ss->validcount = 0;
@@ -1040,7 +1036,7 @@ static void P_LoadSectors(UINT8 *data)
 
 		ss->lightlevel = SHORT(ms->lightlevel);
 		ss->special = SHORT(ms->special);
-		ss->tag = SHORT(ms->tag);
+		Tag_FSet(&ss->tags, SHORT(ms->tag));
 
 		ss->floor_xoffs = ss->floor_yoffs = 0;
 		ss->ceiling_xoffs = ss->ceiling_yoffs = 0;
@@ -1079,10 +1075,6 @@ static void P_InitializeLinedef(line_t *ld)
 	ld->frontsector = ld->backsector = NULL;
 
 	ld->validcount = 0;
-#ifdef WALLSPLATS
-	ld->splats = NULL;
-#endif
-	ld->firsttag = ld->nexttag = -1;
 	ld->polyobj = NULL;
 
 	ld->text = NULL;
@@ -1154,7 +1146,7 @@ static void P_LoadLinedefs(UINT8 *data)
 	{
 		ld->flags = SHORT(mld->flags);
 		ld->special = SHORT(mld->special);
-		ld->tag = SHORT(mld->tag);
+		Tag_FSet(&ld->tags, SHORT(mld->tag));
 		memset(ld->args, 0, NUMLINEARGS*sizeof(*ld->args));
 		memset(ld->stringargs, 0x00, NUMLINESTRINGARGS*sizeof(*ld->stringargs));
 		ld->alpha = FRACUNIT;
@@ -1347,7 +1339,7 @@ static void P_LoadSidedefs(UINT8 *data)
 						|| (msd->toptexture[0] >= 'A' && msd->toptexture[0] <= 'F'))
 						sd->toptexture = axtoi(msd->toptexture);
 					else
-						I_Error("Custom FOF (tag %d) needs a value in the linedef's back side upper texture field.", sd->line->tag);
+						I_Error("Custom FOF (line id %s) needs a value in the linedef's back side upper texture field.", sizeu1(sd->line - lines));
 
 					sd->midtexture = R_TextureNumForName(msd->midtexture);
 					sd->bottomtexture = R_TextureNumForName(msd->bottomtexture);
@@ -1387,8 +1379,8 @@ static void P_LoadThings(UINT8 *data)
 		mt->type = READUINT16(data);
 		mt->options = READUINT16(data);
 		mt->extrainfo = (UINT8)(mt->type >> 12);
+		Tag_FSet(&mt->tags, 0);
 		mt->scale = FRACUNIT;
-		mt->tag = 0;
 		memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args));
 		memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs));
 		mt->pitch = mt->roll = 0;
@@ -1524,7 +1516,17 @@ static void ParseTextmapSectorParameter(UINT32 i, char *param, char *val)
 	else if (fastcmp(param, "special"))
 		sectors[i].special = atol(val);
 	else if (fastcmp(param, "id"))
-		sectors[i].tag = atol(val);
+		Tag_FSet(&sectors[i].tags, atol(val));
+	else if (fastcmp(param, "moreids"))
+	{
+		char* id = val;
+		while (id)
+		{
+			Tag_Add(&sectors[i].tags, atol(id));
+			if ((id = strchr(id, ' ')))
+				id++;
+		}
+	}
 	else if (fastcmp(param, "xpanningfloor"))
 		sectors[i].floor_xoffs = FLOAT_TO_FIXED(atof(val));
 	else if (fastcmp(param, "ypanningfloor"))
@@ -1602,7 +1604,17 @@ static void ParseTextmapSidedefParameter(UINT32 i, char *param, char *val)
 static void ParseTextmapLinedefParameter(UINT32 i, char *param, char *val)
 {
 	if (fastcmp(param, "id"))
-		lines[i].tag = atol(val);
+		Tag_FSet(&lines[i].tags, atol(val));
+	else if (fastcmp(param, "moreids"))
+	{
+		char* id = val;
+		while (id)
+		{
+			Tag_Add(&lines[i].tags, atol(id));
+			if ((id = strchr(id, ' ')))
+				id++;
+		}
+	}
 	else if (fastcmp(param, "special"))
 		lines[i].special = atol(val);
 	else if (fastcmp(param, "v1"))
@@ -1671,8 +1683,18 @@ static void ParseTextmapLinedefParameter(UINT32 i, char *param, char *val)
 static void ParseTextmapThingParameter(UINT32 i, char *param, char *val)
 {
 	if (fastcmp(param, "id"))
-		mapthings[i].tag = atol(val);
-	if (fastcmp(param, "x"))
+		Tag_FSet(&mapthings[i].tags, atol(val));
+	else if (fastcmp(param, "moreids"))
+	{
+		char* id = val;
+		while (id)
+		{
+			Tag_Add(&mapthings[i].tags, atol(id));
+			if ((id = strchr(id, ' ')))
+				id++;
+		}
+	}
+	else if (fastcmp(param, "x"))
 		mapthings[i].x = atol(val);
 	else if (fastcmp(param, "y"))
 		mapthings[i].y = atol(val);
@@ -1829,7 +1851,7 @@ static void P_LoadTextmap(void)
 		sc->lightlevel = 255;
 
 		sc->special = 0;
-		sc->tag = 0;
+		Tag_FSet(&sc->tags, 0);
 
 		sc->floor_xoffs = sc->floor_yoffs = 0;
 		sc->ceiling_xoffs = sc->ceiling_yoffs = 0;
@@ -1847,6 +1869,7 @@ static void P_LoadTextmap(void)
 		textmap_colormap.fadeend = 31;
 		textmap_colormap.flags = 0;
 		TextmapParse(sectorsPos[i], i, ParseTextmapSectorParameter);
+
 		P_InitializeSector(sc);
 		if (textmap_colormap.used)
 		{
@@ -1863,7 +1886,8 @@ static void P_LoadTextmap(void)
 		ld->v1 = ld->v2 = NULL;
 		ld->flags = 0;
 		ld->special = 0;
-		ld->tag = 0;
+		Tag_FSet(&ld->tags, 0);
+
 		memset(ld->args, 0, NUMLINEARGS*sizeof(*ld->args));
 		memset(ld->stringargs, 0x00, NUMLINESTRINGARGS*sizeof(*ld->stringargs));
 		ld->alpha = FRACUNIT;
@@ -1911,8 +1935,8 @@ static void P_LoadTextmap(void)
 		mt->options = 0;
 		mt->z = 0;
 		mt->extrainfo = 0;
+		Tag_FSet(&mt->tags, 0);
 		mt->scale = FRACUNIT;
-		mt->tag = 0;
 		memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args));
 		memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs));
 		mt->mobj = NULL;
@@ -2080,9 +2104,6 @@ static boolean P_LoadMapData(const virtres_t *virt)
 static void P_InitializeSubsector(subsector_t *ss)
 {
 	ss->sector = NULL;
-#ifdef FLOORSPLATS
-	ss->splats = NULL;
-#endif
 	ss->validcount = 0;
 }
 
@@ -2127,7 +2148,7 @@ static void P_LoadNodes(UINT8 *data)
   * \param seg Seg to compute length for.
   * \return Length in fracunits.
   */
-fixed_t P_SegLength(seg_t *seg)
+static fixed_t P_SegLength(seg_t *seg)
 {
 	INT64 dx = (seg->v2->x - seg->v1->x)>>1;
 	INT64 dy = (seg->v2->y - seg->v1->y)>>1;
@@ -2929,30 +2950,6 @@ static void P_LinkMapData(void)
 	}
 }
 
-/** Hashes the sector tags across the sectors and linedefs.
-  *
-  * \sa P_FindSectorFromTag, P_ChangeSectorTag
-  * \author Lee Killough
-  */
-static inline void P_InitTagLists(void)
-{
-	register size_t i;
-
-	for (i = numsectors - 1; i != (size_t)-1; i--)
-	{
-		size_t j = (unsigned)sectors[i].tag % numsectors;
-		sectors[i].nexttag = sectors[j].firsttag;
-		sectors[j].firsttag = (INT32)i;
-	}
-
-	for (i = numlines - 1; i != (size_t)-1; i--)
-	{
-		size_t j = (unsigned)lines[i].tag % numlines;
-		lines[i].nexttag = lines[j].firsttag;
-		lines[j].firsttag = (INT32)i;
-	}
-}
-
 //For maps in binary format, converts setup of specials to UDMF format.
 static void P_ConvertBinaryMap(void)
 {
@@ -2960,14 +2957,28 @@ static void P_ConvertBinaryMap(void)
 
 	for (i = 0; i < numlines; i++)
 	{
+		mtag_t tag = Tag_FGet(&lines[i].tags);
+
 		switch (lines[i].special)
 		{
 		case 20: //PolyObject first line
 		{
-			INT32 paramline = P_FindSpecialLineFromTag(22, lines[i].tag, -1);
+			INT32 check = -1;
+			INT32 paramline = -1;
+
+			TAG_ITER_DECLARECOUNTER(0);
+
+			TAG_ITER_LINES(0, tag, check)
+			{
+				if (lines[check].special == 22)
+				{
+					paramline = check;
+					break;
+				}
+			}
 
 			//PolyObject ID
-			lines[i].args[0] = lines[i].tag;
+			lines[i].args[0] = tag;
 
 			//Default: Invisible planes
 			lines[i].args[3] |= TMPF_INVISIBLEPLANES;
@@ -3013,7 +3024,7 @@ static void P_ConvertBinaryMap(void)
 				CONS_Alert(CONS_WARNING, "Linedef %s is missing the hook name of the Lua function to call! (This should be given in the front texture fields)\n", sizeu1(i));
 			break;
 		case 447: //Change colormap
-			lines[i].args[0] = lines[i].tag;
+			lines[i].args[0] = Tag_FGet(&lines[i].tags);
 			if (lines[i].flags & ML_EFFECT3)
 				lines[i].args[2] |= TMCF_RELATIVE;
 			if (lines[i].flags & ML_EFFECT1)
@@ -3029,7 +3040,7 @@ static void P_ConvertBinaryMap(void)
 				abs(sides[lines[i].sidenum[1]].rowoffset >> FRACBITS)
 				: abs(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS));
 
-			lines[i].args[0] = lines[i].tag;
+			lines[i].args[0] = Tag_FGet(&lines[i].tags);
 			if (lines[i].flags & ML_EFFECT4)
 				lines[i].args[2] = speed;
 			else
@@ -3049,10 +3060,10 @@ static void P_ConvertBinaryMap(void)
 			break;
 		}
 		case 456: //Stop fading colormap
-			lines[i].args[0] = lines[i].tag;
+			lines[i].args[0] = Tag_FGet(&lines[i].tags);
 			break;
 		case 606: //Colormap
-			lines[i].args[0] = lines[i].tag;
+			lines[i].args[0] = Tag_FGet(&lines[i].tags);
 			break;
 		case 700: //Slope front sector floor
 		case 701: //Slope front sector ceiling
@@ -3093,7 +3104,7 @@ static void P_ConvertBinaryMap(void)
 			else if (lines[i].special == 715)
 				lines[i].args[0] = TMSP_BACKCEILING;
 
-			lines[i].args[1] = lines[i].tag;
+			lines[i].args[1] = tag;
 
 			if (lines[i].flags & ML_EFFECT6)
 			{
@@ -3125,9 +3136,9 @@ static void P_ConvertBinaryMap(void)
 		case 721: //Copy front side ceiling slope
 		case 722: //Copy front side floor and ceiling slope
 			if (lines[i].special != 721)
-				lines[i].args[0] = lines[i].tag;
+				lines[i].args[0] = tag;
 			if (lines[i].special != 720)
-				lines[i].args[1] = lines[i].tag;
+				lines[i].args[1] = tag;
 			lines[i].special = 720;
 			break;
 		case 900: //Translucent wall (10%)
@@ -3160,21 +3171,39 @@ static void P_ConvertBinaryMap(void)
 		switch (mapthings[i].type)
 		{
 		case 750:
+			Tag_Add(&mapthings[i].tags, mapthings[i].angle);
+			break;
 		case 760:
 		case 761:
-			mapthings[i].tag = mapthings[i].angle;
+			Tag_FSet(&mapthings[i].tags, mapthings[i].angle);
 			break;
 		case 762:
 		{
-			INT32 firstline = P_FindSpecialLineFromTag(20, mapthings[i].angle, -1);
+			INT32 check = -1;
+			INT32 firstline = -1;
+			mtag_t tag = mapthings[i].angle;
+
+			TAG_ITER_DECLARECOUNTER(0);
+
+			Tag_FSet(&mapthings[i].tags, tag);
+
+			TAG_ITER_LINES(0, tag, check)
+			{
+				if (lines[check].special == 20)
+				{
+					firstline = check;
+					break;
+				}
+			}
+
 			if (firstline != -1)
 				lines[firstline].args[3] |= TMPF_CRUSH;
-			mapthings[i].tag = mapthings[i].angle;
+
 			mapthings[i].type = 761;
 			break;
 		}
 		case 780:
-			mapthings[i].tag = mapthings[i].extrainfo;
+			Tag_FSet(&mapthings[i].tags, mapthings[i].extrainfo);
 			break;
 		default:
 			break;
@@ -3248,6 +3277,7 @@ static boolean P_LoadMapFromFile(void)
 {
 	virtres_t *virt = vres_GetMap(lastloadedmaplumpnum);
 	virtlump_t *textmap = vres_Find(virt, "TEXTMAP");
+	size_t i;
 	udmf = textmap != NULL;
 
 	if (!P_LoadMapData(virt))
@@ -3257,7 +3287,7 @@ static boolean P_LoadMapFromFile(void)
 
 	P_LinkMapData();
 
-	P_InitTagLists();   // Create xref tables for tags
+	Taglist_InitGlobalTables();
 
 	if (!udmf)
 		P_ConvertBinaryMap();
@@ -3271,6 +3301,10 @@ static boolean P_LoadMapFromFile(void)
 	memcpy(spawnlines, lines, numlines * sizeof(*lines));
 	memcpy(spawnsides, sides, numsides * sizeof(*sides));
 
+	for (i = 0; i < numsectors; i++)
+		if (sectors[i].tags.count)
+			spawnsectors[i].tags.tags = memcpy(Z_Malloc(sectors[i].tags.count*sizeof(mtag_t), PU_LEVEL, NULL), sectors[i].tags.tags, sectors[i].tags.count*sizeof(mtag_t));
+
 	P_MakeMapMD5(virt, &mapmd5);
 
 	vres_Free(virt);
@@ -3319,8 +3353,6 @@ static void P_InitLevelSettings(void)
 
 	leveltime = 0;
 
-	localaiming = 0;
-	localaiming2 = 0;
 	modulothing = 0;
 
 	// special stage tokens, emeralds, and ring total
@@ -3435,6 +3467,9 @@ void P_RespawnThings(void)
 
 	P_InitLevelSettings();
 
+	localaiming = 0;
+	localaiming2 = 0;
+
 	P_SpawnMapThings(true);
 
 	// restore skybox viewpoint/centerpoint if necessary, set them to defaults if we can't do that
@@ -3942,7 +3977,7 @@ static void P_InitGametype(void)
   * \param fromnetsave If true, skip some stuff because we're loading a netgame snapshot.
   * \todo Clean up, refactor, split up; get rid of the bloat.
   */
-boolean P_LoadLevel(boolean fromnetsave)
+boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
 {
 	// use gamemap to get map number.
 	// 99% of the things already did, so.
@@ -4012,7 +4047,10 @@ boolean P_LoadLevel(boolean fromnetsave)
 	players[consoleplayer].viewz = 1;
 
 	// Cancel all d_main.c fadeouts (keep fade in though).
-	wipegamestate = FORCEWIPEOFF;
+	if (reloadinggamestate)
+		wipegamestate = gamestate; // Don't fade if reloading the gamestate
+	else
+		wipegamestate = FORCEWIPEOFF;
 	wipestyleflags = 0;
 
 	// Special stage & record attack retry fade to white
@@ -4038,18 +4076,20 @@ boolean P_LoadLevel(boolean fromnetsave)
 
 	// Fade out music here. Deduct 2 tics so the fade volume actually reaches 0.
 	// But don't halt the music! S_Start will take care of that. This dodges a MIDI crash bug.
-	if (!titlemapinaction && (RESETMUSIC ||
+	if (!(reloadinggamestate || titlemapinaction) && (RESETMUSIC ||
 		strnicmp(S_MusicName(),
 			(mapmusflags & MUSIC_RELOADRESET) ? mapheaderinfo[gamemap-1]->musname : mapmusname, 7)))
+	{
 		S_FadeMusic(0, FixedMul(
 			FixedDiv((F_GetWipeLength(wipedefs[wipe_level_toblack])-2)*NEWTICRATERATIO, NEWTICRATE), MUSICRATE));
+	}
 
 	// Let's fade to black here
 	// But only if we didn't do the special stage wipe
-	if (rendermode != render_none && !ranspecialwipe)
+	if (rendermode != render_none && !(ranspecialwipe || reloadinggamestate))
 		P_RunLevelWipe();
 
-	if (!titlemapinaction)
+	if (!(reloadinggamestate || titlemapinaction))
 	{
 		if (ranspecialwipe == 2)
 		{
@@ -4093,13 +4133,10 @@ boolean P_LoadLevel(boolean fromnetsave)
 	// Clear pointers that would be left dangling by the purge
 	R_FlushTranslationColormapCache();
 
+	Patch_FreeTag(PU_PATCH_LOWPRIORITY);
+	Patch_FreeTag(PU_PATCH_ROTATED);
 	Z_FreeTags(PU_LEVEL, PU_PURGELEVEL - 1);
 
-#if defined (WALLSPLATS) || defined (FLOORSPLATS)
-	// clear the splats from previous level
-	R_ClearLevelSplats();
-#endif
-
 	P_InitThinkers();
 	P_InitCachedActions();
 
@@ -4156,14 +4193,14 @@ boolean P_LoadLevel(boolean fromnetsave)
 		P_SpawnPrecipitation();
 
 #ifdef HWRENDER // not win32 only 19990829 by Kin
+	gl_maploaded = false;
+
 	// Lactozilla: Free extrasubsectors regardless of renderer.
-	// Maybe we're not in OpenGL anymore.
-	if (extrasubsectors)
-		free(extrasubsectors);
-	extrasubsectors = NULL;
-	// stuff like HWR_CreatePlanePolygons is called there
+	HWR_FreeExtraSubsectors();
+
+	// Create plane polygons.
 	if (rendermode == render_opengl)
-		HWR_SetupLevel();
+		HWR_LoadLevel();
 #endif
 
 	// oh god I hope this helps
@@ -4174,7 +4211,12 @@ boolean P_LoadLevel(boolean fromnetsave)
 	if (!fromnetsave)
 		P_InitGametype();
 
-	P_InitCamera();
+	if (!reloadinggamestate)
+	{
+		P_InitCamera();
+		localaiming = 0;
+		localaiming2 = 0;
+	}
 
 	// clear special respawning que
 	iquehead = iquetail = 0;
@@ -4182,7 +4224,7 @@ boolean P_LoadLevel(boolean fromnetsave)
 	P_MapEnd();
 
 	// Remove the loading shit from the screen
-	if (rendermode != render_none && !titlemapinaction)
+	if (rendermode != render_none && !(titlemapinaction || reloadinggamestate))
 		F_WipeColorFill(levelfadecol);
 
 	if (precache || dedicated)
@@ -4226,8 +4268,8 @@ boolean P_LoadLevel(boolean fromnetsave)
 		LUAh_MapLoad();
 	}
 
-	// No render mode, stop here.
-	if (rendermode == render_none)
+	// No render mode or reloading gamestate, stop here.
+	if (rendermode == render_none || reloadinggamestate)
 		return true;
 
 	// Title card!
@@ -4245,33 +4287,6 @@ boolean P_LoadLevel(boolean fromnetsave)
 	return true;
 }
 
-#ifdef HWRENDER
-void HWR_SetupLevel(void)
-{
-	// Lactozilla (December 8, 2019)
-	// Level setup used to free EVERY mipmap from memory.
-	// Even mipmaps that aren't related to level textures.
-	// Presumably, the hardware render code used to store textures as level data.
-	// Meaning, they had memory allocated and marked with the PU_LEVEL tag.
-	// Level textures are only reloaded after R_LoadTextures, which is
-	// when the texture list is loaded.
-
-	// Sal: Unfortunately, NOT freeing them causes the dreaded Color Bug.
-	HWR_FreeMipmapCache();
-
-#ifdef ALAM_LIGHTING
-	// BP: reset light between levels (we draw preview frame lights on current frame)
-	HWR_ResetLights();
-#endif
-
-	HWR_CreatePlanePolygons((INT32)numnodes - 1);
-
-	// Build the sky dome
-	HWR_ClearSkyDome();
-	HWR_BuildSkyDome();
-}
-#endif
-
 //
 // P_RunSOC
 //
@@ -4481,6 +4496,8 @@ boolean P_AddWadFile(const char *wadfilename)
 	//
 	// search for sprite replacements
 	//
+	Patch_FreeTag(PU_SPRITE);
+	Patch_FreeTag(PU_PATCH_ROTATED);
 	R_AddSpriteDefs(wadnum);
 
 	// Reload it all anyway, just in case they
diff --git a/src/p_setup.h b/src/p_setup.h
index ef903e103725194d058e0a8bfac1e6a929c7373b..34de9c93da1c4a91f7c46cc25af8107136df530e 100644
--- a/src/p_setup.h
+++ b/src/p_setup.h
@@ -97,15 +97,15 @@ void P_SetupLevelSky(INT32 skynum, boolean global);
 void P_ScanThings(INT16 mapnum, INT16 wadnum, INT16 lumpnum);
 #endif
 void P_RespawnThings(void);
-boolean P_LoadLevel(boolean fromnetsave);
+boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate);
 #ifdef HWRENDER
-void HWR_SetupLevel(void);
+void HWR_LoadLevel(void);
 #endif
 boolean P_AddWadFile(const char *wadfilename);
 boolean P_RunSOC(const char *socfilename);
 void P_LoadSoundsRange(UINT16 wadnum, UINT16 first, UINT16 num);
 void P_LoadMusicsRange(UINT16 wadnum, UINT16 first, UINT16 num);
-void P_WriteThings(lumpnum_t lump);
+void P_WriteThings(void);
 size_t P_PrecacheLevelFlats(void);
 void P_AllocMapHeader(INT16 i);
 
diff --git a/src/p_slopes.c b/src/p_slopes.c
index e93b0f6c993650043a1afe588c21aa1b64bbac37..aa46a84024d459e2c3dab0164d4122b59f30b126 100644
--- a/src/p_slopes.c
+++ b/src/p_slopes.c
@@ -139,7 +139,7 @@ void T_DynamicSlopeVert (dynplanethink_t* th)
 	INT32 l;
 
 	for (i = 0; i < 3; i++) {
-		l = P_FindSpecialLineFromTag(799, th->tags[i], -1);
+		l = Tag_FindLineSpecial(799, th->tags[i]);
 		if (l != -1) {
 			th->vex[i].z = lines[l].frontsector->floorheight;
 		}
@@ -405,9 +405,6 @@ static void line_SpawnViaLine(const int linenum, const boolean spawnthinker)
 				P_AddDynSlopeThinker(cslope, DP_BACKCEIL, line, extent, NULL, NULL);
 		}
 	}
-
-	if(!line->tag)
-		return;
 }
 
 /// Creates a new slope from three mapthings with the specified IDs
@@ -426,11 +423,11 @@ static pslope_t *MakeViaMapthings(INT16 tag1, INT16 tag2, INT16 tag3, UINT8 flag
 		if (mt->type != 750) // Haha, I'm hijacking the old Chaos Spawn thingtype for something!
 			continue;
 
-		if (!vertices[0] && mt->tag == tag1)
+		if (!vertices[0] && Tag_Find(&mt->tags, tag1))
 			vertices[0] = mt;
-		else if (!vertices[1] && mt->tag == tag2)
+		else if (!vertices[1] && Tag_Find(&mt->tags, tag2))
 			vertices[1] = mt;
-		else if (!vertices[2] && mt->tag == tag3)
+		else if (!vertices[2] && Tag_Find(&mt->tags, tag3))
 			vertices[2] = mt;
 	}
 
@@ -549,11 +546,11 @@ static boolean P_SetSlopeFromTag(sector_t *sec, INT32 tag, boolean ceiling)
 {
 	INT32 i;
 	pslope_t **secslope = ceiling ? &sec->c_slope : &sec->f_slope;
+	TAG_ITER_DECLARECOUNTER(0);
 
 	if (!tag || *secslope)
 		return false;
-
-	for (i = -1; (i = P_FindSectorFromTag(tag, i)) >= 0;)
+	TAG_ITER_SECTORS(0, tag, i)
 	{
 		pslope_t *srcslope = ceiling ? sectors[i].c_slope : sectors[i].f_slope;
 		if (srcslope)
diff --git a/src/p_spec.c b/src/p_spec.c
index 7a79bd494f951e8a0befbf2812a23b2326200624..a1afdd00ddb12c4c4253e4dfe4547a8e3391734d 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -988,99 +988,12 @@ static sector_t *P_FindModelCeilingSector(fixed_t ceildestheight, INT32 secnum)
 }
 #endif
 
-/** Searches the tag lists for the next sector with a given tag.
-  *
-  * \param tag   Tag number to look for.
-  * \param start -1 to start anew, or the result of a previous call to keep
-  *              searching.
-  * \return Number of the next tagged sector found.
-  */
-INT32 P_FindSectorFromTag(INT16 tag, INT32 start)
-{
-	if (tag == -1)
-	{
-		start++;
-
-		if (start >= (INT32)numsectors)
-			return -1;
-
-		return start;
-	}
-	else
-	{
-		start = start >= 0 ? sectors[start].nexttag :
-			sectors[(unsigned)tag % numsectors].firsttag;
-		while (start >= 0 && sectors[start].tag != tag)
-			start = sectors[start].nexttag;
-		return start;
-	}
-}
-
-/** Searches the tag lists for the next line with a given tag and special.
-  *
-  * \param tag     Tag number.
-  * \param start   -1 to start anew, or the result of a previous call to keep
-  *                searching.
-  * \return Number of next suitable line found.
-  * \author Graue <graue@oceanbase.org>
-  */
-static INT32 P_FindLineFromTag(INT32 tag, INT32 start)
-{
-	if (tag == -1)
-	{
-		start++;
-
-		if (start >= (INT32)numlines)
-			return -1;
-
-		return start;
-	}
-	else
-	{
-		start = start >= 0 ? lines[start].nexttag :
-			lines[(unsigned)tag % numlines].firsttag;
-		while (start >= 0 && lines[start].tag != tag)
-			start = lines[start].nexttag;
-		return start;
-	}
-}
-
-INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start)
-{
-	if (tag == -1)
-	{
-		start++;
-
-		// This redundant check stops the compiler from complaining about function expansion
-		// elsewhere for some reason and everything is awful
-		if (start >= (INT32)numlines)
-			return -1;
-
-		while (start < (INT32)numlines && lines[start].special != special)
-			start++;
-
-		if (start >= (INT32)numlines)
-			return -1;
-
-		return start;
-	}
-	else
-	{
-		start = start >= 0 ? lines[start].nexttag :
-			lines[(unsigned)tag % numlines].firsttag;
-		while (start >= 0 && (lines[start].tag != tag || lines[start].special != special))
-			start = lines[start].nexttag;
-		return start;
-	}
-}
-
-
 // Parses arguments for parameterized polyobject door types
 static boolean PolyDoor(line_t *line)
 {
 	polydoordata_t pdd;
 
-	pdd.polyObjNum = line->tag; // polyobject id
+	pdd.polyObjNum = Tag_FGet(&line->tags); // polyobject id
 
 	switch(line->special)
 	{
@@ -1117,7 +1030,7 @@ static boolean PolyMove(line_t *line)
 {
 	polymovedata_t pmd;
 
-	pmd.polyObjNum = line->tag;
+	pmd.polyObjNum = Tag_FGet(&line->tags);
 	pmd.speed      = sides[line->sidenum[0]].textureoffset / 8;
 	pmd.angle      = R_PointToAngle2(line->v1->x, line->v1->y, line->v2->x, line->v2->y);
 	pmd.distance   = sides[line->sidenum[0]].rowoffset;
@@ -1131,7 +1044,7 @@ static boolean PolyMove(line_t *line)
 // If NOCLIMB is ticked, the polyobject will still be tangible, just not visible.
 static void PolyInvisible(line_t *line)
 {
-	INT32 polyObjNum = line->tag;
+	INT32 polyObjNum = Tag_FGet(&line->tags);
 	polyobj_t *po;
 
 	if (!(po = Polyobj_GetForNum(polyObjNum)))
@@ -1155,7 +1068,7 @@ static void PolyInvisible(line_t *line)
 // If NOCLIMB is ticked, the polyobject will not be tangible, just visible.
 static void PolyVisible(line_t *line)
 {
-	INT32 polyObjNum = line->tag;
+	INT32 polyObjNum = Tag_FGet(&line->tags);
 	polyobj_t *po;
 
 	if (!(po = Polyobj_GetForNum(polyObjNum)))
@@ -1180,7 +1093,7 @@ static void PolyVisible(line_t *line)
 // Frontsector floor / 100 = translevel
 static void PolyTranslucency(line_t *line)
 {
-	INT32 polyObjNum = line->tag;
+	INT32 polyObjNum = Tag_FGet(&line->tags);
 	polyobj_t *po;
 	INT32 value;
 
@@ -1212,7 +1125,7 @@ static void PolyTranslucency(line_t *line)
 // Makes a polyobject translucency fade and applies tangibility
 static boolean PolyFade(line_t *line)
 {
-	INT32 polyObjNum = line->tag;
+	INT32 polyObjNum = Tag_FGet(&line->tags);
 	polyobj_t *po;
 	polyfadedata_t pfd;
 	INT32 value;
@@ -1274,7 +1187,7 @@ static boolean PolyWaypoint(line_t *line)
 {
 	polywaypointdata_t pwd;
 
-	pwd.polyObjNum = line->tag;
+	pwd.polyObjNum = Tag_FGet(&line->tags);
 	pwd.speed      = sides[line->sidenum[0]].textureoffset / 8;
 	pwd.sequence   = sides[line->sidenum[0]].rowoffset >> FRACBITS; // Sequence #
 
@@ -1301,7 +1214,7 @@ static boolean PolyRotate(line_t *line)
 {
 	polyrotdata_t prd;
 
-	prd.polyObjNum = line->tag;
+	prd.polyObjNum = Tag_FGet(&line->tags);
 	prd.speed      = sides[line->sidenum[0]].textureoffset >> FRACBITS; // angular speed
 	prd.distance   = sides[line->sidenum[0]].rowoffset >> FRACBITS; // angular distance
 
@@ -1326,7 +1239,7 @@ static boolean PolyFlag(line_t *line)
 {
 	polyflagdata_t pfd;
 
-	pfd.polyObjNum = line->tag;
+	pfd.polyObjNum = Tag_FGet(&line->tags);
 	pfd.speed = P_AproxDistance(line->dx, line->dy) >> FRACBITS;
 	pfd.angle = R_PointToAngle2(line->v1->x, line->v1->y, line->v2->x, line->v2->y) >> ANGLETOFINESHIFT;
 	pfd.momx = sides[line->sidenum[0]].textureoffset >> FRACBITS;
@@ -1339,7 +1252,7 @@ static boolean PolyDisplace(line_t *line)
 {
 	polydisplacedata_t pdd;
 
-	pdd.polyObjNum = line->tag;
+	pdd.polyObjNum = Tag_FGet(&line->tags);
 
 	pdd.controlSector = line->frontsector;
 	pdd.dx = line->dx>>8;
@@ -1355,7 +1268,7 @@ static boolean PolyRotDisplace(line_t *line)
 	polyrotdisplacedata_t pdd;
 	fixed_t anginter, distinter;
 
-	pdd.polyObjNum = line->tag;
+	pdd.polyObjNum = Tag_FGet(&line->tags);
 	pdd.controlSector = line->frontsector;
 
 	// Rotate 'anginter' interval for each 'distinter' interval from the control sector.
@@ -1375,66 +1288,6 @@ static boolean PolyRotDisplace(line_t *line)
 	return EV_DoPolyObjRotDisplace(&pdd);
 }
 
-/** Changes a sector's tag.
-  * Used by the linedef executor tag changer and by crumblers.
-  *
-  * \param sector Sector whose tag will be changed.
-  * \param newtag New tag number for this sector.
-  * \sa P_InitTagLists, P_FindSectorFromTag
-  * \author Graue <graue@oceanbase.org>
-  */
-void P_ChangeSectorTag(UINT32 sector, INT16 newtag)
-{
-	INT16 oldtag;
-	INT32 i;
-
-	I_Assert(sector < numsectors);
-
-	if ((oldtag = sectors[sector].tag) == newtag)
-		return;
-
-	// first you have to remove it from the old tag's taglist
-	i = sectors[(unsigned)oldtag % numsectors].firsttag;
-
-	if (i == -1) // shouldn't happen
-		I_Error("Corrupt tag list for sector %u\n", sector);
-	else if ((UINT32)i == sector)
-		sectors[(unsigned)oldtag % numsectors].firsttag = sectors[sector].nexttag;
-	else
-	{
-		while (sectors[i].nexttag != -1 && (UINT32)sectors[i].nexttag < sector )
-			i = sectors[i].nexttag;
-
-		sectors[i].nexttag = sectors[sector].nexttag;
-	}
-
-	sectors[sector].tag = newtag;
-
-	// now add it to the new tag's taglist
-	if ((UINT32)sectors[(unsigned)newtag % numsectors].firsttag > sector)
-	{
-		sectors[sector].nexttag = sectors[(unsigned)newtag % numsectors].firsttag;
-		sectors[(unsigned)newtag % numsectors].firsttag = sector;
-	}
-	else
-	{
-		i = sectors[(unsigned)newtag % numsectors].firsttag;
-
-		if (i == -1)
-		{
-			sectors[(unsigned)newtag % numsectors].firsttag = sector;
-			sectors[sector].nexttag = -1;
-		}
-		else
-		{
-			while (sectors[i].nexttag != -1 && (UINT32)sectors[i].nexttag < sector )
-				i = sectors[i].nexttag;
-
-			sectors[sector].nexttag = sectors[i].nexttag;
-			sectors[i].nexttag = sector;
-		}
-	}
-}
 
 //
 // P_RunNightserizeExecutors
@@ -2103,7 +1956,7 @@ void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller)
 
 	for (masterline = 0; masterline < numlines; masterline++)
 	{
-		if (lines[masterline].tag != tag)
+		if (Tag_FGet(&lines[masterline].tags) != tag)
 			continue;
 
 		// "No More Enemies" and "Level Load" take care of themselves.
@@ -2369,6 +2222,8 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 {
 	INT32 secnum = -1;
 	mobj_t *bot = NULL;
+	mtag_t tag = Tag_FGet(&line->tags);
+	TAG_ITER_DECLARECOUNTER(0);
 
 	I_Assert(!mo || !P_MobjWasRemoved(mo)); // If mo is there, mo must be valid!
 
@@ -2396,7 +2251,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				newceilinglightsec = line->frontsector->ceilinglightsec;
 
 				// act on all sectors with the same tag as the triggering linedef
-				while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+				TAG_ITER_SECTORS(0, tag, secnum)
 				{
 					if (sectors[secnum].lightingdata)
 					{
@@ -2451,17 +2306,17 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		case 409: // Change tagged sectors' tag
 		// (formerly "Change calling sectors' tag", but behavior was changed)
 		{
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
-				P_ChangeSectorTag(secnum,(INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS));
+			TAG_ITER_SECTORS(0, tag, secnum)
+				Tag_SectorFSet(secnum,(INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS));
 			break;
 		}
 
 		case 410: // Change front sector's tag
-			P_ChangeSectorTag((UINT32)(line->frontsector - sectors), (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS));
+			Tag_SectorFSet((UINT32)(line->frontsector - sectors), (INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS));
 			break;
 
 		case 411: // Stop floor/ceiling movement in tagged sector(s)
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 			{
 				if (sectors[secnum].floordata)
 				{
@@ -2531,7 +2386,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				}
 				else
 				{
-					if ((secnum = P_FindSectorFromTag(line->tag, -1)) < 0)
+					if ((secnum = Tag_Iterate_Sectors(tag, 0)) < 0)
 						return;
 
 					dest = P_GetObjectTypeInSectorNum(MT_TELEPORTMAN, secnum);
@@ -2638,7 +2493,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					return;
 				}
 
-				if (line->tag != 0) // Do special stuff only if a non-zero linedef tag is set
+				if (tag != 0) // Do special stuff only if a non-zero linedef tag is set
 				{
 					// Play sounds from tagged sectors' origins.
 					if (line->flags & ML_EFFECT5) // Repeat Midtexture
@@ -2646,7 +2501,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 						// Additionally play the sound from tagged sectors' soundorgs
 						sector_t *sec;
 
-						while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+						TAG_ITER_SECTORS(0, tag, secnum)
 						{
 							sec = &sectors[secnum];
 							S_StartSound(&sec->soundorg, sfxnum);
@@ -2666,7 +2521,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 							if (!camobj)
 								continue;
 
-							if (foundit || (camobj->subsector->sector->tag == line->tag))
+							if (foundit || Tag_Find(&camobj->subsector->sector->tags, tag))
 							{
 								foundit = true;
 								break;
@@ -2675,7 +2530,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 							// Only trigger if mobj is touching the tag
 							for(rover = camobj->subsector->sector->ffloors; rover; rover = rover->next)
 							{
-								if (rover->master->frontsector->tag != line->tag)
+								if (!Tag_Find(&rover->master->frontsector->tags, tag))
 									continue;
 
 								if (camobj->z > P_GetSpecialTopZ(camobj, sectors + rover->secnum, camobj->subsector->sector))
@@ -2761,7 +2616,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 416: // Spawn adjustable fire flicker
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 			{
 				if (line->flags & ML_NOCLIMB && line->backsector)
 				{
@@ -2795,7 +2650,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 417: // Spawn adjustable glowing light
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 			{
 				if (line->flags & ML_NOCLIMB && line->backsector)
 				{
@@ -2829,7 +2684,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 418: // Spawn adjustable strobe flash (unsynchronized)
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 			{
 				if (line->flags & ML_NOCLIMB && line->backsector)
 				{
@@ -2863,7 +2718,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 419: // Spawn adjustable strobe flash (synchronized)
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 			{
 				if (line->flags & ML_NOCLIMB && line->backsector)
 				{
@@ -2897,7 +2752,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 420: // Fade light levels in tagged sectors to new value
-			P_FadeLight(line->tag,
+			P_FadeLight(tag,
 				(line->flags & ML_DONTPEGBOTTOM) ? max(sides[line->sidenum[0]].textureoffset>>FRACBITS, 0) : line->frontsector->lightlevel,
 				// failsafe: if user specifies Back Y Offset and NOT Front Y Offset, use the Back Offset
 				// to be consistent with other light and fade specials
@@ -2911,7 +2766,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 421: // Stop lighting effect in tagged sectors
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 				if (sectors[secnum].lightingdata)
 				{
 					P_RemoveThinker(&((elevator_t *)sectors[secnum].lightingdata)->thinker);
@@ -2926,7 +2781,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				if ((!mo || !mo->player) && !titlemapinaction) // only players have views, and title screens
 					return;
 
-				if ((secnum = P_FindSectorFromTag(line->tag, -1)) < 0)
+				if ((secnum = Tag_Iterate_Sectors(tag, 0)) < 0)
 					return;
 
 				altview = P_GetObjectTypeInSectorNum(MT_ALTVIEWMAN, secnum);
@@ -3108,8 +2963,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 						continue;
 
 					scroller = (scroll_t *)th;
-
-					if (sectors[scroller->affectee].tag != line->tag)
+					if (!Tag_Find(&sectors[scroller->affectee].tags, tag))
 						continue;
 
 					scroller->dx = FixedMul(line->dx>>SCROLL_SHIFT, CARRYFACTOR);
@@ -3126,7 +2980,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				ffloor_t *rover; // FOF that we are going to crumble
 				boolean foundrover = false; // for debug, "Can't find a FOF" message
 
-				for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+				TAG_ITER_SECTORS(0, sectag, secnum)
 				{
 					sec = sectors + secnum;
 
@@ -3138,7 +2992,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 					for (rover = sec->ffloors; rover; rover = rover->next)
 					{
-						if (rover->master->frontsector->tag == foftag)
+						if (Tag_Find(&rover->master->frontsector->tags, foftag))
 						{
 							foundrover = true;
 
@@ -3185,12 +3039,13 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				size_t linenum;
 				side_t *set = &sides[line->sidenum[0]], *this;
 				boolean always = !(line->flags & ML_NOCLIMB); // If noclimb: Only change mid texture if mid texture already exists on tagged lines, etc.
+
 				for (linenum = 0; linenum < numlines; linenum++)
 				{
 					if (lines[linenum].special == 439)
 						continue; // Don't override other set texture lines!
 
-					if (lines[linenum].tag != line->tag)
+					if (!Tag_Find(&lines[linenum].tags, tag))
 						continue; // Find tagged lines
 
 					// Front side
@@ -3250,7 +3105,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			if (line->sidenum[1] != 0xffff)
 				state = (statenum_t)sides[line->sidenum[1]].toptexture;
 
-			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
+			TAG_ITER_SECTORS(0, tag, secnum)
 			{
 				boolean tryagain;
 				sec = sectors + secnum;
@@ -3310,7 +3165,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				boolean foundrover = false; // for debug, "Can't find a FOF" message
 				ffloortype_e oldflags; // store FOF's old flags
 
-				for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+				TAG_ITER_SECTORS(0, sectag, secnum)
 				{
 					sec = sectors + secnum;
 
@@ -3322,7 +3177,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 					for (rover = sec->ffloors; rover; rover = rover->next)
 					{
-						if (rover->master->frontsector->tag == foftag)
+						if (Tag_Find(&rover->master->frontsector->tags, foftag))
 						{
 							foundrover = true;
 
@@ -3368,7 +3223,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				if (line->flags & ML_NOCLIMB) // don't respawn!
 					respawn = false;
 
-				for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+				TAG_ITER_SECTORS(0, sectag, secnum)
 				{
 					sec = sectors + secnum;
 
@@ -3380,7 +3235,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 					for (rover = sec->ffloors; rover; rover = rover->next)
 					{
-						if (rover->master->frontsector->tag == foftag)
+						if (Tag_Find(&rover->master->frontsector->tags, foftag))
 						{
 							foundrover = true;
 
@@ -3415,7 +3270,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					source = line->frontsector->extra_colormap;
 				else
 				{
-					INT32 sourcesec = P_FindSectorFromTag(line->args[1], -1);
+					INT32 sourcesec = Tag_Iterate_Sectors(line->args[1], 0);
 					if (sourcesec == -1)
 					{
 						CONS_Debug(DBG_GAMELOGIC, "Line type 447 Executor: Can't find sector with source colormap (tag %d)!\n", line->args[1]);
@@ -3424,8 +3279,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					source = sectors[sourcesec].extra_colormap;
 				}
 			}
-
-			for (secnum = -1; (secnum = P_FindSectorFromTag(line->args[0], secnum)) >= 0;)
+			TAG_ITER_SECTORS(0, line->args[0], secnum)
 			{
 				if (sectors[secnum].colormap_protected)
 					continue;
@@ -3477,7 +3331,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				{
 					CONS_Alert(CONS_WARNING,
 					M_GetText("Skybox switch linedef (tag %d) doesn't have anything to do.\nConsider changing the linedef's flag configuration or removing it entirely.\n"),
-					line->tag);
+					tag);
 				}
 				else
 				{
@@ -3515,7 +3369,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			{
 				CONS_Alert(CONS_WARNING,
 					M_GetText("Boss enable linedef (tag %d) has an invalid texture x offset.\nConsider changing it or removing it entirely.\n"),
-					line->tag);
+					tag);
 				break;
 			}
 			if (line->flags & ML_NOCLIMB)
@@ -3532,7 +3386,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		}
 
 		case 450: // Execute Linedef Executor - for recursion
-			P_LinedefExecute(line->tag, mo, NULL);
+			P_LinedefExecute(tag, mo, NULL);
 			break;
 
 		case 451: // Execute Random Linedef Executor
@@ -3560,7 +3414,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			ffloor_t *rover; // FOF that we are going to operate
 			boolean foundrover = false; // for debug, "Can't find a FOF" message
 
-			for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+			TAG_ITER_SECTORS(0, sectag, secnum)
 			{
 				sec = sectors + secnum;
 
@@ -3572,7 +3426,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 				for (rover = sec->ffloors; rover; rover = rover->next)
 				{
-					if (rover->master->frontsector->tag == foftag)
+					if (Tag_Find(&rover->master->frontsector->tags, foftag))
 					{
 						foundrover = true;
 
@@ -3624,7 +3478,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			boolean foundrover = false; // for debug, "Can't find a FOF" message
 			size_t j = 0; // sec->ffloors is saved as ffloor #0, ss->ffloors->next is #1, etc
 
-			for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+			TAG_ITER_SECTORS(0, sectag, secnum)
 			{
 				sec = sectors + secnum;
 
@@ -3636,7 +3490,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 				for (rover = sec->ffloors; rover; rover = rover->next)
 				{
-					if (rover->master->frontsector->tag == foftag)
+					if (Tag_Find(&rover->master->frontsector->tags, foftag))
 					{
 						foundrover = true;
 
@@ -3709,7 +3563,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			ffloor_t *rover; // FOF that we are going to operate
 			boolean foundrover = false; // for debug, "Can't find a FOF" message
 
-			for (secnum = -1; (secnum = P_FindSectorFromTag(sectag, secnum)) >= 0 ;)
+			TAG_ITER_SECTORS(0, sectag, secnum)
 			{
 				sec = sectors + secnum;
 
@@ -3721,7 +3575,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 				for (rover = sec->ffloors; rover; rover = rover->next)
 				{
-					if (rover->master->frontsector->tag == foftag)
+					if (Tag_Find(&rover->master->frontsector->tags, foftag))
 					{
 						foundrover = true;
 
@@ -3750,7 +3604,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					dest = line->frontsector->extra_colormap;
 				else
 				{
-					INT32 destsec = P_FindSectorFromTag(line->args[1], -1);
+					INT32 destsec = Tag_Iterate_Sectors(line->args[1], 0);
 					if (destsec == -1)
 					{
 						CONS_Debug(DBG_GAMELOGIC, "Line type 455 Executor: Can't find sector with destination colormap (tag %d)!\n", line->args[1]);
@@ -3760,7 +3614,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				}
 			}
 
-			for (secnum = -1; (secnum = P_FindSectorFromTag(line->args[0], secnum)) >= 0;)
+			TAG_ITER_SECTORS(0, line->args[0], secnum)
 			{
 				extracolormap_t *source_exc, *dest_exc, *exc;
 
@@ -3840,7 +3694,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 		}
 		case 456: // Stop fade colormap
-			for (secnum = -1; (secnum = P_FindSectorFromTag(line->args[0], secnum)) >= 0 ;)
+			TAG_ITER_SECTORS(0, line->args[0], secnum)
 				P_ResetColormapFader(&sectors[secnum]);
 			break;
 
@@ -3854,7 +3708,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				boolean persist = (line->flags & ML_EFFECT2);
 				mobj_t *anchormo;
 
-				if ((secnum = P_FindSectorFromTag(line->tag, -1)) < 0)
+				if ((secnum = Tag_Iterate_Sectors(tag, 0)) < 0)
 					return;
 
 				anchormo = P_GetObjectTypeInSectorNum(MT_ANGLEMAN, secnum);
@@ -3885,7 +3739,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			{
 				INT32 promptnum = max(0, (sides[line->sidenum[0]].textureoffset>>FRACBITS)-1);
 				INT32 pagenum = max(0, (sides[line->sidenum[0]].rowoffset>>FRACBITS)-1);
-				INT32 postexectag = abs((line->sidenum[1] != 0xFFFF) ? sides[line->sidenum[1]].textureoffset>>FRACBITS : line->tag);
+				INT32 postexectag = abs((line->sidenum[1] != 0xFFFF) ? sides[line->sidenum[1]].textureoffset>>FRACBITS : tag);
 
 				boolean closetextprompt = (line->flags & ML_BLOCKMONSTERS);
 				//boolean allplayers = (line->flags & ML_NOCLIMB);
@@ -4009,7 +3863,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					if (!mo2->spawnpoint)
 						continue;
 
-					if (mo2->spawnpoint->angle != line->tag)
+					if (mo2->spawnpoint->angle != tag)
 						continue;
 
 					P_KillMobj(mo2, NULL, mo, 0);
@@ -4033,11 +3887,12 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		case 465: // Set linedef executor delay
 			{
 				INT32 linenum;
+				TAG_ITER_DECLARECOUNTER(1);
 
 				if (!udmf)
 					break;
 
-				for (linenum = -1; (linenum = P_FindLineFromTag(line->args[0], linenum)) >= 0 ;)
+				TAG_ITER_LINES(1, line->args[0], linenum)
 				{
 					if (line->args[2])
 						lines[linenum].executordelay += line->args[1];
@@ -4382,6 +4237,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 	INT32 i = 0;
 	INT32 section1, section2, section3, section4;
 	INT32 special;
+	mtag_t sectag = Tag_FGet(&sector->tags);
 
 	section1 = GETSECSPECIAL(sector->special, 1);
 	section2 = GETSECSPECIAL(sector->special, 2);
@@ -4536,7 +4392,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 		case 6: // Linedef executor (7 Emeralds)
 		case 7: // Linedef executor (NiGHTS Mare)
 			if (!player->bot)
-				P_LinedefExecute(sector->tag, player->mo, sector);
+				P_LinedefExecute(sectag, player->mo, sector);
 			break;
 		case 8: // Tells pushable things to check FOFs
 			break;
@@ -4565,14 +4421,14 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 			sector->special = 0;
 
 			// Move the button down
-			junk.tag = 680;
+			Tag_FSet(&junk.tags, 680);
 			EV_DoElevator(&junk, elevateDown, false);
 
 			// Open the top FOF
-			junk.tag = 681;
+			Tag_FSet(&junk.tags, 681);
 			EV_DoFloor(&junk, raiseFloorToNearestFast);
 			// Open the bottom FOF
-			junk.tag = 682;
+			Tag_FSet(&junk.tags, 682);
 			EV_DoCeiling(&junk, lowerToLowestFast);
 
 			// Mark all players with the time to exit thingy!
@@ -4607,7 +4463,7 @@ DoneSection2:
 			if (player->powers[pw_flashing] != 0 && player->powers[pw_flashing] < TICRATE/2)
 				break;
 
-			i = P_FindSpecialLineFromTag(4, sector->tag, -1);
+			i = Tag_FindLineSpecial(4, sectag);
 
 			if (i != -1)
 			{
@@ -4620,7 +4476,7 @@ DoneSection2:
 
 				if (linespeed == 0)
 				{
-					CONS_Debug(DBG_GAMELOGIC, "ERROR: Speed pad (tag %d) at zero speed.\n", sector->tag);
+					CONS_Debug(DBG_GAMELOGIC, "ERROR: Speed pad (tag %d) at zero speed.\n", sectag);
 					break;
 				}
 
@@ -4714,7 +4570,7 @@ DoneSection2:
 				// important: use sector->tag on next line instead of player->mo->subsector->tag
 				// this part is different from in P_PlayerThink, this is what was causing
 				// FOF custom exits not to work.
-				lineindex = P_FindSpecialLineFromTag(2, sector->tag, -1);
+				lineindex = Tag_FindLineSpecial(2, sectag);
 
 				if (G_CoopGametype() && lineindex != -1) // Custom exit!
 				{
@@ -4840,7 +4696,7 @@ DoneSection2:
 					break;
 
 				// Find line #3 tagged to this sector
-				lineindex = P_FindSpecialLineFromTag(3, sector->tag, -1);
+				lineindex = Tag_FindLineSpecial(3, sectag);
 
 				if (lineindex == -1)
 				{
@@ -4905,7 +4761,7 @@ DoneSection2:
 					break;
 
 				// Find line #3 tagged to this sector
-				lineindex = P_FindSpecialLineFromTag(3, sector->tag, -1);
+				lineindex = Tag_FindLineSpecial(3, sectag);
 
 				if (lineindex == -1)
 				{
@@ -5034,7 +4890,7 @@ DoneSection2:
 				memset(&resulthigh, 0x00, sizeof(resulthigh));
 
 				// Find line #11 tagged to this sector
-				lineindex = P_FindSpecialLineFromTag(11, sector->tag, -1);
+				lineindex = Tag_FindLineSpecial(11, sectag);
 
 				if (lineindex == -1)
 				{
@@ -5654,7 +5510,7 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 	{
 		fixed_t tempceiling = sec2->ceilingheight;
 		//flip the sector around and print an error instead of crashing 12.1.08 -Inuyasha
-		CONS_Alert(CONS_ERROR, M_GetText("A FOF tagged %d has a top height below its bottom.\n"), master->tag);
+		CONS_Alert(CONS_ERROR, M_GetText("FOF (line %s) has a top height below its bottom.\n"), sizeu1(master - lines));
 		sec2->ceilingheight = sec2->floorheight;
 		sec2->floorheight = tempceiling;
 	}
@@ -5794,7 +5650,7 @@ static ffloor_t *P_AddFakeFloor(sector_t *sec, sector_t *sec2, line_t *master, f
 
 	if ((flags & FF_FLOATBOB))
 	{
-		P_AddFloatThinker(sec2, sec->tag, master);
+		P_AddFloatThinker(sec2, Tag_FGet(&master->tags), master);
 		CheckForFloatBob = true;
 	}
 
@@ -5950,7 +5806,7 @@ static void P_AddAirbob(sector_t *sec, INT16 tag, fixed_t dist, boolean raise, b
   * \sa P_SpawnSpecials, T_ThwompSector
   * \author SSNTails <http://www.ssntails.org>
   */
-static inline void P_AddThwompThinker(sector_t *sec, INT16 tag, line_t *sourceline, fixed_t crushspeed, fixed_t retractspeed, UINT16 sound)
+static inline void P_AddThwompThinker(sector_t *sec, line_t *sourceline, fixed_t crushspeed, fixed_t retractspeed, UINT16 sound)
 {
 	thwomp_t *thwomp;
 
@@ -5974,7 +5830,7 @@ static inline void P_AddThwompThinker(sector_t *sec, INT16 tag, line_t *sourceli
 	thwomp->floorstartheight = sec->floorheight;
 	thwomp->ceilingstartheight = sec->ceilingheight;
 	thwomp->delay = 1;
-	thwomp->tag = tag;
+	thwomp->tag = Tag_FGet(&sourceline->tags);
 	thwomp->sound = sound;
 
 	sec->floordata = thwomp;
@@ -6066,8 +5922,9 @@ void T_LaserFlash(laserthink_t *flash)
 	sector_t *sector;
 	sector_t *sourcesec = flash->sourceline->frontsector;
 	fixed_t top, bottom;
+	TAG_ITER_DECLARECOUNTER(0);
 
-	for (s = -1; (s = P_FindSectorFromTag(flash->tag, s)) >= 0 ;)
+	TAG_ITER_SECTORS(0, flash->tag, s)
 	{
 		sector = &sectors[s];
 		for (fflr = sector->ffloors; fflr; fflr = fflr->next)
@@ -6322,6 +6179,8 @@ void P_SpawnSpecials(boolean fromnetsave)
 	// Init line EFFECTs
 	for (i = 0; i < numlines; i++)
 	{
+		mtag_t tag = Tag_FGet(&lines[i].tags);
+
 		if (lines[i].special != 7) // This is a hack. I can at least hope nobody wants to prevent flat alignment in netgames...
 		{
 			// set line specials to 0 here too, same reason as above
@@ -6345,10 +6204,11 @@ void P_SpawnSpecials(boolean fromnetsave)
 			INT32 s;
 			size_t sec;
 			ffloortype_e ffloorflags;
+			TAG_ITER_DECLARECOUNTER(0);
 
 			case 1: // Definable gravity per sector
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 				{
 					sectors[s].gravity = &sectors[sec].floorheight; // This allows it to change in realtime!
 
@@ -6372,7 +6232,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 5: // Change camera info
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_AddCameraScanner(&sectors[sec], &sectors[s], R_PointToAngle2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y));
 				break;
 
@@ -6395,22 +6255,22 @@ void P_SpawnSpecials(boolean fromnetsave)
 					}
 
 					//If no tag is given, apply to front sector
-					if (lines[i].tag == 0)
+					if (tag == 0)
 						P_ApplyFlatAlignment(lines + i, lines[i].frontsector, flatangle, xoffs, yoffs);
 					else
 					{
-						for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0;)
+						TAG_ITER_SECTORS(0, tag, s)
 							P_ApplyFlatAlignment(lines + i, sectors + s, flatangle, xoffs, yoffs);
 					}
 				}
 				else // Otherwise, print a helpful warning. Can I do no less?
 					CONS_Alert(CONS_WARNING,
 					M_GetText("Flat alignment linedef (tag %d) doesn't have anything to do.\nConsider changing the linedef's flag configuration or removing it entirely.\n"),
-					lines[i].tag);
+					tag);
 				break;
 
 			case 8: // Sector Parameters
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 				{
 					if (lines[i].flags & ML_NOCLIMB)
 					{
@@ -6437,8 +6297,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 				break;
 
 			case 10: // Vertical culling plane for sprites and FOFs
-				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					sectors[s].cullheight = &lines[i]; // This allows it to change in realtime!
 				break;
 
@@ -6499,36 +6358,38 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 63: // support for drawn heights coming from different sector
 				sec = sides[*lines[i].sidenum].sector-sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					sectors[s].heightsec = (INT32)sec;
 				break;
 
 			case 64: // Appearing/Disappearing FOF option
 				if (lines[i].flags & ML_BLOCKMONSTERS) { // Find FOFs by control sector tag
-					for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+					TAG_ITER_SECTORS(0, tag, s)
 						for (j = 0; (unsigned)j < sectors[s].linecount; j++)
 							if (sectors[s].lines[j]->special >= 100 && sectors[s].lines[j]->special < 300)
 								Add_MasterDisappearer(abs(lines[i].dx>>FRACBITS), abs(lines[i].dy>>FRACBITS), abs(sides[lines[i].sidenum[0]].sector->floorheight>>FRACBITS), (INT32)(sectors[s].lines[j]-lines), (INT32)i);
 				} else // Find FOFs by effect sector tag
-					for (s = -1; (s = P_FindLineFromTag(lines[i].tag, s)) >= 0 ;)
+				{
+					TAG_ITER_LINES(0, tag, s)
 					{
 						if ((size_t)s == i)
 							continue;
-						if (sides[lines[s].sidenum[0]].sector->tag == sides[lines[i].sidenum[0]].sector->tag)
+						if (Tag_Find(&sides[lines[s].sidenum[0]].sector->tags, Tag_FGet(&sides[lines[i].sidenum[0]].sector->tags)))
 							Add_MasterDisappearer(abs(lines[i].dx>>FRACBITS), abs(lines[i].dy>>FRACBITS), abs(sides[lines[i].sidenum[0]].sector->floorheight>>FRACBITS), s, (INT32)i);
 					}
+				}
 				break;
 
 			case 66: // Displace floor by front sector
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_AddPlaneDisplaceThinker(pd_floor, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
 				break;
 			case 67: // Displace ceiling by front sector
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_AddPlaneDisplaceThinker(pd_ceiling, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
 				break;
 			case 68: // Displace both floor AND ceiling by front sector
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_AddPlaneDisplaceThinker(pd_both, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
 				break;
 
@@ -6725,16 +6586,16 @@ void P_SpawnSpecials(boolean fromnetsave)
 			{
 				fixed_t dist = (lines[i].special == 150) ? 16*FRACUNIT : P_AproxDistance(lines[i].dx, lines[i].dy);
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines[i].tag, dist, false, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddAirbob(lines[i].frontsector, tag, dist, false, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 			}
 			case 152: // Adjustable air bobbing platform in reverse
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines[i].tag, P_AproxDistance(lines[i].dx, lines[i].dy), true, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddAirbob(lines[i].frontsector, tag, P_AproxDistance(lines[i].dx, lines[i].dy), true, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 			case 153: // Dynamic Sinking Platform
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines[i].tag, P_AproxDistance(lines[i].dx, lines[i].dy), false, !!(lines[i].flags & ML_NOCLIMB), true);
+				P_AddAirbob(lines[i].frontsector, tag, P_AproxDistance(lines[i].dx, lines[i].dy), false, !!(lines[i].flags & ML_NOCLIMB), true);
 				break;
 
 			case 160: // Float/bob platform
@@ -6784,13 +6645,13 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 176: // Air bobbing platform that will crumble and bob on the water when it falls and hits
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_FLOATBOB|FF_CRUMBLE, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines[i].tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddAirbob(lines[i].frontsector, tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 
 			case 177: // Air bobbing platform that will crumble and bob on
 				// the water when it falls and hits, then never return
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_FLOATBOB|FF_CRUMBLE|FF_NORETURN, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines[i].tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddAirbob(lines[i].frontsector, tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 
 			case 178: // Crumbling platform that will float when it hits water
@@ -6803,7 +6664,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 180: // Air bobbing platform that will crumble
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines[i].tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddAirbob(lines[i].frontsector, tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 
 			case 190: // Rising Platform FOF (solid, opaque, shadows)
@@ -6830,7 +6691,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 					ffloorflags |= FF_NOSHADE;
 				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
 
-				P_AddRaiseThinker(lines[i].frontsector, lines[i].tag, speed, ceilingtop, ceilingbottom, !!(lines[i].flags & ML_BLOCKMONSTERS), !!(lines[i].flags & ML_NOCLIMB));
+				P_AddRaiseThinker(lines[i].frontsector, tag, speed, ceilingtop, ceilingbottom, !!(lines[i].flags & ML_BLOCKMONSTERS), !!(lines[i].flags & ML_NOCLIMB));
 				break;
 			}
 
@@ -6892,7 +6753,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 				fixed_t crushspeed = (lines[i].flags & ML_EFFECT5) ? lines[i].dy >> 3 : 10*FRACUNIT;
 				fixed_t retractspeed = (lines[i].flags & ML_EFFECT5) ? lines[i].dx >> 3 : 2*FRACUNIT;
 				UINT16 sound = (lines[i].flags & ML_EFFECT4) ? sides[lines[i].sidenum[0]].textureoffset >> FRACBITS : sfx_thwomp;
-				P_AddThwompThinker(lines[i].frontsector, lines[i].tag, &lines[i], crushspeed, retractspeed, sound);
+				P_AddThwompThinker(lines[i].frontsector, &lines[i], crushspeed, retractspeed, sound);
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
 				break;
 			}
@@ -6934,7 +6795,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 				break;
 
 			case 258: // Laser block
-				P_AddLaserThinker(lines[i].tag, lines + i, !!(lines[i].flags & ML_EFFECT1));
+				P_AddLaserThinker(tag, lines + i, !!(lines[i].flags & ML_EFFECT1));
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_RENDERALL|FF_NOSHADE|FF_EXTRA|FF_CUTEXTRA|FF_TRANSLUCENT, secthinkers);
 				break;
 
@@ -6945,7 +6806,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 					P_AddFakeFloorsByLine(i, fofflags, secthinkers);
 				}
 				else
-					I_Error("Custom FOF (tag %d) found without a linedef back side!", lines[i].tag);
+					I_Error("Custom FOF (tag %d) found without a linedef back side!", tag);
 				break;
 
 			case 300: // Linedef executor (combines with sector special 974/975) and commands
@@ -7075,7 +6936,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 				{
 					CONS_Alert(CONS_WARNING,
 						M_GetText("Boss enable linedef (tag %d) has an invalid texture x offset.\nConsider changing it or removing it entirely.\n"),
-						lines[i].tag);
+						tag);
 					break;
 				}
 				if (!(lines[i].flags & ML_NOCLIMB))
@@ -7124,46 +6985,46 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 600: // floor lighting independently (e.g. lava)
 				sec = sides[*lines[i].sidenum].sector-sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					sectors[s].floorlightsec = (INT32)sec;
 				break;
 
 			case 601: // ceiling lighting independently
 				sec = sides[*lines[i].sidenum].sector-sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					sectors[s].ceilinglightsec = (INT32)sec;
 				break;
 
 			case 602: // Adjustable pulsating light
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_SpawnAdjustableGlowingLight(&sectors[sec], &sectors[s],
 						P_AproxDistance(lines[i].dx, lines[i].dy)>>FRACBITS);
 				break;
 
 			case 603: // Adjustable flickering light
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_SpawnAdjustableFireFlicker(&sectors[sec], &sectors[s],
 						P_AproxDistance(lines[i].dx, lines[i].dy)>>FRACBITS);
 				break;
 
 			case 604: // Adjustable Blinking Light (unsynchronized)
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_SpawnAdjustableStrobeFlash(&sectors[sec], &sectors[s],
 						abs(lines[i].dx)>>FRACBITS, abs(lines[i].dy)>>FRACBITS, false);
 				break;
 
 			case 605: // Adjustable Blinking Light (synchronized)
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					P_SpawnAdjustableStrobeFlash(&sectors[sec], &sectors[s],
 						abs(lines[i].dx)>>FRACBITS, abs(lines[i].dy)>>FRACBITS, true);
 				break;
 
 			case 606: // HACK! Copy colormaps. Just plain colormaps.
-				for (s = -1; (s = P_FindSectorFromTag(lines[i].args[0], s)) >= 0;)
+				TAG_ITER_SECTORS(0, lines[i].args[0], s)
 				{
 					extracolormap_t *exc;
 
@@ -7178,7 +7039,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 							exc = lines[i].frontsector->extra_colormap;
 						else
 						{
-							INT32 sourcesec = P_FindSectorFromTag(lines[i].args[1], -1);
+							INT32 sourcesec = Tag_Iterate_Sectors(lines[i].args[1], 0);
 							if (sourcesec == -1)
 							{
 								CONS_Debug(DBG_GAMELOGIC, "Line type 606: Can't find sector with source colormap (tag %d)!\n", lines[i].args[1]);
@@ -7237,11 +7098,14 @@ void P_SpawnSpecials(boolean fromnetsave)
   */
 static void P_AddFakeFloorsByLine(size_t line, ffloortype_e ffloorflags, thinkerlist_t *secthinkers)
 {
+	TAG_ITER_DECLARECOUNTER(0);
 	INT32 s;
+	mtag_t tag = Tag_FGet(&lines[line].tags);
 	size_t sec = sides[*lines[line].sidenum].sector-sectors;
 
-	for (s = -1; (s = P_FindSectorFromTag(lines[line].tag, s)) >= 0 ;)
-		P_AddFakeFloor(&sectors[s], &sectors[sec], lines+line, ffloorflags, secthinkers);
+	line_t* li = lines + line;
+	TAG_ITER_SECTORS(0, tag, s)
+		P_AddFakeFloor(&sectors[s], &sectors[sec], li, ffloorflags, secthinkers);
 }
 
 /*
@@ -7350,6 +7214,7 @@ void T_Scroll(scroll_t *s)
 		size_t i;
 		INT32 sect;
 		ffloor_t *rover;
+		TAG_ITER_DECLARECOUNTER(0);
 
 		case sc_side: // scroll wall texture
 			side = sides + s->affectee;
@@ -7386,7 +7251,7 @@ void T_Scroll(scroll_t *s)
 				if (!is3dblock)
 					continue;
 
-				for (sect = -1; (sect = P_FindSectorFromTag(line->tag, sect)) >= 0 ;)
+				TAG_ITER_SECTORS(0, Tag_FGet(&line->tags), sect)
 				{
 					sector_t *psec;
 					psec = sectors + sect;
@@ -7461,8 +7326,7 @@ void T_Scroll(scroll_t *s)
 
 				if (!is3dblock)
 					continue;
-
-				for (sect = -1; (sect = P_FindSectorFromTag(line->tag, sect)) >= 0 ;)
+				TAG_ITER_SECTORS(0, Tag_FGet(&line->tags), sect)
 				{
 					sector_t *psec;
 					psec = sectors + sect;
@@ -7560,6 +7424,7 @@ static void P_SpawnScrollers(void)
 {
 	size_t i;
 	line_t *l = lines;
+	mtag_t tag;
 
 	for (i = 0; i < numlines; i++, l++)
 	{
@@ -7568,6 +7433,8 @@ static void P_SpawnScrollers(void)
 		INT32 control = -1, accel = 0; // no control sector or acceleration
 		INT32 special = l->special;
 
+		tag = Tag_FGet(&l->tags);
+
 		// These types are same as the ones they get set to except that the
 		// first side's sector's heights cause scrolling when they change, and
 		// this linedef controls the direction and speed of the scrolling. The
@@ -7599,10 +7466,11 @@ static void P_SpawnScrollers(void)
 		switch (special)
 		{
 			register INT32 s;
+			TAG_ITER_DECLARECOUNTER(0);
 
 			case 513: // scroll effect ceiling
 			case 533: // scroll and carry objects on ceiling
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Scroller(sc_ceiling, -dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
 				if (special != 533)
 					break;
@@ -7611,13 +7479,13 @@ static void P_SpawnScrollers(void)
 			case 523:	// carry objects on ceiling
 				dx = FixedMul(dx, CARRYFACTOR);
 				dy = FixedMul(dy, CARRYFACTOR);
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Scroller(sc_carry_ceiling, dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
 				break;
 
 			case 510: // scroll effect floor
 			case 530: // scroll and carry objects on floor
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Scroller(sc_floor, -dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
 				if (special != 530)
 					break;
@@ -7626,14 +7494,15 @@ static void P_SpawnScrollers(void)
 			case 520:	// carry objects on floor
 				dx = FixedMul(dx, CARRYFACTOR);
 				dy = FixedMul(dy, CARRYFACTOR);
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Scroller(sc_carry, dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
 				break;
 
 			// scroll wall according to linedef
 			// (same direction and speed as scrolling floors)
 			case 502:
-				for (s = -1; (s = P_FindLineFromTag(l->tag, s)) >= 0 ;)
+			{
+				TAG_ITER_LINES(0, tag, s)
 					if (s != (INT32)i)
 					{
 						if (l->flags & ML_EFFECT2) // use texture offsets instead
@@ -7650,6 +7519,7 @@ static void P_SpawnScrollers(void)
 							Add_Scroller(sc_side, dx, dy, control, lines[s].sidenum[0], accel, 0);
 					}
 				break;
+			}
 
 			case 505:
 				s = lines[i].sidenum[0];
@@ -7733,8 +7603,10 @@ void T_Disappear(disappear_t *d)
 	{
 		ffloor_t *rover;
 		register INT32 s;
+		mtag_t afftag = Tag_FGet(&lines[d->affectee].tags);
+		TAG_ITER_DECLARECOUNTER(0);
 
-		for (s = -1; (s = P_FindSectorFromTag(lines[d->affectee].tag, s)) >= 0 ;)
+		TAG_ITER_SECTORS(0, afftag, s)
 		{
 			for (rover = sectors[s].ffloors; rover; rover = rover->next)
 			{
@@ -8460,14 +8332,17 @@ static void P_SpawnFriction(void)
 {
 	size_t i;
 	line_t *l = lines;
+	mtag_t tag;
 	register INT32 s;
 	fixed_t strength; // frontside texture offset controls magnitude
 	fixed_t friction; // friction value to be applied during movement
 	INT32 movefactor; // applied to each player move to simulate inertia
+	TAG_ITER_DECLARECOUNTER(0);
 
 	for (i = 0; i < numlines; i++, l++)
 		if (l->special == 540)
 		{
+			tag = Tag_FGet(&l->tags);
 			strength = sides[l->sidenum[0]].textureoffset>>FRACBITS;
 			if (strength > 0) // sludge
 				strength = strength*2; // otherwise, the maximum sludginess value is +967...
@@ -8488,7 +8363,7 @@ static void P_SpawnFriction(void)
 			else
 				movefactor = FRACUNIT;
 
-			for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+			TAG_ITER_SECTORS(0, tag, s)
 				Add_Friction(friction, movefactor, s, -1);
 		}
 }
@@ -9004,22 +8879,26 @@ static void P_SpawnPushers(void)
 {
 	size_t i;
 	line_t *l = lines;
+	mtag_t tag;
 	register INT32 s;
 	mobj_t *thing;
+	TAG_ITER_DECLARECOUNTER(0);
 
 	for (i = 0; i < numlines; i++, l++)
+	{
+		tag = Tag_FGet(&l->tags);
 		switch (l->special)
 		{
 			case 541: // wind
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Pusher(p_wind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 544: // current
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Pusher(p_current, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 547: // push/pull
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 				{
 					thing = P_GetPushThing(s);
 					if (thing) // No MT_P* means no effect
@@ -9027,20 +8906,21 @@ static void P_SpawnPushers(void)
 				}
 				break;
 			case 545: // current up
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Pusher(p_upcurrent, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 546: // current down
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Pusher(p_downcurrent, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 542: // wind up
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Pusher(p_upwind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 543: // wind down
-				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
+				TAG_ITER_SECTORS(0, tag, s)
 					Add_Pusher(p_downwind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 		}
+	}
 }
diff --git a/src/p_spec.h b/src/p_spec.h
index 596d8171d2acfb8b8b337f8a4fc4c686f2d3ce9b..bba7c4a40a090084cce1f9738dc414c5a0c1c013 100644
--- a/src/p_spec.h
+++ b/src/p_spec.h
@@ -55,9 +55,6 @@ fixed_t P_FindNextLowestFloor(sector_t *sec, fixed_t currentheight);
 fixed_t P_FindLowestCeilingSurrounding(sector_t *sec);
 fixed_t P_FindHighestCeilingSurrounding(sector_t *sec);
 
-INT32 P_FindSectorFromTag(INT16 tag, INT32 start);
-INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start);
-
 INT32 P_FindMinSurroundingLight(sector_t *sector, INT32 max);
 
 void P_SetupSignExit(player_t *player);
@@ -67,7 +64,6 @@ void P_SwitchWeather(INT32 weathernum);
 
 boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller);
 void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller);
-void P_ChangeSectorTag(UINT32 sector, INT16 newtag);
 void P_RunNightserizeExecutors(mobj_t *actor);
 void P_RunDeNightserizeExecutors(mobj_t *actor);
 void P_RunNightsLapExecutors(mobj_t *actor);
diff --git a/src/p_tick.c b/src/p_tick.c
index 451e5e6266d9084586e21f0211e9a4ff3b15210d..da2a980c480a54f22d2dc2c3ef2904e96c07a1e8 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -643,7 +643,15 @@ void P_Ticker(boolean run)
 		if (demorecording)
 			G_WriteDemoTiccmd(&players[consoleplayer].cmd, 0);
 		if (demoplayback)
-			G_ReadDemoTiccmd(&players[consoleplayer].cmd, 0);
+		{
+			player_t* p = &players[consoleplayer];
+			G_ReadDemoTiccmd(&p->cmd, 0);
+			if (!cv_freedemocamera.value)
+			{
+				P_ForceLocalAngle(p, p->cmd.angleturn << 16);
+				localaiming = p->aiming;
+			}
+		}
 
 		ps_lua_mobjhooks = 0;
 		ps_checkposition_calls = 0;
diff --git a/src/p_user.c b/src/p_user.c
index f9593158f3b269e2dd03c3bd64fec9ec4f8ebcc8..bb2b20cf7a01f532ec6cd9709c4d58bfc3d3f2ef 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -8692,12 +8692,6 @@ void P_MovePlayer(player_t *player)
 		player->fovadd = 0;
 #endif
 
-#ifdef FLOORSPLATS
-	if (cv_shadow.value && rendermode == render_soft)
-		R_AddFloorSplat(player->mo->subsector, player->mo, "SHADOW", player->mo->x,
-			player->mo->y, player->mo->floorz, SPLATDRAWMODE_OPAQUE);
-#endif
-
 	// Look for blocks to bust up
 	// Because of FF_SHATTER, we should look for blocks constantly,
 	// not just when spinning or playing as Knuckles
@@ -10579,6 +10573,7 @@ static void P_CalcPostImg(player_t *player)
 	postimg_t *type;
 	INT32 *param;
 	fixed_t pviewheight;
+	size_t i;
 
 	if (player->mo->eflags & MFE_VERTICALFLIP)
 		pviewheight = player->mo->z + player->mo->height - player->viewheight;
@@ -10603,28 +10598,45 @@ static void P_CalcPostImg(player_t *player)
 	}
 
 	// see if we are in heat (no, not THAT kind of heat...)
-
-	if (P_FindSpecialLineFromTag(13, sector->tag, -1) != -1)
-		*type = postimg_heat;
-	else if (sector->ffloors)
+	for (i = 0; i < sector->tags.count; i++)
 	{
-		ffloor_t *rover;
-		fixed_t topheight;
-		fixed_t bottomheight;
-
-		for (rover = sector->ffloors; rover; rover = rover->next)
+		if (Tag_FindLineSpecial(13, sector->tags.tags[i]) != -1)
 		{
-			if (!(rover->flags & FF_EXISTS))
-				continue;
+			*type = postimg_heat;
+			break;
+		}
+		else if (sector->ffloors)
+		{
+			ffloor_t *rover;
+			fixed_t topheight;
+			fixed_t bottomheight;
+			boolean gotres = false;
 
-			topheight    = P_GetFFloorTopZAt   (rover, player->mo->x, player->mo->y);
-			bottomheight = P_GetFFloorBottomZAt(rover, player->mo->x, player->mo->y);
+			for (rover = sector->ffloors; rover; rover = rover->next)
+			{
+				size_t j;
 
-			if (pviewheight >= topheight || pviewheight <= bottomheight)
-				continue;
+				if (!(rover->flags & FF_EXISTS))
+					continue;
 
-			if (P_FindSpecialLineFromTag(13, rover->master->frontsector->tag, -1) != -1)
-				*type = postimg_heat;
+				topheight    = P_GetFFloorTopZAt   (rover, player->mo->x, player->mo->y);
+				bottomheight = P_GetFFloorBottomZAt(rover, player->mo->x, player->mo->y);
+
+				if (pviewheight >= topheight || pviewheight <= bottomheight)
+					continue;
+
+				for (j = 0; j < rover->master->frontsector->tags.count; j++)
+				{
+					if (Tag_FindLineSpecial(13, rover->master->frontsector->tags.tags[j]) != -1)
+					{
+						*type = postimg_heat;
+						gotres = true;
+						break;
+					}
+				}
+			}
+			if (gotres)
+				break;
 		}
 	}
 
@@ -10723,22 +10735,21 @@ static sector_t *P_GetMinecartSector(fixed_t x, fixed_t y, fixed_t z, fixed_t *n
 static INT32 P_GetMinecartSpecialLine(sector_t *sec)
 {
 	INT32 line = -1;
+	size_t i;
 
 	if (!sec)
 		return line;
 
-	if (sec->tag != 0)
-		line = P_FindSpecialLineFromTag(16, sec->tag, -1);
+	for (i = 0; i < sec->tags.count; i++)
+		if (sec->tags.tags[i] != 0)
+			line = Tag_FindLineSpecial(16, sec->tags.tags[i]);
 
 	// Also try for lines facing the sector itself, with tag 0.
+	for (i = 0; i < sec->linecount; i++)
 	{
-		UINT32 i;
-		for (i = 0; i < sec->linecount; i++)
-		{
-			line_t *li = sec->lines[i];
-			if (li->tag == 0 && li->special == 16 && li->frontsector == sec)
-				line = li - lines;
-		}
+		line_t *li = sec->lines[i];
+		if (Tag_Find(&li->tags, 0) && li->special == 16 && li->frontsector == sec)
+			line = li - lines;
 	}
 
 	return line;
@@ -12297,12 +12308,14 @@ void P_PlayerThink(player_t *player)
 		sector_t *controlsec;
 		for (j=0; j<numsectors; j++)
 		{
+			mtag_t sectag = Tag_FGet(&sectors[j].tags);
 			controlsec = NULL;
 			// Does this sector have a water linedef?
 			for (i=0; i<numlines;i++)
 			{
+				mtag_t linetag = Tag_FGet(&lines[i].tags);
 				if ((lines[i].special == 121 || lines[i].special == 123)
-				&& lines[i].tag == sectors[j].tag)
+				&& linetag == sectag)
 				{
 					controlsec = lines[i].frontsector;
 					break;
@@ -12311,15 +12324,16 @@ void P_PlayerThink(player_t *player)
 
 			if (i < numlines && controlsec)
 			{
+				controlsectag = Tag_FGet(&controlsec->tags);
 				// Does this sector have a colormap?
 				for (i=0; i<numlines;i++)
 				{
-					if (lines[i].special == 606 && lines[i].tag == controlsec->tag)
+					if (lines[i].special == 606 && linetag == controlsectag)
 						break;
 				}
 
 				if (i == numlines)
-					CONS_Debug(DBG_GAMELOGIC, "%d, %d\n", j, sectors[j].tag);
+					CONS_Debug(DBG_GAMELOGIC, "%d, %d\n", j, sectag);
 			}
 		}
 
diff --git a/src/r_bsp.c b/src/r_bsp.c
index 56d038b44183a49ee7a5571251fce4af8fe888c8..6f2a90d2d5e6c8642b4dc0eded0d47afb189012b 100644
--- a/src/r_bsp.c
+++ b/src/r_bsp.c
@@ -21,6 +21,7 @@
 #include "p_local.h" // camera
 #include "p_slopes.h"
 #include "z_zone.h" // Check R_Prep3DFloors
+#include "taglist.h"
 
 seg_t *curline;
 side_t *sidedef;
@@ -374,7 +375,7 @@ boolean R_IsEmptyLine(seg_t *line, sector_t *front, sector_t *back)
 		// Consider colormaps
 		&& back->extra_colormap == front->extra_colormap
 		&& ((!front->ffloors && !back->ffloors)
-		|| front->tag == back->tag));
+		|| Tag_Compare(&front->tags, &back->tags)));
 }
 
 //
@@ -448,21 +449,25 @@ static void R_AddLine(seg_t *line)
 	// Portal line
 	if (line->linedef->special == 40 && line->side == 0)
 	{
+		// Render portal if recursiveness limit hasn't been reached.
+		// Otherwise, render the wall normally.
 		if (portalrender < cv_maxportals.value)
 		{
-			// Find the other side!
-			INT32 line2 = P_FindSpecialLineFromTag(40, line->linedef->tag, -1);
-			if (line->linedef == &lines[line2])
-				line2 = P_FindSpecialLineFromTag(40, line->linedef->tag, line2);
-			if (line2 >= 0) // found it!
+			size_t p;
+			mtag_t tag = Tag_FGet(&line->linedef->tags);
+			INT32 li1 = line->linedef-lines;
+			INT32 li2;
+
+			for (p = 0; (li2 = Tag_Iterate_Lines(tag, p)) >= 0; p++)
 			{
-				Portal_Add2Lines(line->linedef-lines, line2, x1, x2); // Remember the lines for later rendering
-				//return; // Don't fill in that space now!
+				// Skip invalid lines.
+				if ((tag != Tag_FGet(&lines[li2].tags)) || (lines[li1].special != lines[li2].special) || (li1 == li2))
+					continue;
+
+				Portal_Add2Lines(li1, li2, x1, x2);
 				goto clipsolid;
 			}
 		}
-		// Recursed TOO FAR (viewing a portal within a portal)
-		// So uhhh, render it as a normal wall instead or something ???
 	}
 
 	// Single sided line?
@@ -483,7 +488,7 @@ static void R_AddLine(seg_t *line)
 		if (!line->polyseg &&
 			!line->sidedef->midtexture
 			&& ((!frontsector->ffloors && !backsector->ffloors)
-				|| (frontsector->tag == backsector->tag)))
+				|| Tag_Compare(&frontsector->tags, &backsector->tags)))
 			return; // line is empty, don't even bother
 
 		goto clippass; // treat like wide open window instead
@@ -1048,11 +1053,6 @@ static void R_Subsector(size_t num)
 		}
 	}
 
-#ifdef FLOORSPLATS
-	if (sub->splats)
-		R_AddVisibleFloorSplats(sub);
-#endif
-
    // killough 9/18/98: Fix underwater slowdown, by passing real sector
    // instead of fake one. Improve sprite lighting by basing sprite
    // lightlevels on floor & ceiling lightlevels in the surrounding area.
diff --git a/src/r_data.c b/src/r_data.c
index dd36c2ee28623ce1ce16aa49709196009702e08b..af672f6dc024ee2c6840818982d586b17ceacb07 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -20,6 +20,7 @@
 #include "m_misc.h"
 #include "r_data.h"
 #include "r_textures.h"
+#include "r_patch.h"
 #include "r_picformats.h"
 #include "w_wad.h"
 #include "z_zone.h"
@@ -174,13 +175,15 @@ UINT8 ASTBlendPaletteIndexes(UINT8 background, UINT8 foreground, int style, UINT
 		if (alpha <= ASTTextureBlendingThreshold[1])
 		{
 			UINT8 *mytransmap;
+			INT32 trans;
 
 			// Is the patch way too translucent? Don't blend then.
 			if (alpha < ASTTextureBlendingThreshold[0])
 				return background;
 
 			// The equation's not exact but it works as intended. I'll call it a day for now.
-			mytransmap = transtables + ((8*(alpha) + 255/8)/(255 - 255/11) << FF_TRANSSHIFT);
+			trans = (8*(alpha) + 255/8)/(255 - 255/11);
+			mytransmap = R_GetTranslucencyTable(trans + 1);
 			if (background != 0xFF)
 				return *(mytransmap + (background<<8) + foreground);
 		}
@@ -1307,7 +1310,7 @@ void R_PrecacheLevel(void)
 		lump = sf->lumppat[a];\
 		if (devparm)\
 			spritememory += W_LumpLength(lump);\
-		W_CachePatchNum(lump, PU_PATCH);\
+		W_CachePatchNum(lump, PU_SPRITE);\
 	}
 			// see R_InitSprites for more about lumppat,lumpid
 			switch (sf->rotate)
diff --git a/src/r_defs.h b/src/r_defs.h
index 132106bc88c0dd0b28d93fb0cf24e51947bf804d..9c649fbc4508bf148787566eb6692f6111d24706 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -28,6 +28,8 @@
 #include "m_aatree.h"
 #endif
 
+#include "taglist.h"
+
 //
 // ClipWallSegment
 // Clips the given range of columns
@@ -281,8 +283,7 @@ typedef struct sector_s
 	INT32 ceilingpic;
 	INT16 lightlevel;
 	INT16 special;
-	UINT16 tag;
-	INT32 nexttag, firsttag; // for fast tag searches
+	taglist_t tags;
 
 	// origin for any sounds played by the sector
 	// also considered the center for e.g. Mario blocks
@@ -389,7 +390,7 @@ typedef struct line_s
 	// Animation related.
 	INT16 flags;
 	INT16 special;
-	INT16 tag;
+	taglist_t tags;
 	INT32 args[NUMLINEARGS];
 	char *stringargs[NUMLINESTRINGARGS];
 
@@ -409,10 +410,6 @@ typedef struct line_s
 	sector_t *backsector;
 
 	size_t validcount; // if == validcount, already checked
-#if 1//#ifdef WALLSPLATS
-	void *splats; // wallsplat_t list
-#endif
-	INT32 firsttag, nexttag; // improves searches for tags.
 	polyobj_t *polyobj; // Belongs to a polyobject?
 
 	char *text; // a concatenation of all front and back texture names, for linedef specials that require a string.
@@ -457,9 +454,6 @@ typedef struct subsector_s
 	INT16 numlines;
 	UINT16 firstline;
 	struct polyobj_s *polyList; // haleyjd 02/19/06: list of polyobjects
-#if 1//#ifdef FLOORSPLATS
-	void *splats; // floorsplat_t list
-#endif
 	size_t validcount;
 } subsector_t;
 
@@ -652,8 +646,12 @@ typedef enum
 	RGBA32          = 4,  // 32 bit rgba
 } pic_mode_t;
 
-#if defined(_MSC_VER)
-#pragma pack(1)
+#ifdef ROTSPRITE
+typedef struct
+{
+	INT32 angles;
+	void **patches;
+} rotsprite_t;
 #endif
 
 // Patches.
@@ -661,7 +659,26 @@ typedef enum
 // Patches are used for sprites and all masked pictures, and we compose
 // textures from the TEXTURES list of patches.
 //
-// WARNING: this structure is cloned in GLPatch_t
+typedef struct
+{
+	INT16 width, height;
+	INT16 leftoffset, topoffset;
+
+	INT32 *columnofs; // Column offsets. This is relative to patch->columns
+	UINT8 *columns; // Software column data
+
+	void *hardware; // OpenGL patch, allocated whenever necessary
+	void *flats[4]; // The patch as flats
+
+#ifdef ROTSPRITE
+	rotsprite_t *rotated; // Rotated patches
+#endif
+} patch_t;
+
+#if defined(_MSC_VER)
+#pragma pack(1)
+#endif
+
 typedef struct
 {
 	INT16 width;          // bounding box size
@@ -670,7 +687,7 @@ typedef struct
 	INT16 topoffset;      // pixels below the origin
 	INT32 columnofs[8];     // only [width] used
 	// the [0] is &columnofs[width]
-} ATTRPACK patch_t;
+} ATTRPACK softwarepatch_t;
 
 #ifdef _MSC_VER
 #pragma warning(disable :  4200)
@@ -696,14 +713,32 @@ typedef struct
 #pragma pack()
 #endif
 
-// rotsprite
-#ifdef ROTSPRITE
-typedef struct
+typedef enum
 {
-	patch_t *patch[16][ROTANGLES];
-	UINT16 cached;
-} rotsprite_t;
-#endif/*ROTSPRITE*/
+	RF_HORIZONTALFLIP   = 0x0001,   // Flip sprite horizontally
+	RF_VERTICALFLIP     = 0x0002,   // Flip sprite vertically
+	RF_ABSOLUTEOFFSETS  = 0x0004,   // Sprite uses the object's offsets absolutely, instead of relatively
+	RF_FLIPOFFSETS      = 0x0008,   // Relative object offsets are flipped with the sprite
+
+	RF_SPLATMASK        = 0x00F0,   // --Floor sprite flags
+	RF_SLOPESPLAT       = 0x0010,   // Rotate floor sprites by a slope
+	RF_OBJECTSLOPESPLAT = 0x0020,   // Rotate floor sprites by the object's standing slope
+	RF_NOSPLATBILLBOARD = 0x0040,   // Don't billboard floor sprites (faces forward from the view angle)
+	RF_NOSPLATROLLANGLE = 0x0080,   // Don't rotate floor sprites by the object's rollangle (uses rotated patches instead)
+
+	RF_BLENDMASK        = 0x0F00,   // --Blending modes
+	RF_FULLBRIGHT       = 0x0100,   // Sprite is drawn at full brightness
+	RF_FULLDARK         = 0x0200,   // Sprite is drawn completely dark
+	RF_NOCOLORMAPS      = 0x0400,   // Sprite is not drawn with colormaps
+
+	RF_SPRITETYPEMASK   = 0x7000,   // ---Different sprite types
+	RF_PAPERSPRITE      = 0x1000,   // Paper sprite
+	RF_FLOORSPRITE      = 0x2000,   // Floor sprite
+
+	RF_SHADOWDRAW       = 0x10000,  // Stretches and skews the sprite like a shadow.
+	RF_SHADOWEFFECTS    = 0x20000,  // Scales and becomes transparent like a shadow.
+	RF_DROPSHADOW       = (RF_SHADOWDRAW | RF_SHADOWEFFECTS | RF_FULLDARK),
+} renderflags_t;
 
 typedef enum
 {
@@ -717,24 +752,6 @@ typedef enum
 	SRF_NONE        = 0xff // Initial value
 } spriterotateflags_t;     // SRF's up!
 
-// Same as a patch_t, except just the header
-// and the wadnum/lumpnum combination that points
-// to wherever the patch is in memory.
-struct patchinfo_s
-{
-	INT16 width;          // bounding box size
-	INT16 height;
-	INT16 leftoffset;     // pixels to the left of origin
-	INT16 topoffset;      // pixels below the origin
-
-	UINT16 wadnum;        // the software patch lump num for when the patch
-	UINT16 lumpnum;       // was flushed, and we need to re-create it
-
-	// next patchinfo_t in memory
-	struct patchinfo_s *next;
-};
-typedef struct patchinfo_s patchinfo_t;
-
 //
 // Sprites are patches with a special naming convention so they can be
 //  recognized by R_InitSprites.
@@ -764,7 +781,7 @@ typedef struct
 	UINT16 flip;
 
 #ifdef ROTSPRITE
-	rotsprite_t rotsprite;
+	rotsprite_t *rotated[2][16]; // Rotated patches
 #endif
 } spriteframe_t;
 
diff --git a/src/r_draw.c b/src/r_draw.c
index 2b798c3bf383c42d22e99674e1ceb4521a85c352..d9ea942a2f22b301bdbd1762e0635f31ba085d6e 100644
--- a/src/r_draw.c
+++ b/src/r_draw.c
@@ -75,6 +75,7 @@ UINT8 *dc_source;
 #define NUMTRANSTABLES 9 // how many translucency tables are used
 
 UINT8 *transtables; // translucency tables
+UINT8 *blendtables[NUMBLENDMAPS];
 
 /**	\brief R_DrawTransColumn uses this
 */
@@ -98,15 +99,19 @@ INT32 dc_numlights = 0, dc_maxlights, dc_texheight;
 
 INT32 ds_y, ds_x1, ds_x2;
 lighttable_t *ds_colormap;
+lighttable_t *ds_translation; // Lactozilla: Sprite splat drawer
+
 fixed_t ds_xfrac, ds_yfrac, ds_xstep, ds_ystep;
+INT32 ds_waterofs, ds_bgofs;
+
 UINT16 ds_flatwidth, ds_flatheight;
 boolean ds_powersoftwo;
 
-UINT8 *ds_source; // start of a 64*64 tile image
+UINT8 *ds_source; // points to the start of a flat
 UINT8 *ds_transmap; // one of the translucency tables
 
-pslope_t *ds_slope; // Current slope being used
-floatv3_t ds_su[MAXVIDHEIGHT], ds_sv[MAXVIDHEIGHT], ds_sz[MAXVIDHEIGHT]; // Vectors for... stuff?
+// Vectors for Software's tilted slope drawers
+floatv3_t *ds_su, *ds_sv, *ds_sz;
 floatv3_t *ds_sup, *ds_svp, *ds_szp;
 float focallengthf, zeroheight;
 
@@ -115,10 +120,6 @@ float focallengthf, zeroheight;
 
 UINT32 nflatxshift, nflatyshift, nflatshiftup, nflatmask;
 
-// ==========================================================================
-//                        OLD DOOM FUZZY EFFECT
-// ==========================================================================
-
 // =========================================================================
 //                   TRANSLATION COLORMAP CODE
 // =========================================================================
@@ -138,11 +139,11 @@ UINT8 skincolor_modified[MAXSKINCOLORS];
 
 CV_PossibleValue_t Color_cons_t[MAXSKINCOLORS+1];
 
-/**	\brief The R_InitTranslationTables
+#define TRANSTAB_AMTMUL10 (256.0f / 10.0f)
 
-  load in color translation tables
+/** \brief Initializes the translucency tables used by the Software renderer.
 */
-void R_InitTranslationTables(void)
+void R_InitTranslucencyTables(void)
 {
 	// Load here the transparency lookup tables 'TINTTAB'
 	// NOTE: the TINTTAB resource MUST BE aligned on 64k for the asm
@@ -159,17 +160,94 @@ void R_InitTranslationTables(void)
 	W_ReadLump(W_GetNumForName("TRANS70"), transtables+0x60000);
 	W_ReadLump(W_GetNumForName("TRANS80"), transtables+0x70000);
 	W_ReadLump(W_GetNumForName("TRANS90"), transtables+0x80000);
+
+	R_GenerateBlendTables();
 }
 
+void R_GenerateBlendTables(void)
+{
+	INT32 i;
 
-/**	\brief	Generates a translation colormap.
+	for (i = 0; i < NUMBLENDMAPS; i++)
+	{
+		if (i == blendtab_modulate)
+			continue;
+		blendtables[i] = Z_MallocAlign((NUMTRANSTABLES + 1) * 0x10000, PU_STATIC, NULL, 16);
+	}
 
-	\param	dest_colormap	colormap to populate
-	\param	skinnum		number of skin, TC_DEFAULT or TC_BOSS
-	\param	color		translation color
+	for (i = 0; i <= 9; i++)
+	{
+		const size_t offs = (0x10000 * i);
+		const UINT8 alpha = TRANSTAB_AMTMUL10 * i;
 
-	\return	void
-*/
+		R_GenerateTranslucencyTable(blendtables[blendtab_add] + offs, AST_ADD, alpha);
+		R_GenerateTranslucencyTable(blendtables[blendtab_subtract] + offs, AST_SUBTRACT, alpha);
+		R_GenerateTranslucencyTable(blendtables[blendtab_reversesubtract] + offs, AST_REVERSESUBTRACT, alpha);
+	}
+
+	// Modulation blending only requires a single table
+	blendtables[blendtab_modulate] = Z_MallocAlign(0x10000, PU_STATIC, NULL, 16);
+	R_GenerateTranslucencyTable(blendtables[blendtab_modulate], AST_MODULATE, 0);
+}
+
+static colorlookup_t transtab_lut;
+
+void R_GenerateTranslucencyTable(UINT8 *table, int style, UINT8 blendamt)
+{
+	INT16 bg, fg;
+
+	if (table == NULL)
+		I_Error("R_GenerateTranslucencyTable: input table was NULL!");
+
+	InitColorLUT(&transtab_lut, pMasterPalette, false);
+
+	for (bg = 0; bg < 0xFF; bg++)
+	{
+		for (fg = 0; fg < 0xFF; fg++)
+		{
+			RGBA_t backrgba = V_GetMasterColor(bg);
+			RGBA_t frontrgba = V_GetMasterColor(fg);
+			RGBA_t result;
+
+			result.rgba = ASTBlendPixel(backrgba, frontrgba, style, blendamt);
+			table[((bg * 0x100) + fg)] = GetColorLUT(&transtab_lut, result.s.red, result.s.green, result.s.blue);
+		}
+	}
+}
+
+#define ClipTransLevel(trans) max(min((trans), NUMTRANSMAPS-2), 0)
+
+UINT8 *R_GetTranslucencyTable(INT32 alphalevel)
+{
+	return transtables + (ClipTransLevel(alphalevel-1) << FF_TRANSSHIFT);
+}
+
+UINT8 *R_GetBlendTable(int style, INT32 alphalevel)
+{
+	size_t offs = (ClipTransLevel(alphalevel) << FF_TRANSSHIFT);
+
+	// Lactozilla: Returns the equivalent to AST_TRANSLUCENT
+	// if no alpha style matches any of the blend tables.
+	switch (style)
+	{
+		case AST_ADD:
+			return blendtables[blendtab_add] + offs;
+		case AST_SUBTRACT:
+			return blendtables[blendtab_subtract] + offs;
+		case AST_REVERSESUBTRACT:
+			return blendtables[blendtab_reversesubtract] + offs;
+		case AST_MODULATE:
+			return blendtables[blendtab_modulate];
+		default:
+			break;
+	}
+
+	// Return a normal translucency table
+	if (--alphalevel >= 0)
+		return transtables + (ClipTransLevel(alphalevel) << FF_TRANSSHIFT);
+	else
+		return NULL;
+}
 
 // Define for getting accurate color brightness readings according to how the human eye sees them.
 // https://en.wikipedia.org/wiki/Relative_luminance
@@ -227,6 +305,14 @@ static void R_RainbowColormap(UINT8 *dest_colormap, UINT16 skincolor)
 
 #undef SETBRIGHTNESS
 
+/**	\brief	Generates a translation colormap.
+
+	\param	dest_colormap	colormap to populate
+	\param	skinnum		number of skin, TC_DEFAULT or TC_BOSS
+	\param	color		translation color
+
+	\return	void
+*/
 static void R_GenerateTranslationColormap(UINT8 *dest_colormap, INT32 skinnum, UINT16 color)
 {
 	INT32 i, starttranscolor, skinramplength;
diff --git a/src/r_draw.h b/src/r_draw.h
index 1ca22f18aa7f80840c9c367dcb2c0199bb62a5c0..9957541ca33bcfd84c37b03f4375184a722da459 100644
--- a/src/r_draw.h
+++ b/src/r_draw.h
@@ -37,7 +37,6 @@ extern UINT8 dc_hires;
 extern UINT8 *dc_source; // first pixel in a column
 
 // translucency stuff here
-extern UINT8 *transtables; // translucency tables, should be (*transtables)[5][256][256]
 extern UINT8 *dc_transmap;
 
 // translation stuff here
@@ -56,9 +55,14 @@ extern INT32 dc_texheight;
 
 extern INT32 ds_y, ds_x1, ds_x2;
 extern lighttable_t *ds_colormap;
+extern lighttable_t *ds_translation;
+
 extern fixed_t ds_xfrac, ds_yfrac, ds_xstep, ds_ystep;
+extern INT32 ds_waterofs, ds_bgofs;
+
 extern UINT16 ds_flatwidth, ds_flatheight;
 extern boolean ds_powersoftwo;
+
 extern UINT8 *ds_source;
 extern UINT8 *ds_transmap;
 
@@ -66,8 +70,8 @@ typedef struct {
 	float x, y, z;
 } floatv3_t;
 
-extern pslope_t *ds_slope; // Current slope being used
-extern floatv3_t ds_su[MAXVIDHEIGHT], ds_sv[MAXVIDHEIGHT], ds_sz[MAXVIDHEIGHT]; // Vectors for... stuff?
+// Vectors for Software's tilted slope drawers
+extern floatv3_t *ds_su, *ds_sv, *ds_sz;
 extern floatv3_t *ds_sup, *ds_svp, *ds_szp;
 extern float focallengthf, zeroheight;
 
@@ -110,17 +114,36 @@ extern lumpnum_t viewborderlump[8];
 #define TC_BLINK      -6 // For item blinking, according to kart
 #define TC_DASHMODE   -7 // For Metal Sonic's dashmode
 
+// Custom player skin translation
 // Initialize color translation tables, for player rendering etc.
-void R_InitTranslationTables(void);
 UINT8* R_GetTranslationColormap(INT32 skinnum, skincolornum_t color, UINT8 flags);
 void R_FlushTranslationColormapCache(void);
 UINT16 R_GetColorByName(const char *name);
 UINT16 R_GetSuperColorByName(const char *name);
 
+extern UINT8 *transtables; // translucency tables, should be (*transtables)[5][256][256]
+
+enum
+{
+	blendtab_add,
+	blendtab_subtract,
+	blendtab_reversesubtract,
+	blendtab_modulate,
+	NUMBLENDMAPS
+};
+
+extern UINT8 *blendtables[NUMBLENDMAPS];
+
+void R_InitTranslucencyTables(void);
+void R_GenerateBlendTables(void);
+void R_GenerateTranslucencyTable(UINT8 *table, int style, UINT8 blendamt);
+
+UINT8 *R_GetTranslucencyTable(INT32 alphalevel);
+UINT8 *R_GetBlendTable(int style, INT32 alphalevel);
+
 // Color ramp modification should force a recache
 extern UINT8 skincolor_modified[];
 
-// Custom player skin translation
 void R_InitViewBuffer(INT32 width, INT32 height);
 void R_InitViewBorder(void);
 void R_VideoErase(size_t ofs, INT32 count);
@@ -149,39 +172,47 @@ void R_Draw2sMultiPatchTranslucentColumn_8(void);
 void R_DrawFogColumn_8(void);
 void R_DrawColumnShadowed_8(void);
 
+#define PLANELIGHTFLOAT (BASEVIDWIDTH * BASEVIDWIDTH / vid.width / (zeroheight - FIXED_TO_FLOAT(viewz)) / 21.0f * FIXED_TO_FLOAT(fovtan))
+
 void R_DrawSpan_8(void);
-void R_DrawSplat_8(void);
 void R_DrawTranslucentSpan_8(void);
-void R_DrawTranslucentSplat_8(void);
 void R_DrawTiltedSpan_8(void);
 void R_DrawTiltedTranslucentSpan_8(void);
-#ifndef NOWATER
-void R_DrawTiltedTranslucentWaterSpan_8(void);
-#endif
+
+void R_DrawSplat_8(void);
+void R_DrawTranslucentSplat_8(void);
 void R_DrawTiltedSplat_8(void);
+
+void R_DrawFloorSprite_8(void);
+void R_DrawTranslucentFloorSprite_8(void);
+void R_DrawTiltedFloorSprite_8(void);
+void R_DrawTiltedTranslucentFloorSprite_8(void);
+
 void R_CalcTiltedLighting(fixed_t start, fixed_t end);
 extern INT32 tiltlighting[MAXVIDWIDTH];
-#ifndef NOWATER
+
 void R_DrawTranslucentWaterSpan_8(void);
-extern INT32 ds_bgofs;
-extern INT32 ds_waterofs;
-#endif
+void R_DrawTiltedTranslucentWaterSpan_8(void);
+
 void R_DrawFogSpan_8(void);
 
 // Lactozilla: Non-powers-of-two
 void R_DrawSpan_NPO2_8(void);
 void R_DrawTranslucentSpan_NPO2_8(void);
-void R_DrawSplat_NPO2_8(void);
-void R_DrawTranslucentSplat_NPO2_8(void);
 void R_DrawTiltedSpan_NPO2_8(void);
 void R_DrawTiltedTranslucentSpan_NPO2_8(void);
-#ifndef NOWATER
-void R_DrawTiltedTranslucentWaterSpan_NPO2_8(void);
-#endif
+
+void R_DrawSplat_NPO2_8(void);
+void R_DrawTranslucentSplat_NPO2_8(void);
 void R_DrawTiltedSplat_NPO2_8(void);
-#ifndef NOWATER
+
+void R_DrawFloorSprite_NPO2_8(void);
+void R_DrawTranslucentFloorSprite_NPO2_8(void);
+void R_DrawTiltedFloorSprite_NPO2_8(void);
+void R_DrawTiltedTranslucentFloorSprite_NPO2_8(void);
+
 void R_DrawTranslucentWaterSpan_NPO2_8(void);
-#endif
+void R_DrawTiltedTranslucentWaterSpan_NPO2_8(void);
 
 #ifdef USEASM
 void ASMCALL R_DrawColumn_8_ASM(void);
diff --git a/src/r_draw8.c b/src/r_draw8.c
index 940ea724b31ef2411514799d4a9f7df7e284c907..e78ba8a6c49b8f39a9c54fe0af814340c9462641 100644
--- a/src/r_draw8.c
+++ b/src/r_draw8.c
@@ -536,6 +536,9 @@ void R_DrawTranslatedColumn_8(void)
 // SPANS
 // ==========================================================================
 
+#define SPANSIZE 16
+#define INVSPAN 0.0625f
+
 /**	\brief The R_DrawSpan_8 function
 	Draws the actual span.
 */
@@ -643,8 +646,6 @@ void R_CalcTiltedLighting(fixed_t start, fixed_t end)
 	}
 }
 
-#define PLANELIGHTFLOAT (BASEVIDWIDTH * BASEVIDWIDTH / vid.width / (zeroheight - FIXED_TO_FLOAT(viewz)) / 21.0f * FIXED_TO_FLOAT(fovtan))
-
 /**	\brief The R_DrawTiltedSpan_8 function
 	Draw slopes! Holy sheit!
 */
@@ -704,9 +705,6 @@ void R_DrawTiltedSpan_8(void)
 		vz += ds_svp->x;
 	} while (--width >= 0);
 #else
-#define SPANSIZE 16
-#define INVSPAN	0.0625f
-
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
@@ -839,9 +837,6 @@ void R_DrawTiltedTranslucentSpan_8(void)
 		vz += ds_svp->x;
 	} while (--width >= 0);
 #else
-#define SPANSIZE 16
-#define INVSPAN	0.0625f
-
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
@@ -916,7 +911,6 @@ void R_DrawTiltedTranslucentSpan_8(void)
 #endif
 }
 
-#ifndef NOWATER
 /**	\brief The R_DrawTiltedTranslucentWaterSpan_8 function
 	Like DrawTiltedTranslucentSpan, but for water
 */
@@ -977,9 +971,6 @@ void R_DrawTiltedTranslucentWaterSpan_8(void)
 		vz += ds_svp->x;
 	} while (--width >= 0);
 #else
-#define SPANSIZE 16
-#define INVSPAN	0.0625f
-
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
@@ -1053,7 +1044,6 @@ void R_DrawTiltedTranslucentWaterSpan_8(void)
 	}
 #endif
 }
-#endif // NOWATER
 
 void R_DrawTiltedSplat_8(void)
 {
@@ -1116,9 +1106,6 @@ void R_DrawTiltedSplat_8(void)
 		vz += ds_svp->x;
 	} while (--width >= 0);
 #else
-#define SPANSIZE 16
-#define INVSPAN	0.0625f
-
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
@@ -1419,6 +1406,448 @@ void R_DrawTranslucentSplat_8 (void)
 	}
 }
 
+/**	\brief The R_DrawFloorSprite_8 function
+	Just like R_DrawSplat_8, but for floor sprites.
+*/
+void R_DrawFloorSprite_8 (void)
+{
+	fixed_t xposition;
+	fixed_t yposition;
+	fixed_t xstep, ystep;
+
+	UINT16 *source;
+	UINT8 *colormap;
+	UINT8 *translation;
+	UINT8 *dest;
+	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+
+	size_t count = (ds_x2 - ds_x1 + 1);
+	UINT32 val;
+
+	xposition = ds_xfrac; yposition = ds_yfrac;
+	xstep = ds_xstep; ystep = ds_ystep;
+
+	// SoM: we only need 6 bits for the integer part (0 thru 63) so the rest
+	// can be used for the fraction part. This allows calculation of the memory address in the
+	// texture with two shifts, an OR and one AND. (see below)
+	// for texture sizes > 64 the amount of precision we can allow will decrease, but only by one
+	// bit per power of two (obviously)
+	// Ok, because I was able to eliminate the variable spot below, this function is now FASTER
+	// than the original span renderer. Whodathunkit?
+	xposition <<= nflatshiftup; yposition <<= nflatshiftup;
+	xstep <<= nflatshiftup; ystep <<= nflatshiftup;
+
+	source = (UINT16 *)ds_source;
+	colormap = ds_colormap;
+	translation = ds_translation;
+	dest = ylookup[ds_y] + columnofs[ds_x1];
+
+	while (count >= 8)
+	{
+		// SoM: Why didn't I see this earlier? the spot variable is a waste now because we don't
+		// have the uber complicated math to calculate it now, so that was a memory write we didn't
+		// need!
+		//
+		// <Callum> 4194303 = (2048x2048)-1 (2048x2048 is maximum flat size)
+		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+		val &= 4194303;
+		val = source[val];
+		if (val & 0xFF00)
+			dest[0] = colormap[translation[val & 0xFF]];
+		xposition += xstep;
+		yposition += ystep;
+
+		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+		val &= 4194303;
+		val = source[val];
+		if (val & 0xFF00)
+			dest[1] = colormap[translation[val & 0xFF]];
+		xposition += xstep;
+		yposition += ystep;
+
+		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+		val &= 4194303;
+		val = source[val];
+		if (val & 0xFF00)
+			dest[2] = colormap[translation[val & 0xFF]];
+		xposition += xstep;
+		yposition += ystep;
+
+		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+		val &= 4194303;
+		val = source[val];
+		if (val & 0xFF00)
+			dest[3] = colormap[translation[val & 0xFF]];
+		xposition += xstep;
+		yposition += ystep;
+
+		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+		val &= 4194303;
+		val = source[val];
+		if (val & 0xFF00)
+			dest[4] = colormap[translation[val & 0xFF]];
+		xposition += xstep;
+		yposition += ystep;
+
+		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+		val &= 4194303;
+		val = source[val];
+		if (val & 0xFF00)
+			dest[5] = colormap[translation[val & 0xFF]];
+		xposition += xstep;
+		yposition += ystep;
+
+		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+		val &= 4194303;
+		val = source[val];
+		if (val & 0xFF00)
+			dest[6] = colormap[translation[val & 0xFF]];
+		xposition += xstep;
+		yposition += ystep;
+
+		val = (((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift);
+		val &= 4194303;
+		val = source[val];
+		if (val & 0xFF00)
+			dest[7] = colormap[translation[val & 0xFF]];
+		xposition += xstep;
+		yposition += ystep;
+
+		dest += 8;
+		count -= 8;
+	}
+	while (count-- && dest <= deststop)
+	{
+		val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+		if (val & 0xFF00)
+			*dest = colormap[translation[val & 0xFF]];
+		dest++;
+		xposition += xstep;
+		yposition += ystep;
+	}
+}
+
+/**	\brief The R_DrawTranslucentFloorSplat_8 function
+	Just like R_DrawFloorSprite_8, but is translucent!
+*/
+void R_DrawTranslucentFloorSprite_8 (void)
+{
+	fixed_t xposition;
+	fixed_t yposition;
+	fixed_t xstep, ystep;
+
+	UINT16 *source;
+	UINT8 *colormap;
+	UINT8 *translation;
+	UINT8 *dest;
+	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+
+	size_t count = (ds_x2 - ds_x1 + 1);
+	UINT32 val;
+
+	xposition = ds_xfrac; yposition = ds_yfrac;
+	xstep = ds_xstep; ystep = ds_ystep;
+
+	// SoM: we only need 6 bits for the integer part (0 thru 63) so the rest
+	// can be used for the fraction part. This allows calculation of the memory address in the
+	// texture with two shifts, an OR and one AND. (see below)
+	// for texture sizes > 64 the amount of precision we can allow will decrease, but only by one
+	// bit per power of two (obviously)
+	// Ok, because I was able to eliminate the variable spot below, this function is now FASTER
+	// than the original span renderer. Whodathunkit?
+	xposition <<= nflatshiftup; yposition <<= nflatshiftup;
+	xstep <<= nflatshiftup; ystep <<= nflatshiftup;
+
+	source = (UINT16 *)ds_source;
+	colormap = ds_colormap;
+	translation = ds_translation;
+	dest = ylookup[ds_y] + columnofs[ds_x1];
+
+	while (count >= 8)
+	{
+		// SoM: Why didn't I see this earlier? the spot variable is a waste now because we don't
+		// have the uber complicated math to calculate it now, so that was a memory write we didn't
+		// need!
+		val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+		if (val & 0xFF00)
+			dest[0] = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + dest[0]);
+		xposition += xstep;
+		yposition += ystep;
+
+		val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+		if (val & 0xFF00)
+			dest[1] = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + dest[1]);
+		xposition += xstep;
+		yposition += ystep;
+
+		val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+		if (val & 0xFF00)
+			dest[2] = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + dest[2]);
+		xposition += xstep;
+		yposition += ystep;
+
+		val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+		if (val & 0xFF00)
+			dest[3] = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + dest[3]);
+		xposition += xstep;
+		yposition += ystep;
+
+		val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+		if (val & 0xFF00)
+			dest[4] = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + dest[4]);
+		xposition += xstep;
+		yposition += ystep;
+
+		val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+		if (val & 0xFF00)
+			dest[5] = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + dest[5]);
+		xposition += xstep;
+		yposition += ystep;
+
+		val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+		if (val & 0xFF00)
+			dest[6] = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + dest[6]);
+		xposition += xstep;
+		yposition += ystep;
+
+		val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+		if (val & 0xFF00)
+			dest[7] = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + dest[7]);
+		xposition += xstep;
+		yposition += ystep;
+
+		dest += 8;
+		count -= 8;
+	}
+	while (count-- && dest <= deststop)
+	{
+		val = source[(((UINT32)yposition >> nflatyshift) & nflatmask) | ((UINT32)xposition >> nflatxshift)];
+		if (val & 0xFF00)
+			*dest = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + *dest);
+		dest++;
+		xposition += xstep;
+		yposition += ystep;
+	}
+}
+
+/**	\brief The R_DrawTiltedFloorSprite_8 function
+	Draws a tilted floor sprite.
+*/
+void R_DrawTiltedFloorSprite_8(void)
+{
+	// x1, x2 = ds_x1, ds_x2
+	int width = ds_x2 - ds_x1;
+	double iz, uz, vz;
+	UINT32 u, v;
+	int i;
+
+	UINT16 *source;
+	UINT8 *colormap;
+	UINT8 *translation;
+	UINT8 *dest;
+	UINT16 val;
+
+	double startz, startu, startv;
+	double izstep, uzstep, vzstep;
+	double endz, endu, endv;
+	UINT32 stepu, stepv;
+
+	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
+	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
+	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
+
+	dest = ylookup[ds_y] + columnofs[ds_x1];
+	source = (UINT16 *)ds_source;
+	colormap = ds_colormap;
+	translation = ds_translation;
+
+	startz = 1.f/iz;
+	startu = uz*startz;
+	startv = vz*startz;
+
+	izstep = ds_szp->x * SPANSIZE;
+	uzstep = ds_sup->x * SPANSIZE;
+	vzstep = ds_svp->x * SPANSIZE;
+	//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) + viewx;
+		v = (INT64)(startv) + viewy;
+
+		for (i = SPANSIZE-1; i >= 0; i--)
+		{
+			val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+			if (val & 0xFF00)
+				*dest = colormap[translation[val & 0xFF]];
+			dest++;
+
+			u += stepu;
+			v += stepv;
+		}
+		startu = endu;
+		startv = endv;
+		width -= SPANSIZE;
+	}
+	if (width > 0)
+	{
+		if (width == 1)
+		{
+			u = (INT64)(startu);
+			v = (INT64)(startv);
+			val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+			if (val & 0xFF00)
+				*dest = colormap[translation[val & 0xFF]];
+		}
+		else
+		{
+			double left = width;
+			iz += ds_szp->x * left;
+			uz += ds_sup->x * left;
+			vz += ds_svp->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) + viewx;
+			v = (INT64)(startv) + viewy;
+
+			for (; width != 0; width--)
+			{
+				val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+				if (val & 0xFF00)
+					*dest = colormap[translation[val & 0xFF]];
+				dest++;
+
+				u += stepu;
+				v += stepv;
+			}
+		}
+	}
+}
+
+/**	\brief The R_DrawTiltedTranslucentFloorSprite_8 function
+	Draws a tilted, translucent, floor sprite.
+*/
+void R_DrawTiltedTranslucentFloorSprite_8(void)
+{
+	// x1, x2 = ds_x1, ds_x2
+	int width = ds_x2 - ds_x1;
+	double iz, uz, vz;
+	UINT32 u, v;
+	int i;
+
+	UINT16 *source;
+	UINT8 *colormap;
+	UINT8 *translation;
+	UINT8 *dest;
+	UINT16 val;
+
+	double startz, startu, startv;
+	double izstep, uzstep, vzstep;
+	double endz, endu, endv;
+	UINT32 stepu, stepv;
+
+	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
+	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
+	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
+
+	dest = ylookup[ds_y] + columnofs[ds_x1];
+	source = (UINT16 *)ds_source;
+	colormap = ds_colormap;
+	translation = ds_translation;
+
+	startz = 1.f/iz;
+	startu = uz*startz;
+	startv = vz*startz;
+
+	izstep = ds_szp->x * SPANSIZE;
+	uzstep = ds_sup->x * SPANSIZE;
+	vzstep = ds_svp->x * SPANSIZE;
+	//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) + viewx;
+		v = (INT64)(startv) + viewy;
+
+		for (i = SPANSIZE-1; i >= 0; i--)
+		{
+			val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+			if (val & 0xFF00)
+				*dest = *(ds_transmap + (colormap[translation[val & 0xFF]] << 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);
+			val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+			if (val & 0xFF00)
+				*dest = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + *dest);
+		}
+		else
+		{
+			double left = width;
+			iz += ds_szp->x * left;
+			uz += ds_sup->x * left;
+			vz += ds_svp->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) + viewx;
+			v = (INT64)(startv) + viewy;
+
+			for (; width != 0; width--)
+			{
+				val = source[((v >> nflatyshift) & nflatmask) | (u >> nflatxshift)];
+				if (val & 0xFF00)
+					*dest = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + *dest);
+				dest++;
+
+				u += stepu;
+				v += stepv;
+			}
+		}
+	}
+}
+
 /**	\brief The R_DrawTranslucentSpan_8 function
 	Draws the actual span with translucency.
 */
@@ -1503,7 +1932,6 @@ void R_DrawTranslucentSpan_8 (void)
 	}
 }
 
-#ifndef NOWATER
 void R_DrawTranslucentWaterSpan_8(void)
 {
 	UINT32 xposition;
@@ -1580,7 +2008,6 @@ void R_DrawTranslucentWaterSpan_8(void)
 		yposition += ystep;
 	}
 }
-#endif
 
 /**	\brief The R_DrawFogSpan_8 function
 	Draws the actual span with fogging.
diff --git a/src/r_draw8_npo2.c b/src/r_draw8_npo2.c
index 630b36e6f7cd9afce187ac5e29738b94c7f63708..a34a20e9a9737241bbc183d71f8bae01a982cb7a 100644
--- a/src/r_draw8_npo2.c
+++ b/src/r_draw8_npo2.c
@@ -15,6 +15,9 @@
 // SPANS
 // ==========================================================================
 
+#define SPANSIZE 16
+#define INVSPAN 0.0625f
+
 /**	\brief The R_DrawSpan_NPO2_8 function
 	Draws the actual span.
 */
@@ -83,8 +86,6 @@ void R_DrawSpan_NPO2_8 (void)
 	}
 }
 
-#define PLANELIGHTFLOAT (BASEVIDWIDTH * BASEVIDWIDTH / vid.width / (zeroheight - FIXED_TO_FLOAT(viewz)) / 21.0f * FIXED_TO_FLOAT(fovtan))
-
 /**	\brief The R_DrawTiltedSpan_NPO2_8 function
 	Draw slopes! Holy sheit!
 */
@@ -159,9 +160,6 @@ void R_DrawTiltedSpan_NPO2_8(void)
 		vz += ds_svp->x;
 	} while (--width >= 0);
 #else
-#define SPANSIZE 16
-#define INVSPAN	0.0625f
-
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
@@ -354,9 +352,6 @@ void R_DrawTiltedTranslucentSpan_NPO2_8(void)
 		vz += ds_svp->x;
 	} while (--width >= 0);
 #else
-#define SPANSIZE 16
-#define INVSPAN	0.0625f
-
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
@@ -553,9 +548,6 @@ void R_DrawTiltedSplat_NPO2_8(void)
 		vz += ds_svp->x;
 	} while (--width >= 0);
 #else
-#define SPANSIZE 16
-#define INVSPAN	0.0625f
-
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
@@ -818,6 +810,446 @@ void R_DrawTranslucentSplat_NPO2_8 (void)
 	}
 }
 
+/**	\brief The R_DrawFloorSprite_NPO2_8 function
+	Just like R_DrawSplat_NPO2_8, but for floor sprites.
+*/
+void R_DrawFloorSprite_NPO2_8 (void)
+{
+	fixed_t xposition;
+	fixed_t yposition;
+	fixed_t xstep, ystep;
+	fixed_t x, y;
+	fixed_t fixedwidth, fixedheight;
+
+	UINT16 *source;
+	UINT8 *translation;
+	UINT8 *colormap;
+	UINT8 *dest;
+	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+
+	size_t count = (ds_x2 - ds_x1 + 1);
+	UINT32 val;
+
+	xposition = ds_xfrac; yposition = ds_yfrac;
+	xstep = ds_xstep; ystep = ds_ystep;
+
+	source = (UINT16 *)ds_source;
+	colormap = ds_colormap;
+	translation = ds_translation;
+	dest = ylookup[ds_y] + columnofs[ds_x1];
+
+	fixedwidth = ds_flatwidth << FRACBITS;
+	fixedheight = ds_flatheight << FRACBITS;
+
+	// Fix xposition and yposition if they are out of bounds.
+	if (xposition < 0)
+		xposition = fixedwidth - ((UINT32)(fixedwidth - xposition) % fixedwidth);
+	else if (xposition >= fixedwidth)
+		xposition %= fixedwidth;
+	if (yposition < 0)
+		yposition = fixedheight - ((UINT32)(fixedheight - yposition) % fixedheight);
+	else if (yposition >= fixedheight)
+		yposition %= fixedheight;
+
+	while (count-- && dest <= deststop)
+	{
+		// The loops here keep the texture coordinates within the texture.
+		// They will rarely iterate multiple times, and are cheaper than a modulo operation,
+		// even if using libdivide.
+		if (xstep < 0) // These if statements are hopefully hoisted by the compiler to above this loop
+			while (xposition < 0)
+				xposition += fixedwidth;
+		else
+			while (xposition >= fixedwidth)
+				xposition -= fixedwidth;
+		if (ystep < 0)
+			while (yposition < 0)
+				yposition += fixedheight;
+		else
+			while (yposition >= fixedheight)
+				yposition -= fixedheight;
+
+		x = (xposition >> FRACBITS);
+		y = (yposition >> FRACBITS);
+		val = source[((y * ds_flatwidth) + x)];
+		if (val & 0xFF00)
+			*dest = colormap[translation[val & 0xFF]];
+		dest++;
+		xposition += xstep;
+		yposition += ystep;
+	}
+}
+
+/**	\brief The R_DrawTranslucentFloorSprite_NPO2_8 function
+	Just like R_DrawFloorSprite_NPO2_8, but is translucent!
+*/
+void R_DrawTranslucentFloorSprite_NPO2_8 (void)
+{
+	fixed_t xposition;
+	fixed_t yposition;
+	fixed_t xstep, ystep;
+	fixed_t x, y;
+	fixed_t fixedwidth, fixedheight;
+
+	UINT16 *source;
+	UINT8 *translation;
+	UINT8 *colormap;
+	UINT8 *dest;
+	const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
+
+	size_t count = (ds_x2 - ds_x1 + 1);
+	UINT32 val;
+
+	xposition = ds_xfrac; yposition = ds_yfrac;
+	xstep = ds_xstep; ystep = ds_ystep;
+
+	source = (UINT16 *)ds_source;
+	colormap = ds_colormap;
+	translation = ds_translation;
+	dest = ylookup[ds_y] + columnofs[ds_x1];
+
+	fixedwidth = ds_flatwidth << FRACBITS;
+	fixedheight = ds_flatheight << FRACBITS;
+
+	// Fix xposition and yposition if they are out of bounds.
+	if (xposition < 0)
+		xposition = fixedwidth - ((UINT32)(fixedwidth - xposition) % fixedwidth);
+	else if (xposition >= fixedwidth)
+		xposition %= fixedwidth;
+	if (yposition < 0)
+		yposition = fixedheight - ((UINT32)(fixedheight - yposition) % fixedheight);
+	else if (yposition >= fixedheight)
+		yposition %= fixedheight;
+
+	while (count-- && dest <= deststop)
+	{
+		// The loops here keep the texture coordinates within the texture.
+		// They will rarely iterate multiple times, and are cheaper than a modulo operation,
+		// even if using libdivide.
+		if (xstep < 0) // These if statements are hopefully hoisted by the compiler to above this loop
+			while (xposition < 0)
+				xposition += fixedwidth;
+		else
+			while (xposition >= fixedwidth)
+				xposition -= fixedwidth;
+		if (ystep < 0)
+			while (yposition < 0)
+				yposition += fixedheight;
+		else
+			while (yposition >= fixedheight)
+				yposition -= fixedheight;
+
+		x = (xposition >> FRACBITS);
+		y = (yposition >> FRACBITS);
+		val = source[((y * ds_flatwidth) + x)];
+		if (val & 0xFF00)
+			*dest = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + *dest);
+		dest++;
+		xposition += xstep;
+		yposition += ystep;
+	}
+}
+
+/**	\brief The R_DrawTiltedFloorSprite_NPO2_8 function
+	Draws a tilted floor sprite.
+*/
+void R_DrawTiltedFloorSprite_NPO2_8(void)
+{
+	// x1, x2 = ds_x1, ds_x2
+	int width = ds_x2 - ds_x1;
+	double iz, uz, vz;
+	UINT32 u, v;
+	int i;
+
+	UINT16 *source;
+	UINT8 *colormap;
+	UINT8 *translation;
+	UINT8 *dest;
+	UINT16 val;
+
+	double startz, startu, startv;
+	double izstep, uzstep, vzstep;
+	double endz, endu, endv;
+	UINT32 stepu, stepv;
+
+	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
+	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
+	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
+
+	dest = ylookup[ds_y] + columnofs[ds_x1];
+	source = (UINT16 *)ds_source;
+	colormap = ds_colormap;
+	translation = ds_translation;
+
+	startz = 1.f/iz;
+	startu = uz*startz;
+	startv = vz*startz;
+
+	izstep = ds_szp->x * SPANSIZE;
+	uzstep = ds_sup->x * SPANSIZE;
+	vzstep = ds_svp->x * SPANSIZE;
+	//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) + viewx;
+		v = (INT64)(startv) + viewy;
+
+		for (i = SPANSIZE-1; i >= 0; i--)
+		{
+			// Lactozilla: Non-powers-of-two
+			fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+			fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+			// Carefully align all of my Friends.
+			if (x < 0)
+				x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+			if (y < 0)
+				y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+			x %= ds_flatwidth;
+			y %= ds_flatheight;
+
+			val = source[((y * ds_flatwidth) + x)];
+			if (val & 0xFF00)
+				*dest = colormap[translation[val & 0xFF]];
+			dest++;
+
+			u += stepu;
+			v += stepv;
+		}
+		startu = endu;
+		startv = endv;
+		width -= SPANSIZE;
+	}
+	if (width > 0)
+	{
+		if (width == 1)
+		{
+			u = (INT64)(startu);
+			v = (INT64)(startv);
+			// Lactozilla: Non-powers-of-two
+			{
+				fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+				fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+				// Carefully align all of my Friends.
+				if (x < 0)
+					x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+				if (y < 0)
+					y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+				x %= ds_flatwidth;
+				y %= ds_flatheight;
+
+				val = source[((y * ds_flatwidth) + x)];
+				if (val & 0xFF00)
+					*dest = colormap[translation[val & 0xFF]];
+			}
+		}
+		else
+		{
+			double left = width;
+			iz += ds_szp->x * left;
+			uz += ds_sup->x * left;
+			vz += ds_svp->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) + viewx;
+			v = (INT64)(startv) + viewy;
+
+			for (; width != 0; width--)
+			{
+				// Lactozilla: Non-powers-of-two
+				fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+				fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+				// Carefully align all of my Friends.
+				if (x < 0)
+					x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+				if (y < 0)
+					y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+				x %= ds_flatwidth;
+				y %= ds_flatheight;
+
+				val = source[((y * ds_flatwidth) + x)];
+				if (val & 0xFF00)
+					*dest = colormap[translation[val & 0xFF]];
+				dest++;
+
+				u += stepu;
+				v += stepv;
+			}
+		}
+	}
+}
+
+/**	\brief The R_DrawTiltedTranslucentFloorSprite_NPO2_8 function
+	Draws a tilted, translucent, floor sprite.
+*/
+void R_DrawTiltedTranslucentFloorSprite_NPO2_8(void)
+{
+	// x1, x2 = ds_x1, ds_x2
+	int width = ds_x2 - ds_x1;
+	double iz, uz, vz;
+	UINT32 u, v;
+	int i;
+
+	UINT16 *source;
+	UINT8 *colormap;
+	UINT8 *translation;
+	UINT8 *dest;
+	UINT16 val;
+
+	double startz, startu, startv;
+	double izstep, uzstep, vzstep;
+	double endz, endu, endv;
+	UINT32 stepu, stepv;
+
+	iz = ds_szp->z + ds_szp->y*(centery-ds_y) + ds_szp->x*(ds_x1-centerx);
+	uz = ds_sup->z + ds_sup->y*(centery-ds_y) + ds_sup->x*(ds_x1-centerx);
+	vz = ds_svp->z + ds_svp->y*(centery-ds_y) + ds_svp->x*(ds_x1-centerx);
+
+	dest = ylookup[ds_y] + columnofs[ds_x1];
+	source = (UINT16 *)ds_source;
+	colormap = ds_colormap;
+	translation = ds_translation;
+
+	startz = 1.f/iz;
+	startu = uz*startz;
+	startv = vz*startz;
+
+	izstep = ds_szp->x * SPANSIZE;
+	uzstep = ds_sup->x * SPANSIZE;
+	vzstep = ds_svp->x * SPANSIZE;
+	//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) + viewx;
+		v = (INT64)(startv) + viewy;
+
+		for (i = SPANSIZE-1; i >= 0; i--)
+		{
+			// Lactozilla: Non-powers-of-two
+			fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+			fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+			// Carefully align all of my Friends.
+			if (x < 0)
+				x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+			if (y < 0)
+				y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+			x %= ds_flatwidth;
+			y %= ds_flatheight;
+
+			val = source[((y * ds_flatwidth) + x)];
+			if (val & 0xFF00)
+				*dest = *(ds_transmap + (colormap[translation[val & 0xFF]] << 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);
+			// Lactozilla: Non-powers-of-two
+			{
+				fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+				fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+				// Carefully align all of my Friends.
+				if (x < 0)
+					x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+				if (y < 0)
+					y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+				x %= ds_flatwidth;
+				y %= ds_flatheight;
+
+				val = source[((y * ds_flatwidth) + x)];
+				if (val & 0xFF00)
+					*dest = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + *dest);
+			}
+		}
+		else
+		{
+			double left = width;
+			iz += ds_szp->x * left;
+			uz += ds_sup->x * left;
+			vz += ds_svp->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) + viewx;
+			v = (INT64)(startv) + viewy;
+
+			for (; width != 0; width--)
+			{
+				// Lactozilla: Non-powers-of-two
+				fixed_t x = (((fixed_t)u-viewx) >> FRACBITS);
+				fixed_t y = (((fixed_t)v-viewy) >> FRACBITS);
+
+				// Carefully align all of my Friends.
+				if (x < 0)
+					x = ds_flatwidth - ((UINT32)(ds_flatwidth - x) % ds_flatwidth);
+				if (y < 0)
+					y = ds_flatheight - ((UINT32)(ds_flatheight - y) % ds_flatheight);
+
+				x %= ds_flatwidth;
+				y %= ds_flatheight;
+
+				val = source[((y * ds_flatwidth) + x)];
+				if (val & 0xFF00)
+					*dest = *(ds_transmap + (colormap[translation[val & 0xFF]] << 8) + *dest);
+				dest++;
+
+				u += stepu;
+				v += stepv;
+			}
+		}
+	}
+}
+
 /**	\brief The R_DrawTranslucentSpan_NPO2_8 function
 	Draws the actual span with translucency.
 */
@@ -885,7 +1317,6 @@ void R_DrawTranslucentSpan_NPO2_8 (void)
 	}
 }
 
-#ifndef NOWATER
 void R_DrawTranslucentWaterSpan_NPO2_8(void)
 {
 	fixed_t xposition;
@@ -1024,9 +1455,6 @@ void R_DrawTiltedTranslucentWaterSpan_NPO2_8(void)
 		vz += ds_svp->x;
 	} while (--width >= 0);
 #else
-#define SPANSIZE 16
-#define INVSPAN	0.0625f
-
 	startz = 1.f/iz;
 	startu = uz*startz;
 	startv = vz*startz;
@@ -1145,4 +1573,3 @@ void R_DrawTiltedTranslucentWaterSpan_NPO2_8(void)
 	}
 #endif
 }
-#endif // NOWATER
diff --git a/src/r_main.c b/src/r_main.c
index 5165b3c87b3fa51a1505fb9033715c2f713d6a46..165f74a7975515011d81b4ad8a0d2346b0ad4600 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -959,6 +959,16 @@ void R_ExecuteSetViewSize(void)
 			dy = FixedMul(abs(dy), fovtan);
 			yslopetab[i] = FixedDiv(centerx*FRACUNIT, dy);
 		}
+
+		if (ds_su)
+			Z_Free(ds_su);
+		if (ds_sv)
+			Z_Free(ds_sv);
+		if (ds_sz)
+			Z_Free(ds_sz);
+
+		ds_su = ds_sv = ds_sz = NULL;
+		ds_sup = ds_svp = ds_szp = NULL;
 	}
 
 	memset(scalelight, 0xFF, sizeof(scalelight));
@@ -1011,8 +1021,8 @@ void R_Init(void)
 	//I_OutputMsg("\nR_InitLightTables");
 	R_InitLightTables();
 
-	//I_OutputMsg("\nR_InitTranslationTables\n");
-	R_InitTranslationTables();
+	//I_OutputMsg("\nR_InitTranslucencyTables\n");
+	R_InitTranslucencyTables();
 
 	R_InitDrawNodes();
 
@@ -1473,9 +1483,6 @@ void R_RenderPlayerView(player_t *player)
 	}
 	R_ClearDrawSegs();
 	R_ClearSprites();
-#ifdef FLOORSPLATS
-	R_ClearVisibleFloorSplats();
-#endif
 	Portal_InitList();
 
 	// check for new console commands.
@@ -1558,9 +1565,6 @@ void R_RenderPlayerView(player_t *player)
 
 	ps_sw_planetime = I_GetTimeMicros();
 	R_DrawPlanes();
-#ifdef FLOORSPLATS
-	R_DrawVisibleFloorSplats();
-#endif
 	ps_sw_planetime = I_GetTimeMicros() - ps_sw_planetime;
 
 	// draw mid texture and sprite
@@ -1572,24 +1576,6 @@ void R_RenderPlayerView(player_t *player)
 	free(masks);
 }
 
-#ifdef HWRENDER
-void R_InitHardwareMode(void)
-{
-	HWR_AddSessionCommands();
-	HWR_Switch();
-	HWR_LoadTextures(numtextures);
-	if (gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction))
-		HWR_SetupLevel();
-}
-#endif
-
-void R_ReloadHUDGraphics(void)
-{
-	ST_LoadGraphics();
-	HU_LoadGraphics();
-	ST_ReloadSkinFaceGraphics();
-}
-
 // =========================================================================
 //                    ENGINE COMMANDS & VARS
 // =========================================================================
diff --git a/src/r_main.h b/src/r_main.h
index 379b5b8df1f5d306e50b2eed802c058749cb3928..f1cc9621f00f320ffdbb363b08b783af89fda75b 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -114,10 +114,6 @@ extern consvar_t cv_tailspickup;
 
 // Called by startup code.
 void R_Init(void);
-#ifdef HWRENDER
-void R_InitHardwareMode(void);
-#endif
-void R_ReloadHUDGraphics(void);
 
 void R_CheckViewMorph(void);
 void R_ApplyViewMorph(void);
diff --git a/src/r_patch.c b/src/r_patch.c
new file mode 100644
index 0000000000000000000000000000000000000000..c78ffdd67adcf9f8c59864ce36a0b58dda61d5f9
--- /dev/null
+++ b/src/r_patch.c
@@ -0,0 +1,160 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2020 by Jaime "Lactozilla" Passos.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  r_patch.c
+/// \brief Patch generation.
+
+#include "doomdef.h"
+#include "r_patch.h"
+#include "r_picformats.h"
+#include "r_defs.h"
+#include "z_zone.h"
+
+#ifdef HWRENDER
+#include "hardware/hw_glob.h"
+#endif
+
+//
+// Creates a patch.
+// Assumes a PU_PATCH zone memory tag and no user, but can always be set later
+//
+
+patch_t *Patch_Create(softwarepatch_t *source, size_t srcsize, void *dest)
+{
+	patch_t *patch = (dest == NULL) ? Z_Calloc(sizeof(patch_t), PU_PATCH, NULL) : (patch_t *)(dest);
+
+	if (source)
+	{
+		INT32 col, colsize;
+		size_t size = sizeof(INT32) * source->width;
+		size_t offs = (sizeof(INT16) * 4) + size;
+
+		patch->width      = source->width;
+		patch->height     = source->height;
+		patch->leftoffset = source->leftoffset;
+		patch->topoffset  = source->topoffset;
+		patch->columnofs  = Z_Calloc(size, PU_PATCH_DATA, NULL);
+
+		for (col = 0; col < source->width; col++)
+		{
+			// This makes the column offsets relative to the column data itself,
+			// instead of the entire patch data
+			patch->columnofs[col] = LONG(source->columnofs[col]) - offs;
+		}
+
+		if (!srcsize)
+			I_Error("Patch_Create: no source size!");
+
+		colsize = (INT32)(srcsize) - (INT32)offs;
+		if (colsize <= 0)
+			I_Error("Patch_Create: no column data!");
+
+		patch->columns = Z_Calloc(colsize, PU_PATCH_DATA, NULL);
+		M_Memcpy(patch->columns, ((UINT8 *)source + LONG(source->columnofs[0])), colsize);
+	}
+
+	return patch;
+}
+
+//
+// Frees a patch from memory.
+//
+
+static void Patch_FreeData(patch_t *patch)
+{
+	INT32 i;
+
+#ifdef HWRENDER
+	if (patch->hardware)
+		HWR_FreeTexture(patch);
+#endif
+
+	for (i = 0; i < 2; i++)
+	{
+		if (patch->flats[i])
+			Z_Free(patch->flats[i]);
+	}
+
+#ifdef ROTSPRITE
+	if (patch->rotated)
+	{
+		rotsprite_t *rotsprite = patch->rotated;
+
+		for (i = 0; i < rotsprite->angles; i++)
+		{
+			if (rotsprite->patches[i])
+				Patch_Free(rotsprite->patches[i]);
+		}
+
+		Z_Free(rotsprite->patches);
+		Z_Free(rotsprite);
+	}
+#endif
+
+	if (patch->columnofs)
+		Z_Free(patch->columnofs);
+	if (patch->columns)
+		Z_Free(patch->columns);
+}
+
+void Patch_Free(patch_t *patch)
+{
+	Patch_FreeData(patch);
+	Z_Free(patch);
+}
+
+//
+// Frees patches with a tag range.
+//
+
+static boolean Patch_FreeTagsCallback(void *mem)
+{
+	patch_t *patch = (patch_t *)mem;
+	Patch_FreeData(patch);
+	return true;
+}
+
+void Patch_FreeTags(INT32 lowtag, INT32 hightag)
+{
+	Z_IterateTags(lowtag, hightag, Patch_FreeTagsCallback);
+}
+
+void Patch_GenerateFlat(patch_t *patch, pictureflags_t flags)
+{
+	UINT8 flip = (flags & (PICFLAGS_XFLIP | PICFLAGS_YFLIP));
+	if (patch->flats[flip] == NULL)
+		patch->flats[flip] = Picture_Convert(PICFMT_PATCH, patch, PICFMT_FLAT16, 0, NULL, 0, 0, 0, 0, flags);
+}
+
+#ifdef HWRENDER
+//
+// Allocates a hardware patch.
+//
+
+void *Patch_AllocateHardwarePatch(patch_t *patch)
+{
+	if (!patch->hardware)
+	{
+		GLPatch_t *grPatch = Z_Calloc(sizeof(GLPatch_t), PU_HWRPATCHINFO, &patch->hardware);
+		grPatch->mipmap = Z_Calloc(sizeof(GLMipmap_t), PU_HWRPATCHINFO, &grPatch->mipmap);
+	}
+	return (void *)(patch->hardware);
+}
+
+//
+// Creates a hardware patch.
+//
+
+void *Patch_CreateGL(patch_t *patch)
+{
+	GLPatch_t *grPatch = (GLPatch_t *)Patch_AllocateHardwarePatch(patch);
+	if (!grPatch->mipmap->data) // Run HWR_MakePatch in all cases, to recalculate some things
+		HWR_MakePatch(patch, grPatch, grPatch->mipmap, false);
+	return grPatch;
+}
+#endif // HWRENDER
diff --git a/src/r_patch.h b/src/r_patch.h
new file mode 100644
index 0000000000000000000000000000000000000000..32bcb3909efe057af98d54cd151f56414c71deb1
--- /dev/null
+++ b/src/r_patch.h
@@ -0,0 +1,44 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2020 by Jaime "Lactozilla" Passos.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  r_patch.h
+/// \brief Patch generation.
+
+#ifndef __R_PATCH__
+#define __R_PATCH__
+
+#include "r_defs.h"
+#include "r_picformats.h"
+#include "doomdef.h"
+
+// Patch functions
+patch_t *Patch_Create(softwarepatch_t *source, size_t srcsize, void *dest);
+void Patch_Free(patch_t *patch);
+
+#define Patch_FreeTag(tagnum) Patch_FreeTags(tagnum, tagnum)
+void Patch_FreeTags(INT32 lowtag, INT32 hightag);
+
+void Patch_GenerateFlat(patch_t *patch, pictureflags_t flags);
+
+#ifdef HWRENDER
+void *Patch_AllocateHardwarePatch(patch_t *patch);
+void *Patch_CreateGL(patch_t *patch);
+#endif
+
+#ifdef ROTSPRITE
+void Patch_Rotate(patch_t *patch, INT32 angle, INT32 xpivot, INT32 ypivot, boolean flip);
+patch_t *Patch_GetRotated(patch_t *patch, INT32 angle, boolean flip);
+patch_t *Patch_GetRotatedSprite(
+	spriteframe_t *sprite,
+	size_t frame, size_t spriteangle,
+	boolean flip, boolean adjustfeet,
+	void *info, INT32 rotationangle);
+INT32 R_GetRollAngle(angle_t rollangle);
+#endif
+
+#endif // __R_PATCH__
diff --git a/src/r_patchrotation.c b/src/r_patchrotation.c
new file mode 100644
index 0000000000000000000000000000000000000000..123c4eef229a20fa554094bf44a2cc3853e72dc8
--- /dev/null
+++ b/src/r_patchrotation.c
@@ -0,0 +1,273 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2020 by Jaime "Lactozilla" Passos.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  r_patchrotation.c
+/// \brief Patch rotation.
+
+#include "r_patchrotation.h"
+#include "r_things.h" // FEETADJUST
+#include "z_zone.h"
+#include "w_wad.h"
+
+#ifdef ROTSPRITE
+fixed_t rollcosang[ROTANGLES];
+fixed_t rollsinang[ROTANGLES];
+
+INT32 R_GetRollAngle(angle_t rollangle)
+{
+	INT32 ra = AngleFixed(rollangle)>>FRACBITS;
+#if (ROTANGDIFF > 1)
+	ra += (ROTANGDIFF/2);
+#endif
+	ra /= ROTANGDIFF;
+	ra %= ROTANGLES;
+	return ra;
+}
+
+patch_t *Patch_GetRotated(patch_t *patch, INT32 angle, boolean flip)
+{
+	rotsprite_t *rotsprite = patch->rotated;
+	if (rotsprite == NULL || angle < 1 || angle >= ROTANGLES)
+		return NULL;
+
+	if (flip)
+		angle += rotsprite->angles;
+
+	return rotsprite->patches[angle];
+}
+
+patch_t *Patch_GetRotatedSprite(
+	spriteframe_t *sprite,
+	size_t frame, size_t spriteangle,
+	boolean flip, boolean adjustfeet,
+	void *info, INT32 rotationangle)
+{
+	rotsprite_t *rotsprite;
+	spriteinfo_t *sprinfo = (spriteinfo_t *)info;
+	INT32 idx = rotationangle;
+	UINT8 type = (adjustfeet ? 1 : 0);
+
+	if (rotationangle < 1 || rotationangle >= ROTANGLES)
+		return NULL;
+
+	rotsprite = sprite->rotated[type][spriteangle];
+
+	if (rotsprite == NULL)
+	{
+		rotsprite = RotatedPatch_Create(ROTANGLES);
+		sprite->rotated[type][spriteangle] = rotsprite;
+	}
+
+	if (flip)
+		idx += rotsprite->angles;
+
+	if (rotsprite->patches[idx] == NULL)
+	{
+		patch_t *patch;
+		INT32 xpivot = 0, ypivot = 0;
+		lumpnum_t lump = sprite->lumppat[spriteangle];
+
+		if (lump == LUMPERROR)
+			return NULL;
+
+		patch = W_CachePatchNum(lump, PU_SPRITE);
+
+		if (sprinfo->available)
+		{
+			xpivot = sprinfo->pivot[frame].x;
+			ypivot = sprinfo->pivot[frame].y;
+		}
+		else
+		{
+			xpivot = patch->leftoffset;
+			ypivot = patch->height / 2;
+		}
+
+		RotatedPatch_DoRotation(rotsprite, patch, rotationangle, xpivot, ypivot, flip);
+
+		//BP: we cannot use special tric in hardware mode because feet in ground caused by z-buffer
+		if (adjustfeet)
+			((patch_t *)rotsprite->patches[idx])->topoffset += FEETADJUST>>FRACBITS;
+	}
+
+	return rotsprite->patches[idx];
+}
+
+void Patch_Rotate(patch_t *patch, INT32 angle, INT32 xpivot, INT32 ypivot, boolean flip)
+{
+	if (patch->rotated == NULL)
+		patch->rotated = RotatedPatch_Create(ROTANGLES);
+	RotatedPatch_DoRotation(patch->rotated, patch, angle, xpivot, ypivot, flip);
+}
+
+rotsprite_t *RotatedPatch_Create(INT32 numangles)
+{
+	rotsprite_t *rotsprite = Z_Calloc(sizeof(rotsprite_t), PU_STATIC, NULL);
+	rotsprite->angles = numangles;
+	rotsprite->patches = Z_Calloc(rotsprite->angles * 2 * sizeof(void *), PU_STATIC, NULL);
+	return rotsprite;
+}
+
+static void RotatedPatch_CalculateDimensions(
+	INT32 width, INT32 height,
+	fixed_t ca, fixed_t sa,
+	INT32 *newwidth, INT32 *newheight)
+{
+	fixed_t fixedwidth = (width * FRACUNIT);
+	fixed_t fixedheight = (height * FRACUNIT);
+
+	fixed_t w1 = abs(FixedMul(fixedwidth, ca) - FixedMul(fixedheight, sa));
+	fixed_t w2 = abs(FixedMul(-fixedwidth, ca) - FixedMul(fixedheight, sa));
+	fixed_t h1 = abs(FixedMul(fixedwidth, sa) + FixedMul(fixedheight, ca));
+	fixed_t h2 = abs(FixedMul(-fixedwidth, sa) + FixedMul(fixedheight, ca));
+
+	w1 = FixedInt(FixedCeil(w1 + (FRACUNIT/2)));
+	w2 = FixedInt(FixedCeil(w2 + (FRACUNIT/2)));
+	h1 = FixedInt(FixedCeil(h1 + (FRACUNIT/2)));
+	h2 = FixedInt(FixedCeil(h2 + (FRACUNIT/2)));
+
+	*newwidth = max(width, max(w1, w2));
+	*newheight = max(height, max(h1, h2));
+}
+
+void RotatedPatch_DoRotation(rotsprite_t *rotsprite, patch_t *patch, INT32 angle, INT32 xpivot, INT32 ypivot, boolean flip)
+{
+	patch_t *rotated;
+	UINT16 *rawdst, *rawconv;
+	size_t size;
+	pictureflags_t bflip = (flip) ? PICFLAGS_XFLIP : 0;
+
+	INT32 width = patch->width;
+	INT32 height = patch->height;
+	INT32 leftoffset = patch->leftoffset;
+	INT32 newwidth, newheight;
+
+	fixed_t ca = rollcosang[angle];
+	fixed_t sa = rollsinang[angle];
+	fixed_t xcenter, ycenter;
+	INT32 idx = angle;
+	INT32 x, y;
+	INT32 sx, sy;
+	INT32 dx, dy;
+	INT32 ox, oy;
+	INT32 minx, miny, maxx, maxy;
+
+	// Don't cache angle = 0
+	if (angle < 1 || angle >= ROTANGLES)
+		return;
+
+	if (flip)
+	{
+		idx += rotsprite->angles;
+		xpivot = width - xpivot;
+		leftoffset = width - leftoffset;
+	}
+
+	if (rotsprite->patches[idx])
+		return;
+
+	// Find the dimensions of the rotated patch.
+	RotatedPatch_CalculateDimensions(width, height, ca, sa, &newwidth, &newheight);
+
+	xcenter = (xpivot * FRACUNIT);
+	ycenter = (ypivot * FRACUNIT);
+
+	if (xpivot != width / 2 || ypivot != height / 2)
+	{
+		newwidth *= 2;
+		newheight *= 2;
+	}
+
+	minx = newwidth;
+	miny = newheight;
+	maxx = 0;
+	maxy = 0;
+
+	// Draw the rotated sprite to a temporary buffer.
+	size = (newwidth * newheight);
+	if (!size)
+		size = (width * height);
+	rawdst = Z_Calloc(size * sizeof(UINT16), PU_STATIC, NULL);
+
+	for (dy = 0; dy < newheight; dy++)
+	{
+		for (dx = 0; dx < newwidth; dx++)
+		{
+			x = (dx - (newwidth / 2)) * FRACUNIT;
+			y = (dy - (newheight / 2)) * FRACUNIT;
+			sx = FixedMul(x, ca) + FixedMul(y, sa) + xcenter;
+			sy = -FixedMul(x, sa) + FixedMul(y, ca) + ycenter;
+
+			sx >>= FRACBITS;
+			sy >>= FRACBITS;
+
+			if (sx >= 0 && sy >= 0 && sx < width && sy < height)
+			{
+				void *input = Picture_GetPatchPixel(patch, PICFMT_PATCH, sx, sy, bflip);
+				if (input != NULL)
+				{
+					rawdst[(dy * newwidth) + dx] = (0xFF00 | (*(UINT8 *)input));
+					if (dx < minx)
+						minx = dx;
+					if (dy < miny)
+						miny = dy;
+					if (dx > maxx)
+						maxx = dx;
+					if (dy > maxy)
+						maxy = dy;
+				}
+			}
+		}
+	}
+
+	ox = (newwidth / 2) + (leftoffset - xpivot);
+	oy = (newheight / 2) + (patch->topoffset - ypivot);
+	width = (maxx - minx);
+	height = (maxy - miny);
+
+	if ((unsigned)(width * height) != size)
+	{
+		UINT16 *src, *dest;
+
+		size = (width * height);
+		rawconv = Z_Calloc(size * sizeof(UINT16), PU_STATIC, NULL);
+
+		src = &rawdst[(miny * newwidth) + minx];
+		dest = rawconv;
+		dy = height;
+
+		while (dy--)
+		{
+			M_Memcpy(dest, src, width * sizeof(UINT16));
+			dest += width;
+			src += newwidth;
+		}
+
+		ox -= minx;
+		oy -= miny;
+
+		Z_Free(rawdst);
+	}
+	else
+	{
+		rawconv = rawdst;
+		width = newwidth;
+		height = newheight;
+	}
+
+	// make patch
+	rotated = (patch_t *)Picture_Convert(PICFMT_FLAT16, rawconv, PICFMT_PATCH, 0, NULL, width, height, 0, 0, 0);
+
+	Z_ChangeTag(rotated, PU_PATCH_ROTATED);
+	Z_SetUser(rotated, (void **)(&rotsprite->patches[idx]));
+	Z_Free(rawconv);
+
+	rotated->leftoffset = ox;
+	rotated->topoffset = oy;
+}
+#endif
diff --git a/src/r_patchrotation.h b/src/r_patchrotation.h
new file mode 100644
index 0000000000000000000000000000000000000000..2744f71d25380469b30b1fdcf8b5112578a2abd8
--- /dev/null
+++ b/src/r_patchrotation.h
@@ -0,0 +1,21 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 2020 by Jaime "Lactozilla" Passos.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  r_patchrotation.h
+/// \brief Patch rotation.
+
+#include "r_patch.h"
+#include "r_picformats.h"
+
+#ifdef ROTSPRITE
+rotsprite_t *RotatedPatch_Create(INT32 numangles);
+void RotatedPatch_DoRotation(rotsprite_t *rotsprite, patch_t *patch, INT32 angle, INT32 xpivot, INT32 ypivot, boolean flip);
+
+extern fixed_t rollcosang[ROTANGLES];
+extern fixed_t rollsinang[ROTANGLES];
+#endif
diff --git a/src/r_picformats.c b/src/r_picformats.c
index 95fe23aeb11e6e355ed84644e3b9aa1b421b2f7c..02f1de4ab20d1f0edaeb8753459eacc8c452de23 100644
--- a/src/r_picformats.c
+++ b/src/r_picformats.c
@@ -16,10 +16,11 @@
 #include "dehacked.h"
 #include "i_video.h"
 #include "r_data.h"
-#include "r_textures.h"
-#include "r_draw.h"
+#include "r_patch.h"
 #include "r_picformats.h"
+#include "r_textures.h"
 #include "r_things.h"
+#include "r_draw.h"
 #include "v_video.h"
 #include "z_zone.h"
 #include "w_wad.h"
@@ -142,10 +143,21 @@ void *Picture_PatchConvert(
 	if (Picture_IsPatchFormat(informat))
 	{
 		inpatch = (patch_t *)picture;
-		inwidth = SHORT(inpatch->width);
-		inheight = SHORT(inpatch->height);
-		inleftoffset = SHORT(inpatch->leftoffset);
-		intopoffset = SHORT(inpatch->topoffset);
+		if (Picture_IsDoomPatchFormat(informat))
+		{
+			softwarepatch_t *doompatch = (softwarepatch_t *)picture;
+			inwidth = SHORT(doompatch->width);
+			inheight = SHORT(doompatch->height);
+			inleftoffset = SHORT(doompatch->leftoffset);
+			intopoffset = SHORT(doompatch->topoffset);
+		}
+		else
+		{
+			inwidth = inpatch->width;
+			inheight = inpatch->height;
+			inleftoffset = inpatch->leftoffset;
+			intopoffset = inpatch->topoffset;
+		}
 	}
 
 	// Write image size and offset
@@ -275,6 +287,7 @@ void *Picture_PatchConvert(
 			switch (outformat)
 			{
 				case PICFMT_PATCH32:
+				case PICFMT_DOOMPATCH32:
 				{
 					if (inbpp == PICDEPTH_32BPP)
 					{
@@ -294,6 +307,7 @@ void *Picture_PatchConvert(
 					break;
 				}
 				case PICFMT_PATCH16:
+				case PICFMT_DOOMPATCH16:
 					if (inbpp == PICDEPTH_32BPP)
 					{
 						RGBA_t in = *(RGBA_t *)input;
@@ -338,9 +352,26 @@ void *Picture_PatchConvert(
 	img = Z_Malloc(size, PU_STATIC, NULL);
 	memcpy(img, imgbuf, size);
 
-	if (outsize != NULL)
-		*outsize = size;
-	return img;
+	if (Picture_IsInternalPatchFormat(outformat))
+	{
+		patch_t *converted = Patch_Create((softwarepatch_t *)img, size, NULL);
+
+#ifdef HWRENDER
+		Patch_CreateGL(converted);
+#endif
+
+		Z_Free(img);
+
+		if (outsize != NULL)
+			*outsize = sizeof(patch_t);
+		return converted;
+	}
+	else
+	{
+		if (outsize != NULL)
+			*outsize = size;
+		return img;
+	}
 }
 
 /** Converts a picture to a flat.
@@ -391,8 +422,17 @@ void *Picture_FlatConvert(
 	if (Picture_IsPatchFormat(informat))
 	{
 		inpatch = (patch_t *)picture;
-		inwidth = SHORT(inpatch->width);
-		inheight = SHORT(inpatch->height);
+		if (Picture_IsDoomPatchFormat(informat))
+		{
+			softwarepatch_t *doompatch = ((softwarepatch_t *)picture);
+			inwidth = SHORT(doompatch->width);
+			inheight = SHORT(doompatch->height);
+		}
+		else
+		{
+			inwidth = inpatch->width;
+			inheight = inpatch->height;
+		}
 	}
 
 	size = (inwidth * inheight) * (outbpp / 8);
@@ -503,22 +543,25 @@ void *Picture_GetPatchPixel(
 	UINT8 *s8 = NULL;
 	UINT16 *s16 = NULL;
 	UINT32 *s32 = NULL;
+	softwarepatch_t *doompatch = (softwarepatch_t *)patch;
+	INT16 width;
 
 	if (patch == NULL)
 		I_Error("Picture_GetPatchPixel: patch == NULL");
 
-	if (x >= 0 && x < SHORT(patch->width))
+	width = (Picture_IsDoomPatchFormat(informat) ? patch->width : SHORT(patch->width));
+
+	if (x >= 0 && x < width)
 	{
+		INT32 colx = (flags & PICFLAGS_XFLIP) ? (width-1)-x : x;
 		INT32 topdelta, prevdelta = -1;
-		INT32 colofs = 0;
-
-		if (flags & PICFLAGS_XFLIP)
-			colofs = LONG(patch->columnofs[(SHORT(patch->width)-1)-x]);
-		else
-			colofs = LONG(patch->columnofs[x]);
+		INT32 colofs = (Picture_IsDoomPatchFormat(informat) ? LONG(patch->columnofs[colx]) : patch->columnofs[colx]);
 
 		// Column offsets are pointers so no casting required
-		column = (column_t *)((UINT8 *)patch + colofs);
+		if (Picture_IsDoomPatchFormat(informat))
+			column = (column_t *)((UINT8 *)doompatch + colofs);
+		else
+			column = (column_t *)((UINT8 *)patch->columns + colofs);
 
 		while (column->topdelta != 0xff)
 		{
@@ -527,25 +570,25 @@ void *Picture_GetPatchPixel(
 				topdelta += prevdelta;
 			prevdelta = topdelta;
 			s8 = (UINT8 *)(column) + 3;
-			if (informat == PICFMT_PATCH32)
+			if (Picture_FormatBPP(informat) == PICDEPTH_32BPP)
 				s32 = (UINT32 *)s8;
-			else if (informat == PICFMT_PATCH16)
+			else if (Picture_FormatBPP(informat) == PICDEPTH_16BPP)
 				s16 = (UINT16 *)s8;
 			for (ofs = 0; ofs < column->length; ofs++)
 			{
 				if ((topdelta + ofs) == y)
 				{
-					if (informat == PICFMT_PATCH32)
+					if (Picture_FormatBPP(informat) == PICDEPTH_32BPP)
 						return &s32[ofs];
-					else if (informat == PICFMT_PATCH16)
+					else if (Picture_FormatBPP(informat) == PICDEPTH_16BPP)
 						return &s16[ofs];
-					else // PICFMT_PATCH
+					else // PICDEPTH_8BPP
 						return &s8[ofs];
 				}
 			}
-			if (informat == PICFMT_PATCH32)
+			if (Picture_FormatBPP(informat) == PICDEPTH_32BPP)
 				column = (column_t *)((UINT32 *)column + column->length);
-			else if (informat == PICFMT_PATCH16)
+			else if (Picture_FormatBPP(informat) == PICDEPTH_16BPP)
 				column = (column_t *)((UINT16 *)column + column->length);
 			else
 				column = (column_t *)((UINT8 *)column + column->length);
@@ -568,15 +611,18 @@ INT32 Picture_FormatBPP(pictureformat_t format)
 	{
 		case PICFMT_PATCH32:
 		case PICFMT_FLAT32:
+		case PICFMT_DOOMPATCH32:
 		case PICFMT_PNG:
 			bpp = PICDEPTH_32BPP;
 			break;
 		case PICFMT_PATCH16:
 		case PICFMT_FLAT16:
+		case PICFMT_DOOMPATCH16:
 			bpp = PICDEPTH_16BPP;
 			break;
 		case PICFMT_PATCH:
 		case PICFMT_FLAT:
+		case PICFMT_DOOMPATCH:
 			bpp = PICDEPTH_8BPP;
 			break;
 		default:
@@ -592,7 +638,43 @@ INT32 Picture_FormatBPP(pictureformat_t format)
   */
 boolean Picture_IsPatchFormat(pictureformat_t format)
 {
-	return (format == PICFMT_PATCH || format == PICFMT_PATCH16 || format == PICFMT_PATCH32);
+	return (Picture_IsInternalPatchFormat(format) || Picture_IsDoomPatchFormat(format));
+}
+
+/** Checks if the specified picture format is an internal patch.
+  *
+  * \param format Input picture format.
+  * \return True if the picture format is an internal patch, false if not.
+  */
+boolean Picture_IsInternalPatchFormat(pictureformat_t format)
+{
+	switch (format)
+	{
+		case PICFMT_PATCH:
+		case PICFMT_PATCH16:
+		case PICFMT_PATCH32:
+			return true;
+		default:
+			return false;
+	}
+}
+
+/** Checks if the specified picture format is a Doom patch.
+  *
+  * \param format Input picture format.
+  * \return True if the picture format is a Doom patch, false if not.
+  */
+boolean Picture_IsDoomPatchFormat(pictureformat_t format)
+{
+	switch (format)
+	{
+		case PICFMT_DOOMPATCH:
+		case PICFMT_DOOMPATCH16:
+		case PICFMT_DOOMPATCH32:
+			return true;
+		default:
+			return false;
+	}
 }
 
 /** Checks if the specified picture format is a flat.
@@ -605,14 +687,14 @@ boolean Picture_IsFlatFormat(pictureformat_t format)
 	return (format == PICFMT_FLAT || format == PICFMT_FLAT16 || format == PICFMT_FLAT32);
 }
 
-/** Returns true if the lump is a valid patch.
-  * PICFMT_PATCH only, I think??
+/** Returns true if the lump is a valid Doom patch.
+  * PICFMT_DOOMPATCH only.
   *
   * \param patch Input patch.
   * \param picture Input patch size.
   * \return True if the input patch is valid.
   */
-boolean Picture_CheckIfPatch(patch_t *patch, size_t size)
+boolean Picture_CheckIfDoomPatch(softwarepatch_t *patch, size_t size)
 {
 	INT16 width, height;
 	boolean result;
@@ -887,26 +969,45 @@ static png_bytep *PNG_Read(
 		// matches the color count of SRB2's palette: 256 colors.
 		if (png_get_PLTE(png_ptr, png_info_ptr, &palette, &palette_size))
 		{
-			if (palette_size == 256)
+			if (palette_size == 256 && pMasterPalette)
+			{
+				png_colorp pal = palette;
+				INT32 i;
+
 				usepal = true;
+
+				for (i = 0; i < 256; i++)
+				{
+					UINT32 rgb = R_PutRgbaRGBA(pal->red, pal->green, pal->blue, 0xFF);
+					if (rgb != pMasterPalette[i].rgba)
+					{
+						usepal = false;
+						break;
+					}
+					pal++;
+				}
+			}
 		}
 
 		// If any of the tRNS colors have an alpha lower than 0xFF, and that
 		// color is present on the image, the palette flag is disabled.
-		png_get_tRNS(png_ptr, png_info_ptr, &trans, &trans_num, &trans_values);
-
-		if (trans && trans_num == 256)
+		if (usepal)
 		{
-			int i;
-			for (i = 0; i < trans_num; i++)
+			png_get_tRNS(png_ptr, png_info_ptr, &trans, &trans_num, &trans_values);
+
+			if (trans && trans_num == 256)
 			{
-				// libpng will transform this image into RGB even if
-				// the transparent index does not exist in the image,
-				// and there is no way around that.
-				if (trans[i] < 0xFF)
+				INT32 i;
+				for (i = 0; i < trans_num; i++)
 				{
-					usepal = false;
-					break;
+					// libpng will transform this image into RGB even if
+					// the transparent index does not exist in the image,
+					// and there is no way around that.
+					if (trans[i] < 0xFF)
+					{
+						usepal = false;
+						break;
+					}
 				}
 			}
 		}
@@ -1446,11 +1547,6 @@ static void R_ParseSpriteInfo(boolean spr2)
 	info = Z_Calloc(sizeof(spriteinfo_t), PU_STATIC, NULL);
 	info->available = true;
 
-#ifdef ROTSPRITE
-	if ((sprites != NULL) && (!spr2))
-		R_FreeSingleRotSprite(&sprites[sprnum]);
-#endif
-
 	// Left Curly Brace
 	sprinfoToken = M_GetToken(NULL);
 	if (sprinfoToken == NULL)
@@ -1511,9 +1607,6 @@ static void R_ParseSpriteInfo(boolean spr2)
 						size_t skinnum = skinnumbers[i];
 						skin_t *skin = &skins[skinnum];
 						spriteinfo_t *sprinfo = skin->sprinfo;
-#ifdef ROTSPRITE
-						R_FreeSkinRotSprite(skinnum);
-#endif
 						M_Memcpy(&sprinfo[spr2num], info, sizeof(spriteinfo_t));
 					}
 				}
@@ -1603,316 +1696,3 @@ void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps)
 			R_ParseSPRTINFOLump(wadnum, i);
 	}
 }
-
-#ifdef ROTSPRITE
-//
-// R_GetRollAngle
-//
-// Angles precalculated in R_InitSprites.
-//
-fixed_t rollcosang[ROTANGLES];
-fixed_t rollsinang[ROTANGLES];
-INT32 R_GetRollAngle(angle_t rollangle)
-{
-	INT32 ra = AngleFixed(rollangle)>>FRACBITS;
-#if (ROTANGDIFF > 1)
-	ra += (ROTANGDIFF/2);
-#endif
-	ra /= ROTANGDIFF;
-	ra %= ROTANGLES;
-	return ra;
-}
-
-//
-// R_CacheRotSprite
-//
-// Create a rotated sprite.
-//
-void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, spriteframe_t *sprframe, INT32 rot, UINT8 flip)
-{
-	INT32 angle;
-	patch_t *patch;
-	patch_t *newpatch;
-	UINT16 *rawdst;
-	size_t size;
-	pictureflags_t bflip = (flip) ? PICFLAGS_XFLIP : 0;
-
-#define SPRITE_XCENTER (leftoffset)
-#define SPRITE_YCENTER (height / 2)
-#define ROTSPRITE_XCENTER (newwidth / 2)
-#define ROTSPRITE_YCENTER (newheight / 2)
-
-	if (!(sprframe->rotsprite.cached & (1<<rot)))
-	{
-		INT32 dx, dy;
-		INT32 px, py;
-		INT32 width, height, leftoffset;
-		fixed_t ca, sa;
-		lumpnum_t lump = sprframe->lumppat[rot];
-#ifndef NO_PNG_LUMPS
-		size_t lumplength;
-#endif
-
-		if (lump == LUMPERROR)
-			return;
-
-		patch = (patch_t *)W_CacheLumpNum(lump, PU_STATIC);
-#ifndef NO_PNG_LUMPS
-		lumplength = W_LumpLength(lump);
-
-		if (Picture_IsLumpPNG((const UINT8 *)patch, lumplength))
-			patch = (patch_t *)Picture_PNGConvert((const UINT8 *)patch, PICFMT_PATCH, NULL, NULL, NULL, NULL, lumplength, NULL, 0);
-		else
-#endif
-		// Because there's something wrong with SPR_DFLM, I guess
-		if (!Picture_CheckIfPatch(patch, lumplength))
-			return;
-
-		width = SHORT(patch->width);
-		height = SHORT(patch->height);
-		leftoffset = SHORT(patch->leftoffset);
-
-		// rotation pivot
-		px = SPRITE_XCENTER;
-		py = SPRITE_YCENTER;
-
-		// get correct sprite info for sprite
-		if (sprinfo == NULL)
-			sprinfo = &spriteinfo[sprnum];
-		if (sprinfo->available)
-		{
-			px = sprinfo->pivot[frame].x;
-			py = sprinfo->pivot[frame].y;
-		}
-		if (bflip)
-		{
-			px = width - px;
-			leftoffset = width - leftoffset;
-		}
-
-		// Don't cache angle = 0
-		for (angle = 1; angle < ROTANGLES; angle++)
-		{
-			INT32 newwidth, newheight;
-
-			ca = rollcosang[angle];
-			sa = rollsinang[angle];
-
-			// Find the dimensions of the rotated patch.
-			{
-				INT32 w1 = abs(FixedMul(width << FRACBITS, ca) - FixedMul(height << FRACBITS, sa));
-				INT32 w2 = abs(FixedMul(-(width << FRACBITS), ca) - FixedMul(height << FRACBITS, sa));
-				INT32 h1 = abs(FixedMul(width << FRACBITS, sa) + FixedMul(height << FRACBITS, ca));
-				INT32 h2 = abs(FixedMul(-(width << FRACBITS), sa) + FixedMul(height << FRACBITS, ca));
-				w1 = FixedInt(FixedCeil(w1 + (FRACUNIT/2)));
-				w2 = FixedInt(FixedCeil(w2 + (FRACUNIT/2)));
-				h1 = FixedInt(FixedCeil(h1 + (FRACUNIT/2)));
-				h2 = FixedInt(FixedCeil(h2 + (FRACUNIT/2)));
-				newwidth = max(width, max(w1, w2));
-				newheight = max(height, max(h1, h2));
-			}
-
-			// check boundaries
-			{
-				fixed_t top[2][2];
-				fixed_t bottom[2][2];
-
-				top[0][0] = FixedMul((-ROTSPRITE_XCENTER) << FRACBITS, ca) + FixedMul((-ROTSPRITE_YCENTER) << FRACBITS, sa) + (px << FRACBITS);
-				top[0][1] = FixedMul((-ROTSPRITE_XCENTER) << FRACBITS, sa) + FixedMul((-ROTSPRITE_YCENTER) << FRACBITS, ca) + (py << FRACBITS);
-				top[1][0] = FixedMul((newwidth-ROTSPRITE_XCENTER) << FRACBITS, ca) + FixedMul((-ROTSPRITE_YCENTER) << FRACBITS, sa) + (px << FRACBITS);
-				top[1][1] = FixedMul((newwidth-ROTSPRITE_XCENTER) << FRACBITS, sa) + FixedMul((-ROTSPRITE_YCENTER) << FRACBITS, ca) + (py << FRACBITS);
-
-				bottom[0][0] = FixedMul((-ROTSPRITE_XCENTER) << FRACBITS, ca) + FixedMul((newheight-ROTSPRITE_YCENTER) << FRACBITS, sa) + (px << FRACBITS);
-				bottom[0][1] = -FixedMul((-ROTSPRITE_XCENTER) << FRACBITS, sa) + FixedMul((newheight-ROTSPRITE_YCENTER) << FRACBITS, ca) + (py << FRACBITS);
-				bottom[1][0] = FixedMul((newwidth-ROTSPRITE_XCENTER) << FRACBITS, ca) + FixedMul((newheight-ROTSPRITE_YCENTER) << FRACBITS, sa) + (px << FRACBITS);
-				bottom[1][1] = -FixedMul((newwidth-ROTSPRITE_XCENTER) << FRACBITS, sa) + FixedMul((newheight-ROTSPRITE_YCENTER) << FRACBITS, ca) + (py << FRACBITS);
-
-				top[0][0] >>= FRACBITS;
-				top[0][1] >>= FRACBITS;
-				top[1][0] >>= FRACBITS;
-				top[1][1] >>= FRACBITS;
-
-				bottom[0][0] >>= FRACBITS;
-				bottom[0][1] >>= FRACBITS;
-				bottom[1][0] >>= FRACBITS;
-				bottom[1][1] >>= FRACBITS;
-
-#define BOUNDARYWCHECK(b) (b[0] < 0 || b[0] >= width)
-#define BOUNDARYHCHECK(b) (b[1] < 0 || b[1] >= height)
-#define BOUNDARYADJUST(x) x *= 2
-				// top left/right
-				if (BOUNDARYWCHECK(top[0]) || BOUNDARYWCHECK(top[1]))
-					BOUNDARYADJUST(newwidth);
-				// bottom left/right
-				else if (BOUNDARYWCHECK(bottom[0]) || BOUNDARYWCHECK(bottom[1]))
-					BOUNDARYADJUST(newwidth);
-				// top left/right
-				if (BOUNDARYHCHECK(top[0]) || BOUNDARYHCHECK(top[1]))
-					BOUNDARYADJUST(newheight);
-				// bottom left/right
-				else if (BOUNDARYHCHECK(bottom[0]) || BOUNDARYHCHECK(bottom[1]))
-					BOUNDARYADJUST(newheight);
-#undef BOUNDARYWCHECK
-#undef BOUNDARYHCHECK
-#undef BOUNDARYADJUST
-			}
-
-			// Draw the rotated sprite to a temporary buffer.
-			size = (newwidth * newheight);
-			if (!size)
-				size = (width * height);
-			rawdst = Z_Calloc(size * sizeof(UINT16), PU_STATIC, NULL);
-
-			for (dy = 0; dy < newheight; dy++)
-			{
-				for (dx = 0; dx < newwidth; dx++)
-				{
-					INT32 x = (dx-ROTSPRITE_XCENTER) << FRACBITS;
-					INT32 y = (dy-ROTSPRITE_YCENTER) << FRACBITS;
-					INT32 sx = FixedMul(x, ca) + FixedMul(y, sa) + (px << FRACBITS);
-					INT32 sy = -FixedMul(x, sa) + FixedMul(y, ca) + (py << FRACBITS);
-					sx >>= FRACBITS;
-					sy >>= FRACBITS;
-					if (sx >= 0 && sy >= 0 && sx < width && sy < height)
-					{
-						void *input = Picture_GetPatchPixel(patch, PICFMT_PATCH, sx, sy, bflip);
-						if (input != NULL)
-							rawdst[(dy*newwidth)+dx] = (0xFF00 | (*(UINT8 *)input));
-					}
-				}
-			}
-
-			// make patch
-			newpatch = (patch_t *)Picture_Convert(PICFMT_FLAT16, rawdst, PICFMT_PATCH, 0, &size, newwidth, newheight, 0, 0, 0);
-			{
-				newpatch->leftoffset = (newpatch->width / 2) + (leftoffset - px);
-				newpatch->topoffset = (newpatch->height / 2) + (SHORT(patch->topoffset) - py);
-			}
-
-			//BP: we cannot use special tric in hardware mode because feet in ground caused by z-buffer
-			if (rendermode != render_none) // not for psprite
-				newpatch->topoffset += FEETADJUST>>FRACBITS;
-
-			// P_PrecacheLevel
-			if (devparm) spritememory += size;
-
-			// convert everything to little-endian, for big-endian support
-			newpatch->width = SHORT(newpatch->width);
-			newpatch->height = SHORT(newpatch->height);
-			newpatch->leftoffset = SHORT(newpatch->leftoffset);
-			newpatch->topoffset = SHORT(newpatch->topoffset);
-
-#ifdef HWRENDER
-			if (rendermode == render_opengl)
-			{
-				GLPatch_t *grPatch = Z_Calloc(sizeof(GLPatch_t), PU_HWRPATCHINFO, NULL);
-				grPatch->mipmap = Z_Calloc(sizeof(GLMipmap_t), PU_HWRPATCHINFO, NULL);
-				grPatch->rawpatch = newpatch;
-				sprframe->rotsprite.patch[rot][angle] = (patch_t *)grPatch;
-				HWR_MakePatch(newpatch, grPatch, grPatch->mipmap, false);
-			}
-			else
-#endif // HWRENDER
-				sprframe->rotsprite.patch[rot][angle] = newpatch;
-
-			// free rotated image data
-			Z_Free(rawdst);
-		}
-
-		// This rotation is cached now
-		sprframe->rotsprite.cached |= (1<<rot);
-
-		// free image data
-		Z_Free(patch);
-	}
-#undef SPRITE_XCENTER
-#undef SPRITE_YCENTER
-#undef ROTSPRITE_XCENTER
-#undef ROTSPRITE_YCENTER
-}
-
-//
-// R_FreeSingleRotSprite
-//
-// Free sprite rotation data from memory, for a single spritedef.
-//
-void R_FreeSingleRotSprite(spritedef_t *spritedef)
-{
-	UINT8 frame;
-	INT32 rot, ang;
-
-	for (frame = 0; frame < spritedef->numframes; frame++)
-	{
-		spriteframe_t *sprframe = &spritedef->spriteframes[frame];
-		for (rot = 0; rot < 16; rot++)
-		{
-			if (sprframe->rotsprite.cached & (1<<rot))
-			{
-				for (ang = 0; ang < ROTANGLES; ang++)
-				{
-					patch_t *rotsprite = sprframe->rotsprite.patch[rot][ang];
-					if (rotsprite)
-					{
-#ifdef HWRENDER
-						if (rendermode == render_opengl)
-						{
-							GLPatch_t *grPatch = (GLPatch_t *)rotsprite;
-							if (grPatch->rawpatch)
-							{
-								Z_Free(grPatch->rawpatch);
-								grPatch->rawpatch = NULL;
-							}
-							if (grPatch->mipmap)
-							{
-								if (grPatch->mipmap->data)
-								{
-									Z_Free(grPatch->mipmap->data);
-									grPatch->mipmap->data = NULL;
-								}
-								Z_Free(grPatch->mipmap);
-								grPatch->mipmap = NULL;
-							}
-						}
-#endif
-						Z_Free(rotsprite);
-					}
-				}
-				sprframe->rotsprite.cached &= ~(1<<rot);
-			}
-		}
-	}
-}
-
-//
-// R_FreeSkinRotSprite
-//
-// Free sprite rotation data from memory, for a skin.
-// Calls R_FreeSingleRotSprite.
-//
-void R_FreeSkinRotSprite(size_t skinnum)
-{
-	size_t i;
-	skin_t *skin = &skins[skinnum];
-	spritedef_t *skinsprites = skin->sprites;
-	for (i = 0; i < NUMPLAYERSPRITES*2; i++)
-	{
-		R_FreeSingleRotSprite(skinsprites);
-		skinsprites++;
-	}
-}
-
-//
-// R_FreeAllRotSprite
-//
-// Free ALL sprite rotation data from memory.
-//
-void R_FreeAllRotSprite(void)
-{
-	INT32 i;
-	size_t s;
-	for (s = 0; s < numsprites; s++)
-		R_FreeSingleRotSprite(&sprites[s]);
-	for (i = 0; i < numskins; ++i)
-		R_FreeSkinRotSprite(i);
-}
-#endif
diff --git a/src/r_picformats.h b/src/r_picformats.h
index 3ee76a92f1bf2c521ef92372429543e7de10cba8..8d3999013475f23b9428e0e252148d91c88c8ea2 100644
--- a/src/r_picformats.h
+++ b/src/r_picformats.h
@@ -24,6 +24,7 @@ typedef enum
 	// Doom formats
 	PICFMT_PATCH,
 	PICFMT_FLAT,
+	PICFMT_DOOMPATCH,
 
 	// PNG
 	PICFMT_PNG,
@@ -31,10 +32,12 @@ typedef enum
 	// 16bpp
 	PICFMT_PATCH16,
 	PICFMT_FLAT16,
+	PICFMT_DOOMPATCH16,
 
 	// 32bpp
 	PICFMT_PATCH32,
-	PICFMT_FLAT32
+	PICFMT_FLAT32,
+	PICFMT_DOOMPATCH32
 } pictureformat_t;
 
 typedef enum
@@ -76,8 +79,10 @@ void *Picture_TextureToFlat(size_t trickytex);
 
 INT32 Picture_FormatBPP(pictureformat_t format);
 boolean Picture_IsPatchFormat(pictureformat_t format);
+boolean Picture_IsInternalPatchFormat(pictureformat_t format);
+boolean Picture_IsDoomPatchFormat(pictureformat_t format);
 boolean Picture_IsFlatFormat(pictureformat_t format);
-boolean Picture_CheckIfPatch(patch_t *patch, size_t size);
+boolean Picture_CheckIfDoomPatch(softwarepatch_t *patch, size_t size);
 
 // Structs
 typedef enum
@@ -120,15 +125,4 @@ extern spriteinfo_t spriteinfo[NUMSPRITES];
 void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps);
 void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum);
 
-// Sprite rotation
-#ifdef ROTSPRITE
-INT32 R_GetRollAngle(angle_t rollangle);
-void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, spriteframe_t *sprframe, INT32 rot, UINT8 flip);
-void R_FreeSingleRotSprite(spritedef_t *spritedef);
-void R_FreeSkinRotSprite(size_t skinnum);
-extern fixed_t rollcosang[ROTANGLES];
-extern fixed_t rollsinang[ROTANGLES];
-void R_FreeAllRotSprite(void);
-#endif
-
-#endif // __R_PATCH__
+#endif // __R_PICFORMATS__
diff --git a/src/r_plane.c b/src/r_plane.c
index 9d36c07dc2db74838e853eed12b8381a606fff40..f4fd9c39764ef1542a3c61168717261cd2a67825 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -115,33 +115,40 @@ void R_InitPlanes(void)
 }
 
 //
-// Water ripple effect!!
+// Water ripple effect
 // Needs the height of the plane, and the vertical position of the span.
-// Sets ripple_xfrac and ripple_yfrac, added to ds_xfrac and ds_yfrac, if the span is not tilted.
+// Sets planeripple.xfrac and planeripple.yfrac, added to ds_xfrac and ds_yfrac, if the span is not tilted.
 //
 
-#ifndef NOWATER
-INT32 ds_bgofs;
-INT32 ds_waterofs;
-
-static INT32 wtofs=0;
-static boolean itswater;
-static fixed_t ripple_xfrac;
-static fixed_t ripple_yfrac;
+struct
+{
+	INT32 offset;
+	fixed_t xfrac, yfrac;
+	boolean active;
+} planeripple;
 
-static void R_PlaneRipple(visplane_t *plane, INT32 y, fixed_t plheight)
+static void R_CalculatePlaneRipple(visplane_t *plane, INT32 y, fixed_t plheight, boolean calcfrac)
 {
 	fixed_t distance = FixedMul(plheight, yslope[y]);
-	const INT32 yay = (wtofs + (distance>>9) ) & 8191;
+	const INT32 yay = (planeripple.offset + (distance>>9)) & 8191;
+
 	// ripples da water texture
-	angle_t angle = (plane->viewangle + plane->plangle)>>ANGLETOFINESHIFT;
 	ds_bgofs = FixedDiv(FINESINE(yay), (1<<12) + (distance>>11))>>FRACBITS;
 
-	angle = (angle + 2048) & 8191;  // 90 degrees
-	ripple_xfrac = FixedMul(FINECOSINE(angle), (ds_bgofs<<FRACBITS));
-	ripple_yfrac = FixedMul(FINESINE(angle), (ds_bgofs<<FRACBITS));
+	if (calcfrac)
+	{
+		angle_t angle = (plane->viewangle + plane->plangle)>>ANGLETOFINESHIFT;
+		angle = (angle + 2048) & 8191; // 90 degrees
+		planeripple.xfrac = FixedMul(FINECOSINE(angle), (ds_bgofs<<FRACBITS));
+		planeripple.yfrac = FixedMul(FINESINE(angle), (ds_bgofs<<FRACBITS));
+	}
+}
+
+static void R_UpdatePlaneRipple(void)
+{
+	ds_waterofs = (leveltime & 1)*16384;
+	planeripple.offset = (leveltime * 140);
 }
-#endif
 
 //
 // R_MapPlane
@@ -159,7 +166,7 @@ static void R_PlaneRipple(visplane_t *plane, INT32 y, fixed_t plheight)
 void R_MapPlane(INT32 y, INT32 x1, INT32 x2)
 {
 	angle_t angle, planecos, planesin;
-	fixed_t distance, span;
+	fixed_t distance = 0, span;
 	size_t pindex;
 
 #ifdef RANGECHECK
@@ -167,41 +174,51 @@ void R_MapPlane(INT32 y, INT32 x1, INT32 x2)
 		I_Error("R_MapPlane: %d, %d at %d", x1, x2, y);
 #endif
 
-	// from r_splats's R_RenderFloorSplat
-	if (x1 >= vid.width) x1 = vid.width - 1;
+	if (x1 >= vid.width)
+		x1 = vid.width - 1;
 
-	angle = (currentplane->viewangle + currentplane->plangle)>>ANGLETOFINESHIFT;
-	planecos = FINECOSINE(angle);
-	planesin = FINESINE(angle);
-
-	if (planeheight != cachedheight[y])
+	if (!currentplane->slope)
 	{
-		cachedheight[y] = planeheight;
-		distance = cacheddistance[y] = FixedMul(planeheight, yslope[y]);
-		ds_xstep = cachedxstep[y] = FixedMul(distance, basexscale);
-		ds_ystep = cachedystep[y] = FixedMul(distance, baseyscale);
+		angle = (currentplane->viewangle + currentplane->plangle)>>ANGLETOFINESHIFT;
+		planecos = FINECOSINE(angle);
+		planesin = FINESINE(angle);
 
-		if ((span = abs(centery-y)))
+		if (planeheight != cachedheight[y])
 		{
-			ds_xstep = cachedxstep[y] = FixedMul(planesin, planeheight) / span;
-			ds_ystep = cachedystep[y] = FixedMul(planecos, planeheight) / span;
+			cachedheight[y] = planeheight;
+			cacheddistance[y] = distance = FixedMul(planeheight, yslope[y]);
+			span = abs(centery - y);
+
+			if (span) // don't divide by zero
+			{
+				ds_xstep = FixedMul(planesin, planeheight) / span;
+				ds_ystep = FixedMul(planecos, planeheight) / span;
+			}
+			else
+			{
+				ds_xstep = FixedMul(distance, basexscale);
+				ds_ystep = FixedMul(distance, baseyscale);
+			}
+
+			cachedxstep[y] = ds_xstep;
+			cachedystep[y] = ds_ystep;
+		}
+		else
+		{
+			distance = cacheddistance[y];
+			ds_xstep = cachedxstep[y];
+			ds_ystep = cachedystep[y];
 		}
-	}
-	else
-	{
-		distance = cacheddistance[y];
-		ds_xstep = cachedxstep[y];
-		ds_ystep = cachedystep[y];
-	}
 
-	ds_xfrac = xoffs + FixedMul(planecos, distance) + (x1 - centerx) * ds_xstep;
-	ds_yfrac = yoffs - FixedMul(planesin, distance) + (x1 - centerx) * ds_ystep;
+		ds_xfrac = xoffs + FixedMul(planecos, distance) + (x1 - centerx) * ds_xstep;
+		ds_yfrac = yoffs - FixedMul(planesin, distance) + (x1 - centerx) * ds_ystep;
+	}
 
-#ifndef NOWATER
-	if (itswater)
+	// Water ripple effect
+	if (planeripple.active)
 	{
 		// Needed for ds_bgofs
-		R_PlaneRipple(currentplane, y, planeheight);
+		R_CalculatePlaneRipple(currentplane, y, planeheight, (!currentplane->slope));
 
 		if (currentplane->slope)
 		{
@@ -211,25 +228,25 @@ void R_MapPlane(INT32 y, INT32 x1, INT32 x2)
 		}
 		else
 		{
-			ds_xfrac += ripple_xfrac;
-			ds_yfrac += ripple_yfrac;
+			ds_xfrac += planeripple.xfrac;
+			ds_yfrac += planeripple.yfrac;
 		}
 
-		if (y+ds_bgofs>=viewheight)
+		if ((y + ds_bgofs) >= viewheight)
 			ds_bgofs = viewheight-y-1;
-		if (y+ds_bgofs<0)
+		if ((y + ds_bgofs) < 0)
 			ds_bgofs = -y;
 	}
-#endif
-
-	pindex = distance >> LIGHTZSHIFT;
-	if (pindex >= MAXLIGHTZ)
-		pindex = MAXLIGHTZ - 1;
 
 	if (currentplane->slope)
 		ds_colormap = colormaps;
 	else
+	{
+		pindex = distance >> LIGHTZSHIFT;
+		if (pindex >= MAXLIGHTZ)
+			pindex = MAXLIGHTZ - 1;
 		ds_colormap = planezlight[pindex];
+	}
 
 	if (currentplane->extra_colormap)
 		ds_colormap = currentplane->extra_colormap->colormap + (ds_colormap - colormaps);
@@ -592,9 +609,7 @@ void R_DrawPlanes(void)
 	visplane_t *pl;
 	INT32 i;
 
-	// Note: are these two lines really needed?
-	// R_DrawSinglePlane and R_DrawSkyPlane do span/column drawer resets themselves anyway
-	spanfunc = spanfuncs[BASEDRAWFUNC];
+	R_UpdatePlaneRipple();
 
 	for (i = 0; i < MAXVISPLANES; i++, pl++)
 	{
@@ -606,10 +621,6 @@ void R_DrawPlanes(void)
 			R_DrawSinglePlane(pl);
 		}
 	}
-#ifndef NOWATER
-	ds_waterofs = (leveltime & 1)*16384;
-	wtofs = leveltime * 140;
-#endif
 }
 
 // R_DrawSkyPlane
@@ -655,46 +666,48 @@ static void R_DrawSkyPlane(visplane_t *pl)
 	}
 }
 
-static void R_SlopeVectors(visplane_t *pl, INT32 i, float fudge)
+// Potentially override other stuff for now cus we're mean. :< But draw a slope plane!
+// I copied ZDoom's code and adapted it to SRB2... -Red
+void R_CalculateSlopeVectors(pslope_t *slope, fixed_t planeviewx, fixed_t planeviewy, fixed_t planeviewz, fixed_t planexscale, fixed_t planeyscale, fixed_t planexoffset, fixed_t planeyoffset, angle_t planeviewangle, angle_t planeangle, float fudge)
 {
-	// Potentially override other stuff for now cus we're mean. :< But draw a slope plane!
-	// I copied ZDoom's code and adapted it to SRB2... -Red
 	floatv3_t p, m, n;
 	float ang;
 	float vx, vy, vz;
+	float xscale = FIXED_TO_FLOAT(planexscale);
+	float yscale = FIXED_TO_FLOAT(planeyscale);
 	// compiler complains when P_GetSlopeZAt is used in FLOAT_TO_FIXED directly
 	// use this as a temp var to store P_GetSlopeZAt's return value each time
 	fixed_t temp;
 
-	vx = FIXED_TO_FLOAT(pl->viewx+xoffs);
-	vy = FIXED_TO_FLOAT(pl->viewy-yoffs);
-	vz = FIXED_TO_FLOAT(pl->viewz);
+	vx = FIXED_TO_FLOAT(planeviewx+planexoffset);
+	vy = FIXED_TO_FLOAT(planeviewy-planeyoffset);
+	vz = FIXED_TO_FLOAT(planeviewz);
 
-	temp = P_GetSlopeZAt(pl->slope, pl->viewx, pl->viewy);
+	temp = P_GetSlopeZAt(slope, planeviewx, planeviewy);
 	zeroheight = FIXED_TO_FLOAT(temp);
 
 	// p is the texture origin in view space
 	// Don't add in the offsets at this stage, because doing so can result in
 	// errors if the flat is rotated.
-	ang = ANG2RAD(ANGLE_270 - pl->viewangle);
+	ang = ANG2RAD(ANGLE_270 - planeviewangle);
 	p.x = vx * cos(ang) - vy * sin(ang);
 	p.z = vx * sin(ang) + vy * cos(ang);
-	temp = P_GetSlopeZAt(pl->slope, -xoffs, yoffs);
+	temp = P_GetSlopeZAt(slope, -planexoffset, planeyoffset);
 	p.y = FIXED_TO_FLOAT(temp) - vz;
 
 	// m is the v direction vector in view space
-	ang = ANG2RAD(ANGLE_180 - (pl->viewangle + pl->plangle));
-	m.x = cos(ang);
-	m.z = sin(ang);
+	ang = ANG2RAD(ANGLE_180 - (planeviewangle + planeangle));
+	m.x = yscale * cos(ang);
+	m.z = yscale * sin(ang);
 
 	// n is the u direction vector in view space
-	n.x = sin(ang);
-	n.z = -cos(ang);
+	n.x = xscale * sin(ang);
+	n.z = -xscale * cos(ang);
 
-	ang = ANG2RAD(pl->plangle);
-	temp = P_GetSlopeZAt(pl->slope, pl->viewx + FLOAT_TO_FIXED(sin(ang)), pl->viewy + FLOAT_TO_FIXED(cos(ang)));
+	ang = ANG2RAD(planeangle);
+	temp = P_GetSlopeZAt(slope, planeviewx + yscale * FLOAT_TO_FIXED(sin(ang)), planeviewy + yscale * FLOAT_TO_FIXED(cos(ang)));
 	m.y = FIXED_TO_FLOAT(temp) - zeroheight;
-	temp = P_GetSlopeZAt(pl->slope, pl->viewx + FLOAT_TO_FIXED(cos(ang)), pl->viewy - FLOAT_TO_FIXED(sin(ang)));
+	temp = P_GetSlopeZAt(slope, planeviewx + xscale * FLOAT_TO_FIXED(cos(ang)), planeviewy - xscale * FLOAT_TO_FIXED(sin(ang)));
 	n.y = FIXED_TO_FLOAT(temp) - zeroheight;
 
 	if (ds_powersoftwo)
@@ -710,42 +723,58 @@ static void R_SlopeVectors(visplane_t *pl, INT32 i, float fudge)
 
 	// Eh. I tried making this stuff fixed-point and it exploded on me. Here's a macro for the only floating-point vector function I recall using.
 #define CROSS(d, v1, v2) \
-d.x = (v1.y * v2.z) - (v1.z * v2.y);\
-d.y = (v1.z * v2.x) - (v1.x * v2.z);\
-d.z = (v1.x * v2.y) - (v1.y * v2.x)
-	CROSS(ds_su[i], p, m);
-	CROSS(ds_sv[i], p, n);
-	CROSS(ds_sz[i], m, n);
+d->x = (v1.y * v2.z) - (v1.z * v2.y);\
+d->y = (v1.z * v2.x) - (v1.x * v2.z);\
+d->z = (v1.x * v2.y) - (v1.y * v2.x)
+		CROSS(ds_sup, p, m);
+		CROSS(ds_svp, p, n);
+		CROSS(ds_szp, m, n);
 #undef CROSS
 
-	ds_su[i].z *= focallengthf;
-	ds_sv[i].z *= focallengthf;
-	ds_sz[i].z *= focallengthf;
+	ds_sup->z *= focallengthf;
+	ds_svp->z *= focallengthf;
+	ds_szp->z *= focallengthf;
 
 	// Premultiply the texture vectors with the scale factors
 #define SFMULT 65536.f
 	if (ds_powersoftwo)
 	{
-		ds_su[i].x *= (SFMULT * (1<<nflatshiftup));
-		ds_su[i].y *= (SFMULT * (1<<nflatshiftup));
-		ds_su[i].z *= (SFMULT * (1<<nflatshiftup));
-		ds_sv[i].x *= (SFMULT * (1<<nflatshiftup));
-		ds_sv[i].y *= (SFMULT * (1<<nflatshiftup));
-		ds_sv[i].z *= (SFMULT * (1<<nflatshiftup));
+		ds_sup->x *= (SFMULT * (1<<nflatshiftup));
+		ds_sup->y *= (SFMULT * (1<<nflatshiftup));
+		ds_sup->z *= (SFMULT * (1<<nflatshiftup));
+		ds_svp->x *= (SFMULT * (1<<nflatshiftup));
+		ds_svp->y *= (SFMULT * (1<<nflatshiftup));
+		ds_svp->z *= (SFMULT * (1<<nflatshiftup));
 	}
 	else
 	{
 		// Lactozilla: I'm essentially multiplying the vectors by FRACUNIT...
-		ds_su[i].x *= SFMULT;
-		ds_su[i].y *= SFMULT;
-		ds_su[i].z *= SFMULT;
-		ds_sv[i].x *= SFMULT;
-		ds_sv[i].y *= SFMULT;
-		ds_sv[i].z *= SFMULT;
+		ds_sup->x *= SFMULT;
+		ds_sup->y *= SFMULT;
+		ds_sup->z *= SFMULT;
+		ds_svp->x *= SFMULT;
+		ds_svp->y *= SFMULT;
+		ds_svp->z *= SFMULT;
 	}
 #undef SFMULT
 }
 
+static void R_SetSlopePlaneVectors(visplane_t *pl, INT32 y, fixed_t xoff, fixed_t yoff, float fudge)
+{
+	if (ds_su == NULL)
+		ds_su = Z_Malloc(sizeof(*ds_su) * vid.height, PU_STATIC, NULL);
+	if (ds_sv == NULL)
+		ds_sv = Z_Malloc(sizeof(*ds_sv) * vid.height, PU_STATIC, NULL);
+	if (ds_sz == NULL)
+		ds_sz = Z_Malloc(sizeof(*ds_sz) * vid.height, PU_STATIC, NULL);
+
+	ds_sup = &ds_su[y];
+	ds_svp = &ds_sv[y];
+	ds_szp = &ds_sz[y];
+
+	R_CalculateSlopeVectors(pl->slope, pl->viewx, pl->viewy, pl->viewz, FRACUNIT, FRACUNIT, xoff, yoff, pl->viewangle, pl->plangle, fudge);
+}
+
 void R_DrawSinglePlane(visplane_t *pl)
 {
 	levelflat_t *levelflat;
@@ -755,6 +784,7 @@ void R_DrawSinglePlane(visplane_t *pl)
 	ffloor_t *rover;
 	int type;
 	int spanfunctype = BASEDRAWFUNC;
+	angle_t viewang = viewangle;
 
 	if (!(pl->minx <= pl->maxx))
 		return;
@@ -766,9 +796,7 @@ void R_DrawSinglePlane(visplane_t *pl)
 		return;
 	}
 
-#ifndef NOWATER
-	itswater = false;
-#endif
+	planeripple.active = false;
 	spanfunc = spanfuncs[BASEDRAWFUNC];
 
 	if (pl->polyobj)
@@ -779,7 +807,7 @@ void R_DrawSinglePlane(visplane_t *pl)
 		else if (pl->polyobj->translucency > 0)
 		{
 			spanfunctype = (pl->polyobj->flags & POF_SPLAT) ? SPANDRAWFUNC_TRANSSPLAT : SPANDRAWFUNC_TRANS;
-			ds_transmap = transtables + ((pl->polyobj->translucency-1)<<FF_TRANSSHIFT);
+			ds_transmap = R_GetTranslucencyTable(pl->polyobj->translucency);
 		}
 		else if (pl->polyobj->flags & POF_SPLAT) // Opaque, but allow transparent flat pixels
 			spanfunctype = SPANDRAWFUNC_SPLAT;
@@ -818,23 +846,23 @@ void R_DrawSinglePlane(visplane_t *pl)
 				if (pl->ffloor->alpha < 12)
 					return; // Don't even draw it
 				else if (pl->ffloor->alpha < 38)
-					ds_transmap = transtables + ((tr_trans90-1)<<FF_TRANSSHIFT);
+					ds_transmap = R_GetTranslucencyTable(tr_trans90);
 				else if (pl->ffloor->alpha < 64)
-					ds_transmap = transtables + ((tr_trans80-1)<<FF_TRANSSHIFT);
+					ds_transmap = R_GetTranslucencyTable(tr_trans80);
 				else if (pl->ffloor->alpha < 89)
-					ds_transmap = transtables + ((tr_trans70-1)<<FF_TRANSSHIFT);
+					ds_transmap = R_GetTranslucencyTable(tr_trans70);
 				else if (pl->ffloor->alpha < 115)
-					ds_transmap = transtables + ((tr_trans60-1)<<FF_TRANSSHIFT);
+					ds_transmap = R_GetTranslucencyTable(tr_trans60);
 				else if (pl->ffloor->alpha < 140)
-					ds_transmap = transtables + ((tr_trans50-1)<<FF_TRANSSHIFT);
+					ds_transmap = R_GetTranslucencyTable(tr_trans50);
 				else if (pl->ffloor->alpha < 166)
-					ds_transmap = transtables + ((tr_trans40-1)<<FF_TRANSSHIFT);
+					ds_transmap = R_GetTranslucencyTable(tr_trans40);
 				else if (pl->ffloor->alpha < 192)
-					ds_transmap = transtables + ((tr_trans30-1)<<FF_TRANSSHIFT);
+					ds_transmap = R_GetTranslucencyTable(tr_trans30);
 				else if (pl->ffloor->alpha < 217)
-					ds_transmap = transtables + ((tr_trans20-1)<<FF_TRANSSHIFT);
+					ds_transmap = R_GetTranslucencyTable(tr_trans20);
 				else if (pl->ffloor->alpha < 243)
-					ds_transmap = transtables + ((tr_trans10-1)<<FF_TRANSSHIFT);
+					ds_transmap = R_GetTranslucencyTable(tr_trans10);
 				else // Opaque, but allow transparent flat pixels
 					spanfunctype = SPANDRAWFUNC_SPLAT;
 
@@ -850,12 +878,12 @@ void R_DrawSinglePlane(visplane_t *pl)
 			}
 			else light = (pl->lightlevel >> LIGHTSEGSHIFT);
 
-	#ifndef NOWATER
 			if (pl->ffloor->flags & FF_RIPPLE)
 			{
 				INT32 top, bottom;
 
-				itswater = true;
+				planeripple.active = true;
+
 				if (spanfunctype == SPANDRAWFUNC_TRANS)
 				{
 					spanfunctype = SPANDRAWFUNC_WATER;
@@ -875,26 +903,11 @@ void R_DrawSinglePlane(visplane_t *pl)
 										 vid.width, vid.width);
 				}
 			}
-	#endif
 		}
 		else
 			light = (pl->lightlevel >> LIGHTSEGSHIFT);
 	}
 
-	if (!pl->slope // Don't mess with angle on slopes! We'll handle this ourselves later
-		&& viewangle != pl->viewangle+pl->plangle)
-	{
-		memset(cachedheight, 0, sizeof (cachedheight));
-		angle = (pl->viewangle+pl->plangle-ANGLE_90)>>ANGLETOFINESHIFT;
-		basexscale = FixedDiv(FINECOSINE(angle),centerxfrac);
-		baseyscale = -FixedDiv(FINESINE(angle),centerxfrac);
-		viewangle = pl->viewangle+pl->plangle;
-	}
-
-	xoffs = pl->xoffs;
-	yoffs = pl->yoffs;
-	planeheight = abs(pl->height - pl->viewz);
-
 	currentplane = pl;
 	levelflat = &levelflats[pl->picnum];
 
@@ -919,6 +932,20 @@ void R_DrawSinglePlane(visplane_t *pl)
 				R_CheckFlatLength(ds_flatwidth * ds_flatheight);
 	}
 
+	if (!pl->slope // Don't mess with angle on slopes! We'll handle this ourselves later
+		&& viewangle != pl->viewangle+pl->plangle)
+	{
+		memset(cachedheight, 0, sizeof (cachedheight));
+		angle = (pl->viewangle+pl->plangle-ANGLE_90)>>ANGLETOFINESHIFT;
+		basexscale = FixedDiv(FINECOSINE(angle),centerxfrac);
+		baseyscale = -FixedDiv(FINESINE(angle),centerxfrac);
+		viewangle = pl->viewangle+pl->plangle;
+	}
+
+	xoffs = pl->xoffs;
+	yoffs = pl->yoffs;
+	planeheight = abs(pl->height - pl->viewz);
+
 	if (light >= LIGHTLEVELS)
 		light = LIGHTLEVELS-1;
 
@@ -978,50 +1005,41 @@ void R_DrawSinglePlane(visplane_t *pl)
 				xoffs -= (pl->slope->o.x + (1 << (31-nflatshiftup))) & ~((1 << (32-nflatshiftup))-1);
 				yoffs += (pl->slope->o.y + (1 << (31-nflatshiftup))) & ~((1 << (32-nflatshiftup))-1);
 			}
+
 			xoffs = (fixed_t)(xoffs*fudgecanyon);
 			yoffs = (fixed_t)(yoffs/fudgecanyon);
 		}
 
-		ds_sup = &ds_su[0];
-		ds_svp = &ds_sv[0];
-		ds_szp = &ds_sz[0];
-
-#ifndef NOWATER
-		if (itswater)
+		if (planeripple.active)
 		{
-			INT32 i;
 			fixed_t plheight = abs(P_GetSlopeZAt(pl->slope, pl->viewx, pl->viewy) - pl->viewz);
-			fixed_t rxoffs = xoffs;
-			fixed_t ryoffs = yoffs;
 
 			R_PlaneBounds(pl);
 
-			for (i = pl->high; i < pl->low; i++)
+			for (x = pl->high; x < pl->low; x++)
 			{
-				R_PlaneRipple(pl, i, plheight);
-				xoffs = rxoffs + ripple_xfrac;
-				yoffs = ryoffs + ripple_yfrac;
-				R_SlopeVectors(pl, i, fudgecanyon);
+				R_CalculatePlaneRipple(pl, x, plheight, true);
+				R_SetSlopePlaneVectors(pl, x, (xoffs + planeripple.xfrac), (yoffs + planeripple.yfrac), fudgecanyon);
 			}
-
-			xoffs = rxoffs;
-			yoffs = ryoffs;
 		}
 		else
-#endif
-			R_SlopeVectors(pl, 0, fudgecanyon);
+			R_SetSlopePlaneVectors(pl, 0, xoffs, yoffs, fudgecanyon);
 
-#ifndef NOWATER
-		if (itswater && (spanfunctype == SPANDRAWFUNC_WATER))
-			spanfunctype = SPANDRAWFUNC_TILTEDWATER;
-		else
-#endif
-		if (spanfunctype == SPANDRAWFUNC_TRANS)
-			spanfunctype = SPANDRAWFUNC_TILTEDTRANS;
-		else if (spanfunctype == SPANDRAWFUNC_SPLAT)
-			spanfunctype = SPANDRAWFUNC_TILTEDSPLAT;
-		else
-			spanfunctype = SPANDRAWFUNC_TILTED;
+		switch (spanfunctype)
+		{
+			case SPANDRAWFUNC_WATER:
+				spanfunctype = SPANDRAWFUNC_TILTEDWATER;
+				break;
+			case SPANDRAWFUNC_TRANS:
+				spanfunctype = SPANDRAWFUNC_TILTEDTRANS;
+				break;
+			case SPANDRAWFUNC_SPLAT:
+				spanfunctype = SPANDRAWFUNC_TILTEDSPLAT;
+				break;
+			default:
+				spanfunctype = SPANDRAWFUNC_TILTED;
+				break;
+		}
 
 		planezlight = scalelight[light];
 	}
@@ -1081,7 +1099,7 @@ using the palette colors.
 	if (spanfunc == spanfuncs[BASEDRAWFUNC])
 	{
 		INT32 i;
-		ds_transmap = transtables + ((tr_trans50-1)<<FF_TRANSSHIFT);
+		ds_transmap = R_GetTranslucencyTable(tr_trans50);
 		spanfunc = spanfuncs[SPANDRAWFUNC_TRANS];
 		for (i=0; i<4; i++)
 		{
@@ -1131,6 +1149,8 @@ using the palette colors.
 		}
 	}
 #endif
+
+	viewangle = viewang;
 }
 
 void R_PlaneBounds(visplane_t *plane)
diff --git a/src/r_plane.h b/src/r_plane.h
index 8d5ce9ee42330149e2b39255e580831dfd752f7f..7664858c9a88e44c4ca29946fd34a0c47f9acde8 100644
--- a/src/r_plane.h
+++ b/src/r_plane.h
@@ -87,11 +87,15 @@ visplane_t *R_CheckPlane(visplane_t *pl, INT32 start, INT32 stop);
 void R_ExpandPlane(visplane_t *pl, INT32 start, INT32 stop);
 void R_PlaneBounds(visplane_t *plane);
 
-// Draws a single visplane.
-void R_DrawSinglePlane(visplane_t *pl);
 void R_CheckFlatLength(size_t size);
 boolean R_CheckPowersOfTwo(void);
 
+// Draws a single visplane.
+void R_DrawSinglePlane(visplane_t *pl);
+
+// Calculates the slope vectors needed for tilted span drawing.
+void R_CalculateSlopeVectors(pslope_t *slope, fixed_t planeviewx, fixed_t planeviewy, fixed_t planeviewz, fixed_t planexscale, fixed_t planeyscale, fixed_t planexoffset, fixed_t planeyoffset, angle_t planeviewangle, angle_t planeangle, float fudge);
+
 typedef struct planemgr_s
 {
 	visplane_t *plane;
diff --git a/src/r_segs.c b/src/r_segs.c
index 2cd7ebab2b5ea3018f29dbda41c6e6bc7d2c8ac2..1ed1f0285f785465ea3a307c0a6250b4db0d5c49 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -25,6 +25,7 @@
 #include "p_local.h" // Camera...
 #include "p_slopes.h"
 #include "console.h" // con_clipviewtop
+#include "taglist.h"
 
 // OPTIMIZE: closed two sided lines as single sided
 
@@ -72,170 +73,6 @@ static lighttable_t **walllights;
 static INT16 *maskedtexturecol;
 static fixed_t *maskedtextureheight = NULL;
 
-// ==========================================================================
-// R_Splats Wall Splats Drawer
-// ==========================================================================
-
-#ifdef WALLSPLATS
-static INT16 last_ceilingclip[MAXVIDWIDTH];
-static INT16 last_floorclip[MAXVIDWIDTH];
-
-static void R_DrawSplatColumn(column_t *column)
-{
-	INT32 topscreen, bottomscreen;
-	fixed_t basetexturemid;
-	INT32 topdelta, prevdelta = -1;
-
-	basetexturemid = dc_texturemid;
-
-	for (; column->topdelta != 0xff ;)
-	{
-		// calculate unclipped screen coordinates for post
-		topdelta = column->topdelta;
-		if (topdelta <= prevdelta)
-			topdelta += prevdelta;
-		prevdelta = topdelta;
-		topscreen = sprtopscreen + spryscale*topdelta;
-		bottomscreen = topscreen + spryscale*column->length;
-
-		dc_yl = (topscreen+FRACUNIT-1)>>FRACBITS;
-		dc_yh = (bottomscreen-1)>>FRACBITS;
-
-		if (dc_yh >= last_floorclip[dc_x])
-			dc_yh = last_floorclip[dc_x] - 1;
-		if (dc_yl <= last_ceilingclip[dc_x])
-			dc_yl = last_ceilingclip[dc_x] + 1;
-		if (dc_yl <= dc_yh && dl_yh < vid.height && yh > 0)
-		{
-			dc_source = (UINT8 *)column + 3;
-			dc_texturemid = basetexturemid - (topdelta<<FRACBITS);
-
-			// Drawn by R_DrawColumn.
-			colfunc();
-		}
-		column = (column_t *)((UINT8 *)column + column->length + 4);
-	}
-
-	dc_texturemid = basetexturemid;
-}
-
-static void R_DrawWallSplats(void)
-{
-	wallsplat_t *splat;
-	seg_t *seg;
-	angle_t angle, angle1, angle2;
-	INT32 x1, x2;
-	size_t pindex;
-	column_t *col;
-	patch_t *patch;
-	fixed_t texturecolumn;
-
-	splat = (wallsplat_t *)linedef->splats;
-
-	I_Assert(splat != NULL);
-
-	seg = ds_p->curline;
-
-	// draw all splats from the line that touches the range of the seg
-	for (; splat; splat = splat->next)
-	{
-		angle1 = R_PointToAngle(splat->v1.x, splat->v1.y);
-		angle2 = R_PointToAngle(splat->v2.x, splat->v2.y);
-		angle1 = (angle1 - viewangle + ANGLE_90)>>ANGLETOFINESHIFT;
-		angle2 = (angle2 - viewangle + ANGLE_90)>>ANGLETOFINESHIFT;
-		// out of the viewangletox lut
-		/// \todo clip it to the screen
-		if (angle1 > FINEANGLES/2 || angle2 > FINEANGLES/2)
-			continue;
-		x1 = viewangletox[angle1];
-		x2 = viewangletox[angle2];
-
-		if (x1 >= x2)
-			continue; // does not cross a pixel
-
-		// splat is not in this seg range
-		if (x2 < ds_p->x1 || x1 > ds_p->x2)
-			continue;
-
-		if (x1 < ds_p->x1)
-			x1 = ds_p->x1;
-		if (x2 > ds_p->x2)
-			x2 = ds_p->x2;
-		if (x2 <= x1)
-			continue;
-
-		// calculate incremental stepping values for texture edges
-		rw_scalestep = ds_p->scalestep;
-		spryscale = ds_p->scale1 + (x1 - ds_p->x1)*rw_scalestep;
-		mfloorclip = floorclip;
-		mceilingclip = ceilingclip;
-
-		patch = W_CachePatchNum(splat->patch, PU_PATCH);
-
-		dc_texturemid = splat->top + (SHORT(patch->height)<<(FRACBITS-1)) - viewz;
-		if (splat->yoffset)
-			dc_texturemid += *splat->yoffset;
-
-		sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale);
-
-		// set drawing mode
-		switch (splat->flags & SPLATDRAWMODE_MASK)
-		{
-			case SPLATDRAWMODE_OPAQUE:
-				colfunc = colfuncs[BASEDRAWFUNC];
-				break;
-			case SPLATDRAWMODE_TRANS:
-				if (!cv_translucency.value)
-					colfunc = colfuncs[BASEDRAWFUNC];
-				else
-				{
-					dc_transmap = transtables + ((tr_trans50 - 1)<<FF_TRANSSHIFT);
-					colfunc = colfuncs[COLDRAWFUNC_FUZZY];
-				}
-
-				break;
-			case SPLATDRAWMODE_SHADE:
-				colfunc = colfuncs[COLDRAWFUNC_SHADE];
-				break;
-		}
-
-		dc_texheight = 0;
-
-		// draw the columns
-		for (dc_x = x1; dc_x <= x2; dc_x++, spryscale += rw_scalestep)
-		{
-			pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
-			if (pindex >= MAXLIGHTSCALE)
-				pindex = MAXLIGHTSCALE - 1;
-			dc_colormap = walllights[pindex];
-
-			if (frontsector->extra_colormap)
-				dc_colormap = frontsector->extra_colormap->colormap + (dc_colormap - colormaps);
-
-			sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale);
-			dc_iscale = 0xffffffffu / (unsigned)spryscale;
-
-			// find column of patch, from perspective
-			angle = (rw_centerangle + xtoviewangle[dc_x])>>ANGLETOFINESHIFT;
-				texturecolumn = rw_offset2 - splat->offset
-					- FixedMul(FINETANGENT(angle), rw_distance);
-
-			// FIXME!
-			texturecolumn >>= FRACBITS;
-			if (texturecolumn < 0 || texturecolumn >= SHORT(patch->width))
-				continue;
-
-			// draw the texture
-			col = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[texturecolumn]));
-			R_DrawSplatColumn(col);
-		}
-	} // next splat
-
-	colfunc = colfuncs[BASEDRAWFUNC];
-}
-
-#endif //WALLSPLATS
-
 // ==========================================================================
 // R_RenderMaskedSegRange
 // ==========================================================================
@@ -320,7 +157,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 
 	if (ldef->alpha > 0 && ldef->alpha < FRACUNIT)
 	{
-		dc_transmap = transtables + ((R_GetLinedefTransTable(ldef->alpha) - 1) << FF_TRANSSHIFT);
+		dc_transmap = R_GetTranslucencyTable(R_GetLinedefTransTable(ldef->alpha));
 		colfunc = colfuncs[COLDRAWFUNC_FUZZY];
 
 	}
@@ -338,7 +175,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 		if (curline->polyseg->translucency >= NUMTRANSMAPS)
 			return;
 
-		dc_transmap = transtables + ((curline->polyseg->translucency-1)<<FF_TRANSSHIFT);
+		dc_transmap = R_GetTranslucencyTable(curline->polyseg->translucency);
 		colfunc = colfuncs[COLDRAWFUNC_FUZZY];
 	}
 
@@ -766,23 +603,23 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 		if (pfloor->alpha < 12)
 			return; // Don't even draw it
 		else if (pfloor->alpha < 38)
-			dc_transmap = transtables + ((tr_trans90-1)<<FF_TRANSSHIFT);
+			dc_transmap = R_GetTranslucencyTable(tr_trans90);
 		else if (pfloor->alpha < 64)
-			dc_transmap = transtables + ((tr_trans80-1)<<FF_TRANSSHIFT);
+			dc_transmap = R_GetTranslucencyTable(tr_trans80);
 		else if (pfloor->alpha < 89)
-			dc_transmap = transtables + ((tr_trans70-1)<<FF_TRANSSHIFT);
+			dc_transmap = R_GetTranslucencyTable(tr_trans70);
 		else if (pfloor->alpha < 115)
-			dc_transmap = transtables + ((tr_trans60-1)<<FF_TRANSSHIFT);
+			dc_transmap = R_GetTranslucencyTable(tr_trans60);
 		else if (pfloor->alpha < 140)
-			dc_transmap = transtables + ((tr_trans50-1)<<FF_TRANSSHIFT);
+			dc_transmap = R_GetTranslucencyTable(tr_trans50);
 		else if (pfloor->alpha < 166)
-			dc_transmap = transtables + ((tr_trans40-1)<<FF_TRANSSHIFT);
+			dc_transmap = R_GetTranslucencyTable(tr_trans40);
 		else if (pfloor->alpha < 192)
-			dc_transmap = transtables + ((tr_trans30-1)<<FF_TRANSSHIFT);
+			dc_transmap = R_GetTranslucencyTable(tr_trans30);
 		else if (pfloor->alpha < 217)
-			dc_transmap = transtables + ((tr_trans20-1)<<FF_TRANSSHIFT);
+			dc_transmap = R_GetTranslucencyTable(tr_trans20);
 		else if (pfloor->alpha < 243)
-			dc_transmap = transtables + ((tr_trans10-1)<<FF_TRANSSHIFT);
+			dc_transmap = R_GetTranslucencyTable(tr_trans10);
 		else
 			fuzzy = false; // Opaque
 
@@ -2067,7 +1904,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		    || backsector->floorlightsec != frontsector->floorlightsec
 		    //SoM: 4/3/2000: Check for colormaps
 		    || frontsector->extra_colormap != backsector->extra_colormap
-		    || (frontsector->ffloors != backsector->ffloors && frontsector->tag != backsector->tag))
+		    || (frontsector->ffloors != backsector->ffloors && !Tag_Compare(&frontsector->tags, &backsector->tags)))
 		{
 			markfloor = true;
 		}
@@ -2098,7 +1935,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		    || backsector->ceilinglightsec != frontsector->ceilinglightsec
 		    //SoM: 4/3/2000: Check for colormaps
 		    || frontsector->extra_colormap != backsector->extra_colormap
-		    || (frontsector->ffloors != backsector->ffloors && frontsector->tag != backsector->tag))
+		    || (frontsector->ffloors != backsector->ffloors && !Tag_Compare(&frontsector->tags, &backsector->tags)))
 		{
 				markceiling = true;
 		}
@@ -2188,7 +2025,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 		rw_bottomtexturemid += sidedef->rowoffset;
 
 		// allocate space for masked texture tables
-		if (frontsector && backsector && frontsector->tag != backsector->tag && (backsector->ffloors || frontsector->ffloors))
+		if (frontsector && backsector && !Tag_Compare(&frontsector->tags, &backsector->tags) && (backsector->ffloors || frontsector->ffloors))
 		{
 			ffloor_t *rover;
 			ffloor_t *r2;
@@ -2230,6 +2067,9 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 
 					for (r2 = frontsector->ffloors; r2; r2 = r2->next)
 					{
+						if (r2->master == rover->master) // Skip if same control line.
+							break;
+
 						if (!(r2->flags & FF_EXISTS) || !(r2->flags & FF_RENDERSIDES))
 							continue;
 
@@ -2285,6 +2125,9 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 
 					for (r2 = backsector->ffloors; r2; r2 = r2->next)
 					{
+						if (r2->master == rover->master) // Skip if same control line.
+							break;
+
 						if (!(r2->flags & FF_EXISTS) || !(r2->flags & FF_RENDERSIDES))
 							continue;
 
@@ -2880,20 +2723,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 	rw_tsilheight = &(ds_p->tsilheight);
 	rw_bsilheight = &(ds_p->bsilheight);
 
-#ifdef WALLSPLATS
-	if (linedef->splats && cv_splats.value)
-	{
-		// Isn't a bit wasteful to copy the ENTIRE array for every drawseg?
-		M_Memcpy(last_ceilingclip + ds_p->x1, ceilingclip + ds_p->x1,
-			sizeof (INT16) * (ds_p->x2 - ds_p->x1 + 1));
-		M_Memcpy(last_floorclip + ds_p->x1, floorclip + ds_p->x1,
-			sizeof (INT16) * (ds_p->x2 - ds_p->x1 + 1));
-		R_RenderSegLoop();
-		R_DrawWallSplats();
-	}
-	else
-#endif
-		R_RenderSegLoop();
+	R_RenderSegLoop();
 	colfunc = colfuncs[BASEDRAWFUNC];
 
 	if (portalline) // if curline is a portal, set portalrender for drawseg
diff --git a/src/r_skins.h b/src/r_skins.h
index 04ce459a37889d9455be439da72e3cbd34318966..fbbb38743d84704d3373aafd9e5cc1a7135a46d2 100644
--- a/src/r_skins.h
+++ b/src/r_skins.h
@@ -17,6 +17,7 @@
 #include "info.h"
 #include "sounds.h"
 #include "d_player.h" // skinflags
+#include "r_patch.h"
 #include "r_picformats.h" // spriteinfo_t
 #include "r_defs.h" // spritedef_t
 
diff --git a/src/r_splats.c b/src/r_splats.c
index dfec185a11ef2f4d6fc30ca532bd3de003d61942..636aa30edc160976e291ea6e91ff1b9e3b552e19 100644
--- a/src/r_splats.c
+++ b/src/r_splats.c
@@ -8,619 +8,592 @@
 // See the 'LICENSE' file for more details.
 //-----------------------------------------------------------------------------
 /// \file  r_splats.c
-/// \brief floor and wall splats
+/// \brief Floor splats
 
 #include "r_draw.h"
 #include "r_main.h"
-#include "r_plane.h"
 #include "r_splats.h"
+#include "r_bsp.h"
+#include "p_local.h"
+#include "p_slopes.h"
 #include "w_wad.h"
 #include "z_zone.h"
-#include "d_netcmd.h"
 
-#ifdef WALLSPLATS
-static wallsplat_t wallsplats[MAXLEVELSPLATS]; // WALL splats
-static INT32 freewallsplat;
-#endif
-
-#ifdef USEASM
-/// \brief for floorsplats \note accessed by asm code
-struct rastery_s *prastertab;
-#endif
+struct rastery_s *prastertab; // for ASM code
 
-#ifdef FLOORSPLATS
-static floorsplat_t floorsplats[1]; // FLOOR splats
-static INT32 freefloorsplat;
-
-struct rastery_s
-{
-	fixed_t minx, maxx; // for each raster line starting at line 0
-	fixed_t tx1, ty1;
-	fixed_t tx2, ty2; // start/end points in texture at this line
-};
 static struct rastery_s rastertab[MAXVIDHEIGHT];
-
 static void prepare_rastertab(void);
-#endif
 
-// --------------------------------------------------------------------------
-// setup splat cache
-// --------------------------------------------------------------------------
-void R_ClearLevelSplats(void)
-{
-#ifdef WALLSPLATS
-	freewallsplat = 0;
-	memset(wallsplats, 0, sizeof (wallsplats));
-#endif
-#ifdef FLOORSPLATS
-	freefloorsplat = 0;
-	memset(floorsplats, 0, sizeof (floorsplats));
+// ==========================================================================
+//                                                               FLOOR SPLATS
+// ==========================================================================
 
-	// setup to draw floorsplats
-	prastertab = rastertab;
-	prepare_rastertab();
+#ifdef USEASM
+void ASMCALL rasterize_segment_tex_asm(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 tv1, INT32 tv2, INT32 tc, INT32 dir);
 #endif
-}
 
-// ==========================================================================
-//                                                                WALL SPLATS
-// ==========================================================================
-#ifdef WALLSPLATS
-// --------------------------------------------------------------------------
-// Return a pointer to a splat free for use, or NULL if no more splats are
-// available
-// --------------------------------------------------------------------------
-static wallsplat_t *R_AllocWallSplat(void)
+// Lactozilla
+static void rasterize_segment_tex(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 tv1, INT32 tv2, INT32 tc, INT32 dir)
 {
-	wallsplat_t *splat;
-	wallsplat_t *p_splat;
-	line_t *li;
-
-	// clear the splat from the line if it was in use
-	splat = &wallsplats[freewallsplat];
-	li = splat->line;
-	if (li)
+#ifdef USEASM
+	if (R_ASM)
 	{
-		// remove splat from line splats list
-		if (li->splats == splat)
-			li->splats = splat->next; // remove from head
-		else
-		{
-			I_Assert(li->splats != NULL);
-			for (p_splat = li->splats; p_splat->next; p_splat = p_splat->next)
-				if (p_splat->next == splat)
-				{
-					p_splat->next = splat->next;
-					break;
-				}
-		}
+		rasterize_segment_tex_asm(x1, y1, x2, y2, tv1, tv2, tc, dir);
+		return;
 	}
+	else
+#endif
+	{
+		fixed_t xs, xe, count;
+		fixed_t dx0, dx1;
 
-	memset(splat, 0, sizeof (wallsplat_t));
+		if (y1 == y2)
+			return;
 
-	// for next allocation
-	freewallsplat++;
-	if (freewallsplat >= 20)
-		freewallsplat = 0;
+		if (y2 > y1)
+		{
+			count = (y2-y1)+1;
 
-	return splat;
-}
+			dx0 = FixedDiv((x2-x1)<<FRACBITS, count<<FRACBITS);
+			dx1 = FixedDiv((tv2-tv1)<<FRACBITS, count<<FRACBITS);
 
-// Add a new splat to the linedef:
-// top: top z coord
-// wallfrac: frac along the linedef vector (0 to FRACUNIT)
-// splatpatchname: name of patch to draw
-void R_AddWallSplat(line_t *wallline, INT16 sectorside, const char *patchname, fixed_t top,
-	fixed_t wallfrac, INT32 flags)
-{
-	fixed_t fracsplat, linelength;
-	wallsplat_t *splat = NULL;
-	wallsplat_t *p_splat;
-	patch_t *patch;
-	sector_t *backsector = NULL;
-
-	if (W_CheckNumForName(patchname) != LUMPERROR)
-		splat = R_AllocWallSplat();
-	if (!splat)
-		return;
+			xs = x1 << FRACBITS;
+			xe = tv1 << FRACBITS;
+			tc <<= FRACBITS;
 
-	// set the splat
-	splat->patch = W_GetNumForName(patchname);
-	sectorside ^= 1;
-	if (wallline->sidenum[sectorside] != 0xffff)
-	{
-		backsector = sides[wallline->sidenum[sectorside]].sector;
+			if (dir == 0)
+			{
+				for (;;)
+				{
+					rastertab[y1].maxx = xs;
+					rastertab[y1].tx2 = xe;
+					rastertab[y1].ty2 = tc;
 
-		if (top < backsector->floorheight)
-		{
-			splat->yoffset = &backsector->floorheight;
-			top -= backsector->floorheight;
-		}
-		else if (top > backsector->ceilingheight)
-		{
-			splat->yoffset = &backsector->ceilingheight;
-			top -= backsector->ceilingheight;
-		}
-	}
+					xs += dx0;
+					xe += dx1;
+					y1++;
 
-	splat->top = top;
-	splat->flags = flags;
+					if (count-- < 1) break;
+				}
+			}
+			else
+			{
+				for (;;)
+				{
+					rastertab[y1].maxx = xs;
+					rastertab[y1].tx2 = tc;
+					rastertab[y1].ty2 = xe;
 
-	// bad.. but will be needed for drawing anyway..
-	patch = W_CachePatchNum(splat->patch, PU_PATCH);
+					xs += dx0;
+					xe += dx1;
+					y1++;
 
-	// offset needed by draw code for texture mapping
-	linelength = P_SegLength((seg_t *)wallline);
-	splat->offset = FixedMul(wallfrac, linelength) - (SHORT(patch->width)<<(FRACBITS-1));
-	fracsplat = FixedDiv(((SHORT(patch->width)<<FRACBITS)>>1), linelength);
+					if (count-- < 1) break;
+				}
+			}
+		}
+		else
+		{
+			count = (y1-y2)+1;
 
-	wallfrac -= fracsplat;
-	if (wallfrac > linelength)
-		return;
-	splat->v1.x = wallline->v1->x + FixedMul(wallline->dx, wallfrac);
-	splat->v1.y = wallline->v1->y + FixedMul(wallline->dy, wallfrac);
-	wallfrac += fracsplat + fracsplat;
-	if (wallfrac < 0)
-		return;
-	splat->v2.x = wallline->v1->x + FixedMul(wallline->dx, wallfrac);
-	splat->v2.y = wallline->v1->y + FixedMul(wallline->dy, wallfrac);
+			dx0 = FixedDiv((x1-x2)<<FRACBITS, count<<FRACBITS);
+			dx1 = FixedDiv((tv1-tv2)<<FRACBITS, count<<FRACBITS);
 
-	if (wallline->frontsector && wallline->frontsector == backsector)
-		return;
+			xs = x2 << FRACBITS;
+			xe = tv2 << FRACBITS;
+			tc <<= FRACBITS;
 
-	// insert splat in the linedef splat list
-	// BP: why not insert in head is much more simple?
-	// BP: because for remove it is more simple!
-	splat->line = wallline;
-	splat->next = NULL;
-	if (wallline->splats)
-	{
-		p_splat = wallline->splats;
-		while (p_splat->next)
-			p_splat = p_splat->next;
-		p_splat->next = splat;
-	}
-	else
-		wallline->splats = splat;
-}
-#endif // WALLSPLATS
+			if (dir == 0)
+			{
+				for (;;)
+				{
+					rastertab[y2].minx = xs;
+					rastertab[y2].tx1 = xe;
+					rastertab[y2].ty1 = tc;
 
-// ==========================================================================
-//                                                               FLOOR SPLATS
-// ==========================================================================
-#ifdef FLOORSPLATS
+					xs += dx0;
+					xe += dx1;
+					y2++;
 
-// --------------------------------------------------------------------------
-// Return a pointer to a splat free for use, or NULL if no more splats are
-// available
-// --------------------------------------------------------------------------
-static floorsplat_t *R_AllocFloorSplat(void)
-{
-	floorsplat_t *splat;
-	floorsplat_t *p_splat;
-	subsector_t *sub;
-
-	// find splat to use
-	freefloorsplat++;
-	if (freefloorsplat >= 1)
-		freefloorsplat = 0;
-
-	// clear the splat from the line if it was in use
-	splat = &floorsplats[freefloorsplat];
-	sub = splat->subsector;
-	if (sub)
-	{
-		// remove splat from subsector splats list
-		if (sub->splats == splat)
-			sub->splats = splat->next; // remove from head
-		else
-		{
-			p_splat = sub->splats;
-			while (p_splat->next)
+					if (count-- < 1) break;
+				}
+			}
+			else
 			{
-				if (p_splat->next == splat)
-					p_splat->next = splat->next;
+				for (;;)
+				{
+					rastertab[y2].minx = xs;
+					rastertab[y2].tx1 = tc;
+					rastertab[y2].ty1 = xe;
+
+					xs += dx0;
+					xe += dx1;
+					y2++;
+
+					if (count-- < 1) break;
+				}
 			}
 		}
 	}
-
-	memset(splat, 0, sizeof (floorsplat_t));
-	return splat;
 }
 
-// --------------------------------------------------------------------------
-// Add a floor splat to the subsector
-// --------------------------------------------------------------------------
-void R_AddFloorSplat(subsector_t *subsec, mobj_t *mobj, const char *picname, fixed_t x, fixed_t y, fixed_t z,
-	INT32 flags)
+void R_DrawFloorSprite(vissprite_t *spr)
 {
-	floorsplat_t *splat = NULL;
-	floorsplat_t *p_splat;
-	INT32 size;
+	floorsplat_t splat;
+	mobj_t *mobj = spr->mobj;
+	fixed_t tr_x, tr_y, rot_x, rot_y, rot_z;
+
+	vector3_t *v3d;
+	vector2_t v2d[4];
+	vector2_t rotated[4];
+
+	fixed_t x, y;
+	fixed_t w, h;
+	angle_t angle, splatangle;
+	fixed_t ca, sa;
+	fixed_t xscale, yscale;
+	fixed_t xoffset, yoffset;
+	fixed_t leftoffset, topoffset;
+	pslope_t *slope = NULL;
+	INT32 i;
+
+	boolean hflip = (spr->xiscale < 0);
+	boolean vflip = (spr->cut & SC_VFLIP);
+	UINT8 flipflags = 0;
+
+	renderflags_t renderflags = spr->renderflags;
+
+	if (hflip)
+		flipflags |= PICFLAGS_XFLIP;
+	if (vflip)
+		flipflags |= PICFLAGS_YFLIP;
+
+	if (!mobj || P_MobjWasRemoved(mobj))
+		return;
 
-	if (W_CheckNumForName(picname) != LUMPERROR)
-		splat = R_AllocFloorSplat();
-	if (!splat)
+	Patch_GenerateFlat(spr->patch, flipflags);
+	splat.pic = spr->patch->flats[flipflags];
+	if (splat.pic == NULL)
 		return;
 
-	// set the splat
-	splat->pic = W_GetNumForName(picname);
-	splat->flags = flags;
-	splat->mobj = mobj;
+	splat.mobj = mobj;
+	splat.width = spr->patch->width;
+	splat.height = spr->patch->height;
+	splat.scale = mobj->scale;
 
-	splat->z = z;
+	if (mobj->skin && ((skin_t *)mobj->skin)->flags & SF_HIRES)
+		splat.scale = FixedMul(splat.scale, ((skin_t *)mobj->skin)->highresscale);
 
-	size = W_LumpLength(splat->pic);
+	if (spr->rotateflags & SRF_3D || renderflags & RF_NOSPLATBILLBOARD)
+		splatangle = mobj->angle;
+	else
+		splatangle = viewangle;
 
-	switch (size)
-	{
-		case 4194304: // 2048x2048 lump
-			splat->size = 1024;
-			break;
-		case 1048576: // 1024x1024 lump
-			splat->size = 512;
-			break;
-		case 262144:// 512x512 lump
-			splat->size = 256;
-			break;
-		case 65536: // 256x256 lump
-			splat->size = 128;
-			break;
-		case 16384: // 128x128 lump
-			splat->size = 64;
-			break;
-		case 1024: // 32x32 lump
-			splat->size = 16;
-			break;
-		default: // 64x64 lump
-			splat->size = 32;
-			break;
-	}
+	if (!(spr->cut & SC_ISROTATED))
+		splatangle += mobj->rollangle;
+
+	splat.angle = -splatangle;
+	splat.angle += ANGLE_90;
+
+	topoffset = spr->spriteyoffset;
+	leftoffset = spr->spritexoffset;
+	if (hflip)
+		leftoffset = ((splat.width * FRACUNIT) - leftoffset);
+
+	xscale = spr->spritexscale;
+	yscale = spr->spriteyscale;
+
+	splat.xscale = FixedMul(splat.scale, xscale);
+	splat.yscale = FixedMul(splat.scale, yscale);
+
+	xoffset = FixedMul(leftoffset, splat.xscale);
+	yoffset = FixedMul(topoffset, splat.yscale);
+
+	x = mobj->x;
+	y = mobj->y;
+	w = (splat.width * splat.xscale);
+	h = (splat.height * splat.yscale);
+
+	splat.x = x;
+	splat.y = y;
+	splat.z = mobj->z;
+	splat.tilted = false;
+
+	// Set positions
 
 	// 3--2
 	// |  |
 	// 0--1
-	//
-	splat->verts[0].x = splat->verts[3].x = x - (splat->size<<FRACBITS);
-	splat->verts[2].x = splat->verts[1].x = x + ((splat->size-1)<<FRACBITS);
-	splat->verts[3].y = splat->verts[2].y = y + ((splat->size-1)<<FRACBITS);
-	splat->verts[0].y = splat->verts[1].y = y - (splat->size<<FRACBITS);
-
-	// insert splat in the subsector splat list
-	splat->subsector = subsec;
-	splat->next = NULL;
-	if (subsec->splats)
+
+	splat.verts[0].x = w - xoffset;
+	splat.verts[0].y = yoffset;
+
+	splat.verts[1].x = -xoffset;
+	splat.verts[1].y = yoffset;
+
+	splat.verts[2].x = -xoffset;
+	splat.verts[2].y = -h + yoffset;
+
+	splat.verts[3].x = w - xoffset;
+	splat.verts[3].y = -h + yoffset;
+
+	angle = -splat.angle;
+	ca = FINECOSINE(angle>>ANGLETOFINESHIFT);
+	sa = FINESINE(angle>>ANGLETOFINESHIFT);
+
+	// Rotate
+	for (i = 0; i < 4; i++)
 	{
-		p_splat = subsec->splats;
-		while (p_splat->next)
-			p_splat = p_splat->next;
-		p_splat->next = splat;
+		rotated[i].x = FixedMul(splat.verts[i].x, ca) - FixedMul(splat.verts[i].y, sa);
+		rotated[i].y = FixedMul(splat.verts[i].x, sa) + FixedMul(splat.verts[i].y, ca);
 	}
-	else
-		subsec->splats = splat;
-}
 
-// --------------------------------------------------------------------------
-// Before each frame being rendered, clear the visible floorsplats list
-// --------------------------------------------------------------------------
-static floorsplat_t *visfloorsplats;
+	if (renderflags & (RF_SLOPESPLAT | RF_OBJECTSLOPESPLAT))
+	{
+		pslope_t *standingslope = mobj->standingslope; // The slope that the object is standing on.
 
-void R_ClearVisibleFloorSplats(void)
-{
-	visfloorsplats = NULL;
-}
+		// The slope that was defined for the sprite.
+		if (renderflags & RF_SLOPESPLAT)
+			slope = mobj->floorspriteslope;
 
-// --------------------------------------------------------------------------
-// Add a floorsplat to the visible floorsplats list, for the current frame
-// --------------------------------------------------------------------------
-void R_AddVisibleFloorSplats(subsector_t *subsec)
-{
-	floorsplat_t *pSplat;
-	I_Assert(subsec->splats != NULL);
-
-	pSplat = subsec->splats;
-	// the splat is not visible from below
-	// FIXME: depending on some flag in pSplat->flags, some splats may be visible from 2 sides
-	// (above/below)
-	if (pSplat->z < viewz)
+		if (standingslope && (renderflags & RF_OBJECTSLOPESPLAT))
+			slope = standingslope;
+
+		// Set splat as tilted
+		splat.tilted = (slope != NULL);
+	}
+
+	if (splat.tilted)
 	{
-		pSplat->nextvis = visfloorsplats;
-		visfloorsplats = pSplat;
+		// Lactozilla: Just copy the entire slope LMFAOOOO
+		pslope_t *s = &splat.slope;
+
+		s->o.x = slope->o.x;
+		s->o.y = slope->o.y;
+		s->o.z = slope->o.z;
+
+		s->d.x = slope->d.x;
+		s->d.y = slope->d.y;
+
+		s->normal.x = slope->normal.x;
+		s->normal.y = slope->normal.y;
+		s->normal.z = slope->normal.z;
+
+		s->zdelta = slope->zdelta;
+		s->zangle = slope->zangle;
+		s->xydirection = slope->xydirection;
+
+		s->next = NULL;
+		s->flags = 0;
 	}
 
-	while (pSplat->next)
+	// Translate
+	for (i = 0; i < 4; i++)
 	{
-		pSplat = pSplat->next;
-		if (pSplat->z < viewz)
+		tr_x = rotated[i].x + x;
+		tr_y = rotated[i].y + y;
+
+		if (slope)
 		{
-			pSplat->nextvis = visfloorsplats;
-			visfloorsplats = pSplat;
+			rot_z = P_GetSlopeZAt(slope, tr_x, tr_y);
+			splat.verts[i].z = rot_z;
 		}
+		else
+			splat.verts[i].z = splat.z;
+
+		splat.verts[i].x = tr_x;
+		splat.verts[i].y = tr_y;
 	}
-}
 
-#ifdef USEASM
-// tv1, tv2 = x/y qui varie dans la texture, tc = x/y qui est constant.
-void ASMCALL rasterize_segment_tex(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 tv1, INT32 tv2,
-	INT32 tc, INT32 dir);
-#endif
+	for (i = 0; i < 4; i++)
+	{
+		v3d = &splat.verts[i];
+
+		// transform the origin point
+		tr_x = v3d->x - viewx;
+		tr_y = v3d->y - viewy;
 
-// current test with floor tile
-//#define FLOORSPLATSOLIDCOLOR
+		// rotation around vertical y axis
+		rot_x = FixedMul(tr_x, viewsin) - FixedMul(tr_y, viewcos);
+		rot_y = FixedMul(tr_x, viewcos) + FixedMul(tr_y, viewsin);
+		rot_z = v3d->z - viewz;
+
+		if (rot_y < FRACUNIT)
+			return;
+
+		// note: y from view above of map, is distance far away
+		xscale = FixedDiv(projection, rot_y);
+		yscale = -FixedDiv(projectiony, rot_y);
+
+		// projection
+		v2d[i].x = (centerxfrac + FixedMul(rot_x, xscale))>>FRACBITS;
+		v2d[i].y = (centeryfrac + FixedMul(rot_z, yscale))>>FRACBITS;
+	}
+
+	R_RenderFloorSplat(&splat, v2d, spr);
+}
 
 // --------------------------------------------------------------------------
 // Rasterize the four edges of a floor splat polygon,
 // fill the polygon with linear interpolation, call span drawer for each
 // scan line
 // --------------------------------------------------------------------------
-static void R_RenderFloorSplat(floorsplat_t *pSplat, vertex_t *verts, UINT8 *pTex)
+void R_RenderFloorSplat(floorsplat_t *pSplat, vector2_t *verts, vissprite_t *vis)
 {
 	// rasterizing
-	INT32 miny = vid.height + 1, maxy = 0, y, x1, ry1, x2, y2;
-	fixed_t offsetx, offsety;
-
-#ifdef FLOORSPLATSOLIDCOLOR
-	UINT8 *pDest;
-	INT32 tdx, tdy, ty, tx, x;
-#else
-	lighttable_t **planezlight;
-	fixed_t planeheight;
-	angle_t angle, planecos, planesin;
-	fixed_t distance, span;
-	size_t indexr;
-	INT32 light;
-#endif
-	(void)pTex;
+	INT32 miny = viewheight + 1, maxy = 0;
+	INT32 y, x1, ry1, x2, y2, i;
+	fixed_t offsetx = 0, offsety = 0;
+	fixed_t planeheight = 0;
+	fixed_t step;
 
-	offsetx = pSplat->verts[0].x & ((pSplat->size << FRACBITS)-1);
-	offsety = pSplat->verts[0].y & ((pSplat->size << FRACBITS)-1);
+	int spanfunctype = SPANDRAWFUNC_SPRITE;
 
-	// do segment a -> top of texture
-	x1 = verts[3].x;
-	ry1 = verts[3].y;
-	x2 = verts[2].x;
-	y2 = verts[2].y;
-	if (ry1 < 0)
-		ry1 = 0;
-	if (ry1 >= vid.height)
-		ry1 = vid.height - 1;
-	if (y2 < 0)
-		y2 = 0;
-	if (y2 >= vid.height)
-		y2 = vid.height - 1;
-	rasterize_segment_tex(x1, ry1, x2, y2, 0, pSplat->size - 1, 0, 0);
-	if (ry1 < miny)
-		miny = ry1;
-	if (ry1 > maxy)
-		maxy = ry1;
+	prepare_rastertab();
 
-	// do segment b -> right side of texture
-	x1 = x2;
-	ry1 = y2;
-	x2 = verts[1].x;
-	y2 = verts[1].y;
-	if (ry1 < 0)
-		ry1 = 0;
-	if (ry1 >= vid.height)
-		ry1 = vid.height - 1;
-	if (y2 < 0)
-		y2 = 0;
-	if (y2 >= vid.height)
-		y2 = vid.height - 1;
-	rasterize_segment_tex(x1, ry1, x2, y2, 0, pSplat->size - 1, pSplat->size - 1, 1);
-	if (ry1 < miny)
-		miny = ry1;
-	if (ry1 > maxy)
-		maxy = ry1;
+#define RASTERPARAMS(vnum1, vnum2, tv1, tv2, tc, dir) \
+    x1 = verts[vnum1].x; \
+    ry1 = verts[vnum1].y; \
+    x2 = verts[vnum2].x; \
+    y2 = verts[vnum2].y; \
+    if (y2 > ry1) \
+        step = FixedDiv(x2-x1, y2-ry1+1); \
+    else if (y2 == ry1) \
+        step = 0; \
+    else \
+        step = FixedDiv(x2-x1, ry1-y2+1); \
+    if (ry1 < 0) { \
+        if (step) { \
+            x1 <<= FRACBITS; \
+            x1 += (-ry1)*step; \
+            x1 >>= FRACBITS; \
+        } \
+        ry1 = 0; \
+    } \
+    if (ry1 >= vid.height) { \
+        if (step) { \
+            x1 <<= FRACBITS; \
+            x1 -= (vid.height-1-ry1)*step; \
+            x1 >>= FRACBITS; \
+        } \
+        ry1 = vid.height - 1; \
+    } \
+    if (y2 < 0) { \
+        if (step) { \
+            x2 <<= FRACBITS; \
+            x2 -= (-y2)*step; \
+            x2 >>= FRACBITS; \
+        } \
+        y2 = 0; \
+    } \
+    if (y2 >= vid.height) { \
+        if (step) { \
+            x2 <<= FRACBITS; \
+            x2 += (vid.height-1-y2)*step; \
+            x2 >>= FRACBITS; \
+        } \
+        y2 = vid.height - 1; \
+    } \
+    rasterize_segment_tex(x1, ry1, x2, y2, tv1, tv2, tc, dir); \
+    if (ry1 < miny) \
+        miny = ry1; \
+    if (ry1 > maxy) \
+        maxy = ry1;
 
+	// do segment a -> top of texture
+	RASTERPARAMS(3,2,0,pSplat->width-1,0,0);
+	// do segment b -> right side of texture
+	RASTERPARAMS(2,1,0,pSplat->width-1,pSplat->height-1,0);
 	// do segment c -> bottom of texture
-	x1 = x2;
-	ry1 = y2;
-	x2 = verts[0].x;
-	y2 = verts[0].y;
-	if (ry1 < 0)
-		ry1 = 0;
-	if (ry1 >= vid.height)
-		ry1 = vid.height - 1;
-	if (y2 < 0)
-		y2 = 0;
-	if (y2 >= vid.height)
-		y2 = vid.height - 1;
-	rasterize_segment_tex(x1, ry1, x2, y2, pSplat->size - 1, 0, pSplat->size - 1, 0);
-	if (ry1 < miny)
-		miny = ry1;
-	if (ry1 > maxy)
-		maxy = ry1;
-
+	RASTERPARAMS(1,0,pSplat->width-1,0,pSplat->height-1,0);
 	// do segment d -> left side of texture
-	x1 = x2;
-	ry1 = y2;
-	x2 = verts[3].x;
-	y2 = verts[3].y;
-	if (ry1 < 0)
-		ry1 = 0;
-	if (ry1 >= vid.height)
-		ry1 = vid.height - 1;
-	if (y2 < 0)
-		y2 = 0;
-	if (y2 >= vid.height)
-		y2 = vid.height - 1;
-	rasterize_segment_tex(x1, ry1, x2, y2, pSplat->size - 1, 0, 0, 1);
-	if (ry1 < miny)
-		miny = ry1;
-	if (ry1 > maxy)
-		maxy = ry1;
-
-#ifndef FLOORSPLATSOLIDCOLOR
-	// prepare values for all the splat
-	ds_source = W_CacheLumpNum(pSplat->pic, PU_CACHE);
-	planeheight = abs(pSplat->z - viewz);
-	light = (pSplat->subsector->sector->lightlevel >> LIGHTSEGSHIFT);
-	if (light >= LIGHTLEVELS)
-		light = LIGHTLEVELS - 1;
-	if (light < 0)
-		light = 0;
-	planezlight = zlight[light];
+	RASTERPARAMS(0,3,pSplat->width-1,0,0,1);
 
-	for (y = miny; y <= maxy; y++)
-	{
-		x1 = rastertab[y].minx>>FRACBITS;
-		x2 = rastertab[y].maxx>>FRACBITS;
+	ds_source = (UINT8 *)pSplat->pic;
+	ds_flatwidth = pSplat->width;
+	ds_flatheight = pSplat->height;
 
-		if (x1 < 0)
-			x1 = 0;
-		if (x2 >= vid.width)
-			x2 = vid.width - 1;
+	if (R_CheckPowersOfTwo())
+		R_CheckFlatLength(ds_flatwidth * ds_flatheight);
 
-		angle = (currentplane->viewangle + currentplane->plangle)>>ANGLETOFINESHIFT;
-		planecos = FINECOSINE(angle);
-		planesin = FINESINE(angle);
+	// Lactozilla: I don't know what I'm doing
+	if (pSplat->tilted)
+	{
+		ds_sup = &ds_su[0];
+		ds_svp = &ds_sv[0];
+		ds_szp = &ds_sz[0];
+		R_CalculateSlopeVectors(&pSplat->slope, viewx, viewy, viewz, pSplat->xscale, pSplat->yscale, -pSplat->verts[0].x, pSplat->verts[0].y, viewangle, pSplat->angle, 1.0f);
+		spanfunctype = SPANDRAWFUNC_TILTEDSPRITE;
+	}
+	else
+	{
+		planeheight = abs(pSplat->z - viewz);
 
-		if (planeheight != cachedheight[y])
+		if (pSplat->angle)
 		{
-			cachedheight[y] = planeheight;
-			distance = cacheddistance[y] = FixedMul(planeheight, yslope[y]);
-			ds_xstep = cachedxstep[y] = FixedMul(distance,basexscale);
-			ds_ystep = cachedystep[y] = FixedMul(distance,baseyscale);
-
-			if ((span = abs(centery-y)))
-			{
-				ds_xstep = cachedxstep[y] = FixedMul(planesin, planeheight) / span;
-				ds_ystep = cachedystep[y] = FixedMul(planecos, planeheight) / span;
-			}
+			// Add the view offset, rotated by the plane angle.
+			fixed_t a = -pSplat->verts[0].x + viewx;
+			fixed_t b = -pSplat->verts[0].y + viewy;
+			angle_t angle = (pSplat->angle >> ANGLETOFINESHIFT);
+			offsetx = FixedMul(a, FINECOSINE(angle)) - FixedMul(b,FINESINE(angle));
+			offsety = -FixedMul(a, FINESINE(angle)) - FixedMul(b,FINECOSINE(angle));
+			memset(cachedheight, 0, sizeof(cachedheight));
 		}
 		else
 		{
-			distance = cacheddistance[y];
-			ds_xstep = cachedxstep[y];
-			ds_ystep = cachedystep[y];
+			offsetx = viewx - pSplat->verts[0].x;
+			offsety = pSplat->verts[0].y - viewy;
 		}
+	}
 
-		ds_xfrac = xoffs + FixedMul(planecos, distance) + (x1 - centerx) * ds_xstep;
-		ds_yfrac = yoffs - FixedMul(planesin, distance) + (x1 - centerx) * ds_ystep;
-		ds_xfrac -= offsetx;
-		ds_yfrac += offsety;
+	ds_colormap = vis->colormap;
+	ds_translation = R_GetSpriteTranslation(vis);
+	if (ds_translation == NULL)
+		ds_translation = colormaps;
 
-		indexr = distance >> LIGHTZSHIFT;
-		if (indexr >= MAXLIGHTZ)
-			indexr = MAXLIGHTZ - 1;
-		ds_colormap = planezlight[indexr];
+	if (vis->extra_colormap)
+	{
+		if (!ds_colormap)
+			ds_colormap = vis->extra_colormap->colormap;
+		else
+			ds_colormap = &vis->extra_colormap->colormap[ds_colormap - colormaps];
+	}
 
-		ds_y = y;
-		if (x2 >= x1) // sanity check
-		{
-			ds_x1 = x1;
-			ds_x2 = x2;
-			ds_transmap = transtables + ((tr_trans50-1)<<FF_TRANSSHIFT);
-			(spanfuncs[SPANDRAWFUNC_SPLAT])();
-		}
+	if (vis->transmap)
+	{
+		ds_transmap = vis->transmap;
 
-		// reset for next calls to edge rasterizer
-		rastertab[y].minx = INT32_MAX;
-		rastertab[y].maxx = INT32_MIN;
+		if (pSplat->tilted)
+			spanfunctype = SPANDRAWFUNC_TILTEDTRANSSPRITE;
+		else
+			spanfunctype = SPANDRAWFUNC_TRANSSPRITE;
 	}
+	else
+		ds_transmap = NULL;
+
+	if (ds_powersoftwo)
+		spanfunc = spanfuncs[spanfunctype];
+	else
+		spanfunc = spanfuncs_npo2[spanfunctype];
+
+	if (maxy >= vid.height)
+		maxy = vid.height-1;
 
-#else
 	for (y = miny; y <= maxy; y++)
 	{
+		boolean cliptab[MAXVIDWIDTH+1];
+
 		x1 = rastertab[y].minx>>FRACBITS;
 		x2 = rastertab[y].maxx>>FRACBITS;
+
+		if (x1 > x2)
+		{
+			INT32 swap = x1;
+			x1 = x2;
+			x2 = swap;
+		}
+
+		if (x1 == INT16_MIN || x2 == INT16_MAX)
+			continue;
+
 		if (x1 < 0)
 			x1 = 0;
-		if (x2 >= vid.width)
-			x2 = vid.width - 1;
+		if (x2 >= viewwidth)
+			x2 = viewwidth - 1;
 
-//		pDest = ylookup[y] + columnofs[x1];
-		pDest = &topleft[y*vid.width + x1];
+		if (x1 >= viewwidth || x2 < 0)
+			continue;
 
-		x = x2 - x1 + 1;
+		for (i = x1; i <= x2; i++)
+			cliptab[i] = (y >= mfloorclip[i]);
 
-		// starting point of the texture
-		tx = rastertab[y].tx1;
-		ty = rastertab[y].ty1;
-
-		// HORRIBLE BUG!!!
-		if (x > 0)
+		// clip left
+		while (cliptab[x1])
 		{
-			tdx = (rastertab[y].tx2 - tx) / x;
-			tdy = (rastertab[y].ty2 - ty) / x;
-
-			while (x-- > 0)
-			{
-				*(pDest++) = (UINT8)(y&1);
-				tx += tdx;
-				ty += tdy;
-			}
+			x1++;
+			if (x1 >= viewwidth)
+				break;
 		}
 
-		// reinitialise the minimum and maximum for the next approach
-		rastertab[y].minx = INT32_MAX;
-		rastertab[y].maxx = INT32_MIN;
-	}
-#endif
-}
+		// clip right
+		i = x2;
 
-// --------------------------------------------------------------------------
-// R_DrawVisibleFloorSplats
-// draw the flat floor/ceiling splats
-// --------------------------------------------------------------------------
-void R_DrawVisibleFloorSplats(void)
-{
-	floorsplat_t *pSplat;
-	INT32 iCount = 0, i;
-	fixed_t tr_x, tr_y, rot_x, rot_y, rot_z, xscale, yscale;
-	vertex_t *v3d;
-	vertex_t v2d[4];
-
-	pSplat = visfloorsplats;
-	while (pSplat)
-	{
-		iCount++;
+		while (i > x1)
+		{
+			if (cliptab[i])
+				x2 = i-1;
+			i--;
+			if (i < 0)
+				break;
+		}
 
-		// Draw a floor splat
-		// 3--2
-		// |  |
-		// 0--1
+		if (x2 < x1)
+			continue;
 
-		rot_z = pSplat->z - viewz;
-		for (i = 0; i < 4; i++)
+		if (!pSplat->tilted)
 		{
-			v3d = &pSplat->verts[i];
+			fixed_t xstep, ystep;
+			fixed_t distance, span;
 
-			// transform the origin point
-			tr_x = v3d->x - viewx;
-			tr_y = v3d->y - viewy;
+			angle_t angle = (viewangle + pSplat->angle)>>ANGLETOFINESHIFT;
+			angle_t planecos = FINECOSINE(angle);
+			angle_t planesin = FINESINE(angle);
 
-			// rotation around vertical y axis
-			rot_x = FixedMul(tr_x, viewsin) - FixedMul(tr_y, viewcos);
-			rot_y = FixedMul(tr_x, viewcos) + FixedMul(tr_y, viewsin);
+			if (planeheight != cachedheight[y])
+			{
+				cachedheight[y] = planeheight;
+				distance = cacheddistance[y] = FixedMul(planeheight, yslope[y]);
+				span = abs(centery - y);
 
-			if (rot_y < 4*FRACUNIT)
-				goto skipit;
+				if (span) // don't divide by zero
+				{
+					xstep = FixedMul(planesin, planeheight) / span;
+					ystep = FixedMul(planecos, planeheight) / span;
+				}
+				else
+				{
+					// ah
+					xstep = FRACUNIT;
+					ystep = FRACUNIT;
+				}
+
+				cachedxstep[y] = xstep;
+				cachedystep[y] = ystep;
+			}
+			else
+			{
+				distance = cacheddistance[y];
+				xstep = cachedxstep[y];
+				ystep = cachedystep[y];
+			}
 
-			// note: y from view above of map, is distance far away
-			xscale = FixedDiv(projection, rot_y);
-			yscale = -FixedDiv(projectiony, rot_y);
+			ds_xstep = FixedDiv(xstep, pSplat->xscale);
+			ds_ystep = FixedDiv(ystep, pSplat->yscale);
 
-			// projection
-			v2d[i].x = (centerxfrac + FixedMul (rot_x, xscale))>>FRACBITS;
-			v2d[i].y = (centeryfrac + FixedMul (rot_z, yscale))>>FRACBITS;
+			ds_xfrac = FixedDiv(offsetx + FixedMul(planecos, distance) + (x1 - centerx) * xstep, pSplat->xscale);
+			ds_yfrac = FixedDiv(offsety - FixedMul(planesin, distance) + (x1 - centerx) * ystep, pSplat->yscale);
 		}
 
-		R_RenderFloorSplat(pSplat, v2d, NULL);
-skipit:
-		pSplat = pSplat->nextvis;
+		ds_y = y;
+		ds_x1 = x1;
+		ds_x2 = x2;
+		spanfunc();
+
+		rastertab[y].minx = INT32_MAX;
+		rastertab[y].maxx = INT32_MIN;
 	}
+
+	if (pSplat->angle && !pSplat->tilted)
+		memset(cachedheight, 0, sizeof(cachedheight));
 }
 
 static void prepare_rastertab(void)
 {
-	INT32 iLine;
-	for (iLine = 0; iLine < vid.height; iLine++)
+	INT32 i;
+	prastertab = rastertab;
+	for (i = 0; i < vid.height; i++)
 	{
-		rastertab[iLine].minx = INT32_MAX;
-		rastertab[iLine].maxx = INT32_MIN;
+		rastertab[i].minx = INT32_MAX;
+		rastertab[i].maxx = INT32_MIN;
 	}
 }
-
-#endif // FLOORSPLATS
diff --git a/src/r_splats.h b/src/r_splats.h
index 4ad893abbb2db5dcde78116fd94b65cf8f373aa4..e1f836f489bab54513dafd5b867ebfd7dbc79f44 100644
--- a/src/r_splats.h
+++ b/src/r_splats.h
@@ -14,68 +14,35 @@
 #define __R_SPLATS_H__
 
 #include "r_defs.h"
-
-//#define WALLSPLATS      // comment this out to compile without splat effects
-/*#ifdef USEASM
-#define FLOORSPLATS
-#endif*/
-
-#define MAXLEVELSPLATS      1024
-
-// splat flags
-#define SPLATDRAWMODE_MASK 0x03 // mask to get drawmode from flags
-#define SPLATDRAWMODE_OPAQUE 0x00
-#define SPLATDRAWMODE_SHADE 0x01
-#define SPLATDRAWMODE_TRANS 0x02
+#include "r_things.h"
 
 // ==========================================================================
 // DEFINITIONS
 // ==========================================================================
 
-// WALL SPLATS are patches drawn on top of wall segs
-typedef struct wallsplat_s
+struct rastery_s
 {
-	lumpnum_t patch; // lump id.
-	vertex_t v1, v2; // vertices along the linedef
-	fixed_t top;
-	fixed_t offset; // offset in columns<<FRACBITS from start of linedef to start of splat
-	INT32 flags;
-	fixed_t *yoffset;
-	line_t *line; // the parent line of the splat seg
-	struct wallsplat_s *next;
-} wallsplat_t;
+	fixed_t minx, maxx; // for each raster line starting at line 0
+	fixed_t tx1, ty1;   // start points in texture at this line
+	fixed_t tx2, ty2;   // end points in texture at this line
+};
+extern struct rastery_s *prastertab; // for ASM code
 
-// FLOOR SPLATS are pic_t (raw horizontally stored) drawn on top of the floor or ceiling
 typedef struct floorsplat_s
 {
-	lumpnum_t pic; // a pic_t lump id
-	INT32 flags;
-	INT32 size; // 64, 128, 256, etc.
-	vertex_t verts[4]; // (x,y) as viewn from above on map
-	fixed_t z; // z (height) is constant for all the floorsplats
-	subsector_t *subsector; // the parent subsector
+	UINT16 *pic;
+	INT32 width, height;
+	fixed_t scale, xscale, yscale;
+	angle_t angle;
+	boolean tilted; // Uses the tilted drawer
+	pslope_t slope;
+
+	vector3_t verts[4]; // (x,y,z) as viewed from above on map
+	fixed_t x, y, z; // position
 	mobj_t *mobj; // Mobj it is tied to
-	struct floorsplat_s *next;
-	struct floorsplat_s *nextvis;
 } floorsplat_t;
 
-// p_setup.c
-fixed_t P_SegLength(seg_t *seg);
-
-// call at P_SetupLevel()
-void R_ClearLevelSplats(void);
-
-#ifdef WALLSPLATS
-void R_AddWallSplat(line_t *wallline, INT16 sectorside, const char *patchname, fixed_t top,
-	fixed_t wallfrac, INT32 flags);
-#endif
-#ifdef FLOORSPLATS
-void R_AddFloorSplat(subsector_t *subsec, mobj_t *mobj, const char *picname, fixed_t x, fixed_t y, fixed_t z,
-	INT32 flags);
-#endif
-
-void R_ClearVisibleFloorSplats(void);
-void R_AddVisibleFloorSplats(subsector_t *subsec);
-void R_DrawVisibleFloorSplats(void);
+void R_DrawFloorSprite(vissprite_t *spr);
+void R_RenderFloorSplat(floorsplat_t *pSplat, vector2_t *verts, vissprite_t *vis);
 
 #endif /*__R_SPLATS_H__*/
diff --git a/src/r_textures.c b/src/r_textures.c
index a34c29c728c79395d56ad93864b32c575032d5aa..9de9649e222a9628f0917592570c899917e62722 100644
--- a/src/r_textures.c
+++ b/src/r_textures.c
@@ -20,6 +20,7 @@
 #include "m_misc.h"
 #include "r_data.h"
 #include "r_textures.h"
+#include "r_patch.h"
 #include "r_picformats.h"
 #include "w_wad.h"
 #include "z_zone.h"
@@ -33,7 +34,7 @@
 #endif
 
 #ifdef HWRENDER
-#include "hardware/hw_main.h" // HWR_LoadTextures
+#include "hardware/hw_glob.h" // HWR_LoadMapTextures
 #endif
 
 #include <errno.h>
@@ -266,7 +267,7 @@ UINT8 *R_GenerateTexture(size_t texnum)
 	UINT8 *blocktex;
 	texture_t *texture;
 	texpatch_t *patch;
-	patch_t *realpatch;
+	softwarepatch_t *realpatch;
 	UINT8 *pdata;
 	int x, x1, x2, i, width, height;
 	size_t blocksize;
@@ -296,7 +297,7 @@ UINT8 *R_GenerateTexture(size_t texnum)
 		lumpnum = patch->lump;
 		lumplength = W_LumpLengthPwad(wadnum, lumpnum);
 		pdata = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
-		realpatch = (patch_t *)pdata;
+		realpatch = (softwarepatch_t *)pdata;
 
 #ifndef NO_PNG_LUMPS
 		if (Picture_IsLumpPNG((UINT8 *)realpatch, lumplength))
@@ -392,17 +393,17 @@ UINT8 *R_GenerateTexture(size_t texnum)
 		lumpnum = patch->lump;
 		pdata = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
 		lumplength = W_LumpLengthPwad(wadnum, lumpnum);
-		realpatch = (patch_t *)pdata;
+		realpatch = (softwarepatch_t *)pdata;
 		dealloc = true;
 
 #ifndef NO_PNG_LUMPS
 		if (Picture_IsLumpPNG((UINT8 *)realpatch, lumplength))
-			realpatch = (patch_t *)Picture_PNGConvert((UINT8 *)realpatch, PICFMT_PATCH, NULL, NULL, NULL, NULL, lumplength, NULL, 0);
+			realpatch = (softwarepatch_t *)Picture_PNGConvert((UINT8 *)realpatch, PICFMT_DOOMPATCH, NULL, NULL, NULL, NULL, lumplength, NULL, 0);
 		else
 #endif
 #ifdef WALLFLATS
 		if (texture->type == TEXTURETYPE_FLAT)
-			realpatch = (patch_t *)Picture_Convert(PICFMT_FLAT, pdata, PICFMT_PATCH, 0, NULL, texture->width, texture->height, 0, 0, 0);
+			realpatch = (softwarepatch_t *)Picture_Convert(PICFMT_FLAT, pdata, PICFMT_DOOMPATCH, 0, NULL, texture->width, texture->height, 0, 0, 0);
 		else
 #endif
 		{
@@ -597,13 +598,13 @@ void *R_GetLevelFlat(levelflat_t *levelflat)
 			{
 				UINT8 *converted;
 				size_t size;
-				patch_t *patch = W_CacheLumpNum(levelflat->u.flat.lumpnum, PU_CACHE);
+				softwarepatch_t *patch = W_CacheLumpNum(levelflat->u.flat.lumpnum, PU_CACHE);
 
 				levelflat->width = ds_flatwidth = SHORT(patch->width);
 				levelflat->height = ds_flatheight = SHORT(patch->height);
 
 				levelflat->picture = Z_Malloc(levelflat->width * levelflat->height, PU_LEVEL, NULL);
-				converted = Picture_FlatConvert(PICFMT_PATCH, patch, PICFMT_FLAT, 0, &size, levelflat->width, levelflat->height, patch->topoffset, patch->leftoffset, 0);
+				converted = Picture_FlatConvert(PICFMT_DOOMPATCH, patch, PICFMT_FLAT, 0, &size, levelflat->width, levelflat->height, patch->topoffset, patch->leftoffset, 0);
 				M_Memcpy(levelflat->picture, converted, size);
 				Z_Free(converted);
 			}
@@ -839,7 +840,7 @@ Rloadtextures (INT32 i, INT32 w)
 	UINT16 j;
 	UINT16 texstart, texend, texturesLumpPos;
 	texture_t *texture;
-	patch_t *patchlump;
+	softwarepatch_t *patchlump;
 	texpatch_t *patch;
 
 	// Get the lump numbers for the markers in the WAD, if they exist.
@@ -1062,7 +1063,7 @@ void R_LoadTextures(void)
 
 #ifdef HWRENDER
 	if (rendermode == render_opengl)
-		HWR_LoadTextures(numtextures);
+		HWR_LoadMapTextures(numtextures);
 #endif
 }
 
diff --git a/src/r_things.c b/src/r_things.c
index 7d3f2be3dd8c1fdb9fc602023afa51006ac0807a..2daa6984dc8b1101b9c72e5375d7d092bd2e4b74 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -24,9 +24,12 @@
 #include "i_video.h" // rendermode
 #include "i_system.h"
 #include "r_things.h"
+#include "r_patch.h"
+#include "r_patchrotation.h"
 #include "r_picformats.h"
 #include "r_plane.h"
 #include "r_portal.h"
+#include "r_splats.h"
 #include "p_tick.h"
 #include "p_local.h"
 #include "p_slopes.h"
@@ -96,7 +99,7 @@ static void R_InstallSpriteLump(UINT16 wad,            // graphics patch
 {
 	char cn = R_Frame2Char(frame), cr = R_Rotation2Char(rotation); // for debugging
 
-	INT32 r, ang;
+	INT32 r;
 	lumpnum_t lumppat = wad;
 	lumppat <<= 16;
 	lumppat += lump;
@@ -104,15 +107,13 @@ static void R_InstallSpriteLump(UINT16 wad,            // graphics patch
 	if (maxframe ==(size_t)-1 || frame > maxframe)
 		maxframe = frame;
 
-	// rotsprite
 #ifdef ROTSPRITE
-	sprtemp[frame].rotsprite.cached = 0;
 	for (r = 0; r < 16; r++)
 	{
-		for (ang = 0; ang < ROTANGLES; ang++)
-			sprtemp[frame].rotsprite.patch[r][ang] = NULL;
+		sprtemp[frame].rotated[0][r] = NULL;
+		sprtemp[frame].rotated[1][r] = NULL;
 	}
-#endif/*ROTSPRITE*/
+#endif
 
 	if (rotation == 0)
 	{
@@ -228,7 +229,7 @@ boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef, UINT16
 	UINT8 frame;
 	UINT8 rotation;
 	lumpinfo_t *lumpinfo;
-	patch_t patch;
+	softwarepatch_t patch;
 	UINT8 numadded = 0;
 
 	memset(sprtemp,0xFF, sizeof (sprtemp));
@@ -240,9 +241,6 @@ boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef, UINT16
 	// if so, it might patch only certain frames, not all
 	if (spritedef->numframes) // (then spriteframes is not null)
 	{
-#ifdef ROTSPRITE
-		R_FreeSingleRotSprite(spritedef);
-#endif
 		// copy the already defined sprite frames
 		M_Memcpy(sprtemp, spritedef->spriteframes,
 		 spritedef->numframes * sizeof (spriteframe_t));
@@ -283,7 +281,7 @@ boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef, UINT16
 
 #ifndef NO_PNG_LUMPS
 			{
-				patch_t *png = W_CacheLumpNumPwad(wadnum, l, PU_STATIC);
+				softwarepatch_t *png = W_CacheLumpNumPwad(wadnum, l, PU_STATIC);
 				size_t len = W_LumpLengthPwad(wadnum, l);
 
 				if (Picture_IsLumpPNG((UINT8 *)png, len))
@@ -414,9 +412,6 @@ boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef, UINT16
 	if (spritedef->numframes &&             // has been allocated
 		spritedef->numframes < maxframe)   // more frames are defined ?
 	{
-#ifdef ROTSPRITE
-		R_FreeSingleRotSprite(spritedef);
-#endif
 		Z_Free(spritedef->spriteframes);
 		spritedef->spriteframes = NULL;
 	}
@@ -746,6 +741,55 @@ void R_DrawFlippedMaskedColumn(column_t *column)
 	dc_texturemid = basetexturemid;
 }
 
+boolean R_SpriteIsFlashing(vissprite_t *vis)
+{
+	return (!(vis->cut & SC_PRECIP)
+	&& (vis->mobj->flags & (MF_ENEMY|MF_BOSS))
+	&& (vis->mobj->flags2 & MF2_FRET)
+	&& !(vis->mobj->flags & MF_GRENADEBOUNCE)
+	&& (leveltime & 1));
+}
+
+UINT8 *R_GetSpriteTranslation(vissprite_t *vis)
+{
+	if (R_SpriteIsFlashing(vis)) // Bosses "flash"
+	{
+		if (vis->mobj->type == MT_CYBRAKDEMON || vis->mobj->colorized)
+			return R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
+		else if (vis->mobj->type == MT_METALSONIC_BATTLE)
+			return R_GetTranslationColormap(TC_METALSONIC, 0, GTC_CACHE);
+		else
+			return R_GetTranslationColormap(TC_BOSS, 0, GTC_CACHE);
+	}
+	else if (vis->mobj->color)
+	{
+		// New colormap stuff for skins Tails 06-07-2002
+		if (!(vis->cut & SC_PRECIP) && vis->mobj->colorized)
+			return R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
+		else if (!(vis->cut & SC_PRECIP)
+			&& vis->mobj->player && vis->mobj->player->dashmode >= DASHMODE_THRESHOLD
+			&& (vis->mobj->player->charflags & SF_DASHMODE)
+			&& ((leveltime/2) & 1))
+		{
+			if (vis->mobj->player->charflags & SF_MACHINE)
+				return R_GetTranslationColormap(TC_DASHMODE, 0, GTC_CACHE);
+			else
+				return R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
+		}
+		else if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // This thing is a player!
+		{
+			size_t skinnum = (skin_t*)vis->mobj->skin-skins;
+			return R_GetTranslationColormap((INT32)skinnum, vis->mobj->color, GTC_CACHE);
+		}
+		else // Use the defaults
+			return R_GetTranslationColormap(TC_DEFAULT, vis->mobj->color, GTC_CACHE);
+	}
+	else if (vis->mobj->sprite == SPR_PLAY) // Looks like a player, but doesn't have a color? Get rid of green sonic syndrome.
+		return R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_BLUE, GTC_CACHE);
+
+	return NULL;
+}
+
 //
 // R_DrawVisSprite
 //  mfloorclip and mceilingclip should also be set.
@@ -779,79 +823,26 @@ static void R_DrawVisSprite(vissprite_t *vis)
 
 	colfunc = colfuncs[BASEDRAWFUNC]; // hack: this isn't resetting properly somewhere.
 	dc_colormap = vis->colormap;
-	if (!(vis->cut & SC_PRECIP) && (vis->mobj->flags & (MF_ENEMY|MF_BOSS)) && (vis->mobj->flags2 & MF2_FRET) && !(vis->mobj->flags & MF_GRENADEBOUNCE) && (leveltime & 1)) // Bosses "flash"
-	{
-		// translate certain pixels to white
-		colfunc = colfuncs[COLDRAWFUNC_TRANS];
-		if (vis->mobj->type == MT_CYBRAKDEMON || vis->mobj->colorized)
-			dc_translation = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
-		else if (vis->mobj->type == MT_METALSONIC_BATTLE)
-			dc_translation = R_GetTranslationColormap(TC_METALSONIC, 0, GTC_CACHE);
-		else
-			dc_translation = R_GetTranslationColormap(TC_BOSS, 0, GTC_CACHE);
-	}
+	dc_translation = R_GetSpriteTranslation(vis);
+
+	if (R_SpriteIsFlashing(vis)) // Bosses "flash"
+		colfunc = colfuncs[COLDRAWFUNC_TRANS]; // translate certain pixels to white
 	else if (vis->mobj->color && vis->transmap) // Color mapping
 	{
 		colfunc = colfuncs[COLDRAWFUNC_TRANSTRANS];
 		dc_transmap = vis->transmap;
-		if (!(vis->cut & SC_PRECIP) && vis->mobj->colorized)
-			dc_translation = R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
-		else if (!(vis->cut & SC_PRECIP)
-			&& vis->mobj->player && vis->mobj->player->dashmode >= DASHMODE_THRESHOLD
-			&& (vis->mobj->player->charflags & SF_DASHMODE)
-			&& ((leveltime/2) & 1))
-		{
-			if (vis->mobj->player->charflags & SF_MACHINE)
-				dc_translation = R_GetTranslationColormap(TC_DASHMODE, 0, GTC_CACHE);
-			else
-				dc_translation = R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
-		}
-		else if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // MT_GHOST LOOKS LIKE A PLAYER SO USE THE PLAYER TRANSLATION TABLES. >_>
-		{
-			size_t skinnum = (skin_t*)vis->mobj->skin-skins;
-			dc_translation = R_GetTranslationColormap((INT32)skinnum, vis->mobj->color, GTC_CACHE);
-		}
-		else // Use the defaults
-			dc_translation = R_GetTranslationColormap(TC_DEFAULT, vis->mobj->color, GTC_CACHE);
 	}
 	else if (vis->transmap)
 	{
 		colfunc = colfuncs[COLDRAWFUNC_FUZZY];
 		dc_transmap = vis->transmap;    //Fab : 29-04-98: translucency table
 	}
-	else if (vis->mobj->color)
-	{
-		// translate green skin to another color
+	else if (vis->mobj->color) // translate green skin to another color
 		colfunc = colfuncs[COLDRAWFUNC_TRANS];
-
-		// New colormap stuff for skins Tails 06-07-2002
-		if (!(vis->cut & SC_PRECIP) && vis->mobj->colorized)
-			dc_translation = R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
-		else if (!(vis->cut & SC_PRECIP)
-			&& vis->mobj->player && vis->mobj->player->dashmode >= DASHMODE_THRESHOLD
-			&& (vis->mobj->player->charflags & SF_DASHMODE)
-			&& ((leveltime/2) & 1))
-		{
-			if (vis->mobj->player->charflags & SF_MACHINE)
-				dc_translation = R_GetTranslationColormap(TC_DASHMODE, 0, GTC_CACHE);
-			else
-				dc_translation = R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
-		}
-		else if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // This thing is a player!
-		{
-			size_t skinnum = (skin_t*)vis->mobj->skin-skins;
-			dc_translation = R_GetTranslationColormap((INT32)skinnum, vis->mobj->color, GTC_CACHE);
-		}
-		else // Use the defaults
-			dc_translation = R_GetTranslationColormap(TC_DEFAULT, vis->mobj->color, GTC_CACHE);
-	}
 	else if (vis->mobj->sprite == SPR_PLAY) // Looks like a player, but doesn't have a color? Get rid of green sonic syndrome.
-	{
 		colfunc = colfuncs[COLDRAWFUNC_TRANS];
-		dc_translation = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_BLUE, GTC_CACHE);
-	}
 
-	if (vis->extra_colormap)
+	if (vis->extra_colormap && !(vis->renderflags & RF_NOCOLORMAPS))
 	{
 		if (!dc_colormap)
 			dc_colormap = vis->extra_colormap->colormap;
@@ -910,13 +901,16 @@ static void R_DrawVisSprite(vissprite_t *vis)
 	// Split drawing loops for paper and non-paper to reduce conditional checks per sprite
 	if (vis->scalestep)
 	{
+		fixed_t horzscale = FixedMul(vis->spritexscale, this_scale);
+		fixed_t scalestep = FixedMul(vis->scalestep, vis->spriteyscale);
+
 		pwidth = SHORT(patch->width);
 
 		// Papersprite drawing loop
-		for (dc_x = vis->x1; dc_x <= vis->x2; dc_x++, spryscale += vis->scalestep)
+		for (dc_x = vis->x1; dc_x <= vis->x2; dc_x++, spryscale += scalestep)
 		{
 			angle_t angle = ((vis->centerangle + xtoviewangle[dc_x]) >> ANGLETOFINESHIFT) & 0xFFF;
-			texturecolumn = (vis->paperoffset - FixedMul(FINETANGENT(angle), vis->paperdistance)) / this_scale;
+			texturecolumn = (vis->paperoffset - FixedMul(FINETANGENT(angle), vis->paperdistance)) / horzscale;
 
 			if (texturecolumn < 0 || texturecolumn >= pwidth)
 				continue;
@@ -927,8 +921,30 @@ static void R_DrawVisSprite(vissprite_t *vis)
 			sprtopscreen = (centeryfrac - FixedMul(dc_texturemid, spryscale));
 			dc_iscale = (0xffffffffu / (unsigned)spryscale);
 
-			column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[texturecolumn]));
+			column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn]));
+
+			localcolfunc (column);
+		}
+	}
+	else if (vis->cut & SC_SHEAR)
+	{
+#ifdef RANGECHECK
+		pwidth = SHORT(patch->width);
+#endif
+
+		// Vertically sheared sprite
+		for (dc_x = vis->x1; dc_x <= vis->x2; dc_x++, frac += vis->xiscale, dc_texturemid -= vis->shear.tan)
+		{
+#ifdef RANGECHECK
+			texturecolumn = frac>>FRACBITS;
+			if (texturecolumn < 0 || texturecolumn >= pwidth)
+				I_Error("R_DrawSpriteRange: bad texturecolumn at %d from end", vis->x2 - dc_x);
+			column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn]));
+#else
+			column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[frac>>FRACBITS]));
+#endif
 
+			sprtopscreen = (centeryfrac - FixedMul(dc_texturemid, spryscale));
 			localcolfunc (column);
 		}
 	}
@@ -945,9 +961,9 @@ static void R_DrawVisSprite(vissprite_t *vis)
 			texturecolumn = frac>>FRACBITS;
 			if (texturecolumn < 0 || texturecolumn >= pwidth)
 				I_Error("R_DrawSpriteRange: bad texturecolumn at %d from end", vis->x2 - dc_x);
-			column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[texturecolumn]));
+			column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn]));
 #else
-			column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[frac>>FRACBITS]));
+			column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[frac>>FRACBITS]));
 #endif
 			localcolfunc (column);
 		}
@@ -1012,9 +1028,9 @@ static void R_DrawPrecipitationVisSprite(vissprite_t *vis)
 		if (texturecolumn < 0 || texturecolumn >= SHORT(patch->width))
 			I_Error("R_DrawPrecipitationSpriteRange: bad texturecolumn");
 
-		column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[texturecolumn]));
+		column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn]));
 #else
-		column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[frac>>FRACBITS]));
+		column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[frac>>FRACBITS]));
 #endif
 		R_DrawMaskedColumn(column);
 	}
@@ -1218,6 +1234,29 @@ fixed_t R_GetShadowZ(mobj_t *thing, pslope_t **shadowslope)
 #undef CHECKZ
 }
 
+static void R_SkewShadowSprite(
+			mobj_t *thing, pslope_t *groundslope,
+			fixed_t groundz, INT32 spriteheight, fixed_t scalemul,
+			fixed_t *shadowyscale, fixed_t *shadowskew)
+{
+	// haha let's try some dumb stuff
+	fixed_t xslope, zslope;
+	angle_t sloperelang = (R_PointToAngle(thing->x, thing->y) - groundslope->xydirection) >> ANGLETOFINESHIFT;
+
+	xslope = FixedMul(FINESINE(sloperelang), groundslope->zdelta);
+	zslope = FixedMul(FINECOSINE(sloperelang), groundslope->zdelta);
+
+	//CONS_Printf("Shadow is sloped by %d %d\n", xslope, zslope);
+
+	if (viewz < groundz)
+		*shadowyscale += FixedMul(FixedMul(thing->radius*2 / spriteheight, scalemul), zslope);
+	else
+		*shadowyscale -= FixedMul(FixedMul(thing->radius*2 / spriteheight, scalemul), zslope);
+
+	*shadowyscale = abs((*shadowyscale));
+	*shadowskew = xslope;
+}
+
 static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale, fixed_t tx, fixed_t tz)
 {
 	vissprite_t *shadow;
@@ -1241,45 +1280,27 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 
 	scalemul = FixedMul(FRACUNIT - floordiff/640, scale);
 
-	patch = W_CachePatchName("DSHADOW", PU_CACHE);
+	patch = W_CachePatchName("DSHADOW", PU_SPRITE);
 	xscale = FixedDiv(projection, tz);
 	yscale = FixedDiv(projectiony, tz);
 	shadowxscale = FixedMul(thing->radius*2, scalemul);
 	shadowyscale = FixedMul(FixedMul(thing->radius*2, scalemul), FixedDiv(abs(groundz - viewz), tz));
-	shadowyscale = min(shadowyscale, shadowxscale) / SHORT(patch->height);
-	shadowxscale /= SHORT(patch->width);
+	shadowyscale = min(shadowyscale, shadowxscale) / patch->height;
+	shadowxscale /= patch->width;
 	shadowskew = 0;
 
 	if (groundslope)
-	{
-		// haha let's try some dumb stuff
-		fixed_t xslope, zslope;
-		angle_t sloperelang = (R_PointToAngle(thing->x, thing->y) - groundslope->xydirection) >> ANGLETOFINESHIFT;
+		R_SkewShadowSprite(thing, groundslope, groundz, patch->height, scalemul, &shadowyscale, &shadowskew);
 
-		xslope = FixedMul(FINESINE(sloperelang), groundslope->zdelta);
-		zslope = FixedMul(FINECOSINE(sloperelang), groundslope->zdelta);
-
-		//CONS_Printf("Shadow is sloped by %d %d\n", xslope, zslope);
-
-		if (viewz < groundz)
-			shadowyscale += FixedMul(FixedMul(thing->radius*2 / SHORT(patch->height), scalemul), zslope);
-		else
-			shadowyscale -= FixedMul(FixedMul(thing->radius*2 / SHORT(patch->height), scalemul), zslope);
-
-		shadowyscale = abs(shadowyscale);
-
-		shadowskew = xslope;
-	}
-
-	tx -= SHORT(patch->width) * shadowxscale/2;
+	tx -= patch->width * shadowxscale/2;
 	x1 = (centerxfrac + FixedMul(tx,xscale))>>FRACBITS;
 	if (x1 >= viewwidth) return;
 
-	tx += SHORT(patch->width) * shadowxscale;
+	tx += patch->width * shadowxscale;
 	x2 = ((centerxfrac + FixedMul(tx,xscale))>>FRACBITS); x2--;
 	if (x2 < 0 || x2 <= x1) return;
 
-	if (shadowyscale < FRACUNIT/SHORT(patch->height)) return; // fix some crashes?
+	if (shadowyscale < FRACUNIT/patch->height) return; // fix some crashes?
 
 	shadow = R_NewVisSprite();
 	shadow->patch = patch;
@@ -1290,8 +1311,8 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 	shadow->dispoffset = vis->dispoffset - 5;
 	shadow->gx = thing->x;
 	shadow->gy = thing->y;
-	shadow->gzt = groundz + SHORT(patch->height) * shadowyscale / 2;
-	shadow->gz = shadow->gzt - SHORT(patch->height) * shadowyscale;
+	shadow->gzt = groundz + patch->height * shadowyscale / 2;
+	shadow->gz = shadow->gzt - patch->height * shadowyscale;
 	shadow->texturemid = FixedMul(thing->scale, FixedDiv(shadow->gzt - viewz, shadowyscale));
 	if (thing->skin && ((skin_t *)thing->skin)->flags & SF_HIRES)
 		shadow->texturemid = FixedMul(shadow->texturemid, ((skin_t *)thing->skin)->highresscale);
@@ -1312,7 +1333,7 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 
 	shadow->startfrac = 0;
 	//shadow->xiscale = 0x7ffffff0 / (shadow->xscale/2);
-	shadow->xiscale = (SHORT(patch->width)<<FRACBITS)/(x2-x1+1); // fuck it
+	shadow->xiscale = (patch->width<<FRACBITS)/(x2-x1+1); // fuck it
 
 	if (shadow->x1 > x1)
 		shadow->startfrac += shadow->xiscale*(shadow->x1-x1);
@@ -1321,28 +1342,32 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale,
 	x1 += (x2-x1)/2;
 	shadow->shear.offset = shadow->x1-x1;
 
-	if (thing->subsector->sector->numlights)
+	if (thing->renderflags & RF_NOCOLORMAPS)
+		shadow->extra_colormap = NULL;
+	else
 	{
-		INT32 lightnum;
-		light = thing->subsector->sector->numlights - 1;
-
-		// R_GetPlaneLight won't work on sloped lights!
-		for (lightnum = 1; lightnum < thing->subsector->sector->numlights; lightnum++) {
-			fixed_t h = P_GetLightZAt(&thing->subsector->sector->lightlist[lightnum], thing->x, thing->y);
-			if (h <= shadow->gzt) {
-				light = lightnum - 1;
-				break;
+		if (thing->subsector->sector->numlights)
+		{
+			INT32 lightnum;
+			light = thing->subsector->sector->numlights - 1;
+
+			// R_GetPlaneLight won't work on sloped lights!
+			for (lightnum = 1; lightnum < thing->subsector->sector->numlights; lightnum++) {
+				fixed_t h = P_GetLightZAt(&thing->subsector->sector->lightlist[lightnum], thing->x, thing->y);
+				if (h <= shadow->gzt) {
+					light = lightnum - 1;
+					break;
+				}
 			}
 		}
-		//light = R_GetPlaneLight(thing->subsector->sector, shadow->gzt, false);
-	}
 
-	if (thing->subsector->sector->numlights)
-		shadow->extra_colormap = *thing->subsector->sector->lightlist[light].extra_colormap;
-	else
-		shadow->extra_colormap = thing->subsector->sector->extra_colormap;
+		if (thing->subsector->sector->numlights)
+			shadow->extra_colormap = *thing->subsector->sector->lightlist[light].extra_colormap;
+		else
+			shadow->extra_colormap = thing->subsector->sector->extra_colormap;
+	}
 
-	shadow->transmap = transtables + (trans<<FF_TRANSSHIFT);
+	shadow->transmap = R_GetTranslucencyTable(trans + 1);
 	shadow->colormap = scalelight[0][0]; // full dark!
 
 	objectsdrawn++;
@@ -1358,7 +1383,9 @@ static void R_ProjectSprite(mobj_t *thing)
 	mobj_t *oldthing = thing;
 	fixed_t tr_x, tr_y;
 	fixed_t tx, tz;
-	fixed_t xscale, yscale, sortscale; //added : 02-02-98 : aaargll..if I were a math-guy!!!
+	fixed_t xscale, yscale; //added : 02-02-98 : aaargll..if I were a math-guy!!!
+	fixed_t sortscale, sortsplat = 0;
+	fixed_t sort_x = 0, sort_y = 0, sort_z;
 
 	INT32 x1, x2;
 
@@ -1371,13 +1398,15 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	size_t frame, rot;
 	UINT16 flip;
-	boolean vflip = (!(thing->eflags & MFE_VERTICALFLIP) != !(thing->frame & FF_VERTICALFLIP));
+	boolean vflip = (!(thing->eflags & MFE_VERTICALFLIP) != !R_ThingVerticallyFlipped(thing));
 	boolean mirrored = thing->mirrored;
-	boolean hflip = (!(thing->frame & FF_HORIZONTALFLIP) != !mirrored);
+	boolean hflip = (!R_ThingHorizontallyFlipped(thing) != !mirrored);
 
 	INT32 lindex;
+	INT32 trans;
 
 	vissprite_t *vis;
+	patch_t *patch;
 
 	spritecut_e cut = SC_NONE;
 
@@ -1386,22 +1415,29 @@ static void R_ProjectSprite(mobj_t *thing)
 	fixed_t scalestep;
 	fixed_t offset, offset2;
 
+	fixed_t sheartan = 0;
+	fixed_t shadowscale = FRACUNIT;
 	fixed_t basetx; // drop shadows
 
-	boolean papersprite = !!(thing->frame & FF_PAPERSPRITE);
-	fixed_t paperoffset = 0, paperdistance = 0; angle_t centerangle = 0;
+	boolean shadowdraw, shadoweffects, shadowskew;
+	boolean splat = R_ThingIsFloorSprite(thing);
+	boolean papersprite = (R_ThingIsPaperSprite(thing) && !splat);
+	fixed_t paperoffset = 0, paperdistance = 0;
+	angle_t centerangle = 0;
 
 	INT32 dispoffset = thing->info->dispoffset;
 
 	//SoM: 3/17/2000
-	fixed_t gz, gzt;
+	fixed_t gz = 0, gzt = 0;
 	INT32 heightsec, phs;
 	INT32 light = 0;
 	fixed_t this_scale = thing->scale;
+	fixed_t spritexscale, spriteyscale;
 
 	// rotsprite
 	fixed_t spr_width, spr_height;
 	fixed_t spr_offset, spr_topoffset;
+
 #ifdef ROTSPRITE
 	patch_t *rotsprite = NULL;
 	INT32 rollangle = 0;
@@ -1448,7 +1484,7 @@ static void R_ProjectSprite(mobj_t *thing)
 			thing->frame = states[S_UNKNOWN].frame;
 			sprdef = &sprites[thing->sprite];
 #ifdef ROTSPRITE
-			sprinfo = NULL;
+			sprinfo = &spriteinfo[thing->sprite];
 #endif
 			frame = thing->frame&FF_FRAMEMASK;
 		}
@@ -1457,7 +1493,7 @@ static void R_ProjectSprite(mobj_t *thing)
 	{
 		sprdef = &sprites[thing->sprite];
 #ifdef ROTSPRITE
-		sprinfo = NULL;
+		sprinfo = &spriteinfo[thing->sprite];
 #endif
 
 		if (frame >= sprdef->numframes)
@@ -1472,6 +1508,7 @@ static void R_ProjectSprite(mobj_t *thing)
 			thing->sprite = states[S_UNKNOWN].sprite;
 			thing->frame = states[S_UNKNOWN].frame;
 			sprdef = &sprites[thing->sprite];
+			sprinfo = &spriteinfo[thing->sprite];
 			frame = thing->frame&FF_FRAMEMASK;
 		}
 	}
@@ -1529,19 +1566,28 @@ static void R_ProjectSprite(mobj_t *thing)
 	spr_offset = spritecachedinfo[lump].offset;
 	spr_topoffset = spritecachedinfo[lump].topoffset;
 
+	//Fab: lumppat is the lump number of the patch to use, this is different
+	//     than lumpid for sprites-in-pwad : the graphics are patched
+	patch = W_CachePatchNum(sprframe->lumppat[rot], PU_SPRITE);
+
 #ifdef ROTSPRITE
-	if (thing->rollangle)
+	if (thing->rollangle
+	&& !(splat && !(thing->renderflags & RF_NOSPLATROLLANGLE)))
 	{
 		rollangle = R_GetRollAngle(thing->rollangle);
-		if (!(sprframe->rotsprite.cached & (1<<rot)))
-			R_CacheRotSprite(thing->sprite, frame, sprinfo, sprframe, rot, flip);
-		rotsprite = sprframe->rotsprite.patch[rot][rollangle];
+		rotsprite = Patch_GetRotatedSprite(sprframe, (thing->frame & FF_FRAMEMASK), rot, flip, false, sprinfo, rollangle);
+
 		if (rotsprite != NULL)
 		{
-			spr_width = SHORT(rotsprite->width) << FRACBITS;
-			spr_height = SHORT(rotsprite->height) << FRACBITS;
-			spr_offset = SHORT(rotsprite->leftoffset) << FRACBITS;
-			spr_topoffset = SHORT(rotsprite->topoffset) << FRACBITS;
+			patch = rotsprite;
+			cut |= SC_ISROTATED;
+
+			spr_width = rotsprite->width << FRACBITS;
+			spr_height = rotsprite->height << FRACBITS;
+			spr_offset = rotsprite->leftoffset << FRACBITS;
+			spr_topoffset = rotsprite->topoffset << FRACBITS;
+			spr_topoffset += FEETADJUST;
+
 			// flip -> rotate, not rotate -> flip
 			flip = 0;
 		}
@@ -1551,12 +1597,34 @@ static void R_ProjectSprite(mobj_t *thing)
 	flip = !flip != !hflip;
 
 	// calculate edges of the shape
+	spritexscale = thing->spritexscale;
+	spriteyscale = thing->spriteyscale;
+	if (spritexscale < 1 || spriteyscale < 1)
+		return;
+
+	if (thing->renderflags & RF_ABSOLUTEOFFSETS)
+	{
+		spr_offset = thing->spritexoffset;
+		spr_topoffset = thing->spriteyoffset;
+	}
+	else
+	{
+		SINT8 flipoffset = 1;
+
+		if ((thing->renderflags & RF_FLIPOFFSETS) && flip)
+			flipoffset = -1;
+
+		spr_offset += thing->spritexoffset * flipoffset;
+		spr_topoffset += thing->spriteyoffset * flipoffset;
+	}
+
 	if (flip)
 		offset = spr_offset - spr_width;
 	else
 		offset = -spr_offset;
-	offset = FixedMul(offset, this_scale);
-	offset2 = FixedMul(spr_width, this_scale);
+
+	offset = FixedMul(offset, FixedMul(spritexscale, this_scale));
+	offset2 = FixedMul(spr_width, FixedMul(spritexscale, this_scale));
 
 	if (papersprite)
 	{
@@ -1663,6 +1731,15 @@ static void R_ProjectSprite(mobj_t *thing)
 			return;
 	}
 
+	// Adjust the sort scale if needed
+	if (splat)
+	{
+		sort_z = (patch->height - patch->topoffset) * FRACUNIT;
+		ang = (viewangle >> ANGLETOFINESHIFT);
+		sort_x = FixedMul(FixedMul(FixedMul(spritexscale, this_scale), sort_z), FINECOSINE(ang));
+		sort_y = FixedMul(FixedMul(FixedMul(spriteyscale, this_scale), sort_z), FINESINE(ang));
+	}
+
 	if ((thing->flags2 & MF2_LINKDRAW) && thing->tracer) // toast 16/09/16 (SYMMETRY)
 	{
 		fixed_t linkscale;
@@ -1672,8 +1749,8 @@ static void R_ProjectSprite(mobj_t *thing)
 		if (! R_ThingVisible(thing))
 			return;
 
-		tr_x = thing->x - viewx;
-		tr_y = thing->y - viewy;
+		tr_x = (thing->x + sort_x) - viewx;
+		tr_y = (thing->y + sort_y) - viewy;
 		tz = FixedMul(tr_x, viewcos) + FixedMul(tr_y, viewsin);
 		linkscale = FixedDiv(projectiony, tz);
 
@@ -1684,7 +1761,23 @@ static void R_ProjectSprite(mobj_t *thing)
 			dispoffset *= -1; // if it's physically behind, make sure it's ordered behind (if dispoffset > 0)
 
 		sortscale = linkscale; // now make sure it's linked
-		cut = SC_LINKDRAW;
+		cut |= SC_LINKDRAW;
+	}
+	else if (splat)
+	{
+		tr_x = (thing->x + sort_x) - viewx;
+		tr_y = (thing->y + sort_y) - viewy;
+		sort_z = FixedMul(tr_x, viewcos) + FixedMul(tr_y, viewsin);
+		sortscale = FixedDiv(projectiony, sort_z);
+	}
+
+	// Calculate the splat's sortscale
+	if (splat)
+	{
+		tr_x = (thing->x - sort_x) - viewx;
+		tr_y = (thing->y - sort_y) - viewy;
+		sort_z = FixedMul(tr_x, viewcos) + FixedMul(tr_y, viewsin);
+		sortsplat = FixedDiv(projectiony, sort_z);
 	}
 
 	// PORTAL SPRITE CLIPPING
@@ -1697,19 +1790,93 @@ static void R_ProjectSprite(mobj_t *thing)
 			return;
 	}
 
-	//SoM: 3/17/2000: Disregard sprites that are out of view..
-	if (vflip)
+	// Determine the translucency value.
+	if (oldthing->flags2 & MF2_SHADOW || thing->flags2 & MF2_SHADOW) // actually only the player should use this (temporary invisibility)
+		trans = tr_trans80; // because now the translucency is set through FF_TRANSMASK
+	else if (oldthing->frame & FF_TRANSMASK)
 	{
-		// When vertical flipped, draw sprites from the top down, at least as far as offsets are concerned.
-		// sprite height - sprite topoffset is the proper inverse of the vertical offset, of course.
-		// remember gz and gzt should be seperated by sprite height, not thing height - thing height can be shorter than the sprite itself sometimes!
-		gz = oldthing->z + oldthing->height - FixedMul(spr_topoffset, this_scale);
-		gzt = gz + FixedMul(spr_height, this_scale);
+		trans = (oldthing->frame & FF_TRANSMASK) >> FF_TRANSSHIFT;
+		if (oldthing->blendmode == AST_TRANSLUCENT && trans >= NUMTRANSMAPS)
+			return;
 	}
 	else
+		trans = 0;
+
+	// Check if this sprite needs to be rendered like a shadow
+	shadowdraw = (!!(thing->renderflags & RF_SHADOWDRAW) && !(papersprite || splat));
+	shadoweffects = (thing->renderflags & RF_SHADOWEFFECTS);
+	shadowskew = (shadowdraw && thing->standingslope);
+
+	if (shadowdraw || shadoweffects)
+	{
+		fixed_t groundz = R_GetShadowZ(thing, NULL);
+		boolean isflipped = (thing->eflags & MFE_VERTICALFLIP);
+
+		if (shadoweffects)
+		{
+			mobj_t *caster = thing->target;
+
+			if (caster && !P_MobjWasRemoved(caster))
+			{
+				fixed_t floordiff;
+
+				if (abs(groundz-viewz)/tz > 4)
+					return; // Prevent stretchy shadows and possible crashes
+
+				floordiff = abs((isflipped ? caster->height : 0) + caster->z - groundz);
+				trans += ((floordiff / (100*FRACUNIT)) + 3);
+				shadowscale = FixedMul(FRACUNIT - floordiff/640, caster->scale);
+			}
+			else
+				trans += 3;
+
+			if (trans >= NUMTRANSMAPS)
+				return;
+
+			trans--;
+		}
+
+		if (shadowdraw)
+		{
+			spritexscale = FixedMul(thing->radius * 2, FixedMul(shadowscale, spritexscale));
+			spriteyscale = FixedMul(thing->radius * 2, FixedMul(shadowscale, spriteyscale));
+			spriteyscale = FixedMul(spriteyscale, FixedDiv(abs(groundz - viewz), tz));
+			spriteyscale = min(spriteyscale, spritexscale) / patch->height;
+			spritexscale /= patch->width;
+		}
+		else
+		{
+			spritexscale = FixedMul(shadowscale, spritexscale);
+			spriteyscale = FixedMul(shadowscale, spriteyscale);
+		}
+
+		if (shadowskew)
+		{
+			R_SkewShadowSprite(thing, thing->standingslope, groundz, patch->height, shadowscale, &spriteyscale, &sheartan);
+
+			gzt = (isflipped ? (thing->z + thing->height) : thing->z) + patch->height * spriteyscale / 2;
+			gz = gzt - patch->height * spriteyscale;
+
+			cut |= SC_SHEAR;
+		}
+	}
+
+	if (!shadowskew)
 	{
-		gzt = oldthing->z + FixedMul(spr_topoffset, this_scale);
-		gz = gzt - FixedMul(spr_height, this_scale);
+		//SoM: 3/17/2000: Disregard sprites that are out of view..
+		if (vflip)
+		{
+			// When vertical flipped, draw sprites from the top down, at least as far as offsets are concerned.
+			// sprite height - sprite topoffset is the proper inverse of the vertical offset, of course.
+			// remember gz and gzt should be seperated by sprite height, not thing height - thing height can be shorter than the sprite itself sometimes!
+			gz = oldthing->z + oldthing->height - FixedMul(spr_topoffset, FixedMul(spriteyscale, this_scale));
+			gzt = gz + FixedMul(spr_height, FixedMul(spriteyscale, this_scale));
+		}
+		else
+		{
+			gzt = oldthing->z + FixedMul(spr_topoffset, FixedMul(spriteyscale, this_scale));
+			gz = gzt - FixedMul(spr_height, FixedMul(spriteyscale, this_scale));
+		}
 	}
 
 	if (thing->subsector->sector->cullheight)
@@ -1721,12 +1888,13 @@ static void R_ProjectSprite(mobj_t *thing)
 	if (thing->subsector->sector->numlights)
 	{
 		INT32 lightnum;
+		fixed_t top = (splat) ? gz : gzt;
 		light = thing->subsector->sector->numlights - 1;
 
 		// R_GetPlaneLight won't work on sloped lights!
 		for (lightnum = 1; lightnum < thing->subsector->sector->numlights; lightnum++) {
 			fixed_t h = P_GetLightZAt(&thing->subsector->sector->lightlist[lightnum], thing->x, thing->y);
-			if (h <= gzt) {
+			if (h <= top) {
 				light = lightnum - 1;
 				break;
 			}
@@ -1762,21 +1930,23 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	// store information in a vissprite
 	vis = R_NewVisSprite();
+	vis->renderflags = thing->renderflags;
+	vis->rotateflags = sprframe->rotate;
 	vis->heightsec = heightsec; //SoM: 3/17/2000
 	vis->mobjflags = thing->flags;
-	vis->scale = yscale; //<<detailshift;
 	vis->sortscale = sortscale;
+	vis->sortsplat = sortsplat;
 	vis->dispoffset = dispoffset; // Monster Iestyn: 23/11/15
 	vis->gx = thing->x;
 	vis->gy = thing->y;
 	vis->gz = gz;
 	vis->gzt = gzt;
-	vis->texturemid = vis->gzt - viewz;
+	vis->texturemid = FixedDiv(gzt - viewz, spriteyscale);
 	vis->scalestep = scalestep;
 	vis->paperoffset = paperoffset;
 	vis->paperdistance = paperdistance;
 	vis->centerangle = centerangle;
-	vis->shear.tan = 0;
+	vis->shear.tan = sheartan;
 	vis->shear.offset = 0;
 
 	vis->mobj = thing; // Easy access! Tails 06-07-2002
@@ -1784,17 +1954,34 @@ static void R_ProjectSprite(mobj_t *thing)
 	vis->x1 = x1 < portalclipstart ? portalclipstart : x1;
 	vis->x2 = x2 >= portalclipend ? portalclipend-1 : x2;
 
-	vis->xscale = xscale; //SoM: 4/17/2000
 	vis->sector = thing->subsector->sector;
 	vis->szt = (INT16)((centeryfrac - FixedMul(vis->gzt - viewz, sortscale))>>FRACBITS);
 	vis->sz = (INT16)((centeryfrac - FixedMul(vis->gz - viewz, sortscale))>>FRACBITS);
 	vis->cut = cut;
+
 	if (thing->subsector->sector->numlights)
 		vis->extra_colormap = *thing->subsector->sector->lightlist[light].extra_colormap;
 	else
 		vis->extra_colormap = thing->subsector->sector->extra_colormap;
 
-	iscale = FixedDiv(FRACUNIT, xscale);
+	vis->xscale = FixedMul(spritexscale, xscale); //SoM: 4/17/2000
+	vis->scale = FixedMul(spriteyscale, yscale); //<<detailshift;
+
+	vis->spritexscale = spritexscale;
+	vis->spriteyscale = spriteyscale;
+	vis->spritexoffset = spr_offset;
+	vis->spriteyoffset = spr_topoffset;
+
+	if (shadowdraw || shadoweffects)
+	{
+		iscale = (patch->width<<FRACBITS)/(x2-x1+1); // fuck it
+		x1 += (x2-x1)/2; // reusing x1 variable
+		vis->shear.offset = vis->x1-x1;
+	}
+	else
+		iscale = FixedDiv(FRACUNIT, vis->xscale);
+
+	vis->shadowscale = shadowscale;
 
 	if (flip)
 	{
@@ -1809,41 +1996,31 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	if (vis->x1 > x1)
 	{
-		vis->startfrac += FixedDiv(vis->xiscale, this_scale)*(vis->x1-x1);
-		vis->scale += scalestep*(vis->x1 - x1);
+		vis->startfrac += FixedDiv(vis->xiscale, this_scale) * (vis->x1 - x1);
+		vis->scale += FixedMul(scalestep, spriteyscale) * (vis->x1 - x1);
 	}
 
-	//Fab: lumppat is the lump number of the patch to use, this is different
-	//     than lumpid for sprites-in-pwad : the graphics are patched
-#ifdef ROTSPRITE
-	if (rotsprite != NULL)
-		vis->patch = rotsprite;
+	if ((oldthing->blendmode != AST_COPY) && cv_translucency.value)
+		vis->transmap = R_GetBlendTable(oldthing->blendmode, trans);
 	else
-#endif
-		vis->patch = W_CachePatchNum(sprframe->lumppat[rot], PU_CACHE);
-
-//
-// determine the colormap (lightlevel & special effects)
-//
-	vis->transmap = NULL;
-
-	// specific translucency
-	if (!cv_translucency.value)
-		; // no translucency
-	else if (oldthing->flags2 & MF2_SHADOW || thing->flags2 & MF2_SHADOW) // actually only the player should use this (temporary invisibility)
-		vis->transmap = transtables + ((tr_trans80-1)<<FF_TRANSSHIFT); // because now the translucency is set through FF_TRANSMASK
-	else if (oldthing->frame & FF_TRANSMASK)
-		vis->transmap = transtables + (oldthing->frame & FF_TRANSMASK) - 0x10000;
+		vis->transmap = NULL;
 
-	if (oldthing->frame & FF_FULLBRIGHT || oldthing->flags2 & MF2_SHADOW || thing->flags2 & MF2_SHADOW)
+	if (R_ThingIsFullBright(oldthing) || oldthing->flags2 & MF2_SHADOW || thing->flags2 & MF2_SHADOW)
 		vis->cut |= SC_FULLBRIGHT;
+	else if (R_ThingIsFullDark(oldthing))
+		vis->cut |= SC_FULLDARK;
 
+	//
+	// determine the colormap (lightlevel & special effects)
+	//
 	if (vis->cut & SC_FULLBRIGHT
 		&& (!vis->extra_colormap || !(vis->extra_colormap->flags & CMF_FADEFULLBRIGHTSPRITES)))
 	{
 		// full bright: goggles
 		vis->colormap = colormaps;
 	}
+	else if (vis->cut & SC_FULLDARK)
+		vis->colormap = scalelight[0][0];
 	else
 	{
 		// diminished light
@@ -1857,8 +2034,12 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	if (vflip)
 		vis->cut |= SC_VFLIP;
+	if (splat)
+		vis->cut |= SC_SPLAT; // I like ya cut g
 
-	if (thing->subsector->sector->numlights)
+	vis->patch = patch;
+
+	if (thing->subsector->sector->numlights && !(shadowdraw || splat))
 		R_SplitSprite(vis);
 
 	if (oldthing->shadowscale && cv_shadow.value)
@@ -2000,7 +2181,7 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing)
 
 	//Fab: lumppat is the lump number of the patch to use, this is different
 	//     than lumpid for sprites-in-pwad : the graphics are patched
-	vis->patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+	vis->patch = W_CachePatchNum(sprframe->lumppat[0], PU_SPRITE);
 
 	// specific translucency
 	if (thing->frame & FF_TRANSMASK)
@@ -2464,13 +2645,45 @@ static void R_CreateDrawNodes(maskcount_t* mask, drawnode_t* head, boolean temps
 			}
 			else if (r2->sprite)
 			{
-				if (r2->sprite->x1 > rover->x2 || r2->sprite->x2 < rover->x1)
-					continue;
-				if (r2->sprite->szt > rover->sz || r2->sprite->sz < rover->szt)
-					continue;
+				boolean infront = (r2->sprite->sortscale > rover->sortscale
+								|| (r2->sprite->sortscale == rover->sortscale && r2->sprite->dispoffset > rover->dispoffset));
+
+				if (rover->cut & SC_SPLAT || r2->sprite->cut & SC_SPLAT)
+				{
+					fixed_t scale1 = (rover->cut & SC_SPLAT ? rover->sortsplat : rover->sortscale);
+					fixed_t scale2 = (r2->sprite->cut & SC_SPLAT ? r2->sprite->sortsplat : r2->sprite->sortscale);
+					boolean behind = (scale2 > scale1 || (scale2 == scale1 && r2->sprite->dispoffset > rover->dispoffset));
+
+					if (!behind)
+					{
+						fixed_t z1 = 0, z2 = 0;
+
+						if (rover->mobj->z - viewz > 0)
+						{
+							z1 = rover->pz;
+							z2 = r2->sprite->pz;
+						}
+						else
+						{
+							z1 = r2->sprite->pz;
+							z2 = rover->pz;
+						}
+
+						z1 -= viewz;
+						z2 -= viewz;
 
-				if (r2->sprite->sortscale > rover->sortscale
-				 || (r2->sprite->sortscale == rover->sortscale && r2->sprite->dispoffset > rover->dispoffset))
+						infront = (z1 >= z2);
+					}
+				}
+				else
+				{
+					if (r2->sprite->x1 > rover->x2 || r2->sprite->x2 < rover->x1)
+						continue;
+					if (r2->sprite->szt > rover->sz || r2->sprite->sz < rover->szt)
+						continue;
+				}
+
+				if (infront)
 				{
 					entry = R_CreateDrawNode(NULL);
 					(entry->prev = r2->prev)->next = entry;
@@ -2556,7 +2769,11 @@ static void R_DrawSprite(vissprite_t *spr)
 {
 	mfloorclip = spr->clipbot;
 	mceilingclip = spr->cliptop;
-	R_DrawVisSprite(spr);
+
+	if (spr->cut & SC_SPLAT)
+		R_DrawFloorSprite(spr);
+	else
+		R_DrawVisSprite(spr);
 }
 
 // Special drawer for precipitation sprites Tails 08-18-2002
@@ -2567,207 +2784,212 @@ static void R_DrawPrecipitationSprite(vissprite_t *spr)
 	R_DrawPrecipitationVisSprite(spr);
 }
 
-// R_ClipSprites
+// R_ClipVisSprite
 // Clips vissprites without drawing, so that portals can work. -Red
-void R_ClipSprites(drawseg_t* dsstart, portal_t* portal)
+void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, drawseg_t* dsstart, portal_t* portal)
 {
-	vissprite_t *spr;
-	for (; clippedvissprites < visspritecount; clippedvissprites++)
-	{
-		drawseg_t *ds;
-		INT32		x;
-		INT32		r1;
-		INT32		r2;
-		fixed_t		scale;
-		fixed_t		lowscale;
-		INT32		silhouette;
-
-		spr = R_GetVisSprite(clippedvissprites);
-
-		for (x = spr->x1; x <= spr->x2; x++)
-			spr->clipbot[x] = spr->cliptop[x] = -2;
-
-		// Scan drawsegs from end to start for obscuring segs.
-		// The first drawseg that has a greater scale
-		//  is the clip seg.
-		//SoM: 4/8/2000:
-		// Pointer check was originally nonportable
-		// and buggy, by going past LEFT end of array:
+	drawseg_t *ds;
+	INT32		x;
+	INT32		r1;
+	INT32		r2;
+	fixed_t		scale;
+	fixed_t		lowscale;
+	INT32		silhouette;
+
+	for (x = x1; x <= x2; x++)
+		spr->clipbot[x] = spr->cliptop[x] = -2;
+
+	// Scan drawsegs from end to start for obscuring segs.
+	// The first drawseg that has a greater scale
+	//  is the clip seg.
+	//SoM: 4/8/2000:
+	// Pointer check was originally nonportable
+	// and buggy, by going past LEFT end of array:
+
+	//    for (ds = ds_p-1; ds >= drawsegs; ds--)    old buggy code
+	for (ds = ds_p; ds-- > dsstart;)
+	{
+		// determine if the drawseg obscures the sprite
+		if (ds->x1 > x2 ||
+			ds->x2 < x1 ||
+			(!ds->silhouette
+			 && !ds->maskedtexturecol))
+		{
+			// does not cover sprite
+			continue;
+		}
 
-		//    for (ds = ds_p-1; ds >= drawsegs; ds--)    old buggy code
-		for (ds = ds_p; ds-- > dsstart;)
+		if (ds->portalpass != 66)
 		{
-			// determine if the drawseg obscures the sprite
-			if (ds->x1 > spr->x2 ||
-			    ds->x2 < spr->x1 ||
-			    (!ds->silhouette
-			     && !ds->maskedtexturecol))
+			if (ds->portalpass > 0 && ds->portalpass <= portalrender)
+				continue; // is a portal
+
+			if (ds->scale1 > ds->scale2)
 			{
-				// does not cover sprite
-				continue;
+				lowscale = ds->scale2;
+				scale = ds->scale1;
 			}
-
-			if (ds->portalpass != 66)
+			else
 			{
-				if (ds->portalpass > 0 && ds->portalpass <= portalrender)
-					continue; // is a portal
-
-				if (ds->scale1 > ds->scale2)
-				{
-					lowscale = ds->scale2;
-					scale = ds->scale1;
-				}
-				else
-				{
-					lowscale = ds->scale1;
-					scale = ds->scale2;
-				}
+				lowscale = ds->scale1;
+				scale = ds->scale2;
+			}
 
-				if (scale < spr->sortscale ||
-					(lowscale < spr->sortscale &&
-					 !R_PointOnSegSide (spr->gx, spr->gy, ds->curline)))
-				{
-					// masked mid texture?
-					/*if (ds->maskedtexturecol)
-						R_RenderMaskedSegRange (ds, r1, r2);*/
-					// seg is behind sprite
-					continue;
-				}
+			if (scale < spr->sortscale ||
+				(lowscale < spr->sortscale &&
+				 !R_PointOnSegSide (spr->gx, spr->gy, ds->curline)))
+			{
+				// masked mid texture?
+				/*if (ds->maskedtexturecol)
+					R_RenderMaskedSegRange (ds, r1, r2);*/
+				// seg is behind sprite
+				continue;
 			}
+		}
 
-			r1 = ds->x1 < spr->x1 ? spr->x1 : ds->x1;
-			r2 = ds->x2 > spr->x2 ? spr->x2 : ds->x2;
+		r1 = ds->x1 < x1 ? x1 : ds->x1;
+		r2 = ds->x2 > x2 ? x2 : ds->x2;
 
-			// clip this piece of the sprite
-			silhouette = ds->silhouette;
+		// clip this piece of the sprite
+		silhouette = ds->silhouette;
 
-			if (spr->gz >= ds->bsilheight)
-				silhouette &= ~SIL_BOTTOM;
+		if (spr->gz >= ds->bsilheight)
+			silhouette &= ~SIL_BOTTOM;
 
-			if (spr->gzt <= ds->tsilheight)
-				silhouette &= ~SIL_TOP;
+		if (spr->gzt <= ds->tsilheight)
+			silhouette &= ~SIL_TOP;
 
-			if (silhouette == SIL_BOTTOM)
+		if (silhouette == SIL_BOTTOM)
+		{
+			// bottom sil
+			for (x = r1; x <= r2; x++)
+				if (spr->clipbot[x] == -2)
+					spr->clipbot[x] = ds->sprbottomclip[x];
+		}
+		else if (silhouette == SIL_TOP)
+		{
+			// top sil
+			for (x = r1; x <= r2; x++)
+				if (spr->cliptop[x] == -2)
+					spr->cliptop[x] = ds->sprtopclip[x];
+		}
+		else if (silhouette == (SIL_TOP|SIL_BOTTOM))
+		{
+			// both
+			for (x = r1; x <= r2; x++)
 			{
-				// bottom sil
-				for (x = r1; x <= r2; x++)
-					if (spr->clipbot[x] == -2)
-						spr->clipbot[x] = ds->sprbottomclip[x];
+				if (spr->clipbot[x] == -2)
+					spr->clipbot[x] = ds->sprbottomclip[x];
+				if (spr->cliptop[x] == -2)
+					spr->cliptop[x] = ds->sprtopclip[x];
 			}
-			else if (silhouette == SIL_TOP)
-			{
-				// top sil
-				for (x = r1; x <= r2; x++)
-					if (spr->cliptop[x] == -2)
-						spr->cliptop[x] = ds->sprtopclip[x];
+		}
+	}
+	//SoM: 3/17/2000: Clip sprites in water.
+	if (spr->heightsec != -1)  // only things in specially marked sectors
+	{
+		fixed_t mh, h;
+		INT32 phs = viewplayer->mo->subsector->sector->heightsec;
+		if ((mh = sectors[spr->heightsec].floorheight) > spr->gz &&
+			(h = centeryfrac - FixedMul(mh -= viewz, spr->sortscale)) >= 0 &&
+			(h >>= FRACBITS) < viewheight)
+		{
+			if (mh <= 0 || (phs != -1 && viewz > sectors[phs].floorheight))
+			{                          // clip bottom
+				for (x = x1; x <= x2; x++)
+					if (spr->clipbot[x] == -2 || h < spr->clipbot[x])
+						spr->clipbot[x] = (INT16)h;
 			}
-			else if (silhouette == (SIL_TOP|SIL_BOTTOM))
+			else						// clip top
 			{
-				// both
-				for (x = r1; x <= r2; x++)
-				{
-					if (spr->clipbot[x] == -2)
-						spr->clipbot[x] = ds->sprbottomclip[x];
-					if (spr->cliptop[x] == -2)
-						spr->cliptop[x] = ds->sprtopclip[x];
-				}
+				for (x = x1; x <= x2; x++)
+					if (spr->cliptop[x] == -2 || h > spr->cliptop[x])
+						spr->cliptop[x] = (INT16)h;
 			}
 		}
-		//SoM: 3/17/2000: Clip sprites in water.
-		if (spr->heightsec != -1)  // only things in specially marked sectors
+
+		if ((mh = sectors[spr->heightsec].ceilingheight) < spr->gzt &&
+			(h = centeryfrac - FixedMul(mh-viewz, spr->sortscale)) >= 0 &&
+			(h >>= FRACBITS) < viewheight)
 		{
-			fixed_t mh, h;
-			INT32 phs = viewplayer->mo->subsector->sector->heightsec;
-			if ((mh = sectors[spr->heightsec].floorheight) > spr->gz &&
-				(h = centeryfrac - FixedMul(mh -= viewz, spr->sortscale)) >= 0 &&
-				(h >>= FRACBITS) < viewheight)
-			{
-				if (mh <= 0 || (phs != -1 && viewz > sectors[phs].floorheight))
-				{                          // clip bottom
-					for (x = spr->x1; x <= spr->x2; x++)
-						if (spr->clipbot[x] == -2 || h < spr->clipbot[x])
-							spr->clipbot[x] = (INT16)h;
-				}
-				else						// clip top
-				{
-					for (x = spr->x1; x <= spr->x2; x++)
-						if (spr->cliptop[x] == -2 || h > spr->cliptop[x])
-							spr->cliptop[x] = (INT16)h;
-				}
+			if (phs != -1 && viewz >= sectors[phs].ceilingheight)
+			{                         // clip bottom
+				for (x = x1; x <= x2; x++)
+					if (spr->clipbot[x] == -2 || h < spr->clipbot[x])
+						spr->clipbot[x] = (INT16)h;
 			}
-
-			if ((mh = sectors[spr->heightsec].ceilingheight) < spr->gzt &&
-			    (h = centeryfrac - FixedMul(mh-viewz, spr->sortscale)) >= 0 &&
-			    (h >>= FRACBITS) < viewheight)
+			else                       // clip top
 			{
-				if (phs != -1 && viewz >= sectors[phs].ceilingheight)
-				{                         // clip bottom
-					for (x = spr->x1; x <= spr->x2; x++)
-						if (spr->clipbot[x] == -2 || h < spr->clipbot[x])
-							spr->clipbot[x] = (INT16)h;
-				}
-				else                       // clip top
-				{
-					for (x = spr->x1; x <= spr->x2; x++)
-						if (spr->cliptop[x] == -2 || h > spr->cliptop[x])
-							spr->cliptop[x] = (INT16)h;
-				}
+				for (x = x1; x <= x2; x++)
+					if (spr->cliptop[x] == -2 || h > spr->cliptop[x])
+						spr->cliptop[x] = (INT16)h;
 			}
 		}
-		if (spr->cut & SC_TOP && spr->cut & SC_BOTTOM)
+	}
+	if (spr->cut & SC_TOP && spr->cut & SC_BOTTOM)
+	{
+		for (x = x1; x <= x2; x++)
 		{
-			for (x = spr->x1; x <= spr->x2; x++)
-			{
-				if (spr->cliptop[x] == -2 || spr->szt > spr->cliptop[x])
-					spr->cliptop[x] = spr->szt;
+			if (spr->cliptop[x] == -2 || spr->szt > spr->cliptop[x])
+				spr->cliptop[x] = spr->szt;
 
-				if (spr->clipbot[x] == -2 || spr->sz < spr->clipbot[x])
-					spr->clipbot[x] = spr->sz;
-			}
+			if (spr->clipbot[x] == -2 || spr->sz < spr->clipbot[x])
+				spr->clipbot[x] = spr->sz;
 		}
-		else if (spr->cut & SC_TOP)
+	}
+	else if (spr->cut & SC_TOP)
+	{
+		for (x = x1; x <= x2; x++)
 		{
-			for (x = spr->x1; x <= spr->x2; x++)
-			{
-				if (spr->cliptop[x] == -2 || spr->szt > spr->cliptop[x])
-					spr->cliptop[x] = spr->szt;
-			}
+			if (spr->cliptop[x] == -2 || spr->szt > spr->cliptop[x])
+				spr->cliptop[x] = spr->szt;
 		}
-		else if (spr->cut & SC_BOTTOM)
+	}
+	else if (spr->cut & SC_BOTTOM)
+	{
+		for (x = x1; x <= x2; x++)
 		{
-			for (x = spr->x1; x <= spr->x2; x++)
-			{
-				if (spr->clipbot[x] == -2 || spr->sz < spr->clipbot[x])
-					spr->clipbot[x] = spr->sz;
-			}
+			if (spr->clipbot[x] == -2 || spr->sz < spr->clipbot[x])
+				spr->clipbot[x] = spr->sz;
 		}
+	}
 
-		// all clipping has been performed, so store the values - what, did you think we were drawing them NOW?
+	// all clipping has been performed, so store the values - what, did you think we were drawing them NOW?
 
-		// check for unclipped columns
-		for (x = spr->x1; x <= spr->x2; x++)
-		{
-			if (spr->clipbot[x] == -2)
-				spr->clipbot[x] = (INT16)viewheight;
+	// check for unclipped columns
+	for (x = x1; x <= x2; x++)
+	{
+		if (spr->clipbot[x] == -2)
+			spr->clipbot[x] = (INT16)viewheight;
 
-			if (spr->cliptop[x] == -2)
-				//Fab : 26-04-98: was -1, now clips against console bottom
-				spr->cliptop[x] = (INT16)con_clipviewtop;
-		}
+		if (spr->cliptop[x] == -2)
+			//Fab : 26-04-98: was -1, now clips against console bottom
+			spr->cliptop[x] = (INT16)con_clipviewtop;
+	}
 
-		if (portal)
+	if (portal)
+	{
+		for (x = x1; x <= x2; x++)
 		{
-			for (x = spr->x1; x <= spr->x2; x++)
-			{
-				if (spr->clipbot[x] > portal->floorclip[x - portal->start])
-					spr->clipbot[x] = portal->floorclip[x - portal->start];
-				if (spr->cliptop[x] < portal->ceilingclip[x - portal->start])
-					spr->cliptop[x] = portal->ceilingclip[x - portal->start];
-			}
+			if (spr->clipbot[x] > portal->floorclip[x - portal->start])
+				spr->clipbot[x] = portal->floorclip[x - portal->start];
+			if (spr->cliptop[x] < portal->ceilingclip[x - portal->start])
+				spr->cliptop[x] = portal->ceilingclip[x - portal->start];
 		}
 	}
 }
 
+void R_ClipSprites(drawseg_t* dsstart, portal_t* portal)
+{
+	for (; clippedvissprites < visspritecount; clippedvissprites++)
+	{
+		vissprite_t *spr = R_GetVisSprite(clippedvissprites);
+		INT32 x1 = (spr->cut & SC_SPLAT) ? 0 : spr->x1;
+		INT32 x2 = (spr->cut & SC_SPLAT) ? viewwidth : spr->x2;
+		R_ClipVisSprite(spr, x1, x2, dsstart, portal);
+	}
+}
+
 /* Check if thing may be drawn from our current view. */
 boolean R_ThingVisible (mobj_t *thing)
 {
@@ -2817,6 +3039,36 @@ boolean R_PrecipThingVisible (precipmobj_t *precipthing,
 	return ( approx_dist <= limit_dist );
 }
 
+boolean R_ThingHorizontallyFlipped(mobj_t *thing)
+{
+	return (thing->frame & FF_HORIZONTALFLIP || thing->renderflags & RF_HORIZONTALFLIP);
+}
+
+boolean R_ThingVerticallyFlipped(mobj_t *thing)
+{
+	return (thing->frame & FF_VERTICALFLIP || thing->renderflags & RF_VERTICALFLIP);
+}
+
+boolean R_ThingIsPaperSprite(mobj_t *thing)
+{
+	return (thing->frame & FF_PAPERSPRITE || thing->renderflags & RF_PAPERSPRITE);
+}
+
+boolean R_ThingIsFloorSprite(mobj_t *thing)
+{
+	return (thing->flags2 & MF2_SPLAT || thing->renderflags & RF_FLOORSPRITE);
+}
+
+boolean R_ThingIsFullBright(mobj_t *thing)
+{
+	return (thing->frame & FF_FULLBRIGHT || thing->renderflags & RF_FULLBRIGHT);
+}
+
+boolean R_ThingIsFullDark(mobj_t *thing)
+{
+	return (thing->renderflags & RF_FULLDARK);
+}
+
 //
 // R_DrawMasked
 //
diff --git a/src/r_things.h b/src/r_things.h
index c9a48dd2813271ab92c5af93f2e6e5934c4f4194..d15ae818c4c273dee396fe9c6b7919a95c87b98b 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -15,6 +15,7 @@
 #define __R_THINGS__
 
 #include "r_plane.h"
+#include "r_patch.h"
 #include "r_picformats.h"
 #include "r_portal.h"
 #include "r_defs.h"
@@ -64,7 +65,6 @@ fixed_t R_GetShadowZ(mobj_t *thing, pslope_t **shadowslope);
 void R_AddSprites(sector_t *sec, INT32 lightlevel);
 void R_InitSprites(void);
 void R_ClearSprites(void);
-void R_ClipSprites(drawseg_t* dsstart, portal_t* portal);
 
 boolean R_ThingVisible (mobj_t *thing);
 
@@ -75,6 +75,15 @@ boolean R_ThingVisibleWithinDist (mobj_t *thing,
 boolean R_PrecipThingVisible (precipmobj_t *precipthing,
 		fixed_t precip_draw_dist);
 
+boolean R_ThingHorizontallyFlipped (mobj_t *thing);
+boolean R_ThingVerticallyFlipped (mobj_t *thing);
+
+boolean R_ThingIsPaperSprite (mobj_t *thing);
+boolean R_ThingIsFloorSprite (mobj_t *thing);
+
+boolean R_ThingIsFullBright (mobj_t *thing);
+boolean R_ThingIsFullDark (mobj_t *thing);
+
 // --------------
 // MASKED DRAWING
 // --------------
@@ -107,19 +116,23 @@ void R_DrawMasked(maskcount_t* masks, UINT8 nummasks);
 typedef enum
 {
 	// actual cuts
-	SC_NONE = 0,
-	SC_TOP = 1,
-	SC_BOTTOM = 1<<1,
+	SC_NONE       = 0,
+	SC_TOP        = 1,
+	SC_BOTTOM     = 1<<1,
 	// other flags
-	SC_PRECIP = 1<<2,
-	SC_LINKDRAW = 1<<3,
+	SC_PRECIP     = 1<<2,
+	SC_LINKDRAW   = 1<<3,
 	SC_FULLBRIGHT = 1<<4,
-	SC_VFLIP = 1<<5,
-	SC_ISSCALED = 1<<6,
-	SC_SHADOW = 1<<7,
+	SC_FULLDARK   = 1<<5,
+	SC_VFLIP      = 1<<6,
+	SC_ISSCALED   = 1<<7,
+	SC_ISROTATED  = 1<<8,
+	SC_SHADOW     = 1<<9,
+	SC_SHEAR      = 1<<10,
+	SC_SPLAT      = 1<<11,
 	// masks
-	SC_CUTMASK = SC_TOP|SC_BOTTOM,
-	SC_FLAGMASK = ~SC_CUTMASK
+	SC_CUTMASK    = SC_TOP|SC_BOTTOM,
+	SC_FLAGMASK   = ~SC_CUTMASK
 } spritecut_e;
 
 // A vissprite_t is a thing that will be drawn during a refresh,
@@ -141,7 +154,9 @@ typedef struct vissprite_s
 	fixed_t gz, gzt; // global bottom/top for silhouette clipping and sorting with 3D floors
 
 	fixed_t startfrac; // horizontal position of x1
-	fixed_t scale, sortscale; // sortscale only differs from scale for paper sprites and MF2_LINKDRAW
+	fixed_t scale;
+	fixed_t sortscale; // sortscale only differs from scale for paper sprites, floor sprites, and MF2_LINKDRAW
+	fixed_t sortsplat; // the sortscale from behind the floor sprite
 	fixed_t scalestep; // only for paper sprites, 0 otherwise
 	fixed_t paperoffset, paperdistance; // for paper sprites, offset/dist relative to the angle
 	fixed_t xiscale; // negative if flipped
@@ -174,6 +189,13 @@ typedef struct vissprite_s
 	INT16 sz, szt;
 
 	spritecut_e cut;
+	UINT32 renderflags;
+	UINT8 rotateflags;
+
+	fixed_t spritexscale, spriteyscale;
+	fixed_t spritexoffset, spriteyoffset;
+
+	fixed_t shadowscale;
 
 	INT16 clipbot[MAXVIDWIDTH], cliptop[MAXVIDWIDTH];
 
@@ -182,6 +204,12 @@ typedef struct vissprite_s
 
 extern UINT32 visspritecount;
 
+void R_ClipSprites(drawseg_t* dsstart, portal_t* portal);
+void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2, drawseg_t* dsstart, portal_t* portal);
+
+boolean R_SpriteIsFlashing(vissprite_t *vis);
+UINT8 *R_GetSpriteTranslation(vissprite_t *vis);
+
 // ----------
 // DRAW NODES
 // ----------
diff --git a/src/s_sound.c b/src/s_sound.c
index 793794aa7c70bcabbcd02e7fade906e108e39bb1..36bd454c104b02867c29d3f16f22f639ed06dc97 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -29,10 +29,7 @@
 #include "fastcmp.h"
 #include "m_misc.h" // for tunes command
 #include "m_cond.h" // for conditionsets
-
-#ifdef HAVE_LUA_MUSICPLUS
 #include "lua_hook.h" // MusicChange hook
-#endif
 
 #ifdef HW3SOUND
 // 3D Sound Interface
@@ -2272,10 +2269,8 @@ void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32
 		return;
 
 	strncpy(newmusic, mmusic, 7);
-#ifdef HAVE_LUA_MUSICPLUS
-	if(LUAh_MusicChange(music_name, newmusic, &mflags, &looping, &position, &prefadems, &fadeinms))
+	if (LUAh_MusicChange(music_name, newmusic, &mflags, &looping, &position, &prefadems, &fadeinms))
 		return;
-#endif
 	newmusic[6] = 0;
 
  	// No Music (empty string)
diff --git a/src/screen.c b/src/screen.c
index 048480fb27d8d6a54024cac0ff5664f72862e711..f14cf4bf6c3065790f0cad4ea413a3a5749606dd 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -28,6 +28,7 @@
 #include "d_main.h"
 #include "d_clisrv.h"
 #include "f_finale.h"
+#include "y_inter.h" // usebuffer
 #include "i_sound.h" // closed captions
 #include "s_sound.h" // ditto
 #include "g_game.h" // ditto
@@ -63,15 +64,15 @@ consvar_t cv_scr_height = CVAR_INIT ("scr_height", "800", CV_SAVE, CV_Unsigned,
 consvar_t cv_scr_depth = CVAR_INIT ("scr_depth", "16 bits", CV_SAVE, scr_depth_cons_t, NULL);
 consvar_t cv_renderview = CVAR_INIT ("renderview", "On", 0, CV_OnOff, NULL);
 
-static void SCR_ActuallyChangeRenderer(void);
-static CV_PossibleValue_t cv_renderer_t[] = {
+CV_PossibleValue_t cv_renderer_t[] = {
 	{1, "Software"},
 #ifdef HWRENDER
 	{2, "OpenGL"},
 #endif
 	{0, NULL}
 };
-consvar_t cv_renderer = CVAR_INIT ("renderer", "Software", CV_SAVE|CV_NOLUA|CV_CALL, cv_renderer_t, SCR_ChangeRenderer);
+
+consvar_t cv_renderer = CVAR_INIT ("renderer", "Software", CV_SAVE|CV_NOLUA|CV_CALL, cv_renderer_t, SCR_SetTargetRenderer);
 
 static void SCR_ChangeFullscreen(void);
 
@@ -121,34 +122,34 @@ void SCR_SetDrawFuncs(void)
 		colfuncs[COLDRAWFUNC_FOG] = R_DrawFogColumn_8;
 
 		spanfuncs[SPANDRAWFUNC_TRANS] = R_DrawTranslucentSpan_8;
+		spanfuncs[SPANDRAWFUNC_TILTED] = R_DrawTiltedSpan_8;
+		spanfuncs[SPANDRAWFUNC_TILTEDTRANS] = R_DrawTiltedTranslucentSpan_8;
 		spanfuncs[SPANDRAWFUNC_SPLAT] = R_DrawSplat_8;
 		spanfuncs[SPANDRAWFUNC_TRANSSPLAT] = R_DrawTranslucentSplat_8;
-		spanfuncs[SPANDRAWFUNC_FOG] = R_DrawFogSpan_8;
-#ifndef NOWATER
+		spanfuncs[SPANDRAWFUNC_TILTEDSPLAT] = R_DrawTiltedSplat_8;
+		spanfuncs[SPANDRAWFUNC_SPRITE] = R_DrawFloorSprite_8;
+		spanfuncs[SPANDRAWFUNC_TRANSSPRITE] = R_DrawTranslucentFloorSprite_8;
+		spanfuncs[SPANDRAWFUNC_TILTEDSPRITE] = R_DrawTiltedFloorSprite_8;
+		spanfuncs[SPANDRAWFUNC_TILTEDTRANSSPRITE] = R_DrawTiltedTranslucentFloorSprite_8;
 		spanfuncs[SPANDRAWFUNC_WATER] = R_DrawTranslucentWaterSpan_8;
-#endif
-		spanfuncs[SPANDRAWFUNC_TILTED] = R_DrawTiltedSpan_8;
-		spanfuncs[SPANDRAWFUNC_TILTEDTRANS] = R_DrawTiltedTranslucentSpan_8;
-#ifndef NOWATER
 		spanfuncs[SPANDRAWFUNC_TILTEDWATER] = R_DrawTiltedTranslucentWaterSpan_8;
-#endif
-		spanfuncs[SPANDRAWFUNC_TILTEDSPLAT] = R_DrawTiltedSplat_8;
+		spanfuncs[SPANDRAWFUNC_FOG] = R_DrawFogSpan_8;
 
 		// Lactozilla: Non-powers-of-two
 		spanfuncs_npo2[BASEDRAWFUNC] = R_DrawSpan_NPO2_8;
 		spanfuncs_npo2[SPANDRAWFUNC_TRANS] = R_DrawTranslucentSpan_NPO2_8;
+		spanfuncs_npo2[SPANDRAWFUNC_TILTED] = R_DrawTiltedSpan_NPO2_8;
+		spanfuncs_npo2[SPANDRAWFUNC_TILTEDTRANS] = R_DrawTiltedTranslucentSpan_NPO2_8;
 		spanfuncs_npo2[SPANDRAWFUNC_SPLAT] = R_DrawSplat_NPO2_8;
 		spanfuncs_npo2[SPANDRAWFUNC_TRANSSPLAT] = R_DrawTranslucentSplat_NPO2_8;
-		spanfuncs_npo2[SPANDRAWFUNC_FOG] = NULL; // Not needed
-#ifndef NOWATER
+		spanfuncs_npo2[SPANDRAWFUNC_TILTEDSPLAT] = R_DrawTiltedSplat_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;
+		spanfuncs_npo2[SPANDRAWFUNC_TILTEDTRANSSPRITE] = R_DrawTiltedTranslucentFloorSprite_NPO2_8;
 		spanfuncs_npo2[SPANDRAWFUNC_WATER] = R_DrawTranslucentWaterSpan_NPO2_8;
-#endif
-		spanfuncs_npo2[SPANDRAWFUNC_TILTED] = R_DrawTiltedSpan_NPO2_8;
-		spanfuncs_npo2[SPANDRAWFUNC_TILTEDTRANS] = R_DrawTiltedTranslucentSpan_NPO2_8;
-#ifndef NOWATER
 		spanfuncs_npo2[SPANDRAWFUNC_TILTEDWATER] = R_DrawTiltedTranslucentWaterSpan_NPO2_8;
-#endif
-		spanfuncs_npo2[SPANDRAWFUNC_TILTEDSPLAT] = R_DrawTiltedSplat_NPO2_8;
+		spanfuncs_npo2[SPANDRAWFUNC_FOG] = NULL; // Not needed
 
 #ifdef RUSEASM
 		if (R_ASM)
@@ -202,14 +203,15 @@ void SCR_SetMode(void)
 	// Lactozilla: Renderer switching
 	if (setrenderneeded)
 	{
-		Z_PreparePatchFlush();
-		needpatchflush = true;
-		needpatchrecache = true;
+		// stop recording movies (APNG only)
+		if (setrenderneeded && (moviemode == MM_APNG))
+			M_StopMovie();
+
 		VID_CheckRenderer();
-		if (!setmodeneeded)
-			VID_SetMode(vid.modenum);
+		vid.recalc = 1;
 	}
 
+	// Set the video mode in the video interface.
 	if (setmodeneeded)
 		VID_SetMode(--setmodeneeded);
 
@@ -279,34 +281,9 @@ void SCR_Startup(void)
 
 	vid.modenum = 0;
 
-	vid.dupx = vid.width / BASEVIDWIDTH;
-	vid.dupy = vid.height / BASEVIDHEIGHT;
-	vid.dupx = vid.dupy = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
-	vid.fdupx = FixedDiv(vid.width*FRACUNIT, BASEVIDWIDTH*FRACUNIT);
-	vid.fdupy = FixedDiv(vid.height*FRACUNIT, BASEVIDHEIGHT*FRACUNIT);
-
-#ifdef HWRENDER
-	if (rendermode != render_opengl && rendermode != render_none) // This was just placing it incorrectly at non aspect correct resolutions in opengl
-#endif
-		vid.fdupx = vid.fdupy = (vid.fdupx < vid.fdupy ? vid.fdupx : vid.fdupy);
-
-	vid.meddupx = (UINT8)(vid.dupx >> 1) + 1;
-	vid.meddupy = (UINT8)(vid.dupy >> 1) + 1;
-#ifdef HWRENDER
-	vid.fmeddupx = vid.meddupx*FRACUNIT;
-	vid.fmeddupy = vid.meddupy*FRACUNIT;
-#endif
-
-	vid.smalldupx = (UINT8)(vid.dupx / 3) + 1;
-	vid.smalldupy = (UINT8)(vid.dupy / 3) + 1;
-#ifdef HWRENDER
-	vid.fsmalldupx = vid.smalldupx*FRACUNIT;
-	vid.fsmalldupy = vid.smalldupy*FRACUNIT;
-#endif
-
-	vid.baseratio = FRACUNIT;
-
 	V_Init();
+	V_Recalc();
+
 	CV_RegisterVar(&cv_ticrate);
 	CV_RegisterVar(&cv_constextsize);
 
@@ -323,38 +300,7 @@ void SCR_Recalc(void)
 	// bytes per pixel quick access
 	scr_bpp = vid.bpp;
 
-	// scale 1,2,3 times in x and y the patches for the menus and overlays...
-	// calculated once and for all, used by routines in v_video.c
-	vid.dupx = vid.width / BASEVIDWIDTH;
-	vid.dupy = vid.height / BASEVIDHEIGHT;
-	vid.dupx = vid.dupy = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
-	vid.fdupx = FixedDiv(vid.width*FRACUNIT, BASEVIDWIDTH*FRACUNIT);
-	vid.fdupy = FixedDiv(vid.height*FRACUNIT, BASEVIDHEIGHT*FRACUNIT);
-
-#ifdef HWRENDER
-	//if (rendermode != render_opengl && rendermode != render_none) // This was just placing it incorrectly at non aspect correct resolutions in opengl
-	// 13/11/18:
-	// The above is no longer necessary, since we want OpenGL to be just like software now
-	// -- Monster Iestyn
-#endif
-		vid.fdupx = vid.fdupy = (vid.fdupx < vid.fdupy ? vid.fdupx : vid.fdupy);
-
-	//vid.baseratio = FixedDiv(vid.height << FRACBITS, BASEVIDHEIGHT << FRACBITS);
-	vid.baseratio = FRACUNIT;
-
-	vid.meddupx = (UINT8)(vid.dupx >> 1) + 1;
-	vid.meddupy = (UINT8)(vid.dupy >> 1) + 1;
-#ifdef HWRENDER
-	vid.fmeddupx = vid.meddupx*FRACUNIT;
-	vid.fmeddupy = vid.meddupy*FRACUNIT;
-#endif
-
-	vid.smalldupx = (UINT8)(vid.dupx / 3) + 1;
-	vid.smalldupy = (UINT8)(vid.dupy / 3) + 1;
-#ifdef HWRENDER
-	vid.fsmalldupx = vid.smalldupx*FRACUNIT;
-	vid.fsmalldupy = vid.smalldupy*FRACUNIT;
-#endif
+	V_Recalc();
 
 	// toggle off (then back on) the automap because some screensize-dependent values will
 	// be calculated next time the automap is activated.
@@ -374,6 +320,12 @@ void SCR_Recalc(void)
 	// vid.recalc lasts only for the next refresh...
 	con_recalc = true;
 	am_recalc = true;
+
+#ifdef HWRENDER
+	// Shoot! The screen texture was flushed!
+	if ((rendermode == render_opengl) && (gamestate == GS_INTERMISSION))
+		usebuffer = false;
+#endif
 }
 
 // Check for screen cmd-line parms: to force a resolution.
@@ -411,7 +363,16 @@ void SCR_CheckDefaultMode(void)
 		setmodeneeded = VID_GetModeForSize(cv_scr_width.value, cv_scr_height.value) + 1;
 	}
 
-	SCR_ActuallyChangeRenderer();
+	if (cv_renderer.value != (signed)rendermode)
+	{
+		if (chosenrendermode == render_none) // nothing set at command line
+			SCR_ChangeRenderer();
+		else
+		{
+			// Set cv_renderer to the current render mode
+			CV_StealthSetValue(&cv_renderer, rendermode);
+		}
+	}
 }
 
 // sets the modenum as the new default video mode to be saved in the config file
@@ -441,64 +402,33 @@ void SCR_ChangeFullscreen(void)
 #endif
 }
 
-static int target_renderer = 0;
+void SCR_SetTargetRenderer(void)
+{
+	if (!con_refresh)
+		SCR_ChangeRenderer();
+}
 
-void SCR_ActuallyChangeRenderer(void)
+void SCR_ChangeRenderer(void)
 {
-	setrenderneeded = target_renderer;
+	if ((signed)rendermode == cv_renderer.value)
+		return;
 
 #ifdef HWRENDER
-	// Well, it didn't even load anyway.
-	if ((vid_opengl_state == -1) && (setrenderneeded == render_opengl))
+	// Check if OpenGL loaded successfully (or wasn't disabled) before switching to it.
+	if ((vid.glstate == VID_GL_LIBRARY_ERROR)
+	&& (cv_renderer.value == render_opengl))
 	{
 		if (M_CheckParm("-nogl"))
 			CONS_Alert(CONS_ERROR, "OpenGL rendering was disabled!\n");
 		else
 			CONS_Alert(CONS_ERROR, "OpenGL never loaded\n");
-		setrenderneeded = 0;
 		return;
 	}
 #endif
 
-	// setting the same renderer twice WILL crash your game, so let's not, please
-	if (rendermode == setrenderneeded)
-		setrenderneeded = 0;
-}
-
-// Lactozilla: Renderer switching
-void SCR_ChangeRenderer(void)
-{
-	setrenderneeded = 0;
-
-	if (con_startup)
-	{
-		target_renderer = cv_renderer.value;
-#ifdef HWRENDER
-		if (M_CheckParm("-opengl") && (vid_opengl_state == 1))
-			target_renderer = rendermode = render_opengl;
-		else
-#endif
-		if (M_CheckParm("-software"))
-			target_renderer = rendermode = render_soft;
-		// set cv_renderer back
-		SCR_ChangeRendererCVars(rendermode);
-		return;
-	}
-
-	if (cv_renderer.value == 1)
-		target_renderer = render_soft;
-	else if (cv_renderer.value == 2)
-		target_renderer = render_opengl;
-	SCR_ActuallyChangeRenderer();
-}
-
-void SCR_ChangeRendererCVars(INT32 mode)
-{
-	// set cv_renderer back
-	if (mode == render_soft)
-		CV_StealthSetValue(&cv_renderer, 1);
-	else if (mode == render_opengl)
-		CV_StealthSetValue(&cv_renderer, 2);
+	// Set the new render mode
+	setrenderneeded = cv_renderer.value;
+	con_refresh = false;
 }
 
 boolean SCR_IsAspectCorrect(INT32 width, INT32 height)
diff --git a/src/screen.h b/src/screen.h
index 2cb2cf839160ab6e73e1544d6f7f6869ac4bdbb9..66452289cf360d1bbde5e182693043c22dabf1f8 100644
--- a/src/screen.h
+++ b/src/screen.h
@@ -72,10 +72,16 @@ typedef struct viddef_s
 #ifdef HWRENDER
 	INT32/*fixed_t*/ fsmalldupx, fsmalldupy;
 	INT32/*fixed_t*/ fmeddupx, fmeddupy;
+	INT32 glstate;
 #endif
 } viddef_t;
-#define VIDWIDTH vid.width
-#define VIDHEIGHT vid.height
+
+enum
+{
+	VID_GL_LIBRARY_NOTLOADED  = 0,
+	VID_GL_LIBRARY_LOADED     = 1,
+	VID_GL_LIBRARY_ERROR      = -1,
+};
 
 // internal additional info for vesa modes only
 typedef struct
@@ -134,18 +140,22 @@ enum
 {
 	SPANDRAWFUNC_BASE = BASEDRAWFUNC,
 	SPANDRAWFUNC_TRANS,
-	SPANDRAWFUNC_SPLAT,
-	SPANDRAWFUNC_TRANSSPLAT,
-	SPANDRAWFUNC_FOG,
-#ifndef NOWATER
-	SPANDRAWFUNC_WATER,
-#endif
 	SPANDRAWFUNC_TILTED,
 	SPANDRAWFUNC_TILTEDTRANS,
+
+	SPANDRAWFUNC_SPLAT,
+	SPANDRAWFUNC_TRANSSPLAT,
 	SPANDRAWFUNC_TILTEDSPLAT,
-#ifndef NOWATER
+
+	SPANDRAWFUNC_SPRITE,
+	SPANDRAWFUNC_TRANSSPRITE,
+	SPANDRAWFUNC_TILTEDSPRITE,
+	SPANDRAWFUNC_TILTEDTRANSSPRITE,
+
+	SPANDRAWFUNC_WATER,
 	SPANDRAWFUNC_TILTEDWATER,
-#endif
+
+	SPANDRAWFUNC_FOG,
 
 	SPANDRAWFUNC_MAX
 };
@@ -170,10 +180,12 @@ extern boolean R_SSE2;
 // ----------------
 extern viddef_t vid;
 extern INT32 setmodeneeded; // mode number to set if needed, or 0
+extern UINT8 setrenderneeded;
 
 void SCR_ChangeRenderer(void);
-void SCR_ChangeRendererCVars(INT32 mode);
-extern UINT8 setrenderneeded;
+void SCR_SetTargetRenderer(void);
+
+extern CV_PossibleValue_t cv_renderer_t[];
 
 extern INT32 scr_bpp;
 extern UINT8 *scr_borderpatch; // patch used to fill the view borders
@@ -182,17 +194,23 @@ extern consvar_t cv_scr_width, cv_scr_height, cv_scr_depth, cv_renderview, cv_re
 // wait for page flipping to end or not
 extern consvar_t cv_vidwait;
 
+// Initialize the screen
+void SCR_Startup(void);
+
 // Change video mode, only at the start of a refresh.
 void SCR_SetMode(void);
+
+// Set drawer functions for Software
 void SCR_SetDrawFuncs(void);
+
 // Recalc screen size dependent stuff
 void SCR_Recalc(void);
+
 // Check parms once at startup
 void SCR_CheckDefaultMode(void);
-// Set the mode number which is saved in the config
-void SCR_SetDefaultMode (void);
 
-void SCR_Startup (void);
+// Set the mode number which is saved in the config
+void SCR_SetDefaultMode(void);
 
 FUNCMATH boolean SCR_IsAspectCorrect(INT32 width, INT32 height);
 
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj b/src/sdl/Srb2SDL-vc10.vcxproj
index c2d6456e462bbf67bca399703e5b30b63d773d0a..22ac9fb8ff68228332cd142555f283be039ecfdc 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj
+++ b/src/sdl/Srb2SDL-vc10.vcxproj
@@ -283,6 +283,8 @@
     <ClInclude Include="..\r_draw.h" />
     <ClInclude Include="..\r_local.h" />
     <ClInclude Include="..\r_main.h" />
+    <ClInclude Include="..\r_patch.h" />
+    <ClInclude Include="..\r_patchrotation.h" />
     <ClInclude Include="..\r_picformats.h" />
     <ClInclude Include="..\r_plane.h" />
     <ClInclude Include="..\r_portal.h" />
@@ -452,6 +454,8 @@
       <ExcludedFromBuild>true</ExcludedFromBuild>
     </ClCompile>
     <ClCompile Include="..\r_main.c" />
+    <ClCompile Include="..\r_patch.c" />
+    <ClCompile Include="..\r_patchrotation.c" />
     <ClCompile Include="..\r_picformats.c" />
     <ClCompile Include="..\r_plane.c" />
     <ClCompile Include="..\r_portal.c" />
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj.filters b/src/sdl/Srb2SDL-vc10.vcxproj.filters
index 438746ac7f02a01745f3785c9bf60b1e969e8de1..adae2f446dbde8267e375bb794eacc50bed9c663 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj.filters
+++ b/src/sdl/Srb2SDL-vc10.vcxproj.filters
@@ -477,12 +477,18 @@
     <ClInclude Include="..\hardware\hw_clip.h">
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
-    <ClInclude Include="..\r_textures.h">
+    <ClInclude Include="..\r_patch.h">
+      <Filter>R_Rend</Filter>
+    </ClInclude>
+    <ClInclude Include="..\r_patchrotation.h">
       <Filter>R_Rend</Filter>
     </ClInclude>
     <ClInclude Include="..\r_picformats.h">
       <Filter>R_Rend</Filter>
     </ClInclude>
+    <ClInclude Include="..\r_textures.h">
+      <Filter>R_Rend</Filter>
+    </ClInclude>
     <ClInclude Include="..\r_portal.h">
       <Filter>R_Rend</Filter>
     </ClInclude>
@@ -961,12 +967,18 @@
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
     <ClCompile Include="..\apng.c" />
-    <ClCompile Include="..\r_textures.c">
+    <ClCompile Include="..\r_patch.c">
+      <Filter>R_Rend</Filter>
+    </ClCompile>
+    <ClCompile Include="..\r_patchrotation.c">
       <Filter>R_Rend</Filter>
     </ClCompile>
     <ClCompile Include="..\r_picformats.c">
       <Filter>R_Rend</Filter>
     </ClCompile>
+    <ClCompile Include="..\r_textures.c">
+      <Filter>R_Rend</Filter>
+    </ClCompile>
     <ClCompile Include="..\r_portal.c">
       <Filter>R_Rend</Filter>
     </ClCompile>
diff --git a/src/sdl/hwsym_sdl.c b/src/sdl/hwsym_sdl.c
index e545bbb6363a72210fdb25aeffb4de35a638b491..96e3d7d6926ef23771c8dcf489b4d8d2a16c0a1c 100644
--- a/src/sdl/hwsym_sdl.c
+++ b/src/sdl/hwsym_sdl.c
@@ -86,6 +86,7 @@ void *hwSym(const char *funcName,void *handle)
 	GETFUNC(ClearBuffer);
 	GETFUNC(SetTexture);
 	GETFUNC(UpdateTexture);
+	GETFUNC(DeleteTexture);
 	GETFUNC(ReadRect);
 	GETFUNC(GClipRect);
 	GETFUNC(ClearMipMapCache);
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index a49adb50866cb5fa9e096d599cabc8fd8af30421..b9423ac21a36fdaf5d7a7dbf8732f800552cf4f5 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -42,7 +42,7 @@
 
 #ifdef HAVE_IMAGE
 #include "SDL_image.h"
-#elif defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON) // Windows doesn't need this, as SDL will do it for us.
+#elif defined (__unix__) || (!defined(__APPLE__) && defined (UNIXCOMMON)) // Windows & Mac don't need this, as SDL will do it for us.
 #define LOAD_XPM //I want XPM!
 #include "IMG_xpm.c" //Alam: I don't want to add SDL_Image.dll/so
 #define HAVE_IMAGE //I have SDL_Image, sortof
@@ -95,7 +95,7 @@ static INT32 numVidModes = -1;
 static char vidModeName[33][32]; // allow 33 different modes
 
 rendermode_t rendermode = render_soft;
-static rendermode_t chosenrendermode = render_soft; // set by command line arguments
+rendermode_t chosenrendermode = render_none; // set by command line arguments
 
 boolean highcolor = false;
 
@@ -105,7 +105,6 @@ static consvar_t cv_stretch = CVAR_INIT ("stretch", "Off", CV_SAVE|CV_NOSHOWHELP
 static consvar_t cv_alwaysgrabmouse = CVAR_INIT ("alwaysgrabmouse", "Off", CV_SAVE, CV_OnOff, NULL);
 
 UINT8 graphics_started = 0; // Is used in console.c and screen.c
-INT32 vid_opengl_state = 0;
 
 // To disable fullscreen at startup; is set in VID_PrepareModeList
 boolean allow_fullscreen = false;
@@ -1443,7 +1442,8 @@ static SDL_bool Impl_CreateContext(void)
 {
 	// Renderer-specific stuff
 #ifdef HWRENDER
-	if ((rendermode == render_opengl) && (vid_opengl_state != -1))
+	if ((rendermode == render_opengl)
+	&& (vid.glstate != VID_GL_LIBRARY_ERROR))
 	{
 		if (!sdlglcontext)
 			sdlglcontext = SDL_GL_CreateContext(window);
@@ -1480,7 +1480,7 @@ void VID_CheckGLLoaded(rendermode_t oldrender)
 {
 	(void)oldrender;
 #ifdef HWRENDER
-	if (vid_opengl_state == -1) // Well, it didn't work the first time anyway.
+	if (vid.glstate == VID_GL_LIBRARY_ERROR) // Well, it didn't work the first time anyway.
 	{
 		CONS_Alert(CONS_ERROR, "OpenGL never loaded\n");
 		rendermode = oldrender;
@@ -1495,14 +1495,16 @@ void VID_CheckGLLoaded(rendermode_t oldrender)
 #endif
 }
 
-void VID_CheckRenderer(void)
+boolean VID_CheckRenderer(void)
 {
 	boolean rendererchanged = false;
 	boolean contextcreated = false;
+#ifdef HWRENDER
 	rendermode_t oldrenderer = rendermode;
+#endif
 
 	if (dedicated)
-		return;
+		return false;
 
 	if (setrenderneeded)
 	{
@@ -1516,11 +1518,12 @@ void VID_CheckRenderer(void)
 
 			// Initialise OpenGL before calling SDLSetMode!!!
 			// This is because SDLSetMode calls OglSdlSurface.
-			if (vid_opengl_state == 0)
+			if (vid.glstate == VID_GL_LIBRARY_NOTLOADED)
 			{
 				VID_StartupOpenGL();
+
 				// Loaded successfully!
-				if (vid_opengl_state == 1)
+				if (vid.glstate == VID_GL_LIBRARY_LOADED)
 				{
 					// Destroy the current window, if it exists.
 					if (window)
@@ -1543,7 +1546,7 @@ void VID_CheckRenderer(void)
 					contextcreated = true;
 				}
 			}
-			else if (vid_opengl_state == -1)
+			else if (vid.glstate == VID_GL_LIBRARY_ERROR)
 				rendererchanged = false;
 		}
 #endif
@@ -1565,27 +1568,22 @@ void VID_CheckRenderer(void)
 			bufSurface = NULL;
 		}
 
-		if (rendererchanged)
-		{
 #ifdef HWRENDER
-			if (vid_opengl_state == 1) // Only if OpenGL ever loaded!
-				HWR_FreeTextureCache();
+		if (rendererchanged && vid.glstate == VID_GL_LIBRARY_LOADED) // Only if OpenGL ever loaded!
+			HWR_ClearAllTextures();
 #endif
-			SCR_SetDrawFuncs();
-		}
+
+		SCR_SetDrawFuncs();
 	}
 #ifdef HWRENDER
-	else if (rendermode == render_opengl)
+	else if (rendermode == render_opengl && rendererchanged)
 	{
-		if (rendererchanged)
-		{
-			R_InitHardwareMode();
-			V_SetPalette(0);
-		}
+		HWR_Switch();
+		V_SetPalette(0);
 	}
-#else
-	(void)oldrenderer;
 #endif
+
+	return rendererchanged;
 }
 
 INT32 VID_SetMode(INT32 modeNum)
@@ -1626,7 +1624,7 @@ static SDL_bool Impl_CreateWindow(SDL_bool fullscreen)
 		flags |= SDL_WINDOW_BORDERLESS;
 
 #ifdef HWRENDER
-	if (vid_opengl_state == 1)
+	if (vid.glstate == VID_GL_LIBRARY_LOADED)
 		flags |= SDL_WINDOW_OPENGL;
 #endif
 
@@ -1747,12 +1745,44 @@ void I_StartupGraphics(void)
 			framebuffer = SDL_TRUE;
 	}
 
-#ifdef HWRENDER
-	if (M_CheckParm("-opengl"))
-		chosenrendermode = rendermode = render_opengl;
+	// Renderer choices
+	// Takes priority over the config.
+	if (M_CheckParm("-renderer"))
+	{
+		INT32 i = 0;
+		CV_PossibleValue_t *renderer_list = cv_renderer_t;
+		const char *modeparm = M_GetNextParm();
+		while (renderer_list[i].strvalue)
+		{
+			if (!stricmp(modeparm, renderer_list[i].strvalue))
+			{
+				chosenrendermode = renderer_list[i].value;
+				break;
+			}
+			i++;
+		}
+	}
+
+	// Choose Software renderer
 	else if (M_CheckParm("-software"))
+		chosenrendermode = render_soft;
+
+#ifdef HWRENDER
+	// Choose OpenGL renderer
+	else if (M_CheckParm("-opengl"))
+		chosenrendermode = render_opengl;
+
+	// Don't startup OpenGL
+	if (M_CheckParm("-nogl"))
+	{
+		vid.glstate = VID_GL_LIBRARY_ERROR;
+		if (chosenrendermode == render_opengl)
+			chosenrendermode = render_none;
+	}
 #endif
-		chosenrendermode = rendermode = render_soft;
+
+	if (chosenrendermode != render_none)
+		rendermode = chosenrendermode;
 
 	usesdl2soft = M_CheckParm("-softblit");
 	borderlesswindow = M_CheckParm("-borderless");
@@ -1761,9 +1791,7 @@ void I_StartupGraphics(void)
 	VID_Command_ModeList_f();
 
 #ifdef HWRENDER
-	if (M_CheckParm("-nogl"))
-		vid_opengl_state = -1; // Don't startup OpenGL
-	else if (chosenrendermode == render_opengl)
+	if (rendermode == render_opengl)
 		VID_StartupOpenGL();
 #endif
 
@@ -1836,6 +1864,7 @@ void VID_StartupOpenGL(void)
 		HWD.pfnClearBuffer      = hwSym("ClearBuffer",NULL);
 		HWD.pfnSetTexture       = hwSym("SetTexture",NULL);
 		HWD.pfnUpdateTexture    = hwSym("UpdateTexture",NULL);
+		HWD.pfnDeleteTexture    = hwSym("DeleteTexture",NULL);
 		HWD.pfnReadRect         = hwSym("ReadRect",NULL);
 		HWD.pfnGClipRect        = hwSym("GClipRect",NULL);
 		HWD.pfnClearMipMapCache = hwSym("ClearMipMapCache",NULL);
@@ -1863,9 +1892,9 @@ void VID_StartupOpenGL(void)
 		HWD.pfnSetShaderInfo    = hwSym("SetShaderInfo",NULL);
 		HWD.pfnLoadCustomShader = hwSym("LoadCustomShader",NULL);
 
-		vid_opengl_state = HWD.pfnInit() ? 1 : -1; // let load the OpenGL library
+		vid.glstate = HWD.pfnInit() ? VID_GL_LIBRARY_LOADED : VID_GL_LIBRARY_ERROR; // let load the OpenGL library
 
-		if (vid_opengl_state == -1)
+		if (vid.glstate == VID_GL_LIBRARY_ERROR)
 		{
 			rendermode = render_soft;
 			setrenderneeded = 0;
diff --git a/src/sdl/ogl_sdl.c b/src/sdl/ogl_sdl.c
index 04214ad03e3031f2f3de772b5c60aa46b02ebc10..52727c05600a5f33221e729d51263b08fcdde30b 100644
--- a/src/sdl/ogl_sdl.c
+++ b/src/sdl/ogl_sdl.c
@@ -189,7 +189,7 @@ boolean OglSdlSurface(INT32 w, INT32 h)
 
 	SetupGLFunc4();
 
-	granisotropicmode_cons_t[1].value = maximumAnisotropy;
+	glanisotropicmode_cons_t[1].value = maximumAnisotropy;
 
 	SDL_GL_SetSwapInterval(cv_vidwait.value ? 1 : 0);
 
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 86e0b37542224bc780017c44449ef0fb3e55d306..bd98c0a143a7da7bd7d8659b8ad0ca9559c6c9b2 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -203,9 +203,7 @@ void ST_doPaletteStuff(void)
 {
 	INT32 palette;
 
-	if (paused || P_AutoPause())
-		palette = 0;
-	else if (stplyr && stplyr->flashcount)
+	if (stplyr && stplyr->flashcount)
 		palette = stplyr->flashpal;
 	else
 		palette = 0;
@@ -215,8 +213,6 @@ void ST_doPaletteStuff(void)
 		palette = 0; // No flashpals here in OpenGL
 #endif
 
-	palette = min(max(palette, 0), 13);
-
 	if (palette != st_palette)
 	{
 		st_palette = palette;
@@ -232,7 +228,7 @@ void ST_doPaletteStuff(void)
 
 void ST_UnloadGraphics(void)
 {
-	Z_FreeTag(PU_HUDGFX);
+	Patch_FreeTag(PU_HUDGFX);
 }
 
 void ST_LoadGraphics(void)
@@ -2080,21 +2076,21 @@ static void ST_drawNiGHTSHUD(void)
 			if (stplyr->powers[pw_nights_superloop])
 			{
 				pwr = stplyr->powers[pw_nights_superloop];
-				V_DrawSmallScaledPatch(110, 44, 0, W_CachePatchName("NPRUA0",PU_CACHE));
+				V_DrawSmallScaledPatch(110, 44, 0, W_CachePatchName("NPRUA0",PU_SPRITE));
 				V_DrawThinString(106, 52, V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
 			}
 
 			if (stplyr->powers[pw_nights_helper])
 			{
 				pwr = stplyr->powers[pw_nights_helper];
-				V_DrawSmallScaledPatch(150, 44, 0, W_CachePatchName("NPRUC0",PU_CACHE));
+				V_DrawSmallScaledPatch(150, 44, 0, W_CachePatchName("NPRUC0",PU_SPRITE));
 				V_DrawThinString(146, 52, V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
 			}
 
 			if (stplyr->powers[pw_nights_linkfreeze])
 			{
 				pwr = stplyr->powers[pw_nights_linkfreeze];
-				V_DrawSmallScaledPatch(190, 44, 0, W_CachePatchName("NPRUE0",PU_CACHE));
+				V_DrawSmallScaledPatch(190, 44, 0, W_CachePatchName("NPRUE0",PU_SPRITE));
 				V_DrawThinString(186, 52, V_MONOSPACE, va("%2d.%02d", pwr/TICRATE, G_TicsToCentiseconds(pwr)));
 			}
 		}
@@ -2755,9 +2751,6 @@ static void ST_overlayDrawer(void)
 
 void ST_Drawer(void)
 {
-	if (needpatchrecache)
-		R_ReloadHUDGraphics();
-
 #ifdef SEENAMES
 	if (cv_seenames.value && cv_allowseenames.value && displayplayer == consoleplayer && seenplayer && seenplayer->mo)
 	{
diff --git a/src/taglist.c b/src/taglist.c
new file mode 100644
index 0000000000000000000000000000000000000000..b11216b6cf7b3e7c709b73750c2d46e8e9155c40
--- /dev/null
+++ b/src/taglist.c
@@ -0,0 +1,366 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C)      2020 by Nev3r.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  taglist.c
+/// \brief Ingame sector/line/mapthing tagging.
+
+#include "taglist.h"
+#include "z_zone.h"
+#include "r_data.h"
+
+// Taggroups are used to list elements of the same tag, for iteration.
+// Since elements can now have multiple tags, it means an element may appear
+// in several taggroups at the same time. These are built on level load.
+taggroup_t* tags_sectors[MAXTAGS + 1];
+taggroup_t* tags_lines[MAXTAGS + 1];
+taggroup_t* tags_mapthings[MAXTAGS + 1];
+
+/// Adds a tag to a given element's taglist.
+/// \warning This does not rebuild the global taggroups, which are used for iteration.
+void Tag_Add (taglist_t* list, const mtag_t tag)
+{
+	list->tags = Z_Realloc(list->tags, (list->count + 1) * sizeof(list->tags), PU_LEVEL, NULL);
+	list->tags[list->count++] = tag;
+}
+
+/// Sets the first tag entry in a taglist.
+/// Replicates the old way of accessing element->tag.
+void Tag_FSet (taglist_t* list, const mtag_t tag)
+{
+	if (!list->count)
+	{
+		Tag_Add(list, tag);
+		return;
+	}
+
+	list->tags[0] = tag;
+}
+
+/// Gets the first tag entry in a taglist.
+/// Replicates the old way of accessing element->tag.
+mtag_t Tag_FGet (const taglist_t* list)
+{
+	if (list->count)
+		return list->tags[0];
+
+	return 0;
+}
+
+/// Returns true if the given tag exist inside the list.
+boolean Tag_Find (const taglist_t* list, const mtag_t tag)
+{
+	size_t i;
+	for (i = 0; i < list->count; i++)
+		if (list->tags[i] == tag)
+			return true;
+
+	return false;
+}
+
+/// Returns true if at least one tag is shared between two given lists.
+boolean Tag_Share (const taglist_t* list1, const taglist_t* list2)
+{
+	size_t i;
+	for (i = 0; i < list1->count; i++)
+		if (Tag_Find(list2, list1->tags[i]))
+			return true;
+
+	return false;
+}
+
+/// Returns true if both lists are identical.
+boolean Tag_Compare (const taglist_t* list1, const taglist_t* list2)
+{
+	size_t i;
+
+	if (list1->count != list2->count)
+		return false;
+
+	for (i = 0; i < list1->count; i++)
+		if (list1->tags[i] != list2->tags[i])
+			return false;
+
+	return true;
+}
+
+/// Search for an element inside a global taggroup.
+size_t Taggroup_Find (const taggroup_t *group, const size_t id)
+{
+	size_t i;
+
+	if (!group)
+		return -1;
+
+	for (i = 0; i < group->count; i++)
+		if (group->elements[i] == id)
+			return i;
+
+	return -1;
+}
+
+/// Add an element to a global taggroup.
+void Taggroup_Add (taggroup_t *garray[], const mtag_t tag, size_t id)
+{
+	taggroup_t *group;
+	size_t i; // Insert position.
+
+	if (tag == MTAG_GLOBAL)
+		return;
+
+	group = garray[(UINT16)tag];
+
+	// Don't add duplicate entries.
+	if (Taggroup_Find(group, id) != (size_t)-1)
+		return;
+
+	// Create group if empty.
+	if (!group)
+	{
+		i = 0;
+		group = garray[(UINT16)tag] = Z_Calloc(sizeof(taggroup_t), PU_LEVEL, NULL);
+	}
+	else
+	{
+		// Keep the group element ids in an ascending order.
+		// Find the location to insert the element to.
+		for (i = 0; i < group->count; i++)
+			if (group->elements[i] > id)
+				break;
+
+		group->elements = Z_Realloc(group->elements, (group->count + 1) * sizeof(size_t), PU_LEVEL, NULL);
+
+		// Offset existing elements to make room for the new one.
+		if (i < group->count)
+			memmove(&group->elements[i + 1], &group->elements[i], group->count - i);
+	}
+
+	group->count++;
+	group->elements = Z_Realloc(group->elements, group->count * sizeof(size_t), PU_LEVEL, NULL);
+	group->elements[i] = id;
+}
+
+/// Remove an element from a global taggroup.
+void Taggroup_Remove (taggroup_t *garray[], const mtag_t tag, size_t id)
+{
+	taggroup_t *group;
+	size_t rempos;
+	size_t newcount;
+
+	if (tag == MTAG_GLOBAL)
+		return;
+
+	group = garray[(UINT16)tag];
+
+	if ((rempos = Taggroup_Find(group, id)) == (size_t)-1)
+		return;
+
+	// Strip away taggroup if no elements left.
+	if (!(newcount = --group->count))
+	{
+		Z_Free(group->elements);
+		Z_Free(group);
+		garray[(UINT16)tag] = NULL;
+	}
+	else
+	{
+		size_t *newelements = Z_Malloc(newcount * sizeof(size_t), PU_LEVEL, NULL);
+		size_t i;
+
+		// Copy the previous entries save for the one to remove.
+		for (i = 0; i < rempos; i++)
+			newelements[i] = group->elements[i];
+
+		for (i = rempos + 1; i < group->count; i++)
+			newelements[i - 1] = group->elements[i];
+
+		Z_Free(group->elements);
+		group->elements = newelements;
+		group->count = newcount;
+	}
+}
+
+// Initialization.
+
+static void Taglist_AddToSectors (const mtag_t tag, const size_t itemid)
+{
+	Taggroup_Add(tags_sectors, tag, itemid);
+}
+
+static void Taglist_AddToLines (const mtag_t tag, const size_t itemid)
+{
+	Taggroup_Add(tags_lines, tag, itemid);
+}
+
+static void Taglist_AddToMapthings (const mtag_t tag, const size_t itemid)
+{
+	Taggroup_Add(tags_mapthings, tag, itemid);
+}
+
+/// After all taglists have been built for each element (sectors, lines, things),
+/// the global taggroups, made for iteration, are built here.
+void Taglist_InitGlobalTables(void)
+{
+	size_t i, j;
+
+	for (i = 0; i < MAXTAGS; i++)
+	{
+		tags_sectors[i] = NULL;
+		tags_lines[i] = NULL;
+		tags_mapthings[i] = NULL;
+	}
+	for (i = 0; i < numsectors; i++)
+	{
+		for (j = 0; j < sectors[i].tags.count; j++)
+			Taglist_AddToSectors(sectors[i].tags.tags[j], i);
+	}
+	for (i = 0; i < numlines; i++)
+	{
+		for (j = 0; j < lines[i].tags.count; j++)
+			Taglist_AddToLines(lines[i].tags.tags[j], i);
+	}
+	for (i = 0; i < nummapthings; i++)
+	{
+		for (j = 0; j < mapthings[i].tags.count; j++)
+			Taglist_AddToMapthings(mapthings[i].tags.tags[j], i);
+	}
+}
+
+// Iteration, ingame search.
+
+INT32 Tag_Iterate_Sectors (const mtag_t tag, const size_t p)
+{
+	if (tag == MTAG_GLOBAL)
+	{
+		if (p < numsectors)
+			return p;
+		return -1;
+	}
+
+	if (tags_sectors[(UINT16)tag])
+	{
+		if (p < tags_sectors[(UINT16)tag]->count)
+			return tags_sectors[(UINT16)tag]->elements[p];
+		return -1;
+	}
+	return -1;
+}
+
+INT32 Tag_Iterate_Lines (const mtag_t tag, const size_t p)
+{
+	if (tag == MTAG_GLOBAL)
+	{
+		if (p < numlines)
+			return p;
+		return -1;
+	}
+
+	if (tags_lines[(UINT16)tag])
+	{
+		if (p < tags_lines[(UINT16)tag]->count)
+			return tags_lines[(UINT16)tag]->elements[p];
+		return -1;
+	}
+	return -1;
+}
+
+INT32 Tag_Iterate_Things (const mtag_t tag, const size_t p)
+{
+	if (tag == MTAG_GLOBAL)
+	{
+		if (p < nummapthings)
+			return p;
+		return -1;
+	}
+
+	if (tags_mapthings[(UINT16)tag])
+	{
+		if (p < tags_mapthings[(UINT16)tag]->count)
+			return tags_mapthings[(UINT16)tag]->elements[p];
+		return -1;
+	}
+	return -1;
+}
+
+INT32 Tag_FindLineSpecial(const INT16 special, const mtag_t tag)
+{
+	size_t i;
+
+	if (tag == MTAG_GLOBAL)
+	{
+		for (i = 0; i < numlines; i++)
+			if (lines[i].special == special)
+				return i;
+	}
+	else if (tags_lines[(UINT16)tag])
+	{
+		taggroup_t *tagged = tags_lines[(UINT16)tag];
+		for (i = 0; i < tagged->count; i++)
+			if (lines[tagged->elements[i]].special == special)
+				return tagged->elements[i];
+	}
+	return -1;
+}
+
+/// Backwards compatibility iteration function for Lua scripts.
+INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start)
+{
+	if (tag == -1)
+	{
+		start++;
+
+		if (start >= (INT32)numlines)
+			return -1;
+
+		while (start < (INT32)numlines && lines[start].special != special)
+			start++;
+
+		return start;
+	}
+	else
+	{
+		size_t p = 0;
+		INT32 id;
+
+		// For backwards compatibility's sake, simulate the old linked taglist behavior:
+		// Iterate through the taglist and find the "start" line's position in the list,
+		// And start checking with the next one (if it exists).
+		if (start != -1)
+		{
+			for (; (id = Tag_Iterate_Lines(tag, p)) >= 0; p++)
+				if (id == start)
+				{
+					p++;
+					break;
+				}
+		}
+
+		for (; (id = Tag_Iterate_Lines(tag, p)) >= 0; p++)
+			if (lines[id].special == special)
+				return id;
+
+		return -1;
+	}
+}
+
+
+// Ingame list manipulation.
+
+/// Changes the first tag for a given sector, and updates the global taggroups.
+void Tag_SectorFSet (const size_t id, const mtag_t tag)
+{
+	sector_t* sec = &sectors[id];
+	mtag_t curtag = Tag_FGet(&sec->tags);
+	if (curtag == tag)
+		return;
+
+	Taggroup_Remove(tags_sectors, curtag, id);
+	Taggroup_Add(tags_sectors, tag, id);
+	Tag_FSet(&sec->tags, tag);
+}
diff --git a/src/taglist.h b/src/taglist.h
new file mode 100644
index 0000000000000000000000000000000000000000..0e6d9f8422bbc150dbbabe3c6048d6e66c448f05
--- /dev/null
+++ b/src/taglist.h
@@ -0,0 +1,127 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1998-2000 by DooM Legacy Team.
+// Copyright (C) 1999-2020 by Sonic Team Junior.
+// Copyright (C)      2020 by Nev3r.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  taglist.h
+/// \brief Tag iteration and reading functions and macros' declarations.
+
+#ifndef __R_TAGLIST__
+#define __R_TAGLIST__
+
+#include "doomtype.h"
+
+typedef INT16 mtag_t;
+#define MAXTAGS UINT16_MAX
+#define MTAG_GLOBAL -1
+
+/// Multitag list. Each taggable element will have its own taglist.
+typedef struct
+{
+	mtag_t* tags;
+	UINT16 count;
+} taglist_t;
+
+void Tag_Add (taglist_t* list, const mtag_t tag);
+void Tag_FSet (taglist_t* list, const mtag_t tag);
+mtag_t Tag_FGet (const taglist_t* list);
+boolean Tag_Find (const taglist_t* list, const mtag_t tag);
+boolean Tag_Share (const taglist_t* list1, const taglist_t* list2);
+boolean Tag_Compare (const taglist_t* list1, const taglist_t* list2);
+
+void Tag_SectorFSet (const size_t id, const mtag_t tag);
+
+/// Taggroup list. It is essentially just an element id list.
+typedef struct
+{
+	size_t *elements;
+	size_t count;
+} taggroup_t;
+
+extern taggroup_t* tags_sectors[];
+extern taggroup_t* tags_lines[];
+extern taggroup_t* tags_mapthings[];
+
+void Taggroup_Add (taggroup_t *garray[], const mtag_t tag, size_t id);
+void Taggroup_Remove (taggroup_t *garray[], const mtag_t tag, size_t id);
+size_t Taggroup_Find (const taggroup_t *group, const size_t id);
+
+void Taglist_InitGlobalTables(void);
+
+INT32 Tag_Iterate_Sectors (const mtag_t tag, const size_t p);
+INT32 Tag_Iterate_Lines (const mtag_t tag, const size_t p);
+INT32 Tag_Iterate_Things (const mtag_t tag, const size_t p);
+
+INT32 Tag_FindLineSpecial(const INT16 special, const mtag_t tag);
+INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start);
+
+// Use this macro to declare an iterator position variable.
+#define TAG_ITER_DECLARECOUNTER(level) size_t ICNT_##level
+
+#define TAG_ITER(level, fn, tag, return_varname) for(ICNT_##level = 0; (return_varname = fn(tag, ICNT_##level)) >= 0; ICNT_##level++)
+
+// Use these macros as wrappers for a taglist iteration.
+#define TAG_ITER_SECTORS(level, tag, return_varname) TAG_ITER(level, Tag_Iterate_Sectors, tag, return_varname)
+#define TAG_ITER_LINES(level, tag, return_varname)   TAG_ITER(level, Tag_Iterate_Lines, tag, return_varname)
+#define TAG_ITER_THINGS(level, tag, return_varname)  TAG_ITER(level, Tag_Iterate_Things, tag, return_varname)
+
+/* ITERATION MACROS
+TAG_ITER_DECLARECOUNTER must be used before using the iterators.
+
+'level':
+For each nested iteration, an additional TAG_ITER_DECLARECOUNTER
+must be used with a different level number to avoid conflict with
+the outer iterations.
+Most cases don't have nested iterations and thus the level is just 0.
+
+'tag':
+Pretty much the elements' tag to iterate through.
+
+'return_varname':
+Target variable's name to return the iteration results to.
+
+
+EXAMPLE:
+{
+	TAG_ITER_DECLARECOUNTER(0);
+	TAG_ITER_DECLARECOUNTER(1); // For the nested iteration.
+
+	size_t li;
+	size_t sec;
+
+	INT32 tag1 = 4;
+
+	...
+
+	TAG_ITER_LINES(0, tag1, li)
+	{
+		line_t *line = lines + li;
+
+		...
+
+		if (something)
+		{
+			mtag_t tag2 = 8;
+
+			// Nested iteration; just make sure the level is higher
+			// and that it has its own counter declared in scope.
+			TAG_ITER_SECTORS(1, tag2, sec)
+			{
+				sector_t *sector = sectors + sec;
+
+				...
+			}
+		}
+	}
+}
+
+Notes:
+If no elements are found for a given tag, the loop inside won't be executed.
+*/
+
+#endif //__R_TAGLIST__
diff --git a/src/tmap.nas b/src/tmap.nas
index 106f38e962b03fef0f0edc9e93dbd71112449ced..69282d0b471dd2c86802df544f4a346e4b96baa9 100644
--- a/src/tmap.nas
+++ b/src/tmap.nas
@@ -763,8 +763,8 @@ TX2             EQU    16
 TY2             EQU    20
 RASTERY_SIZEOF  EQU    24
 
-cglobal rasterize_segment_tex
-rasterize_segment_tex:
+cglobal rasterize_segment_tex_asm
+rasterize_segment_tex_asm:
         push    ebp
         mov     ebp,esp
 
diff --git a/src/v_video.c b/src/v_video.c
index 9c91261de6eb3247945e33eec33384145e99ea6a..522883475d255790e61fc457a4076d0a41efeea7 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -529,7 +529,7 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
 	//if (rendermode != render_soft && !con_startup)		// Why?
 	if (rendermode == render_opengl)
 	{
-		HWR_DrawStretchyFixedPatch((GLPatch_t *)patch, x, y, pscale, vscale, scrn, colormap);
+		HWR_DrawStretchyFixedPatch(patch, x, y, pscale, vscale, scrn, colormap);
 		return;
 	}
 #endif
@@ -551,7 +551,7 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
 
 		if (alphalevel)
 		{
-			v_translevel = transtables + ((alphalevel-1)<<FF_TRANSSHIFT);
+			v_translevel = R_GetTranslucencyTable(alphalevel);
 			patchdrawfunc = translucentpdraw;
 		}
 	}
@@ -714,7 +714,7 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
 			// if it's meant to cover the whole screen, black out the rest (ONLY IF TOP LEFT ISN'T TRANSPARENT)
 			if (x == 0 && SHORT(patch->width) == BASEVIDWIDTH && y == 0 && SHORT(patch->height) == BASEVIDHEIGHT)
 			{
-				column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[0]));
+				column = (const column_t *)((const UINT8 *)(patch->columns) + (patch->columnofs[0]));
 				if (!column->topdelta)
 				{
 					source = (const UINT8 *)(column) + 3;
@@ -782,7 +782,7 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
 			if (x+offx >= vid.width) // don't draw off the right of the screen (WRAP PREVENTION)
 				break;
 		}
-		column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[col>>FRACBITS]));
+		column = (const column_t *)((const UINT8 *)(patch->columns) + (patch->columnofs[col>>FRACBITS]));
 
 		while (column->topdelta != 0xff)
 		{
@@ -829,7 +829,7 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 	//if (rendermode != render_soft && !con_startup)		// Not this again
 	if (rendermode == render_opengl)
 	{
-		HWR_DrawCroppedPatch((GLPatch_t*)patch,x,y,pscale,scrn,sx,sy,w,h);
+		HWR_DrawCroppedPatch(patch,x,y,pscale,scrn,sx,sy,w,h);
 		return;
 	}
 #endif
@@ -851,7 +851,7 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 
 		if (alphalevel)
 		{
-			v_translevel = transtables + ((alphalevel-1)<<FF_TRANSSHIFT);
+			v_translevel = R_GetTranslucencyTable(alphalevel);
 			patchdrawfunc = translucentpdraw;
 		}
 	}
@@ -1005,7 +1005,7 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_
 			continue;
 		if (x >= vid.width) // don't draw off the right of the screen (WRAP PREVENTION)
 			break;
-		column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[col>>FRACBITS]));
+		column = (const column_t *)((const UINT8 *)(patch->columns) + (patch->columnofs[col>>FRACBITS]));
 
 		while (column->topdelta != 0xff)
 		{
@@ -1500,7 +1500,7 @@ void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 	// Jimita (12-04-2018)
 	if (alphalevel)
 	{
-		fadetable = ((UINT8 *)transtables + ((alphalevel-1)<<FF_TRANSSHIFT) + (c*256));
+		fadetable = R_GetTranslucencyTable(alphalevel) + (c*256);
 		for (;(--h >= 0) && dest < deststop; dest += vid.width)
 		{
 			u = 0;
@@ -1683,7 +1683,7 @@ void V_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c, UINT16 color, U
 
 	fadetable = ((color & 0xFF00) // Color is not palette index?
 		? ((UINT8 *)colormaps + strength*256) // Do COLORMAP fade.
-		: ((UINT8 *)transtables + ((9-strength)<<FF_TRANSSHIFT) + color*256)); // Else, do TRANSMAP** fade.
+		: ((UINT8 *)R_GetTranslucencyTable((9-strength)+1) + color*256)); // Else, do TRANSMAP** fade.
 	for (;(--h >= 0) && dest < deststop; dest += vid.width)
 	{
 		u = 0;
@@ -1829,7 +1829,7 @@ void V_DrawFadeScreen(UINT16 color, UINT8 strength)
 		? ((UINT8 *)(((color & 0x0F00) == 0x0A00) ? fadecolormap // Do fadecolormap fade.
 		: (((color & 0x0F00) == 0x0B00) ? fadecolormap + (256 * FADECOLORMAPROWS) // Do white fadecolormap fade.
 		: colormaps)) + strength*256) // Do COLORMAP fade.
-		: ((UINT8 *)transtables + ((9-strength)<<FF_TRANSSHIFT) + color*256)); // Else, do TRANSMAP** fade.
+		: ((UINT8 *)R_GetTranslucencyTable((9-strength)+1) + color*256)); // Else, do TRANSMAP** fade.
 		const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
 		UINT8 *buf = screens[0];
 
@@ -3565,7 +3565,7 @@ void V_DoPostProcessor(INT32 view, postimg_t type, INT32 param)
 		angle_t disStart = (leveltime * 128) & FINEMASK; // in 0 to FINEANGLE
 		INT32 newpix;
 		INT32 sine;
-		//UINT8 *transme = transtables + ((tr_trans50-1)<<FF_TRANSSHIFT);
+		//UINT8 *transme = R_GetTranslucencyTable(tr_trans50);
 
 		for (y = yoffset; y < yoffset+height; y++)
 		{
@@ -3622,7 +3622,7 @@ Unoptimized version
 		INT32 x, y;
 
 		// TODO: Add a postimg_param so that we can pick the translucency level...
-		UINT8 *transme = transtables + ((param-1)<<FF_TRANSSHIFT);
+		UINT8 *transme = R_GetTranslucencyTable(param);
 
 		for (y = yoffset; y < yoffset+height; y++)
 		{
@@ -3770,3 +3770,36 @@ void V_Init(void)
 		CONS_Debug(DBG_RENDER, " screens[%d] = %x\n", i, screens[i]);
 #endif
 }
+
+void V_Recalc(void)
+{
+	// scale 1,2,3 times in x and y the patches for the menus and overlays...
+	// calculated once and for all, used by routines in v_video.c and v_draw.c
+	vid.dupx = vid.width / BASEVIDWIDTH;
+	vid.dupy = vid.height / BASEVIDHEIGHT;
+	vid.dupx = vid.dupy = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
+	vid.fdupx = FixedDiv(vid.width*FRACUNIT, BASEVIDWIDTH*FRACUNIT);
+	vid.fdupy = FixedDiv(vid.height*FRACUNIT, BASEVIDHEIGHT*FRACUNIT);
+
+#ifdef HWRENDER
+	//if (rendermode != render_opengl && rendermode != render_none) // This was just placing it incorrectly at non aspect correct resolutions in opengl
+	// 13/11/18:
+	// The above is no longer necessary, since we want OpenGL to be just like software now
+	// -- Monster Iestyn
+#endif
+		vid.fdupx = vid.fdupy = (vid.fdupx < vid.fdupy ? vid.fdupx : vid.fdupy);
+
+	vid.meddupx = (UINT8)(vid.dupx >> 1) + 1;
+	vid.meddupy = (UINT8)(vid.dupy >> 1) + 1;
+#ifdef HWRENDER
+	vid.fmeddupx = vid.meddupx*FRACUNIT;
+	vid.fmeddupy = vid.meddupy*FRACUNIT;
+#endif
+
+	vid.smalldupx = (UINT8)(vid.dupx / 3) + 1;
+	vid.smalldupy = (UINT8)(vid.dupy / 3) + 1;
+#ifdef HWRENDER
+	vid.fsmalldupx = vid.smalldupx*FRACUNIT;
+	vid.fsmalldupy = vid.smalldupy*FRACUNIT;
+#endif
+}
diff --git a/src/v_video.h b/src/v_video.h
index 2af4fe29313055edab8478dfcb912ca28a8435aa..8a18f82ad7ab834988e672e3f5c21189764876b6 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -36,6 +36,9 @@ cv_rsaturation, cv_ysaturation, cv_gsaturation, cv_csaturation, cv_bsaturation,
 // Allocates buffer screens, call before R_Init.
 void V_Init(void);
 
+// Recalculates the viddef (dupx, dupy, etc.) according to the current screen resolution.
+void V_Recalc(void);
+
 // Color look-up table
 #define CLUTINDEX(r, g, b) (((r) >> 3) << 11) | (((g) >> 2) << 5) | ((b) >> 3)
 
diff --git a/src/w_wad.c b/src/w_wad.c
index fd70f8ec33f1fe4e95cbf4fb9f9b3a5b6dc2be64..aca530fa518c7134636349b769f6760590cc79e3 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -57,6 +57,7 @@
 #include "r_defs.h"
 #include "r_data.h"
 #include "r_textures.h"
+#include "r_patch.h"
 #include "r_picformats.h"
 #include "i_system.h"
 #include "md5.h"
@@ -833,11 +834,6 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
 	Z_Calloc(numlumps * sizeof (*wadfile->lumpcache), PU_STATIC, &wadfile->lumpcache);
 	Z_Calloc(numlumps * sizeof (*wadfile->patchcache), PU_STATIC, &wadfile->patchcache);
 
-#ifdef HWRENDER
-	// allocates GLPatch info structures and store them in a tree
-	wadfile->hwrcache = M_AATreeAlloc(AATREE_ZUSER);
-#endif
-
 	//
 	// add the wadfile
 	//
@@ -847,7 +843,7 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
 
 #ifdef HWRENDER
 	// Read shaders from file
-	if (rendermode == render_opengl && (vid_opengl_state == 1))
+	if (rendermode == render_opengl && (vid.glstate == VID_GL_LIBRARY_LOADED))
 	{
 		HWR_LoadCustomShadersFromFile(numwadfiles - 1, (type == RET_PK3));
 		HWR_CompileShaders();
@@ -1670,13 +1666,7 @@ void *W_CacheSoftwarePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
 	if (!lumpcache[lump])
 	{
 		size_t len = W_LumpLengthPwad(wad, lump);
-		void *ptr, *lumpdata;
-#ifndef NO_PNG_LUMPS
-		void *srcdata = NULL;
-#endif
-
-		ptr = Z_Malloc(len, tag, &lumpcache[lump]);
-		lumpdata = Z_Malloc(len, tag, NULL);
+		void *ptr, *dest, *lumpdata = Z_Malloc(len, PU_STATIC, NULL);
 
 		// read the lump in full
 		W_ReadLumpHeaderPwad(wad, lump, lumpdata, 0, 0);
@@ -1686,14 +1676,25 @@ void *W_CacheSoftwarePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
 		if (Picture_IsLumpPNG((UINT8 *)lumpdata, len))
 		{
 			size_t newlen;
-			srcdata = Picture_PNGConvert((UINT8 *)lumpdata, PICFMT_PATCH, NULL, NULL, NULL, NULL, len, &newlen, 0);
-			ptr = Z_Realloc(ptr, newlen, tag, &lumpcache[lump]);
-			M_Memcpy(ptr, srcdata, newlen);
-			Z_Free(srcdata);
+			void *converted = Picture_PNGConvert((UINT8 *)lumpdata, PICFMT_DOOMPATCH, NULL, NULL, NULL, NULL, len, &newlen, 0);
+			ptr = Z_Malloc(newlen, PU_STATIC, NULL);
+			M_Memcpy(ptr, converted, newlen);
+			Z_Free(converted);
+			len = newlen;
 		}
 		else // just copy it into the patch cache
 #endif
+		{
+			ptr = Z_Malloc(len, PU_STATIC, NULL);
 			M_Memcpy(ptr, lumpdata, len);
+		}
+
+		Z_Free(lumpdata);
+
+		dest = Z_Calloc(sizeof(patch_t), tag, &lumpcache[lump]);
+		Patch_Create(ptr, len, dest);
+
+		Z_Free(ptr);
 	}
 	else
 		Z_ChangeTag(lumpcache[lump], tag);
@@ -1708,45 +1709,22 @@ void *W_CacheSoftwarePatchNum(lumpnum_t lumpnum, INT32 tag)
 
 void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
 {
-#ifdef HWRENDER
-	GLPatch_t *grPatch;
-#endif
+	patch_t *patch;
 
 	if (!TestValidLump(wad, lump))
 		return NULL;
 
+	patch = W_CacheSoftwarePatchNumPwad(wad, lump, tag);
+
 #ifdef HWRENDER
 	// Software-only compile cache the data without conversion
 	if (rendermode == render_soft || rendermode == render_none)
 #endif
-	{
-		return W_CacheSoftwarePatchNumPwad(wad, lump, tag);
-	}
-#ifdef HWRENDER
-
-	grPatch = HWR_GetCachedGLPatchPwad(wad, lump);
-
-	if (grPatch->mipmap->data)
-	{
-		if (tag == PU_CACHE)
-			tag = PU_HWRCACHE;
-		Z_ChangeTag(grPatch->mipmap->data, tag);
-	}
-	else
-	{
-		patch_t *ptr = NULL;
-
-		// Only load the patch if we haven't initialised the grPatch yet
-		if (grPatch->mipmap->width == 0)
-			ptr = W_CacheLumpNumPwad(grPatch->wadnum, grPatch->lumpnum, PU_STATIC);
+		return (void *)patch;
 
-		// Run HWR_MakePatch in all cases, to recalculate some things
-		HWR_MakePatch(ptr, grPatch, grPatch->mipmap, false);
-		Z_Free(ptr);
-	}
-
-	// return GLPatch_t, which can be casted to (patch_t) with valid patch header info
-	return (void *)grPatch;
+#ifdef HWRENDER
+	Patch_CreateGL(patch);
+	return (void *)patch;
 #endif
 }
 
@@ -1761,7 +1739,7 @@ void W_UnlockCachedPatch(void *patch)
 	// have different lifetimes from software's.
 #ifdef HWRENDER
 	if (rendermode == render_opengl)
-		HWR_UnlockCachedPatch((GLPatch_t*)patch);
+		HWR_UnlockCachedPatch((GLPatch_t *)((patch_t *)patch)->hardware);
 	else
 #endif
 		Z_Unlock(patch);
@@ -2074,14 +2052,59 @@ int W_VerifyNMUSlumps(const char *filename)
 		{"CLM", 3}, // Colormap changes
 		{"TRANS", 5}, // Translucency map changes
 
+		{"CONSBACK", 8}, // Console Background graphic
+
+		{"SAVE", 4}, // Save Select graphics here and below
+		{"BLACXLVL", 8},
+		{"GAMEDONE", 8},
+		{"CONT", 4}, // Continue icons on saves (probably not used anymore)
+		{"STNONEX", 7}, // "X" graphic
+		{"ULTIMATE", 8}, // Ultimate no-save
+
+		{"CRFNT", 5}, // Sonic 1 font changes
+		{"NTFNT", 5}, // Character Select font changes
+		{"NTFNO", 5}, // Character Select font (outline)
 		{"LTFNT", 5}, // Level title font changes
 		{"TTL", 3}, // Act number changes
 		{"STCFN", 5}, // Console font changes
 		{"TNYFN", 5}, // Tiny console font changes
+
+		{"STLIVE", 6}, // Life graphics, background and the "X" that shows under skin's HUDNAME
+		{"CROSHAI", 7}, // First person crosshairs
+		{"INTERSC", 7}, // Default intermission backgrounds (co-op)
 		{"STT", 3}, // Acceptable HUD changes (Score Time Rings)
 		{"YB_", 3}, // Intermission graphics, goes with the above
-		{"M_", 2}, // As does menu stuff
+		{"RESULT", 6}, // Used in intermission for competitive modes, above too :3
+		{"RACE", 4}, // Race mode graphics, 321go
+		{"M_", 2}, // Menu stuff
+		{"LT", 2}, // Titlecard changes
+
+		{"SLID", 4}, // Continue
+		{"CONT", 4}, 
+
+		{"MINICAPS", 8}, // NiGHTS graphics here and below
+		{"BLUESTAT", 8}, // Sphere status
+		{"BYELSTAT", 8},
+		{"ORNGSTAT", 8},
+		{"REDSTAT", 7},
+		{"YELSTAT", 7},
+		{"NBRACKET", 8},
+		{"NGHTLINK", 8},
+		{"NGT", 3}, // Link numbers
+		{"NARROW", 6},
+		{"NREDAR", 6},
+		{"NSS", 3},
+		{"NBON", 4},
+		{"NRNG", 4},
+		{"NHUD", 4},
+		{"CAPS", 4},
+		{"DRILL", 5},
+		{"GRADE", 5},
+		{"MINUS5", 6},
+
 		{"MUSICDEF", 8}, // Song definitions (thanks kart)
+		{"SHADERS", 7}, // OpenGL shader definitions
+		{"SH_", 3}, // GLSL shader
 
 		{NULL, 0},
 	};
diff --git a/src/w_wad.h b/src/w_wad.h
index 41232cba1c5f5e006c33ed8496462c98a1ccf2f4..1e86eea5a6b2ac991d26d47b98cf3416f4de5b2b 100644
--- a/src/w_wad.h
+++ b/src/w_wad.h
@@ -102,10 +102,6 @@ virtlump_t* vres_Find(const virtres_t*, const char*);
 
 #define lumpcache_t void *
 
-#ifdef HWRENDER
-#include "m_aatree.h"
-#endif
-
 // Resource type of the WAD. Yeah, I know this sounds dumb, but I'll leave it like this until I clean up the code further.
 typedef enum restype
 {
@@ -123,9 +119,6 @@ typedef struct wadfile_s
 	lumpinfo_t *lumpinfo;
 	lumpcache_t *lumpcache;
 	lumpcache_t *patchcache;
-#ifdef HWRENDER
-	aatree_t *hwrcache; // patches are cached in renderer's native format
-#endif
 	UINT16 numlumps; // this wad's number of resources
 	FILE *handle;
 	UINT32 filesize; // for network
diff --git a/src/win32/Srb2win-vc10.vcxproj b/src/win32/Srb2win-vc10.vcxproj
index 52617037b210f0eb913daf9a4e7eab6d1bee973f..3e8af3b0ed866b9039879abbc4b4e28b1d623ba4 100644
--- a/src/win32/Srb2win-vc10.vcxproj
+++ b/src/win32/Srb2win-vc10.vcxproj
@@ -298,6 +298,8 @@
       <ExcludedFromBuild>true</ExcludedFromBuild>
     </ClCompile>
     <ClCompile Include="..\r_main.c" />
+    <ClCompile Include="..\r_patch.c" />
+    <ClCompile Include="..\r_patchrotation.c" />
     <ClCompile Include="..\r_picformats.c" />
     <ClCompile Include="..\r_plane.c" />
     <ClCompile Include="..\r_portal.c" />
@@ -454,6 +456,8 @@
     <ClInclude Include="..\r_draw.h" />
     <ClInclude Include="..\r_local.h" />
     <ClInclude Include="..\r_main.h" />
+    <ClInclude Include="..\r_patch.h" />
+    <ClInclude Include="..\r_patchrotation.h" />
     <ClInclude Include="..\r_picformats.h" />
     <ClInclude Include="..\r_plane.h" />
     <ClInclude Include="..\r_portal.h" />
diff --git a/src/win32/Srb2win-vc10.vcxproj.filters b/src/win32/Srb2win-vc10.vcxproj.filters
index 0689a4ac0893e10c660a72ed32c9bb0c3c054c82..7279368f1423f4a02e962395e8d4b451a30e44cd 100644
--- a/src/win32/Srb2win-vc10.vcxproj.filters
+++ b/src/win32/Srb2win-vc10.vcxproj.filters
@@ -469,9 +469,18 @@
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
     <ClCompile Include="..\apng.c" />
+    <ClCompile Include="..\r_patch.c">
+      <Filter>R_Rend</Filter>
+    </ClCompile>
+    <ClCompile Include="..\r_patchrotation.c">
+      <Filter>R_Rend</Filter>
+    </ClCompile>
     <ClCompile Include="..\r_picformats.c">
       <Filter>R_Rend</Filter>
     </ClCompile>
+    <ClCompile Include="..\r_textures.c">
+      <Filter>R_Rend</Filter>
+    </ClCompile>
     <ClCompile Include="..\r_portal.c">
       <Filter>R_Rend</Filter>
     </ClCompile>
@@ -886,12 +895,18 @@
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
     <ClInclude Include="..\apng.h" />
-    <ClInclude Include="..\r_textures.h">
+    <ClInclude Include="..\r_patch.h">
+      <Filter>R_Rend</Filter>
+    </ClInclude>
+    <ClInclude Include="..\r_patchrotation.h">
       <Filter>R_Rend</Filter>
     </ClInclude>
     <ClInclude Include="..\r_picformats.h">
       <Filter>R_Rend</Filter>
     </ClInclude>
+    <ClInclude Include="..\r_textures.h">
+      <Filter>R_Rend</Filter>
+    </ClInclude>
     <ClInclude Include="..\r_portal.h">
       <Filter>R_Rend</Filter>
     </ClInclude>
diff --git a/src/win32/win_dll.c b/src/win32/win_dll.c
index fa648a89c77d2f889799b5b31ebbba7507c1cf0b..4743cec34b2e6af738caeec60d7c179e58ec14d1 100644
--- a/src/win32/win_dll.c
+++ b/src/win32/win_dll.c
@@ -107,6 +107,7 @@ static loadfunc_t hwdFuncTable[] = {
 	{"ClearBuffer@12",      &hwdriver.pfnClearBuffer},
 	{"SetTexture@4",        &hwdriver.pfnSetTexture},
 	{"UpdateTexture@4",     &hwdriver.pfnUpdateTexture},
+	{"DeleteTexture@4",     &hwdriver.pfnDeleteTexture},
 	{"ReadRect@24",         &hwdriver.pfnReadRect},
 	{"GClipRect@20",        &hwdriver.pfnGClipRect},
 	{"ClearMipMapCache@0",  &hwdriver.pfnClearMipMapCache},
@@ -139,6 +140,7 @@ static loadfunc_t hwdFuncTable[] = {
 	{"ClearBuffer",         &hwdriver.pfnClearBuffer},
 	{"SetTexture",          &hwdriver.pfnSetTexture},
 	{"UpdateTexture",       &hwdriver.pfnUpdateTexture},
+	{"DeleteTexture",       &hwdriver.pfnDeleteTexture},
 	{"ReadRect",            &hwdriver.pfnReadRect},
 	{"GClipRect",           &hwdriver.pfnGClipRect},
 	{"ClearMipMapCache",    &hwdriver.pfnClearMipMapCache},
diff --git a/src/win32/win_vid.c b/src/win32/win_vid.c
index 931e006eb35416dc251e5fbf7bd2ade432154f5c..7a33e19311f876fe595b72f7552fcf55f0af23fd 100644
--- a/src/win32/win_vid.c
+++ b/src/win32/win_vid.c
@@ -48,6 +48,7 @@
 
 // this is the CURRENT rendermode!! very important: used by w_wad, and much other code
 rendermode_t rendermode = render_soft;
+rendermode_t chosenrendermode = render_none; // set by command line arguments
 static void OnTop_OnChange(void);
 // synchronize page flipping with screen refresh
 static CV_PossibleValue_t CV_NeverOnOff[] = {{-1, "Never"}, {0, "Off"}, {1, "On"}, {0, NULL}};
@@ -56,7 +57,6 @@ static consvar_t cv_stretch = CVAR_INIT ("stretch", "On", CV_SAVE|CV_NOSHOWHELP,
 static consvar_t cv_ontop = CVAR_INIT ("ontop", "Never", 0, CV_NeverOnOff, NULL);
 
 boolean highcolor;
-int vid_opengl_state = 0;
 
 static BOOL bDIBMode; // means we are using DIB instead of DirectDraw surfaces
 static LPBITMAPINFO bmiMain = NULL;
@@ -952,7 +952,11 @@ INT32 VID_SetMode(INT32 modenum)
 	return 1;
 }
 
-void VID_CheckRenderer(void) {}
+boolean VID_CheckRenderer(void)
+{
+	return false;
+}
+
 void VID_CheckGLLoaded(rendermode_t oldrender)
 {
 	(void)oldrender;
diff --git a/src/y_inter.c b/src/y_inter.c
index d263bf6357c186e30c4d44612ec16c27ddb2d03c..acdf5f8d74c478d81348710e28669e474601634c 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -152,7 +152,6 @@ typedef struct
 
 boolean usebuffer = false;
 static boolean useinterpic;
-static boolean safetorender = true;
 static y_buffer_t *y_buffer;
 
 static INT32 intertic;
@@ -169,7 +168,6 @@ static void Y_CalculateCompetitionWinners(void);
 static void Y_CalculateTimeRaceWinners(void);
 static void Y_CalculateMatchWinners(void);
 static void Y_UnloadData(void);
-static void Y_CleanupData(void);
 
 // Stuff copy+pasted from st_stuff.c
 #define ST_DrawNumFromHud(h,n)        V_DrawTallNum(hudinfo[h].x, hudinfo[h].y, hudinfo[h].f, n)
@@ -316,19 +314,6 @@ void Y_IntermissionDrawer(void)
 	if (intertype == int_none || rendermode == render_none)
 		return;
 
-	// Lactozilla: Renderer switching
-	if (needpatchrecache)
-	{
-		Y_CleanupData();
-		safetorender = false;
-	}
-
-	if (!safetorender)
-		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
-
-	if (!safetorender)
-		goto dontdrawbg;
-
 	if (useinterpic)
 		V_DrawScaledPatch(0, 0, 0, interpic);
 	else if (!usetile)
@@ -366,7 +351,6 @@ void Y_IntermissionDrawer(void)
 	if (!LUA_HudEnabled(hud_intermissiontally))
 		goto skiptallydrawer;
 
-dontdrawbg:
 	if (intertype == int_coop)
 	{
 		INT32 bonusy;
@@ -416,17 +400,14 @@ dontdrawbg:
 
 		bonusy = 150;
 		// Total
-		if (safetorender)
-		{
-			V_DrawScaledPatch(152, bonusy, 0, data.coop.ptotal);
-			V_DrawTallNum(BASEVIDWIDTH - 68, bonusy + 1, 0, data.coop.total);
-		}
+		V_DrawScaledPatch(152, bonusy, 0, data.coop.ptotal);
+		V_DrawTallNum(BASEVIDWIDTH - 68, bonusy + 1, 0, data.coop.total);
 		bonusy -= (3*SHORT(tallnum[0]->height)/2) + 1;
 
 		// Draw bonuses
 		for (i = 3; i >= 0; --i)
 		{
-			if (data.coop.bonuses[i].display && safetorender)
+			if (data.coop.bonuses[i].display)
 			{
 				V_DrawScaledPatch(152, bonusy, 0, data.coop.bonuspatches[i]);
 				V_DrawTallNum(BASEVIDWIDTH - 68, bonusy + 1, 0, data.coop.bonuses[i].points);
@@ -655,8 +636,7 @@ dontdrawbg:
 		char strtime[10];
 
 		// draw the header
-		if (safetorender)
-			V_DrawScaledPatch(112, 2, 0, data.match.result);
+		V_DrawScaledPatch(112, 2, 0, data.match.result);
 
 		// draw the level name
 		V_DrawCenteredString(BASEVIDWIDTH/2, 20, 0, data.match.levelstring);
@@ -1212,8 +1192,6 @@ void Y_StartIntermission(void)
 		I_Error("endtic is dirty");
 #endif
 
-	safetorender = true;
-
 	if (!multiplayer)
 	{
 		timer = 0;
@@ -2060,19 +2038,13 @@ void Y_EndIntermission(void)
 	usebuffer = false;
 }
 
-#define UNLOAD(x) if (x) {Z_ChangeTag(x, PU_CACHE);} x = NULL;
-#define CLEANUP(x) x = NULL;
+#define UNLOAD(x) if (x) {Patch_Free(x);} x = NULL;
 
 //
 // Y_UnloadData
 //
 static void Y_UnloadData(void)
 {
-	// In hardware mode, don't Z_ChangeTag a pointer returned by W_CachePatchName().
-	// It doesn't work and is unnecessary.
-	if (rendermode != render_soft)
-		return;
-
 	// unload the background patches
 	UNLOAD(bgpatch);
 	UNLOAD(bgtile);
@@ -2112,45 +2084,3 @@ static void Y_UnloadData(void)
 			break;
 	}
 }
-
-static void Y_CleanupData(void)
-{
-	// unload the background patches
-	CLEANUP(bgpatch);
-	CLEANUP(bgtile);
-	CLEANUP(interpic);
-
-	switch (intertype)
-	{
-		case int_coop:
-			// unload the coop and single player patches
-			CLEANUP(data.coop.bonuspatches[3]);
-			CLEANUP(data.coop.bonuspatches[2]);
-			CLEANUP(data.coop.bonuspatches[1]);
-			CLEANUP(data.coop.bonuspatches[0]);
-			CLEANUP(data.coop.ptotal);
-			break;
-		case int_spec:
-			// unload the special stage patches
-			//CLEANUP(data.spec.cemerald);
-			//CLEANUP(data.spec.nowsuper);
-			CLEANUP(data.spec.bonuspatches[1]);
-			CLEANUP(data.spec.bonuspatches[0]);
-			CLEANUP(data.spec.pscore);
-			CLEANUP(data.spec.pcontinues);
-			break;
-		case int_match:
-		case int_race:
-			CLEANUP(data.match.result);
-			break;
-		case int_ctf:
-			CLEANUP(data.match.blueflag);
-			CLEANUP(data.match.redflag);
-			break;
-		default:
-			//without this default,
-			//int_none, int_tag, int_chaos, and int_classicrace
-			//are not handled
-			break;
-	}
-}
diff --git a/src/z_zone.c b/src/z_zone.c
index 2c7384c3da22fead194f365dc8fea0a4768949b9..ad64a3a07f04f01d40ac291cfa4e77f26bd37e88 100644
--- a/src/z_zone.c
+++ b/src/z_zone.c
@@ -27,6 +27,7 @@
 
 #include "doomdef.h"
 #include "doomstat.h"
+#include "r_patch.h"
 #include "r_picformats.h"
 #include "i_system.h" // I_GetFreeMem
 #include "i_video.h" // rendermode
@@ -495,36 +496,37 @@ void Z_FreeTags(INT32 lowtag, INT32 hightag)
 	}
 }
 
-// -----------------
-// Utility functions
-// -----------------
+/** Iterates through all memory for a given set of tags.
+  *
+  * \param lowtag The lowest tag to consider.
+  * \param hightag The highest tag to consider.
+  * \param iterfunc The iterator function.
+  */
+void Z_IterateTags(INT32 lowtag, INT32 hightag, boolean (*iterfunc)(void *))
+{
+	memblock_t *block, *next;
 
-// for renderer switching
-boolean needpatchflush = false;
-boolean needpatchrecache = false;
+	if (!iterfunc)
+		I_Error("Z_IterateTags: no iterator function was given");
 
-// flush all patches from memory
-void Z_FlushCachedPatches(void)
-{
-	CONS_Debug(DBG_RENDER, "Z_FlushCachedPatches()...\n");
-	Z_FreeTag(PU_PATCH);
-	Z_FreeTag(PU_HUDGFX);
-	Z_FreeTag(PU_HWRPATCHINFO);
-	Z_FreeTag(PU_HWRMODELTEXTURE);
-	Z_FreeTag(PU_HWRCACHE);
-	Z_FreeTag(PU_HWRCACHE_UNLOCKED);
-	Z_FreeTag(PU_HWRPATCHINFO_UNLOCKED);
-	Z_FreeTag(PU_HWRMODELTEXTURE_UNLOCKED);
-}
+	for (block = head.next; block != &head; block = next)
+	{
+		next = block->next; // get link before possibly freeing
 
-void Z_PreparePatchFlush(void)
-{
-	CONS_Debug(DBG_RENDER, "Z_PreparePatchFlush()...\n");
-#ifdef ROTSPRITE
-	R_FreeAllRotSprite();
-#endif
+		if (block->tag >= lowtag && block->tag <= hightag)
+		{
+			void *mem = (UINT8 *)block->hdr + sizeof *block->hdr;
+			boolean free = iterfunc(mem);
+			if (free)
+				Z_Free(mem);
+		}
+	}
 }
 
+// -----------------
+// Utility functions
+// -----------------
+
 // starting value of nextcleanup
 #define CLEANUPCOUNT 2000
 
@@ -793,14 +795,19 @@ static void Command_Memfree_f(void)
 
 	Z_CheckHeap(-1);
 	CONS_Printf("\x82%s", M_GetText("Memory Info\n"));
-	CONS_Printf(M_GetText("Total heap used   : %7s KB\n"), sizeu1(Z_TotalUsage()>>10));
-	CONS_Printf(M_GetText("Static            : %7s KB\n"), sizeu1(Z_TagUsage(PU_STATIC)>>10));
-	CONS_Printf(M_GetText("Static (sound)    : %7s KB\n"), sizeu1(Z_TagUsage(PU_SOUND)>>10));
-	CONS_Printf(M_GetText("Static (music)    : %7s KB\n"), sizeu1(Z_TagUsage(PU_MUSIC)>>10));
-	CONS_Printf(M_GetText("Locked cache      : %7s KB\n"), sizeu1(Z_TagUsage(PU_CACHE)>>10));
-	CONS_Printf(M_GetText("Level             : %7s KB\n"), sizeu1(Z_TagUsage(PU_LEVEL)>>10));
-	CONS_Printf(M_GetText("Special thinker   : %7s KB\n"), sizeu1(Z_TagUsage(PU_LEVSPEC)>>10));
-	CONS_Printf(M_GetText("All purgable      : %7s KB\n"),
+	CONS_Printf(M_GetText("Total heap used        : %7s KB\n"), sizeu1(Z_TotalUsage()>>10));
+	CONS_Printf(M_GetText("Static                 : %7s KB\n"), sizeu1(Z_TagUsage(PU_STATIC)>>10));
+	CONS_Printf(M_GetText("Static (sound)         : %7s KB\n"), sizeu1(Z_TagUsage(PU_SOUND)>>10));
+	CONS_Printf(M_GetText("Static (music)         : %7s KB\n"), sizeu1(Z_TagUsage(PU_MUSIC)>>10));
+	CONS_Printf(M_GetText("Patches                : %7s KB\n"), sizeu1(Z_TagUsage(PU_PATCH)>>10));
+	CONS_Printf(M_GetText("Patches (low priority) : %7s KB\n"), sizeu1(Z_TagUsage(PU_PATCH_LOWPRIORITY)>>10));
+	CONS_Printf(M_GetText("Patches (rotated)      : %7s KB\n"), sizeu1(Z_TagUsage(PU_PATCH_ROTATED)>>10));
+	CONS_Printf(M_GetText("Sprites                : %7s KB\n"), sizeu1(Z_TagUsage(PU_SPRITE)>>10));
+	CONS_Printf(M_GetText("HUD graphics           : %7s KB\n"), sizeu1(Z_TagUsage(PU_HUDGFX)>>10));
+	CONS_Printf(M_GetText("Locked cache           : %7s KB\n"), sizeu1(Z_TagUsage(PU_CACHE)>>10));
+	CONS_Printf(M_GetText("Level                  : %7s KB\n"), sizeu1(Z_TagUsage(PU_LEVEL)>>10));
+	CONS_Printf(M_GetText("Special thinker        : %7s KB\n"), sizeu1(Z_TagUsage(PU_LEVSPEC)>>10));
+	CONS_Printf(M_GetText("All purgable           : %7s KB\n"),
 		sizeu1(Z_TagsUsage(PU_PURGELEVEL, INT32_MAX)>>10));
 
 #ifdef HWRENDER
diff --git a/src/z_zone.h b/src/z_zone.h
index 5cbcc6655bc24eb2208627f4cf57e31b48c88a1a..e80a45e7fb4f2ed6223868fc78d18649e75ab4ad 100644
--- a/src/z_zone.h
+++ b/src/z_zone.h
@@ -42,8 +42,13 @@ enum
 
 	PU_SOUND                 = 11, // static while playing
 	PU_MUSIC                 = 12, // static while playing
-	PU_HUDGFX                = 13, // static until WAD added
-	PU_PATCH                 = 14, // static until renderer change
+
+	PU_PATCH                 = 14, // static entire execution time
+	PU_PATCH_LOWPRIORITY     = 15, // lower priority patch, static until level exited
+	PU_PATCH_ROTATED         = 16, // rotated patch, static until level exited or WAD added
+	PU_PATCH_DATA            = 17, // patch data, lifetime depends on the patch that owns it
+	PU_SPRITE                = 18, // sprite patch, static until WAD added
+	PU_HUDGFX                = 19, // HUD patch, static until WAD added
 
 	PU_HWRPATCHINFO          = 21, // Hardware GLPatch_t struct for OpenGL texture cache
 	PU_HWRPATCHCOLMIPMAP     = 22, // Hardware GLMipmap_t struct colormap variation of patch
@@ -63,7 +68,7 @@ enum
 	PU_HWRCACHE_UNLOCKED     = 102, // 'unlocked' PU_HWRCACHE memory:
 									// 'second-level' cache for graphics
                                     // stored in hardware format and downloaded as needed
-	PU_HWRPATCHINFO_UNLOCKED = 103, // 'unlocked' PU_HWRPATCHINFO memory
+	PU_HWRPATCHINFO_UNLOCKED    = 103, // 'unlocked' PU_HWRPATCHINFO memory
 	PU_HWRMODELTEXTURE_UNLOCKED = 104, // 'unlocked' PU_HWRMODELTEXTURE memory
 };
 
@@ -107,6 +112,10 @@ void *Z_ReallocAlign(void *ptr, size_t size, INT32 tag, void *user, INT32 alignb
 #define Z_FreeTag(tagnum) Z_FreeTags(tagnum, tagnum)
 void Z_FreeTags(INT32 lowtag, INT32 hightag);
 
+// Iterate memory by tag
+#define Z_IterateTag(tagnum, func) Z_IterateTags(tagnum, tagnum, func)
+void Z_IterateTags(INT32 lowtag, INT32 hightag, boolean (*iterfunc)(void *));
+
 //
 // Utility functions
 //
@@ -144,10 +153,4 @@ size_t Z_TagsUsage(INT32 lowtag, INT32 hightag);
 char *Z_StrDup(const char *in);
 #define Z_Unlock(p) (void)p // TODO: remove this now that NDS code has been removed
 
-// For renderer switching
-extern boolean needpatchflush;
-extern boolean needpatchrecache;
-void Z_FlushCachedPatches(void);
-void Z_PreparePatchFlush(void);
-
 #endif