diff --git a/src/actionspecials.h b/src/actionspecials.h
index 08f989754d..9a8427ebc4 100644
--- a/src/actionspecials.h
+++ b/src/actionspecials.h
@@ -265,6 +265,6 @@ DEFINE_SPECIAL(Ceiling_Stop, 276, 1, 1, 1)
 DEFINE_SPECIAL(Sector_SetFloorGlow, 277, 5, 5, 5)
 DEFINE_SPECIAL(Sector_SetCeilingGlow, 278, 5, 5, 5)
 DEFINE_SPECIAL(Floor_MoveToValueAndCrush, 279, 4, 5, 5)
-DEFINE_SPECIAL(Ceiling_MoveToValueAndCrush, 290, 4, 5, 5)
+DEFINE_SPECIAL(Ceiling_MoveToValueAndCrush, 280, 4, 5, 5)
 
 #undef DEFINE_SPECIAL
diff --git a/src/am_map.cpp b/src/am_map.cpp
index f4382f0fd5..721bd3650e 100644
--- a/src/am_map.cpp
+++ b/src/am_map.cpp
@@ -790,6 +790,7 @@ static double mapystart=0; // y-value for the start of the map bitmap...used in
 static double mapxstart=0; //x-value for the bitmap.
 
 static bool stopped = true;
+static int viewbottom;
 
 static void AM_calcMinMaxMtoF();
 
@@ -1062,7 +1063,7 @@ static void AM_findMinMaxBoundaries ()
 static void AM_calcMinMaxMtoF()
 {
 	double a = SCREENWIDTH / max_w;
-	double b = gST_Y / max_h;
+	double b = viewbottom / max_h;
 
 	min_scale_mtof = a < b ? a : b;
 	max_scale_mtof = SCREENHEIGHT / (2*PLAYERRADIUS);
@@ -1420,7 +1421,7 @@ void AM_NewResolution()
 	else if (scale_mtof > max_scale_mtof)
 		AM_maxOutWindowScale();
 	f_w = screen->GetWidth();
-	f_h = gST_Y;
+	f_h = viewbottom;
 	AM_activateNewScale();
 }
 
@@ -3160,7 +3161,7 @@ void AM_drawCrosshair (const AMColor &color)
 //
 //=============================================================================
 
-void AM_Drawer ()
+void AM_Drawer (int bottom)
 {
 	if (!automapactive)
 		return;
@@ -3168,6 +3169,7 @@ void AM_Drawer ()
 	bool allmap = (level.flags2 & LEVEL2_ALLMAP) != 0;
 	bool allthings = allmap && players[consoleplayer].mo->FindInventory(NAME_PowerScanner, true) != nullptr;
 
+	viewbottom = bottom;
 	if (am_portaloverlay)
 	{
 		sector_t *sec;
@@ -3184,7 +3186,7 @@ void AM_Drawer ()
 		// and view size adjustments.
 		f_x = f_y = 0;
 		f_w = screen->GetWidth ();
-		f_h = gST_Y;
+		f_h = viewbottom;
 		f_p = screen->GetPitch ();
 
 		AM_clearFB(AMColors[AMColors.Background]);
diff --git a/src/am_map.h b/src/am_map.h
index fd9079c1e3..8bf9b08c22 100644
--- a/src/am_map.h
+++ b/src/am_map.h
@@ -37,7 +37,7 @@ void AM_Ticker (void);
 
 // Called by main loop,
 // called instead of view drawer if automap active.
-void AM_Drawer (void);
+void AM_Drawer (int bottom);
 
 // Called to force the automap to quit
 // if the level is completed while it is up.
diff --git a/src/c_console.cpp b/src/c_console.cpp
index e2ffe2ec61..0d37198f3f 100644
--- a/src/c_console.cpp
+++ b/src/c_console.cpp
@@ -146,10 +146,9 @@ static int worklen = 0;
 
 CVAR(Float, con_notifytime, 3.f, CVAR_ARCHIVE)
 CVAR(Bool, con_centernotify, false, CVAR_ARCHIVE)
-CUSTOM_CVAR(Int, con_scaletext, 1, CVAR_ARCHIVE)		// Scale notify text at high resolutions?
+CUSTOM_CVAR(Int, con_scaletext, 0, CVAR_ARCHIVE)		// Scale notify text at high resolutions?
 {
 	if (self < 0) self = 0;
-	if (self > 3) self = 3;
 }
 
 CUSTOM_CVAR(Int, con_scale, 0, CVAR_ARCHIVE)
@@ -157,36 +156,6 @@ CUSTOM_CVAR(Int, con_scale, 0, CVAR_ARCHIVE)
 	if (self < 0) self = 0;
 }
 
-int active_con_scale()
-{
-	int scale = con_scale;
-	if (scale <= 0)
-	{
-		scale = uiscale;
-		if (scale == 0)
-		{
-			scale = CleanXfac - 1;
-			if (scale <= 0)
-			{
-				scale = 1;
-			}
-		}
-	}
-	return scale;
-}
-
-int active_con_scaletext()
-{
-	switch (con_scaletext)
-	{
-	default:
-	case 0: return 1;
-	case 1: return uiscale;
-	case 2: return 2;
-	case 3: return 4;
-	}
-}
-
 CUSTOM_CVAR(Float, con_alpha, 0.75f, CVAR_ARCHIVE)
 {
 	if (self < 0.f) self = 0.f;
@@ -811,14 +780,7 @@ void FNotifyBuffer::AddString(int printlevel, FString source)
 		return;
 	}
 
-	if (active_con_scaletext() == 0)
-	{
-		width = DisplayWidth / CleanXfac;
-	}
-	else
-	{
-		width = DisplayWidth / active_con_scaletext();
-	}
+	width = DisplayWidth / active_con_scaletext();
 
 	if (AddType == APPENDLINE && Text.Size() > 0 && Text[Text.Size() - 1].PrintLevel == printlevel)
 	{
@@ -1061,10 +1023,6 @@ void FNotifyBuffer::Draw()
 	canskip = true;
 
 	lineadv = SmallFont->GetHeight ();
-	if (active_con_scaletext() == 0)
-	{
-		lineadv *= CleanYfac;
-	}
 
 	BorderTopRefresh = screen->GetPageCount ();
 
@@ -1088,45 +1046,21 @@ void FNotifyBuffer::Draw()
 			else
 				color = PrintColors[notify.PrintLevel];
 
-			if (active_con_scaletext() == 0)
-			{
-				if (!center)
-					screen->DrawText (SmallFont, color, 0, line, notify.Text,
-						DTA_CleanNoMove, true, DTA_Alpha, alpha, TAG_DONE);
-				else
-					screen->DrawText (SmallFont, color, (SCREENWIDTH -
-						SmallFont->StringWidth (notify.Text)*CleanXfac)/2,
-						line, notify.Text, DTA_CleanNoMove, true,
-						DTA_Alpha, alpha, TAG_DONE);
-			}
-			else if (active_con_scaletext() == 1)
-			{
-				if (!center)
-					screen->DrawText (SmallFont, color, 0, line, notify.Text,
-						DTA_Alpha, alpha, TAG_DONE);
-				else
-					screen->DrawText (SmallFont, color, (SCREENWIDTH -
-						SmallFont->StringWidth (notify.Text))/2,
-						line, notify.Text,
-						DTA_Alpha, alpha, TAG_DONE);
-			}
+			int scale = active_con_scaletext();
+			if (!center)
+				screen->DrawText (SmallFont, color, 0, line, notify.Text,
+					DTA_VirtualWidth, screen->GetWidth() / scale,
+					DTA_VirtualHeight, screen->GetHeight() / scale,
+					DTA_KeepRatio, true,
+					DTA_Alpha, alpha, TAG_DONE);
 			else
-			{
-				if (!center)
-					screen->DrawText (SmallFont, color, 0, line, notify.Text,
-						DTA_VirtualWidth, screen->GetWidth() / active_con_scaletext(),
-						DTA_VirtualHeight, screen->GetHeight() / active_con_scaletext(),
-						DTA_KeepRatio, true,
-						DTA_Alpha, alpha, TAG_DONE);
-				else
-					screen->DrawText (SmallFont, color, (screen->GetWidth() -
-						SmallFont->StringWidth (notify.Text) * active_con_scaletext()) / 2 / active_con_scaletext(),
-						line, notify.Text,
-						DTA_VirtualWidth, screen->GetWidth() / active_con_scaletext(),
-						DTA_VirtualHeight, screen->GetHeight() / active_con_scaletext(),
-						DTA_KeepRatio, true,
-						DTA_Alpha, alpha, TAG_DONE);
-			}
+				screen->DrawText (SmallFont, color, (screen->GetWidth() -
+					SmallFont->StringWidth (notify.Text) * scale) / 2 / scale,
+					line, notify.Text,
+					DTA_VirtualWidth, screen->GetWidth() / scale,
+					DTA_VirtualHeight, screen->GetHeight() / scale,
+					DTA_KeepRatio, true,
+					DTA_Alpha, alpha, TAG_DONE);
 			line += lineadv;
 			canskip = false;
 		}
diff --git a/src/cmdlib.cpp b/src/cmdlib.cpp
index 6fc253ff38..342ce05aa3 100644
--- a/src/cmdlib.cpp
+++ b/src/cmdlib.cpp
@@ -596,7 +596,7 @@ int strbin (char *str)
 				case '5':
 				case '6':
 				case '7':
-					c = 0;
+					c = *p - '0';
 					for (i = 0; i < 2; i++)
 					{
 						p++;
@@ -699,7 +699,7 @@ FString strbin1 (const char *start)
 				case '5':
 				case '6':
 				case '7':
-					c = 0;
+					c = *p - '0';
 					for (i = 0; i < 2; i++)
 					{
 						p++;
diff --git a/src/ct_chat.cpp b/src/ct_chat.cpp
index c49e95f578..c632df2038 100644
--- a/src/ct_chat.cpp
+++ b/src/ct_chat.cpp
@@ -31,6 +31,7 @@
 #include "templates.h"
 #include "d_net.h"
 #include "d_event.h"
+#include "sbar.h"
 
 #define QUEUESIZE		128
 #define MESSAGESIZE		128
@@ -226,29 +227,12 @@ void CT_Drawer (void)
 		int i, x, scalex, y, promptwidth;
 
 		y = (viewactive || gamestate != GS_LEVEL) ? -10 : -30;
-		if (active_con_scaletext() == 0)
-		{
-			scalex = CleanXfac;
-			y *= CleanYfac;
-		}
-		else
-		{
-			scalex = 1;
-		}
 
-		int screen_width, screen_height, st_y;
-		if (active_con_scaletext() == 0)
-		{
-			screen_width = SCREENWIDTH;
-			screen_height = SCREENHEIGHT;
-			st_y = gST_Y;
-		}
-		else
-		{
-			screen_width = SCREENWIDTH / active_con_scaletext();
-			screen_height = SCREENHEIGHT / active_con_scaletext();
-			st_y = gST_Y / active_con_scaletext();
-		}
+		scalex = 1;
+		int scale = active_con_scaletext();
+		int screen_width = SCREENWIDTH / scale;
+		int screen_height= SCREENHEIGHT / scale;
+		int st_y = StatusBar->GetTopOfStatusbar() / scale;
 
 		y += ((SCREENHEIGHT == viewheight && viewactive) || gamestate != GS_LEVEL) ? screen_height : st_y;
 
@@ -274,18 +258,10 @@ void CT_Drawer (void)
 		// draw the prompt, text, and cursor
 		ChatQueue[len] = SmallFont->GetCursor();
 		ChatQueue[len+1] = '\0';
-		if (active_con_scaletext() < 2)
-		{
-			screen->DrawText (SmallFont, CR_GREEN, 0, y, prompt, DTA_CleanNoMove, active_con_scaletext() == 0, TAG_DONE);
-			screen->DrawText (SmallFont, CR_GREY, promptwidth, y, (char *)(ChatQueue + i), DTA_CleanNoMove, active_con_scaletext() == 0, TAG_DONE);
-		}
-		else
-		{
-			screen->DrawText (SmallFont, CR_GREEN, 0, y, prompt, 
-				DTA_VirtualWidth, screen_width, DTA_VirtualHeight, screen_height, DTA_KeepRatio, true, TAG_DONE);
-			screen->DrawText (SmallFont, CR_GREY, promptwidth, y, (char *)(ChatQueue + i), 
-				DTA_VirtualWidth, screen_width, DTA_VirtualHeight, screen_height, DTA_KeepRatio, true, TAG_DONE);
-		}
+		screen->DrawText (SmallFont, CR_GREEN, 0, y, prompt, 
+			DTA_VirtualWidth, screen_width, DTA_VirtualHeight, screen_height, DTA_KeepRatio, true, TAG_DONE);
+		screen->DrawText (SmallFont, CR_GREY, promptwidth, y, (char *)(ChatQueue + i), 
+			DTA_VirtualWidth, screen_width, DTA_VirtualHeight, screen_height, DTA_KeepRatio, true, TAG_DONE);
 		ChatQueue[len] = '\0';
 
 		BorderTopRefresh = screen->GetPageCount ();
diff --git a/src/d_main.cpp b/src/d_main.cpp
index 22ba39cfe4..80a37b21e4 100644
--- a/src/d_main.cpp
+++ b/src/d_main.cpp
@@ -160,7 +160,6 @@ EXTERN_CVAR (Bool, sv_unlimited_pickup)
 extern int testingmode;
 extern bool setmodeneeded;
 extern int NewWidth, NewHeight, NewBits, DisplayBits;
-EXTERN_CVAR (Bool, st_scale)
 extern bool gameisdead;
 extern bool demorecording;
 extern bool M_DemoNoPlay;	// [RH] if true, then skip any demos in the loop
@@ -790,13 +789,7 @@ void D_Display ()
 			screen->DrawBlendingRect();
 			if (automapactive)
 			{
-				int saved_ST_Y = gST_Y;
-				if (hud_althud && viewheight == SCREENHEIGHT)
-				{
-					gST_Y = viewheight;
-				}
-				AM_Drawer ();
-				gST_Y = saved_ST_Y;
+				AM_Drawer (hud_althud? viewheight : StatusBar->GetTopOfStatusbar());
 			}
 			if (!automapactive || viewactive)
 			{
@@ -907,7 +900,6 @@ void D_Display ()
 		NetUpdate ();			// send out any new accumulation
 		// normal update
 		// draw ZScript UI stuff
-		//E_RenderOverlay();
 		C_DrawConsole (hw2d);	// draw console
 		M_Drawer ();			// menu is drawn even on top of everything
 		FStat::PrintStat ();
diff --git a/src/events.cpp b/src/events.cpp
index 5a40a39835..970b68da5f 100755
--- a/src/events.cpp
+++ b/src/events.cpp
@@ -448,6 +448,12 @@ void E_Console(int player, FString name, int arg1, int arg2, int arg3, bool manu
 		handler->ConsoleProcess(player, name, arg1, arg2, arg3, manual);
 }
 
+void E_RenderOverlay(EHudState state)
+{
+	for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next)
+		handler->RenderOverlay(state);
+}
+
 bool E_CheckUiProcessors()
 {
 	for (DStaticEventHandler* handler = E_FirstEventHandler; handler; handler = handler->next)
@@ -468,7 +474,6 @@ bool E_CheckRequireMouse()
 
 // normal event loopers (non-special, argument-less)
 DEFINE_EVENT_LOOPER(RenderFrame)
-DEFINE_EVENT_LOOPER(RenderOverlay)
 DEFINE_EVENT_LOOPER(WorldLightning)
 DEFINE_EVENT_LOOPER(WorldTick)
 DEFINE_EVENT_LOOPER(UiTick)
@@ -798,7 +803,7 @@ void DStaticEventHandler::RenderFrame()
 	}
 }
 
-void DStaticEventHandler::RenderOverlay()
+void DStaticEventHandler::RenderOverlay(EHudState state)
 {
 	IFVIRTUAL(DStaticEventHandler, RenderOverlay)
 	{
@@ -806,6 +811,7 @@ void DStaticEventHandler::RenderOverlay()
 		if (func == DStaticEventHandler_RenderOverlay_VMPtr)
 			return;
 		FRenderEvent e = E_SetupRenderEvent();
+		e.HudState = int(state);
 		VMValue params[2] = { (DStaticEventHandler*)this, &e };
 		GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr);
 	}
diff --git a/src/events.h b/src/events.h
index a2f014a32b..5ef7f85aa4 100755
--- a/src/events.h
+++ b/src/events.h
@@ -5,6 +5,7 @@
 #include "serializer.h"
 #include "d_event.h"
 #include "d_gui.h"
+#include "sbar.h"
 
 class DStaticEventHandler;
 
@@ -48,7 +49,7 @@ void E_UiTick();
 // called on each render frame once.
 void E_RenderFrame();
 // called after everything's been rendered, but before console/menus
-void E_RenderOverlay();
+void E_RenderOverlay(EHudState state);
 // this executes when a player enters the level (once). PlayerEnter+inhub = RETURN
 void E_PlayerEntered(int num, bool fromhub);
 // this executes when a player respawns. includes resurrect cheat.
@@ -141,7 +142,7 @@ public:
 
 	//
 	void RenderFrame();
-	void RenderOverlay();
+	void RenderOverlay(EHudState state);
 
 	//
 	void PlayerEntered(int num, bool fromhub);
@@ -175,6 +176,7 @@ struct FRenderEvent
 	DAngle ViewRoll;
 	double FracTic = 0; // 0..1 value that describes where we are inside the current gametic, render-wise.
 	AActor* Camera = nullptr;
+	int HudState;
 };
 
 struct FWorldEvent
diff --git a/src/f_wipe.cpp b/src/f_wipe.cpp
index 95f170a85a..5515cd96c3 100644
--- a/src/f_wipe.cpp
+++ b/src/f_wipe.cpp
@@ -490,7 +490,6 @@ bool wipe_ScreenWipe (int ticks)
 		return true;
 
 	// do a piece of wipe-in
-	V_MarkRect(0, 0, SCREENWIDTH, SCREENHEIGHT);
 	rc = (*wipes[(CurrentWipeType-1)*3+1])(ticks);
 
 	return rc;
diff --git a/src/g_shared/hudmessages.cpp b/src/g_shared/hudmessages.cpp
index eab0e8d291..caa041d4c6 100644
--- a/src/g_shared/hudmessages.cpp
+++ b/src/g_shared/hudmessages.cpp
@@ -42,7 +42,6 @@
 #include "serializer.h"
 
 EXTERN_CVAR(Int, con_scaletext)
-int active_con_scaletext();
 
 IMPLEMENT_CLASS(DHUDMessage, false, true)
 
@@ -269,11 +268,7 @@ void DHUDMessage::ResetText (const char *text)
 	}
 	else
 	{
-		switch (active_con_scaletext())
-		{
-		case 0: width = SCREENWIDTH / CleanXfac; break;
-		default: width = SCREENWIDTH / active_con_scaletext(); break;
-		}
+		width = SCREENWIDTH / active_con_scaletext();
 	}
 
 	if (Lines != NULL)
