From ed72bd8fa13d5e2e87122170627632de724c1391 Mon Sep 17 00:00:00 2001
From: Alam Ed Arias <alam@srb2.org>
Date: Fri, 21 Mar 2014 14:42:55 -0400
Subject: [PATCH] SRB2 2.1.4 release

---
 src/d_clisrv.c         | 1129 ++++++++++++++-----------
 src/d_clisrv.h         |  272 ++++---
 src/d_main.c           |    5 +-
 src/d_net.c            |   12 -
 src/d_netcmd.c         |   13 +-
 src/d_player.h         |   21 +-
 src/dehacked.c         |   26 +-
 src/doomdata.h         |    1 +
 src/doomdef.h          |    6 +-
 src/doomstat.h         |    2 +-
 src/g_game.c           |   20 +-
 src/hardware/hw_draw.c |  287 +------
 src/hardware/hw_main.h |    7 +-
 src/hu_stuff.c         |   10 +-
 src/lua_hudlib.c       |   61 +-
 src/lua_infolib.c      |    2 +-
 src/lua_playerlib.c    |   24 +-
 src/m_cheat.c          |   10 +-
 src/m_menu.c           |   32 +-
 src/p_map.c            |    2 +-
 src/p_saveg.c          |   36 +-
 src/p_spec.c           |   27 +-
 src/p_user.c           |   18 +-
 src/r_main.c           |    6 +-
 src/r_main.h           |    3 +-
 src/st_stuff.c         |  324 ++++----
 src/st_stuff.h         |   21 +-
 src/v_video.c          | 1762 ++++------------------------------------
 src/v_video.h          |   43 +-
 src/y_inter.c          |    4 +-
 src/y_inter.h          |    2 +
 31 files changed, 1407 insertions(+), 2781 deletions(-)

diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index f4b262bacd..46a711aac7 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -95,10 +95,21 @@ static tic_t nettics[MAXNETNODES]; // what tic the client have received
 static tic_t supposedtics[MAXNETNODES]; // nettics prevision for smaller packet
 static UINT8 nodewaiting[MAXNETNODES];
 static tic_t firstticstosend; // min of the nettics
-static INT16 consistancy[BACKUPTICS];
 static tic_t tictoclear = 0; // optimize d_clearticcmd
 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][MAXNETNODES]; // 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;
+
 // client specific
 static ticcmd_t localcmds;
 static ticcmd_t localcmds2;
@@ -463,6 +474,541 @@ 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->aiming = (angle_t)LONG(players[i].aiming);
+	rsp->currentweapon = LONG(players[i].currentweapon);
+	rsp->ringweapons = LONG(players[i].ringweapons);
+
+	for (j = 0; j < NUMPOWERS; ++j)
+		rsp->powers[j] = (UINT16)SHORT(players[i].powers[j]);
+
+	// Score is resynched in the rspfirm resync packet
+	rsp->health = 0; // resynched with mo health
+	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);
+	// Just in case Lua does something like
+	// modify these at runtime
+	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->actionspd = LONG(players[i].actionspd);
+	rsp->mindash = LONG(players[i].mindash);
+	rsp->maxdash = LONG(players[i].maxdash);
+	rsp->jumpfactor = (fixed_t)LONG(players[i].jumpfactor);
+
+	rsp->speed = (fixed_t)LONG(players[i].speed);
+	rsp->jumping = players[i].jumping;
+	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->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->maxlink = LONG(players[i].maxlink);
+	rsp->dashspeed = (fixed_t)LONG(players[i].dashspeed);
+	rsp->dashtime = LONG(players[i].dashtime);
+	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->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->tics = LONG(players[i].mo->tics);
+	rsp->statenum = (statenum_t)LONG(players[i].mo->state-states); // :(
+	rsp->eflags = (UINT32)LONG(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].aiming = (angle_t)LONG(rsp->aiming);
+	players[i].currentweapon = LONG(rsp->currentweapon);
+	players[i].ringweapons = LONG(rsp->ringweapons);
+
+	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].health = rsp->health;
+	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);
+	// Just in case Lua does something like
+	// modify these at runtime
+	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].actionspd = LONG(rsp->actionspd);
+	players[i].mindash = LONG(rsp->mindash);
+	players[i].maxdash = LONG(rsp->maxdash);
+	players[i].jumpfactor = (fixed_t)LONG(rsp->jumpfactor);
+
+	players[i].speed = (fixed_t)LONG(rsp->speed);
+	players[i].jumping = rsp->jumping;
+	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].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].maxlink = LONG(rsp->maxlink);
+	players[i].dashspeed = (fixed_t)LONG(rsp->dashspeed);
+	players[i].dashtime = LONG(rsp->dashtime);
+	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->eflags = (UINT32)LONG(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);
+	players[i].mo->tics = LONG(rsp->tics);
+	P_SetMobjStateNF(players[i].mo, 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.
+	players[i].mo->skin = &skins[players[i].skin];
+	players[i].mo->color = players[i].skincolor;
+
+	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)
+			{
+				if (!playeringame[j] || players[j].gotflag != i)
+					continue;
+				rst->flagplayer[i] = (SINT8)j;
+				break;
+			}
+			if (j == MAXPLAYERS)
+				I_Error("One of the flags has gone completely missing!");
+
+			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] != -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] != -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_REDFLAG;
+		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[0]);
+		blueflag->y = (fixed_t)LONG(p->flagy[0]);
+		blueflag->z = (fixed_t)LONG(p->flagz[0]);
+		blueflag->flags2 = LONG(p->flagflags[0]);
+		blueflag->fuse = LONG(p->flagloose[0]);
+		P_SetThingPosition(blueflag);
+	}
+}
+
+static inline void resynch_write_others(resynchend_pak *rst)
+{
+	UINT8 i;
+
+	rst->ingame = rst->ctfteam = 0;
+
+	for (i = 0; i < MAXPLAYERS; ++i)
+	{
+		if (!playeringame[i])
+		{
+			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].ctfteam > 1)
+				rst->ctfteam |= (1<<i);
+		}
+		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);
+	rst->ctfteam = (UINT32)LONG(rst->ctfteam);
+}
+
+static inline void resynch_read_others(resynchend_pak *p)
+{
+	UINT8 i;
+	UINT32 loc_ingame = (UINT32)LONG(p->ingame);
+	UINT32 loc_ctfteam = (UINT32)LONG(p->ctfteam);
+
+	for (i = 0; i < MAXPLAYERS; ++i)
+	{
+		// We don't care if they're in the game or not, just write all the data.
+		if (loc_ingame & (1<<i))
+		{
+			players[i].spectator = false;
+			players[i].ctfteam = (loc_ctfteam & (1<<i)) ? 2 : 1;
+		}
+		else
+		{
+			players[i].spectator = true;
+			players[i].ctfteam = 0;
+		}
+		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, MAXNETNODES);
+}
+
+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] = 0xFF; // 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, MAXNETNODES);
+	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 (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;
+
+	// resynched?
+	if (!resynch_status[node])
+	{
+		// you are now synched
+		resynch_inprogress[node] = false;
+
+		netbuffer->packettype = PT_RESYNCHEND;
+
+		netbuffer->u.resynchend.randomseed = P_GetRandSeed();
+		if (gametype == GT_CTF)
+			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)
+	{
+		XBOXSTATIC UINT8 buf[2];
+		buf[0] = (UINT8)nodetoplayer[node];
+		buf[1] = KICK_MSG_CON_FAIL;
+		SendNetXCmd(XD_KICK, &buf, 2);
+		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
+	}
+}
+// -----------------------------------------------------------------
+// end resynch
+// -----------------------------------------------------------------
+
 static INT16 Consistancy(void);
 
 #ifndef NONET
@@ -1982,7 +2528,7 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 			break;
 #endif
 		case KICK_MSG_CON_FAIL:
-			CONS_Printf(M_GetText("left the game (Consistency failure)\n"));
+			CONS_Printf(M_GetText("left the game (Synch failure)\n"));
 
 			if (M_CheckParm("-consisdump")) // Helps debugging some problems
 			{
@@ -2046,10 +2592,7 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 		CL_Reset();
 		D_StartTitle();
 		if (msg == KICK_MSG_CON_FAIL)
-		{
-			M_StartMessage(M_GetText("Server closed connection\n(consistency failure)\nPress ESC\n"), NULL,
-				MM_NOTHING);
-		}
+			M_StartMessage(M_GetText("Server closed connection\n(synch failure)\nPress ESC\n"), NULL, MM_NOTHING);
 #ifdef NEWPING
 		else if (msg == KICK_MSG_PING_HIGH)
 			M_StartMessage(M_GetText("Server closed connection\n(Broke ping limit)\nPress ESC\n"), NULL, MM_NOTHING);
@@ -2071,8 +2614,8 @@ consvar_t cv_allownewplayer = {"allowjoin", "On", CV_NETVAR, CV_OnOff, NULL, 0,
 consvar_t cv_joinnextround = {"joinnextround", "Off", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; /// \todo not done
 static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {32, "MAX"}, {0, NULL}};
 consvar_t cv_maxplayers = {"maxplayers", "8", CV_SAVE, maxplayers_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-static CV_PossibleValue_t consfailprotect_cons_t[] = {{0, "MIN"}, {20, "MAX"}, {0, NULL}};
-consvar_t cv_consfailprotect = {"consfailprotect", "10", 0, consfailprotect_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL	};
+static CV_PossibleValue_t resynchattempts_cons_t[] = {{0, "MIN"}, {20, "MAX"}, {0, NULL}};
+consvar_t cv_resynchattempts = {"resynchattempts", "10", 0, resynchattempts_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL	};
 consvar_t cv_blamecfail = {"blamecfail", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL	};
 
 // max file size to send to a player (in kilobytes)
@@ -2104,7 +2647,7 @@ void D_ClientServerInit(void)
 	CV_RegisterVar(&cv_allownewplayer);
 	CV_RegisterVar(&cv_joinnextround);
 	CV_RegisterVar(&cv_showjoinaddress);
-	CV_RegisterVar(&cv_consfailprotect);
+	CV_RegisterVar(&cv_resynchattempts);
 	CV_RegisterVar(&cv_blamecfail);
 #ifdef DUMPCONSISTENCY
 	CV_RegisterVar(&cv_dumpconsistency);
@@ -2482,388 +3025,6 @@ static void SV_SendRefuse(INT32 node, const char *reason)
 	Net_CloseConnection(node);
 }
 
-static inline void writeconplayer(cons_pak *con, const size_t i)
-{
-	size_t j;
-
-	con->playernum = (UINT8)i;
-
-	con->playerstate = (UINT8)players[i].playerstate;
-	G_MoveTiccmd(&con->cmd, &players[i].cmd, 1);
-	con->viewz = LONG(players[i].viewz);
-	con->viewheight = LONG(players[i].viewheight);
-	con->deltaviewheight = LONG(players[i].deltaviewheight);
-	con->bob = LONG(players[i].bob);
-	con->aiming = (angle_t)LONG(players[i].aiming);
-	con->awayviewaiming = (angle_t)LONG(players[i].awayviewaiming);
-	con->phealth = LONG(players[i].health);
-	con->pity = players[i].pity;
-	con->currentweapon = LONG(players[i].currentweapon);
-	con->ringweapons = LONG(players[i].ringweapons);
-
-	for (j = 0; j < NUMPOWERS; j++)
-		con->powers[j] = (UINT16)SHORT(players[i].powers[j]);
-
-	con->pflags = (UINT32)LONG(players[i].pflags);
-	con->panim = (UINT8)players[i].panim;
-	con->flashcount = LONG(players[i].flashcount);
-	con->skincolor = players[i].skincolor;
-	con->skin = LONG(players[i].skin);
-	con->score = (UINT32)LONG(players[i].score);
-	con->maxlink = LONG(players[i].maxlink);
-	con->dashspeed = LONG(players[i].dashspeed);
-	con->dashtime = LONG(players[i].dashtime);
-	con->normalspeed = LONG(players[i].normalspeed);
-	con->runspeed = LONG(players[i].runspeed);
-	con->thrustfactor = players[i].thrustfactor;
-	con->accelstart = players[i].accelstart;
-	con->acceleration = players[i].acceleration;
-	con->charability = players[i].charability;
-	con->charability2 = players[i].charability2;
-	con->charflags = (UINT32)LONG(players[i].charflags);
-	con->thokitem = (UINT32)LONG(players[i].thokitem);
-	con->spinitem = (UINT32)LONG(players[i].spinitem);
-	con->revitem = (UINT32)LONG(players[i].revitem);
-	con->actionspd = LONG(players[i].actionspd);
-	con->mindash = LONG(players[i].mindash);
-	con->maxdash = LONG(players[i].maxdash);
-	con->jumpfactor = LONG(players[i].jumpfactor);
-	con->lives = LONG(players[i].lives);
-	con->continues = LONG(players[i].continues);
-	con->xtralife = LONG(players[i].xtralife);
-	con->speed = LONG(players[i].speed);
-	con->jumping = LONG(players[i].jumping);
-	con->secondjump =players[i].secondjump;
-	con->fly1 = players[i].fly1;
-	con->scoreadd = (UINT32)LONG(players[i].scoreadd);
-	con->glidetime = (tic_t)LONG(players[i].glidetime);
-	con->climbing = players[i].climbing;
-	con->deadtimer = LONG(players[i].deadtimer);
-	con->exiting = (tic_t)LONG(players[i].exiting);
-	con->homing = players[i].homing;
-	con->skidtime = (tic_t)LONG(players[i].skidtime);
-	con->cmomx = LONG(players[i].cmomx);
-	con->cmomy = LONG(players[i].cmomy);
-	con->rmomx = LONG(players[i].rmomx);
-	con->rmomy = LONG(players[i].rmomy);
-	con->numboxes = LONG(players[i].numboxes);
-	con->totalring = LONG(players[i].totalring);
-	con->realtime = (tic_t)LONG(players[i].realtime);
-	con->laps = (UINT32)LONG(players[i].laps);
-	con->ctfteam = LONG(players[i].ctfteam);
-	con->gotflag = (UINT16)SHORT(players[i].gotflag);
-	con->weapondelay = LONG(players[i].weapondelay);
-	con->tossdelay = LONG(players[i].tossdelay);
-	con->starpostx = SHORT(players[i].starpostx);
-	con->starposty = SHORT(players[i].starposty);
-	con->starpostz = SHORT(players[i].starpostz);
-	con->starpostnum = LONG(players[i].starpostnum);
-	con->starposttime = (tic_t)LONG(players[i].starposttime);
-	con->starpostangle = (angle_t)LONG(players[i].starpostangle);
-	con->angle_pos = (angle_t)LONG(players[i].angle_pos);
-	con->old_angle_pos = (angle_t)LONG(players[i].old_angle_pos);
-	con->bumpertime = (tic_t)LONG(players[i].bumpertime);
-	con->flyangle = LONG(players[i].flyangle);
-	con->drilltimer = (tic_t)LONG(players[i].drilltimer);
-	con->linkcount = LONG(players[i].linkcount);
-	con->linktimer = (tic_t)LONG(players[i].linktimer);
-	con->anotherflyangle = LONG(players[i].anotherflyangle);
-	con->nightstime = (tic_t)LONG(players[i].nightstime);
-	con->drillmeter = LONG(players[i].drillmeter);
-	con->drilldelay = players[i].drilldelay;
-	con->bonustime = (UINT8)players[i].bonustime;
-	con->mare = players[i].mare;
-	con->lastsidehit = SHORT(players[i].lastsidehit);
-	con->lastlinehit = SHORT(players[i].lastlinehit);
-	con->losstime = (tic_t)LONG(players[i].losstime);
-	con->timeshit = (UINT8)players[i].timeshit;
-	con->onconveyor = LONG(players[i].onconveyor);
-	con->spectator = (UINT8)players[i].spectator;
-	con->jointime = (tic_t)LONG(players[i].jointime);
-
-	con->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;
-
-	con->hasmo = true;
-	con->angle = (angle_t)LONG(players[i].mo->angle);
-	con->eflags = (UINT32)LONG(players[i].mo->eflags);
-	con->flags = LONG(players[i].mo->flags);
-	con->flags2 = LONG(players[i].mo->flags2);
-	con->friction = LONG(players[i].mo->friction);
-	con->health = LONG(players[i].mo->health);
-	con->momx = LONG(players[i].mo->momx);
-	con->momy = LONG(players[i].mo->momy);
-	con->momz = LONG(players[i].mo->momz);
-	con->movefactor = LONG(players[i].mo->movefactor);
-	con->tics = LONG(players[i].mo->tics);
-	con->statenum = (statenum_t)LONG(players[i].mo->state-states); // :(
-	con->x = LONG(players[i].mo->x);
-	con->y = LONG(players[i].mo->y);
-	con->z = LONG(players[i].mo->z);
-	con->radius = LONG(players[i].mo->radius);
-	con->height = LONG(players[i].mo->height);
-	con->scale = LONG(players[i].mo->scale);
-	con->destscale = LONG(players[i].mo->destscale);
-	con->scalespeed = LONG(players[i].mo->scalespeed);
-}
-
-static void readconplayer(cons_pak *con, const INT32 playernum)
-{
-	size_t i;
-	mobj_t *savedmo = players[playernum].mo;
-
-	//We get a packet for each player in game.
-
-	//Restore CTF information
-	if (gametype == GT_CTF)
-	{
-		// Remove old flags.
-		if (redflag)
-		{
-			P_RemoveMobj(redflag);
-			redflag = NULL;
-		}
-		if (blueflag)
-		{
-			P_RemoveMobj(blueflag);
-			blueflag = NULL;
-		}
-
-		// Spawn the flags if players aren't carrying them.
-		if (con->rflagloose != 2)
-		{
-			mobj_t *newflag = P_SpawnMobj(con->rflagx << FRACBITS, con->rflagy << FRACBITS, con->rflagz << FRACBITS, MT_REDFLAG);
-			newflag->flags |= MF_SPECIAL;
-			newflag->flags2 = con->rflags2;
-			newflag->fuse = con->rfuse;
-			newflag->spawnpoint = rflagpoint;
-			redflag = newflag;
-		}
-
-		if (con->bflagloose != 2)
-		{
-			mobj_t *newflag = P_SpawnMobj(con->bflagx << FRACBITS, con->bflagy << FRACBITS, con->bflagz << FRACBITS, MT_BLUEFLAG);
-			newflag->flags |= MF_SPECIAL;
-			newflag->flags2 = con->bflags2;
-			newflag->fuse = con->bfuse;
-			newflag->spawnpoint = bflagpoint;
-			blueflag = newflag;
-		}
-	}
-
-	if (!playeringame[playernum])
-		return;
-
-	//Tranfer player information.
-	players[playernum].playerstate = (playerstate_t)con->playerstate;
-	G_MoveTiccmd(&players[playernum].cmd, &con->cmd, 1);
-	players[playernum].viewz = LONG(con->viewz);
-	players[playernum].viewheight = LONG(con->viewheight);
-	players[playernum].deltaviewheight = LONG(con->deltaviewheight);
-	players[playernum].bob = LONG(con->bob);
-	players[playernum].aiming = (angle_t)LONG(con->aiming);
-	players[playernum].awayviewaiming = (angle_t)LONG(con->awayviewaiming);
-	players[playernum].health = LONG(con->phealth);
-	players[playernum].pity = con->pity;
-	players[playernum].currentweapon = LONG(con->currentweapon);
-	players[playernum].ringweapons = LONG(con->ringweapons);
-
-	for (i = 0; i < NUMPOWERS; i++)
-		players[playernum].powers[i] = (UINT16)SHORT(con->powers[i]);
-
-	players[playernum].pflags = (pflags_t)LONG(con->pflags);
-	players[playernum].panim = (panim_t)con->panim;
-	players[playernum].flashcount = LONG(con->flashcount);
-	players[playernum].skincolor = con->skincolor;
-	players[playernum].skin = LONG(con->skin);
-	players[playernum].score = (UINT32)LONG(con->score);
-	players[playernum].maxlink = LONG(con->maxlink);
-	players[playernum].dashspeed = LONG(con->dashspeed);
-	players[playernum].dashtime = LONG(con->dashtime);
-	players[playernum].normalspeed = LONG(con->normalspeed);
-	players[playernum].runspeed = LONG(con->runspeed);
-	players[playernum].thrustfactor = con->thrustfactor;
-	players[playernum].accelstart = con->accelstart;
-	players[playernum].acceleration = con->acceleration;
-	players[playernum].charability = con->charability;
-	players[playernum].charability2 = con->charability2;
-	players[playernum].charflags = (UINT32)LONG(con->charflags);
-	players[playernum].thokitem = (mobjtype_t)LONG(con->thokitem);
-	players[playernum].spinitem = (mobjtype_t)LONG(con->spinitem);
-	players[playernum].revitem = (mobjtype_t)LONG(con->revitem);
-	players[playernum].actionspd = LONG(con->actionspd);
-	players[playernum].mindash = LONG(con->mindash);
-	players[playernum].maxdash = LONG(con->maxdash);
-	players[playernum].jumpfactor = LONG(con->jumpfactor);
-	players[playernum].lives = LONG(con->lives);
-	players[playernum].continues = LONG(con->continues);
-	players[playernum].xtralife = LONG(con->xtralife);
-	players[playernum].speed = LONG(con->speed);
-	players[playernum].jumping = LONG(con->jumping);
-	players[playernum].secondjump = con->secondjump;
-	players[playernum].fly1 = con->fly1;
-	players[playernum].scoreadd = (UINT32)LONG(con->scoreadd);
-	players[playernum].glidetime = (tic_t)LONG(con->glidetime);
-	players[playernum].climbing = con->climbing;
-	players[playernum].deadtimer = LONG(con->deadtimer);
-	players[playernum].exiting = (tic_t)LONG(con->exiting);
-	players[playernum].homing = con->homing;
-	players[playernum].skidtime = (tic_t)LONG(con->skidtime);
-	players[playernum].cmomx = LONG(con->cmomx);
-	players[playernum].cmomy = LONG(con->cmomy);
-	players[playernum].rmomx = LONG(con->rmomx);
-	players[playernum].rmomy = LONG(con->rmomy);
-	players[playernum].numboxes = LONG(con->numboxes);
-	players[playernum].totalring = LONG(con->totalring);
-	players[playernum].realtime = (tic_t)LONG(con->realtime);
-	players[playernum].laps = (UINT32)LONG(con->laps);
-	players[playernum].ctfteam = LONG(con->ctfteam);
-	players[playernum].gotflag = (UINT16)SHORT(con->gotflag);
-	players[playernum].weapondelay = LONG(con->weapondelay);
-	players[playernum].tossdelay = LONG(con->tossdelay);
-	players[playernum].starpostx = LONG(con->starpostx);
-	players[playernum].starposty = LONG(con->starposty);
-	players[playernum].starpostz = LONG(con->starpostz);
-	players[playernum].starpostnum = LONG(con->starpostnum);
-	players[playernum].starposttime = (tic_t)LONG(con->starposttime);
-	players[playernum].starpostangle = (angle_t)LONG(con->starpostangle);
-	players[playernum].angle_pos = (angle_t)LONG(con->angle_pos);
-	players[playernum].old_angle_pos = (angle_t)LONG(con->old_angle_pos);
-	players[playernum].bumpertime = (tic_t)LONG(con->bumpertime);
-	players[playernum].flyangle = LONG(con->flyangle);
-	players[playernum].drilltimer = (tic_t)LONG(con->drilltimer);
-	players[playernum].linkcount = LONG(con->linkcount);
-	players[playernum].linktimer = (tic_t)LONG(con->linktimer);
-	players[playernum].anotherflyangle = LONG(con->anotherflyangle);
-	players[playernum].nightstime = (tic_t)LONG(con->nightstime);
-	players[playernum].drillmeter = LONG(con->drillmeter);
-	players[playernum].drilldelay = con->drilldelay;
-	players[playernum].bonustime = (boolean)con->bonustime;
-	players[playernum].mare = con->mare;
-	players[playernum].lastsidehit = SHORT(con->lastsidehit);
-	players[playernum].lastlinehit = SHORT(con->lastlinehit);
-	players[playernum].losstime = (tic_t)LONG(con->losstime);
-	players[playernum].timeshit = (UINT8)con->timeshit;
-	players[playernum].onconveyor = LONG(con->onconveyor);
-	players[playernum].spectator = (boolean)con->spectator;
-	players[playernum].jointime = (tic_t)LONG(con->jointime);
-
-	//...but keep old mo even if it is corrupt or null!
-	players[playernum].mo = savedmo;
-
-	//Transfer important mo information if they have a valid mo.
-	if (!con->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[playernum].mo) //client thinks it has no body.
-		P_SpawnPlayer(playernum);
-
-	//At this point, the player should have a body, whether they were respawned or not.
-	P_UnsetThingPosition(players[playernum].mo);
-	players[playernum].mo->angle = (angle_t)LONG(con->angle);
-	players[playernum].mo->eflags = (UINT32)LONG(con->eflags);
-	players[playernum].mo->flags = LONG(con->flags);
-	players[playernum].mo->flags2 = LONG(con->flags2);
-	players[playernum].mo->friction = LONG(con->friction);
-	players[playernum].mo->health = LONG(con->health);
-	players[playernum].mo->momx = LONG(con->momx);
-	players[playernum].mo->momy = LONG(con->momy);
-	players[playernum].mo->momz = LONG(con->momz);
-	players[playernum].mo->movefactor = LONG(con->movefactor);
-	players[playernum].mo->tics = LONG(con->tics);
-	P_SetPlayerMobjState(players[playernum].mo, LONG(con->statenum));
-	players[playernum].mo->x = LONG(con->x);
-	players[playernum].mo->y = LONG(con->y);
-	players[playernum].mo->z = LONG(con->z);
-	players[playernum].mo->radius = LONG(con->radius);
-	players[playernum].mo->height = LONG(con->height);
-	// P_SetScale is redundant for this, as all related variables are already restored properly.
-	players[playernum].mo->scale = LONG(con->scale);
-	players[playernum].mo->destscale = LONG(con->destscale);
-	players[playernum].mo->scalespeed = LONG(con->scalespeed);
-
-	// And finally, SET THE MOBJ SKIN damn it.
-	players[playernum].mo->skin = &skins[players[playernum].skin];
-	players[playernum].mo->color = players[playernum].skincolor;
-
-	P_SetThingPosition(players[playernum].mo);
-}
-
-static inline void handlectfconstuff(cons_pak *con)
-{
-	if (redflag)
-	{
-		// Flag is loose
-		if (redflag->fuse)
-		{
-			con->rflagloose = 1;
-			con->rflagx = SHORT(redflag->x >> FRACBITS);
-			con->rflagy = SHORT(redflag->y >> FRACBITS);
-			con->rflagz = SHORT(redflag->z >> FRACBITS);
-			con->rflags2 = LONG(redflag->flags2);
-			con->rfuse = LONG(redflag->fuse);
-		}
-		else // flag is at base
-		{
-			con->rflagloose = 0;
-			con->rflagx = SHORT(rflagpoint->x);
-			con->rflagy = SHORT(rflagpoint->y);
-			con->rflagz = SHORT(rflagpoint->z);
-			con->rflags2 = 0;
-			con->rfuse = 0;
-		}
-	}
-	else // player has flag
-		con->rflagloose = 2;
-
-	if (blueflag)
-	{
-		// Flag is loose
-		if (blueflag->fuse)
-		{
-			con->bflagloose = 1;
-			con->bflagx = SHORT(blueflag->x >> FRACBITS);
-			con->bflagy = SHORT(blueflag->y >> FRACBITS);
-			con->bflagz = SHORT(blueflag->z >> FRACBITS);
-			con->bflags2 = LONG(blueflag->flags2);
-			con->bfuse = LONG(blueflag->fuse);
-		}
-		else // flag is at base
-		{
-			con->bflagloose = 0;
-			con->bflagx = SHORT(bflagpoint->x);
-			con->bflagy = SHORT(bflagpoint->y);
-			con->bflagz = SHORT(bflagpoint->z);
-			con->bflags2 = 0;
-			con->bfuse = 0;
-		}
-	}
-	else // player has flag
-		con->bflagloose = 2;
-}
-
-/// \todo Remove this AWFUL consistency fixing packet and replace it with re-sending $$$.sav, or at least pause the game until it gets acked!
-static void SV_SendConsistency(INT32 node)
-{
-	INT32 i;
-
-	netbuffer->packettype = PT_CONSISTENCY;
-
-	if (gametype == GT_CTF)
-		handlectfconstuff(&netbuffer->u.consistency);
-
-	for (i = 0; i < MAXPLAYERS; i++)
-		if (playeringame[i])
-		{
-			writeconplayer(&netbuffer->u.consistency, i);
-			HSendPacket(node, true, 0, (sizeof(cons_pak)));
-		}
-}
-
 // used at txtcmds received to check packetsize bound
 static size_t TotalTextCmdPerTic(tic_t tic)
 {
@@ -2910,6 +3071,10 @@ static void HandleConnect(SINT8 node)
 			newnode = true;
 #endif
 			SV_AddNode(node);
+
+			// 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))
@@ -2934,6 +3099,7 @@ static void HandleConnect(SINT8 node)
 				DEBFILE("send savegame\n");
 			}
 			SV_AddWaitingPlayers();
+			player_joining = true;
 		}
 #endif
 	}
@@ -2982,6 +3148,9 @@ static void GetPackets(void)
 	XBOXSTATIC tic_t realend,realstart;
 	XBOXSTATIC UINT8 *pak, *txtpak, numtxtpak;
 FILESTAMP