@@ -338,21 +333,13 @@ void DHUDMessage::Draw (int bottom, int visibility)
 
 	int screen_width = SCREENWIDTH;
 	int screen_height = SCREENHEIGHT;
-	if (HUDWidth == 0 && active_con_scaletext() == 0)
+	xscale = yscale = 1;
+	if (HUDWidth == 0)
 	{
-		clean = true;
-		xscale = CleanXfac;
-		yscale = CleanYfac;
-	}
-	else
-	{
-		xscale = yscale = 1;
-		if (HUDWidth == 0)
-		{
-			screen_width /= active_con_scaletext();
-			screen_height /= active_con_scaletext();
-			bottom /= active_con_scaletext();
-		}
+		int scale = active_con_scaletext();
+		screen_width /= scale;
+		screen_height /= scale;
+		bottom /= scale;
 	}
 
 	if (HUDWidth == 0)
@@ -453,24 +440,14 @@ void DHUDMessage::DoDraw (int linenum, int x, int y, bool clean, int hudheight)
 {
 	if (hudheight == 0)
 	{
-		if (active_con_scaletext() <= 1)
-		{
-			screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text,
-				DTA_CleanNoMove, clean,
-				DTA_Alpha, Alpha,
-				DTA_RenderStyle, Style,
-				TAG_DONE);
-		}
-		else
-		{
-			screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text,
-				DTA_VirtualWidth, SCREENWIDTH / active_con_scaletext(),
-				DTA_VirtualHeight, SCREENHEIGHT / active_con_scaletext(),
-				DTA_Alpha, Alpha,
-				DTA_RenderStyle, Style,
-				DTA_KeepRatio, true,
-				TAG_DONE);
-		}
+		int scale = active_con_scaletext();
+		screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text,
+			DTA_VirtualWidth, SCREENWIDTH / scale,
+			DTA_VirtualHeight, SCREENHEIGHT / scale,
+			DTA_Alpha, Alpha,
+			DTA_RenderStyle, Style,
+			DTA_KeepRatio, true,
+			TAG_DONE);
 	}
 	else
 	{
@@ -556,24 +533,14 @@ void DHUDMessageFadeOut::DoDraw (int linenum, int x, int y, bool clean, int hudh
 		float trans = float(Alpha * -(Tics - FadeOutTics) / FadeOutTics);
 		if (hudheight == 0)
 		{
-			if (active_con_scaletext() <= 1)
-			{
-				screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text,
-					DTA_CleanNoMove, clean,
-					DTA_Alpha, trans,
-					DTA_RenderStyle, Style,
-					TAG_DONE);
-			}
-			else
-			{
-				screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text,
-					DTA_VirtualWidth, SCREENWIDTH / active_con_scaletext(),
-					DTA_VirtualHeight, SCREENHEIGHT / active_con_scaletext(),
-					DTA_Alpha, trans,
-					DTA_RenderStyle, Style,
-					DTA_KeepRatio, true,
-					TAG_DONE);
-			}
+			int scale = active_con_scaletext();
+			screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text,
+				DTA_VirtualWidth, SCREENWIDTH / scale,
+				DTA_VirtualHeight, SCREENHEIGHT / scale,
+				DTA_Alpha, trans,
+				DTA_RenderStyle, Style,
+				DTA_KeepRatio, true,
+				TAG_DONE);
 		}
 		else
 		{
@@ -656,24 +623,14 @@ void DHUDMessageFadeInOut::DoDraw (int linenum, int x, int y, bool clean, int hu
 		float trans = float(Alpha * Tics / FadeInTics);
 		if (hudheight == 0)
 		{
-			if (active_con_scaletext() <= 1)
-			{
-				screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text,
-					DTA_CleanNoMove, clean,
-					DTA_Alpha, trans,
-					DTA_RenderStyle, Style,
-					TAG_DONE);
-			}
-			else
-			{
-				screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text,
-					DTA_VirtualWidth, SCREENWIDTH / active_con_scaletext(),
-					DTA_VirtualHeight, SCREENHEIGHT / active_con_scaletext(),
-					DTA_Alpha, trans,
-					DTA_RenderStyle, Style,
-					DTA_KeepRatio, true,
-					TAG_DONE);
-			}
+			int scale = active_con_scaletext();
+			screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text,
+				DTA_VirtualWidth, SCREENWIDTH / scale,
+				DTA_VirtualHeight, SCREENHEIGHT / scale,
+				DTA_Alpha, trans,
+				DTA_RenderStyle, Style,
+				DTA_KeepRatio, true,
+				TAG_DONE);
 		}
 		else
 		{
@@ -837,26 +794,15 @@ void DHUDMessageTypeOnFadeOut::DoDraw (int linenum, int x, int y, bool clean, in
 		{
 			if (hudheight == 0)
 			{
-				if (active_con_scaletext() <= 1)
-				{
-					screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text,
-						DTA_CleanNoMove, clean,
-						DTA_TextLen, LineVisible,
-						DTA_Alpha, Alpha,
-						DTA_RenderStyle, Style,
-						TAG_DONE);
-				}
-				else
-				{
-					screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text,
-						DTA_VirtualWidth, SCREENWIDTH / active_con_scaletext(),
-						DTA_VirtualHeight, SCREENHEIGHT / active_con_scaletext(),
-						DTA_KeepRatio, true,
-						DTA_TextLen, LineVisible,
-						DTA_Alpha, Alpha,
-						DTA_RenderStyle, Style,
-						TAG_DONE);
-				}
+				int scale = active_con_scaletext();
+				screen->DrawText (Font, TextColor, x, y, Lines[linenum].Text,
+					DTA_VirtualWidth, SCREENWIDTH / scale,
+					DTA_VirtualHeight, SCREENHEIGHT / scale,
+					DTA_KeepRatio, true,
+					DTA_TextLen, LineVisible,
+					DTA_Alpha, Alpha,
+					DTA_RenderStyle, Style,
+					TAG_DONE);
 			}
 			else
 			{
diff --git a/src/g_shared/shared_hud.cpp b/src/g_shared/shared_hud.cpp
index ce8a4965e0..19183a4377 100644
--- a/src/g_shared/shared_hud.cpp
+++ b/src/g_shared/shared_hud.cpp
@@ -65,11 +65,12 @@ EXTERN_CVAR(Bool,am_follow)
 EXTERN_CVAR (Int, con_scaletext)
 EXTERN_CVAR (Bool, idmypos)
 EXTERN_CVAR (Int, screenblocks)
+EXTERN_CVAR(Bool, hud_aspectscale)
 
 EXTERN_CVAR (Bool, am_showtime)
 EXTERN_CVAR (Bool, am_showtotaltime)
 
-CVAR(Int,hud_althudscale, 4, CVAR_ARCHIVE)				// Scale the hud to 640x400?
+CVAR(Int,hud_althudscale, 0, CVAR_ARCHIVE)				// Scale the hud to 640x400?
 CVAR(Bool,hud_althud, false, CVAR_ARCHIVE)				// Enable/Disable the alternate HUD
 
 														// These are intentionally not the same as in the automap!
@@ -121,7 +122,6 @@ static int hudwidth, hudheight;				// current width/height for HUD display
 static int statspace;
 
 DVector2 AM_GetPosition();
-int active_con_scaletext();
 
 //---------------------------------------------------------------------------
 //
@@ -897,43 +897,31 @@ static void DrawCoordinates(player_t * CPlayer)
 		pos = DVector3(apos, z);
 	}
 
-	int vwidth, vheight;
-	if (active_con_scaletext() == 0)
-	{
-		vwidth = SCREENWIDTH / 2;
-		vheight = SCREENHEIGHT / 2;
-	}
-	else
-	{
-		vwidth = SCREENWIDTH / active_con_scaletext();
-		vheight = SCREENHEIGHT / active_con_scaletext();
-	}
-
-	int xpos = vwidth - SmallFont->StringWidth("X: -00000")-6;
+	int xpos = hudwidth - SmallFont->StringWidth("X: -00000")-6;
 	int ypos = 18;
 
-	screen->DrawText(SmallFont, hudcolor_titl, vwidth - 6 - SmallFont->StringWidth(level.MapName), ypos, level.MapName,
+	screen->DrawText(SmallFont, hudcolor_titl, hudwidth - 6 - SmallFont->StringWidth(level.MapName), ypos, level.MapName,
 		DTA_KeepRatio, true,
-		DTA_VirtualWidth, vwidth, DTA_VirtualHeight, vheight, TAG_DONE);
+		DTA_VirtualWidth, hudwidth, DTA_VirtualHeight, hudheight, TAG_DONE);
 
-	screen->DrawText(SmallFont, hudcolor_titl, vwidth - 6 - SmallFont->StringWidth(level.LevelName), ypos + h, level.LevelName,
+	screen->DrawText(SmallFont, hudcolor_titl, hudwidth - 6 - SmallFont->StringWidth(level.LevelName), ypos + h, level.LevelName,
 		DTA_KeepRatio, true,
-		DTA_VirtualWidth, vwidth, DTA_VirtualHeight, vheight, TAG_DONE);
+		DTA_VirtualWidth, hudwidth, DTA_VirtualHeight, hudheight, TAG_DONE);
 
 	mysnprintf(coordstr, countof(coordstr), "X: %d", int(pos.X));
 	screen->DrawText(SmallFont, hudcolor_xyco, xpos, ypos+2*h, coordstr,
 		DTA_KeepRatio, true,
-		DTA_VirtualWidth, vwidth, DTA_VirtualHeight, vheight, TAG_DONE);
+		DTA_VirtualWidth, hudwidth, DTA_VirtualHeight, hudheight, TAG_DONE);
 
 	mysnprintf(coordstr, countof(coordstr), "Y: %d", int(pos.Y));
 	screen->DrawText(SmallFont, hudcolor_xyco, xpos, ypos+3*h, coordstr,
 		DTA_KeepRatio, true,
-		DTA_VirtualWidth, vwidth, DTA_VirtualHeight, vheight, TAG_DONE);
+		DTA_VirtualWidth, hudwidth, DTA_VirtualHeight, hudheight, TAG_DONE);
 
 	mysnprintf(coordstr, countof(coordstr), "Z: %d", int(pos.Z));
 	screen->DrawText(SmallFont, hudcolor_xyco, xpos, ypos+4*h, coordstr,
 		DTA_KeepRatio, true,
-		DTA_VirtualWidth, vwidth, DTA_VirtualHeight, vheight, TAG_DONE);
+		DTA_VirtualWidth, hudwidth, DTA_VirtualHeight, hudheight, TAG_DONE);
 }
 
 //---------------------------------------------------------------------------
@@ -1134,49 +1122,9 @@ void DrawHUD()
 	player_t * CPlayer = StatusBar->CPlayer;
 
 	players[consoleplayer].inventorytics = 0;