+
+	player_joining = false;
+
 	while (HGetPacket())
 	{
 		node = (SINT8)doomcom->remotenode;
@@ -3153,9 +3322,14 @@ FILESTAMP
 			I_Error("bad table nodetoplayer: node %d player %d", doomcom->remotenode, netconsole);
 #endif
 
+		txtpak = NULL;
+
 		switch (netbuffer->packettype)
 		{
 // -------------------------------------------- SERVER RECEIVE ----------
+			case PT_RESYNCHGET:
+				SV_AcknowledgeResynchAck(netconsole, netbuffer->u.resynchgot);
+				break;
 			case PT_CLIENTCMD:
 			case PT_CLIENT2CMD:
 			case PT_CLIENTMIS:
@@ -3165,6 +3339,10 @@ FILESTAMP
 				if (!server)
 					break;
 
+				// ignore tics from those not synched
+				if (resynch_inprogress[node])
+					break;
+
 				// to save bytes, only the low byte of tic numbers are sent
 				// Figure out what the rest of the bytes are
 				realstart = ExpandTics(netbuffer->u.clientpak.client_tic);
@@ -3213,42 +3391,30 @@ FILESTAMP
 					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
-				// Careful: When a consistency packet is sent, it overwrites the incoming packet containing the ticcmd.
-				//          Keep this in mind when changing the code that responds to these packets.
-				if (realstart <= gametic && realstart > gametic - BACKUPTICS+1
-					&& gamestate == GS_LEVEL && playeringame[netconsole]
-					&& players[netconsole].playerstate == PST_LIVE
-					&& !players[netconsole].spectator
-					&& players[netconsole].jointime > 10
+				if (realstart <= gametic && realstart > gametic - BACKUPTICS+1 && gamestate == GS_LEVEL
 					&& consistancy[realstart%BACKUPTICS] != SHORT(netbuffer->u.clientpak.consistancy))
 				{
-					if (cv_consfailprotect.value && consfailcount[netconsole] < cv_consfailprotect.value)
-					{
-						if (!consfailstatus[netconsole])
-						{
-							if (cv_blamecfail.value)
-								CONS_Printf(M_GetText("Consistency failure for player %d (%s), restoring...\n"), netconsole+1, player_names[netconsole]);
-
-							DEBFILE(va("Restoring player %d (consistency failure) [%update] %d!=%d\n",
-								netconsole, realstart, consistancy[realstart%BACKUPTICS],
-								SHORT(netbuffer->u.clientpak.consistancy)));
+					SV_RequireResynch(node);
 
-							SV_SendConsistency(netconsole);
-							consfailstatus[netconsole] = 1;
-							consfailcount[netconsole]++;
-							break; // ticcmd packet is gone.
-						}
-						else
-						{
-							//We don't want to send any more packets than we have to.
-							//If the client doesn't resync in a certain time,
-							//assume they didn't get the packet. Send another.
-							if (consfailstatus[netconsole] < 10)
-								consfailstatus[netconsole]++;
-							else
-								consfailstatus[netconsole] = 0;
-						}
+					if (cv_resynchattempts.value && resynch_score[node] <= (unsigned)cv_resynchattempts.value*250)
+					{
+						if (cv_blamecfail.value)
+							CONS_Printf(M_GetText("Synch failure for player %d (%s); expected %hd, got %hd\n"),
+								netconsole+1, player_names[netconsole],
+								consistancy[realstart%BACKUPTICS],
+								SHORT(netbuffer->u.clientpak.consistancy));
+						DEBFILE(va("Restoring player %d (synch failure) [%update] %d!=%d\n",
+							netconsole, realstart, consistancy[realstart%BACKUPTICS],
+							SHORT(netbuffer->u.clientpak.consistancy)));
+						break;
 					}
 					else
 					{
@@ -3257,19 +3423,14 @@ FILESTAMP
 						buf[0] = (UINT8)netconsole;
 						buf[1] = KICK_MSG_CON_FAIL;
 						SendNetXCmd(XD_KICK, &buf, 2);
-						DEBFILE(va("player %d kicked (consistency failure) [%u] %d!=%d\n",
+						DEBFILE(va("player %d kicked (synch failure) [%u] %d!=%d\n",
 							netconsole, realstart, consistancy[realstart%BACKUPTICS],
 							SHORT(netbuffer->u.clientpak.consistancy)));
-						consfailstatus[netconsole] = 0;
-						consfailcount[netconsole] = 0;
 						break;
 					}
 				}
-				else
-				{
-					consfailstatus[netconsole] = 0;
-					consfailcount[netconsole] = 0;
-				}
+				else if (resynch_score[node])
+					--resynch_score[node];
 				break;
 			case PT_TEXTCMD2: // splitscreen special
 				netconsole = nodetoplayer2[node];
@@ -3346,6 +3507,31 @@ FILESTAMP
 				nodeingame[node] = false;
 				break;
 // -------------------------------------------- CLIENT RECEIVE ----------
+			case PT_RESYNCHEND:
+				// Only accept PT_RESYNCHEND from the server.
+				if (node != servernode)
+				{
+					CONS_Alert(CONS_WARNING, M_GetText("%s recieved from non-host %d\n"), "PT_RESYNCHEND", node);
+
+					if (server)
+					{
+						XBOXSTATIC UINT8 buf[2];
+						buf[0] = (UINT8)node;
+						buf[1] = KICK_MSG_CON_FAIL;
+						SendNetXCmd(XD_KICK, &buf, 2);
+					}
+
+					break;
+				}
+				resynch_local_inprogress = false;
+
+				P_SetRandSeed(netbuffer->u.resynchend.randomseed);
+
+				if (gametype == GT_CTF)
+					resynch_read_ctf(&netbuffer->u.resynchend);
+				resynch_read_others(&netbuffer->u.resynchend);
+
+				break;
 			case PT_SERVERTICS:
 				// Only accept PT_SERVERTICS from the server.
 				if (node != servernode)
@@ -3366,8 +3552,9 @@ FILESTAMP
 				realstart = ExpandTics(netbuffer->u.serverpak.starttic);
 				realend = realstart + netbuffer->u.serverpak.numtics;
 
-				txtpak = (UINT8 *)&netbuffer->u.serverpak.cmds[netbuffer->u.serverpak.numslots
-					* netbuffer->u.serverpak.numtics];
+				if (!txtpak)
+					txtpak = (UINT8 *)&netbuffer->u.serverpak.cmds[netbuffer->u.serverpak.numslots
+						* netbuffer->u.serverpak.numtics];
 
 				if (realend > gametic + BACKUPTICS)
 					realend = gametic + BACKUPTICS;
@@ -3404,11 +3591,11 @@ FILESTAMP
 				else
 					DEBFILE(va("frame not in bound: %u\n", neededtic));
 				break;
-			case PT_CONSISTENCY:
-				// Only accept PT_CONSISTENCY from the server.
+			case PT_RESYNCHING:
+				// Only accept PT_RESYNCHING from the server.
 				if (node != servernode)
 				{
-					CONS_Alert(CONS_WARNING, M_GetText("%s recieved from non-host %d\n"), "PT_CONSISTENCY", node);
+					CONS_Alert(CONS_WARNING, M_GetText("%s recieved from non-host %d\n"), "PT_RESYNCHING", node);
 
 					if (server)
 					{
@@ -3420,8 +3607,8 @@ FILESTAMP
 
 					break;
 				}
-
-				readconplayer(&netbuffer->u.consistency, netbuffer->u.consistency.playernum);
+				resynch_local_inprogress = true;
+				CL_AcknowledgeResynch(&netbuffer->u.resynchpak);
 				break;
 #ifdef NEWPING
 			case PT_PING:
@@ -3470,25 +3657,35 @@ FILESTAMP
 // Builds ticcmds for console player,
 // sends out a packet
 //
+// no more use random generator, because at very first tic isn't yet synchronized
 // Note: It is called consistAncy on purpose.
 //
 static INT16 Consistancy(void)
 {
-	INT16 ret = 0;
 	INT32 i;
+	UINT32 ret = 0;
 
 	DEBFILE(va("TIC %u ", gametic));
+
 	for (i = 0; i < MAXPLAYERS; i++)
-		if (playeringame[i] && players[i].mo && players[i].playerstate == PST_LIVE && !players[i].spectator)
+	{
+		if (!playeringame[i])
+			ret ^= 0xCCCC;
+		else if (!players[i].mo);
+		else
 		{
-			//DEBFILE(va("p[%d].x = %f ", i, (double)FIXED_TO_FLOAT(players[i].mo->x)));
-			ret = (INT16)((ret + (players[i].mo->x>>8)) & 0xFFFF);
-			ret = (INT16)((ret + players[i].powers[pw_shield]) & 0xFFFF);
+			ret += players[i].mo->x;
+			ret -= players[i].mo->y;
+			ret += players[i].powers[pw_shield];
+			ret *= i+1;
 		}
-	DEBFILE(va("players = %d, rnd %d\n", ret, P_GetRandSeed()));
-	ret = (INT16)(ret + P_GetRandSeed());
+	}
+	// I give up
+	// Coop desynching enemies is painful
+	if (!G_PlatformGametype())
+		ret += P_GetRandSeed();
 
-	return ret;
+	return (INT16)(ret & 0xFFFF);
 }
 
 // send the client packet to the server
@@ -3776,6 +3973,9 @@ void TryRunTics(tic_t realtics)
 	}
 #endif
 
+	if (player_joining)
+		return;
+
 	if (neededtic > gametic)
 	{
 		if (advancedemo)
@@ -3911,13 +4111,19 @@ FILESTAMP
 	MasterClient_Ticker(); // acking the master server
 
 	if (!server)
-		CL_SendClientCmd(); // send tic cmd
+	{
+		if (!resynch_local_inprogress)
+			CL_SendClientCmd(); // send tic cmd
+		hu_resynching = resynch_local_inprogress;
+	}
 	else
 	{
 		if (!demoplayback)
 		{
 			INT32 counts;
 
+			hu_resynching = false;
+
 			firstticstosend = gametic;
 			for (i = 0; i < MAXNETNODES; i++)
 				if (nodeingame[i] && nettics[i] < firstticstosend)
@@ -3926,18 +4132,31 @@ FILESTAMP
 			// Don't erase tics not acknowledged
 			counts = realtics;
 
-			if (maketic + counts >= firstticstosend + BACKUPTICS)
-				counts = firstticstosend+BACKUPTICS-maketic-1;
+			for (i = 0; i < MAXNETNODES; ++i)
+				if (resynch_inprogress[i])
+				{
+					SV_SendResynch(i);
+					counts = -666;
+				}
 
-			for (i = 0; i < counts; i++)
-				SV_Maketic(); // create missed tics and increment maketic
+			// 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 (; tictoclear < firstticstosend; tictoclear++) // clear only when acknoledged
-				D_Clearticcmd(tictoclear);                    // clear the maketic the new tic
+				for (; tictoclear < firstticstosend; tictoclear++) // clear only when acknoledged
+					D_Clearticcmd(tictoclear);                    // clear the maketic the new tic
 
-			SV_SendTics();
+				SV_SendTics();
 
-			neededtic = maketic; // the server is a client too
+				neededtic = maketic; // the server is a client too
+			}
+			else
+				hu_resynching = true;
 		}
 	}
 	Net_AckTicker();
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index d35c5cb3c6..0086132e6c 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -53,6 +53,8 @@ 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
 
 	// Add non-PT_CANFAIL packet types here to avoid breaking MS compatibility.
 
@@ -66,7 +68,8 @@ 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_CONSISTENCY,   // Packet sent to resync players.
+	PT_RESYNCHING,    // Packet sent to resync players.
+	                  // Blocks game advance until synched.
 #ifdef NEWPING
 	PT_PING,          // Packet sent to tell clients the other client's latency to server.
 #endif
@@ -110,133 +113,59 @@ typedef struct
 	ticcmd_t cmds[45]; // normally [BACKUPTIC][MAXPLAYERS] but too large
 } ATTRPACK servertics_pak;
 
-typedef struct
-{
-	UINT8 version; // different versions don't work
-	UINT8 subversion; // contains build version
-
-	// server launch stuffs
-	UINT8 serverplayer;
-	UINT8 totalslotnum; // "Slots": highest player number in use plus one.
-
-	tic_t gametic;
-	UINT8 clientnode;
-	UINT8 gamestate;
-
-	UINT32 playerdetected; // playeringame vector in bit field
-	UINT8 gametype;
-	UINT8 modifiedgame;
-	SINT8 adminplayer; // needs to be signed
-
-	char server_context[8]; // unique context id, generated at server startup.
-
-	UINT8 netcvarstates[0];
-} ATTRPACK serverconfig_pak;
-
-typedef struct {
-	UINT8 fileid;
-	UINT32 position;
-	UINT16 size;
-	UINT8 data[0]; // size is variable using hardware_MAXPACKETLENGTH
-} ATTRPACK filetx_pak;
-
-#ifdef _MSC_VER
-#pragma warning(default : 4200)
-#endif
-
-typedef struct
-{
-	UINT8 version; // different versions don't work
-	UINT8 subversion; // contains build version
-	UINT8 localplayers;
-	UINT8 mode;
-} ATTRPACK clientconfig_pak;
-
-#define MAXSERVERNAME 32
-// this packet is too large
-typedef struct
-{
-	UINT8 version;
-	UINT8 subversion;
-	UINT8 numberofplayer;
-	UINT8 maxplayer;
-	UINT8 gametype;
-	UINT8 modifiedgame;
-	UINT8 cheatsenabled;
-	UINT8 isdedicated;
-	UINT8 fileneedednum;
-	SINT8 adminplayer;
-	tic_t time;
-	tic_t leveltime;
-	char servername[MAXSERVERNAME];
-	char mapname[8];
-	char maptitle[33];
-	unsigned char mapmd5[16];
-	UINT8 actnum;
-	UINT8 iszone;
-	UINT8 fileneeded[915]; // is filled with writexxx (byteptr.h)
-} ATTRPACK serverinfo_pak;
-
-typedef struct
-{
-	char reason[255];
-} ATTRPACK serverrefuse_pak;
-
-typedef struct
-{
-	UINT8 version;
-	tic_t time; // used for ping evaluation
-} ATTRPACK askinfo_pak;
-
-typedef struct
-{
-	char clientaddr[22];
-	tic_t time; // used for ping evaluation
-} ATTRPACK msaskinfo_pak;
-
+// sent to client when all consistency data
+// for players has been restored
 typedef struct
 {
 	UINT32 randomseed;
 
 	//ctf flag stuff
-	UINT8 rflagloose;
-	UINT8 bflagloose;
-	INT32 rfuse;
-	INT32 bfuse;
-	INT32 rflags2;
-	INT32 bflags2;
-	INT16 rflagx;
-	INT16 rflagy;
-	INT16 rflagz;
-	INT16 bflagx;
-	INT16 bflagy;
-	INT16 bflagz;
+	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 ctfteam; // if not spectator, then which team?
+
+	// 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
-	ticcmd_t cmd;
-	fixed_t viewz;
-	fixed_t viewheight;
-	fixed_t deltaviewheight;
-	fixed_t bob;
+	UINT32 pflags; //pflags_t
+	UINT8 panim; //panim_t
+
 	angle_t aiming;
-	angle_t awayviewaiming;
-	INT32 phealth;
-	SINT8 pity;
 	INT32 currentweapon;
 	INT32 ringweapons;
 	UINT16 powers[NUMPOWERS];
-	UINT32 pflags; //pflags_t
-	UINT8 panim; //panim_t
-	INT32 flashcount;
+
+	// Score is resynched in the confirm resync packet
+	INT32 health;
+	SINT8 lives;
+	SINT8 continues;
+	UINT8 scoreadd;
+	SINT8 xtralife;
+	SINT8 pity;
+
 	UINT8 skincolor;
 	INT32 skin;
-	UINT32 score;
-	INT32 maxlink;
-	fixed_t dashspeed;
-	INT32 dashtime;
+	// Just in case Lua does something like
+	// modify these at runtime
 	fixed_t normalspeed;
 	fixed_t runspeed;
 	UINT8 thrustfactor;
@@ -252,38 +181,34 @@ typedef struct
 	INT32 mindash;
 	INT32 maxdash;
 	fixed_t jumpfactor;
-	INT32 lives;
-	INT32 continues;
-	INT32 xtralife;
+
 	fixed_t speed;
-	INT32 jumping;
+	UINT8 jumping;
 	UINT8 secondjump;
 	UINT8 fly1;
-	UINT8 scoreadd;
 	tic_t glidetime;
 	UINT8 climbing;
 	INT32 deadtimer;
 	tic_t exiting;
 	UINT8 homing;
-	tic_t skidtime;
 	fixed_t cmomx;
 	fixed_t cmomy;
 	fixed_t rmomx;
 	fixed_t rmomy;
-	INT32 numboxes;
-	INT32 totalring;
-	tic_t realtime;
-	UINT32 laps;
-	INT32 ctfteam;
-	UINT16 gotflag;
+
 	INT32 weapondelay;
 	INT32 tossdelay;
+
 	INT16 starpostx;
 	INT16 starposty;
 	INT16 starpostz;
 	INT32 starpostnum;
 	tic_t starposttime;
 	angle_t starpostangle;
+
+	INT32 maxlink;
+	fixed_t dashspeed;
+	INT32 dashtime;
 	angle_t angle_pos;
 	angle_t old_angle_pos;
 	tic_t bumpertime;
@@ -298,11 +223,10 @@ typedef struct
 	UINT8 bonustime;
 	UINT8 mare;
 	INT16 lastsidehit, lastlinehit;
+
 	tic_t losstime;
 	UINT8 timeshit;
 	INT32 onconveyor;
-	UINT8 spectator; //boolean
-	tic_t jointime;
 
 	//player->mo stuff
 	UINT8 hasmo; //boolean
@@ -317,19 +241,102 @@ typedef struct
 	fixed_t friction;
 	fixed_t movefactor;
 
-	INT32 tics;
+	INT16 tics;
 	statenum_t statenum;
 	UINT32 flags;
 	UINT32 flags2;
 	UINT8 eflags;
-	INT32 health;
 
 	fixed_t radius;
 	fixed_t height;
 	fixed_t scale;
 	fixed_t destscale;
 	fixed_t scalespeed;
-} ATTRPACK cons_pak;
+} ATTRPACK resynch_pak;
+
+typedef struct
+{
+	UINT8 version; // different versions don't work
+	UINT8 subversion; // contains build version
+
+	// server launch stuffs
+	UINT8 serverplayer;
+	UINT8 totalslotnum; // "Slots": highest player number in use plus one.
+
+	tic_t gametic;
+	UINT8 clientnode;
+	UINT8 gamestate;
+
+	UINT32 playerdetected; // playeringame vector in bit field
+	UINT8 gametype;
+	UINT8 modifiedgame;
+	SINT8 adminplayer; // needs to be signed
+
+	char server_context[8]; // unique context id, generated at server startup.
+
+	UINT8 netcvarstates[0];
+} ATTRPACK serverconfig_pak;
+
+typedef struct {
+	UINT8 fileid;
+	UINT32 position;
+	UINT16 size;
+	UINT8 data[0]; // size is variable using hardware_MAXPACKETLENGTH
+} ATTRPACK filetx_pak;
+
+#ifdef _MSC_VER
+#pragma warning(default : 4200)
+#endif
+
+typedef struct
+{
+	UINT8 version; // different versions don't work
+	UINT8 subversion; // contains build version
+	UINT8 localplayers;
+	UINT8 mode;
+} ATTRPACK clientconfig_pak;
+
+#define MAXSERVERNAME 32
+// this packet is too large
+typedef struct
+{
+	UINT8 version;
+	UINT8 subversion;
+	UINT8 numberofplayer;
+	UINT8 maxplayer;
+	UINT8 gametype;
+	UINT8 modifiedgame;
+	UINT8 cheatsenabled;
+	UINT8 isdedicated;
+	UINT8 fileneedednum;
+	SINT8 adminplayer;
+	tic_t time;
+	tic_t leveltime;
+	char servername[MAXSERVERNAME];
+	char mapname[8];
+	char maptitle[33];
+	unsigned char mapmd5[16];
+	UINT8 actnum;
+	UINT8 iszone;
+	UINT8 fileneeded[915]; // is filled with writexxx (byteptr.h)
+} ATTRPACK serverinfo_pak;
+
+typedef struct
+{
+	char reason[255];
+} ATTRPACK serverrefuse_pak;
+
+typedef struct
+{
+	UINT8 version;
+	tic_t time; // used for ping evaluation
+} ATTRPACK askinfo_pak;
+
+typedef struct
+{
+	char clientaddr[22];
+	tic_t time; // used for ping evaluation
+} ATTRPACK msaskinfo_pak;
 
 // Shorter player information for external use.
 typedef struct
@@ -372,6 +379,9 @@ typedef struct
 		client2cmd_pak client2pak;  //      200 bytes
 		servertics_pak serverpak;   //   132495 bytes
 		serverconfig_pak servercfg; //      773 bytes
+		resynchend_pak resynchend;  //
+		resynch_pak resynchpak;     //
+		UINT8 resynchgot;           //
 		UINT8 textcmd[MAXTEXTCMD+1]; //   66049 bytes
 		filetx_pak filetxpak;       //      139 bytes
 		clientconfig_pak clientcfg; //      136 bytes
@@ -379,7 +389,6 @@ typedef struct
 		serverrefuse_pak serverrefuse; // 65025 bytes
 		askinfo_pak askinfo;        //       61 bytes
 		msaskinfo_pak msaskinfo;    //       22 bytes
-		cons_pak consistency;       //      544 bytes
 		plrinfo playerinfo[MAXPLAYERS]; // 1152 bytes
 		plrconfig playerconfig[MAXPLAYERS]; // (up to) 896 bytes
 #ifdef NEWPING
@@ -437,7 +446,7 @@ extern UINT32 realpingtable[MAXPLAYERS];
 extern UINT32 playerpingtable[MAXPLAYERS];
 #endif
 
-extern consvar_t cv_joinnextround, cv_allownewplayer, cv_maxplayers, cv_consfailprotect, cv_blamecfail, cv_maxsend;
+extern consvar_t cv_joinnextround, cv_allownewplayer, cv_maxplayers, cv_resynchattempts, cv_blamecfail, cv_maxsend;
 
 // used in d_net, the only dependence
 tic_t ExpandTics(INT32 low);
@@ -492,4 +501,5 @@ void D_ResetTiccmds(void);
 tic_t GetLag(INT32 node);
 UINT8 GetFreeXCmdSize(void);
 
+extern UINT8 hu_resynching;
 #endif
diff --git a/src/d_main.c b/src/d_main.c
index 2492f622ce..0259039045 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -401,7 +401,10 @@ static void D_Display(void)
 		if (lastdraw)
 		{
 			if (rendermode == render_soft)
+			{
 				VID_BlitLinearScreen(screens[0], screens[1], vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes);
+				usebuffer = true;
+			}
 			lastdraw = false;
 		}
 
@@ -1094,7 +1097,7 @@ void D_SRB2Main(void)
 	W_VerifyFileMD5(1, "a894044b555dfcc71865cee16a996e88"); // zones.dta
 	W_VerifyFileMD5(2, "4c410c1de6e0440cc5b2858dcca80c3e"); // player.dta
 	W_VerifyFileMD5(3, "85901ad4bf94637e5753d2ac2c03ea26"); // rings.dta
-	W_VerifyFileMD5(4, "12c58561edf3be16a15505f1d5eacee0"); // patch.dta
+	W_VerifyFileMD5(4, "e868046d2d2da1d8c706c900edfb03f8"); // patch.dta
 
 	// don't check music.dta because people like to modify it, and it doesn't matter if they do
 	// ...except it does if they slip maps in there, and that's what W_VerifyNMUSlumps is for.
diff --git a/src/d_net.c b/src/d_net.c
index 3c2213fca2..d93b80c95b 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -784,18 +784,6 @@ static void DebugPrintpacket(const char *header)
 			fprintfstring((char *)&netbuffer->u.serverpak.cmds[netbuffer->u.serverpak.numslots*netbuffer->u.serverpak.numtics],(size_t)(
 				&((UINT8 *)netbuffer)[doomcom->datalength] - (UINT8 *)&netbuffer->u.serverpak.cmds[netbuffer->u.serverpak.numslots*netbuffer->u.serverpak.numtics]));
 			break;
-		case PT_CONSISTENCY:
-			fprintf(debugfile, "    randomseed %d playernum %d hasmo %d\n",
-				netbuffer->u.consistency.randomseed, netbuffer->u.consistency.playernum, netbuffer->u.consistency.hasmo);
-			fprintf(debugfile, "    x %d y %d z %d momx %d momy %d momz %d\n",
-				netbuffer->u.consistency.x, netbuffer->u.consistency.y, netbuffer->u.consistency.z,
-				netbuffer->u.consistency.momx, netbuffer->u.consistency.momy, netbuffer->u.consistency.momz);
-			fprintf(debugfile, "    angle %d health %d eflags %d flags %d flags2 %d\n",
-				netbuffer->u.consistency.angle, netbuffer->u.consistency.health, netbuffer->u.consistency.eflags,
-				netbuffer->u.consistency.flags, netbuffer->u.consistency.flags2);
-			fprintf(debugfile, "    friction %d movefactor %d tics %d statenum %d\n",
-				netbuffer->u.consistency.friction, netbuffer->u.consistency.movefactor,
-				netbuffer->u.consistency.tics, (INT32)netbuffer->u.consistency.statenum);
 		case PT_CLIENTCMD:
 		case PT_CLIENT2CMD:
 		case PT_CLIENTMIS:
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 57445244d6..06c6fcb0ea 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -3138,10 +3138,8 @@ static void Command_Addfile(void)
 	p = fn+strlen(fn);
 	while(--p >= fn)
 		if (*p == '\\' || *p == '/' || *p == ':')
-		{
-			++p;
 			break;
-		}
+	++p;
 	WRITESTRINGN(buf_p,p,240);
 
 	{
@@ -4094,8 +4092,17 @@ static void Command_Isgamemodified_f(void)
 
 static void Command_Cheats_f(void)
 {
+	if (COM_CheckParm("off"))
+	{
+		CV_ResetCheatNetVars();
+		return;
+	}
+
 	if (CV_CheatsEnabled())
+	{
 		CONS_Printf(M_GetText("At least one CHEAT-marked variable has been changed -- Cheats are enabled.\n"));
+		CONS_Printf(M_GetText("Type CHEATS OFF to reset all cheat variables to default."));
+	}
 	else
 		CONS_Printf(M_GetText("No CHEAT-marked variables are changed -- Cheats are disabled.\n"));
 }
diff --git a/src/d_player.h b/src/d_player.h
index 3abc6ddf3d..4b29785464 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -147,7 +147,7 @@ typedef enum
 	PF_TAGGED            = 1<<27, // Player has been tagged and awaits the next round in hide and seek.
 	PF_TAGIT             = 1<<28, // The player is it! For Tag Mode
 
-	// free: 1<<29, 1<<31
+	// free: 1<<29 through 1<<31
 } pflags_t;
 
 typedef enum
@@ -268,10 +268,9 @@ typedef struct player_s
 	// It is updated with cmd->aiming.
 	angle_t aiming;
 
-	angle_t awayviewaiming; // Used for cut-away view
-
 	// This is only used between levels,
 	// mo->health is used during levels.
+	/// \todo Remove this.  We don't need a second health definition for players.
 	INT32 health;
 
 	SINT8 pity; // i pity the fool.
@@ -324,15 +323,14 @@ typedef struct player_s
 
 	fixed_t jumpfactor; // How high can the player jump?
 
-	INT32 lives;
-	INT32 continues; // continues that player has acquired
+	SINT8 lives;
+	SINT8 continues; // continues that player has acquired
 
-	INT32 xtralife; // Ring Extra Life counter
+	SINT8 xtralife; // Ring Extra Life counter
 	UINT8 gotcontinue; // Got continue from this stage?
 
 	fixed_t speed; // Player's speed (distance formula of MOMX and MOMY values)
-	INT32 jumping; // Jump counter
-
+	UINT8 jumping; // Jump counter
 	UINT8 secondjump;
 
 	UINT8 fly1; // Tails flying
@@ -357,10 +355,10 @@ typedef struct player_s
 	/////////////////////
 	// Race Mode Stuff //
 	/////////////////////
-	INT32 numboxes; // Number of item boxes obtained for Race Mode
-	INT32 totalring; // Total number of rings obtained for Race Mode
+	INT16 numboxes; // Number of item boxes obtained for Race Mode
+	INT16 totalring; // Total number of rings obtained for Race Mode
 	tic_t realtime; // integer replacement for leveltime
-	UINT32 laps; // Number of laps (optional)
+	UINT8 laps; // Number of laps (optional)
 
 	////////////////////
 	// CTF Mode Stuff //
@@ -421,6 +419,7 @@ typedef struct player_s
 
 	mobj_t *awayviewmobj;
 	INT32 awayviewtics;
+	angle_t awayviewaiming; // Used for cut-away view
 
 	boolean spectator;
 	UINT8 bot;
diff --git a/src/dehacked.c b/src/dehacked.c
index e25f66e72e..6eae1ce23e 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -1132,7 +1132,7 @@ static void readlevelheader(MYFILE *f, INT32 num)
 			else if (fastcmp(word, "PALETTE"))
 				mapheaderinfo[num-1]->palette = (UINT16)i;
 			else if (fastcmp(word, "NUMLAPS"))
-				mapheaderinfo[num-1]->numlaps = (UINT32)i;
+				mapheaderinfo[num-1]->numlaps = (UINT8)i;
 			else if (fastcmp(word, "UNLOCKABLE"))
 			{
 				if (i >= 0 && i <= MAXUNLOCKABLES) // 0 for no unlock required, anything else requires something
@@ -7325,24 +7325,29 @@ static const char *const HUDITEMS_LIST[] = {
 	"LIVESPIC",
 	"LIVESNUM",
 	"LIVESX",
-	"RINGSSPLIT",
-	"RINGSNUMSPLIT",
+
 	"RINGS",
+	"RINGSSPLIT",
 	"RINGSNUM",
+	"RINGSNUMSPLIT",
+
 	"SCORE",
 	"SCORENUM",
+
+	"TIME",
 	"TIMESPLIT",
-	"SECONDSSPLIT",
+	"MINUTES",
 	"MINUTESSPLIT",
+	"TIMECOLON",
 	"TIMECOLONSPLIT",
-	"TIME",
-	"TICS",
 	"SECONDS",
-	"MINUTES",
-	"TIMECOLON",
+	"SECONDSSPLIT",
 	"TIMETICCOLON",
-	"SS_TOTALRINGS_SPLIT",
+	"TICS",
+
 	"SS_TOTALRINGS",
+	"SS_TOTALRINGS_SPLIT",
+
 	"GETRINGS",
 	"GETRINGSNUM",
 	"TIMELEFT",
@@ -7683,6 +7688,9 @@ struct {
 	{"V_70TRANS",V_70TRANS},
 	{"V_80TRANS",V_80TRANS},
 	{"V_90TRANS",V_90TRANS},
+	{"V_HUDTRANSHALF",V_HUDTRANSHALF},
+	{"V_HUDTRANS",V_HUDTRANS},
+	{"V_HUDTRANSDOUBLE",V_HUDTRANSDOUBLE},
 	{"V_AUTOFADEOUT",V_AUTOFADEOUT},
 	{"V_RETURN8",V_RETURN8},
 	{"V_OFFSET",V_OFFSET},
diff --git a/src/doomdata.h b/src/doomdata.h
index 4023c49792..371decc2d6 100644
--- a/src/doomdata.h
+++ b/src/doomdata.h
@@ -131,6 +131,7 @@ typedef struct
 #define ML_NOSONIC           2048
 #define ML_NOTAILS           4096
 #define ML_NOKNUX            8192
+#define ML_NETONLY          14336 // all of the above
 
 // Bounce off walls!
 #define ML_BOUNCY           16384
diff --git a/src/doomdef.h b/src/doomdef.h
index d4ad4a68a8..a5454851fd 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -144,8 +144,8 @@ extern FILE *logstream;
 #define VERSIONSTRING "Trunk"
 #else
 #define VERSION    201 // Game version
-#define SUBVERSION 3   // more precise version number
-#define VERSIONSTRING "v2.1.3"
+#define SUBVERSION 4  // more precise version number
+#define VERSIONSTRING "v2.1.4"
 #endif
 
 // Modification options
@@ -201,7 +201,7 @@ extern FILE *logstream;
 // it's only for detection of the version the player is using so the MS can alert them of an update.
 // Only set it higher, not lower, obviously.
 // Note that we use this to help keep internal testing in check; this is why v2.1.0 is not version "1".
-#define MODVERSION 7
+#define MODVERSION 9
 
 
 
diff --git a/src/doomstat.h b/src/doomstat.h
index 576b70099f..642e9bfc32 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -227,7 +227,7 @@ typedef struct
 	UINT8 cutscenenum;    ///< Cutscene number to use, 0 for none.
 	INT16 countdown;      ///< Countdown until level end?
 	UINT16 palette;       ///< PAL lump to use on this map
-	UINT32 numlaps;       ///< Number of laps in circuit mode, unless overridden.
+	UINT8 numlaps;        ///< Number of laps in circuit mode, unless overridden.
 	SINT8 unlockrequired; ///< Is an unlockable required to play this level? -1 if no.
 	UINT8 levelselect;    ///< Is this map available in the level select? If so, which map list is it available in?
 	SINT8 bonustype;      ///< What type of bonus does this level have? (-1 for null.)
diff --git a/src/g_game.c b/src/g_game.c
index e3f2a2813e..dfd9aacd08 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1467,10 +1467,12 @@ static void Analog_OnChange(void)
 {
 	if (!cv_cam_dist.string)
 		return;
+
+	// cameras are not initialized at this point
+
 	if (leveltime > 1)
 		CV_SetValue(&cv_cam_dist, 128);
-
-	if (netgame || !camera.chase)
+	if (netgame)
 		CV_StealthSetValue(&cv_analog, 0);
 	else if (cv_analog.value || demoplayback)
 		CV_SetValue(&cv_cam_dist, 192);
@@ -1480,9 +1482,12 @@ static void Analog2_OnChange(void)
 {
 	if (!splitscreen || !cv_cam2_dist.string)
 		return;
+
+	// cameras are not initialized at this point
+
 	if (leveltime > 1)
 		CV_SetValue(&cv_cam2_dist, 128);
-	if (netgame || !camera2.chase)
+	if (netgame)
 		CV_StealthSetValue(&cv_analog2, 0);
 	else if (cv_analog2.value)
 		CV_SetValue(&cv_cam2_dist, 192);
@@ -1927,7 +1932,6 @@ void G_PlayerReborn(INT32 player)
 	INT32 score;
 	INT32 lives;
 	INT32 continues;
-	INT32 xtralife;
 	UINT8 charability;
 	UINT8 charability2;
 	fixed_t normalspeed;
@@ -1952,9 +1956,9 @@ void G_PlayerReborn(INT32 player)
 	INT32 starpostangle;
 	fixed_t jumpfactor;
 	INT32 exiting;
-	INT32 numboxes;
-	INT32 laps;
-	INT32 totalring;
+	INT16 numboxes;
+	INT16 totalring;
+	UINT8 laps;
 	UINT8 mare;
 	UINT8 skincolor;
 	INT32 skin;
@@ -1966,7 +1970,6 @@ void G_PlayerReborn(INT32 player)
 	score = players[player].score;
 	lives = players[player].lives;
 	continues = players[player].continues;
-	xtralife = players[player].xtralife;
 	ctfteam = players[player].ctfteam;
 	exiting = players[player].exiting;
 	jointime = players[player].jointime;
@@ -2017,7 +2020,6 @@ void G_PlayerReborn(INT32 player)
 	p->lives = lives;
 	p->continues = continues;
 	p->pflags = pflags;
-	p->xtralife = xtralife;
 	p->ctfteam = ctfteam;
 	p->jointime = jointime;
 	p->spectator = spectator;
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index 43d4537fd3..168761a07d 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -67,6 +67,10 @@ typedef UINT8 GLRGB[3];
 
 #define BLENDMODE PF_Translucent
 
+static UINT8 softwaretranstogl[11]    = {  0, 25, 51, 76,102,127,153,178,204,229,255};
+static UINT8 softwaretranstogl_hi[11] = {  0, 51,102,153,204,255,255,255,255,255,255};
+static UINT8 softwaretranstogl_lo[11] = {  0, 12, 24, 36, 48, 60, 71, 83, 95,111,127};
+
 //
 // -----------------+
 // HWR_DrawPatch    : Draw a 'tile' graphic
@@ -128,24 +132,16 @@ void HWR_DrawPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option)
 		flags |= PF_ForceWrapY;
 
 	// clip it since it is used for bunny scroll in doom I
-	if (option & V_TRANSLUCENT)
-	{
-		FSurfaceInfo Surf;
-		Surf.FlatColor.s.red = Surf.FlatColor.s.green = Surf.FlatColor.s.blue = 0xff;
-		Surf.FlatColor.s.alpha = (UINT8)cv_grtranslucenthud.value;
-		flags |= PF_Modulated;
-		HWD.pfnDrawPolygon(&Surf, v, 4, flags);
-	}
-	else
-		HWD.pfnDrawPolygon(NULL, v, 4, flags);
+	HWD.pfnDrawPolygon(NULL, v, 4, flags);
 }
 
-void HWR_DrawSciencePatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, INT32 option, fixed_t scale)
+void HWR_DrawFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, 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);
 
 //  3--2
 //  | /|
@@ -153,11 +149,17 @@ void HWR_DrawSciencePatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, INT32 option,
 //  0--1
 	float sdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f;
 	float sdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f;
-	float pdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f*FIXED_TO_FLOAT(scale);
-	float pdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f*FIXED_TO_FLOAT(scale);
+	float pdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f*FIXED_TO_FLOAT(pscale);
+	float pdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f*FIXED_TO_FLOAT(pscale);
+
+	if (alphalevel >= 10 && alphalevel < 13)
+		return;
 
 	// make patch ready in hardware cache
-	HWR_GetPatch(gpatch);
+	if (!colormap)
+		HWR_GetPatch(gpatch);
+	else
+		HWR_GetMappedPatch(gpatch, colormap);
 
 	switch (option & V_SCALEPATCHMASK)
 	{
@@ -197,11 +199,14 @@ void HWR_DrawSciencePatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, INT32 option,
 		flags |= PF_ForceWrapY;
 
 	// clip it since it is used for bunny scroll in doom I
-	if (option & V_TRANSLUCENT)
+	if (alphalevel)
 	{
 		FSurfaceInfo Surf;
 		Surf.FlatColor.s.red = Surf.FlatColor.s.green = Surf.FlatColor.s.blue = 0xff;
-		Surf.FlatColor.s.alpha = (UINT8)cv_grtranslucenthud.value;
+		if (alphalevel == 13) Surf.FlatColor.s.alpha = softwaretranstogl_lo[cv_translucenthud.value];
+		else if (alphalevel == 14) Surf.FlatColor.s.alpha = softwaretranstogl[cv_translucenthud.value];
+		else if (alphalevel == 15) Surf.FlatColor.s.alpha = softwaretranstogl_hi[cv_translucenthud.value];
+		else Surf.FlatColor.s.alpha = softwaretranstogl[10-alphalevel];
 		flags |= PF_Modulated;
 		HWD.pfnDrawPolygon(&Surf, v, 4, flags);
 	}
@@ -209,13 +214,13 @@ void HWR_DrawSciencePatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, INT32 option,
 		HWD.pfnDrawPolygon(NULL, v, 4, flags);
 }
 
-void HWR_DrawCroppedPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option, fixed_t scale, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h)
+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)
 {
 	FOutVector v[4];
 	FBITFIELD flags;
-
 	float cx = FIXED_TO_FLOAT(x);
 	float cy = FIXED_TO_FLOAT(y);
+	UINT8 alphalevel = ((option & V_ALPHAMASK) >> V_ALPHASHIFT);
 
 //  3--2
 //  | /|
@@ -223,8 +228,11 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option, fix
 //  0--1
 	float sdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f;
 	float sdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f;
-	float pdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f*FIXED_TO_FLOAT(scale);
-	float pdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f*FIXED_TO_FLOAT(scale);
+	float pdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f*FIXED_TO_FLOAT(pscale);
+	float pdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f*FIXED_TO_FLOAT(pscale);
+
+	if (alphalevel >= 10 && alphalevel < 13)
+		return;
 
 	// make patch ready in hardware cache
 	HWR_GetPatch(gpatch);
@@ -247,17 +255,17 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option, fix
 	if (option & V_NOSCALESTART)
 		sdupx = sdupy = 2.0f;
 
-	v[0].x = v[3].x = (cx*sdupx-gpatch->leftoffset*pdupx)/vid.width - 1;
-	v[2].x = v[1].x = ((cx-sx)*sdupx+(w-gpatch->leftoffset)*pdupx)/vid.width - 1;
-	v[0].y = v[1].y = 1-(cy*sdupy-gpatch->topoffset*pdupy)/vid.height;
-	v[2].y = v[3].y = 1-((cy-sy)*sdupy+(h-gpatch->topoffset)*pdupy)/vid.height;
+	v[0].x = v[3].x =     (cx*sdupx -           gpatch->leftoffset  * pdupx) / vid.width - 1;
+	v[2].x = v[1].x =     (cx*sdupx + ((w-sx) - gpatch->leftoffset) * pdupx) / vid.width - 1;
+	v[0].y = v[1].y = 1 - (cy*sdupy -           gpatch->topoffset   * pdupy) / vid.height;
+	v[2].y = v[3].y = 1 - (cy*sdupy + ((h-sy) - gpatch->topoffset)  * pdupy) / vid.height;
 
 	v[0].z = v[1].z = v[2].z = v[3].z = 1.0f;
 
-	v[0].sow = v[3].sow = ((float)sx/(float)gpatch->height);
-	v[2].sow = v[1].sow = gpatch->max_s*((float)w/(float)gpatch->width);
-	v[0].tow = v[1].tow = ((float)sy/(float)gpatch->height);
-	v[2].tow = v[3].tow = gpatch->max_t*((float)h/(float)gpatch->height);
+	v[0].sow = v[3].sow = ((sx)/(float)gpatch->width )*gpatch->max_s;
+	v[2].sow = v[1].sow = ((w )/(float)gpatch->width )*gpatch->max_s;
+	v[0].tow = v[1].tow = ((sy)/(float)gpatch->height)*gpatch->max_t;
+	v[2].tow = v[3].tow = ((h )/(float)gpatch->height)*gpatch->max_t;
 
 	flags = BLENDMODE|PF_Clip|PF_NoZClip|PF_NoDepthTest;
 
@@ -267,217 +275,14 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option, fix
 		flags |= PF_ForceWrapY;
 
 	// clip it since it is used for bunny scroll in doom I
-	if (option & V_TRANSLUCENT)
-	{
-		FSurfaceInfo Surf;
-		Surf.FlatColor.s.red = Surf.FlatColor.s.green = Surf.FlatColor.s.blue = 0xff;
-		Surf.FlatColor.s.alpha = (UINT8)cv_grtranslucenthud.value;
-		flags |= PF_Modulated;
-		HWD.pfnDrawPolygon(&Surf, v, 4, flags);
-	}
-	else
-		HWD.pfnDrawPolygon(NULL, v, 4, flags);
-}
-
-void HWR_DrawClippedPatch (GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option)
-{
-	// hardware clips the patch quite nicely anyway :)
-	HWR_DrawPatch(gpatch, x, y, option); /// \todo do real cliping
-}
-
-// Only supports one kind of translucent for now. Tails 06-12-2003
-// Boked
-// Alam_GBC: Why? you could not get a FSurfaceInfo to set the alpha channel?
-void HWR_DrawTranslucentPatch (GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option)
-{
-	FOutVector      v[4];
-	FBITFIELD flags;
-
-//  3--2
-//  | /|
-//  |/ |
-//  0--1
-	float sdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f;
-	float sdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f;
-	float pdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f;
-	float pdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f;
-	FSurfaceInfo Surf;
-
-	// make patch ready in hardware cache
-	HWR_GetPatch (gpatch);
-
-	switch (option & V_SCALEPATCHMASK)
-	{
-	case V_NOSCALEPATCH:
-		pdupx = pdupy = 2.0f;
-		break;
-	case V_SMALLSCALEPATCH:
-		pdupx = 2.0f * FIXED_TO_FLOAT(vid.fsmalldupx);
-		pdupy = 2.0f * FIXED_TO_FLOAT(vid.fsmalldupy);
-		break;
-	case V_MEDSCALEPATCH:
-		pdupx = 2.0f * FIXED_TO_FLOAT(vid.fmeddupx);
-		pdupy = 2.0f * FIXED_TO_FLOAT(vid.fmeddupy);
-		break;
-	}
-
-	if (option & V_NOSCALESTART)
-		sdupx = sdupy = 2.0f;
-
-	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].sow = v[3].sow = 0.0f;
-	v[2].sow = v[1].sow = gpatch->max_s;
-	v[0].tow = v[1].tow = 0.0f;
-	v[2].tow = v[3].tow = gpatch->max_t;
-
-	flags = PF_Modulated | BLENDMODE | PF_Clip | PF_NoZClip | PF_NoDepthTest;
-
-	if (option & V_WRAPX)
-		flags |= PF_ForceWrapX;
-	if (option & V_WRAPY)
-		flags |= PF_ForceWrapY;
-
-	Surf.FlatColor.s.red = Surf.FlatColor.s.green = Surf.FlatColor.s.blue = 0xff;
-	// Alam_GBC: There, you have translucent HW Draw, OK?
-	if ((option & V_TRANSLUCENT) && cv_grtranslucenthud.value != 255)
-	{
-		Surf.FlatColor.s.alpha = (UINT8)(cv_grtranslucenthud.value/2);
-	}
-	else
-		Surf.FlatColor.s.alpha = 127;
-
-	HWD.pfnDrawPolygon(&Surf, v, 4, flags);
-}
-
-// Draws a patch 2x as small SSNTails 06-10-2003
-void HWR_DrawSmallPatch (GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option, const UINT8 *colormap)
-{
-	FOutVector      v[4];
-	FBITFIELD flags;
-
-	float sdupx = FIXED_TO_FLOAT(vid.fdupx);
-	float sdupy = FIXED_TO_FLOAT(vid.fdupy);
-	float pdupx = FIXED_TO_FLOAT(vid.fdupx);
-	float pdupy = FIXED_TO_FLOAT(vid.fdupy);
-
-	// make patch ready in hardware cache
-	HWR_GetMappedPatch (gpatch, colormap);
-
-	switch (option & V_SCALEPATCHMASK)
-	{
-	case V_NOSCALEPATCH:
-		pdupx = pdupy = 2.0f;
-		break;
-	case V_SMALLSCALEPATCH:
-		pdupx = 2.0f * FIXED_TO_FLOAT(vid.fsmalldupx);
-		pdupy = 2.0f * FIXED_TO_FLOAT(vid.fsmalldupy);
-		break;
-	case V_MEDSCALEPATCH:
-		pdupx = 2.0f * FIXED_TO_FLOAT(vid.fmeddupx);
-		pdupy = 2.0f * FIXED_TO_FLOAT(vid.fmeddupy);
-		break;
-	}
-
-	if (option & V_NOSCALESTART)
-		sdupx = sdupy = 2.0f;
-
-	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].sow = v[3].sow = 0.0f;
-	v[2].sow = v[1].sow = gpatch->max_s;
-	v[0].tow = v[1].tow = 0.0f;
-	v[2].tow = v[3].tow = gpatch->max_t;
-
-	flags = BLENDMODE | PF_Clip | PF_NoZClip | PF_NoDepthTest;
-
-	if (option & V_WRAPX)
-		flags |= PF_ForceWrapX;
-	if (option & V_WRAPY)
-		flags |= PF_ForceWrapY;
-
-	// clip it since it is used for bunny scroll in doom I
-	if (option & V_TRANSLUCENT)
-	{
-		FSurfaceInfo Surf;
-		Surf.FlatColor.s.red = Surf.FlatColor.s.green = Surf.FlatColor.s.blue = 0xff;
-		Surf.FlatColor.s.alpha = (UINT8)cv_grtranslucenthud.value;
-		flags |= PF_Modulated;
-		HWD.pfnDrawPolygon(&Surf, v, 4, flags);
-	}
-	else
-		HWD.pfnDrawPolygon(NULL, v, 4, flags);
-}
-
-//
-// HWR_DrawMappedPatch(): Like HWR_DrawPatch but with translated color
-//
-void HWR_DrawMappedPatch (GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option, const UINT8 *colormap)
-{
-	FOutVector      v[4];
-	FBITFIELD flags;
-
-	float sdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f;
-	float sdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f;
-	float pdupx = FIXED_TO_FLOAT(vid.fdupx)*2.0f;
-	float pdupy = FIXED_TO_FLOAT(vid.fdupy)*2.0f;
-
-	// make patch ready in hardware cache
-	HWR_GetMappedPatch (gpatch, colormap);
-
-	switch (option & V_SCALEPATCHMASK)
-	{
-	case V_NOSCALEPATCH:
-		pdupx = pdupy = 2.0f;
-		break;
-	case V_SMALLSCALEPATCH:
-		pdupx = 2.0f * FIXED_TO_FLOAT(vid.fsmalldupx);
-		pdupy = 2.0f * FIXED_TO_FLOAT(vid.fsmalldupy);
-		break;
-	case V_MEDSCALEPATCH:
-		pdupx = 2.0f * FIXED_TO_FLOAT(vid.fmeddupx);
-		pdupy = 2.0f * FIXED_TO_FLOAT(vid.fmeddupy);
-		break;
-	}
-
-	if (option & V_NOSCALESTART)
-		sdupx = sdupy = 2.0f;
-
-	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].sow = v[3].sow = 0.0f;
-	v[2].sow = v[1].sow = gpatch->max_s;
-	v[0].tow = v[1].tow = 0.0f;
-	v[2].tow = v[3].tow = gpatch->max_t;
-
-	flags = BLENDMODE | PF_Clip | PF_NoZClip | PF_NoDepthTest;
-
-	if (option & V_WRAPX)
-		flags |= PF_ForceWrapX;
-	if (option & V_WRAPY)
-		flags |= PF_ForceWrapY;
-
-	// clip it since it is used for bunny scroll in doom I
-	if (option & V_TRANSLUCENT)
+	if (alphalevel)
 	{
 		FSurfaceInfo Surf;
 		Surf.FlatColor.s.red = Surf.FlatColor.s.green = Surf.FlatColor.s.blue = 0xff;
-		Surf.FlatColor.s.alpha = (UINT8)cv_grtranslucenthud.value;
+		if (alphalevel == 13) Surf.FlatColor.s.alpha = softwaretranstogl_lo[cv_translucenthud.value];
+		else if (alphalevel == 14) Surf.FlatColor.s.alpha = softwaretranstogl[cv_translucenthud.value];
+		else if (alphalevel == 15) Surf.FlatColor.s.alpha = softwaretranstogl_hi[cv_translucenthud.value];
+		else Surf.FlatColor.s.alpha = softwaretranstogl[10-alphalevel];
 		flags |= PF_Modulated;
 		HWD.pfnDrawPolygon(&Surf, v, 4, flags);
 	}
@@ -517,15 +322,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
-	if (cv_grtranslucenthud.value != 255)
-	{
-		FSurfaceInfo Surf;
-		Surf.FlatColor.s.red = Surf.FlatColor.s.green = Surf.FlatColor.s.blue = 0xff;
-		Surf.FlatColor.s.alpha = (UINT8)cv_grtranslucenthud.value;
-		HWD.pfnDrawPolygon(&Surf, v, 4, PF_Modulated | BLENDMODE | PF_NoDepthTest | PF_Clip | PF_NoZClip);
-	}
-	else
-		HWD.pfnDrawPolygon(NULL, v, 4, BLENDMODE | PF_NoDepthTest | PF_Clip | PF_NoZClip);
+	HWD.pfnDrawPolygon(NULL, v, 4, BLENDMODE | PF_NoDepthTest | PF_Clip | PF_NoZClip);
 }
 
 // ==========================================================================
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index a6cf3fb7f1..c93d4ea6bd 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -44,12 +44,9 @@ boolean HWR_Screenshot(const char *lbmname);
 void HWR_InitTextureMapping(void);
 void HWR_SetViewSize(void);
 void HWR_DrawPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option);
-void HWR_DrawClippedPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option);
-void HWR_DrawSciencePatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, INT32 option, fixed_t scale);
+void HWR_DrawFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t scale, 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_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, INT32 option, fixed_t scale, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h);
-void HWR_DrawTranslucentPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option);
-void HWR_DrawSmallPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option, const UINT8 *colormap);
-void HWR_DrawMappedPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option, const UINT8 *colormap);
 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);
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index c8f452a69e..14f6d54adc 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -903,7 +903,7 @@ static inline void HU_DrawCrosshair(void)
 #endif
 		y = viewwindowy + (viewheight>>1);
 
-	V_DrawTranslucentPatch(vid.width>>1, y, V_NOSCALESTART|V_OFFSET, crosshair[i - 1]);
+	V_DrawScaledPatch(vid.width>>1, y, V_NOSCALESTART|V_OFFSET|V_TRANSLUCENT, crosshair[i - 1]);
 }
 
 static inline void HU_DrawCrosshair2(void)
@@ -933,7 +933,7 @@ static inline void HU_DrawCrosshair2(void)
 #endif
 			y += viewheight;
 
-		V_DrawTranslucentPatch(vid.width>>1, y, V_NOSCALESTART|V_OFFSET, crosshair[i - 1]);
+		V_DrawScaledPatch(vid.width>>1, y, V_NOSCALESTART|V_OFFSET|V_TRANSLUCENT, crosshair[i - 1]);
 	}
 }
 
@@ -1086,6 +1086,10 @@ void HU_Drawer(void)
 
 	if (!automapactive && cv_crosshair2.value && !demoplayback && !camera2.chase && !players[secondarydisplayplayer].spectator)
 		HU_DrawCrosshair2();
+
+	// draw desynch text
+	if (hu_resynching)
+		V_DrawCenteredString(BASEVIDWIDTH/2, 180, V_YELLOWMAP, "Resynching...");
 }
 
 //======================================================================
@@ -1619,7 +1623,7 @@ static void HU_DrawRankings(void)
 				{
 					if (circuitmap)
 					{
-						if (players[i].laps+1 >= tab[scorelines].count && completed[i] == false)
+						if ((unsigned)players[i].laps+1 >= tab[scorelines].count && completed[i] == false)
 						{
 							tab[scorelines].count = players[i].laps+1;
 							tab[scorelines].num = i;
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index f80d9809e9..47bd4370fd 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -222,21 +222,7 @@ static int libd_draw(lua_State *L)
 
 	flags &= ~V_PARAMMASK; // Don't let crashes happen.
 
-	if (colormap)
-	{
-		if (flags & V_ALPHAMASK)
-			V_DrawTranslucentMappedPatch(x, y, flags, patch, colormap);
-		else
-			V_DrawMappedPatch(x, y, flags, patch, colormap);
-	}
-	else
-	{
-		if (flags & V_ALPHAMASK)
-			V_DrawTranslucentPatch(x, y, flags, patch);
-		else
-			V_DrawScaledPatch(x, y, flags, patch);
-	}
-
+	V_DrawFixedPatch(x<<FRACBITS, y<<FRACBITS, FRACUNIT, flags, patch, colormap);
 	return 0;
 }
 
@@ -245,18 +231,55 @@ static int libd_drawScaled(lua_State *L)
 	fixed_t x, y, scale;
 	INT32 flags;
 	patch_t *patch;
+	const UINT8 *colormap = NULL;
 
 	if (!hud_running)
 		return luaL_error(L, "HUD rendering code should not be called outside of rendering hooks!");
 
 	x = luaL_checkinteger(L, 1);
 	y = luaL_checkinteger(L, 2);
-	patch = *((patch_t **)luaL_checkudata(L, 3, META_PATCH));
-	scale = luaL_optinteger(L, 4, FRACUNIT);
+	scale = luaL_checkinteger(L, 3);
+	patch = *((patch_t **)luaL_checkudata(L, 4, META_PATCH));
+	flags = luaL_optinteger(L, 5, 0);
+	if (!lua_isnoneornil(L, 6))
+		colormap = luaL_checkudata(L, 6, META_COLORMAP);
+
+	flags &= ~V_PARAMMASK; // Don't let crashes happen.
+
+	V_DrawFixedPatch(x, y, scale, flags, patch, colormap);
+	return 0;
+}
+
+static int libd_drawNum(lua_State *L)
+{
+	INT32 x, y, flags, num;
+	if (!hud_running)
+		return luaL_error(L, "HUD rendering code should not be called outside of rendering hooks!");
+
+	x = luaL_checkinteger(L, 1);
+	y = luaL_checkinteger(L, 2);
+	num = luaL_checkinteger(L, 3);
+	flags = luaL_optinteger(L, 4, 0);
+	flags &= ~V_PARAMMASK; // Don't let crashes happen.
+
+	V_DrawTallNum(x, y, flags, num);
+	return 0;
+}
+
+static int libd_drawPaddedNum(lua_State *L)
+{
+	INT32 x, y, flags, num, digits;
+	if (!hud_running)
+		return luaL_error(L, "HUD rendering code should not be called outside of rendering hooks!");
+
+	x = luaL_checkinteger(L, 1);
+	y = luaL_checkinteger(L, 2);
+	num = abs(luaL_checkinteger(L, 3));
+	digits = luaL_optinteger(L, 4, 2);
 	flags = luaL_optinteger(L, 5, 0);
 	flags &= ~V_PARAMMASK; // Don't let crashes happen.
 
-	V_DrawSciencePatch(x, y, flags, patch, scale);
+	V_DrawPaddedTallNum(x, y, flags, num, digits);
 	return 0;
 }
 
@@ -322,6 +345,8 @@ static luaL_Reg lib_draw[] = {
 	{"cachePatch", libd_cachePatch},
 	{"draw", libd_draw},
 	{"drawScaled", libd_drawScaled},
+	{"drawNum", libd_drawNum},
+	{"drawPaddedNum", libd_drawPaddedNum},
 	{"drawFill", libd_drawFill},
 	{"drawString", libd_drawString},
 	{"stringWidth", libd_stringWidth},
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 6aa503a6d9..2c968218c6 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -742,7 +742,7 @@ static int lib_setSfxInfo(lua_State *L)
 		enum sfxinfo_write i;
 
 		if (lua_isnumber(L, 2))
-			i = lua_tointeger(L, 2);
+			i = lua_tointeger(L, 2) - 1; // lua is one based, this enum is zero based.
 		else
 			i = luaL_checkoption(L, 2, NULL, sfxinfo_wopt);
 
diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c
index 34f79a42b2..18f8b28305 100644
--- a/src/lua_playerlib.c
+++ b/src/lua_playerlib.c
@@ -118,8 +118,6 @@ static int player_get(lua_State *L)
 		lua_pushinteger(L, plr->bob);
 	else if (fastcmp(field,"aiming"))
 		lua_pushinteger(L, plr->aiming);
-	else if (fastcmp(field,"awayviewaiming"))
-		lua_pushinteger(L, plr->awayviewaiming);
 	else if (fastcmp(field,"health"))
 		lua_pushinteger(L, plr->health);
 	else if (fastcmp(field,"pity"))
@@ -187,7 +185,7 @@ static int player_get(lua_State *L)
 	else if (fastcmp(field,"speed"))
 		lua_pushinteger(L, plr->speed);
 	else if (fastcmp(field,"jumping"))
-		lua_pushinteger(L, plr->jumping);
+		lua_pushboolean(L, plr->jumping);
 	else if (fastcmp(field,"secondjump"))
 		lua_pushinteger(L, plr->secondjump);
 	else if (fastcmp(field,"fly1"))
@@ -306,6 +304,8 @@ static int player_get(lua_State *L)
 		LUA_PushUserdata(L, plr->awayviewmobj, META_MOBJ);
 	else if (fastcmp(field,"awayviewtics"))
 		lua_pushinteger(L, plr->awayviewtics);
+	else if (fastcmp(field,"awayviewaiming"))
+		lua_pushinteger(L, plr->awayviewaiming);
 	else if (fastcmp(field,"spectator"))
 		lua_pushinteger(L, plr->spectator);
 	else if (fastcmp(field,"bot"))
@@ -365,8 +365,6 @@ static int player_set(lua_State *L)
 		else if (plr == &players[secondarydisplayplayer])
 			localaiming2 = plr->aiming;
 	}
-	else if (fastcmp(field,"awayviewaiming"))
-		plr->awayviewaiming = (angle_t)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"health"))
 		plr->health = (INT32)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"pity"))
@@ -424,17 +422,17 @@ static int player_set(lua_State *L)
 	else if (fastcmp(field,"jumpfactor"))
 		plr->jumpfactor = (INT32)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"lives"))