-	if (hud_althudscale && SCREENWIDTH>640) 
-	{
-		hudwidth=SCREENWIDTH/2;
-		if (hud_althudscale == 4)
-		{
-			if (uiscale == 0)
-			{
-				hudwidth = CleanWidth;
-				hudheight = CleanHeight;
-			}
-			else
-			{
-				hudwidth = SCREENWIDTH / uiscale;
-				hudheight = SCREENHEIGHT / uiscale;
-			}
-		}
-		else if (hud_althudscale == 3)
-		{
-			hudwidth = SCREENWIDTH / 4;
-			hudheight = SCREENHEIGHT / 4;
-		}
-		else if (hud_althudscale == 2)
-		{
-			// Optionally just double the pixels to reduce scaling artifacts.
-			hudheight=SCREENHEIGHT/2;
-		}
-		else 
-		{
-			if (AspectTallerThanWide(r_viewwindow.WidescreenRatio))
-			{
-				hudheight = hudwidth * 30 / AspectMultiplier(r_viewwindow.WidescreenRatio);	// BaseRatioSizes is inverted for this mode
-			}
-			else
-			{
-				hudheight = hudwidth * 30 / (48*48/AspectMultiplier(r_viewwindow.WidescreenRatio));
-			}
-		}
-	}
-	else
-	{
-		hudwidth=SCREENWIDTH;
-		hudheight=SCREENHEIGHT;
-	}
+	int scale = GetUIScale(hud_althudscale);
+	hudwidth = SCREENWIDTH / scale;
+	hudheight = hud_aspectscale ? int(SCREENHEIGHT / (scale*1.2)) : SCREENHEIGHT / scale;
 
 	if (!automapactive)
 	{
diff --git a/src/g_statusbar/sbar.h b/src/g_statusbar/sbar.h
index 3ac2681f1d..2f3f7b202f 100644
--- a/src/g_statusbar/sbar.h
+++ b/src/g_statusbar/sbar.h
@@ -357,7 +357,7 @@ public:
 	};
 
 	DBaseStatusBar ();
-	void SetSize(int reltop = 32, int hres = 320, int vres = 200);
+	void SetSize(int reltop = 32, int hres = 320, int vres = 200, int hhres = -1, int hvres = -1);
 	void OnDestroy() override;
 
 	void AttachMessage (DHUDMessage *msg, uint32_t id=0, int layer=HUDMSGLayer_Default);
@@ -373,8 +373,7 @@ public:
 	// do not make this a DObject Serialize function because it's not used like one!
 	void SerializeMessages(FSerializer &arc);
 
-	virtual void SetScaled(bool scale, bool force = false);
-	void CallSetScaled(bool scale, bool force = false);
+	void SetScale();
 	virtual void Tick ();
 	void CallTick();
 	virtual void Draw (EHudState state);
@@ -399,10 +398,14 @@ public:
 	void DrawString(FFont *font, const FString &cstring, double x, double y, int flags, double Alpha, int translation, int spacing, bool monospaced, int shadowX, int shadowY);
 	void Fill(PalEntry color, double x, double y, double w, double h, int flags = 0);
 
-	void BeginStatusBar(int resW, int resH, int relTop, bool completeborder = false, bool forceScaled = false);
+	void BeginStatusBar(int resW, int resH, int relTop, bool forceScaled);
 	void BeginHUD(int resW, int resH, double Alpha, bool forceScaled = false);
-	void ForceHUDScale(bool on) { ForcedScale = on; }	// This is for SBARINFO which should not use BeginStatusBar or BeginHUD.
+	bool ForceHUDScale(bool on) { std::swap(ForcedScale, on); return on; }	// This is for SBARINFO which should not use BeginStatusBar or BeginHUD.
 	void StatusbarToRealCoords(double &x, double &y, double &w, double &h) const;
+	int GetTopOfStatusbar() const
+	{
+		return SBarTop;
+	}
 
 //protected:
 	void DrawPowerups ();
@@ -416,7 +419,10 @@ public:
 	void DrawCrosshair ();
 
 	// Sizing info for ths status bar.
-	int ST_X, ST_Y;
+	int ST_X;
+	int ST_Y;
+	int SBarTop;
+	DVector2 SBarScale;
 	int RelTop;
 	int HorizontalResolution, VerticalResolution;
 	bool Scaled;							// This needs to go away.
@@ -445,8 +451,16 @@ private:
 	void DrawMessages (int layer, int bottom);
 	void DrawConsistancy () const;
 	void DrawWaiting () const;
+	void SetDrawSize(int reltop, int hres, int vres);
 
 	TObjPtr<DHUDMessage*> Messages[NUM_HUDMSGLAYERS];
+
+	int BaseRelTop;
+	int BaseSBarHorizontalResolution;
+	int BaseSBarVerticalResolution;
+	int BaseHUDHorizontalResolution;
+	int BaseHUDVerticalResolution;
+
 };
 
 extern DBaseStatusBar *StatusBar;
diff --git a/src/g_statusbar/sbarinfo.cpp b/src/g_statusbar/sbarinfo.cpp
index 8c775dd84c..2c9d962c4e 100644
--- a/src/g_statusbar/sbarinfo.cpp
+++ b/src/g_statusbar/sbarinfo.cpp
@@ -973,7 +973,7 @@ public:
 	DSBarInfo (DBaseStatusBar *wrapper, SBarInfo *script=NULL) :
 		ammo1(NULL), ammo2(NULL), ammocount1(0), ammocount2(0), armor(NULL),
 		pendingPopup(DBaseStatusBar::POP_None), currentPopup(DBaseStatusBar::POP_None), lastHud(-1),
-		scalingWasForced(false), lastInventoryBar(NULL), lastPopup(NULL)
+		lastInventoryBar(NULL), lastPopup(NULL)
 	{
 		this->script = script;
 		this->wrapper = wrapper;
@@ -1030,17 +1030,7 @@ public:
 		{
 			hud = STBAR_NONE;
 		}
-		if(script->huds[hud]->ForceScaled()) //scale the statusbar
-		{
-			if(script->huds[hud]->FullScreenOffsets())
-				wrapper->ForceHUDScale(true);
-			else if(!wrapper->Scaled)
-			{
-				scalingWasForced = true;
-				wrapper->SetScaled(true, true);
-				setsizeneeded = true;
-			}
-		}
+		wrapper->ForceHUDScale(script->huds[hud]->ForceScaled());
 
 		if (CPlayer->ReadyWeapon != NULL)
 		{
@@ -1067,15 +1057,9 @@ public:
 			if(hud != lastHud)
 			{
 				script->huds[hud]->Tick(NULL, this, true);
-
 				// Restore scaling if need be.
-				if(scalingWasForced)
-				{
-					scalingWasForced = false;
-					wrapper->SetScaled(false);
-					setsizeneeded = true;
-				}
 			}
+			wrapper->ForceHUDScale(script->huds[hud]->ForceScaled());
 
 			if(currentPopup != DBaseStatusBar::POP_None && !script->huds[hud]->FullScreenOffsets())
 				script->huds[hud]->Draw(NULL, this, script->popups[currentPopup-1].getXDisplacement(), script->popups[currentPopup-1].getYDisplacement(), 1.);
@@ -1096,6 +1080,8 @@ public:
 				else
 					inventoryBar->DrawAux(NULL, this, 0, 0, 1.);
 			}
+			// Reset hud scale
+			wrapper->ForceHUDScale(false);
 		}
 
 		// Handle popups
@@ -1119,8 +1105,6 @@ public:
 		else
 			lastPopup = NULL;
 
-		// Reset hud scale
-		wrapper->ForceHUDScale(false);
 	}
 
 	void _NewGame ()
@@ -1478,7 +1462,6 @@ private:
 	int pendingPopup;
 	int currentPopup;
 	int lastHud;
-	bool scalingWasForced;
 	SBarInfoMainBlock *lastInventoryBar;
 	SBarInfoMainBlock *lastPopup;
 };
@@ -1487,30 +1470,9 @@ private:
 void SBarInfoMainBlock::DrawAux(const SBarInfoMainBlock *block, DSBarInfo *statusBar, int xOffset, int yOffset, double alpha)
 {
 	// Popups can also be forced to scale
-	bool rescale = false;
-	if(ForceScaled())
-	{
-		if(FullScreenOffsets())
-		{
-			rescale = true;
-			statusBar->wrapper->ForceHUDScale(true);
-		}
-		else if(!statusBar->wrapper->Scaled)
-		{
-			rescale = true;
-			statusBar->wrapper->SetScaled(true, true);
-		}
-	}
-
+	bool old = statusBar->wrapper->ForceHUDScale(ForceScaled());
 	Draw(block, statusBar, xOffset, yOffset, alpha);
-
-	if(rescale)
-	{
-		if(FullScreenOffsets())
-			statusBar->wrapper->ForceHUDScale(false);
-		else
-			statusBar->wrapper->SetScaled(false);
-	}
+	statusBar->wrapper->ForceHUDScale(old);
 }
 
 #include "sbarinfo_commands.cpp"
@@ -1586,7 +1548,6 @@ DBaseStatusBar *CreateCustomStatusBar(int scriptno)
 	auto core = new DSBarInfo(sbar, script);
 	sbar->PointerVar<DSBarInfo>("core") = core;
 	sbar->SetSize(script->height, script->_resW, script->_resH);
-	sbar->SetScaled(sbar->Scaled);
 	sbar->CompleteBorder = script->completeBorder;
 	return sbar;
 }
diff --git a/src/g_statusbar/shared_sbar.cpp b/src/g_statusbar/shared_sbar.cpp
index f6b0644ccf..d5330b2b56 100644
--- a/src/g_statusbar/shared_sbar.cpp
+++ b/src/g_statusbar/shared_sbar.cpp
@@ -61,6 +61,7 @@
 #include "p_acs.h"
 #include "r_data/r_translate.h"
 #include "sbarinfo.h"
+#include "events.h"
 
 #include "../version.h"
 
@@ -86,14 +87,11 @@ EXTERN_CVAR (Int, con_scaletext)
 EXTERN_CVAR(Bool, vid_fps)
 CVAR(Int, hud_scale, 0, CVAR_ARCHIVE);
 
-int active_con_scaletext();
 
 DBaseStatusBar *StatusBar;
 
 extern int setblocks;
 
-int gST_Y;
-
 FTexture *CrosshairImage;
 static int CrosshairNum;
 
@@ -108,11 +106,24 @@ CVAR (Flag, pf_ice,			paletteflash, PF_ICE)
 CVAR (Flag, pf_hazard,		paletteflash, PF_HAZARD)
 
 // Stretch status bar to full screen width?
-CUSTOM_CVAR (Bool, st_scale, true, CVAR_ARCHIVE)
+CUSTOM_CVAR (Int, st_scale, 0, CVAR_ARCHIVE)
+{
+	if (self < -1)
+	{
+		self = -1;
+		return;
+	}
+	if (StatusBar)
+	{
+		StatusBar->SetScale();
+		setsizeneeded = true;
+	}
+}
+CUSTOM_CVAR(Bool, hud_aspectscale, false, CVAR_ARCHIVE)
 {
 	if (StatusBar)
 	{
-		StatusBar->CallSetScaled (self);
+		StatusBar->SetScale();
 		setsizeneeded = true;
 	}
 }
@@ -149,6 +160,16 @@ void ST_FormatMapName(FString &mapname, const char *mapnamecolor)
 	mapname << mapnamecolor << level.LevelName;
 }
 
+DEFINE_ACTION_FUNCTION(FLevelLocals, FormatMapName)
+{
+	PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals);
+	PARAM_INT(cr);
+	char mapnamecolor[3] = { '\34', char(cr + 'A'), 0 };
+	FString rets;
+	ST_FormatMapName(rets, mapnamecolor);
+	ACTION_RETURN_STRING(rets);
+}
+
 //---------------------------------------------------------------------------
 //
 // Load crosshair definitions
@@ -329,7 +350,17 @@ DBaseStatusBar::DBaseStatusBar ()
 	defaultScale = { (double)CleanXfac, (double)CleanYfac };
 }
 
-void DBaseStatusBar::SetSize(int reltop, int hres, int vres)
+void DBaseStatusBar::SetSize(int reltop, int hres, int vres, int hhres, int hvres)
+{
+	BaseRelTop = reltop;
+	BaseSBarHorizontalResolution = hres;
+	BaseSBarVerticalResolution = vres;
+	BaseHUDHorizontalResolution = hhres < 0? hres : hhres;
+	BaseHUDVerticalResolution = hvres < 0? vres : hvres;
+	SetDrawSize(reltop, hres, vres);
+}
+
+void DBaseStatusBar::SetDrawSize(int reltop, int hres, int vres)
 {
 	RelTop = reltop;
 	HorizontalResolution = hres;
@@ -338,16 +369,19 @@ void DBaseStatusBar::SetSize(int reltop, int hres, int vres)
 	V_CalcCleanFacs(hres, vres, SCREENWIDTH, SCREENHEIGHT, &x, &y);
 	defaultScale = { (double)x, (double)y };
 
-	CallSetScaled(st_scale);
+	SetScale();	// recalculate positioning info.
 }
 
+
 DEFINE_ACTION_FUNCTION(DBaseStatusBar, SetSize)
 {
 	PARAM_SELF_PROLOGUE(DBaseStatusBar);
-	PARAM_INT_DEF(rt);
-	PARAM_INT_DEF(vw);
-	PARAM_INT_DEF(vh);
-	self->SetSize(rt, vw, vh);
+	PARAM_INT(rt);
+	PARAM_INT(vw);
+	PARAM_INT(vh);
+	PARAM_INT_DEF(hvw);
+	PARAM_INT_DEF(hvh);
+	self->SetSize(rt, vw, vh, hvw, hvh);
 	return 0;
 }
 
@@ -380,62 +414,57 @@ void DBaseStatusBar::OnDestroy ()
 //
 //---------------------------------------------------------------------------
 
-//[BL] Added force argument to have forcescaled mean forcescaled.
-// - Also, if the VerticalResolution is something other than the default (200)
-//   We should always obey the value of scale.
-void DBaseStatusBar::SetScaled (bool scale, bool force)
+void DBaseStatusBar::SetScale ()
 {
-	Scaled = (RelTop != 0 || force) && ((SCREENWIDTH != 320 || HorizontalResolution != 320) && scale);
-
-	if (!Scaled)
+	int w = SCREENWIDTH;
+	int h = SCREENHEIGHT;
+	if (st_scale < 0 || ForcedScale)
 	{
-		ST_X = (SCREENWIDTH - HorizontalResolution) / 2;
-		ST_Y = SCREENHEIGHT - RelTop;
-		gST_Y = ST_Y;
+		// This is the classic fullscreen scale with aspect ratio compensation.
+		int sby = VerticalResolution - RelTop;
+		float aspect = ActiveRatio(w, h);
+		if (!AspectTallerThanWide(aspect))
+		{ 
+			// Wider or equal than 4:3 
+			SBarTop = Scale(sby, h, VerticalResolution);
+			double width4_3 = w * 1.333 / aspect;
+			ST_X = int((w - width4_3) / 2);
+		}
+		else
+		{ // 5:4 resolution
+			ST_X = 0;
+
+			// this was far more obtuse before...
+			double height4_3 = h * aspect / 1.333;
+			SBarTop = int(h - height4_3 + sby * height4_3 / VerticalResolution);
+		}
+		Displacement = 0;
+		SBarScale.X = -1;
+		ST_Y = 0;
+	}
+	else
+	{
+		// Since status bars and HUDs can be designed for non 320x200 screens this needs to be factored in here.
+		// The global scaling factors are for resources at 320x200, so if the actual ones are higher resolution
+		// the resulting scaling factor needs to be reduced accordingly.
+		int realscale = clamp((320 * GetUIScale(st_scale)) / HorizontalResolution, 1, w / HorizontalResolution);
+
+		double realscaley = realscale * (hud_aspectscale ? 1.2 : 1.);
+
+		ST_X = (w - HorizontalResolution * realscale) / 2;
+		SBarTop = int(h - RelTop * realscaley);
 		if (RelTop > 0)
 		{
-			Displacement = double((ST_Y * VerticalResolution / SCREENHEIGHT) - (VerticalResolution - RelTop))/RelTop;
+			Displacement = double((SBarTop * VerticalResolution / h) - (VerticalResolution - RelTop))/RelTop/realscaley;
 		}
 		else
 		{
 			Displacement = 0;
 		}
+		SBarScale.X = realscale;
+		SBarScale.Y = realscaley;
+		ST_Y = int(h - VerticalResolution * realscaley);
 	}
-	else
-	{
-		ST_X = 0;
-		ST_Y = VerticalResolution - RelTop;
-		float aspect = ActiveRatio(SCREENWIDTH, SCREENHEIGHT);
-		if (!AspectTallerThanWide(aspect))
-		{ // Normal resolution
-			gST_Y = Scale (ST_Y, SCREENHEIGHT, VerticalResolution);
-		}
-		else
-		{ // 5:4 resolution
-			gST_Y = Scale(ST_Y - VerticalResolution/2, SCREENHEIGHT*3, Scale(VerticalResolution, AspectBaseHeight(aspect), 200)) + SCREENHEIGHT/2
-				+ (SCREENHEIGHT - SCREENHEIGHT * AspectMultiplier(aspect) / 48) / 2;
-		}
-		Displacement = 0;
-	}
-}
-
-DEFINE_ACTION_FUNCTION(DBaseStatusBar, SetScaled)
-{
-	PARAM_SELF_PROLOGUE(DBaseStatusBar);
-	PARAM_BOOL(scale);
-	PARAM_BOOL_DEF(force);
-	self->SetScaled(scale, force);
-	return 0;
-}
-
-void DBaseStatusBar::CallSetScaled(bool scale, bool force)
-{
-	IFVIRTUAL(DBaseStatusBar, SetScaled)
-	{
-		VMValue params[] = { (DObject*)this, scale, force };
-		GlobalVMStack.Call(func, params, countof(params), nullptr, 0);
-	}
-	else SetScaled(scale, force);
 }
 
 //---------------------------------------------------------------------------
@@ -451,24 +480,13 @@ DVector2 DBaseStatusBar::GetHUDScale() const
 	{
 		return defaultScale;
 	}
-	if (hud_scale > 0)		// use the scale as an absolute value, but also factor in the specified resolution of the HUD
-	{
-		scale = hud_scale;
-	}
-	else  if (uiscale == 0)
-	{
-		return defaultScale;
-	}
-	else
-	{
-		scale = MAX<int>(1, uiscale);
-	}
+	scale = GetUIScale(hud_scale);
 
 	// Since status bars and HUDs can be designed for non 320x200 screens this needs to be factored in here.
 	// The global scaling factors are for resources at 320x200, so if the actual ones are higher resolution
 	// the resulting scaling factor needs to be reduced accordingly.
 	int realscale = MAX<int>(1, (320 * scale) / HorizontalResolution);
-	return{ double(realscale), double(realscale) };
+	return{ double(realscale), double(realscale * (hud_aspectscale ? 1.2 : 1.)) };
 }
 
 DEFINE_ACTION_FUNCTION(DBaseStatusBar, GetHUDScale)
@@ -483,23 +501,21 @@ DEFINE_ACTION_FUNCTION(DBaseStatusBar, GetHUDScale)
 //
 //---------------------------------------------------------------------------
 
-void DBaseStatusBar::BeginStatusBar(int resW, int resH, int relTop, bool completeborder, bool forceScaled)
+void DBaseStatusBar::BeginStatusBar(int resW, int resH, int relTop, bool forceScaled)
 {
-	SetSize(relTop, resW, resH);
-	SetScaled(st_scale, forceScaled);
-	CompleteBorder = completeborder;
+	SetDrawSize(relTop < 0? BaseRelTop : relTop, resW < 0? BaseSBarHorizontalResolution : resW, resH < 0? BaseSBarVerticalResolution : resH);
+	ForcedScale = forceScaled;
 	fullscreenOffsets = false;
 }
 
 DEFINE_ACTION_FUNCTION(DBaseStatusBar, BeginStatusBar)
 {
 	PARAM_SELF_PROLOGUE(DBaseStatusBar);
-	PARAM_INT(w);
-	PARAM_INT(h);
-	PARAM_INT(r);
-	PARAM_BOOL_DEF(cb);
 	PARAM_BOOL_DEF(fs);
-	self->BeginStatusBar(w, h, r, cb, fs);
+	PARAM_INT_DEF(w);
+	PARAM_INT_DEF(h);
+	PARAM_INT_DEF(r);
+	self->BeginStatusBar(w, h, r, fs);
 	return 0;
 }
 //---------------------------------------------------------------------------
@@ -510,7 +526,7 @@ DEFINE_ACTION_FUNCTION(DBaseStatusBar, BeginStatusBar)
 
 void DBaseStatusBar::BeginHUD(int resW, int resH, double Alpha, bool forcescaled)
 {
-	SetSize(RelTop, resW, resH);	
+	SetDrawSize(RelTop, resW < 0? BaseHUDHorizontalResolution : resW, resH < 0? BaseHUDVerticalResolution : resH);	
 	this->Alpha = Alpha;
 	ForcedScale = forcescaled;
 	CompleteBorder = false;
@@ -520,10 +536,10 @@ void DBaseStatusBar::BeginHUD(int resW, int resH, double Alpha, bool forcescaled
 DEFINE_ACTION_FUNCTION(DBaseStatusBar, BeginHUD)
 {
 	PARAM_SELF_PROLOGUE(DBaseStatusBar);
-	PARAM_INT(w);
-	PARAM_INT(h);
-	PARAM_FLOAT(a);
+	PARAM_FLOAT_DEF(a);
 	PARAM_BOOL_DEF(fs);
+	PARAM_INT_DEF(w);
+	PARAM_INT_DEF(h);
 	self->BeginHUD(w, h, a, fs);
 	return 0;
 }
@@ -765,8 +781,8 @@ void DBaseStatusBar::RefreshBackground () const
 	int x, x2, y;
 
 	float ratio = ActiveRatio (SCREENWIDTH, SCREENHEIGHT);
-	x = (ratio < 1.5f || !Scaled) ? ST_X : SCREENWIDTH*(48-AspectMultiplier(ratio))/(48*2);
-	y = x == ST_X && x > 0 ? ST_Y : gST_Y;
+	x = ST_X;
+	y = SBarTop;
 
 	if(!CompleteBorder)
 	{
@@ -785,8 +801,7 @@ void DBaseStatusBar::RefreshBackground () const
 	{
 		if(!CompleteBorder)
 		{
-			x2 = ratio < 1.5f || !Scaled ? ST_X+HorizontalResolution :
-				SCREENWIDTH - (SCREENWIDTH*(48-AspectMultiplier(ratio))+48*2-1)/(48*2);
+			x2 = SCREENWIDTH - ST_X;
 		}
 		else
 		{
@@ -808,13 +823,6 @@ void DBaseStatusBar::RefreshBackground () const
 	}
 }
 
-DEFINE_ACTION_FUNCTION(DBaseStatusBar, RefreshBackground)
-{
-	PARAM_SELF_PROLOGUE(DBaseStatusBar);
-	self->RefreshBackground();
-	return 0;
-}
-
 //---------------------------------------------------------------------------
 //
 // DrawCrosshair
@@ -949,66 +957,20 @@ void DBaseStatusBar::Draw (EHudState state)
 	if (state == HUD_AltHud)
 		return;
 
-	char line[64+10];
-
 	if (state == HUD_StatusBar)
 	{
 		RefreshBackground ();
 	}
 
 	if (idmypos)
-	{ // Draw current coordinates
-		int height = SmallFont->GetHeight();
-		char labels[3] = { 'X', 'Y', 'Z' };
-		int i;
-
-		int vwidth;
-		int vheight;
-		int xpos;
-		int y;
-
-		if (active_con_scaletext() == 1)
+	{ 
+		// Draw current coordinates
+		IFVIRTUAL(DBaseStatusBar, DrawMyPos)
 		{
-			vwidth = SCREENWIDTH;
-			vheight = SCREENHEIGHT;
-			xpos = vwidth - 80;
-			y = gST_Y - height;
-		}
-		else if (active_con_scaletext() > 1)
-		{
-			vwidth = SCREENWIDTH / active_con_scaletext();
-			vheight = SCREENHEIGHT / active_con_scaletext();
-			xpos = vwidth - SmallFont->StringWidth("X: -00000")-6;
-			y = gST_Y/4 - height;
-		}
-		else
-		{
-			vwidth = SCREENWIDTH/2;
-			vheight = SCREENHEIGHT/2;
-			xpos = vwidth - SmallFont->StringWidth("X: -00000")-6;
-			y = gST_Y/2 - height;
-		}
-
-		if (gameinfo.gametype == GAME_Strife)
-		{
-			if (active_con_scaletext() == 1)
-				y -= height * 4;
-			else if (active_con_scaletext() > 3)
-				y -= height;
-			else
-				y -= height * 2;
-		}
-
-		DVector3 pos = CPlayer->mo->Pos();
-		for (i = 2; i >= 0; y -= height, --i)
-		{
-			mysnprintf (line, countof(line), "%c: %d", labels[i], int(pos[i]));
-			screen->DrawText (SmallFont, CR_GREEN, xpos, y, line, 
-				DTA_KeepRatio, true,
-				DTA_VirtualWidth, vwidth, DTA_VirtualHeight, vheight, 				
-				TAG_DONE);
-			V_SetBorderNeedRefresh();
+			VMValue params[] = { (DObject*)this };
+			GlobalVMStack.Call(func, params, countof(params), nullptr, 0);
 		}
+		V_SetBorderNeedRefresh();
 	}
 
 	if (viewactive)
@@ -1020,101 +982,10 @@ void DBaseStatusBar::Draw (EHudState state)
 	}
 	else if (automapactive)
 	{
-		int y, time = Tics2Seconds(level.time), height;
-		int totaltime = Tics2Seconds(level.totaltime);
-		EColorRange highlight = (gameinfo.gametype & GAME_DoomChex) ?
-			CR_UNTRANSLATED : CR_YELLOW;
-
-		height = SmallFont->GetHeight () * CleanYfac;
-
-		// Draw timer
-		y = 8;
-		if (am_showtime)
+		IFVIRTUAL(DBaseStatusBar, DrawAutomapHUD)
 		{
-			mysnprintf (line, countof(line), "%02d:%02d:%02d", time/3600, (time%3600)/60, time%60);	// Time
-			screen->DrawText (SmallFont, CR_GREY, SCREENWIDTH - 80*CleanXfac, y, line, DTA_CleanNoMove, true, TAG_DONE);
-			y+=8*CleanYfac;
-		}
-		if (am_showtotaltime)
-		{
-			mysnprintf (line, countof(line), "%02d:%02d:%02d", totaltime/3600, (totaltime%3600)/60, totaltime%60);	// Total time
-			screen->DrawText (SmallFont, CR_GREY, SCREENWIDTH - 80*CleanXfac, y, line, DTA_CleanNoMove, true, TAG_DONE);
-		}
-
-		// Draw map name
-		y = gST_Y - height;
-		if (gameinfo.gametype == GAME_Heretic && SCREENWIDTH > 320 && !Scaled)
-		{
-			y -= 8;
-		}
-		else if (gameinfo.gametype == GAME_Hexen)
-		{
-			if (Scaled)
-			{
-				y -= Scale (11, SCREENHEIGHT, 200);
-			}
-			else
-			{
-				if (SCREENWIDTH < 640)
-				{
-					y -= 12;
-				}
-				else
-				{ // Get past the tops of the gargoyles' wings
-					y -= 28;
-				}
-			}
-		}
-		else if (gameinfo.gametype == GAME_Strife)
-		{
-			if (Scaled)
-			{
-				y -= Scale (8, SCREENHEIGHT, 200);
-			}
-			else
-			{
-				y -= 8;
-			}
-		}
-		FString mapname;
-
-		ST_FormatMapName(mapname, TEXTCOLOR_GREY);
-		screen->DrawText (SmallFont, highlight,
-			(SCREENWIDTH - SmallFont->StringWidth (mapname)*CleanXfac)/2, y, mapname,
-			DTA_CleanNoMove, true, TAG_DONE);
-
-		if (!deathmatch)
-		{
-			int y = 8;
-
-			// Draw monster count
-			if (am_showmonsters)
-			{
-				mysnprintf (line, countof(line), "%s" TEXTCOLOR_GREY " %d/%d",
-					GStrings("AM_MONSTERS"), level.killed_monsters, level.total_monsters);
-				screen->DrawText (SmallFont, highlight, 8, y, line,
-					DTA_CleanNoMove, true, TAG_DONE);
-				y += height;
-			}
-
-			// Draw secret count
-			if (am_showsecrets)
-			{
-				mysnprintf (line, countof(line), "%s" TEXTCOLOR_GREY " %d/%d",
-					GStrings("AM_SECRETS"), level.found_secrets, level.total_secrets);
-				screen->DrawText (SmallFont, highlight, 8, y, line,
-					DTA_CleanNoMove, true, TAG_DONE);
-				y += height;
-			}
-
-			// Draw item count
-			if (am_showitems)
-			{
-				mysnprintf (line, countof(line), "%s" TEXTCOLOR_GREY " %d/%d",
-					GStrings("AM_ITEMS"), level.found_items, level.total_items);
-				screen->DrawText (SmallFont, highlight, 8, y, line,
-					DTA_CleanNoMove, true, TAG_DONE);
-			}
+			VMValue params[] = { (DObject*)this, r_viewpoint.TicFrac };
+			GlobalVMStack.Call(func, params, countof(params), nullptr, 0);
 		}
 	}
 }
@@ -1136,6 +1007,7 @@ void DBaseStatusBar::CallDraw(EHudState state)
 	}
 	else Draw(state);
 	screen->ClearClipRect();	// make sure the scripts don't leave a valid clipping rect behind.
+	BeginStatusBar(BaseSBarHorizontalResolution, BaseSBarVerticalResolution, BaseRelTop, false);
 }
 
 