-		plr->lives = (INT32)luaL_checkinteger(L, 3);
+		plr->lives = (SINT8)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"continues"))
-		plr->continues = (INT32)luaL_checkinteger(L, 3);
+		plr->continues = (SINT8)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"xtralife"))
-		plr->xtralife = (INT32)luaL_checkinteger(L, 3);
+		plr->xtralife = (SINT8)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"gotcontinue"))
 		plr->gotcontinue = (UINT8)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"speed"))
 		plr->speed = (fixed_t)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"jumping"))
-		plr->jumping = (INT32)luaL_checkinteger(L, 3);
+		plr->jumping = luaL_checkboolean(L, 3);
 	else if (fastcmp(field,"secondjump"))
 		plr->secondjump = (UINT8)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"fly1"))
@@ -462,13 +460,13 @@ static int player_set(lua_State *L)
 	else if (fastcmp(field,"rmomy"))
 		plr->rmomy = (fixed_t)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"numboxes"))
-		plr->numboxes = (INT32)luaL_checkinteger(L, 3);
+		plr->numboxes = (INT16)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"totalring"))
-		plr->totalring = (INT32)luaL_checkinteger(L, 3);
+		plr->totalring = (INT16)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"realtime"))
 		plr->realtime = (tic_t)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"laps"))
-		plr->laps = (UINT32)luaL_checkinteger(L, 3);
+		plr->laps = (UINT8)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"ctfteam"))
 		plr->ctfteam = (INT32)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"gotflag"))
@@ -567,6 +565,8 @@ static int player_set(lua_State *L)
 		if (plr->awayviewtics && !plr->awayviewmobj) // awayviewtics must ALWAYS have an awayviewmobj set!!
 			P_SetTarget(&plr->awayviewmobj, plr->mo); // but since the script might set awayviewmobj immediately AFTER setting awayviewtics, use player mobj as filler for now.
 	}
+	else if (fastcmp(field,"awayviewaiming"))
+		plr->awayviewaiming = (angle_t)luaL_checkinteger(L, 3);
 	else if (fastcmp(field,"spectator"))
 		plr->spectator = lua_toboolean(L, 3);
 	else if (fastcmp(field,"bot"))
diff --git a/src/m_cheat.c b/src/m_cheat.c
index 44c6f03e35..16bd88ad84 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -464,9 +464,9 @@ void Command_Savecheckpoint_f(void)
 // Like M_GetAllEmeralds() but for console devmode junkies.
 void Command_Getallemeralds_f(void)
 {
-	REQUIRE_PANDORA;
 	REQUIRE_SINGLEPLAYER;
 	REQUIRE_NOULTIMATE;
+	REQUIRE_PANDORA;
 
 	emeralds = ((EMERALD7)*2)-1;
 
@@ -475,8 +475,8 @@ void Command_Getallemeralds_f(void)
 
 void Command_Resetemeralds_f(void)
 {
-	REQUIRE_PANDORA;
 	REQUIRE_SINGLEPLAYER;
+	REQUIRE_PANDORA;
 
 	emeralds = 0;
 
@@ -511,10 +511,10 @@ void Command_Devmode_f(void)
 
 void Command_Setrings_f(void)
 {
-	REQUIRE_PANDORA;
 	REQUIRE_INLEVEL;
 	REQUIRE_SINGLEPLAYER;
 	REQUIRE_NOULTIMATE;
+	REQUIRE_PANDORA;
 
 	if (COM_Argc() > 1)
 	{
@@ -530,10 +530,10 @@ void Command_Setrings_f(void)
 
 void Command_Setlives_f(void)
 {
-	REQUIRE_PANDORA;
 	REQUIRE_INLEVEL;
 	REQUIRE_SINGLEPLAYER;
 	REQUIRE_NOULTIMATE;
+	REQUIRE_PANDORA;
 
 	if (COM_Argc() > 1)
 	{
@@ -547,10 +547,10 @@ void Command_Setlives_f(void)
 
 void Command_Setcontinues_f(void)
 {
-	REQUIRE_PANDORA;
 	REQUIRE_INLEVEL;
 	REQUIRE_SINGLEPLAYER;
 	REQUIRE_NOULTIMATE;
+	REQUIRE_PANDORA;
 
 	if (COM_Argc() > 1)
 	{
diff --git a/src/m_menu.c b/src/m_menu.c
index 5aad4e77df..0676bb5220 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1137,8 +1137,6 @@ static menuitem_t OP_OpenGLOptionsMenu[] =
 #ifdef _WINDOWS
 	{IT_STRING|IT_CVAR,         NULL, "Fullscreen",      &cv_fullscreen,       50},
 #endif
-	{IT_STRING|IT_CVAR|IT_CV_SLIDER,
-	                            NULL, "Translucent HUD", &cv_grtranslucenthud, 60},
 #ifdef ALAM_LIGHTING
 	{IT_SUBMENU|IT_STRING,      NULL, "Lighting...",     &OP_OpenGLLightingDef,     70},
 #endif
@@ -1245,17 +1243,19 @@ static menuitem_t OP_GameOptionsMenu[] =
 	                      NULL, "Master server",          &cv_masterserver,     10},
 #endif
 	{IT_STRING | IT_CVAR, NULL, "Show HUD",               &cv_showhud,     40},
-	{IT_STRING | IT_CVAR, NULL, "Timer Display",          &cv_timetic,     50},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER,
+	                      NULL, "HUD Visibility",         &cv_translucenthud, 50},
+	{IT_STRING | IT_CVAR, NULL, "Timer Display",          &cv_timetic,     60},
 #ifdef SEENAMES
-	{IT_STRING | IT_CVAR, NULL, "HUD Player Names",       &cv_seenames,    60},
+	{IT_STRING | IT_CVAR, NULL, "HUD Player Names",       &cv_seenames,    80},
 #endif
-	{IT_STRING | IT_CVAR, NULL, "Log Hazard Damage",      &cv_hazardlog,   70},
+	{IT_STRING | IT_CVAR, NULL, "Log Hazard Damage",      &cv_hazardlog,   90},
 
-	{IT_STRING | IT_CVAR, NULL, "Console Back Color",     &cons_backcolor,  90},
-	{IT_STRING | IT_CVAR, NULL, "Console Text Size",      &cv_constextsize,100},
-	{IT_STRING | IT_CVAR, NULL, "Uppercase Console",      &cv_allcaps,     110},
+	{IT_STRING | IT_CVAR, NULL, "Console Back Color",     &cons_backcolor, 100},
+	{IT_STRING | IT_CVAR, NULL, "Console Text Size",      &cv_constextsize,110},
+	{IT_STRING | IT_CVAR, NULL, "Uppercase Console",      &cv_allcaps,     120},
 
-	{IT_STRING | IT_CVAR, NULL, "Title Screen Demos",     &cv_rollingdemos, 130},
+	{IT_STRING | IT_CVAR, NULL, "Title Screen Demos",     &cv_rollingdemos, 140},
 };
 
 static menuitem_t OP_ServerOptionsMenu[] =
@@ -1276,7 +1276,7 @@ static menuitem_t OP_ServerOptionsMenu[] =
 	{IT_STRING | IT_CVAR,    NULL, "Max Players",                 &cv_maxplayers,        110},
 	{IT_STRING | IT_CVAR,    NULL, "Allow players to join",       &cv_allownewplayer,    120},
 	{IT_STRING | IT_CVAR,    NULL, "Allow WAD Downloading",       &cv_downloading,       130},
-	{IT_STRING | IT_CVAR,    NULL, "Consistency Protection",      &cv_consfailprotect,   140},
+	{IT_STRING | IT_CVAR,    NULL, "Attempts to Resynch",         &cv_resynchattempts,   140},
 #endif
 };
 
@@ -4681,9 +4681,9 @@ static void M_DrawSetupChoosePlayerMenu(void)
 		}
 		patch = W_CachePatchName(picname, PU_CACHE);
 		if (SHORT(patch->width) >= 256)
-			V_DrawCroppedPatch(8<<FRACBITS, (my + 8)<<FRACBITS, 0, patch, FRACUNIT/2, 0, SHORT(patch->height) - 64 + o*2, SHORT(patch->width), SHORT(patch->height));
+			V_DrawCroppedPatch(8<<FRACBITS, (my + 8)<<FRACBITS, FRACUNIT/2, 0, patch, 0, SHORT(patch->height) - 64 + o*2, SHORT(patch->width), SHORT(patch->height));
 		else
-			V_DrawCroppedPatch(8<<FRACBITS, (my + 8)<<FRACBITS, 0, patch, FRACUNIT, 0, SHORT(patch->height) - 32 + o, SHORT(patch->width), SHORT(patch->height));
+			V_DrawCroppedPatch(8<<FRACBITS, (my + 8)<<FRACBITS, FRACUNIT, 0, patch, 0, SHORT(patch->height) - 32 + o, SHORT(patch->width), SHORT(patch->height));
 		W_UnlockCachedPatch(patch);
 	}
 
@@ -4707,9 +4707,9 @@ static void M_DrawSetupChoosePlayerMenu(void)
 		}
 		patch = W_CachePatchName(picname, PU_CACHE);
 		if (SHORT(patch->width) >= 256)
-			V_DrawCroppedPatch(8<<FRACBITS, (my + 168 - o)<<FRACBITS, 0, patch, FRACUNIT/2, 0, 0, SHORT(patch->width), o*2);
+			V_DrawCroppedPatch(8<<FRACBITS, (my + 168 - o)<<FRACBITS, FRACUNIT/2, 0, patch, 0, 0, SHORT(patch->width), o*2);
 		else
-			V_DrawCroppedPatch(8<<FRACBITS, (my + 168 - o)<<FRACBITS, 0, patch, FRACUNIT, 0, 0, SHORT(patch->width), o);
+			V_DrawCroppedPatch(8<<FRACBITS, (my + 168 - o)<<FRACBITS, FRACUNIT, 0, patch, 0, 0, SHORT(patch->width), o);
 		W_UnlockCachedPatch(patch);
 	}
 
@@ -4741,9 +4741,9 @@ static void M_DrawSetupChoosePlayerMenu(void)
 		else
 		{
 			if (SHORT(patch->width) >= 256)
-				V_DrawCroppedPatch(8<<FRACBITS, (my + 8)<<FRACBITS, 0, patch, FRACUNIT/2, 0, (o - 32)*2, SHORT(patch->width), SHORT(patch->height));
+				V_DrawCroppedPatch(8<<FRACBITS, (my + 8)<<FRACBITS, FRACUNIT/2, 0, patch, 0, (o - 32)*2, SHORT(patch->width), SHORT(patch->height));
 			else
-				V_DrawCroppedPatch(8<<FRACBITS, (my + 8)<<FRACBITS, 0, patch, FRACUNIT, 0, o - 32, SHORT(patch->width), SHORT(patch->height));
+				V_DrawCroppedPatch(8<<FRACBITS, (my + 8)<<FRACBITS, FRACUNIT, 0, patch, 0, o - 32, SHORT(patch->width), SHORT(patch->height));
 		}
 		W_UnlockCachedPatch(patch);
 	}
diff --git a/src/p_map.c b/src/p_map.c
index 8b7869b838..eb7c6ad751 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -2552,7 +2552,7 @@ static boolean PTR_SlideTraverse(intercept_t *in)
 	// one-sided linedef
 	if (!li->backsector)
 	{
-		if (P_PointOnLineSide(mapcampointer->x, mapcampointer->y, li))
+		if (P_PointOnLineSide(slidemo->x, slidemo->y, li))
 			return true; // don't hit the back side
 		goto isblocking;
 	}
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 17db006257..baa0421123 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -120,6 +120,13 @@ static inline void P_NetArchivePlayers(void)
 
 		flags = 0;
 
+		// ticcmd write
+		WRITESINT8(save_p, players[i].cmd.forwardmove);
+		WRITESINT8(save_p, players[i].cmd.sidemove);
+		WRITEINT16(save_p, players[i].cmd.angleturn);
+		WRITEINT16(save_p, players[i].cmd.aiming);
+		WRITEUINT16(save_p, players[i].cmd.buttons);
+
 		WRITESTRINGN(save_p, player_names[i], MAXPLAYERNAME);
 		WRITEANGLE(save_p, players[i].aiming);
 		WRITEANGLE(save_p, players[i].awayviewaiming);
@@ -146,12 +153,12 @@ static inline void P_NetArchivePlayers(void)
 		WRITEUINT32(save_p, players[i].score);
 		WRITEINT32(save_p, players[i].dashspeed);
 		WRITEINT32(save_p, players[i].dashtime);
-		WRITEINT32(save_p, players[i].lives);
-		WRITEINT32(save_p, players[i].continues);
-		WRITEINT32(save_p, players[i].xtralife);
+		WRITESINT8(save_p, players[i].lives);
+		WRITESINT8(save_p, players[i].continues);
+		WRITESINT8(save_p, players[i].xtralife);
 		WRITEUINT8(save_p, players[i].gotcontinue);
 		WRITEINT32(save_p, players[i].speed);
-		WRITEINT32(save_p, players[i].jumping);
+		WRITEUINT8(save_p, players[i].jumping);
 		WRITEUINT8(save_p, players[i].secondjump);
 		WRITEUINT8(save_p, players[i].fly1);
 		WRITEUINT8(save_p, players[i].scoreadd);
@@ -176,7 +183,7 @@ static inline void P_NetArchivePlayers(void)
 		WRITEINT32(save_p, players[i].numboxes);
 		WRITEINT32(save_p, players[i].totalring);
 		WRITEUINT32(save_p, players[i].realtime);
-		WRITEUINT32(save_p, players[i].laps);
+		WRITEUINT8(save_p, players[i].laps);
 
 		////////////////////
 		// CTF Mode Stuff //