@@ -1147,16 +1019,9 @@ void DBaseStatusBar::DrawLog ()
 	if (CPlayer->LogText.IsNotEmpty())
 	{
 		// This uses the same scaling as regular HUD messages
-		if (active_con_scaletext() == 0)
-		{
-			hudwidth = SCREENWIDTH / CleanXfac;
-			hudheight = SCREENHEIGHT / CleanYfac;
-		}
-		else
-		{
-			hudwidth = SCREENWIDTH / active_con_scaletext();
-			hudheight = SCREENHEIGHT / active_con_scaletext();
-		}
+		auto scale = active_con_scaletext();
+		hudwidth = SCREENWIDTH / scale;
+		hudheight = SCREENHEIGHT / scale;
 
 		int linelen = hudwidth<640? Scale(hudwidth,9,10)-40 : 560;
 		FBrokenLines *lines = V_BreakLines (SmallFont, linelen, CPlayer->LogText);
@@ -1237,7 +1102,7 @@ void DBaseStatusBar::SetMugShotState(const char *stateName, bool waitTillDone, b
 
 void DBaseStatusBar::DrawBottomStuff (EHudState state)
 {
-	DrawMessages (HUDMSGLayer_UnderHUD, (state == HUD_StatusBar) ? gST_Y : SCREENHEIGHT);
+	DrawMessages (HUDMSGLayer_UnderHUD, (state == HUD_StatusBar) ? GetTopOfStatusbar() : SCREENHEIGHT);
 }
 
 //---------------------------------------------------------------------------
@@ -1250,7 +1115,7 @@ void DBaseStatusBar::DrawTopStuff (EHudState state)
 {
 	if (demoplayback && demover != DEMOGAMEVERSION)
 	{
-		screen->DrawText (SmallFont, CR_TAN, 0, ST_Y - 40 * CleanYfac,
+		screen->DrawText (SmallFont, CR_TAN, 0, GetTopOfStatusbar() - 40 * CleanYfac,
 			"Demo was recorded with a different version\n"
 			"of " GAMENAME ". Expect it to go out of sync.",
 			DTA_CleanNoMove, true, TAG_DONE);
@@ -1270,9 +1135,11 @@ void DBaseStatusBar::DrawTopStuff (EHudState state)
 
 	if (automapactive && !viewactive)
 	{
-		DrawMessages (HUDMSGLayer_OverMap, (state == HUD_StatusBar) ? gST_Y : SCREENHEIGHT);
+		DrawMessages (HUDMSGLayer_OverMap, (state == HUD_StatusBar) ? GetTopOfStatusbar() : SCREENHEIGHT);
 	}
-	DrawMessages (HUDMSGLayer_OverHUD, (state == HUD_StatusBar) ? gST_Y : SCREENHEIGHT);
+	DrawMessages (HUDMSGLayer_OverHUD, (state == HUD_StatusBar) ? GetTopOfStatusbar() : SCREENHEIGHT);
+	E_RenderOverlay(state);
+
 	DrawConsistancy ();
 	DrawWaiting ();
 	if (ShowLog && MustDrawLog(state)) DrawLog ();
@@ -1566,17 +1433,41 @@ uint32_t DBaseStatusBar::GetTranslation() const
 
 void DBaseStatusBar::StatusbarToRealCoords(double &x, double &y, double &w, double &h) const
 {
-	if (Scaled)
+	if (SBarScale.X == -1 || ForcedScale)
 	{
 		screen->VirtualToRealCoords(x, y, w, h, HorizontalResolution, VerticalResolution, true, true);
 	}
 	else
 	{
-		x += ST_X;
-		y += screen->GetHeight() - VerticalResolution;
+		x = ST_X + x * SBarScale.X;
+		y = ST_Y + y * SBarScale.Y;
+		w *= SBarScale.X;
+		h *= SBarScale.Y;
 	}
 }
 
+DEFINE_ACTION_FUNCTION(DBaseStatusBar, StatusbarToRealCoords)
+{
+	PARAM_SELF_PROLOGUE(DBaseStatusBar);
+	PARAM_FLOAT(x);
+	PARAM_FLOAT_DEF(y);
+	PARAM_FLOAT_DEF(w);
+	PARAM_FLOAT_DEF(h);
+	self->StatusbarToRealCoords(x, y, w, h);
+	if (numret > 0) ret[0].SetFloat(x);
+	if (numret > 1) ret[1].SetFloat(y);
+	if (numret > 2) ret[2].SetFloat(w);
+	if (numret > 3) ret[3].SetFloat(h);
+	return MIN(4, numret);
+}
+
+
+DEFINE_ACTION_FUNCTION(DBaseStatusBar, GetTopOfStatusbar)
+{
+	PARAM_SELF_PROLOGUE(DBaseStatusBar);
+	ACTION_RETURN_INT(self->GetTopOfStatusbar());
+}
+
 //============================================================================
 //
 // draw stuff
@@ -2048,12 +1939,9 @@ CCMD (showpop)
 	}
 }
 
-DEFINE_FIELD(DBaseStatusBar, ST_X);
-DEFINE_FIELD(DBaseStatusBar, ST_Y);
 DEFINE_FIELD(DBaseStatusBar, RelTop);
 DEFINE_FIELD(DBaseStatusBar, HorizontalResolution);
 DEFINE_FIELD(DBaseStatusBar, VerticalResolution);
-DEFINE_FIELD(DBaseStatusBar, Scaled);
 DEFINE_FIELD(DBaseStatusBar, Centering);
 DEFINE_FIELD(DBaseStatusBar, FixedOrigin);
 DEFINE_FIELD(DBaseStatusBar, CompleteBorder);
diff --git a/src/gameconfigfile.cpp b/src/gameconfigfile.cpp
index cd221958d9..ffeba90f2b 100644
--- a/src/gameconfigfile.cpp
+++ b/src/gameconfigfile.cpp
@@ -356,12 +356,7 @@ void FGameConfigFile::DoGlobalSetup ()
 			}
 			if (last < 213)
 			{
-				FBaseCVar *var = FindCVar("hud_scale", NULL);
-				if (var != NULL)
-				{
-					var->ResetToDefault();
-				}
-				var = FindCVar("snd_channels", NULL);
+				auto var = FindCVar("snd_channels", NULL);
 				if (var != NULL)
 				{
 					// old settings were default 32, minimum 8, new settings are default 128, minimum 64.
@@ -369,6 +364,21 @@ void FGameConfigFile::DoGlobalSetup ()
 					if (v.Int < 64) var->ResetToDefault();
 				}
 			}
+			if (last < 214)
+			{
+				FBaseCVar *var = FindCVar("hud_scale", NULL);
+				if (var != NULL) var->ResetToDefault();
+				var = FindCVar("st_scale", NULL);
+				if (var != NULL) var->ResetToDefault();
+				var = FindCVar("hud_althudscale", NULL);
+				if (var != NULL) var->ResetToDefault();
+				var = FindCVar("con_scale", NULL);
+				if (var != NULL) var->ResetToDefault();
+				var = FindCVar("con_scaletext", NULL);
+				if (var != NULL) var->ResetToDefault();
+
+			}
+
 		}
 	}
 }
diff --git a/src/gl/data/gl_data.cpp b/src/gl/data/gl_data.cpp
index 6b4edcb9da..a055523fc2 100644
--- a/src/gl/data/gl_data.cpp
+++ b/src/gl/data/gl_data.cpp
@@ -69,6 +69,7 @@ CUSTOM_CVAR(Bool, gl_notexturefill, false, 0)
 
 
 void gl_CreateSections();
+void AddAutoBrightmaps();
 
 //-----------------------------------------------------------------------------
 //
@@ -364,6 +365,7 @@ void gl_RecalcVertexHeights(vertex_t * v)
 void gl_InitData()
 {
 	AdjustSpriteOffsets();
+	AddAutoBrightmaps();
 }
 
 //==========================================================================
diff --git a/src/gl/renderer/gl_2ddrawer.cpp b/src/gl/renderer/gl_2ddrawer.cpp
index 3f4f74cc52..0e08073cca 100644
--- a/src/gl/renderer/gl_2ddrawer.cpp
+++ b/src/gl/renderer/gl_2ddrawer.cpp
@@ -140,6 +140,7 @@ void F2DDrawer::AddTexture(FTexture *img, DrawParms &parms)
 		color = PalEntry(light, light, light);
 	}
 	color.a = (uint8_t)(parms.Alpha * 255);
+	color = PalEntry((color.a * parms.color.a) / 255, (color.r * parms.color.r) / 255, (color.g * parms.color.g) / 255, (color.b * parms.color.b) / 255);
 
 	// scissor test doesn't use the current viewport for the coordinates, so use real screen coordinates
 	dg.mScissor[0] = GLRenderer->ScreenToWindowX(parms.lclip);
diff --git a/src/gl/scene/gl_weapon.cpp b/src/gl/scene/gl_weapon.cpp
index 5f261d9d4c..c0dc250f71 100644
--- a/src/gl/scene/gl_weapon.cpp
+++ b/src/gl/scene/gl_weapon.cpp
@@ -52,7 +52,6 @@
 
 EXTERN_CVAR (Bool, r_drawplayersprites)
 EXTERN_CVAR(Float, transsouls)
-EXTERN_CVAR (Bool, st_scale)
 EXTERN_CVAR(Int, gl_fuzztype)
 EXTERN_CVAR (Bool, r_deathcamera)
 
@@ -121,7 +120,7 @@ void GLSceneDrawer::DrawPSprite (player_t * player,DPSprite *psp, float sx, floa
 		{
 			ftexturemid -= fYAd;
 		}
-		else if (!st_scale)
+		else 
 		{
 			ftexturemid -= StatusBar->GetDisplacement () * fYAd;
 		}
diff --git a/src/gl/system/gl_swframebuffer.cpp b/src/gl/system/gl_swframebuffer.cpp
index acf5dc40b0..3bb0fd8d88 100644
--- a/src/gl/system/gl_swframebuffer.cpp
+++ b/src/gl/system/gl_swframebuffer.cpp
@@ -2773,6 +2773,12 @@ void OpenGLSWFrameBuffer::DrawTextureParms(FTexture *img, DrawParms &parms)
 
 	vert = &VertexData[VertexPos];
 
+	{
+		PalEntry color = color1;
+		color = PalEntry((color.a * parms.color.a) / 255, (color.r * parms.color.r) / 255, (color.g * parms.color.g) / 255, (color.b * parms.color.b) / 255);
+		color1 = color; 
+	}
+
 	// Fill the vertex buffer.
 	vert[0].x = float(x0);
 	vert[0].y = float(y0);
diff --git a/src/gl/textures/gl_texture.cpp b/src/gl/textures/gl_texture.cpp
index fbed6b3837..63e29d240f 100644
--- a/src/gl/textures/gl_texture.cpp
+++ b/src/gl/textures/gl_texture.cpp
@@ -665,11 +665,13 @@ void gl_ParseBrightmap(FScanner &sc, int deflump)
 
 	if (bmtex != NULL)
 	{
+		/* I do not think this is needed any longer
 		if (tex->bWarped != 0)
 		{
 			Printf("Cannot combine warping with brightmap on texture '%s'\n", tex->Name.GetChars());
 			return;
 		}
+		*/
 
 		bmtex->bMasked = false;
 		tex->gl_info.Brightmap = bmtex;
@@ -677,6 +679,33 @@ void gl_ParseBrightmap(FScanner &sc, int deflump)
 	tex->gl_info.bDisableFullbright = disable_fullbright;
 }
 
+//==========================================================================
+//
+//
+//
+//==========================================================================
+
+void AddAutoBrightmaps()
+{
+	int num = Wads.GetNumLumps();
+	for (unsigned i = 0; i < num; i++)
+	{
+		const char *name = Wads.GetLumpFullName(i);
+		if (strstr(name, "brightmaps/auto/") == name)
+		{
+			TArray<FTextureID> list;
+			FString texname = ExtractFileBase(name, false);
+			TexMan.ListTextures(texname, list);
+			auto bmtex = TexMan.FindTexture(name, FTexture::TEX_Any, FTextureManager::TEXMAN_TryAny);
+			for (auto texid : list)
+			{
+				bmtex->bMasked = false;
+				TexMan[texid]->gl_info.Brightmap = bmtex;
+			}
+		}
+	}
+}
+
 //==========================================================================
 //
 // Parses a GLBoom+ detail texture definition
diff --git a/src/hu_scores.cpp b/src/hu_scores.cpp
index 0c001a3ba0..a399011812 100644
--- a/src/hu_scores.cpp
+++ b/src/hu_scores.cpp
@@ -51,6 +51,7 @@
 #include "d_net.h"
 #include "c_dispatch.h"
 #include "g_levellocals.h"
+#include "sbar.h"
 
 // MACROS ------------------------------------------------------------------
 
@@ -262,7 +263,7 @@ static void HU_DoDrawScores (player_t *player, player_t *sortedplayers[MAXPLAYER
 	lineheight = MAX(height, maxiconheight * CleanYfac);
 	ypadding = (lineheight - height + 1) / 2;
 
-	bottom = gST_Y;
+	bottom = StatusBar->GetTopOfStatusbar();
 	y = MAX(48*CleanYfac, (bottom - MAXPLAYERS * (height + CleanYfac + 1)) / 2);
 
 	HU_DrawTimeRemaining (bottom - height);
diff --git a/src/menu/menu.cpp b/src/menu/menu.cpp
index 5ae578499a..89503c42ac 100644
--- a/src/menu/menu.cpp
+++ b/src/menu/menu.cpp
@@ -244,6 +244,12 @@ void DMenu::Close ()
 	if (CurrentMenu != nullptr)
 	{
 		GC::WriteBarrier(CurrentMenu);
+		IFVIRTUALPTR(CurrentMenu, DMenu, OnReturn)
+		{
+			VMValue params[] = { CurrentMenu };
+			GlobalVMStack.Call(func, params, 1, nullptr, 0, nullptr);
+		}
+
 	}
 	else
 	{
diff --git a/src/menu/playermenu.cpp b/src/menu/playermenu.cpp
index c730f2d813..fa5796a453 100644
--- a/src/menu/playermenu.cpp
+++ b/src/menu/playermenu.cpp
@@ -91,7 +91,7 @@ DEFINE_ACTION_FUNCTION(DPlayerMenu, PlayerNameChanged)
 	const char *pp = s;
 	FString command("name \"");
 
-	if (self == CurrentMenu)
+	if (self == CurrentMenu || self == CurrentMenu->mParentMenu)
 	{
 		// Escape any backslashes or quotation marks before sending the name to the console.
 		for (auto p = pp; *p != '\0'; ++p)
diff --git a/src/p_interaction.cpp b/src/p_interaction.cpp
index eb1fb46fe4..b4db7cb0ba 100644
--- a/src/p_interaction.cpp
+++ b/src/p_interaction.cpp
@@ -1415,9 +1415,19 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da
 				}
 			}
 
-			if ( P_GiveBody( source, int(draindamage * damage)))
+			if (draindamage > 0)
 			{
-				S_Sound(source, CHAN_ITEM, "*drainhealth", 1, ATTN_NORM );
+				int draindmg = int(draindamage * damage);
+				IFVIRTUALPTR(source, AActor, OnDrain)
+				{
+					VMValue params[] = { source, target, draindmg, mod.GetIndex() };
+					VMReturn ret(&draindmg);
+					GlobalVMStack.Call(func, params, countof(params), &ret, 1);
+				}
+				if (P_GiveBody(source, draindmg))
+				{
+					S_Sound(source, CHAN_ITEM, "*drainhealth", 1, ATTN_NORM);
+				}
 			}
 		}
 	}
diff --git a/src/p_user.cpp b/src/p_user.cpp
index 694daffa1d..ab9319f20c 100644
--- a/src/p_user.cpp
+++ b/src/p_user.cpp
@@ -759,6 +759,7 @@ bool player_t::Resurrect()
 	E_PlayerRespawned(int(this - players));
 	//
 	FBehavior::StaticStartTypedScripts(SCRIPT_Respawn, mo, true);
+	return true;
 }
 
 DEFINE_ACTION_FUNCTION(_PlayerInfo, Resurrect)