@@ -282,6 +289,7 @@ static inline void P_NetUnArchivePlayers(void)
 {
 	INT32 i, j;
 	UINT16 flags;
+	ticcmd_t tmptic;
 
 	if (READUINT32(save_p) != ARCHIVEBLOCK_PLAYERS)
 		I_Error("Bad $$$.sav at archive block Players");
@@ -292,6 +300,14 @@ static inline void P_NetUnArchivePlayers(void)
 		if (!playeringame[i])
 			continue;
 
+		memset(&tmptic, 0, sizeof(ticcmd_t));
+		tmptic.forwardmove = READSINT8(save_p);
+		tmptic.sidemove = READSINT8(save_p);
+		tmptic.angleturn = READINT16(save_p);
+		tmptic.aiming = READINT16(save_p);
+		tmptic.buttons = READUINT16(save_p);
+		G_CopyTiccmd(&players[i].cmd, &tmptic, 1);
+
 		READSTRINGN(save_p, player_names[i], MAXPLAYERNAME);
 		players[i].aiming = READANGLE(save_p);
 		players[i].awayviewaiming = READANGLE(save_p);
@@ -318,12 +334,12 @@ static inline void P_NetUnArchivePlayers(void)
 		players[i].score = READUINT32(save_p);
 		players[i].dashspeed = READINT32(save_p); // dashing speed
 		players[i].dashtime = READINT32(save_p); // dashing speed
-		players[i].lives = READINT32(save_p);
-		players[i].continues = READINT32(save_p); // continues that player has acquired
-		players[i].xtralife = READINT32(save_p); // Ring Extra Life counter
+		players[i].lives = READSINT8(save_p);
+		players[i].continues = READSINT8(save_p); // continues that player has acquired
+		players[i].xtralife = READSINT8(save_p); // Ring Extra Life counter
 		players[i].gotcontinue = READUINT8(save_p); // got continue from stage
 		players[i].speed = READINT32(save_p); // Player's speed (distance formula of MOMX and MOMY values)
-		players[i].jumping = READINT32(save_p); // Jump counter
+		players[i].jumping = READUINT8(save_p); // Jump counter
 		players[i].secondjump = READUINT8(save_p);
 		players[i].fly1 = READUINT8(save_p); // Tails flying
 		players[i].scoreadd = READUINT8(save_p); // Used for multiple enemy attack bonus
@@ -348,7 +364,7 @@ static inline void P_NetUnArchivePlayers(void)
 		players[i].numboxes = READINT32(save_p); // Number of item boxes obtained for Race Mode
 		players[i].totalring = READINT32(save_p); // Total number of rings obtained for Race Mode
 		players[i].realtime = READUINT32(save_p); // integer replacement for leveltime
-		players[i].laps = READUINT32(save_p); // Number of laps (optional)
+		players[i].laps = READUINT8(save_p); // Number of laps (optional)
 
 		////////////////////
 		// CTF Mode Stuff //
diff --git a/src/p_spec.c b/src/p_spec.c
index 20d86176f5..fa6200cd54 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -3854,10 +3854,10 @@ DoneSection2:
 					if (player->pflags & PF_NIGHTSMODE)
 						player->drillmeter += 48*20;
 
-					if (player->laps >= (unsigned)cv_numlaps.value)
+					if (player->laps >= (UINT8)cv_numlaps.value)
 						CONS_Printf(M_GetText("%s has finished the race.\n"), player_names[player-players]);
 					else
-						CONS_Printf(M_GetText("%s started lap %d\n"), player_names[player-players],player->laps+1);
+						CONS_Printf(M_GetText("%s started lap %u\n"), player_names[player-players], (UINT32)player->laps+1);
 
 					// Reset starposts (checkpoints) info
 					player->starpostangle = player->starposttime = player->starpostnum = 0;
@@ -5342,10 +5342,14 @@ void P_SpawnSpecials(INT32 fromnetsave)
 		if (lines[i].special == 6)
 		{
 			// Ability flags can disable disable linedefs now, lol
-			if ((netgame || multiplayer)
-			|| (!(players[consoleplayer].charability == CA_THOK && (lines[i].flags & ML_NOSONIC))
-			&& !(players[consoleplayer].charability == CA_FLY && (lines[i].flags & ML_NOTAILS))
-			&& !(players[consoleplayer].charability == CA_GLIDEANDCLIMB && (lines[i].flags & ML_NOKNUX))))
+			if (netgame || multiplayer)
+			{
+				// future: nonet flag?
+			}
+			else if (((lines[i].flags & ML_NETONLY) != ML_NETONLY)
+			&& !(players[consoleplayer].charability == CA_THOK          && (lines[i].flags & ML_NOSONIC))
+			&& !(players[consoleplayer].charability == CA_FLY           && (lines[i].flags & ML_NOTAILS))
+			&& !(players[consoleplayer].charability == CA_GLIDEANDCLIMB && (lines[i].flags & ML_NOKNUX )))
 			{
 				for (j = -1; (j = P_FindLineFromLineTag(&lines[i], j)) >= 0;)
 				{
@@ -5404,7 +5408,16 @@ void P_SpawnSpecials(INT32 fromnetsave)
 	for (i = 0; i < numlines; i++)
 	{
 		// set line specials to 0 here too, same reason as above
-		if (!(netgame || multiplayer))
+		if (netgame || multiplayer)
+		{
+			// future: nonet flag?
+		}
+		else if ((lines[i].flags & ML_NETONLY) == ML_NETONLY)
+		{
+			lines[i].special = 0;
+			continue;
+		}
+		else
 		{
 			if (players[consoleplayer].charability == CA_THOK && (lines[i].flags & ML_NOSONIC))
 			{
diff --git a/src/p_user.c b/src/p_user.c
index 5aa0d50a1b..ac026a11af 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -8113,19 +8113,15 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 					+ ((*rover->topheight - *rover->bottomheight)/2));
 				delta2 = thingtop - (*rover->bottomheight
 					+ ((*rover->topheight - *rover->bottomheight)/2));
-				if (*rover->topheight > tmfloorz && abs(delta1) < abs(delta2))
-				{
+				if (*rover->topheight > myfloorz && abs(delta1) < abs(delta2))
 					myfloorz = *rover->topheight;
-				}
-				if (*rover->bottomheight < tmceilingz && abs(delta1) >= abs(delta2))
-				{
+				if (*rover->bottomheight < myceilingz && abs(delta1) >= abs(delta2))
 					myceilingz = *rover->bottomheight;
-				}
 			}
 		}
 
 #ifdef POLYOBJECTS
-	// Check polyobjects and see if tmfloorz/tmceilingz need to be altered
+	// Check polyobjects and see if floorz/ceilingz need to be altered
 	{
 		INT32 xl, xh, yl, yh, bx, by;
 		validcount++;
@@ -8191,10 +8187,10 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 						delta1 = midz - (polybottom + ((polytop - polybottom)/2));
 						delta2 = thingtop - (polybottom + ((polytop - polybottom)/2));
 
-						if (polytop > tmfloorz && abs(delta1) < abs(delta2))
+						if (polytop > myfloorz && abs(delta1) < abs(delta2))
 							myfloorz = polytop;
 
-						if (polybottom < tmceilingz && abs(delta1) >= abs(delta2))
+						if (polybottom < myceilingz && abs(delta1) >= abs(delta2))
 							myceilingz = polybottom;
 					}
 					plink = (polymaplink_t *)(plink->link.next);
@@ -8204,7 +8200,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 #endif
 
 		// crushed camera
-		if (myceilingz <= myfloorz && !resetcalled && !cameranoclip)
+		if (myceilingz <= myfloorz + thiscam->height && !resetcalled && !cameranoclip)
 		{
 			P_ResetCamera(player, thiscam);
 			return true;
@@ -8645,7 +8641,7 @@ void P_PlayerThink(player_t *player)
 				if (players[i].lives <= 0)
 					continue;
 
-				if (!players[i].exiting)
+				if (!players[i].exiting || players[i].exiting > 3)
 					break;
 			}
 
diff --git a/src/r_main.c b/src/r_main.c
index c55de3940c..6af1bf4686 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -122,7 +122,7 @@ static CV_PossibleValue_t drawdist_cons_t[] = {
 	{3072, "3072"},	{4096, "4096"},	{6144, "6144"},
 	{8192, "8192"},	{0, "Infinite"},	{0, NULL}};
 static CV_PossibleValue_t precipdensity_cons_t[] = {{0, "None"}, {1, "Light"}, {2, "Moderate"}, {4, "Heavy"}, {6, "Thick"}, {8, "V.Thick"}, {0, NULL}};
-static CV_PossibleValue_t grtranslucenthud_cons_t[] = {{1, "MIN"}, {255, "MAX"}, {0, NULL}};
+static CV_PossibleValue_t translucenthud_cons_t[] = {{0, "MIN"}, {10, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t homremoval_cons_t[] = {{0, "No"}, {1, "Yes"}, {2, "Flash"}, {0, NULL}};
 
 static void ChaseCam_OnChange(void);
@@ -141,7 +141,7 @@ consvar_t cv_skybox = {"skybox", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0
 consvar_t cv_soniccd = {"soniccd", "Off", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_allowmlook = {"allowmlook", "Yes", CV_NETVAR, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_showhud = {"showhud", "Yes", CV_CALL,  CV_YesNo, R_SetViewSize, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_grtranslucenthud = {"gr_translucenthud", "255", CV_SAVE|CV_CALL, grtranslucenthud_cons_t, R_SetViewSize, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_translucenthud = {"translucenthud", "10", CV_SAVE, translucenthud_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 consvar_t cv_drawdist = {"drawdist", "Infinite", CV_SAVE, drawdist_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_drawdist_nights = {"drawdist_nights", "2048", CV_SAVE, drawdist_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -1237,11 +1237,11 @@ void R_RegisterEngineStuff(void)
 	CV_RegisterVar(&cv_cam2_rotspeed);
 
 	CV_RegisterVar(&cv_showhud);
+	CV_RegisterVar(&cv_translucenthud);
 
 	// Default viewheight is changeable,
 	// initialized to standard viewheight
 	CV_RegisterVar(&cv_viewheight);
-	CV_RegisterVar(&cv_grtranslucenthud);
 
 #ifdef HWRENDER
 	// GL-specific Commands
diff --git a/src/r_main.h b/src/r_main.h
index 8a39b7d30e..a367960c76 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -81,9 +81,8 @@ subsector_t *R_IsPointInSubsector(fixed_t x, fixed_t y);
 // REFRESH - the actual rendering functions.
 //
 
-extern consvar_t cv_showhud;
+extern consvar_t cv_showhud, cv_translucenthud;
 extern consvar_t cv_homremoval;
-extern consvar_t cv_grtranslucenthud;
 extern consvar_t cv_chasecam, cv_chasecam2;
 extern consvar_t cv_flipcam, cv_flipcam2;
 extern consvar_t cv_shadow, cv_shadowoffs;
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 56ce8e4e95..9f5a691eef 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -129,27 +129,29 @@ hudinfo_t hudinfo[NUMHUDITEMS] =
 	{  16, 176}, // HUD_LIVESPIC
 	{  74, 184}, // HUD_LIVESNUM
 	{  38, 186}, // HUD_LIVESX
-	{ 220,  10}, // HUD_RINGSSPLIT
-	{ 288,  10}, // HUD_RINGSNUMSPLIT
+
 	{  16,  42}, // HUD_RINGS
+	{ 220,  10}, // HUD_RINGSSPLIT
 	{ 112,  42}, // HUD_RINGSNUM
+	{ 288,  10}, // HUD_RINGSNUMSPLIT
+
 	{  16,  10}, // HUD_SCORE
 	{ 128,  10}, // HUD_SCORENUM
-	{ 136,  10}, // HUD_TIMESPLIT
 
-	{ 212,  10}, // HUD_SECONDSSPLIT
-	{ 188,  10}, // HUD_MINUTESSPLIT
-	{ 188,  10}, // HUD_TIMECOLONSPLIT
 	{  17,  26}, // HUD_TIME
-
-	{ 136,  26}, // HUD_TICS
-
-	{ 112,  26}, // HUD_SECONDS
+	{ 136,  10}, // HUD_TIMESPLIT
 	{  88,  26}, // HUD_MINUTES
+	{ 188,  10}, // HUD_MINUTESSPLIT
 	{  88,  26}, // HUD_TIMECOLON
+	{ 188,  10}, // HUD_TIMECOLONSPLIT
+	{ 112,  26}, // HUD_SECONDS
+	{ 212,  10}, // HUD_SECONDSSPLIT
 	{ 112,  26}, // HUD_TIMETICCOLON
-	{ 288,  40}, // HUD_SS_TOTALRINGS_SPLIT
+	{ 136,  26}, // HUD_TICS
+
 	{ 112,  56}, // HUD_SS_TOTALRINGS
+	{ 288,  40}, // HUD_SS_TOTALRINGS_SPLIT
+
 	{ 110,  93}, // HUD_GETRINGS
 	{ 160,  93}, // HUD_GETRINGSNUM
 	{ 124, 160}, // HUD_TIMELEFT
@@ -160,9 +162,6 @@ hudinfo_t hudinfo[NUMHUDITEMS] =
 	{ 240, 160}, // HUD_LAP
 };
 
-#define ST_DrawOverlayNum(x,y,f,n)         V_DrawTallNum(x, y, f|V_TRANSLUCENT, n)
-#define ST_DrawPaddedOverlayNum(x,y,n,d) V_DrawPaddedTallNum(x, y, V_NOSCALESTART|V_TRANSLUCENT, n, d)
-
 //
 // STATUS BAR CODE
 //
@@ -463,6 +462,7 @@ static INT32 SCX(INT32 x)
 	return FixedInt(FixedMul(x<<FRACBITS, vid.fdupx));
 }
 
+#if 0
 static INT32 SCR(INT32 r)
 {
 	fixed_t y;
@@ -478,40 +478,27 @@ static INT32 SCR(INT32 r)
 	}
 	return FixedInt(FixedDiv(y, vid.fdupy));
 }
+#endif
 
-// Draw a number, scaled, over the view
-// Always draw the number completely since it's overlay
-//
-// Supports different colors! woo!
-static void ST_DrawNightsOverlayNum(INT32 x /* right border */, INT32 y, INT32 num,
-	patch_t **numpat, skincolors_t colornum)
-{
-	INT32 w = SHORT(numpat[0]->width);
-	const UINT8 *colormap;
-
-	if (colornum == 0)
-		colormap = colormaps;
-	else // Uses the player colors.
-		colormap = R_GetTranslationColormap(TC_DEFAULT, colornum, GTC_CACHE);
-
-	I_Assert(num >= 0); // this function does not draw negative numbers
-
-	// draw the number
-	do
-	{
-		x -= (w * vid.dupx);
-		V_DrawMappedPatch(x, y, V_NOSCALESTART|V_TRANSLUCENT, numpat[num % 10], colormap);
-		num /= 10;
-	} while (num);
-
-	// Sorry chum, this function only draws UNSIGNED values!
-}
-
-// Draw a number, scaled, over the view, with set translucency
+// =========================================================================
+//                          INTERNAL DRAWING
+// =========================================================================
+#define ST_DrawOverlayNum(x,y,n)           V_DrawTallNum(x, y, V_NOSCALESTART|V_HUDTRANS, n)
+#define ST_DrawPaddedOverlayNum(x,y,n,d)   V_DrawPaddedTallNum(x, y, V_NOSCALESTART|V_HUDTRANS, n, d)
+#define ST_DrawOverlayPatch(x,y,p)         V_DrawScaledPatch(x, y, V_NOSCALESTART|V_HUDTRANS, p)
+#define ST_DrawMappedOverlayPatch(x,y,p,c) V_DrawMappedScaledPatch(x, y, V_NOSCALESTART|V_HUDTRANS, p, c)
+#define ST_DrawNumFromHud(h,n)        V_DrawTallNum(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART|V_HUDTRANS, n)
+#define ST_DrawPadNumFromHud(h,n,q)   V_DrawPaddedTallNum(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART|V_HUDTRANS, n, q)
+#define ST_DrawPatchFromHud(h,p)      V_DrawScaledPatch(SCX(hudinfo[h].x), SCY(hudinfo[h].y), V_NOSCALESTART|V_HUDTRANS, p)
+#define ST_DrawNumFromHudWS(h,n)      V_DrawTallNum(SCX(hudinfo[h+!!splitscreen].x), SCY(hudinfo[h+!!splitscreen].y), V_NOSCALESTART|V_HUDTRANS, n)
+#define ST_DrawPadNumFromHudWS(h,n,q) V_DrawPaddedTallNum(SCX(hudinfo[h+!!splitscreen].x), SCY(hudinfo[h+!!splitscreen].y), V_NOSCALESTART|V_HUDTRANS, n, q)
+#define ST_DrawPatchFromHudWS(h,p)    V_DrawScaledPatch(SCX(hudinfo[h+!!splitscreen].x), SCY(hudinfo[h+!!splitscreen].y), V_NOSCALESTART|V_HUDTRANS, p)
+
+// Draw a number, scaled, over the view, maybe with set translucency
 // Always draw the number completely since it's overlay
 //
 // Supports different colors! woo!
-static void ST_DrawTranslucentNightsOverlayNum(INT32 x /* right border */, INT32 y, INT32 a,
+static void ST_DrawNightsOverlayNum(INT32 x /* right border */, INT32 y, INT32 a,
 	INT32 num, patch_t **numpat, skincolors_t colornum)
 {
 	INT32 w = SHORT(numpat[0]->width);
@@ -603,16 +590,16 @@ static void ST_drawDebugInfo(void)
 static void ST_drawScore(void)
 {
 	// SCORE:
-	V_DrawScaledPatch(SCX(hudinfo[HUD_SCORE].x), SCY(hudinfo[HUD_SCORE].y), V_NOSCALESTART|V_TRANSLUCENT, sboscore);
+	ST_DrawPatchFromHud(HUD_SCORE, sboscore);
 	if (objectplacing)
 	{
 		if (op_displayflags > UINT16_MAX)
-			V_DrawScaledPatch(SCX(hudinfo[HUD_SCORENUM].x-tallminus->width), SCY(hudinfo[HUD_SCORENUM].y), V_NOSCALESTART, tallminus);
+			ST_DrawOverlayPatch(SCX(hudinfo[HUD_SCORENUM].x-tallminus->width), SCY(hudinfo[HUD_SCORENUM].y), tallminus);
 		else
-			ST_DrawOverlayNum(SCX(hudinfo[HUD_SCORENUM].x), SCY(hudinfo[HUD_SCORENUM].y), V_NOSCALESTART, op_displayflags);
+			ST_DrawNumFromHud(HUD_SCORENUM, op_displayflags);
 	}
 	else
-		ST_DrawOverlayNum(SCX(hudinfo[HUD_SCORENUM].x), SCY(hudinfo[HUD_SCORENUM].y), V_NOSCALESTART, stplyr->score);
+		ST_DrawNumFromHud(HUD_SCORENUM, stplyr->score);
 }
 
 static void ST_drawTime(void)
@@ -620,8 +607,7 @@ static void ST_drawTime(void)
 	INT32 seconds, minutes, tictrn, tics;
 
 	// TIME:
-	V_DrawScaledPatch(SCX(hudinfo[(splitscreen) ? HUD_TIMESPLIT : HUD_TIME].x),
-	                  SCY(hudinfo[(splitscreen) ? HUD_TIMESPLIT : HUD_TIME].y), V_NOSCALESTART|V_TRANSLUCENT, sbotime);
+	ST_DrawPatchFromHudWS(HUD_TIME, sbotime);
 
 	if (objectplacing)
 	{
@@ -633,61 +619,45 @@ static void ST_drawTime(void)
 	else
 	{
 		tics = stplyr->realtime;
-		seconds = tics/TICRATE % 60;
-		minutes = tics/(60*TICRATE);
+		seconds = G_TicsToSeconds(tics);
+		minutes = G_TicsToMinutes(tics, true);
 		tictrn  = G_TicsToCentiseconds(tics);
 	}
 
 	if (cv_timetic.value == 1) // Tics only -- how simple is this?
-		ST_DrawOverlayNum(SCX(hudinfo[(splitscreen) ? HUD_SECONDSSPLIT : HUD_SECONDS].x),
-		                  SCY(hudinfo[(splitscreen) ? HUD_SECONDSSPLIT : HUD_SECONDS].y), V_NOSCALESTART, tics);
+		ST_DrawNumFromHudWS(HUD_SECONDS, tics);
 	else
 	{
-		// Minutes
-		ST_DrawOverlayNum(SCX(hudinfo[(splitscreen) ? HUD_MINUTESSPLIT : HUD_MINUTES].x),
-		                  SCY(hudinfo[(splitscreen) ? HUD_MINUTESSPLIT : HUD_MINUTES].y), V_NOSCALESTART, minutes);
-		// Colon
-		V_DrawScaledPatch(SCX(hudinfo[(splitscreen) ? HUD_TIMECOLONSPLIT : HUD_TIMECOLON].x),
-		                  SCY(hudinfo[(splitscreen) ? HUD_TIMECOLONSPLIT : HUD_TIMECOLON].y), V_NOSCALESTART|V_TRANSLUCENT, sbocolon);
-		// Seconds
-		ST_DrawPaddedOverlayNum(SCX(hudinfo[(splitscreen) ? HUD_SECONDSSPLIT : HUD_SECONDS].x),
-		                        SCY(hudinfo[(splitscreen) ? HUD_SECONDSSPLIT : HUD_SECONDS].y), seconds, 2);
+		ST_DrawNumFromHudWS(HUD_MINUTES, minutes); // Minutes
+		ST_DrawPatchFromHudWS(HUD_TIMECOLON, sbocolon); // Colon
+		ST_DrawPadNumFromHudWS(HUD_SECONDS, seconds, 2); // Seconds
 
 		if (!splitscreen && (cv_timetic.value == 2 || modeattacking)) // there's not enough room for tics in splitscreen, don't even bother trying!
 		{
-			// Colon
-			V_DrawScaledPatch(SCX(hudinfo[HUD_TIMETICCOLON].x), SCY(hudinfo[HUD_TIMETICCOLON].y), V_NOSCALESTART|V_TRANSLUCENT, sbocolon);
-			// Tics
-			ST_DrawPaddedOverlayNum(SCX(hudinfo[HUD_TICS].x), SCY(hudinfo[HUD_TICS].y), tictrn, 2);
+			ST_DrawPatchFromHud(HUD_TIMETICCOLON, sbocolon); // Colon
+			ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2); // Tics
 		}
 	}
 }
 
 static inline void ST_drawRings(void)
 {
-	hudnum_t ringHUD    = (splitscreen) ? HUD_RINGSSPLIT    : HUD_RINGS;
-	hudnum_t ringnumHUD = (splitscreen) ? HUD_RINGSNUMSPLIT : HUD_RINGSNUM;
+	INT32 ringnum = max(stplyr->health-1, 0);
 
-	V_DrawScaledPatch(SCX(hudinfo[ringHUD].x), SCY(hudinfo[ringHUD].y), V_NOSCALESTART|V_TRANSLUCENT,
-	                  (stplyr->health <= 1 && leveltime/5 & 1) ? rrings : sborings);
+	ST_DrawPatchFromHudWS(HUD_RINGS, ((stplyr->health <= 1 && leveltime/5 & 1) ? rrings : sborings));
 
 	if (objectplacing)
-	{
-		ST_DrawOverlayNum(SCX(hudinfo[ringnumHUD].x), SCY(hudinfo[ringnumHUD].y), V_NOSCALESTART, op_currentdoomednum);
-	}
+		ringnum = op_currentdoomednum;
 	else if (!useNightsSS && G_IsSpecialStage(gamemap))
 	{
-		INT32 ringscollected = 0; // Total # everyone has collected
 		INT32 i;
-
+		ringnum = 0;
 		for (i = 0; i < MAXPLAYERS; i++)
 			if (playeringame[i] && players[i].mo && players[i].mo->health > 1)
-				ringscollected += players[i].mo->health - 1;
-
-		ST_DrawOverlayNum(SCX(hudinfo[ringnumHUD].x), SCY(hudinfo[ringnumHUD].y), V_NOSCALESTART, ringscollected);
+				ringnum += players[i].mo->health - 1;
 	}
-	else
-		ST_DrawOverlayNum(SCX(hudinfo[ringnumHUD].x), SCY(hudinfo[ringnumHUD].y), V_NOSCALESTART, stplyr->health > 0 ? stplyr->health-1 : 0);
+
+	ST_DrawNumFromHudWS(HUD_RINGSNUM, ringnum);
 }
 
 static void ST_drawLives(void)
@@ -699,7 +669,7 @@ static void ST_drawLives(void)
 
 	// face background
 	V_DrawSmallScaledPatch(hudinfo[HUD_LIVESPIC].x, hudinfo[HUD_LIVESPIC].y + (v_splitflag ? -12 : 0),
-		V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_TRANSLUCENT|v_splitflag, livesback);
+		V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag, livesback);
 
 	// face
 	if (stplyr->mo && stplyr->mo->color)
@@ -710,25 +680,29 @@ static void ST_drawLives(void)
 		if (stplyr->powers[pw_super] || stplyr->pflags & PF_NIGHTSMODE)
 			face = superprefix[stplyr->skin];
 		V_DrawSmallMappedPatch(hudinfo[HUD_LIVESPIC].x, hudinfo[HUD_LIVESPIC].y + (v_splitflag ? -12 : 0),
-			V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_TRANSLUCENT|v_splitflag,face, colormap);
+			V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag,face, colormap);
 	}
 	else if (stplyr->skincolor)
 	{
 		// skincolor face
 		UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE);
 		V_DrawSmallMappedPatch(hudinfo[HUD_LIVESPIC].x, hudinfo[HUD_LIVESPIC].y + (v_splitflag ? -12 : 0),
-			V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_TRANSLUCENT|v_splitflag,faceprefix[stplyr->skin], colormap);
+			V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag,faceprefix[stplyr->skin], colormap);
 	}
 
 	// name
 	if (strlen(skins[stplyr->skin].hudname) > 8)
-		V_DrawThinString(hudinfo[HUD_LIVESNAME].x, hudinfo[HUD_LIVESNAME].y + (v_splitflag ? -12 : 0), V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_MONOSPACE|V_YELLOWMAP|v_splitflag, skins[stplyr->skin].hudname);
+		V_DrawThinString(hudinfo[HUD_LIVESNAME].x, hudinfo[HUD_LIVESNAME].y + (v_splitflag ? -12 : 0),
+			V_HUDTRANS|V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_MONOSPACE|V_YELLOWMAP|v_splitflag, skins[stplyr->skin].hudname);
 	else
-		V_DrawString(hudinfo[HUD_LIVESNAME].x, hudinfo[HUD_LIVESNAME].y + (v_splitflag ? -12 : 0), V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_MONOSPACE|V_YELLOWMAP|v_splitflag, skins[stplyr->skin].hudname);
+		V_DrawString(hudinfo[HUD_LIVESNAME].x, hudinfo[HUD_LIVESNAME].y + (v_splitflag ? -12 : 0),
+			V_HUDTRANS|V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_MONOSPACE|V_YELLOWMAP|v_splitflag, skins[stplyr->skin].hudname);
 	// x
-	V_DrawScaledPatch(hudinfo[HUD_LIVESX].x, hudinfo[HUD_LIVESX].y + (v_splitflag ? -4 : 0), V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_TRANSLUCENT|v_splitflag, stlivex);
+	V_DrawScaledPatch(hudinfo[HUD_LIVESX].x, hudinfo[HUD_LIVESX].y + (v_splitflag ? -4 : 0),
+		V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag, stlivex);
 	// lives
-	V_DrawRightAlignedString(hudinfo[HUD_LIVESNUM].x, hudinfo[HUD_LIVESNUM].y + (v_splitflag ? -4 : 0), V_SNAPTOLEFT|V_SNAPTOBOTTOM|v_splitflag, va("%d",stplyr->lives));
+	V_DrawRightAlignedString(hudinfo[HUD_LIVESNUM].x, hudinfo[HUD_LIVESNUM].y + (v_splitflag ? -4 : 0),
+		V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS|v_splitflag, va("%d",stplyr->lives));
 }
 
 static void ST_drawLevelTitle(void)
@@ -820,9 +794,9 @@ static void ST_drawFirstPersonHUD(void)
 	if (p)
 	{
 		if (splitscreen)
-			V_DrawSmallScaledPatch(312, STRINGY(24), V_SNAPTORIGHT|V_SNAPTOTOP|V_TRANSLUCENT, p);
+			V_DrawSmallScaledPatch(312, STRINGY(24), V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, p);
 		else
-			V_DrawScaledPatch(304, 24, V_SNAPTORIGHT|V_SNAPTOTOP|V_TRANSLUCENT, p);
+			V_DrawScaledPatch(304, 24, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, p);
 	}
 
 	// pw_flashing just sets the icon to flash no matter what.
@@ -830,17 +804,17 @@ static void ST_drawFirstPersonHUD(void)
 	if (invulntime > 3*TICRATE || (invulntime && leveltime & 1))
 	{
 		if (splitscreen)
-			V_DrawSmallScaledPatch(312, STRINGY(24) + 14, V_SNAPTORIGHT|V_SNAPTOTOP|V_TRANSLUCENT, invincibility);
+			V_DrawSmallScaledPatch(312, STRINGY(24) + 14, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, invincibility);
 		else
-			V_DrawScaledPatch(304, 24 + 28, V_SNAPTORIGHT|V_SNAPTOTOP|V_TRANSLUCENT, invincibility);
+			V_DrawScaledPatch(304, 24 + 28, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, invincibility);
 	}
 
 	if (player->powers[pw_sneakers] > 3*TICRATE || (player->powers[pw_sneakers] && leveltime & 1))
 	{
 		if (splitscreen)
-			V_DrawSmallScaledPatch(312, STRINGY(24) + 28, V_SNAPTORIGHT|V_SNAPTOTOP|V_TRANSLUCENT, sneakers);
+			V_DrawSmallScaledPatch(312, STRINGY(24) + 28, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, sneakers);
 		else
-			V_DrawScaledPatch(304, 24 + 56, V_SNAPTORIGHT|V_SNAPTOTOP|V_TRANSLUCENT, sneakers);
+			V_DrawScaledPatch(304, 24 + 56, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, sneakers);
 	}
 
 	p = NULL;
@@ -890,8 +864,8 @@ static void ST_drawFirstPersonHUD(void)
 	}
 
 	if (p)
-		V_DrawTranslucentPatch(SCX((BASEVIDWIDTH/2) - (SHORT(p->width)/2) + SHORT(p->leftoffset)), SCY(60 - SHORT(p->topoffset)),
-			V_NOSCALESTART|V_OFFSET, p);
+		V_DrawScaledPatch(SCX((BASEVIDWIDTH/2) - (SHORT(p->width)/2) + SHORT(p->leftoffset)), SCY(60 - SHORT(p->topoffset)),
+			V_NOSCALESTART|V_OFFSET|V_TRANSLUCENT, p);
 }
 
 // [21:42] <+Rob> Beige - Lavender - Steel Blue - Peach - Orange - Purple - Silver - Yellow - Pink - Red - Blue - Green - Cyan - Gold
@@ -946,11 +920,7 @@ static void ST_drawNightsRecords(void)
 		V_DrawString(BASEVIDWIDTH/2 - 48, STRINGY(148), aflag, "BONUS:");
 		V_DrawRightAlignedString(BASEVIDWIDTH/2 + 48, STRINGY(140), V_ORANGEMAP|aflag, va("%d", stplyr->finishedrings));
 		V_DrawRightAlignedString(BASEVIDWIDTH/2 + 48, STRINGY(148), V_ORANGEMAP|aflag, va("%d", stplyr->finishedrings * 50));
-
-		if (aflag)
-			ST_DrawTranslucentNightsOverlayNum(SCX(BASEVIDWIDTH/2 + 48), SCY(160), aflag, stplyr->lastmarescore, nightsnum, SKINCOLOR_STEELBLUE);
-		else
-			ST_DrawNightsOverlayNum(SCX(BASEVIDWIDTH/2 + 48), SCY(160), stplyr->lastmarescore, nightsnum, SKINCOLOR_STEELBLUE);
+		ST_DrawNightsOverlayNum(SCX(BASEVIDWIDTH/2 + 48), SCY(160), aflag, stplyr->lastmarescore, nightsnum, SKINCOLOR_STEELBLUE);
 
 		// If new record, say so!
 		if (!(netgame || multiplayer) && G_GetBestNightsScore(gamemap, stplyr->lastmare + 1) <= stplyr->lastmarescore)
@@ -1012,15 +982,13 @@ static void ST_drawNiGHTSHUD(void)
 
 			if (splitscreen)
 			{
-				ST_DrawTranslucentNightsOverlayNum(SCX(256), SCY(160), linktrans, (stplyr->linkcount-1),
-					nightsnum, colornum);
+				ST_DrawNightsOverlayNum(SCX(256), SCY(160), linktrans, (stplyr->linkcount-1), nightsnum, colornum);
 				V_DrawTranslucentMappedPatch(SCX(264), SCY(160), V_NOSCALESTART|linktrans, nightslink,
 					colornum == 0 ? colormaps : R_GetTranslationColormap(TC_DEFAULT, colornum, GTC_CACHE));
 			}
 			else
 			{
-				ST_DrawTranslucentNightsOverlayNum(SCX(160), SCY(160), linktrans, (stplyr->linkcount-1),
-					nightsnum, colornum);
+				ST_DrawNightsOverlayNum(SCX(160), SCY(160), linktrans, (stplyr->linkcount-1), nightsnum, colornum);
 				V_DrawTranslucentMappedPatch(SCX(168), SCY(160), V_NOSCALESTART|linktrans, nightslink,
 					colornum == 0 ? colormaps : R_GetTranslationColormap(TC_DEFAULT, colornum, GTC_CACHE));
 			}
@@ -1032,20 +1000,20 @@ static void ST_drawNiGHTSHUD(void)
 			{
 				INT32 offs = 10 - (stplyr->linktimer - (2*TICRATE - 9));
 				INT32 ghosttrans = offs << V_ALPHASHIFT;
-				ST_DrawTranslucentNightsOverlayNum(SCX(160), SCY(160)+(offs*2), ghosttrans, (stplyr->linkcount-2),
+				ST_DrawNightsOverlayNum(SCX(160), SCY(160)+(offs*2), ghosttrans, (stplyr->linkcount-2),
 					nightsnum, colornum);
 			}
 #endif
 
 			if (splitscreen)
 			{
-				ST_DrawNightsOverlayNum(SCX(256), SCY(160), (stplyr->linkcount-1), nightsnum, colornum);
+				ST_DrawNightsOverlayNum(SCX(256), SCY(160), 0, (stplyr->linkcount-1), nightsnum, colornum);
 				V_DrawMappedPatch(SCX(264), SCY(160), V_NOSCALESTART, nightslink,
 					colornum == 0 ? colormaps : R_GetTranslationColormap(TC_DEFAULT, colornum, GTC_CACHE));
 			}
 			else
 			{
-				ST_DrawNightsOverlayNum(SCX(160), SCY(160), (stplyr->linkcount-1), nightsnum, colornum);
+				ST_DrawNightsOverlayNum(SCX(160), SCY(160), 0, (stplyr->linkcount-1), nightsnum, colornum);
 				V_DrawMappedPatch(SCX(168), SCY(160), V_NOSCALESTART, nightslink,
 					colornum == 0 ? colormaps : R_GetTranslationColormap(TC_DEFAULT, colornum, GTC_CACHE));
 			}
@@ -1086,32 +1054,32 @@ static void ST_drawNiGHTSHUD(void)
 
 		if (splitscreen)
 		{ // Dirty hack because V_SNAPTOBOTTOM doesn't have a way to account for splitscreen, but better than overlapping bars.
-			V_DrawScaledPatch(SCX(locx), SCY(locy), V_NOSCALESTART|V_TRANSLUCENT, drillbar);
+			V_DrawScaledPatch(SCX(locx), SCY(locy), V_NOSCALESTART|V_HUDTRANS, drillbar);
 			for (dfill = 0; dfill < stplyr->drillmeter/20 && dfill < 96; ++dfill)
-				V_DrawScaledPatch(SCX(locx + 2 + dfill), SCY(locy + 3), V_NOSCALESTART|V_TRANSLUCENT, drillfill[fillpatch]);
+				V_DrawScaledPatch(SCX(locx + 2 + dfill), SCY(locy + 3), V_NOSCALESTART|V_HUDTRANS, drillfill[fillpatch]);
 		}
 		else if (nosshack)
 		{ // Even dirtier hack-of-a-hack to draw seperate drill meters in splitscreen special stages but nothing else.
 			splitscreen = true;
-			V_DrawScaledPatch(SCX(locx), SCY(locy), V_NOSCALESTART|V_TRANSLUCENT, drillbar);
+			V_DrawScaledPatch(SCX(locx), SCY(locy), V_NOSCALESTART|V_HUDTRANS, drillbar);
 			for (dfill = 0; dfill < stplyr->drillmeter/20 && dfill < 96; ++dfill)
-				V_DrawScaledPatch(SCX(locx + 2 + dfill), SCY(locy + 3), V_NOSCALESTART|V_TRANSLUCENT, drillfill[fillpatch]);
+				V_DrawScaledPatch(SCX(locx + 2 + dfill), SCY(locy + 3), V_NOSCALESTART|V_HUDTRANS, drillfill[fillpatch]);
 			stplyr = &players[secondarydisplayplayer];
 			if (stplyr->pflags & PF_DRILLING)
 				fillpatch = (stplyr->drillmeter & 1) + 1;
 			else
 				fillpatch = 0;
-			V_DrawScaledPatch(locx, locy, V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_TRANSLUCENT, drillbar);
+			V_DrawScaledPatch(locx, locy, V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS, drillbar);
 			for (dfill = 0; dfill < stplyr->drillmeter/20 && dfill < 96; ++dfill)
-				V_DrawScaledPatch(locx + 2 + dfill, locy + 3, V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_TRANSLUCENT, drillfill[fillpatch]);
+				V_DrawScaledPatch(locx + 2 + dfill, locy + 3, V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS, drillfill[fillpatch]);
 			stplyr = &players[displayplayer];
 			splitscreen = false;
 		}
 		else
 		{ // Draw normally. <:3
-			V_DrawScaledPatch(locx, locy, V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_TRANSLUCENT, drillbar);
+			V_DrawScaledPatch(locx, locy, V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS, drillbar);
 			for (dfill = 0; dfill < stplyr->drillmeter/20 && dfill < 96; ++dfill)
-				V_DrawScaledPatch(locx + 2 + dfill, locy + 3, V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_TRANSLUCENT, drillfill[fillpatch]);
+				V_DrawScaledPatch(locx + 2 + dfill, locy + 3, V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_HUDTRANS, drillfill[fillpatch]);
 		}
 
 		// Display actual drill amount and bumper time
@@ -1136,11 +1104,11 @@ static void ST_drawNiGHTSHUD(void)
 	if (LUA_HudEnabled(hud_nightsrings))
 	{
 #endif
-	V_DrawScaledPatch(SCX(16), SCY(8), V_NOSCALESTART|V_TRANSLUCENT, nbracket);
+	ST_DrawOverlayPatch(SCX(16), SCY(8), nbracket);
 	if (G_IsSpecialStage(gamemap))
-		V_DrawScaledPatch(SCX(24), SCY(8) + SCZ(8), V_NOSCALESTART|V_TRANSLUCENT, nsshud);
+		ST_DrawOverlayPatch(SCX(24), SCY(8) + SCZ(8), nsshud);
 	else
-		V_DrawScaledPatch(SCX(24), SCY(8) + SCZ(8), V_NOSCALESTART|V_TRANSLUCENT, nhud[(leveltime/2)%12]);
+		ST_DrawOverlayPatch(SCX(24), SCY(8) + SCZ(8), nhud[(leveltime/2)%12]);
 
 	if (G_IsSpecialStage(gamemap))
 	{
@@ -1161,8 +1129,8 @@ static void ST_drawNiGHTSHUD(void)
 		origamount = stplyr->capsule->spawnpoint->angle;
 		I_Assert(origamount > 0); // should not happen now
 
-		V_DrawScaledPatch(SCX(72), SCY(8), V_NOSCALESTART|V_TRANSLUCENT, nbracket);
-		V_DrawScaledPatch(SCX(74), SCY(8) + SCZ(4), V_NOSCALESTART|V_TRANSLUCENT, minicaps);
+		ST_DrawOverlayPatch(SCX(72), SCY(8), nbracket);
+		ST_DrawOverlayPatch(SCX(74), SCY(8) + SCZ(4), minicaps);
 
 		if (stplyr->capsule->reactiontime != 0)
 		{
@@ -1171,10 +1139,10 @@ static void ST_drawNiGHTSHUD(void)
 
 			for (r = 0; r < 5; r++)
 			{
-				V_DrawScaledPatch(SCX(230 - (7*r)), SCY(144), V_NOSCALESTART|V_TRANSLUCENT, redstat);
-				V_DrawScaledPatch(SCX(188 - (7*r)), SCY(144), V_NOSCALESTART|V_TRANSLUCENT, orngstat);
-				V_DrawScaledPatch(SCX(146 - (7*r)), SCY(144), V_NOSCALESTART|V_TRANSLUCENT, yelstat);
-				V_DrawScaledPatch(SCX(104 - (7*r)), SCY(144), V_NOSCALESTART|V_TRANSLUCENT, byelstat);
+				ST_DrawOverlayPatch(SCX(230 - (7*r)), SCY(144), redstat);
+				ST_DrawOverlayPatch(SCX(188 - (7*r)), SCY(144), orngstat);
+				ST_DrawOverlayPatch(SCX(146 - (7*r)), SCY(144), yelstat);
+				ST_DrawOverlayPatch(SCX(104 - (7*r)), SCY(144), byelstat);
 			}
 
 			amount = (origamount - stplyr->capsule->health);
@@ -1193,7 +1161,7 @@ static void ST_drawNiGHTSHUD(void)
 					if (r > 10) ++t;
 					if (r > 5)  ++t;
 
-					V_DrawScaledPatch(SCX(69 + (7*t)), SCY(144), V_NOSCALESTART|V_TRANSLUCENT, bluestat);
+					ST_DrawOverlayPatch(SCX(69 + (7*t)), SCY(144), bluestat);
 				}
 			}
 		}
@@ -1202,27 +1170,27 @@ static void ST_drawNiGHTSHUD(void)
 			INT32 cfill;
 
 			// Lil' white box!
-			V_DrawScaledPatch(15, STRINGY(8) + 34, V_SNAPTOLEFT|V_SNAPTOTOP|V_TRANSLUCENT, capsulebar);
+			V_DrawScaledPatch(15, STRINGY(8) + 34, V_SNAPTOLEFT|V_SNAPTOTOP|V_HUDTRANS, capsulebar);
 
 			amount = (origamount - stplyr->capsule->health);
 			amount = (amount * length)/origamount;
 
 			for (cfill = 0; cfill < amount && cfill < 88; ++cfill)
-				V_DrawScaledPatch(15 + cfill + 1, STRINGY(8) + 35, V_SNAPTOLEFT|V_SNAPTOTOP|V_TRANSLUCENT, capsulefill);
+				V_DrawScaledPatch(15 + cfill + 1, STRINGY(8) + 35, V_SNAPTOLEFT|V_SNAPTOTOP|V_HUDTRANS, capsulefill);
 		}
 
 		if (total_ringcount >= stplyr->capsule->health)
-			V_DrawScaledPatch(SCX(40), SCY(8) + SCZ(5), V_NOSCALESTART|V_TRANSLUCENT, nredar[leveltime%8]);
+			ST_DrawOverlayPatch(SCX(40), SCY(8) + SCZ(5), nredar[leveltime%8]);
 		else
-			V_DrawScaledPatch(SCX(40), SCY(8) + SCZ(5), V_NOSCALESTART|V_TRANSLUCENT, narrow[(leveltime/2)%8]);
+			ST_DrawOverlayPatch(SCX(40), SCY(8) + SCZ(5), narrow[(leveltime/2)%8]);
 	}
 	else
-		V_DrawScaledPatch(SCX(40), SCY(8) + SCZ(5), V_NOSCALESTART|V_TRANSLUCENT, narrow[8]);
+		ST_DrawOverlayPatch(SCX(40), SCY(8) + SCZ(5), narrow[8]);
 
 	if (total_ringcount >= 100)
-		ST_DrawOverlayNum((total_ringcount >= 1000) ? SCX(76) : SCX(72), SCY(8) + SCZ(11), V_NOSCALESTART, total_ringcount);
+		ST_DrawOverlayNum((total_ringcount >= 1000) ? SCX(76) : SCX(72), SCY(8) + SCZ(11), total_ringcount);
 	else
-		ST_DrawOverlayNum(SCX(68), SCY(8) + SCZ(11), V_NOSCALESTART, total_ringcount);
+		ST_DrawOverlayNum(SCX(68), SCY(8) + SCZ(11), total_ringcount);
 #ifdef HAVE_BLUA
 	}
 #endif
@@ -1233,7 +1201,7 @@ static void ST_drawNiGHTSHUD(void)
 	&& LUA_HudEnabled(hud_nightsscore)
 #endif
 	)
-		ST_DrawNightsOverlayNum(SCX(304), SCY(16), stplyr->marescore, nightsnum, SKINCOLOR_STEELBLUE);
+		ST_DrawNightsOverlayNum(SCX(304), SCY(16), 0, stplyr->marescore, nightsnum, SKINCOLOR_STEELBLUE);
 
 	// Ideya time remaining
 	if (!stplyr->exiting && stplyr->nightstime > 0
@@ -1275,10 +1243,10 @@ static void ST_drawNiGHTSHUD(void)
 			numbersize = SCX(48)/2;
 
 		if (realnightstime < 10)
-			ST_DrawNightsOverlayNum(SCX(160) + numbersize, SCY(12), realnightstime,
+			ST_DrawNightsOverlayNum(SCX(160) + numbersize, SCY(12), 0, realnightstime,
 				nightsnum, SKINCOLOR_RED);
 		else
-			ST_DrawNightsOverlayNum(SCX(160) + numbersize, SCY(12), realnightstime,
+			ST_DrawNightsOverlayNum(SCX(160) + numbersize, SCY(12), 0, realnightstime,
 				nightsnum, SKINCOLOR_SUPER4);
 
 		// Show exact time in debug
@@ -1325,31 +1293,34 @@ static void ST_drawNiGHTSHUD(void)
 
 static void ST_drawWeaponRing(powertype_t weapon, INT32 rwflag, INT32 wepflag, INT32 xoffs, patch_t *pat)
 {
-	INT32 yelflag = 0;
+	INT32 txtflags = 0, patflags = 0;
 
 	if (stplyr->powers[weapon])
 	{
 		if (stplyr->powers[weapon] >= rw_maximums[wepflag])
-			yelflag = V_YELLOWMAP;
+			txtflags |= V_YELLOWMAP;
 
 		if (weapon == pw_infinityring
 		|| (stplyr->ringweapons & rwflag && stplyr->health > 1))
-			V_DrawScaledPatch(8 + xoffs, STRINGY(162), V_SNAPTOLEFT, pat);
+			txtflags |= V_20TRANS;
 		else
-			V_DrawTranslucentPatch(8 + xoffs, STRINGY(162), V_SNAPTOLEFT|V_80TRANS, pat);
+		{
+			txtflags |= V_TRANSLUCENT;
+			patflags =  V_80TRANS;
+		}
+
+		V_DrawScaledPatch(8 + xoffs, STRINGY(162), V_SNAPTOLEFT|patflags, pat);
 
 		if (stplyr->powers[weapon] > 99)
-			V_DrawThinString(8 + xoffs + 1, STRINGY(162), V_TRANSLUCENT | V_SNAPTOLEFT | yelflag,
-				va("%d", stplyr->powers[weapon]));
+			V_DrawThinString(8 + xoffs + 1, STRINGY(162), V_SNAPTOLEFT|txtflags, va("%d", stplyr->powers[weapon]));
 		else
-			V_DrawString(8 + xoffs, STRINGY(162), V_TRANSLUCENT | V_SNAPTOLEFT | yelflag,
-				va("%d", stplyr->powers[weapon]));
+			V_DrawString(8 + xoffs, STRINGY(162), V_SNAPTOLEFT|txtflags, va("%d", stplyr->powers[weapon]));
 
 		if (stplyr->currentweapon == wepflag)
 			V_DrawScaledPatch(6 + xoffs, STRINGY(162 - (splitscreen ? 4 : 2)), V_SNAPTOLEFT, curweapon);
 	}
 	else if (stplyr->ringweapons & rwflag)
-		V_DrawTranslucentPatch(8 + xoffs, STRINGY(162), V_SNAPTOLEFT, pat);
+		V_DrawScaledPatch(8 + xoffs, STRINGY(162), V_SNAPTOLEFT|V_TRANSLUCENT, pat);
 }
 
 static void ST_drawMatchHUD(void)
@@ -1503,15 +1474,15 @@ static void ST_drawCTFHUD(void)
 	UINT16 whichflag = 0;
 
 	// Draw the flags
-	V_DrawSmallScaledPatch(256, (splitscreen) ? STRINGY(160) : STRINGY(176), 0, rflagico);
-	V_DrawSmallScaledPatch(288, (splitscreen) ? STRINGY(160) : STRINGY(176), 0, bflagico);
+	V_DrawSmallScaledPatch(256, (splitscreen) ? STRINGY(160) : STRINGY(176), V_HUDTRANS, rflagico);
+	V_DrawSmallScaledPatch(288, (splitscreen) ? STRINGY(160) : STRINGY(176), V_HUDTRANS, bflagico);
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
 		if (players[i].gotflag & GF_REDFLAG) // Red flag isn't at base
-			V_DrawScaledPatch(256, (splitscreen) ? STRINGY(156) : STRINGY(174), 0, nonicon);
+			V_DrawScaledPatch(256, (splitscreen) ? STRINGY(156) : STRINGY(174), V_HUDTRANS, nonicon);
 		else if (players[i].gotflag & GF_BLUEFLAG) // Blue flag isn't at base
-			V_DrawScaledPatch(288, (splitscreen) ? STRINGY(156) : STRINGY(174), 0, nonicon);
+			V_DrawScaledPatch(288, (splitscreen) ? STRINGY(156) : STRINGY(174), V_HUDTRANS, nonicon);
 
 		whichflag |= players[i].gotflag;
 		if ((whichflag & (GF_REDFLAG|GF_BLUEFLAG)) == (GF_REDFLAG|GF_BLUEFLAG))
@@ -1524,9 +1495,9 @@ static void ST_drawCTFHUD(void)
 		patch_t *p = (stplyr->gotflag & GF_REDFLAG) ? gotrflag : gotbflag;
 
 		if (splitscreen)
-			V_DrawSmallScaledPatch(312, STRINGY(24) + 42, V_SNAPTORIGHT|V_SNAPTOTOP|V_TRANSLUCENT, p);
+			V_DrawSmallScaledPatch(312, STRINGY(24) + 42, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, p);
 		else
-			V_DrawScaledPatch(304, 24 + 84, V_SNAPTORIGHT|V_SNAPTOTOP|V_TRANSLUCENT, p);
+			V_DrawScaledPatch(304, 24 + 84, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, p);
 	}
 
 	// Display a countdown timer showing how much time left until the flag your team dropped returns to base.
@@ -1535,13 +1506,13 @@ static void ST_drawCTFHUD(void)
 		if (redflag && redflag->fuse > 1)
 		{
 			sprintf(timeleft, "%u", (redflag->fuse / TICRATE));
-			V_DrawCenteredString(268, STRINGY(184), V_YELLOWMAP, timeleft);
+			V_DrawCenteredString(268, STRINGY(184), V_YELLOWMAP|V_HUDTRANS, timeleft);
 		}
 
 		if (blueflag && blueflag->fuse > 1)
 		{
 			sprintf(timeleft, "%u", (blueflag->fuse / TICRATE));
-			V_DrawCenteredString(300, STRINGY(184), V_YELLOWMAP, timeleft);
+			V_DrawCenteredString(300, STRINGY(184), V_YELLOWMAP|V_HUDTRANS, timeleft);
 		}
 	}
 }
@@ -1550,11 +1521,11 @@ static void ST_drawCTFHUD(void)
 static inline void ST_drawTeamName(void)
 {
 	if (stplyr->ctfteam == 1)
-		V_DrawString(256, (splitscreen) ? STRINGY(184) : STRINGY(192), V_TRANSLUCENT, "RED TEAM");
+		V_DrawString(256, (splitscreen) ? STRINGY(184) : STRINGY(192), V_HUDTRANSHALF, "RED TEAM");
 	else if (stplyr->ctfteam == 2)
-		V_DrawString(248, (splitscreen) ? STRINGY(184) : STRINGY(192), V_TRANSLUCENT, "BLUE TEAM");
+		V_DrawString(248, (splitscreen) ? STRINGY(184) : STRINGY(192), V_HUDTRANSHALF, "BLUE TEAM");
 	else
-		V_DrawString(244, (splitscreen) ? STRINGY(184) : STRINGY(192), V_TRANSLUCENT, "SPECTATOR");
+		V_DrawString(244, (splitscreen) ? STRINGY(184) : STRINGY(192), V_HUDTRANSHALF, "SPECTATOR");
 }
 
 #ifdef CHAOSISNOTDEADYET
@@ -1562,33 +1533,28 @@ static inline void ST_drawChaosHUD(void)
 {
 	char chains[33];
 	sprintf(chains, "CHAINS: %u", stplyr->scoreadd);
-	V_DrawString(8, STRINGY(184), V_TRANSLUCENT, chains);
+	V_DrawString(8, STRINGY(184), V_HUDTRANSHALF, chains);
 }
 #endif
 
 static void ST_drawSpecialStageHUD(void)
 {
 	if (totalrings > 0)
-	{
-		if (splitscreen)
-			ST_DrawOverlayNum(hudinfo[HUD_SS_TOTALRINGS_SPLIT].x, hudinfo[HUD_SS_TOTALRINGS_SPLIT].y, V_SNAPTOTOP, totalrings);
-		else
-			ST_DrawOverlayNum(hudinfo[HUD_SS_TOTALRINGS].x, hudinfo[HUD_SS_TOTALRINGS].y, V_SNAPTOTOP, totalrings);
-	}
+		ST_DrawNumFromHudWS(HUD_SS_TOTALRINGS, totalrings);
 
 	if (leveltime < 5*TICRATE && totalrings > 0)
 	{
-		V_DrawScaledPatch(hudinfo[HUD_GETRINGS].x, SCR(hudinfo[HUD_GETRINGS].y), V_TRANSLUCENT, getall);
-		ST_DrawOverlayNum(hudinfo[HUD_GETRINGSNUM].x, SCR(hudinfo[HUD_GETRINGSNUM].y), 0, totalrings);
+		ST_DrawPatchFromHud(HUD_GETRINGS, getall);
+		ST_DrawNumFromHud(HUD_GETRINGSNUM, totalrings);
 	}
 
 	if (sstimer)
 	{
-		V_DrawString(hudinfo[HUD_TIMELEFT].x, STRINGY(hudinfo[HUD_TIMELEFT].y), 0, M_GetText("TIME LEFT"));
-		ST_DrawNightsOverlayNum(SCX(hudinfo[HUD_TIMELEFTNUM].x), SCY(hudinfo[HUD_TIMELEFTNUM].y), sstimer/TICRATE, tallnum, SKINCOLOR_WHITE);
+		V_DrawString(hudinfo[HUD_TIMELEFT].x, STRINGY(hudinfo[HUD_TIMELEFT].y), V_HUDTRANS, M_GetText("TIME LEFT"));
+		ST_DrawNightsOverlayNum(SCX(hudinfo[HUD_TIMELEFTNUM].x), SCY(hudinfo[HUD_TIMELEFTNUM].y), V_HUDTRANS, sstimer/TICRATE, tallnum, SKINCOLOR_WHITE);
 	}
 	else
-		V_DrawScaledPatch(hudinfo[HUD_TIMEUP].x, hudinfo[HUD_TIMEUP].y, V_TRANSLUCENT, timeup);
+		ST_DrawPatchFromHud(HUD_TIMEUP, timeup);
 }
 
 static INT32 ST_drawEmeraldHuntIcon(mobj_t *hunt, patch_t **patches, INT32 offset)
@@ -1627,7 +1593,7 @@ static INT32 ST_drawEmeraldHuntIcon(mobj_t *hunt, patch_t **patches, INT32 offse
 		interval = 0;
 	}
 
-	V_DrawScaledPatch(hudinfo[HUD_HUNTPICS].x+offset, STRINGY(hudinfo[HUD_HUNTPICS].y), V_TRANSLUCENT, patches[i]);
+	V_DrawScaledPatch(hudinfo[HUD_HUNTPICS].x+offset, STRINGY(hudinfo[HUD_HUNTPICS].y), V_HUDTRANS, patches[i]);
 	return interval;
 }
 
@@ -1895,9 +1861,9 @@ static void ST_overlayDrawer(void)
 		{
 			INT32 respawntime = cv_respawntime.value - stplyr->deadtimer/TICRATE;
 			if (respawntime > 0 && !stplyr->spectator)
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_TRANSLUCENT, va(M_GetText("Respawn in: %d second%s."), respawntime, respawntime == 1 ? "" : "s"));
+				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_HUDTRANSHALF, va(M_GetText("Respawn in: %d second%s."), respawntime, respawntime == 1 ? "" : "s"));
 			else
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_TRANSLUCENT, M_GetText("Press Jump to respawn."));
+				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_HUDTRANSHALF, M_GetText("Press Jump to respawn."));
 		}
 		else if (stplyr->spectator
 #ifdef HAVE_BLUA
@@ -1905,13 +1871,13 @@ static void ST_overlayDrawer(void)
 #endif
 		)
 		{
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(60), V_TRANSLUCENT, M_GetText("You are a spectator."));
+			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(60), V_HUDTRANSHALF, M_GetText("You are a spectator."));
 			if (G_GametypeHasTeams())
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_TRANSLUCENT, M_GetText("Press Fire to be assigned to a team."));
+				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_HUDTRANSHALF, M_GetText("Press Fire to be assigned to a team."));
 			else
-				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_TRANSLUCENT, M_GetText("Press Fire to enter the game."));
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(148), V_TRANSLUCENT, M_GetText("Press F12 to watch another player."));
-			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(164), V_TRANSLUCENT, M_GetText("Press Jump to float and Spin to sink."));
+				V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), V_HUDTRANSHALF, M_GetText("Press Fire to enter the game."));
+			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(148), V_HUDTRANSHALF, M_GetText("Press F12 to watch another player."));
+			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(164), V_HUDTRANSHALF, M_GetText("Press Jump to float and Spin to sink."));
 		}
 	}
 
@@ -1924,12 +1890,12 @@ void ST_Drawer(boolean refresh)
 	if (cv_seenames.value && cv_allowseenames.value && displayplayer == consoleplayer && seenplayer && seenplayer->mo)
 	{
 		if (cv_seenames.value == 1)
-			V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2 + 15, V_TRANSLUCENT, player_names[seenplayer-players]);
+			V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2 + 15, V_HUDTRANSHALF, player_names[seenplayer-players]);
 		else if (cv_seenames.value == 2)
-			V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2 + 15, V_TRANSLUCENT,
+			V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2 + 15, V_HUDTRANSHALF,
 			va("%s%s", G_GametypeHasTeams() ? ((seenplayer->ctfteam == 1) ? "\x85" : "\x84") : "", player_names[seenplayer-players]));
 		else //if (cv_seenames.value == 3)
-			V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2 + 15, V_TRANSLUCENT,
+			V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2 + 15, V_HUDTRANSHALF,
 			va("%s%s", !G_RingSlingerGametype() || (G_GametypeHasTeams() && players[consoleplayer].ctfteam == seenplayer->ctfteam)
 			 ? "\x83" : "\x85", player_names[seenplayer-players]));
 	}
diff --git a/src/st_stuff.h b/src/st_stuff.h
index 5b5df5c126..d303bc8040 100644
--- a/src/st_stuff.h
+++ b/src/st_stuff.h
@@ -83,24 +83,29 @@ typedef enum
 	HUD_LIVESPIC,
 	HUD_LIVESNUM,
 	HUD_LIVESX,
-	HUD_RINGSSPLIT,
-	HUD_RINGSNUMSPLIT,
+
 	HUD_RINGS,
+	HUD_RINGSSPLIT,
 	HUD_RINGSNUM,
+	HUD_RINGSNUMSPLIT,
+
 	HUD_SCORE,
 	HUD_SCORENUM,
+
+	HUD_TIME,
 	HUD_TIMESPLIT,
-	HUD_SECONDSSPLIT,
+	HUD_MINUTES,
 	HUD_MINUTESSPLIT,
+	HUD_TIMECOLON,
 	HUD_TIMECOLONSPLIT,
-	HUD_TIME,
-	HUD_TICS,
 	HUD_SECONDS,
-	HUD_MINUTES,
-	HUD_TIMECOLON,
+	HUD_SECONDSSPLIT,
 	HUD_TIMETICCOLON,
-	HUD_SS_TOTALRINGS_SPLIT,
+	HUD_TICS,
+
 	HUD_SS_TOTALRINGS,
+	HUD_SS_TOTALRINGS_SPLIT,
+
 	HUD_GETRINGS,
 	HUD_GETRINGSNUM,
 	HUD_TIMELEFT,
diff --git a/src/v_video.c b/src/v_video.c
index 76728a35db..032a4ec949 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -297,1355 +297,127 @@ void VID_BlitLinearScreen(const UINT8 *srcptr, UINT8 *destptr, INT32 width, INT3
 #endif
 }
 