diff --git a/src/polyrenderer/scene/poly_playersprite.cpp b/src/polyrenderer/scene/poly_playersprite.cpp
index 360af43bc2..eb1ff323ed 100644
--- a/src/polyrenderer/scene/poly_playersprite.cpp
+++ b/src/polyrenderer/scene/poly_playersprite.cpp
@@ -32,7 +32,6 @@
 
 EXTERN_CVAR(Bool, r_drawplayersprites)
 EXTERN_CVAR(Bool, r_deathcamera)
-EXTERN_CVAR(Bool, st_scale)
 EXTERN_CVAR(Bool, r_fullbrightignoresectorcolor)
 EXTERN_CVAR(Bool, r_shadercolormaps)
 
@@ -275,7 +274,7 @@ void RenderPolyPlayerSprites::RenderSprite(DPSprite *pspr, AActor *owner, float
 
 	if (viewpoint.camera->player && (renderTarget != screen ||
 		viewheight == renderTarget->GetHeight() ||
-		(renderTarget->GetWidth() > (BASEXCENTER * 2) && !st_scale)))
+		(renderTarget->GetWidth() > (BASEXCENTER * 2))))
 	{	// Adjust PSprite for fullscreen views
 		AWeapon *weapon = dyn_cast<AWeapon>(pspr->GetCaller());
 		if (weapon != nullptr && weapon->YAdjust != 0)
diff --git a/src/r_utility.cpp b/src/r_utility.cpp
index df6c1e3ddb..6af399bfbd 100644
--- a/src/r_utility.cpp
+++ b/src/r_utility.cpp
@@ -59,6 +59,7 @@
 #include "p_local.h"
 #include "g_levellocals.h"
 #include "p_maputl.h"
+#include "sbar.h"
 #include "math/cmath.h"
 
 
@@ -262,13 +263,13 @@ void R_ExecuteSetViewSize (FRenderViewpoint &viewpoint, FViewWindow &viewwindow)
 	setsizeneeded = false;
 	V_SetBorderNeedRefresh();
 
-	R_SetWindow (viewpoint, viewwindow, setblocks, SCREENWIDTH, SCREENHEIGHT, gST_Y);
+	R_SetWindow (viewpoint, viewwindow, setblocks, SCREENWIDTH, SCREENHEIGHT, StatusBar->GetTopOfStatusbar());
 
 	// Handle resize, e.g. smaller view windows with border and/or status bar.
 	viewwindowx = (screen->GetWidth() - viewwidth) >> 1;
 
 	// Same with base row offset.
-	viewwindowy = (viewwidth == screen->GetWidth()) ? 0 : (gST_Y - viewheight) >> 1;
+	viewwindowy = (viewwidth == screen->GetWidth()) ? 0 : (StatusBar->GetTopOfStatusbar() - viewheight) >> 1;
 }
 
 //==========================================================================
diff --git a/src/scripting/zscript/zcc-parse.lemon b/src/scripting/zscript/zcc-parse.lemon
index aa618fa945..cf7eb45661 100644
--- a/src/scripting/zscript/zcc-parse.lemon
+++ b/src/scripting/zscript/zcc-parse.lemon
@@ -1859,6 +1859,7 @@ for_init(X) ::= for_bump(A).						{ X = A /*X-overwrites-A*/; }
 %type for_bump{ZCC_Statement *}
 for_bump(X) ::= .									{ X = NULL; }
 for_bump(X) ::= expression_statement(A).			{ X = A; /*X-overwrites-A*/ }
+for_bump(X) ::= for_bump(A) COMMA expression_statement(B). { X = A; /*X-overwrites-A*/ AppendTreeNodeSibling(X, B); }
 
 /*----- If Statements -----*/
 
diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp
index 8dfed30e7f..fe93be7c9c 100644
--- a/src/scripting/zscript/zcc_compile.cpp
+++ b/src/scripting/zscript/zcc_compile.cpp
@@ -3482,7 +3482,15 @@ FxExpression *ZCCCompiler::ConvertNode(ZCC_TreeNode *ast)
 		}
 		else if (iter->LoopBumper != nullptr)
 		{
-			return new FxForLoop(nullptr, ConvertNode(iter->LoopCondition), ConvertNode(iter->LoopBumper), ConvertNode(iter->LoopStatement), *ast);
+			FArgumentList bumper;
+			ConvertNodeList(bumper, iter->LoopBumper);
+			FxCompoundStatement *bumps = new FxCompoundStatement(*ast);
+			for (auto &ex : bumper)
+			{
+				bumps->Add(ex);
+				ex = nullptr;
+			}
+			return new FxForLoop(nullptr, ConvertNode(iter->LoopCondition), bumps, ConvertNode(iter->LoopStatement), *ast);
 		}
 		else
 		{
diff --git a/src/st_stuff.h b/src/st_stuff.h
index a0434f8b42..19e17a1114 100644
--- a/src/st_stuff.h
+++ b/src/st_stuff.h
@@ -26,8 +26,6 @@
 
 struct event_t;
 
-extern int gST_Y;
-
 bool ST_Responder(event_t* ev);
 
 // [RH] Base blending values (for e.g. underwater)
diff --git a/src/swrenderer/things/r_playersprite.cpp b/src/swrenderer/things/r_playersprite.cpp
index 4afa44d607..c56b95980b 100644
--- a/src/swrenderer/things/r_playersprite.cpp
+++ b/src/swrenderer/things/r_playersprite.cpp
@@ -58,7 +58,6 @@
 #include "swrenderer/r_renderthread.h"
 #include "g_levellocals.h"
 
-EXTERN_CVAR(Bool, st_scale)
 EXTERN_CVAR(Bool, r_drawplayersprites)
 EXTERN_CVAR(Bool, r_deathcamera)
 EXTERN_CVAR(Bool, r_shadercolormaps)
@@ -275,7 +274,7 @@ namespace swrenderer
 
 		if (Thread->Viewport->viewpoint.camera->player && (viewport->RenderTarget != screen ||
 			viewheight == viewport->RenderTarget->GetHeight() ||
-			(viewport->RenderTarget->GetWidth() > (BASEXCENTER * 2) && !st_scale)))
+			(viewport->RenderTarget->GetWidth() > (BASEXCENTER * 2))))
 		{	// Adjust PSprite for fullscreen views
 			AWeapon *weapon = dyn_cast<AWeapon>(pspr->GetCaller());
 			if (weapon != nullptr && weapon->YAdjust != 0)
diff --git a/src/v_draw.cpp b/src/v_draw.cpp
index b95ade5bda..78b0dd7f36 100644
--- a/src/v_draw.cpp
+++ b/src/v_draw.cpp
@@ -65,12 +65,37 @@
 
 CUSTOM_CVAR(Int, uiscale, 2, CVAR_ARCHIVE | CVAR_NOINITCALL)
 {
+	if (self < 0)
+	{
+		self = 0;
+		return;
+	}
 	if (StatusBar != NULL)
 	{
 		StatusBar->CallScreenSizeChanged();
 	}
 }
 
+int GetUIScale(int altval)
+{
+	int scaleval;
+	if (altval > 0) scaleval = altval;
+	else if (uiscale == 0)
+	{
+		// Default should try to scale to 640x480
+		int vscale = screen->GetHeight() / 640;
+		int hscale = screen->GetWidth() / 480;
+		scaleval = clamp(vscale, 1, hscale);
+	}
+	else scaleval = uiscale;
+
+	// block scales that result in something larger than the current screen.
+	int vmax = screen->GetHeight() / 200;
+	int hmax = screen->GetWidth() / 320;
+	int max = MAX(vmax, hmax);
+	return MIN(scaleval, max);
+}
+
 // [RH] Stretch values to make a 320x200 image best fit the screen
 // without using fractional steppings
 int CleanXfac, CleanYfac;
@@ -361,6 +386,7 @@ bool DCanvas::ParseDrawTextureTags(FTexture *img, double x, double y, uint32_t t
 	parms->colorOverlay = 0;
 	parms->alphaChannel = false;
 	parms->flipX = false;
+	parms->color = 0xffffffff;
 	//parms->shadowAlpha = 0;
 	parms->shadowColor = 0;
 	parms->virtWidth = this->GetWidth();
@@ -537,6 +563,10 @@ bool DCanvas::ParseDrawTextureTags(FTexture *img, double x, double y, uint32_t t
 			parms->colorOverlay = ListGetInt(tags);
 			break;
 
+		case DTA_Color:
+			parms->color = ListGetInt(tags);
+			break;
+
 		case DTA_FlipX:
 			parms->flipX = ListGetInt(tags);
 			break;
@@ -1257,10 +1287,9 @@ static void V_DrawViewBorder (void)
 	V_DrawBorder (0, 0, SCREENWIDTH, viewwindowy);
 	V_DrawBorder (0, viewwindowy, viewwindowx, viewheight + viewwindowy);
 	V_DrawBorder (viewwindowx + viewwidth, viewwindowy, SCREENWIDTH, viewheight + viewwindowy);
-	V_DrawBorder (0, viewwindowy + viewheight, SCREENWIDTH, gST_Y);
+	V_DrawBorder (0, viewwindowy + viewheight, SCREENWIDTH, StatusBar->GetTopOfStatusbar());
 
 	V_DrawFrame (viewwindowx, viewwindowy, viewwidth, viewheight);
-	V_MarkRect (0, 0, SCREENWIDTH, gST_Y);
 }
 
 //==========================================================================
diff --git a/src/v_font.cpp b/src/v_font.cpp
index a1730bd044..e6aa57a505 100644
--- a/src/v_font.cpp
+++ b/src/v_font.cpp
@@ -756,9 +756,18 @@ void FFont::BuildTranslations (const double *luminosity, const uint8_t *identity
 //
 //==========================================================================
 
-FRemapTable *FFont::GetColorTranslation (EColorRange range) const
+FRemapTable *FFont::GetColorTranslation (EColorRange range, PalEntry *color) const
 {
-	if (ActiveColors == 0 || noTranslate)
+	if (noTranslate)
+	{
+		PalEntry retcolor = PalEntry(255, 255, 255, 255);
+		if (range >= 0 && range < NumTextColors && range != CR_UNTRANSLATED)
+		{
+			retcolor = TranslationColors[range];
+		}
+		if (color != nullptr) *color = retcolor;
+	}
+	if (ActiveColors == 0)
 		return NULL;
 	else if (range >= NumTextColors)
 		range = CR_UNTRANSLATED;
diff --git a/src/v_font.h b/src/v_font.h
index 6553f58555..c3d2ce5612 100644
--- a/src/v_font.h
+++ b/src/v_font.h
@@ -80,7 +80,7 @@ public:
 
 	virtual FTexture *GetChar (int code, int *const width) const;
 	virtual int GetCharWidth (int code) const;
-	FRemapTable *GetColorTranslation (EColorRange range) const;
+	FRemapTable *GetColorTranslation (EColorRange range, PalEntry *color = nullptr) const;
 	int GetLump() const { return Lump; }
 	int GetSpaceWidth () const { return SpaceWidth; }
 	int GetHeight () const { return FontHeight; }
diff --git a/src/v_text.cpp b/src/v_text.cpp
index 90131ed864..454964aa3f 100644
--- a/src/v_text.cpp
+++ b/src/v_text.cpp
@@ -80,7 +80,9 @@ void DCanvas::DrawChar (FFont *font, int normalcolor, double x, double y, int ch
 		{
 			return;
 		}
-		parms.remap = font->GetColorTranslation((EColorRange)normalcolor);
+		PalEntry color = 0xffffffff;
+		parms.remap = font->GetColorTranslation((EColorRange)normalcolor, &color);
+		parms.color = PalEntry((color.a * parms.color.a) / 255, (color.r * parms.color.r) / 255, (color.g * parms.color.g) / 255, (color.b * parms.color.b) / 255);
 		DrawTextureParms(pic, parms);
 	}
 }
@@ -102,7 +104,9 @@ void DCanvas::DrawChar(FFont *font, int normalcolor, double x, double y, int cha
 		uint32_t tag = ListGetInt(args);
 		bool res = ParseDrawTextureTags(pic, x, y, tag, args, &parms, false);
 		if (!res) return;
-		parms.remap = font->GetColorTranslation((EColorRange)normalcolor);
+		PalEntry color = 0xffffffff;
+		parms.remap = font->GetColorTranslation((EColorRange)normalcolor, &color);
+		parms.color = PalEntry((color.a * parms.color.a) / 255, (color.r * parms.color.r) / 255, (color.g * parms.color.g) / 255, (color.b * parms.color.b) / 255);
 		DrawTextureParms(pic, parms);
 	}
 }
@@ -151,7 +155,10 @@ void DCanvas::DrawTextCommon(FFont *font, int normalcolor, double x, double y, c
 		normalcolor = CR_UNTRANSLATED;
 	boldcolor = normalcolor ? normalcolor - 1 : NumTextColors - 1;
 
-	range = font->GetColorTranslation((EColorRange)normalcolor);
+	PalEntry colorparm = parms.color;
+	PalEntry color = 0xffffffff;
+	range = font->GetColorTranslation((EColorRange)normalcolor, &color);
+	parms.color = PalEntry(colorparm.a, (color.r * colorparm.r) / 255, (color.g * colorparm.g) / 255, (color.b * colorparm.b) / 255);
 
 	kerning = font->GetDefaultKerning();
 
@@ -171,7 +178,8 @@ void DCanvas::DrawTextCommon(FFont *font, int normalcolor, double x, double y, c
 			EColorRange newcolor = V_ParseFontColor(ch, normalcolor, boldcolor);
 			if (newcolor != CR_UNDEFINED)
 			{
-				range = font->GetColorTranslation(newcolor);
+				range = font->GetColorTranslation(newcolor, &color);
+				parms.color = PalEntry(colorparm.a, (color.r * colorparm.r) / 255, (color.g * colorparm.g) / 255, (color.b * colorparm.b) / 255);
 			}
 			continue;
 		}
diff --git a/src/v_video.cpp b/src/v_video.cpp
index 3dea831be6..3073e259c0 100644
--- a/src/v_video.cpp
+++ b/src/v_video.cpp
@@ -197,13 +197,6 @@ bool	setmodeneeded = false;
 int		NewWidth, NewHeight, NewBits;
 
 
-//
-// V_MarkRect 
-// 
-void V_MarkRect (int x, int y, int width, int height)
-{
-}
-
 //==========================================================================
 //
 // DCanvas Constructor
@@ -877,13 +870,10 @@ void DFrameBuffer::DrawRateStuff ()
 			chars = mysnprintf (fpsbuff, countof(fpsbuff), "%2u ms (%3u fps)", howlong, LastCount);
 			rate_x = Width / textScale - ConFont->StringWidth(&fpsbuff[0]);
 			Clear (rate_x * textScale, 0, Width, ConFont->GetHeight() * textScale, GPalette.BlackIndex, 0);
-			if (textScale == 1)
-				DrawText (ConFont, CR_WHITE, rate_x, 0, (char *)&fpsbuff[0], TAG_DONE);
-			else
-				DrawText (ConFont, CR_WHITE, rate_x, 0, (char *)&fpsbuff[0],
-					DTA_VirtualWidth, screen->GetWidth() / textScale,
-					DTA_VirtualHeight, screen->GetHeight() / textScale,
-					DTA_KeepRatio, true, TAG_DONE);
+			DrawText (ConFont, CR_WHITE, rate_x, 0, (char *)&fpsbuff[0],
+				DTA_VirtualWidth, screen->GetWidth() / textScale,
+				DTA_VirtualHeight, screen->GetHeight() / textScale,
+				DTA_KeepRatio, true, TAG_DONE);
 
 			uint32_t thisSec = ms/1000;
 			if (LastSec < thisSec)
diff --git a/src/v_video.h b/src/v_video.h
index 2d554dea92..d836e1a1f0 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -125,6 +125,9 @@ enum
 	DTA_TextLen,		// stop after this many characters, even if \0 not hit
 	DTA_CellX,			// horizontal size of character cell
 	DTA_CellY,			// vertical size of character cell
+
+	// New additions. 
+	DTA_Color,
 };
 
 enum
@@ -161,6 +164,7 @@ struct DrawParms
 	uint32_t fillcolor;
 	FRemapTable *remap;
 	uint32_t colorOverlay;
+	PalEntry color;
 	INTBOOL alphaChannel;
 	INTBOOL flipX;
 	//float shadowAlpha;
@@ -528,8 +532,6 @@ void V_Init2 ();
 
 void V_Shutdown ();
 
-void V_MarkRect (int x, int y, int width, int height);
-
 class FScanner;
 // Returns the closest color to the one desired. String
 // should be of the form "rr gg bb".
@@ -561,7 +563,21 @@ int AspectBaseHeight(float aspect);
 double AspectPspriteOffset(float aspect);
 int AspectMultiplier(float aspect);
 bool AspectTallerThanWide(float aspect);
+int GetUIScale(int altval);
 
 EXTERN_CVAR(Int, uiscale);
+EXTERN_CVAR(Int, con_scaletext);
+EXTERN_CVAR(Int, con_scale);
+
+inline int active_con_scaletext()
+{
+	return GetUIScale(con_scaletext);
+}
+
+inline int active_con_scale()
+{
+	return GetUIScale(con_scale);
+}
+
 
 #endif // __V_VIDEO_H__
diff --git a/src/version.h b/src/version.h
index 9601911eb8..ef25c91e62 100644
--- a/src/version.h
+++ b/src/version.h
@@ -66,7 +66,7 @@ const char *GetVersionString();
 // Version stored in the ini's [LastRun] section.
 // Bump it if you made some configuration change that you want to
 // be able to migrate in FGameConfigFile::DoGlobalSetup().
-#define LASTRUNVERSION "213"
+#define LASTRUNVERSION "214"
 
 // Protocol version used in demos.
 // Bump it if you change existing DEM_ commands or add new ones.
diff --git a/src/win32/fb_d3d9.cpp b/src/win32/fb_d3d9.cpp
index 4fd198a1bc..332e2416db 100644
--- a/src/win32/fb_d3d9.cpp
+++ b/src/win32/fb_d3d9.cpp
@@ -2942,6 +2942,12 @@ void D3DFB::DrawTextureParms (FTexture *img, DrawParms &parms)
 
 	vert = &VertexData[VertexPos];
 
+	{
+		PalEntry color = color1;
+		color = PalEntry((color.a * parms.color.a) / 255, (color.r * parms.color.r) / 255, (color.g * parms.color.g) / 255, (color.b * parms.color.b) / 255);
+		color1 = color; 
+	}
+
 	// Fill the vertex buffer.
 	vert[0].x = float(x0);
 	vert[0].y = float(y0);
diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu
index ecf47f78a0..f38967b742 100644
--- a/wadsrc/static/language.enu
+++ b/wadsrc/static/language.enu
@@ -1847,7 +1847,6 @@ HUDMNU_CROSSHAIRHEALTH			= "Crosshair shows health";
 HUDMNU_CROSSHAIRSCALE			= "Scale crosshair";
 HUDMNU_NAMETAGS					= "Display nametags";
 HUDMNU_NAMETAGCOLOR				= "Nametag color";
-HUDMNU_SCALESTATBAR				= "Stretch status bar";
 HUDMNU_SCALEFULLSCREENHUD		= "Stretch Fullscreen HUD";
 HUDMNU_OLDOUCH					= "Use old ouch mug shot formula";
 HUDMNU_HEXENFLASHES				= "Hexen weapon flashes";
@@ -1855,10 +1854,19 @@ HUDMNU_POISONFLASHES			= "Poison damage flashes";
 HUDMNU_ICEFLASHES				= "Ice death flashes";
 HUDMNU_HAZARDFLASHES			= "Poison Buildup flashes";
 
+// Scaling options
+SCALEMNU_TITLE					= "Scaling Options";
+SCALEMNU_OVERRIDE				= "Overrides for above setting";
+SCALEMNU_MESSAGES 				= "Messages";
+SCALEMNU_CONSOLE				= "Console";
+SCALEMNU_STATBAR				= "Status bar";
+SCALEMNU_HUD					= "Fullscreen HUD";
+SCALEMNU_ALTHUD					= "Alternative HUD";
+SCALEMNU_HUDASPECT				= "HUD preserves aspect ratio";
+
 // AltHUD Options
 ALTHUDMNU_TITLE					= "Alternative HUD";
 ALTHUDMNU_ENABLE				= "Enable alternative HUD";
-ALTHUDMNU_SCALEHUD				= "Stretch alternative HUD";
 ALTHUDMNU_SHOWSECRETS			= "Show secret count";
 ALTHUDMNU_SHOWMONSTERS			= "Show monster count";
 ALTHUDMNU_SHOWITEMS				= "Show item count";
@@ -1977,8 +1985,6 @@ MSGMNU_TITLE				= "MESSAGES";
 MSGMNU_SHOWMESSAGES			= "Show messages";
 MSGMNU_SHOWOBITUARIES		= "Show obituaries";
 MSGMNU_SHOWSECRETS			= "Show secret notifications";
-MSGMNU_SCALETEXT			= "Scale text in high res";
-MSGMNU_SCALECONSOLE			= "Scale console";
 MSGMNU_MESSAGELEVEL			= "Minimum message level";
 MSGMNU_CENTERMESSAGES		= "Center messages";
 MSGMNU_MESSAGECOLORS		= "Message Colors";
diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt
index 7f55396085..f086a6478b 100644
--- a/wadsrc/static/menudef.txt
+++ b/wadsrc/static/menudef.txt
@@ -828,11 +828,10 @@ OptionValue ZDoomStrife
 OptionMenu "HUDOptions"
 {
 	Title "$HUDMNU_TITLE"
+	Submenu "$HUDMNU_SCALEOPT",				"ScalingOptions"
 	Submenu "$HUDMNU_ALTHUD",				"AltHudOptions"
 	Submenu "$HUDMNU_MESSAGE", 				"MessageOptions"
 	StaticText " "
-	Slider "$HUDMNU_UISCALE",				"uiscale", 0.0, 8.0, 1.0, 0
-	StaticText " "
 	Option "$HUDMNU_CROSSHAIR",				"crosshair", "Crosshairs"
 	Option "$HUDMNU_FORCECROSSHAIR",		"crosshairforce", "OnOff"
 	Option "$HUDMNU_GROWCROSSHAIR",			"crosshairgrow", "OnOff"
@@ -842,7 +841,6 @@ OptionMenu "HUDOptions"
 	StaticText " "
 	Option "$HUDMNU_NAMETAGS",				"displaynametags", "DisplayTagsTypes"
 	Option "$HUDMNU_NAMETAGCOLOR",			"nametagcolor", "TextColors", "displaynametags"
-	Option "$HUDMNU_SCALESTATBAR",			"st_scale", "OnOff"
 	Option "$HUDMNU_OLDOUCH",				"st_oldouch", "OnOff"
 	StaticText " "
 	Option "$HUDMNU_HEXENFLASHES",			"pf_hexenweaps", "ZDoomHexen"
@@ -850,7 +848,22 @@ OptionMenu "HUDOptions"
 	Option "$HUDMNU_ICEFLASHES",			"pf_ice", "ZDoomHexen"
 	Option "$HUDMNU_HAZARDFLASHES",			"pf_hazard", "ZDoomStrife"
 }
-	
+
+OptionMenu "ScalingOptions"
+{
+	Title "$SCALEMNU_TITLE"
+	Slider "$HUDMNU_UISCALE",				"uiscale", 0.0, 8.0, 1.0, 0
+	StaticText " "
+	// These will need a new control type.
+	StaticText "$SCALEMNU_OVERRIDE", 1
+	Option "$SCALEMNU_MESSAGES", 				"con_scaletext", "OnOff"
+	Option "$SCALEMNU_CONSOLE", 				"con_scale", "OnOff"
+	Option "$SCALEMNU_STATBAR",					"st_scale", "OnOff"
+	Option "$SCALEMNU_HUD", 					"hud_scale", "OnOff"
+	Option "$SCALEMNU_ALTHUD",					"hud_althudscale", "OnOff"
+	StaticText " "
+	Option "$SCALEMNU_HUDASPECT", 				"hud_aspectscale", "OnOff"
+}
 //-------------------------------------------------------------------------------------------
 //
 // Alternative HUD
@@ -863,15 +876,6 @@ OptionValue "AMCoordinates"
 	1, "$OPTVAL_MAP"
 }
 
-OptionValue "AltHUDScale"
-{
-	0, "$OPTVAL_OFF"
-	4, "$OPTVAL_ON"
-	1, "$OPTVAL_SCALETO640X400"
-	2, "$OPTVAL_PIXELDOUBLE"
-	3, "$OPTVAL_PIXELQUADRUPLE"
-}
-
 OptionValue "AltHUDAmmo"
 {
 	0, "$OPTVAL_CURRENTWEAPON"
@@ -911,7 +915,6 @@ OptionMenu "AltHUDOptions"
 	Title "$ALTHUDMNU_TITLE"
 	//Indent 220
 	Option "$ALTHUDMNU_ENABLE",					"hud_althud", "OnOff"
-	Option "$ALTHUDMNU_SCALEHUD",				"hud_althudscale", "AltHUDScale"
 	Option "$ALTHUDMNU_SHOWSECRETS",			"hud_showsecrets", "OnOff"
 	Option "$ALTHUDMNU_SHOWMONSTERS",			"hud_showmonsters", "OnOff"
 	Option "$ALTHUDMNU_SHOWITEMS",				"hud_showitems", "OnOff"
@@ -1189,23 +1192,6 @@ OptionMenu ColorPickerMenu
 //-------------------------------------------------------------------------------------------
 
 
-OptionValue ScaleValues
-{
-	0, "$OPTVAL_OFF"
-	1, "$OPTVAL_ON"
-	2, "$OPTVAL_DOUBLE"
-	3, "$OPTVAL_QUADRUPLE"
-}
-
-OptionValue ConsoleScaleValues
-{
-	1, "$OPTVAL_OFF"
-	0, "$OPTVAL_ON"
-	2, "$OPTVAL_DOUBLE"
-	3, "$OPTVAL_TRIPLE"
-	4, "$OPTVAL_QUADRUPLE"
-}
-
 OptionValue MessageLevels
 {
 	0.0, "$OPTVAL_ITEMPICKUP"
@@ -1228,8 +1214,6 @@ OptionMenu MessageOptions
 	Option "$MSGMNU_SHOWMESSAGES",				"show_messages", "OnOff"
 	Option "$MSGMNU_SHOWOBITUARIES",			"show_obituaries", "OnOff"
 	Option "$MSGMNU_SHOWSECRETS",				"cl_showsecretmessage", "OnOff"
-	Option "$MSGMNU_SCALETEXT", 				"con_scaletext", "ScaleValues"
-	Option "$MSGMNU_SCALECONSOLE", 				"con_scale", "ConsoleScaleValues"
 	Option "$MSGMNU_MESSAGELEVEL", 				"msg", "MessageLevels"
 	Option "$MSGMNU_DEVELOPER", 				"developer", "DevMessageLevels"
 	Option "$MSGMNU_CENTERMESSAGES",			"con_centernotify", "OnOff"
diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt
index daac2f23dd..fb883f846c 100644
--- a/wadsrc/static/zscript/actor.txt
+++ b/wadsrc/static/zscript/actor.txt
@@ -489,6 +489,11 @@ class Actor : Thinker native
 		return Obituary;
 	}
 	
+	virtual int OnDrain(Actor victim, int damage, Name dmgtype)
+	{
+		return damage;
+	}
+	
 	native virtual bool OkayToSwitchTarget(Actor other);
 	native static class<Actor> GetReplacement(class<Actor> cls);
 	native static class<Actor> GetReplacee(class<Actor> cls);
diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt
index 379e660ce6..d92f79b6da 100644
--- a/wadsrc/static/zscript/base.txt
+++ b/wadsrc/static/zscript/base.txt
@@ -521,10 +521,11 @@ struct LevelLocals native
 	native static void WorldDone();
 	native static void RemoveAllBots(bool fromlist);
 	native void SetInterMusic(String nextmap);
+	native String FormatMapName(int mapnamecolor);
 	
-	String TimeFormatted()
+	String TimeFormatted(bool totals = false)
 	{
-		int sec = Thinker.Tics2Seconds(time); 
+		int sec = Thinker.Tics2Seconds(totals? totaltime : time); 
 		return String.Format("%02d:%02d:%02d", sec / 3600, (sec % 3600) / 60, sec % 60);
 	}
 }
diff --git a/wadsrc/static/zscript/events.txt b/wadsrc/static/zscript/events.txt
index 0c0947f45a..f3a281e0c5 100755
--- a/wadsrc/static/zscript/events.txt
+++ b/wadsrc/static/zscript/events.txt
@@ -305,7 +305,7 @@ class StaticEventHandler : Object native play version("2.4")
 
     //
     //virtual native ui void RenderFrame(RenderEvent e);
-    //virtual native ui void RenderOverlay(RenderEvent e);
+    virtual native ui void RenderOverlay(RenderEvent e);
     
     //
     virtual native void PlayerEntered(PlayerEvent e);
diff --git a/wadsrc/static/zscript/hexen/clericflame.txt b/wadsrc/static/zscript/hexen/clericflame.txt
index d222a07db3..1978f43f17 100644
--- a/wadsrc/static/zscript/hexen/clericflame.txt
+++ b/wadsrc/static/zscript/hexen/clericflame.txt
@@ -274,6 +274,7 @@ class CFlameMissile : FastProjectile
 	void A_CFlamePuff()
 	{
 		bInvisible = false;
+		bMissile = false;
 		Vel = (0,0,0);
 		A_PlaySound ("ClericFlameExplode", CHAN_BODY);
 	}
diff --git a/wadsrc/static/zscript/menu/menu.txt b/wadsrc/static/zscript/menu/menu.txt
index 09f5d9cdeb..32b738c5c7 100644
--- a/wadsrc/static/zscript/menu/menu.txt
+++ b/wadsrc/static/zscript/menu/menu.txt
@@ -271,6 +271,7 @@ class Menu : Object native ui version("2.4")
 	virtual void ResetColor() {}
 	virtual bool MouseEvent(int type, int mx, int my) { return true; }
 	virtual void Ticker() {}
+	virtual void OnReturn() {}
 
 	//=============================================================================
 	//
diff --git a/wadsrc/static/zscript/shared/fastprojectile.txt b/wadsrc/static/zscript/shared/fastprojectile.txt
index 657c4680e5..a0fa1a91c0 100644
--- a/wadsrc/static/zscript/shared/fastprojectile.txt
+++ b/wadsrc/static/zscript/shared/fastprojectile.txt
@@ -156,7 +156,7 @@ class FastProjectile : Actor
 					ExplodeMissile (NULL, NULL);
 					return;
 				}