-//
-// V_DrawTranslucentMappedPatch: like V_DrawMappedPatch, but with translucency.
-//
-void V_DrawTranslucentMappedPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch, const UINT8 *colormap)
-{
-	size_t count;
-	INT32 col, w, dupx, dupy, ofs, colfrac, rowfrac;
-	const column_t *column;
-	UINT8 *desttop, *dest;
-	const UINT8 *source, *translevel, *deststop;
-
-	if (rendermode == render_none)
-		return;
-#ifdef HWRENDER
-	// draw a hardware converted patch
-	else if (rendermode != render_soft)
-	{
-		HWR_DrawMappedPatch((GLPatch_t *)patch, x, y, scrn, colormap);
-		return;
-	}
-#endif
-
-	if (scrn & V_ALPHAMASK)
-	{
-		INT32 alphalevel = (scrn & V_ALPHAMASK) >> V_ALPHASHIFT;
-		if (alphalevel >= NUMTRANSMAPS)
-			return; // fully translucent
-		translevel = ((alphalevel)<<FF_TRANSSHIFT) - 0x10000 + transtables;
-	}
-	else // default fallback
-		translevel = ((tr_trans50)<<FF_TRANSSHIFT) - 0x10000 + transtables;
-
-	switch (scrn & V_SCALEPATCHMASK)
-	{
-	case V_NOSCALEPATCH:
-		dupx = dupy = 1;
-		break;
-	case V_SMALLSCALEPATCH:
-		dupx = vid.smalldupx;
-		dupy = vid.smalldupy;
-		break;
-	case V_MEDSCALEPATCH:
-		dupx = vid.meddupx;
-		dupy = vid.meddupy;
-		break;
-	default:
-		dupx = vid.dupx;
-		dupy = vid.dupy;
-		break;
-	}
-
-	// Only use one dup, to avoid stretching.
-	if (dupx < dupy)
-		dupy = dupx;
-	else
-		dupx = dupy;
-
-	y -= SHORT(patch->topoffset);
-	x -= SHORT(patch->leftoffset);
-
-	if (scrn & V_NOSCALESTART)
-	{
-		desttop = screens[scrn&V_PARAMMASK] + (y*vid.width) + x;
-		deststop = screens[scrn&V_PARAMMASK] + vid.rowbytes * vid.height;
-	}
-	else
-	{
-		desttop = screens[scrn&V_PARAMMASK] + (y*vid.dupy*vid.width) + (x*vid.dupx);
-		deststop = screens[scrn&V_PARAMMASK] + vid.rowbytes * vid.height;
-
-		// Center it if necessary
-		if (!(scrn & V_SCALEPATCHMASK))
-		{
-			if (vid.width != BASEVIDWIDTH * dupx)
-			{
-				// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
-				// so center this imaginary screen
-				if (scrn & V_SNAPTORIGHT)
-					desttop += (vid.width - (BASEVIDWIDTH * dupx));
-				else if (!(scrn & V_SNAPTOLEFT))
-					desttop += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
-			}
-			if (vid.height != BASEVIDHEIGHT * dupy)
-			{
-				// same thing here
-				if (scrn & V_SNAPTOBOTTOM)
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width;
-				else if (!(scrn & V_SNAPTOTOP))
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width / 2;
-			}
-		}
-	}
-	scrn &= V_PARAMMASK;
-
-	col = 0;
-	colfrac = FixedDiv(FRACUNIT, dupx<<FRACBITS);
-	rowfrac = FixedDiv(FRACUNIT, dupy<<FRACBITS);
-
-	w = SHORT(patch->width)<<FRACBITS;
-
-	for (; col < w; col += colfrac, desttop++)
-	{
-		INT32 topdelta, prevdelta = -1;
-		column = (const column_t *)((const UINT8 *)patch + LONG(patch->columnofs[col>>FRACBITS]));
-
-		while (column->topdelta != 0xff)
-		{
-			topdelta = column->topdelta;
-			if (topdelta <= prevdelta)
-				topdelta += prevdelta;
-			prevdelta = topdelta;
-			source = (const UINT8 *)column + 3;
-			dest = desttop + topdelta*dupy*vid.width;
-			count = column->length*dupy;
-
-			ofs = 0;
-			while (count--)
-			{
-				if (dest < deststop)
-					*dest = *(translevel + (((*(colormap + source[ofs>>FRACBITS]))<<8)&0xff00) + (*dest&0xff));
-				else
-					count = 0;
-				dest += vid.width;
-				ofs += rowfrac;
-			}
-
-			column = (const column_t *)((const UINT8 *)column + column->length + 4);
-		}
-	}
-}
-
-//
-// V_DrawMappedPatch: like V_DrawScaledPatch, but with a colormap.
-//
-void V_DrawMappedPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch, const UINT8 *colormap)
-{
-	size_t count;
-	INT32 col, w, dupx, dupy, ofs, colfrac, rowfrac;
-	const column_t *column;
-	UINT8 *desttop, *dest, *deststart, *destend;
-	const UINT8 *source, *deststop;
-	boolean flip = false;
-
-#ifdef HWRENDER
-	// draw a hardware converted patch
-	if (rendermode != render_soft && rendermode != render_none)
-	{
-		HWR_DrawMappedPatch((GLPatch_t *)patch, x, y, scrn, colormap);
-		return;
-	}
-#endif
-
-	switch (scrn & V_SCALEPATCHMASK)
-	{
-	case V_NOSCALEPATCH:
-		dupx = dupy = 1;
-		break;
-	case V_SMALLSCALEPATCH:
-		dupx = vid.smalldupx;
-		dupy = vid.smalldupy;
-		break;
-	case V_MEDSCALEPATCH:
-		dupx = vid.meddupx;
-		dupy = vid.meddupy;
-		break;
-	default:
-		dupx = vid.dupx;
-		dupy = vid.dupy;
-		break;
-	}
-
-	// Only use one dup, to avoid stretching.
-	if (dupx < dupy)
-		dupy = dupx;
-	else
-		dupx = dupy;
-
-	y -= SHORT(patch->topoffset);
-	if (scrn & V_FLIP)
-	{
-		flip = true;
-		x -= SHORT(patch->width) - SHORT(patch->leftoffset);
-	}
-	else
-		x -= SHORT(patch->leftoffset);
-
-	if (scrn & V_SPLITSCREEN)
-		y /= 2;
-
-	if (scrn & V_NOSCALESTART)
-	{
-		desttop = screens[scrn&V_PARAMMASK] + (y*vid.width) + x;
-		deststop = screens[scrn&V_PARAMMASK] + vid.rowbytes * vid.height;
-	}
-	else
-	{
-		desttop = screens[scrn&V_PARAMMASK] + (y*dupy*vid.width) + (x*dupx);
-		deststop = screens[scrn&V_PARAMMASK] + vid.rowbytes * vid.height;
-
-		// Center it if necessary
-		if (!(scrn & V_SCALEPATCHMASK))
-		{
-			if (vid.width != BASEVIDWIDTH * dupx)
-			{
-				// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
-				// so center this imaginary screen
-				if (scrn & V_SNAPTORIGHT)
-					desttop += (vid.width - (BASEVIDWIDTH * dupx));
-				else if (!(scrn & V_SNAPTOLEFT))
-					desttop += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
-			}
-			if (vid.height != BASEVIDHEIGHT * dupy)
-			{
-				// same thing here
-				if (scrn & V_SPLITSCREEN && scrn & V_SNAPTOBOTTOM)
-					desttop += (vid.height/2 - (BASEVIDHEIGHT/2 * dupy)) * vid.width;
-				else if (scrn & V_SNAPTOBOTTOM)
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width;
-				else if (!(scrn & V_SNAPTOTOP))
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width / 2;
-			}
-			// if it's meant to cover the whole screen, black out the rest
-			if (x == 0 && SHORT(patch->width) == BASEVIDWIDTH && y == 0 && SHORT(patch->height) == BASEVIDHEIGHT)
-			{
-				column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[0]));
-				source = (const UINT8 *)(column) + 3;
-				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : *(colormap + source[0])));
-			}
-		}
-	}
-	scrn &= V_PARAMMASK;
-
-	deststart = desttop;
-	destend = desttop + SHORT(patch->width) * dupx;
-
-	col = 0;
-	colfrac = FixedDiv(FRACUNIT, dupx<<FRACBITS);
-	rowfrac = FixedDiv(FRACUNIT, dupy<<FRACBITS);
-
-	w = SHORT(patch->width)<<FRACBITS;
-
-	for (; col < w; col += colfrac, desttop++)
-	{
-		INT32 topdelta, prevdelta = -1;
-		column = (const column_t *)((const UINT8 *)patch + LONG(patch->columnofs[col>>FRACBITS]));
-
-		while (column->topdelta != 0xff)
-		{
-			topdelta = column->topdelta;
-			if (topdelta <= prevdelta)
-				topdelta += prevdelta;
-			prevdelta = topdelta;
-			source = (const UINT8 *)column + 3;
-			dest = desttop;
-			if (flip)
-				dest = deststart + (destend - desttop);
-			dest += topdelta*dupy*vid.width;
-			count = column->length*dupy;
-
-			ofs = 0;
-			while (count--)
-			{
-				if (dest < deststop)
-					*dest = *(colormap + source[ofs>>FRACBITS]);
-				else
-					count = 0;
-				dest += vid.width;
-				ofs += rowfrac;
-			}
-
-			column = (const column_t *)((const UINT8 *)column + column->length + 4);
-		}
-	}
-}
-
-//
-// V_DrawScaledPatch
-//
-// Like V_DrawPatch, but scaled 2, 3, 4 times the original size and position.
-// This is used for menu and title screens, with high resolutions.
-//
-void V_DrawScaledPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch)
-{
-	size_t count;
-	INT32 col, dupx, dupy, ofs, colfrac, rowfrac;
-	const column_t *column;
-	UINT8 *desttop, *dest, *deststart, *destend;
-	const UINT8 *source, *deststop;
-	boolean flip = false;
-
-#ifdef PARANOIA
-	if (!patch)
-		CONS_Alert(CONS_ERROR, "NULL patch passed to V_DrawScaledPatch?! Crashing!\n");
-#endif
-
-	if (rendermode == render_none)
-		return;
-#ifdef HWRENDER
-	// draw a hardware converted patch
-	else if (rendermode != render_soft && !con_startup)
-	{
-		HWR_DrawPatch((GLPatch_t *)patch, x, y, scrn);
-		return;
-	}
-#endif
-
-	switch (scrn & V_SCALEPATCHMASK)
-	{
-	case V_NOSCALEPATCH:
-		dupx = dupy = 1;
-		break;
-	case V_SMALLSCALEPATCH:
-		dupx = vid.smalldupx;
-		dupy = vid.smalldupy;
-		break;
-	case V_MEDSCALEPATCH:
-		dupx = vid.meddupx;
-		dupy = vid.meddupy;
-		break;
-	default:
-		dupx = vid.dupx;
-		dupy = vid.dupy;
-		break;
-	}
-
-	// Only use one dup, to avoid stretching.
-	if (dupx < dupy)
-		dupy = dupx;
-	else
-		dupx = dupy;
-
-	y -= SHORT(patch->topoffset);
-	if (scrn & V_FLIP)
-	{
-		flip = true;
-		x -= SHORT(patch->width) - SHORT(patch->leftoffset);
-	}
-	else
-		x -= SHORT(patch->leftoffset);
-
-	if (scrn & V_SPLITSCREEN)
-		y /= 2;
-
-	colfrac = FixedDiv(FRACUNIT, dupx<<FRACBITS);
-	rowfrac = FixedDiv(FRACUNIT, dupy<<FRACBITS);
-
-#ifdef _WINDOWS
-	// Special case for hardware mode before display mode is set
-	if (rendermode != render_soft && rendermode != render_none)
-		desttop = vid.buffer;
-	else
-#endif
-		desttop = screens[scrn&V_PARAMMASK];
-
-	deststop = desttop + vid.rowbytes * vid.height;
-
-	if (!desttop)
-		return;
-
-	if (scrn & V_NOSCALESTART)
-		desttop += (y*vid.width) + x;
-	else
-	{
-		desttop += (y*dupy*vid.width) + (x*dupx);
-
-		// Center it if necessary
-		if (!(scrn & V_SCALEPATCHMASK))
-		{
-			if (vid.width != BASEVIDWIDTH * dupx)
-			{
-				// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
-				// so center this imaginary screen
-				if (scrn & V_SNAPTORIGHT)
-					desttop += (vid.width - (BASEVIDWIDTH * dupx));
-				else if (!(scrn & V_SNAPTOLEFT))
-					desttop += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
-			}
-			if (vid.height != BASEVIDHEIGHT * dupy)
-			{
-				// same thing here
-				if (scrn & V_SPLITSCREEN && scrn & V_SNAPTOBOTTOM)
-					desttop += (vid.height/2 - (BASEVIDHEIGHT/2 * dupy)) * vid.width;
-				else if (scrn & V_SNAPTOBOTTOM)
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width;
-				else if (!(scrn & V_SNAPTOTOP))
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width / 2;
-			}
-			// if it's meant to cover the whole screen, black out the rest
-			if (x == 0 && SHORT(patch->width) == BASEVIDWIDTH && y == 0 && SHORT(patch->height) == BASEVIDHEIGHT)
-			{
-				column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[0]));
-				source = (const UINT8 *)(column) + 3;
-				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
-			}
-		}
-	}
-	deststart = desttop;
-	destend = desttop + SHORT(patch->width) * dupx;
-
-	if (destend + (SHORT(patch->height) * dupy) * vid.width < screens[scrn&V_PARAMMASK])
-		return;
-
-	for (col = 0; desttop < destend; col += colfrac, desttop++)
-	{
-		register INT32 heightmask;
-		INT32 topdelta, prevdelta = -1;
-
-		column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[col>>FRACBITS]));
-
-		while (column->topdelta != 0xff)
-		{
-			topdelta = column->topdelta;
-			if (topdelta <= prevdelta)
-				topdelta += prevdelta;
-			prevdelta = topdelta;
-			source = (const UINT8 *)(column) + 3;
-			dest = desttop;
-			if (flip)
-				dest = deststart + (destend - desttop);
-			dest += topdelta*dupy*vid.width;
-			count = column->length*dupy;
-
-			ofs = 0;
-
-			heightmask = column->length - 1;
-
-			while (dest < screens[scrn&V_PARAMMASK] && count)
-			{
-				dest += vid.width;
-				ofs += rowfrac;
-				--count;
-			}
-
-			if (count && column->length & heightmask)
-			{
-				heightmask++;
-				heightmask <<= FRACBITS;
-
-				if (rowfrac < 0)
-					while ((rowfrac += heightmask) < 0)
-						;
-				else
-					while (rowfrac >= heightmask)
-						rowfrac -= heightmask;
-
-				do
-				{
-					if (dest < deststop)
-						*dest = source[ofs>>FRACBITS];
-					else
-						count = 0;
-					dest += vid.width;
-					ofs += rowfrac;
-					if ((ofs + rowfrac) > heightmask)
-						goto donedrawing;
-				} while (count--);
-			}
-			else
-			{
-				while (count--)
-				{
-					if (dest < deststop)
-						*dest = source[ofs>>FRACBITS];
-					else
-						count = 0;
-					dest += vid.width;
-					ofs += rowfrac;
-				}
-			}
-donedrawing:
-			column = (const column_t *)((const UINT8 *)column + column->length + 4);
-		}
-	}
-}
-
-/** Draws a patch to the screen, being careful not to go off the right
-  * side or bottom of the screen. This is slower than a normal draw, so
-  * it gets a separate function.
-  *
-  * With hardware rendering, the patch is clipped anyway, so this is
-  * just the same as V_DrawScaledPatch().
-  *
-  * \param x     X coordinate for left side, based on 320x200 screen.
-  * \param y     Y coordinate for top, based on 320x200 screen.
-  * \param scrn  Any of several flags to change the drawing behavior.
-  * \param patch Patch to draw.
-  * \sa V_DrawScaledPatch
-  * \author Graue <graue@oceanbase.org>
-  */
-static void V_DrawClippedScaledPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch)
-{
-	size_t count;
-	INT32 col, dupx, dupy, ofs, colfrac, rowfrac;
-	const column_t *column;
-	UINT8 *desttop, *dest, *destend;
-	const UINT8 *source, *deststop;
-
-#ifdef HWRENDER
-	// draw a hardware converted patch
-	if (rendermode != render_soft && rendermode != render_none)
-	{
-		// V_NOSCALESTART might be impled for software, but not for hardware!
-		HWR_DrawClippedPatch((GLPatch_t *)patch, x, y, V_NOSCALESTART);
-		return;
-	}
-#endif
-
-	switch (scrn & V_SCALEPATCHMASK)
-	{
-	case V_NOSCALEPATCH:
-		dupx = dupy = 1;
-		break;
-	case V_SMALLSCALEPATCH:
-		dupx = vid.smalldupx;
-		dupy = vid.smalldupy;
-		break;
-	case V_MEDSCALEPATCH:
-		dupx = vid.meddupx;
-		dupy = vid.meddupy;
-		break;
-	default:
-		dupx = vid.dupx;
-		dupy = vid.dupy;
-		break;
-	}
-
-	// Only use one dup, to avoid stretching.
-	if (dupx < dupy)
-		dupy = dupx;
-	else
-		dupx = dupy;
-
-	y -= SHORT(patch->topoffset);
-	x -= SHORT(patch->leftoffset);
-
-	if (x < 0 || y < 0 || x >= vid.width || y >= vid.height)
-		return;
-
-	colfrac = FixedDiv(FRACUNIT, dupx<<FRACBITS);
-	rowfrac = FixedDiv(FRACUNIT, dupy<<FRACBITS);
-
-	if (!screens[scrn&V_PARAMMASK])
-		return;
-
-	desttop = screens[scrn&V_PARAMMASK] + (y*vid.width) + x;
-	deststop = screens[scrn&V_PARAMMASK] + vid.rowbytes * vid.height;
-
-	if (!desttop)
-		return;
-
-	// make sure it doesn't go off the right
-	if (x + SHORT(patch->width)*dupx <= vid.width)
-		destend = desttop + SHORT(patch->width) * dupx;
-	else
-		destend = desttop + vid.width - x;
-
-	for (col = 0; desttop < destend; col += colfrac, desttop++)
-	{
-		register INT32 heightmask;
-		INT32 topdelta, prevdelta = -1;
-
-		column = (const column_t *)((const UINT8 *)patch + LONG(patch->columnofs[col>>FRACBITS]));
-
-		while (column->topdelta != 0xff)
-		{
-			topdelta = column->topdelta;
-			if (topdelta <= prevdelta)
-				topdelta += prevdelta;
-			prevdelta = topdelta;
-			source = (const UINT8 *)column + 3;
-			dest = desttop + topdelta*dupy*vid.width;
-			count = column->length*dupy;
-			if ((dest-screens[scrn&V_PARAMMASK])/vid.width + count > (unsigned)vid.height - 1)
-				count = vid.height - 1 - (dest-screens[scrn&V_PARAMMASK])/vid.width;
-			if (count <= 0)
-				break;
-
-			ofs = 0;
-
-			heightmask = column->length - 1;
-
-			if (column->length & heightmask)
-			{
-				// length is not a power of two
-				heightmask++;
-				heightmask <<= FRACBITS;
-
-				if (rowfrac < 0)
-					while ((rowfrac += heightmask) < 0)
-						;
-				else
-					while (rowfrac >= heightmask)
-						rowfrac -= heightmask;
-
-				do
-				{
-					if (dest < deststop)
-						*dest = source[ofs>>FRACBITS];
-					else
-						count = 0;
-					dest += vid.width;
-					ofs += rowfrac;
-					if ((ofs + rowfrac) > heightmask)
-						goto doneclipping;
-				} while (count--);
-			}
-			else
-			{
-				// length is a power of two
-				while (count--)
-				{
-					if (dest < deststop)
-						*dest = source[ofs>>FRACBITS];
-					else
-						count = 0;
-					dest += vid.width;
-					ofs += rowfrac;
-				}
-			}
-doneclipping:
-			column = (const column_t *)((const UINT8 *)column + column->length + 4);
-		}
-	}
-}
-
-// Draws a patch scaled to arbitrary size.
-void V_DrawSciencePatch(fixed_t x, fixed_t y, INT32 scrn, patch_t *patch, fixed_t science)
-{
-	fixed_t col, ofs, colfrac, rowfrac, fdup;
-	INT32 dupx, dupy;
-	const column_t *column;
-	UINT8 *desttop, *dest;
-	const UINT8 *source, *deststop;
-
-#ifdef HWRENDER
-	// oh please
-	if (rendermode != render_soft && rendermode != render_none)
-	{
-		HWR_DrawSciencePatch((GLPatch_t *)patch, x, y, scrn, science);
-		return;
-	}
-#endif
-
-	// only use one dup, to avoid stretching (har har)
-	dupx = dupy = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
-	fdup = FixedMul(dupx<<FRACBITS, science);
-	colfrac = FixedDiv(FRACUNIT, fdup);
-	rowfrac = FixedDiv(FRACUNIT, fdup);
-
-	y -= FixedMul(SHORT(patch->topoffset)<<FRACBITS, science);
-	x -= FixedMul(SHORT(patch->leftoffset)<<FRACBITS, science);
-
-	desttop = screens[scrn&V_PARAMMASK];
-
-	if (!desttop)
-		return;
-
-	deststop = desttop + vid.rowbytes * vid.height;
-
-	if (scrn & V_NOSCALESTART) {
-		x >>= FRACBITS;
-		y >>= FRACBITS;
-		desttop += (y*vid.width) + x;
-	} else
-	{
-		x = FixedMul(x,dupx<<FRACBITS);
-		y = FixedMul(y,dupy<<FRACBITS);
-		x >>= FRACBITS;
-		y >>= FRACBITS;
-		desttop += (y*vid.width) + x;
-
-		// Center it if necessary
-		if (!(scrn & V_SCALEPATCHMASK))
-		{
-			if (vid.width != BASEVIDWIDTH * dupx)
-			{
-				// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
-				// so center this imaginary screen
-				if (scrn & V_SNAPTORIGHT)
-					desttop += (vid.width - (BASEVIDWIDTH * dupx));
-				else if (!(scrn & V_SNAPTOLEFT))
-					desttop += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
-			}
-			if (vid.height != BASEVIDHEIGHT * dupy)
-			{
-				// same thing here
-				if (scrn & V_SNAPTOBOTTOM)
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width;
-				else if (!(scrn & V_SNAPTOTOP))
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width / 2;
-			}
-			// if it's meant to cover the whole screen, black out the rest
-			if (x == 0 && SHORT(patch->width) == BASEVIDWIDTH && y == 0 && SHORT(patch->height) == BASEVIDHEIGHT)
-			{
-				column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[0]));
-				source = (const UINT8 *)(column) + 3;
-				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
-			}
-		}
-	}
-
-	for (col = 0; (col>>FRACBITS) < SHORT(patch->width); col += colfrac, desttop++)
-	{
-		INT32 topdelta, prevdelta = -1;
-		if (x < 0) { // don't draw off the left of the screen (WRAP PREVENTION)
-			x++;
-			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]));
-
-		while (column->topdelta != 0xff)
-		{
-			topdelta = column->topdelta;
-			if (topdelta <= prevdelta)
-				topdelta += prevdelta;
-			prevdelta = topdelta;
-			source = (const UINT8 *)(column) + 3;
-			dest = desttop;
-			dest += FixedInt(FixedMul(topdelta<<FRACBITS,fdup))*vid.width;
-
-			for (ofs = 0; dest < deststop && (ofs>>FRACBITS) < column->length; ofs += rowfrac)
-			{
-				if (dest >= screens[scrn&V_PARAMMASK]) // don't draw off the top of the screen (CRASH PREVENTION)
-					*dest = source[ofs>>FRACBITS];
-				dest += vid.width;
-			}
-			column = (const column_t *)((const UINT8 *)column + column->length + 4);
-		}
-	}
-}
-
-// Draws a patch cropped and scaled to arbitrary size.
-void V_DrawCroppedPatch(fixed_t x, fixed_t y, INT32 scrn, patch_t *patch, fixed_t science, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h)
-{
-	fixed_t col, ofs, colfrac, rowfrac, fdup;
-	INT32 dupx, dupy;
-	const column_t *column;
-	UINT8 *desttop, *dest;
-	const UINT8 *source, *deststop;
-
-#ifdef HWRENDER
-	// Done
-	if (rendermode != render_soft && rendermode != render_none)
-	{
-		HWR_DrawCroppedPatch((GLPatch_t *)patch, x, y, scrn, science, sx, sy, w, h);
-		return;
-	}
-#endif
-
-	// only use one dup, to avoid stretching (har har)
-	dupx = dupy = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
-	fdup = FixedMul(dupx<<FRACBITS, science);
-	colfrac = FixedDiv(FRACUNIT, fdup);
-	rowfrac = FixedDiv(FRACUNIT, fdup);
-
-	y -= FixedMul(SHORT(patch->topoffset)<<FRACBITS, science);
-	x -= FixedMul(SHORT(patch->leftoffset)<<FRACBITS, science);
-
-	desttop = screens[scrn&V_PARAMMASK];
-
-	if (!desttop)
-		return;
-
-	deststop = desttop + vid.rowbytes * vid.height;
-
-	if (scrn & V_NOSCALESTART) {
-		x >>= FRACBITS;
-		y >>= FRACBITS;
-		desttop += (y*vid.width) + x;
-	} else
-	{
-		x = FixedMul(x,dupx<<FRACBITS);
-		y = FixedMul(y,dupy<<FRACBITS);
-		x >>= FRACBITS;
-		y >>= FRACBITS;
-		desttop += (y*vid.width) + x;
-
-		// Center it if necessary
-		if (!(scrn & V_SCALEPATCHMASK))
-		{
-			if (vid.width != BASEVIDWIDTH * dupx)
-			{
-				// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
-				// so center this imaginary screen
-				if (scrn & V_SNAPTORIGHT)
-					desttop += (vid.width - (BASEVIDWIDTH * dupx));
-				else if (!(scrn & V_SNAPTOLEFT))
-					desttop += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
-			}
-			if (vid.height != BASEVIDHEIGHT * dupy)
-			{
-				// same thing here
-				if (scrn & V_SNAPTOBOTTOM)
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width;
-				else if (!(scrn & V_SNAPTOTOP))
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width / 2;
-			}
-			// if it's meant to cover the whole screen, black out the rest
-			if (x == 0 && SHORT(patch->width) == BASEVIDWIDTH && y == 0 && SHORT(patch->height) == BASEVIDHEIGHT)
-			{
-				column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[0]));
-				source = (const UINT8 *)(column) + 3;
-				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
-			}
-		}
-	}
-
-	for (col = sx<<FRACBITS; (col>>FRACBITS) < SHORT(patch->width) && (col>>FRACBITS) < w; col += colfrac, desttop++)
-	{
-		INT32 topdelta, prevdelta = -1;
-		if (x < 0) { // don't draw off the left of the screen (WRAP PREVENTION)
-			x++;
-			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]));
-
-		while (column->topdelta != 0xff)
-		{
-			topdelta = column->topdelta;
-			if (topdelta <= prevdelta)
-				topdelta += prevdelta;
-			prevdelta = topdelta;
-			source = (const UINT8 *)(column) + 3;
-			dest = desttop;
-			dest += FixedInt(FixedMul(topdelta<<FRACBITS,fdup))*vid.width;
-
-			for (ofs = sy<<FRACBITS; dest < deststop && (ofs>>FRACBITS) < column->length && (ofs>>FRACBITS) < h; ofs += rowfrac)
-			{
-				if (dest >= screens[scrn&V_PARAMMASK]) // don't draw off the top of the screen (CRASH PREVENTION)
-					*dest = source[ofs>>FRACBITS];
-				dest += vid.width;
-			}
-			column = (const column_t *)((const UINT8 *)column + column->length + 4);
-		}
-	}
-}
-
-// Draws a patch 2x as small.
-void V_DrawSmallScaledPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch)
-{
-	fixed_t col, ofs, colfrac, rowfrac, fdup;
-	INT32 dupx, dupy;
-	const column_t *column;
-	UINT8 *desttop, *dest, *deststart, *destend;
-	const UINT8 *source, *deststop;
-	boolean flip = false;
-
-#ifdef HWRENDER
-	// draw a hardware converted patch
-	if (rendermode != render_soft && rendermode != render_none)
-	{
-		if (!(scrn & V_NOSCALESTART)) // Graue 07-08-2004: I have no idea why this works
-		{
-			x = FixedInt(FixedMul(vid.fdupx, x*FRACUNIT));
-			y = FixedInt(FixedMul(vid.fdupy, y*FRACUNIT));
-			scrn |= V_NOSCALESTART;
-		}
-		HWR_DrawSmallPatch((GLPatch_t *)patch, x, y, scrn, colormaps);
-		return;
-	}
-#endif
-
-	// only use one dup, to avoid stretching.
-	dupx = dupy = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
-	fdup = dupx<<(FRACBITS-1);
-	colfrac = FixedDiv(FRACUNIT, fdup);
-	rowfrac = FixedDiv(FRACUNIT, fdup);
-
-	y -= SHORT(patch->topoffset)/2;
-	if (scrn & V_FLIP)
-	{
-		flip = true;
-		x -= (SHORT(patch->width) - SHORT(patch->leftoffset))/2;
-	}
-	else
-		x -= SHORT(patch->leftoffset)/2;
-
-	if (scrn & V_SPLITSCREEN)
-		y /= 2;
-
-	desttop = screens[scrn&V_PARAMMASK];
-
-	if (!desttop)
-		return;
-
-	deststop = desttop + vid.rowbytes * vid.height;
-
-	if (scrn & V_NOSCALESTART)
-		desttop += (y*vid.width) + x;
-	else
-	{
-		x = x*dupx;
-		y = y*dupy;
-		desttop += (y*vid.width) + x;
-
-		// Center it if necessary
-		if (!(scrn & V_SCALEPATCHMASK))
-		{
-			if (vid.width != BASEVIDWIDTH * dupx)
-			{
-				// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
-				// so center this imaginary screen
-				if (scrn & V_SNAPTORIGHT)
-					desttop += (vid.width - (BASEVIDWIDTH * dupx));
-				else if (!(scrn & V_SNAPTOLEFT))
-					desttop += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
-			}
-			if (vid.height != BASEVIDHEIGHT * dupy)
-			{
-				// same thing here
-				if (scrn & V_SPLITSCREEN && scrn & V_SNAPTOBOTTOM)
-					desttop += (vid.height/2 - (BASEVIDHEIGHT/2 * dupy)) * vid.width;
-				else if (scrn & V_SNAPTOBOTTOM)
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width;
-				else if (!(scrn & V_SNAPTOTOP))
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width / 2;
-			}
-			// if it's meant to cover the whole screen, black out the rest
-			if (x == 0 && SHORT(patch->width) == BASEVIDWIDTH && y == 0 && SHORT(patch->height) == BASEVIDHEIGHT)
-			{
-				column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[0]));
-				source = (const UINT8 *)(column) + 3;
-				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
-			}
-		}
-	}
-
-	deststart = desttop;
-	destend = desttop + SHORT(patch->width) * dupx / 2;
-
-	for (col = 0; (col>>FRACBITS) < SHORT(patch->width); col += colfrac, desttop++)
-	{
-		INT32 topdelta, prevdelta = -1;
-		if (x < 0) { // don't draw off the left of the screen (WRAP PREVENTION)
-			x++;
-			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]));
-
-		while (column->topdelta != 0xff)
-		{
-			topdelta = column->topdelta;
-			if (topdelta <= prevdelta)
-				topdelta += prevdelta;
-			prevdelta = topdelta;
-			source = (const UINT8 *)(column) + 3;
-			dest = desttop;
-			if (flip)
-				dest = deststart + (destend - desttop);
-			dest += FixedInt(FixedMul(topdelta<<FRACBITS,fdup))*vid.width;
-
-			for (ofs = 0; dest < deststop && (ofs>>FRACBITS) < column->length; ofs += rowfrac)
-			{
-				if (dest >= screens[scrn&V_PARAMMASK]) // don't draw off the top of the screen (CRASH PREVENTION)
-					*dest = source[ofs>>FRACBITS];
-				dest += vid.width;
-			}
-			column = (const column_t *)((const UINT8 *)column + column->length + 4);
-		}
-	}
-}
-
-// Draws a patch 2x as small, translucent, and colormapped.
-void V_DrawSmallTranslucentMappedPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch, const UINT8 *colormap)
-{
-	fixed_t col, ofs, colfrac, rowfrac, fdup;
-	INT32 dupx, dupy;
-	const column_t *column;
-	UINT8 *desttop, *dest, *deststart, *destend;
-	const UINT8 *source, *deststop;
-	UINT8 *translevel;
-	boolean flip = false;
-
-#ifdef HWRENDER
-	// draw a hardware converted patch
-	if (rendermode != render_soft && rendermode != render_none)
-	{
-		if (!(scrn & V_NOSCALESTART)) // Graue 07-08-2004: I have no idea why this works
-		{
-			x = FixedInt(FixedMul(vid.fdupx, x*FRACUNIT));
-			y = FixedInt(FixedMul(vid.fdupy, y*FRACUNIT));
-			scrn |= V_NOSCALESTART;
-		}
-		HWR_DrawSmallPatch((GLPatch_t *)patch, x, y, scrn, colormap);
-		return;
-	}
-#endif
-
-	if (scrn & V_ALPHAMASK)
-	{
-		INT32 alphalevel = (scrn & V_ALPHAMASK) >> V_ALPHASHIFT;
-		if (alphalevel >= NUMTRANSMAPS)
-			return; // fully translucent
-		translevel = ((alphalevel)<<FF_TRANSSHIFT) - 0x10000 + transtables;
-	}
-	else // default fallback
-		translevel = ((tr_trans50)<<FF_TRANSSHIFT) - 0x10000 + transtables;
-
-	// only use one dup, to avoid stretching.
-	fdup = (vid.dupx < vid.dupy ? vid.dupx<<FRACBITS : vid.dupy<<FRACBITS)/2;
-	dupx = dupy = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
-	colfrac = FixedDiv(FRACUNIT, fdup);
-	rowfrac = FixedDiv(FRACUNIT, fdup);
-
-	y -= SHORT(patch->topoffset)/2;
-	if (scrn & V_FLIP)
-	{
-		flip = true;
-		x -= (SHORT(patch->width) - SHORT(patch->leftoffset))/2;
-	}
-	else
-		x -= SHORT(patch->leftoffset)/2;
-
-	desttop = screens[scrn&V_PARAMMASK];
-
-	if (!desttop)
-		return;
-
-	deststop = desttop + vid.rowbytes * vid.height;
-
-	if (scrn & V_NOSCALESTART)
-		desttop += (y*vid.width) + x;
-	else
-	{
-		desttop += (y*dupy*vid.width) + (x*dupx);
-
-		// Center it if necessary
-		if (!(scrn & V_SCALEPATCHMASK))
-		{
-			if (vid.width != BASEVIDWIDTH * dupx)
-			{
-				// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
-				// so center this imaginary screen
-				if (scrn & V_SNAPTORIGHT)
-					desttop += (vid.width - (BASEVIDWIDTH * dupx));
-				else if (!(scrn & V_SNAPTOLEFT))
-					desttop += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
-			}
-			if (vid.height != BASEVIDHEIGHT * dupy)
-			{
-				// same thing here
-				if (scrn & V_SNAPTOBOTTOM)
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width;
-				else if (!(scrn & V_SNAPTOTOP))
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width / 2;
-			}
-		}
-	}
-
-	deststart = desttop;
-	destend = desttop + SHORT(patch->width) * dupx / 2;
-
-	for (col = 0; (col>>FRACBITS) < SHORT(patch->width); col += colfrac, desttop++)
-	{
-		INT32 topdelta, prevdelta = -1;
-		column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[col>>FRACBITS]));
-
-		while (column->topdelta != 0xff)
-		{
-			topdelta = column->topdelta;
-			if (topdelta <= prevdelta)
-				topdelta += prevdelta;
-			prevdelta = topdelta;
-			source = (const UINT8 *)(column) + 3;
-			dest = desttop;
-			if (flip)
-				dest = deststart + (destend - desttop);
-			dest += FixedInt(FixedMul(topdelta<<FRACBITS,fdup))*vid.width;
-
-			for (ofs = 0; dest < deststop && (ofs>>FRACBITS) < column->length; ofs += rowfrac)
-			{
-				*dest = *(translevel + (colormap[source[ofs>>FRACBITS]]<<8) + (*dest));
-				dest += vid.width;
-			}
-			column = (const column_t *)((const UINT8 *)column + column->length + 4);
-		}
-	}
-}
-
-// Draws a patch 2x as small, and translucent.
-void V_DrawSmallTranslucentPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch)
-{
-	fixed_t col, ofs, colfrac, rowfrac, fdup;
-	INT32 dupx, dupy;
-	const column_t *column;
-	UINT8 *desttop, *dest, *deststart, *destend;
-	const UINT8 *source, *deststop;
-	UINT8 *translevel;
-	boolean flip = false;
-
-#ifdef HWRENDER
-	// draw a hardware converted patch
-	if (rendermode != render_soft && rendermode != render_none)
-	{
-		if (!(scrn & V_NOSCALESTART)) // Graue 07-08-2004: I have no idea why this works
-		{
-			x = FixedInt(FixedMul(vid.fdupx, x*FRACUNIT));
-			y = FixedInt(FixedMul(vid.fdupy, y*FRACUNIT));
-			scrn |= V_NOSCALESTART;
-		}
-		HWR_DrawSmallPatch((GLPatch_t *)patch, x, y, scrn, colormaps);
-		return;
-	}
-#endif
-
-	if (scrn & V_ALPHAMASK)
-	{
-		INT32 alphalevel = (scrn & V_ALPHAMASK) >> V_ALPHASHIFT;
-		if (alphalevel >= NUMTRANSMAPS)
-			return; // fully translucent
-		translevel = ((alphalevel)<<FF_TRANSSHIFT) - 0x10000 + transtables;
-	}
-	else // default fallback
-		translevel = ((tr_trans50)<<FF_TRANSSHIFT) - 0x10000 + transtables;
-
-	// only use one dup, to avoid stretching.
-	dupx = dupy = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
-	fdup = dupx<<(FRACBITS-1);
-	colfrac = FixedDiv(FRACUNIT, fdup);
-	rowfrac = FixedDiv(FRACUNIT, fdup);
-
-	y -= SHORT(patch->topoffset)/2;
-	if (scrn & V_FLIP)
-	{
-		flip = true;
-		x -= (SHORT(patch->width) - SHORT(patch->leftoffset))/2;
-	}
-	else
-		x -= SHORT(patch->leftoffset)/2;
-
-	desttop = screens[scrn&V_PARAMMASK];
-
-	if (!desttop)
-		return;
-
-	deststop = desttop + vid.rowbytes * vid.height;
-
-	if (scrn & V_NOSCALESTART)
-		desttop += (y*vid.width) + x;
-	else
-	{
-		desttop += (y*dupy*vid.width) + (x*dupx);
-
-		// Center it if necessary
-		if (!(scrn & V_SCALEPATCHMASK))
-		{
-			if (vid.width != BASEVIDWIDTH * dupx)
-			{
-				// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
-				// so center this imaginary screen
-				if (scrn & V_SNAPTORIGHT)
-					desttop += (vid.width - (BASEVIDWIDTH * dupx));
-				else if (!(scrn & V_SNAPTOLEFT))
-					desttop += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
-			}
-			if (vid.height != BASEVIDHEIGHT * dupy)
-			{
-				// same thing here
-				if (scrn & V_SNAPTOBOTTOM)
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width;
-				else if (!(scrn & V_SNAPTOTOP))
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width / 2;
-			}
-		}
-	}
-
-	deststart = desttop;
-	destend = desttop + SHORT(patch->width) * dupx / 2;
-
-	for (col = 0; (col>>FRACBITS) < SHORT(patch->width); col += colfrac, desttop++)
-	{
-		INT32 topdelta, prevdelta = -1;
-		column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[col>>FRACBITS]));
-
-		while (column->topdelta != 0xff)
-		{
-			topdelta = column->topdelta;
-			if (topdelta <= prevdelta)
-				topdelta += prevdelta;
-			prevdelta = topdelta;
-			source = (const UINT8 *)(column) + 3;
-			dest = desttop;
-			if (flip)
-				dest = deststart + (destend - desttop);
-			dest += FixedInt(FixedMul(topdelta<<FRACBITS,fdup))*vid.width;
-
-			for (ofs = 0; dest < deststop && (ofs>>FRACBITS) < column->length; ofs += rowfrac)
-			{
-				*dest = *(translevel + ((source[ofs>>FRACBITS]<<8)&0xff00) + (*dest&0xff));
-				dest += vid.width;
-			}
-			column = (const column_t *)((const UINT8 *)column + column->length + 4);
-		}
-	}
-}
-
-// Draws a patch 2x as small, and colormapped.
-void V_DrawSmallMappedPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch, const UINT8 *colormap)
-{
-	fixed_t col, ofs, colfrac, rowfrac, fdup;
-	INT32 dupx, dupy;
-	const column_t *column;
-	UINT8 *desttop, *dest, *deststart, *destend;
-	const UINT8 *source, *deststop;
-	boolean flip = false;
-
-#ifdef HWRENDER
-	// draw a hardware converted patch
-	if (rendermode != render_soft && rendermode != render_none)
-	{
-		if (!(scrn & V_NOSCALESTART)) // Graue 07-08-2004: I have no idea why this works
-		{
-			x = FixedInt(FixedMul(vid.fdupx, x*FRACUNIT));
-			y = FixedInt(FixedMul(vid.fdupy, y*FRACUNIT));
-			scrn |= V_NOSCALESTART;
-		}
-		HWR_DrawSmallPatch((GLPatch_t *)patch, x, y, scrn, colormap);
-		return;
-	}
-#endif
-
-	// only use one dup, to avoid stretching.
-	dupx = dupy = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
-	fdup = dupx<<(FRACBITS-1);
-	colfrac = FixedDiv(FRACUNIT, fdup);
-	rowfrac = FixedDiv(FRACUNIT, fdup);
-
-	y -= SHORT(patch->topoffset)/2;
-	if (scrn & V_FLIP)
-	{
-		flip = true;
-		x -= (SHORT(patch->width) - SHORT(patch->leftoffset))/2;
-	}
-	else
-		x -= SHORT(patch->leftoffset)/2;
-
-	if (scrn & V_SPLITSCREEN)
-		y /= 2;
-
-	desttop = screens[scrn&V_PARAMMASK];
-
-	if (!desttop)
-		return;
-
-	deststop = desttop + vid.rowbytes * vid.height;
-
-	if (scrn & V_NOSCALESTART)
-		desttop += (y*vid.width) + x;
-	else
-	{
-		desttop += (y*dupy*vid.width) + (x*dupx);
-
-		// Center it if necessary
-		if (!(scrn & V_SCALEPATCHMASK))
-		{
-			if (vid.width != BASEVIDWIDTH * dupx)
-			{
-				// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
-				// so center this imaginary screen
-				if (scrn & V_SNAPTORIGHT)
-					desttop += (vid.width - (BASEVIDWIDTH * dupx));
-				else if (!(scrn & V_SNAPTOLEFT))
-					desttop += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
-			}
-			if (vid.height != BASEVIDHEIGHT * dupy)
-			{
-				// same thing here
-				if (scrn & V_SPLITSCREEN && scrn & V_SNAPTOBOTTOM)
-					desttop += (vid.height/2 - (BASEVIDHEIGHT/2 * dupy)) * vid.width;
-				else if (scrn & V_SNAPTOBOTTOM)
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width;
-				else if (!(scrn & V_SNAPTOTOP))
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width / 2;
-			}
-			// if it's meant to cover the whole screen, black out the rest
-			if (x == 0 && SHORT(patch->width) == BASEVIDWIDTH && y == 0 && SHORT(patch->height) == BASEVIDHEIGHT)
-			{
-				column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[0]));
-				source = (const UINT8 *)(column) + 3;
-				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : *(colormap + source[0])));
-			}
-		}
-	}
-	deststart = desttop;
-	destend = desttop + SHORT(patch->width) * dupx / 2;
-
-	for (col = 0; (col>>FRACBITS) < SHORT(patch->width); col += colfrac, desttop++)
-	{
-		INT32 topdelta, prevdelta = -1;
-		column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[col>>FRACBITS]));
-
-		while (column->topdelta != 0xff)
-		{
-			topdelta = column->topdelta;
-			if (topdelta <= prevdelta)
-				topdelta += prevdelta;
-			prevdelta = topdelta;
-			source = (const UINT8 *)(column) + 3;
-			dest = desttop;
-			if (flip)
-				dest = deststart + (destend - desttop);
-			dest += FixedInt(FixedMul(topdelta<<FRACBITS,fdup))*vid.width;
+static UINT8 hudplusalpha[11]  = { 10,  8,  6,  4,  2,  0,  0,  0,  0,  0,  0};
+static UINT8 hudminusalpha[11] = { 10,  9,  9,  8,  8,  7,  7,  6,  6,  5,  5};
 
-			for (ofs = 0; dest < deststop && (ofs>>FRACBITS) < column->length; ofs += rowfrac)
-			{
-				*dest = *(colormap + source[ofs>>FRACBITS]);
-				dest += vid.width;
-			}
-			column = (const column_t *)((const UINT8 *)column + column->length + 4);
-		}
-	}
+static const UINT8 *v_colormap = NULL;
+static const UINT8 *v_translevel = NULL;
+
+static inline UINT8 standardpdraw(const UINT8 *dest, const UINT8 *source, fixed_t ofs)
+{
+	(void)dest; return source[ofs>>FRACBITS];
+}
+static inline UINT8 mappedpdraw(const UINT8 *dest, const UINT8 *source, fixed_t ofs)
+{
+	(void)dest; return *(v_colormap + source[ofs>>FRACBITS]);
+}
+static inline UINT8 translucentpdraw(const UINT8 *dest, const UINT8 *source, fixed_t ofs)
+{
+	return *(v_translevel + ((source[ofs>>FRACBITS]<<8)&0xff00) + (*dest&0xff));
+}
+static inline UINT8 transmappedpdraw(const UINT8 *dest, const UINT8 *source, fixed_t ofs)
+{
+	return *(v_translevel + (((*(v_colormap + source[ofs>>FRACBITS]))<<8)&0xff00) + (*dest&0xff));
 }
 