-				if (!(frac.xy ~== (0, 0)) && ripcount <= 0) 
+				if (changexy && ripcount <= 0) 
 				{
 					ripcount = count >> 3;
 
diff --git a/wadsrc/static/zscript/statusbar/doom_sbar.txt b/wadsrc/static/zscript/statusbar/doom_sbar.txt
index b1671889a8..f5ac66ac28 100644
--- a/wadsrc/static/zscript/statusbar/doom_sbar.txt
+++ b/wadsrc/static/zscript/statusbar/doom_sbar.txt
@@ -20,18 +20,24 @@ class DoomStatusBar : BaseStatusBar
 		diparms = InventoryBarState.Create();
 	}
 
+	override void DrawAutomapHUD(double ticFrac)
+	{
+		// This uses the normal automap HUD but just changes the highlight color.
+		DoDrawAutomapHUD(Font.CR_GREY, Font.CR_UNTRANSLATED);
+	}
+	
 	override void Draw (int state, double TicFrac)
 	{
 		Super.Draw (state, TicFrac);
 
 		if (state == HUD_StatusBar)
 		{
-			BeginStatusBar(320, 200, 32);
+			BeginStatusBar();
 			DrawMainBar (TicFrac);
 		}
 		else if (state == HUD_Fullscreen)
 		{
-			BeginHUD(320, 200, 1., false);
+			BeginHUD();
 			DrawFullScreenStuff ();
 		}
 	}
diff --git a/wadsrc/static/zscript/statusbar/heretic_sbar.txt b/wadsrc/static/zscript/statusbar/heretic_sbar.txt
index 1273ad4ce1..b6db47dcf0 100644
--- a/wadsrc/static/zscript/statusbar/heretic_sbar.txt
+++ b/wadsrc/static/zscript/statusbar/heretic_sbar.txt
@@ -25,6 +25,11 @@ class HereticStatusBar : BaseStatusBar
 		mHealthInterpolator = DynamicValueInterpolator.Create(0, 0.25, 1, 8);
 	}
 	
+	override int GetProtrusion(double scaleratio) const
+	{
+		return scaleratio > 0.7? 8 : 0;
+	}
+
 	override void NewGame ()
 	{
 		Super.NewGame();
@@ -43,12 +48,12 @@ class HereticStatusBar : BaseStatusBar
 
 		if (state == HUD_StatusBar)
 		{
-			BeginStatusBar(320, 200, 42);
+			BeginStatusBar();
 			DrawMainBar (TicFrac);
 		}
 		else if (state == HUD_Fullscreen)
 		{
-			BeginHUD(320, 200, 1., false);
+			BeginHUD();
 			DrawFullScreenStuff ();
 		}
 	}
diff --git a/wadsrc/static/zscript/statusbar/hexen_sbar.txt b/wadsrc/static/zscript/statusbar/hexen_sbar.txt
index f1ff81693d..d1418763f6 100644
--- a/wadsrc/static/zscript/statusbar/hexen_sbar.txt
+++ b/wadsrc/static/zscript/statusbar/hexen_sbar.txt
@@ -34,6 +34,11 @@ class HexenStatusBar : BaseStatusBar
 		mHealthInterpolator2.Reset (0);
 	}
 
+	override int GetProtrusion(double scaleratio) const
+	{
+		return scaleratio > 0.85? 28 : 12;	// need to get past the gargoyle's wings
+	}
+
 	override void Tick()
 	{
 		Super.Tick();
@@ -47,12 +52,12 @@ class HexenStatusBar : BaseStatusBar
 
 		if (state == HUD_StatusBar)
 		{
-			BeginStatusBar(320, 200, 38);
+			BeginStatusBar();
 			DrawMainBar (TicFrac);
 		}
 		else if (state == HUD_Fullscreen)
 		{
-			BeginHUD(320, 200, 1., false);
+			BeginHUD();
 			DrawFullScreenStuff ();
 		}
 	}
diff --git a/wadsrc/static/zscript/statusbar/statusbar.txt b/wadsrc/static/zscript/statusbar/statusbar.txt
index 01fbcc7aee..238e177bb2 100644
--- a/wadsrc/static/zscript/statusbar/statusbar.txt
+++ b/wadsrc/static/zscript/statusbar/statusbar.txt
@@ -270,10 +270,8 @@ class BaseStatusBar native ui
 	const POWERUPICONSIZE = 32;
 	
 
-	native int ST_X, ST_Y;
 	native int RelTop;
 	native int HorizontalResolution, VerticalResolution;
-	native bool Scaled;
 	native bool Centering;
 	native bool FixedOrigin;
 	native bool CompleteBorder;
@@ -291,15 +289,19 @@ class BaseStatusBar native ui
 	native double drawClip[4];		// defines a clipping rectangle (not used yet)
 	native bool fullscreenOffsets;	// current screen is displayed with fullscreen behavior.
 	
+	private HUDFont mSmallFont;
 	
-	native void SetSize(int height, int vwidth, int vheight);
+	
+	native void SetSize(int height, int vwidth, int vheight, int hwidth = -1, int hheight = -1);
 	native Vector2 GetHUDScale();
-	native void BeginStatusBar(int resW, int resH, int relTop, bool completeborder = false, bool forceScaled = false);
-	native void BeginHUD(int resW, int resH, double Alpha, bool forcescaled = false);
+	native void BeginStatusBar(bool forceScaled = false, int resW = -1, int resH = -1, int rel = -1);
+	native void BeginHUD(double Alpha = 1., bool forcescaled = false, int resW = -1, int resH = -1);
 	
-	virtual void Init() {}
+	virtual void Init() 
+	{
+		mSmallFont = HUDFont.Create("SmallFont");
+	}
 
-	native virtual void SetScaled(bool scale, bool force = false);
 	native virtual void Tick ();
 	native virtual void Draw (int state, double TicFrac);
 	native virtual void ScreenSizeChanged ();
@@ -313,7 +315,6 @@ class BaseStatusBar native ui
 	virtual void ShowPop (int popnum) { ShowLog = (popnum == POP_Log && !ShowLog); }
 	virtual bool MustDrawLog(int state) { return true; }
 	
-	native void RefreshBackground () const;
 	native TextureID GetMugshot(int accuracy, int stateflags=MugShot.STANDARD, String default_face = "STF");
 	
 	// These functions are kept native solely for performance reasons. They get called repeatedly and can drag down performance easily if they get too slow.
@@ -324,7 +325,8 @@ class BaseStatusBar native ui
 	native void DrawString(HUDFont font, String string, Vector2 pos, int flags = 0, int translation = Font.CR_UNTRANSLATED, double Alpha = 1., int wrapwidth = -1, int linespacing = 4);
 	native void Fill(Color col, double x, double y, double w, double h, int flags = 0);
 	native static String FormatNumber(int number, int minsize = 0, int maxsize = 0, int format = 0, String prefix = "");
-	
+	native double, double, double, double StatusbarToRealCoords(double x, double y=0, double w=0, double h=0);
+	native int GetTopOfStatusBar();
 	
 	//============================================================================
 	//
@@ -381,6 +383,20 @@ class BaseStatusBar native ui
 		return icon, scale;
 	}
 	
+	//============================================================================
+	//
+	// Returns how much the status bar's graphics extend into the view
+	// Used for automap text positioning
+	// The parameter specifies how much of the status bar area will be covered
+	// by the element requesting this information.
+	//
+	//============================================================================
+	
+	virtual int GetProtrusion(double scaleratio) const
+	{
+		return 0;
+	}
+
 	//============================================================================
 	//
 	// Convenience functions to retrieve item tags
@@ -410,10 +426,10 @@ class BaseStatusBar native ui
 	}	
 
 	// These cannot be done in ZScript.
-	native String GetGlobalACSString(int index);
-	native String GetGlobalACSArrayString(int arrayno, int index);
-	native int GetGlobalACSValue(int index);
-	native int GetGlobalACSArrayValue(int arrayno, int index);
+	native static String GetGlobalACSString(int index);
+	native static String GetGlobalACSArrayString(int arrayno, int index);
+	native static int GetGlobalACSValue(int index);
+	native static int GetGlobalACSArrayValue(int arrayno, int index);
 	
 	//============================================================================
 	//
@@ -658,6 +674,125 @@ class BaseStatusBar native ui
 		return it != null && it.Amount >= amount;
 	}
 
+	//============================================================================
+	//
+	// mypos cheat
+	//
+	//============================================================================
+
+	virtual void DrawMyPos()
+	{
+		int height = SmallFont.GetHeight();
+		int i;
+
+		int vwidth;
+		int vheight;
+		int xpos;
+		int y;
+		let scalevec = GetHUDScale();
+		int scale = int(scalevec.X);
+
+		vwidth = screen.GetWidth() / scale;
+		vheight = screen.GetHeight() / scale;
+		xpos = vwidth - SmallFont.StringWidth("X: -00000")-6;
+		y = GetTopOfStatusBar() / (3*scale) - height;
+
+		Vector3 pos = CPlayer.mo.Pos;
+		
+		for (i = 0; i < 3; y += height, ++i)
+		{
+			double v = i == 0? pos.X : i == 1? pos.Y : pos.Z;
+			String line = String.Format("%c: %d", int("X") + i, v);
+			screen.DrawText (SmallFont, Font.CR_GREEN, xpos, y, line, DTA_KeepRatio, true,
+				DTA_VirtualWidth, vwidth, DTA_VirtualHeight, vheight);
+		}
+	}
+	
+	//============================================================================
+	//
+	// automap HUD common drawer
+	// This is not called directly to give a status bar the opportunity to
+	// change the text colors. If you want to do something different,
+	// override DrawAutomap directly.
+	//
+	//============================================================================
+
+	protected void DoDrawAutomapHUD(int crdefault, int highlight)
+	{
+		let scale = GetHUDScale();
+		double textdist = 8. / scale.Y;
+		int height = SmallFont.GetHeight();
+		String printtext;
+		int SCREENWIDTH = screen.GetWidth();
+
+		BeginHUD();
+		
+		// Draw timer
+		let y = textdist;
+		let width = SmallFont.StringWidth("00:00:00");
+		if (am_showtime)
+		{
+			printtext = level.TimeFormatted();
+			DrawString(mSmallFont, level.TimeFormatted(), (-textdist-width, y), 0, crdefault);
+			y += height;
+		}
+		if (am_showtotaltime)
+		{
+			DrawString(mSmallFont, level.TimeFormatted(true), (-textdist-width, y), 0, crdefault);
+		}
+
+		if (!deathmatch)
+		{
+			y = textdist;
+
+			// Draw monster count
+			if (am_showmonsters)
+			{
+				DrawString(mSmallFont, String.Format("%s\34%c %d/%d", Stringtable.Localize("$AM_MONSTERS"), crdefault+65, level.killed_monsters, level.total_monsters), (textdist, y), 0, highlight);
+				y += height;
+			}
+
+			// Draw secret count
+			if (am_showsecrets)
+			{
+				DrawString(mSmallFont, String.Format("%s\34%c %d/%d", Stringtable.Localize("$AM_SECRETS"), crdefault+65, level.found_secrets, level.total_secrets), (textdist, y), 0, highlight);
+				y += height;
+			}
+
+			// Draw item count
+			if (am_showitems)
+			{
+				DrawString(mSmallFont, String.Format("%s\34%c %d/%d", Stringtable.Localize("$AM_ITEMS"), crdefault+65, level.found_items, level.total_items), (1, y), 0, highlight);
+			}
+		}
+
+		String mapname = level.FormatMapName(crdefault);
+		BrokenLines lines = SmallFont.BreakLines(mapname, SCREENWIDTH / scale.X);
+		int numlines = lines.Count();
+		int finalwidth = SmallFont.StringWidth(lines.StringAt(numlines-1)) * scale.X;
+		
+		// calculate the top of the statusbar including any protrusion and transform it from status bar to screen space.
+		double tmp, hres;
+		[tmp, tmp, hres] = StatusbarToRealCoords(0, 0, HorizontalResolution);
+		int protrusion = GetProtrusion(finalwidth / hres);
+		[tmp, tmp, tmp, hres] = StatusbarToRealCoords(0, 0, 0, protrusion);
+	
+
+		// transform the top of the status bar position from screen to HUD space (a direct transformation from status bar to HUD space does not exist.)
+		y = (GetTopOfStatusBar() - hres) / scale.Y - height * numlines;
+
+		// Draw the texts centered above the status bar.
+		for(int i = 0; i < numlines; i++)
+		{
+			DrawString(mSmallFont, lines.StringAt(i), (0, y), DI_TEXT_ALIGN_CENTER|DI_SCREEN_HCENTER|DI_SCREEN_TOP, highlight);
+			y += height;
+		}
+	}
+	
+	virtual void DrawAutomapHUD(double ticFrac)
+	{
+		DoDrawAutomapHUD(Font.CR_GREY, Font.CR_YELLOW);
+	}
 	
 	//---------------------------------------------------------------------------
 	//
diff --git a/wadsrc/static/zscript/statusbar/strife_sbar.txt b/wadsrc/static/zscript/statusbar/strife_sbar.txt
index 148b1e2371..8a7717e99e 100644
--- a/wadsrc/static/zscript/statusbar/strife_sbar.txt
+++ b/wadsrc/static/zscript/statusbar/strife_sbar.txt
@@ -74,20 +74,25 @@ class StrifeStatusBar : BaseStatusBar
 		Reset ();
 	}
 
+	override int GetProtrusion(double scaleratio) const
+	{
+		return 10;
+	}
+
 	override void Draw (int state, double TicFrac)
 	{
 		Super.Draw (state, TicFrac);
 
 		if (state == HUD_StatusBar)
 		{
-			BeginStatusBar(320, 200, 32);
+			BeginStatusBar();
 			DrawMainBar (TicFrac);
 		}
 		else
 		{
 			if (state == HUD_Fullscreen)
 			{
-				BeginHUD(320, 200, 1., false);
+				BeginHUD();
 				DrawFullScreenStuff ();
 			}
 
@@ -95,7 +100,7 @@ class StrifeStatusBar : BaseStatusBar
 			if (CurrentPop != POP_None && PopHeight < 0)
 			{
 				// This uses direct low level draw commands and would otherwise require calling
-				// BeginStatusBar(320, 200, false, true);
+				// BeginStatusBar(true);
 				DrawPopScreen (screen.GetHeight(), TicFrac);
 			}
 		}
@@ -259,7 +264,9 @@ class StrifeStatusBar : BaseStatusBar
 		// Pop screen (log, keys, and status)
 		if (CurrentPop != POP_None && PopHeight < 0)
 		{
-			DrawPopScreen (Scaled ? (ST_Y - 8) * screen.GetHeight() / 200 : ST_Y - 8, TicFrac);
+			double tmp, h;
+			[tmp, tmp, h] = StatusbarToRealCoords(0, 0, 8);
+			DrawPopScreen (GetTopOfStatusBar() - h, TicFrac);
 		}
 
 		DrawImage("INVBACK", (0, 168), DI_ITEM_OFFSETS);