-// Draws a patch 4x as small.
-void V_DrawTinyScaledPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch)
+// Draws a patch scaled to arbitrary size.
+void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t *patch, const UINT8 *colormap)
 {
+	UINT8 (*patchdrawfunc)(const UINT8*, const UINT8*, fixed_t);
+	UINT32 alphalevel = 0;
+	boolean flip = false;
+
 	fixed_t col, ofs, colfrac, rowfrac, fdup;
 	INT32 dupx, dupy;
 	const column_t *column;
 	UINT8 *desttop, *dest, *deststart, *destend;
 	const UINT8 *source, *deststop;
-	boolean flip = false;
+
+	if (rendermode == render_none)
+		return;
 
 #ifdef HWRENDER
-	// No.
-	if (rendermode != render_soft && rendermode != render_none)
+	// oh please
+	if (rendermode != render_soft && !con_startup)
+	{
+		HWR_DrawFixedPatch((GLPatch_t *)patch, x, y, pscale, scrn, colormap);
 		return;
+	}
 #endif
 
-	// only use one dup, to avoid stretching.
-	fdup = (vid.dupx < vid.dupy ? vid.dupx<<FRACBITS : vid.dupy<<FRACBITS)/4;
-	dupx = dupy = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
+	patchdrawfunc = standardpdraw;
+
+	v_translevel = NULL;
+	if ((alphalevel = ((scrn & V_ALPHAMASK) >> V_ALPHASHIFT)))
+	{
+		if (alphalevel == 13)
+			alphalevel = hudminusalpha[cv_translucenthud.value];
+		else if (alphalevel == 14)
+			alphalevel = 10 - cv_translucenthud.value;
+		else if (alphalevel == 15)
+			alphalevel = hudplusalpha[cv_translucenthud.value];
+
+		if (alphalevel >= 10)
+			return; // invis
+	}
+	if (alphalevel)
+	{
+		v_translevel = ((alphalevel)<<FF_TRANSSHIFT) - 0x10000 + transtables;
+		patchdrawfunc = translucentpdraw;
+	}
+
+	v_colormap = NULL;
+	if (colormap)
+	{
+		v_colormap = colormap;
+		patchdrawfunc = (v_translevel) ? transmappedpdraw : mappedpdraw;
+	}
+
+	dupx = vid.dupx;
+	dupy = vid.dupy;
+	if (scrn & V_SCALEPATCHMASK) switch ((scrn & V_SCALEPATCHMASK) >> V_SCALEPATCHSHIFT)
+	{
+		case 1: // V_NOSCALEPATCH
+			dupx = dupy = 1;
+			break;
+		case 2: // V_SMALLSCALEPATCH
+			dupx = vid.smalldupx;
+			dupy = vid.smalldupy;
+			break;
+		case 3: // V_MEDSCALEPATCH
+			dupx = vid.meddupx;
+			dupy = vid.meddupy;
+			break;
+		default:
+			break;
+	}
+
+	// only use one dup, to avoid stretching (har har)
+	dupx = dupy = (dupx < dupy ? dupx : dupy);
+	fdup = FixedMul(dupx<<FRACBITS, pscale);
 	colfrac = FixedDiv(FRACUNIT, fdup);
 	rowfrac = FixedDiv(FRACUNIT, fdup);
 
-	y -= SHORT(patch->topoffset)/4;
-	if (scrn & V_FLIP)
+	if (scrn & V_OFFSET) // Crosshair shit
 	{
-		flip = true;
-		x -= (SHORT(patch->width) - SHORT(patch->leftoffset))/4;
+		y -= FixedMul((SHORT(patch->topoffset)*dupy)<<FRACBITS,  pscale);
+		x -= FixedMul((SHORT(patch->leftoffset)*dupx)<<FRACBITS, pscale);
 	}
 	else
-		x -= SHORT(patch->leftoffset)/4;
+	{
+		y -= FixedMul(SHORT(patch->topoffset)<<FRACBITS, pscale);
+
+		if (scrn & V_FLIP)
+		{
+			flip = true;
+			x -= FixedMul((SHORT(patch->width) - SHORT(patch->leftoffset))<<FRACBITS, pscale);
+		}
+		else
+			x -= FixedMul(SHORT(patch->leftoffset)<<FRACBITS, pscale);
+	}
+
+	if (scrn & V_SPLITSCREEN)
+		y>>=1;
 
 	desttop = screens[scrn&V_PARAMMASK];
 
@@ -1655,10 +427,18 @@ void V_DrawTinyScaledPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch)
 	deststop = desttop + vid.rowbytes * vid.height;
 
 	if (scrn & V_NOSCALESTART)
+	{
+		x >>= FRACBITS;
+		y >>= FRACBITS;
 		desttop += (y*vid.width) + x;
+	}
 	else
 	{
-		desttop += (y*dupy*vid.width) + (x*dupx);
+		x = FixedMul(x,dupx<<FRACBITS);
+		y = FixedMul(y,dupy<<FRACBITS);
+		x >>= FRACBITS;
+		y >>= FRACBITS;
+		desttop += (y*vid.width) + x;
 
 		// Center it if necessary
 		if (!(scrn & V_SCALEPATCHMASK))
@@ -1675,7 +455,9 @@ void V_DrawTinyScaledPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch)
 			if (vid.height != BASEVIDHEIGHT * dupy)
 			{
 				// same thing here
-				if (scrn & V_SNAPTOBOTTOM)
+				if ((scrn & (V_SPLITSCREEN|V_SNAPTOBOTTOM)) == (V_SPLITSCREEN|V_SNAPTOBOTTOM))
+					desttop += (vid.height/2 - (BASEVIDHEIGHT/2 * dupy)) * vid.width;
+				else if (scrn & V_SNAPTOBOTTOM)
 					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width;
 				else if (!(scrn & V_SNAPTOTOP))
 					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width / 2;
@@ -1689,12 +471,20 @@ void V_DrawTinyScaledPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch)
 			}
 		}
 	}
+
 	deststart = desttop;
-	destend = desttop + SHORT(patch->width) * dupx / 4;
+	destend = desttop + SHORT(patch->width) * dupx;
 
 	for (col = 0; (col>>FRACBITS) < SHORT(patch->width); col += colfrac, desttop++)
 	{
 		INT32 topdelta, prevdelta = -1;
+		if (x < 0)
+		{ // don't draw off the left of the screen (WRAP PREVENTION)
+			x++;
+			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]));
 
 		while (column->topdelta != 0xff)
@@ -1711,7 +501,8 @@ void V_DrawTinyScaledPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch)
 
 			for (ofs = 0; dest < deststop && (ofs>>FRACBITS) < column->length; ofs += rowfrac)
 			{
-				*dest = source[ofs>>FRACBITS];
+				if (dest >= screens[scrn&V_PARAMMASK]) // don't draw off the top of the screen (CRASH PREVENTION)
+					*dest = patchdrawfunc(dest, source, ofs);
 				dest += vid.width;
 			}
 			column = (const column_t *)((const UINT8 *)column + column->length + 4);
@@ -1719,36 +510,35 @@ void V_DrawTinyScaledPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch)
 	}
 }
 
-// Draws a patch 4x as small, and colormapped.
-void V_DrawTinyMappedPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch, const UINT8 *colormap)
+// Draws a patch cropped and scaled to arbitrary size.
+void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t *patch, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h)
 {
 	fixed_t col, ofs, colfrac, rowfrac, fdup;
 	INT32 dupx, dupy;
 	const column_t *column;
-	UINT8 *desttop, *dest, *deststart, *destend;
+	UINT8 *desttop, *dest;
 	const UINT8 *source, *deststop;
-	boolean flip = false;
+
+	if (rendermode == render_none)
+		return;
 
 #ifdef HWRENDER
-	// No.
-	if (rendermode != render_soft && rendermode != render_none)
+	// Done
+	if (rendermode != render_soft && !con_startup)
+	{
+		HWR_DrawCroppedPatch((GLPatch_t*)patch,x,y,pscale,scrn,sx,sy,w,h);
 		return;
+	}
 #endif
 
-	// only use one dup, to avoid stretching.
+	// only use one dup, to avoid stretching (har har)
 	dupx = dupy = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
-	fdup = dupx<<(FRACBITS-2);
+	fdup = FixedMul(dupx<<FRACBITS, pscale);
 	colfrac = FixedDiv(FRACUNIT, fdup);
 	rowfrac = FixedDiv(FRACUNIT, fdup);
 
-	y -= SHORT(patch->topoffset)/4;
-	if (scrn & V_FLIP)
-	{
-		flip = true;
-		x -= (SHORT(patch->width) - SHORT(patch->leftoffset))/4;
-	}
-	else
-		x -= SHORT(patch->leftoffset)/4;
+	y -= FixedMul(SHORT(patch->topoffset)<<FRACBITS, pscale);
+	x -= FixedMul(SHORT(patch->leftoffset)<<FRACBITS, pscale);
 
 	desttop = screens[scrn&V_PARAMMASK];
 
@@ -1757,11 +547,18 @@ void V_DrawTinyMappedPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch, const U
 
 	deststop = desttop + vid.rowbytes * vid.height;
 
-	if (scrn & V_NOSCALESTART)
+	if (scrn & V_NOSCALESTART) {
+		x >>= FRACBITS;
+		y >>= FRACBITS;
 		desttop += (y*vid.width) + x;
+	}
 	else
 	{
-		desttop += (y*dupy*vid.width) + (x*dupx);
+		x = FixedMul(x,dupx<<FRACBITS);
+		y = FixedMul(y,dupy<<FRACBITS);
+		x >>= FRACBITS;
+		y >>= FRACBITS;
+		desttop += (y*vid.width) + x;
 
 		// Center it if necessary
 		if (!(scrn & V_SCALEPATCHMASK))
@@ -1788,16 +585,20 @@ void V_DrawTinyMappedPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch, const U
 			{
 				column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[0]));
 				source = (const UINT8 *)(column) + 3;
-				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : *(colormap + source[0])));
+				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0]));
 			}
 		}
 	}
-	deststart = desttop;
-	destend = desttop + SHORT(patch->width) * dupx / 4;
 
-	for (col = 0; (col>>FRACBITS) < SHORT(patch->width); col += colfrac, desttop++)
+	for (col = sx<<FRACBITS; (col>>FRACBITS) < SHORT(patch->width) && (col>>FRACBITS) < w; col += colfrac, desttop++)
 	{
 		INT32 topdelta, prevdelta = -1;
+		if (x < 0) { // don't draw off the left of the screen (WRAP PREVENTION)
+			x++;
+			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]));
 
 		while (column->topdelta != 0xff)
@@ -1808,218 +609,12 @@ void V_DrawTinyMappedPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch, const U
 			prevdelta = topdelta;
 			source = (const UINT8 *)(column) + 3;
 			dest = desttop;
-			if (flip)
-				dest = deststart + (destend - desttop);
 			dest += FixedInt(FixedMul(topdelta<<FRACBITS,fdup))*vid.width;
 
-			for (ofs = 0; dest < deststop && (ofs>>FRACBITS) < column->length; ofs += rowfrac)
-			{
-				*dest = *(colormap + source[ofs>>FRACBITS]);
-				dest += vid.width;
-			}
-			column = (const column_t *)((const UINT8 *)column + column->length + 4);
-		}
-	}
-}
-
-// This draws a patch over a background with translucency...SCALED.
-// SCALE THE STARTING COORDS!
-// Used for crosshair.
-//
-void V_DrawTranslucentPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch)
-{
-	size_t count;
-	INT32 col, w, dupx, dupy, ofs, colfrac, rowfrac;
-	const column_t *column;
-	UINT8 *desttop, *dest;
-	const UINT8 *source, *translevel, *deststop;
-
-#ifdef HWRENDER
-	// draw a hardware converted patch
-	if (rendermode != render_soft && rendermode != render_none)
-	{
-		HWR_DrawTranslucentPatch((GLPatch_t *)patch, x, y, scrn);
-		return;
-	}
-#endif
-
-	if (scrn & V_ALPHAMASK)
-	{
-		INT32 alphalevel = (scrn & V_ALPHAMASK) >> V_ALPHASHIFT;
-		if (alphalevel >= NUMTRANSMAPS)
-			return; // fully translucent
-		translevel = ((alphalevel)<<FF_TRANSSHIFT) - 0x10000 + transtables;
-	}
-	else // default fallback
-		translevel = ((tr_trans50)<<FF_TRANSSHIFT) - 0x10000 + transtables;
-
-	switch (scrn & V_SCALEPATCHMASK)
-	{
-	case V_NOSCALEPATCH:
-		dupx = dupy = 1;
-		break;
-	case V_SMALLSCALEPATCH:
-		dupx = vid.smalldupx;
-		dupy = vid.smalldupy;
-		break;
-	case V_MEDSCALEPATCH:
-		dupx = vid.meddupx;
-		dupy = vid.meddupy;
-		break;
-	default:
-		dupx = vid.dupx;
-		dupy = vid.dupy;
-		break;
-	}
-
-	// Only use one dup, to avoid stretching.
-	if (dupx < dupy)
-		dupy = dupx;
-	else
-		dupx = dupy;
-
-	if (scrn & V_OFFSET)
-	{
-		y -= SHORT(patch->topoffset)*dupy;
-		x -= SHORT(patch->leftoffset)*dupx;
-	}
-	else
-	{
-		y -= SHORT(patch->topoffset);
-		x -= SHORT(patch->leftoffset);
-	}
-
-	colfrac = FixedDiv(FRACUNIT, dupx<<FRACBITS);
-	rowfrac = FixedDiv(FRACUNIT, dupy<<FRACBITS);
-
-	desttop = screens[scrn&V_PARAMMASK];
-	deststop = screens[scrn&V_PARAMMASK] + vid.rowbytes * vid.height;
-
-	if (!desttop)
-		return;
-
-	if (scrn & V_NOSCALESTART)
-		desttop += (y*vid.width) + x;
-	else
-	{
-		desttop += (y*dupy*vid.width) + (x*dupx);
-
-		// Center it if necessary
-		if (!(scrn & V_SCALEPATCHMASK))
-		{
-			if (vid.width != BASEVIDWIDTH * dupx)
-			{
-				// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
-				// so center this imaginary screen
-				if (scrn & V_SNAPTORIGHT)
-					desttop += (vid.width - (BASEVIDWIDTH * dupx));
-				else if (!(scrn & V_SNAPTOLEFT))
-					desttop += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
-			}
-			if (vid.height != BASEVIDHEIGHT * dupy)
-			{
-				// same thing here
-				if (scrn & V_SNAPTOBOTTOM)
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width;
-				else if (!(scrn & V_SNAPTOTOP))
-					desttop += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width / 2;
-			}
-		}
-	}
-
-	w = SHORT(patch->width)<<FRACBITS;
-
-	for (col = 0; col < w; col += colfrac, desttop++)
-	{
-		INT32 topdelta, prevdelta = -1;
-		column = (const column_t *)((const UINT8 *)patch + LONG(patch->columnofs[col>>FRACBITS]));
-
-		while (column->topdelta != 0xff)
-		{
-			topdelta = column->topdelta;
-			if (topdelta <= prevdelta)
-				topdelta += prevdelta;
-			prevdelta = topdelta;
-			source = (const UINT8 *)column + 3;
-			dest = desttop + topdelta*dupy*vid.width;
-			count = column->length*dupy;
-
-			ofs = 0;
-			while (count--)
-			{
-				if (dest < deststop)
-					*dest = *(translevel + ((source[ofs>>FRACBITS]<<8)&0xff00) + (*dest&0xff));
-				else
-					count = 0;
-				dest += vid.width;
-				ofs += rowfrac;
-			}
-
-			column = (const column_t *)((const UINT8 *)column + column->length + 4);
-		}
-	}
-}
-
-//
-// V_DrawPatch
-// Masks a column based masked pic to the screen. NO SCALING!
-//
-void V_DrawPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch)
-{
-	size_t count;
-	INT32 col, w;
-	const column_t *column;
-	UINT8 *desttop, *dest;
-	const UINT8 *source, *deststop;
-
-#ifdef HWRENDER
-	// draw a hardware converted patch
-	if (rendermode != render_soft && rendermode != render_none)
-	{
-		HWR_DrawPatch((GLPatch_t *)patch, x, y, V_NOSCALESTART|V_NOSCALEPATCH);
-		return;
-	}
-#endif
-
-	y -= SHORT(patch->topoffset);
-	x -= SHORT(patch->leftoffset);
-#ifdef RANGECHECK
-	if (x < 0 || x + SHORT(patch->width) > vid.width || y < 0
-		|| y + SHORT(patch->height) > vid.height || (unsigned)scrn > 4)
-	{
-		fprintf(stderr, "Patch at %d, %d exceeds LFB\n", x, y);
-		// No I_Error abort - what is up with TNT.WAD?
-		fprintf(stderr, "V_DrawPatch: bad patch (ignored)\n");
-		return;
-	}
-#endif
-
-	desttop = screens[scrn] + y*vid.width + x;
-	deststop = screens[scrn&V_PARAMMASK] + vid.rowbytes * vid.height;
-	w = SHORT(patch->width);
-
-	for (col = 0; col < w; x++, col++, desttop++)
-	{
-		INT32 topdelta, prevdelta = -1;
-		column = (const column_t *)((const UINT8 *)patch + LONG(patch->columnofs[col]));
-
-		// step through the posts in a column
-		while (column->topdelta != 0xff)
-		{
-			topdelta = column->topdelta;
-			if (topdelta <= prevdelta)
-				topdelta += prevdelta;
-			prevdelta = topdelta;
-			source = (const UINT8 *)column + 3;
-			dest = desttop + topdelta*vid.width;
-			count = column->length;
-
-			while (count--)
+			for (ofs = sy<<FRACBITS; dest < deststop && (ofs>>FRACBITS) < column->length && (ofs>>FRACBITS) < h; ofs += rowfrac)
 			{
-				if (dest < deststop)
-					*dest = *source++;
-				else
-					count = 0;
+				if (dest >= screens[scrn&V_PARAMMASK]) // don't draw off the top of the screen (CRASH PREVENTION)
+					*dest = source[ofs>>FRACBITS];
 				dest += vid.width;
 			}
 			column = (const column_t *)((const UINT8 *)column + column->length + 4);
@@ -2035,7 +630,7 @@ void V_DrawContinueIcon(INT32 x, INT32 y, INT32 flags, INT32 skinnum, UINT8 skin
 {
 	if (skins[skinnum].flags & SF_HIRES
 #ifdef HWRENDER
-	|| (rendermode != render_soft && rendermode != render_none)
+//	|| (rendermode != render_soft && rendermode != render_none)
 #endif
 	)
 		V_DrawScaledPatch(x - 10, y - 14, flags, W_CachePatchName("CONTINS", PU_CACHE));
@@ -2313,12 +908,7 @@ void V_DrawPatchFill(patch_t *pat)
 	for (x = 0; x < vid.width; x += pw)
 	{
 		for (y = 0; y < vid.height; y += ph)
-		{
-			if (x + pw >= vid.width || y + ph >= vid.height)
-				V_DrawClippedScaledPatch(x, y, 0, pat); // V_NOSCALESTART is implied
-			else
-				V_DrawScaledPatch(x, y, V_NOSCALESTART, pat);
-		}
+			V_DrawScaledPatch(x, y, V_NOSCALESTART, pat);
 	}
 }
 
@@ -2681,15 +1271,7 @@ void V_DrawString(INT32 x, INT32 y, INT32 option, const char *string)
 		}
 
 		colormap = V_GetStringColormap(charflags);
-
-		if (colormap != NULL && (option & V_ALPHAMASK))
-			V_DrawTranslucentMappedPatch(cx + center, cy, option, hu_font[c], colormap);
-		else if (colormap != NULL)
-			V_DrawMappedPatch(cx + center, cy, option, hu_font[c], colormap);
-		else if (option & V_ALPHAMASK)
-			V_DrawTranslucentPatch(cx + center, cy, option, hu_font[c]);
-		else
-			V_DrawScaledPatch(cx + center, cy, option, hu_font[c]);
+		V_DrawFixedPatch((cx + center)<<FRACBITS, cy<<FRACBITS, FRACUNIT, option, hu_font[c], colormap);
 
 		cx += w;
 	}
@@ -2796,15 +1378,7 @@ void V_DrawSmallString(INT32 x, INT32 y, INT32 option, const char *string)
 		}
 
 		colormap = V_GetStringColormap(charflags);
-
-		if (colormap != NULL && (option & V_ALPHAMASK))
-			V_DrawSmallTranslucentMappedPatch(cx + center, cy, option, hu_font[c], colormap);
-		else if (colormap != NULL)
-			V_DrawSmallMappedPatch(cx + center, cy, option, hu_font[c], colormap);
-		else if (option & V_ALPHAMASK)
-			V_DrawSmallTranslucentPatch(cx + center, cy, option, hu_font[c]);
-		else
-			V_DrawSmallScaledPatch(cx + center, cy, option, hu_font[c]);
+		V_DrawFixedPatch((cx + center)<<FRACBITS, cy<<FRACBITS, FRACUNIT/2, option, hu_font[c], colormap);
 
 		cx += w;
 	}
@@ -2897,15 +1471,7 @@ void V_DrawThinString(INT32 x, INT32 y, INT32 option, const char *string)
 		}
 
 		colormap = V_GetStringColormap(charflags);
-
-		if (colormap != NULL && (option & V_ALPHAMASK))
-			V_DrawTranslucentMappedPatch(cx, cy, option, tny_font[c], colormap);
-		else if (colormap != NULL)
-			V_DrawMappedPatch(cx, cy, option, tny_font[c], colormap);
-		else if (option & V_ALPHAMASK)
-			V_DrawTranslucentPatch(cx, cy, option, tny_font[c]);
-		else
-			V_DrawScaledPatch(cx, cy, option, tny_font[c]);
+		V_DrawFixedPatch(cx<<FRACBITS, cy<<FRACBITS, FRACUNIT, option, tny_font[c], colormap);
 
 		cx += w;
 	}
diff --git a/src/v_video.h b/src/v_video.h
index a1c98b2e05..4e08e7004c 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -51,6 +51,7 @@ extern RGBA_t *pLocalPalette;
 
 // flags hacked in scrn (not supported by all functions (see src))
 // patch scaling uses bits 9 and 10
+#define V_SCALEPATCHSHIFT    8
 #define V_SCALEPATCHMASK     0x00000300
 #define V_NOSCALEPATCH       0x00000100
 #define V_SMALLSCALEPATCH    0x00000200
@@ -84,11 +85,14 @@ extern RGBA_t *pLocalPalette;
 #define V_20TRANS            0x00020000
 #define V_30TRANS            0x00030000
 #define V_40TRANS            0x00040000
-#define V_TRANSLUCENT        0x00050000  // TRANS50
+#define V_TRANSLUCENT        0x00050000 // TRANS50
 #define V_60TRANS            0x00060000
 #define V_70TRANS            0x00070000
-#define V_80TRANS            0x00080000  // used to be V_8020TRANS
+#define V_80TRANS            0x00080000 // used to be V_8020TRANS
 #define V_90TRANS            0x00090000
+#define V_HUDTRANSHALF       0x000D0000
+#define V_HUDTRANS           0x000E0000 // draw the hud translucent
+#define V_HUDTRANSDOUBLE     0x000F0000
 
 #define V_AUTOFADEOUT        0x00100000 // used by CECHOs, automatic fade out when almost over
 #define V_RETURN8            0x00200000 // 8 pixel return instead of 12
@@ -107,24 +111,23 @@ extern RGBA_t *pLocalPalette;
 #define V_NOSCALESTART       0x40000000  // don't scale x, y, start coords
 #define V_SPLITSCREEN        0x80000000
 
-// default params: scale patch and scale start
-void V_DrawScaledPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch);
-void V_DrawMappedPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch, const UINT8 *colormap);
-void V_DrawTranslucentMappedPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch, const UINT8 *colormap);
-void V_DrawTranslucentPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch);
-void V_DrawSciencePatch(fixed_t x, fixed_t y, INT32 scrn, patch_t *patch, fixed_t science); // FOR SCIENCE!!
-void V_DrawCroppedPatch(fixed_t x, fixed_t y, INT32 scrn, patch_t *patch, fixed_t science, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h);
-
-// default params: scale patch and scale start
-void V_DrawSmallScaledPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch);
-void V_DrawSmallMappedPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch, const UINT8 *colormap);
-void V_DrawSmallTranslucentMappedPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch, const UINT8 *colormap);
-void V_DrawSmallTranslucentPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch);
-
-void V_DrawTinyScaledPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch);
-void V_DrawTinyMappedPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch, const UINT8 *colormap);
-
-void V_DrawPatch(INT32 x, INT32 y, INT32 scrn, patch_t *patch);
+// defines for old functions
+#define V_DrawPatch(x,y,s,p) V_DrawFixedPatch((x)<<FRACBITS, (y)<<FRACBITS, FRACUNIT, s|V_NOSCALESTART|V_NOSCALEPATCH, p, NULL)
+#define V_DrawTranslucentMappedPatch(x,y,s,p,c) V_DrawFixedPatch((x)<<FRACBITS, (y)<<FRACBITS, FRACUNIT, s, p, c)
+#define V_DrawSmallTranslucentMappedPatch(x,y,s,p,c) V_DrawFixedPatch((x)<<FRACBITS, (y)<<FRACBITS, FRACUNIT/2, s, p, c)
+#define V_DrawTinyTranslucentMappedPatch(x,y,s,p,c) V_DrawFixedPatch((x)<<FRACBITS, (y)<<FRACBITS, FRACUNIT/4, s, p, c)
+#define V_DrawMappedPatch(x,y,s,p,c) V_DrawFixedPatch((x)<<FRACBITS, (y)<<FRACBITS, FRACUNIT, s, p, c)
+#define V_DrawSmallMappedPatch(x,y,s,p,c) V_DrawFixedPatch((x)<<FRACBITS, (y)<<FRACBITS, FRACUNIT/2, s, p, c)
+#define V_DrawTinyMappedPatch(x,y,s,p,c) V_DrawFixedPatch((x)<<FRACBITS, (y)<<FRACBITS, FRACUNIT/4, s, p, c)
+#define V_DrawScaledPatch(x,y,s,p) V_DrawFixedPatch((x)<<FRACBITS, (y)<<FRACBITS, FRACUNIT, s, p, NULL)
+#define V_DrawSmallScaledPatch(x,y,s,p) V_DrawFixedPatch((x)<<FRACBITS, (y)<<FRACBITS, FRACUNIT/2, s, p, NULL)
+#define V_DrawTinyScaledPatch(x,y,s,p) V_DrawFixedPatch((x)<<FRACBITS, (y)<<FRACBITS, FRACUNIT/4, s, p, NULL)
+#define V_DrawTranslucentPatch(x,y,s,p) V_DrawFixedPatch((x)<<FRACBITS, (y)<<FRACBITS, FRACUNIT, s, p, NULL)
+#define V_DrawSmallTranslucentPatch(x,y,s,p) V_DrawFixedPatch((x)<<FRACBITS, (y)<<FRACBITS, FRACUNIT/2, s, p, NULL)
+#define V_DrawTinyTranslucentPatch(x,y,s,p) V_DrawFixedPatch((x)<<FRACBITS, (y)<<FRACBITS, FRACUNIT/4, s, p, NULL)
+#define V_DrawSciencePatch(x,y,s,p,sc) V_DrawFixedPatch(x,y,sc,s,p,NULL)
+void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t *patch, const UINT8 *colormap);
+void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t *patch, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h);
 
 void V_DrawContinueIcon(INT32 x, INT32 y, INT32 flags, INT32 skinnum, UINT8 skincolor);
 
diff --git a/src/y_inter.c b/src/y_inter.c
index e35f285532..49bac308bc 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -138,7 +138,7 @@ static patch_t *widebgpatch = NULL; // INTERSCW
 static patch_t *bgtile = NULL;      // SPECTILE/SRB2BACK
 static patch_t *interpic = NULL;    // custom picture defined in map header
 static boolean usetile;
-static boolean usebuffer;
+boolean usebuffer = false;
 static boolean useinterpic;
 static INT32 timer;
 
@@ -990,7 +990,6 @@ void Y_StartIntermission(void)
 			else
 			{
 				useinterpic = false;
-				usebuffer = true;
 			}
 			usetile = false;
 
@@ -1712,6 +1711,7 @@ void Y_EndIntermission(void)
 
 	endtic = -1;
 	intertype = int_none;
+	usebuffer = false;
 }
 
 //
diff --git a/src/y_inter.h b/src/y_inter.h
index a4f58d7b63..67ab4f4e20 100644
--- a/src/y_inter.h
+++ b/src/y_inter.h
@@ -9,6 +9,8 @@
 /// \file  y_inter.h
 /// \brief Intermission
 
+extern boolean usebuffer;
+
 void Y_IntermissionDrawer(void);
 void Y_Ticker(void);
 void Y_StartIntermission(void);
-- 
GitLab