diff --git a/src/b_bot.c b/src/b_bot.c
index 51dc4d2f21c9f4db04a4304c7604290b06e622a4..f83aaa34ce7d9e175374218d4ca2312918809e0f 100644
--- a/src/b_bot.c
+++ b/src/b_bot.c
@@ -127,13 +127,17 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 	}
 
 	// Orientation
-	if ((bot->pflags & (PF_SPINNING|PF_STARTDASH)) || flymode == 2)
+	if (bot->pflags & (PF_SPINNING|PF_STARTDASH))
 	{
-		cmd->angleturn = (sonic->angle - tails->angle) >> FRACBITS;
+		cmd->angleturn = (sonic->angle - tails->angle) >> 16; // NOT FRACBITS DAMNIT
+	}
+	else if (flymode == 2)
+	{
+		cmd->angleturn = sonic->player->cmd.angleturn - (tails->angle >> 16);
 	}
 	else
 	{
-		cmd->angleturn = (ang - tails->angle) >> FRACBITS;
+		cmd->angleturn = (ang - tails->angle) >> 16; // NOT FRACBITS DAMNIT
 	}
 
 	// ********
@@ -222,7 +226,7 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 			{
 				if (dist < followthres && dist > touchdist) // Do positioning
 				{
-					cmd->angleturn = (ang - tails->angle) >> FRACBITS;
+					cmd->angleturn = (ang - tails->angle) >> 16; // NOT FRACBITS DAMNIT
 					cmd->forwardmove = 50;
 					spinmode = true;
 				}
@@ -230,7 +234,7 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 				{
 					if (!bmom && (!(bot->pflags & PF_SPINNING) || (bot->dashspeed && bot->pflags & PF_SPINNING)))
 					{
-						cmd->angleturn = (sonic->angle - tails->angle) >> FRACBITS;
+						cmd->angleturn = (sonic->angle - tails->angle) >> 16; // NOT FRACBITS DAMNIT
 						spin = true;
 					}
 					spinmode = true;
@@ -244,7 +248,7 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 				if (bot->pflags & PF_SPINNING || !spin_last)
 				{
 					spin = true;
-					cmd->angleturn = (sonic->angle - tails->angle) >> FRACBITS;
+					cmd->angleturn = (sonic->angle - tails->angle) >> 16; // NOT FRACBITS DAMNIT
 					cmd->forwardmove = MAXPLMOVE;
 					spinmode = true;
 				}
@@ -290,7 +294,7 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
 		else if (dist < followmin)
 		{
 			// Copy inputs
-			cmd->angleturn = (sonic->angle - tails->angle) >> FRACBITS;
+			cmd->angleturn = (sonic->angle - tails->angle) >> 16; // NOT FRACBITS DAMNIT
 			bot->drawangle = ang;
 			cmd->forwardmove = 8 * pcmd->forwardmove / 10;
 			cmd->sidemove = 8 * pcmd->sidemove / 10;
@@ -358,7 +362,7 @@ void B_BuildTiccmd(player_t *player, ticcmd_t *cmd)
 	}
 
 	// Bot AI isn't programmed in analog.
-	CV_SetValue(&cv_analog2, false);
+	CV_SetValue(&cv_analog[1], false);
 
 #ifdef HAVE_BLUA
 	// Let Lua scripts build ticcmds
diff --git a/src/command.c b/src/command.c
index 2893106dd7ca6690bcf0ac863165e2ea67b76f02..0afc0711815dd551e8fb6bcc9a9e53e2beccb69f 100644
--- a/src/command.c
+++ b/src/command.c
@@ -2114,7 +2114,12 @@ void CV_SaveVariables(FILE *f)
 
 			// Silly hack for Min/Max vars
 			if (!strcmp(cvar->string, "MAX") || !strcmp(cvar->string, "MIN"))
-				sprintf(stringtowrite, "%d", cvar->value);
+			{
+				if (cvar->flags & CV_FLOAT)
+					sprintf(stringtowrite, "%f", FIXED_TO_FLOAT(cvar->value));
+				else
+					sprintf(stringtowrite, "%d", cvar->value);
+			}
 			else
 				strcpy(stringtowrite, cvar->string);
 
diff --git a/src/command.h b/src/command.h
index 42bd6eb84fccc3b99c903e54ae8ca20560d914e0..e7371b2fa758219d6a1a7152ebef1cf4c0e5e7fa 100644
--- a/src/command.h
+++ b/src/command.h
@@ -20,6 +20,15 @@
 // Command buffer & command execution
 //===================================
 
+/* Lua command registration flags. */
+enum
+{
+	COM_ADMIN       = 1,
+	COM_SPLITSCREEN = 2,
+	COM_LOCAL       = 4,
+};
+
+/* Command buffer flags. */
 enum
 {
 	COM_SAFE = 1,
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 98ff3a6f8ed95785f76d0a53e6883d0797fe5167..46f404f9ad3d89e434886aa3306253080c4fd0c9 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -4651,9 +4651,9 @@ static void Local_Maketic(INT32 realtics)
 	                   // and G_MapEventsToControls
 	if (!dedicated) rendergametic = gametic;
 	// translate inputs (keyboard/mouse/joystick) into game controls
-	G_BuildTiccmd(&localcmds, realtics);
+	G_BuildTiccmd(&localcmds, realtics, 1);
 	if (splitscreen || botingame)
-		G_BuildTiccmd2(&localcmds2, realtics);
+		G_BuildTiccmd(&localcmds2, realtics, 2);
 
 	localcmds.angleturn |= TICCMD_RECEIVED;
 }
diff --git a/src/d_main.c b/src/d_main.c
index 432dfd34f5043cc517cc295f2c14db9a195b8be9..92492e74715aa9e1717387305d4fa683c793b763 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -832,7 +832,7 @@ void D_StartTitle(void)
 		CV_SetValue(&cv_usemouse, tutorialusemouse);
 		CV_SetValue(&cv_alwaysfreelook, tutorialfreelook);
 		CV_SetValue(&cv_mousemove, tutorialmousemove);
-		CV_SetValue(&cv_analog, tutorialanalog);
+		CV_SetValue(&cv_analog[0], tutorialanalog);
 		M_StartMessage("Do you want to \x82save the recommended \x82movement controls?\x80\n\nPress 'Y' or 'Enter' to confirm\nPress 'N' or any key to keep \nyour current controls",
 			M_TutorialSaveControlResponse, MM_YESNO);
 	}
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 19e1f1dad915e08d3559502f45b99f5851e48653..29e68143c49d1d363d9aa9fee5b3d2af276e399f 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -781,6 +781,10 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_fireaxis2);
 	CV_RegisterVar(&cv_firenaxis);
 	CV_RegisterVar(&cv_firenaxis2);
+	CV_RegisterVar(&cv_deadzone);
+	CV_RegisterVar(&cv_deadzone2);
+	CV_RegisterVar(&cv_digitaldeadzone);
+	CV_RegisterVar(&cv_digitaldeadzone2);
 
 	// filesrch.c
 	CV_RegisterVar(&cv_addons_option);
@@ -819,20 +823,34 @@ void D_RegisterClientCommands(void)
 	CV_RegisterVar(&cv_joyscale2);
 
 	// Analog Control
-	CV_RegisterVar(&cv_analog);
-	CV_RegisterVar(&cv_analog2);
-	CV_RegisterVar(&cv_useranalog);
-	CV_RegisterVar(&cv_useranalog2);
+	CV_RegisterVar(&cv_analog[0]);
+	CV_RegisterVar(&cv_analog[1]);
+	CV_RegisterVar(&cv_useranalog[0]);
+	CV_RegisterVar(&cv_useranalog[1]);
 
 	// deez New User eXperiences
-	CV_RegisterVar(&cv_directionchar);
-	CV_RegisterVar(&cv_directionchar2);
+	CV_RegisterVar(&cv_directionchar[0]);
+	CV_RegisterVar(&cv_directionchar[1]);
 	CV_RegisterVar(&cv_autobrake);
 	CV_RegisterVar(&cv_autobrake2);
 
-	// Ported from kart
-	CV_RegisterVar(&cv_deadzone);
-	CV_RegisterVar(&cv_deadzone2);
+	// hi here's some new controls
+	CV_RegisterVar(&cv_cam_shiftfacing[0]);
+	CV_RegisterVar(&cv_cam_shiftfacing[1]);
+	CV_RegisterVar(&cv_cam_turnfacing[0]);
+	CV_RegisterVar(&cv_cam_turnfacing[1]);
+	CV_RegisterVar(&cv_cam_turnfacingability[0]);
+	CV_RegisterVar(&cv_cam_turnfacingability[1]);
+	CV_RegisterVar(&cv_cam_turnfacingspindash[0]);
+	CV_RegisterVar(&cv_cam_turnfacingspindash[1]);
+	CV_RegisterVar(&cv_cam_turnfacinginput[0]);
+	CV_RegisterVar(&cv_cam_turnfacinginput[1]);
+	CV_RegisterVar(&cv_cam_centertoggle[0]);
+	CV_RegisterVar(&cv_cam_centertoggle[1]);
+	CV_RegisterVar(&cv_cam_lockedinput[0]);
+	CV_RegisterVar(&cv_cam_lockedinput[1]);
+	CV_RegisterVar(&cv_cam_lockonboss[0]);
+	CV_RegisterVar(&cv_cam_lockonboss[1]);
 
 	// s_sound.c
 	CV_RegisterVar(&cv_soundvolume);
@@ -1501,9 +1519,9 @@ void SendWeaponPref(void)
 	buf[0] = 0;
 	if (cv_flipcam.value)
 		buf[0] |= 1;
-	if (cv_analog.value)
+	if (cv_analog[0].value && cv_directionchar[0].value != 2)
 		buf[0] |= 2;
-	if (cv_directionchar.value)
+	if (cv_directionchar[0].value == 1)
 		buf[0] |= 4;
 	if (cv_autobrake.value)
 		buf[0] |= 8;
@@ -1517,9 +1535,9 @@ void SendWeaponPref2(void)
 	buf[0] = 0;
 	if (cv_flipcam2.value)
 		buf[0] |= 1;
-	if (cv_analog2.value)
+	if (cv_analog[1].value && cv_directionchar[1].value != 2)
 		buf[0] |= 2;
-	if (cv_directionchar2.value)
+	if (cv_directionchar[1].value == 1)
 		buf[0] |= 4;
 	if (cv_autobrake2.value)
 		buf[0] |= 8;
@@ -1984,7 +2002,7 @@ static void Command_Map_f(void)
 		CV_SetValue(&cv_usemouse, tutorialusemouse);
 		CV_SetValue(&cv_alwaysfreelook, tutorialfreelook);
 		CV_SetValue(&cv_mousemove, tutorialmousemove);
-		CV_SetValue(&cv_analog, tutorialanalog);
+		CV_SetValue(&cv_analog[0], tutorialanalog);
 	}
 	tutorialmode = false; // warping takes us out of tutorial mode
 
diff --git a/src/dehacked.c b/src/dehacked.c
index 6f39f754561ffe31b8d80302e4a9056cdbd7c872..c8e3a79017b449221774e8c1c1999f56295ca849 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -9462,6 +9462,10 @@ struct {
 	{"SF_MULTIABILITY",SF_MULTIABILITY},
 	{"SF_NONIGHTSROTATION",SF_NONIGHTSROTATION},
 
+	// Dashmode constants
+	{"DASHMODE_THRESHOLD",DASHMODE_THRESHOLD},
+	{"DASHMODE_MAX",DASHMODE_MAX},
+
 	// Character abilities!
 	// Primary
 	{"CA_NONE",CA_NONE}, // now slot 0!
@@ -9738,6 +9742,11 @@ struct {
 	{"BT_CUSTOM2",BT_CUSTOM2}, // Lua customizable
 	{"BT_CUSTOM3",BT_CUSTOM3}, // Lua customizable
 
+	// Lua command registration flags
+	{"COM_ADMIN",COM_ADMIN},
+	{"COM_SPLITSCREEN",COM_SPLITSCREEN},
+	{"COM_LOCAL",COM_LOCAL},
+
 	// cvflags_t
 	{"CV_SAVE",CV_SAVE},
 	{"CV_CALL",CV_CALL},
diff --git a/src/doomstat.h b/src/doomstat.h
index 6d1d6eb3665b95eceb0c58683086722d0f98b92f..7e961677fb3221d51ca6fa3e71a4d967836b993d 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -140,7 +140,7 @@ extern INT32 tutorialgcs; // which control scheme is loaded?
 extern INT32 tutorialusemouse; // store cv_usemouse user value
 extern INT32 tutorialfreelook; // store cv_alwaysfreelook user value
 extern INT32 tutorialmousemove; // store cv_mousemove user value
-extern INT32 tutorialanalog; // store cv_analog user value
+extern INT32 tutorialanalog; // store cv_analog[0] user value
 
 extern boolean looptitle;
 
diff --git a/src/f_finale.c b/src/f_finale.c
index 317a57f3d385efdaa1795788bbf96390588b9ddf..bbee48bdc2a52d69033c92f285f17c0338cca45b 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -3790,7 +3790,7 @@ void F_ContinueDrawer(void)
 								sprdef = &contskins[n]->sprites[cont_spr2[n][0]];\
 								sprframe = &sprdef->spriteframes[cont_spr2[n][1]];\
 								patch = W_CachePatchNum(sprframe->lumppat[cont_spr2[n][2]], PU_PATCH);\
-								V_DrawFixedPatch((dx), (dy), FRACUNIT, (sprframe->flip & (1<<cont_spr2[n][2])) ? V_FLIP : 0, patch, contcolormaps[n]);\
+								V_DrawFixedPatch((dx), (dy), contskins[n]->highresscale, (sprframe->flip & (1<<cont_spr2[n][2])) ? V_FLIP : 0, patch, contcolormaps[n]);\
 							}
 
 	if (offsy < 0)
diff --git a/src/f_wipe.c b/src/f_wipe.c
index a83d104f24ed09696cdf10ea33885b64242e0191..a350e0f36c646169d31e373ebd6b46f4c49cc466 100644
--- a/src/f_wipe.c
+++ b/src/f_wipe.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 2013-2016 by Matthew "Inuyasha" Walsh.
+// Copyright (C) 2013-2016 by Matthew "Kaito Sinclaire" Walsh.
 // Copyright (C) 1999-2019 by Sonic Team Junior.
 //
 // This program is free software distributed under the
diff --git a/src/g_game.c b/src/g_game.c
index b4f5d4c64583d0177b1db25dd81662232c53f230..f7778df8f25304906e36ab7a30083d36556ccab5 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -139,7 +139,7 @@ INT32 tutorialgcs = gcs_custom; // which control scheme is loaded?
 INT32 tutorialusemouse = 0; // store cv_usemouse user value
 INT32 tutorialfreelook = 0; // store cv_alwaysfreelook user value
 INT32 tutorialmousemove = 0; // store cv_mousemove user value
-INT32 tutorialanalog = 0; // store cv_analog user value
+INT32 tutorialanalog = 0; // store cv_analog[0] user value
 
 boolean looptitle = false;
 
@@ -388,22 +388,71 @@ consvar_t cv_mousemove2 = {"mousemove2", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL
 
 // previously "analog", "analog2", "useranalog", and "useranalog2", invalidating 2.1-era copies of config.cfg
 // changed because it'd be nice to see people try out our actually good controls with gamepads now autobrake exists
-consvar_t cv_analog = {"sessionanalog", "Off", CV_CALL|CV_NOSHOWHELP, CV_OnOff, Analog_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_analog2 = {"sessionanalog2", "Off", CV_CALL|CV_NOSHOWHELP, CV_OnOff, Analog2_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_useranalog = {"configanalog", "Off", CV_SAVE|CV_CALL|CV_NOSHOWHELP, CV_OnOff, UserAnalog_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_useranalog2 = {"configanalog2", "Off", CV_SAVE|CV_CALL|CV_NOSHOWHELP, CV_OnOff, UserAnalog2_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_analog[2] = {
+	{"sessionanalog", "Off", CV_CALL|CV_NOSHOWHELP, CV_OnOff, Analog_OnChange, 0, NULL, NULL, 0, 0, NULL},
+	{"sessionanalog2", "Off", CV_CALL|CV_NOSHOWHELP, CV_OnOff, Analog2_OnChange, 0, NULL, NULL, 0, 0, NULL}
+};
+consvar_t cv_useranalog[2] = {
+	{"configanalog", "Off", CV_SAVE|CV_CALL|CV_NOSHOWHELP, CV_OnOff, UserAnalog_OnChange, 0, NULL, NULL, 0, 0, NULL},
+	{"configanalog2", "Off", CV_SAVE|CV_CALL|CV_NOSHOWHELP, CV_OnOff, UserAnalog2_OnChange, 0, NULL, NULL, 0, 0, NULL}
+};
 
 // deez New User eXperiences
-static CV_PossibleValue_t directionchar_cons_t[] = {{0, "Camera"}, {1, "Movement"}, {0, NULL}};
-consvar_t cv_directionchar = {"directionchar", "Movement", CV_SAVE|CV_CALL, directionchar_cons_t, DirectionChar_OnChange, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_directionchar2 = {"directionchar2", "Movement", CV_SAVE|CV_CALL, directionchar_cons_t, DirectionChar2_OnChange, 0, NULL, NULL, 0, 0, NULL};
+static CV_PossibleValue_t directionchar_cons_t[] = {{0, "Camera"}, {1, "Movement"}, {2, "Simple Locked"}, {0, NULL}};
+consvar_t cv_directionchar[2] = {
+	{"directionchar", "Movement", CV_SAVE|CV_CALL, directionchar_cons_t, DirectionChar_OnChange, 0, NULL, NULL, 0, 0, NULL},
+	{"directionchar2", "Movement", CV_SAVE|CV_CALL, directionchar_cons_t, DirectionChar2_OnChange, 0, NULL, NULL, 0, 0, NULL}
+};
 consvar_t cv_autobrake = {"autobrake", "On", CV_SAVE|CV_CALL, CV_OnOff, AutoBrake_OnChange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_autobrake2 = {"autobrake2", "On", CV_SAVE|CV_CALL, CV_OnOff, AutoBrake2_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
-static CV_PossibleValue_t deadzone_cons_t[] = {{0, "MIN"}, {FRACUNIT, "MAX"}, {0, NULL}};
-consvar_t cv_deadzone = {"deadzone", "0.25", CV_FLOAT|CV_SAVE, deadzone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_deadzone2 = {"deadzone2", "0.25", CV_FLOAT|CV_SAVE, deadzone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+// hi here's some new controls
+static CV_PossibleValue_t zerotoone_cons_t[] = {{0, "MIN"}, {FRACUNIT, "MAX"}, {0, NULL}};
+consvar_t cv_cam_shiftfacing[2] = {
+	{"cam_shiftfacingchar", "0.33", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+	{"cam2_shiftfacingchar", "0.33", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+};
+consvar_t cv_cam_turnfacing[2] = {
+	{"cam_turnfacingchar", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+	{"cam2_turnfacingchar", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+};
+consvar_t cv_cam_turnfacingability[2] = {
+	{"cam_turnfacingability", "0.125", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+	{"cam2_turnfacingability", "0.125", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+};
+consvar_t cv_cam_turnfacingspindash[2] = {
+	{"cam_turnfacingspindash", "0.5", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+	{"cam2_turnfacingspindash", "0.5", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+};
+consvar_t cv_cam_turnfacinginput[2] = {
+	{"cam_turnfacinginput", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+	{"cam2_turnfacinginput", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+};
 
+static CV_PossibleValue_t centertoggle_cons_t[] = {{0, "Hold"}, {1, "Toggle"}, {2, "Sticky Hold"}, {0, NULL}};
+consvar_t cv_cam_centertoggle[2] = {
+	{"cam_centertoggle", "Hold", CV_SAVE, centertoggle_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+	{"cam2_centertoggle", "Hold", CV_SAVE, centertoggle_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+};
+
+static CV_PossibleValue_t lockedinput_cons_t[] = {{0, "Strafe"}, {1, "Turn"}, {0, NULL}};
+consvar_t cv_cam_lockedinput[2] = {
+	{"cam_lockedinput", "Strafe", CV_SAVE, lockedinput_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+	{"cam2_lockedinput", "Strafe", CV_SAVE, lockedinput_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+};
+
+static CV_PossibleValue_t lockedassist_cons_t[] = {
+	{0, "Off"},
+	{LOCK_BOSS, "Bosses"},
+	{LOCK_BOSS|LOCK_ENEMY, "Enemies"},
+	{LOCK_BOSS|LOCK_INTERESTS, "Interests"},
+	{LOCK_BOSS|LOCK_ENEMY|LOCK_INTERESTS, "Full"},
+	{0, NULL}
+};
+consvar_t cv_cam_lockonboss[2] = {
+	{"cam_lockaimassist", "Bosses", CV_SAVE, lockedassist_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+	{"cam2_lockaimassist", "Bosses", CV_SAVE, lockedassist_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL},
+};
 
 typedef enum
 {
@@ -412,6 +461,9 @@ typedef enum
 	AXISMOVE,
 	AXISLOOK,
 	AXISSTRAFE,
+
+	AXISDIGITAL, // axes below this use digital deadzone
+
 	AXISJUMP,
 	AXISSPIN,
 	AXISFIRE,
@@ -426,6 +478,8 @@ consvar_t cv_jumpaxis = {"joyaxis_jump", "None", CV_SAVE, joyaxis_cons_t, NULL,
 consvar_t cv_spinaxis = {"joyaxis_spin", "None", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_fireaxis = {"joyaxis_fire", "Z-Axis-", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_firenaxis = {"joyaxis_firenormal", "Z-Axis", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_deadzone = {"joy_deadzone", "0.125", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_digitaldeadzone = {"joy_digdeadzone", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 consvar_t cv_turnaxis2 = {"joyaxis2_turn", "X-Rudder", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_moveaxis2 = {"joyaxis2_move", "Y-Axis", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -435,6 +489,8 @@ consvar_t cv_jumpaxis2 = {"joyaxis2_jump", "None", CV_SAVE, joyaxis_cons_t, NULL
 consvar_t cv_spinaxis2 = {"joyaxis2_spin", "None", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_fireaxis2 = {"joyaxis2_fire", "Z-Axis-", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_firenaxis2 = {"joyaxis2_firenormal", "Z-Axis", CV_SAVE, joyaxis_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_deadzone2 = {"joy_deadzone2", "0.125", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_digitaldeadzone2 = {"joy_digdeadzone2", "0.25", CV_FLOAT|CV_SAVE, zerotoone_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 #ifdef SEENAMES
 player_t *seenplayer; // player we're aiming at right now
@@ -892,6 +948,14 @@ static INT32 JoyAxis(axis_input_e axissel)
 		retaxis = -JOYAXISRANGE;
 	if (retaxis > (+JOYAXISRANGE))
 		retaxis = +JOYAXISRANGE;
+
+	if (!Joystick.bGamepadStyle && axissel > AXISDIGITAL)
+	{
+		const INT32 jdeadzone = ((JOYAXISRANGE-1) * cv_digitaldeadzone.value) >> FRACBITS;
+		if (-jdeadzone < retaxis && retaxis < jdeadzone)
+			return 0;
+	}
+
 	if (flp) retaxis = -retaxis; //flip it around
 	return retaxis;
 }
@@ -959,10 +1023,21 @@ static INT32 Joy2Axis(axis_input_e axissel)
 		retaxis = -JOYAXISRANGE;
 	if (retaxis > (+JOYAXISRANGE))
 		retaxis = +JOYAXISRANGE;
+
+	if (!Joystick2.bGamepadStyle && axissel > AXISDIGITAL)
+	{
+		const INT32 jdeadzone = ((JOYAXISRANGE-1) * cv_digitaldeadzone2.value) >> FRACBITS;
+		if (-jdeadzone < retaxis && retaxis < jdeadzone)
+			return 0;
+	}
+
 	if (flp) retaxis = -retaxis; //flip it around
 	return retaxis;
 }
 
+
+#define PlayerJoyAxis(p, ax) ((p) == 1 ? JoyAxis(ax) : Joy2Axis(ax))
+
 // Take a magnitude of two axes, and adjust it to take out the deadzone
 // Will return a value between 0 and JOYAXISRANGE
 static INT32 G_BasicDeadZoneCalculation(INT32 magnitude, fixed_t deadZone)
@@ -1029,6 +1104,7 @@ static void G_HandleAxisDeadZone(UINT8 splitnum, joystickvector2_t *joystickvect
 }
 
 
+
 //
 // G_BuildTiccmd
 // Builds a ticcmd from all of the available inputs
@@ -1044,61 +1120,118 @@ static fixed_t forwardmove[2] = {25<<FRACBITS>>16, 50<<FRACBITS>>16};
 static fixed_t sidemove[2] = {25<<FRACBITS>>16, 50<<FRACBITS>>16}; // faster!
 static fixed_t angleturn[3] = {640, 1280, 320}; // + slow turn
 
-void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
+boolean ticcmd_centerviewdown[2]; // For simple controls, lock the camera behind the player
+mobj_t *ticcmd_ztargetfocus[2]; // Locking onto an object?
+void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
 {
 	boolean forcestrafe = false;
 	boolean forcefullinput = false;
 	INT32 tspeed, forward, side, axis, strafeaxis, moveaxis, turnaxis, lookaxis, i;
+
+	joystickvector2_t movejoystickvector, lookjoystickvector;
+
 	const INT32 speed = 1;
 	// these ones used for multiple conditions
 	boolean turnleft, turnright, strafelkey, straferkey, movefkey, movebkey, mouseaiming, analogjoystickmove, gamepadjoystickmove, thisjoyaiming;
-	player_t *player = &players[consoleplayer];
-	camera_t *thiscam = &camera;
-	joystickvector2_t movejoystickvector, lookjoystickvector;
-
-	static INT32 turnheld; // for accelerative turning
-	static boolean keyboard_look; // true if lookup/down using keyboard
-	static boolean resetdown; // don't cam reset every frame
-	static boolean joyaiming; // check the last frame's value if we need to reset the camera
+	boolean strafeisturn; // Simple controls only
+	player_t *player = &players[ssplayer == 2 ? secondarydisplayplayer : consoleplayer];
+	camera_t *thiscam = ((ssplayer == 1 || player->bot == 2) ? &camera : &camera2);
+	angle_t *myangle = (ssplayer == 1 ? &localangle : &localangle2);
+	INT32 *myaiming = (ssplayer == 1 ? &localaiming : &localaiming2);
+
+	angle_t drawangleoffset = (player->powers[pw_carry] == CR_ROLLOUT) ? ANGLE_180 : 0;
+	INT32 chasecam, chasefreelook, alwaysfreelook, usejoystick, invertmouse, mousemove;
+	controlstyle_e controlstyle = G_ControlStyle(ssplayer);
+	INT32 *mx; INT32 *my; INT32 *mly;
+
+	static INT32 turnheld[2]; // for accelerative turning
+	static boolean keyboard_look[2]; // true if lookup/down using keyboard
+	static boolean resetdown[2]; // don't cam reset every frame
+	static boolean joyaiming[2]; // check the last frame's value if we need to reset the camera
+
+	// simple mode vars
+	static boolean zchange[2]; // only switch z targets once per press
+	static fixed_t tta_factor[2] = {FRACUNIT, FRACUNIT}; // disables turn-to-angle when manually turning camera until movement happens
+	boolean centerviewdown = false;
+
+	UINT8 forplayer = ssplayer-1;
+
+	if (ssplayer == 1)
+	{
+		chasecam = cv_chasecam.value;
+		chasefreelook = cv_chasefreelook.value;
+		alwaysfreelook = cv_alwaysfreelook.value;
+		usejoystick = cv_usejoystick.value;
+		invertmouse = cv_invertmouse.value;
+		mousemove = cv_mousemove.value;
+		mx = &mousex;
+		my = &mousey;
+		mly = &mlooky;
+		G_CopyTiccmd(cmd, I_BaseTiccmd(), 1); // empty, or external driver
+	}
+	else
+	{
+		chasecam = cv_chasecam2.value;
+		chasefreelook = cv_chasefreelook2.value;
+		alwaysfreelook = cv_alwaysfreelook2.value;
+		usejoystick = cv_usejoystick2.value;
+		invertmouse = cv_invertmouse2.value;
+		mousemove = cv_mousemove2.value;
+		mx = &mouse2x;
+		my = &mouse2y;
+		mly = &mlook2y;
+		G_CopyTiccmd(cmd, I_BaseTiccmd2(), 1); // empty, or external driver
+	}
 
-	G_CopyTiccmd(cmd, I_BaseTiccmd(), 1); // empty, or external driver
+	strafeisturn = controlstyle == CS_SIMPLE && ticcmd_centerviewdown[forplayer] &&
+		((cv_cam_lockedinput[forplayer].value && !ticcmd_ztargetfocus[forplayer]) || (player->pflags & PF_STARTDASH)) &&
+		!player->climbing && player->powers[pw_carry] != CR_MINECART;
 
 	// why build a ticcmd if we're paused?
 	// Or, for that matter, if we're being reborn.
 	// ...OR if we're blindfolded. No looking into the floor.
 	if (paused || P_AutoPause() || (gamestate == GS_LEVEL && (player->playerstate == PST_REBORN || ((gametyperules & GTR_TAG)
 	&& (leveltime < hidetime * TICRATE) && (player->pflags & PF_TAGIT)))))
-	{
-		cmd->angleturn = (INT16)(localangle >> 16);
-		cmd->aiming = G_ClipAimingPitch(&localaiming);
+	{//@TODO splitscreen player
+		cmd->angleturn = (INT16)(*myangle >> 16);
+		cmd->aiming = G_ClipAimingPitch(myaiming);
 		return;
 	}
 
-	turnright = PLAYER1INPUTDOWN(gc_turnright);
-	turnleft = PLAYER1INPUTDOWN(gc_turnleft);
+	turnright = PLAYERINPUTDOWN(ssplayer, gc_turnright);
+	turnleft = PLAYERINPUTDOWN(ssplayer, gc_turnleft);
 
-	straferkey = PLAYER1INPUTDOWN(gc_straferight);
-	strafelkey = PLAYER1INPUTDOWN(gc_strafeleft);
-	movefkey = PLAYER1INPUTDOWN(gc_forward);
-	movebkey = PLAYER1INPUTDOWN(gc_backward);
+	straferkey = PLAYERINPUTDOWN(ssplayer, gc_straferight);
+	strafelkey = PLAYERINPUTDOWN(ssplayer, gc_strafeleft);
+	movefkey = PLAYERINPUTDOWN(ssplayer, gc_forward);
+	movebkey = PLAYERINPUTDOWN(ssplayer, gc_backward);
 
-	mouseaiming = (PLAYER1INPUTDOWN(gc_mouseaiming)) ^
-		((cv_chasecam.value && !player->spectator) ? cv_chasefreelook.value : cv_alwaysfreelook.value);
-	analogjoystickmove = cv_usejoystick.value && !Joystick.bGamepadStyle;
-	gamepadjoystickmove = cv_usejoystick.value && Joystick.bGamepadStyle;
+	if (strafeisturn)
+	{
+		turnright |= straferkey;
+		turnleft |= strafelkey;
+		straferkey = strafelkey = false;
+	}
 
-	thisjoyaiming = (cv_chasecam.value && !player->spectator) ? cv_chasefreelook.value : cv_alwaysfreelook.value;
+	mouseaiming = (PLAYERINPUTDOWN(ssplayer, gc_mouseaiming)) ^
+		((chasecam && !player->spectator) ? chasefreelook : alwaysfreelook);
+	analogjoystickmove = usejoystick && !Joystick.bGamepadStyle;
+	gamepadjoystickmove = usejoystick && Joystick.bGamepadStyle;
 
-	// Reset the vertical look if we're no longer joyaiming
-	if (!thisjoyaiming && joyaiming)
-		localaiming = 0;
-	joyaiming = thisjoyaiming;
+	thisjoyaiming = (chasecam && !player->spectator) ? chasefreelook : alwaysfreelook;
 
-	turnaxis = JoyAxis(AXISTURN);
-	lookaxis = JoyAxis(AXISLOOK);
+	// Reset the vertical look if we're no longer joyaiming
+	if (!thisjoyaiming && joyaiming[forplayer])
+		*myaiming = 0;
+	joyaiming[forplayer] = thisjoyaiming;
+
+	turnaxis = PlayerJoyAxis(ssplayer, AXISTURN);
+	if (strafeisturn)
+		turnaxis += PlayerJoyAxis(ssplayer, AXISSTRAFE);
+	lookaxis = PlayerJoyAxis(ssplayer, AXISLOOK);
 	lookjoystickvector.xaxis = turnaxis;
 	lookjoystickvector.yaxis = lookaxis;
-	G_HandleAxisDeadZone(0, &lookjoystickvector);
+	G_HandleAxisDeadZone(forplayer, &lookjoystickvector);
 
 	if (gamepadjoystickmove && lookjoystickvector.xaxis != 0)
 	{
@@ -1110,17 +1243,17 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 	// use two stage accelerative turning
 	// on the keyboard and joystick
 	if (turnleft || turnright)
-		turnheld += realtics;
+		turnheld[forplayer] += realtics;
 	else
-		turnheld = 0;
+		turnheld[forplayer] = 0;
 
-	if (turnheld < SLOWTURNTICS)
+	if (turnheld[forplayer] < SLOWTURNTICS)
 		tspeed = 2; // slow turn
 	else
 		tspeed = speed;
 
 	// let movement keys cancel each other out
-	if (cv_analog.value) // Analog
+	if (controlstyle == CS_LMAOGALOG) // Analog
 	{
 		if (turnright)
 			cmd->angleturn = (INT16)(cmd->angleturn - angleturn[tspeed]);
@@ -1149,7 +1282,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 			side += ((lookjoystickvector.xaxis * sidemove[1]) >> 10);
 		}
 	}
-	else if (cv_analog.value) // Analog
+	else if (controlstyle == CS_LMAOGALOG) // Analog
 	{
 		if (turnright)
 			cmd->buttons |= BT_CAMRIGHT;
@@ -1158,7 +1291,8 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 	}
 	else
 	{
-		if (turnright)
+		if (turnright && turnleft);
+		else if (turnright)
 			cmd->angleturn = (INT16)(cmd->angleturn - ((angleturn[tspeed] * cv_cam_turnmultiplier.value)>>FRACBITS));
 		else if (turnleft)
 			cmd->angleturn = (INT16)(cmd->angleturn + ((angleturn[tspeed] * cv_cam_turnmultiplier.value)>>FRACBITS));
@@ -1168,13 +1302,16 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 			// JOYAXISRANGE should be 1023 (divide by 1024)
 			cmd->angleturn = (INT16)(cmd->angleturn - ((((lookjoystickvector.xaxis * angleturn[1]) >> 10) * cv_cam_turnmultiplier.value)>>FRACBITS)); // ANALOG!
 		}
+
+		if (turnright || turnleft || abs(cmd->angleturn) > angleturn[2])
+			tta_factor[forplayer] = 0; // suspend turn to angle
 	}
 
-	strafeaxis = JoyAxis(AXISSTRAFE);
-	moveaxis = JoyAxis(AXISMOVE);
+	strafeaxis = strafeisturn ? 0 : PlayerJoyAxis(ssplayer, AXISSTRAFE);
+	moveaxis = PlayerJoyAxis(ssplayer, AXISMOVE);
 	movejoystickvector.xaxis = strafeaxis;
 	movejoystickvector.yaxis = moveaxis;
-	G_HandleAxisDeadZone(0, &movejoystickvector);
+	G_HandleAxisDeadZone(forplayer, &movejoystickvector);
 
 	if (gamepadjoystickmove && movejoystickvector.xaxis != 0)
 	{
@@ -1192,11 +1329,11 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 	// forward with key or button
 	if (movefkey || (gamepadjoystickmove && movejoystickvector.yaxis < 0)
 		|| ((player->powers[pw_carry] == CR_NIGHTSMODE)
-			&& (PLAYER1INPUTDOWN(gc_lookup) || (gamepadjoystickmove && lookjoystickvector.yaxis > 0))))
+			&& (PLAYERINPUTDOWN(ssplayer, gc_lookup) || (gamepadjoystickmove && lookjoystickvector.yaxis > 0))))
 		forward = forwardmove[speed];
 	if (movebkey || (gamepadjoystickmove && movejoystickvector.yaxis > 0)
 		|| ((player->powers[pw_carry] == CR_NIGHTSMODE)
-			&& (PLAYER1INPUTDOWN(gc_lookdown) || (gamepadjoystickmove && lookjoystickvector.yaxis < 0))))
+			&& (PLAYERINPUTDOWN(ssplayer, gc_lookdown) || (gamepadjoystickmove && lookjoystickvector.yaxis < 0))))
 		forward -= forwardmove[speed];
 
 	if (analogjoystickmove && movejoystickvector.yaxis != 0)
@@ -1209,9 +1346,9 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 	if (strafelkey)
 		side -= sidemove[speed];
 
-	if (PLAYER1INPUTDOWN(gc_weaponnext))
+	if (PLAYERINPUTDOWN(ssplayer, gc_weaponnext))
 		cmd->buttons |= BT_WEAPONNEXT; // Next Weapon
-	if (PLAYER1INPUTDOWN(gc_weaponprev))
+	if (PLAYERINPUTDOWN(ssplayer, gc_weaponprev))
 		cmd->buttons |= BT_WEAPONPREV; // Previous Weapon
 
 #if NUM_WEAPONS > 10
@@ -1220,461 +1357,223 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics)
 	//use the four avaliable bits to determine the weapon.
 	cmd->buttons &= ~BT_WEAPONMASK;
 	for (i = 0; i < NUM_WEAPONS; ++i)
-		if (PLAYER1INPUTDOWN(gc_wepslot1 + i))
+		if (PLAYERINPUTDOWN(ssplayer, gc_wepslot1 + i))
 		{
 			cmd->buttons |= (UINT16)(i + 1);
 			break;
 		}
 
 	// fire with any button/key
-	axis = JoyAxis(AXISFIRE);
-	if (PLAYER1INPUTDOWN(gc_fire) || (cv_usejoystick.value && axis > 0))
+	axis = PlayerJoyAxis(ssplayer, AXISFIRE);
+	if (PLAYERINPUTDOWN(ssplayer, gc_fire) || (usejoystick && axis > 0))
 		cmd->buttons |= BT_ATTACK;
 
 	// fire normal with any button/key
-	axis = JoyAxis(AXISFIRENORMAL);
-	if (PLAYER1INPUTDOWN(gc_firenormal) || (cv_usejoystick.value && axis > 0))
+	axis = PlayerJoyAxis(ssplayer, AXISFIRENORMAL);
+	if (PLAYERINPUTDOWN(ssplayer, gc_firenormal) || (usejoystick && axis > 0))
 		cmd->buttons |= BT_FIRENORMAL;
 
-	if (PLAYER1INPUTDOWN(gc_tossflag))
+	if (PLAYERINPUTDOWN(ssplayer, gc_tossflag))
 		cmd->buttons |= BT_TOSSFLAG;
 
 	// Lua scriptable buttons
-	if (PLAYER1INPUTDOWN(gc_custom1))
+	if (PLAYERINPUTDOWN(ssplayer, gc_custom1))
 		cmd->buttons |= BT_CUSTOM1;
-	if (PLAYER1INPUTDOWN(gc_custom2))
+	if (PLAYERINPUTDOWN(ssplayer, gc_custom2))
 		cmd->buttons |= BT_CUSTOM2;
-	if (PLAYER1INPUTDOWN(gc_custom3))
+	if (PLAYERINPUTDOWN(ssplayer, gc_custom3))
 		cmd->buttons |= BT_CUSTOM3;
 
 	// use with any button/key
-	axis = JoyAxis(AXISSPIN);
-	if (PLAYER1INPUTDOWN(gc_use) || (cv_usejoystick.value && axis > 0))
+	axis = PlayerJoyAxis(ssplayer, AXISSPIN);
+	if (PLAYERINPUTDOWN(ssplayer, gc_use) || (usejoystick && axis > 0))
 		cmd->buttons |= BT_USE;
 
-	if (PLAYER1INPUTDOWN(gc_camreset))
-	{
-		if (camera.chase && !resetdown)
-			P_ResetCamera(&players[displayplayer], &camera);
-		resetdown = true;
-	}
-	else
-		resetdown = false;
-
-	// jump button
-	axis = JoyAxis(AXISJUMP);
-	if (PLAYER1INPUTDOWN(gc_jump) || (cv_usejoystick.value && axis > 0))
-		cmd->buttons |= BT_JUMP;
-
-	// player aiming shit, ahhhh...
+	// Centerview can be a toggle in simple mode!
 	{
-		INT32 player_invert = cv_invertmouse.value ? -1 : 1;
-		INT32 screen_invert =
-			(player->mo && (player->mo->eflags & MFE_VERTICALFLIP)
-			 && (!camera.chase || player->pflags & PF_FLIPCAM)) //because chasecam's not inverted
-			 ? -1 : 1; // set to -1 or 1 to multiply
+		static boolean last_centerviewdown[2], centerviewhold[2]; // detect taps for toggle behavior
+		boolean down = PLAYERINPUTDOWN(ssplayer, gc_centerview);
 
-		// mouse look stuff (mouse look is not the same as mouse aim)
-		if (mouseaiming)
+		if (!(controlstyle == CS_SIMPLE && cv_cam_centertoggle[forplayer].value))
+			centerviewdown = down;
+		else
 		{
-			keyboard_look = false;
-
-			// looking up/down
-			localaiming += (mlooky<<19)*player_invert*screen_invert;
-		}
+			if (down && !last_centerviewdown[forplayer])
+				centerviewhold[forplayer] = !centerviewhold[forplayer];
+			last_centerviewdown[forplayer] = down;
 
-		if (analogjoystickmove && joyaiming && lookjoystickvector.yaxis != 0 && cv_lookaxis.value != 0)
-			localaiming += (lookjoystickvector.yaxis<<16) * screen_invert;
+			if (cv_cam_centertoggle[forplayer].value == 2 && !down && !ticcmd_ztargetfocus[forplayer])
+				centerviewhold[forplayer] = false;
 
-		// spring back if not using keyboard neither mouselookin'
-		if (!keyboard_look && cv_lookaxis.value == 0 && !joyaiming && !mouseaiming)
-			localaiming = 0;
-
-		if (!(player->powers[pw_carry] == CR_NIGHTSMODE))
-		{
-			if (PLAYER1INPUTDOWN(gc_lookup) || (gamepadjoystickmove && lookjoystickvector.yaxis > 0))
-			{
-				localaiming += KB_LOOKSPEED * screen_invert;
-				keyboard_look = true;
-			}
-			else if (PLAYER1INPUTDOWN(gc_lookdown) || (gamepadjoystickmove && lookjoystickvector.yaxis < 0))
-			{
-				localaiming -= KB_LOOKSPEED * screen_invert;
-				keyboard_look = true;
-			}
-			else if (PLAYER1INPUTDOWN(gc_centerview))
-				localaiming = 0;
+			centerviewdown = centerviewhold[forplayer];
 		}
-
-		// accept no mlook for network games
-		if (!cv_allowmlook.value)
-			localaiming = 0;
-
-		cmd->aiming = G_ClipAimingPitch(&localaiming);
 	}
 
-	if (!mouseaiming && cv_mousemove.value)
-		forward += mousey;
-
-	if ((!demoplayback && (player->pflags & PF_SLIDING))) // Analog for mouse
-		side += mousex*2;
-	else if (cv_analog.value)
+	if (centerviewdown)
 	{
-		if (mousex)
+		if (controlstyle == CS_SIMPLE && !ticcmd_centerviewdown[forplayer] && !G_RingSlingerGametype())
 		{
-			if (mousex > 0)
-				cmd->buttons |= BT_CAMRIGHT;
-			else
-				cmd->buttons |= BT_CAMLEFT;
-		}
-	}
-	else
-		cmd->angleturn = (INT16)(cmd->angleturn - (mousex*8));
-
-	mousex = mousey = mlooky = 0;
-
-	if (forward > MAXPLMOVE)
-		forward = MAXPLMOVE;
-	else if (forward < -MAXPLMOVE)
-		forward = -MAXPLMOVE;
-	if (side > MAXPLMOVE)
-		side = MAXPLMOVE;
-	else if (side < -MAXPLMOVE)
-		side = -MAXPLMOVE;
-
-	// No additional acceleration when moving forward/backward and strafing simultaneously.
-	// do this AFTER we cap to MAXPLMOVE so people can't find ways to cheese around this.
-	if (!forcefullinput && forward && side)
-	{
-		angle_t angle = R_PointToAngle2(0, 0, side << FRACBITS, forward << FRACBITS);
-		INT32 maxforward = abs(P_ReturnThrustY(NULL, angle, MAXPLMOVE));
-		INT32 maxside = abs(P_ReturnThrustX(NULL, angle, MAXPLMOVE));
-		forward = max(min(forward, maxforward), -maxforward);
-		side = max(min(side, maxside), -maxside);
-	}
-
-	//Silly hack to make 2d mode *somewhat* playable with no chasecam.
-	if ((twodlevel || (player->mo && player->mo->flags2 & MF2_TWOD)) && !camera.chase)
-	{
-		INT32 temp = forward;
-		forward = side;
-		side = temp;
-	}
-
-	cmd->forwardmove = (SINT8)(cmd->forwardmove + forward);
-	cmd->sidemove = (SINT8)(cmd->sidemove + side);
-
-	if (cv_analog.value) {
-		if (player->awayviewtics)
-			cmd->angleturn = (INT16)(player->awayviewmobj->angle >> 16);
-		else
-			cmd->angleturn = (INT16)(thiscam->angle >> 16);
-	}
-	else
-	{
-		localangle += (cmd->angleturn<<16);
-		cmd->angleturn = (INT16)(localangle >> 16);
-	}
-
-	//Reset away view if a command is given.
-	if ((cmd->forwardmove || cmd->sidemove || cmd->buttons)
-		&& displayplayer != consoleplayer)
-	{
-#ifdef HAVE_BLUA
-		// Call ViewpointSwitch hooks here.
-		// The viewpoint was forcibly changed.
-		LUAh_ViewpointSwitch(player, &players[displayplayer], true);
-#endif
-		displayplayer = consoleplayer;
-	}
-}
-
-// like the g_buildticcmd 1 but using mouse2, gamcontrolbis, ...
-void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
-{
-	boolean forcestrafe = false;
-	boolean forcefullinput = false;
-	INT32 tspeed, forward, side, axis, strafeaxis, moveaxis, turnaxis, lookaxis, i;
-	const INT32 speed = 1;
-	// these ones used for multiple conditions
-	boolean turnleft, turnright, strafelkey, straferkey, movefkey, movebkey, mouseaiming, analogjoystickmove, gamepadjoystickmove, thisjoyaiming;
-	player_t *player = &players[secondarydisplayplayer];
-	camera_t *thiscam = (player->bot == 2 ? &camera : &camera2);
-	joystickvector2_t movejoystickvector, lookjoystickvector;
-
-	static INT32 turnheld; // for accelerative turning
-	static boolean keyboard_look; // true if lookup/down using keyboard
-	static boolean resetdown; // don't cam reset every frame
-	static boolean joyaiming; // check the last frame's value if we need to reset the camera
-
-	G_CopyTiccmd(cmd,  I_BaseTiccmd2(), 1); // empty, or external driver
-
-	//why build a ticcmd if we're paused?
-	// Or, for that matter, if we're being reborn.
-	if (paused || P_AutoPause() || player->playerstate == PST_REBORN)
-	{
-		cmd->angleturn = (INT16)(localangle2 >> 16);
-		cmd->aiming = G_ClipAimingPitch(&localaiming2);
-		return;
-	}
+			CV_SetValue(&cv_directionchar[forplayer], 2);
+			*myangle = player->mo->angle;
+			*myaiming = 0;
 
-	turnright = PLAYER2INPUTDOWN(gc_turnright);
-	turnleft = PLAYER2INPUTDOWN(gc_turnleft);
-
-	straferkey = PLAYER2INPUTDOWN(gc_straferight);
-	strafelkey = PLAYER2INPUTDOWN(gc_strafeleft);
-	movefkey = PLAYER2INPUTDOWN(gc_forward);
-	movebkey = PLAYER2INPUTDOWN(gc_backward);
-
-	mouseaiming = (PLAYER2INPUTDOWN(gc_mouseaiming)) ^
-		((cv_chasecam2.value && !player->spectator) ? cv_chasefreelook2.value : cv_alwaysfreelook2.value);
-	analogjoystickmove = cv_usejoystick2.value && !Joystick2.bGamepadStyle;
-	gamepadjoystickmove = cv_usejoystick2.value && Joystick2.bGamepadStyle;
-
-	thisjoyaiming = (cv_chasecam2.value && !player->spectator) ? cv_chasefreelook2.value : cv_alwaysfreelook2.value;
-
-	// Reset the vertical look if we're no longer joyaiming
-	if (!thisjoyaiming && joyaiming)
-		localaiming2 = 0;
-	joyaiming = thisjoyaiming;
-
-	turnaxis = Joy2Axis(AXISTURN);
-	lookaxis = Joy2Axis(AXISLOOK);
-	lookjoystickvector.xaxis = turnaxis;
-	lookjoystickvector.yaxis = lookaxis;
-	G_HandleAxisDeadZone(1, &lookjoystickvector);
-
-	if (gamepadjoystickmove && lookjoystickvector.xaxis != 0)
-	{
-		turnright = turnright || (lookjoystickvector.xaxis > 0);
-		turnleft = turnleft || (lookjoystickvector.xaxis < 0);
-	}
-	forward = side = 0;
-
-	// use two stage accelerative turning
-	// on the keyboard and joystick
-	if (turnleft || turnright)
-		turnheld += realtics;
-	else
-		turnheld = 0;
-
-	if (turnheld < SLOWTURNTICS)
-		tspeed = 2; // slow turn
-	else
-		tspeed = speed;
+			if (cv_cam_lockonboss[forplayer].value)
+				P_SetTarget(&ticcmd_ztargetfocus[forplayer], P_LookForFocusTarget(player, NULL, 0, cv_cam_lockonboss[forplayer].value));
+		}
 
-	// let movement keys cancel each other out
-	if (cv_analog2.value) // Analog
-	{
-		if (turnright)
-			cmd->angleturn = (INT16)(cmd->angleturn - angleturn[tspeed]);
-		if (turnleft)
-			cmd->angleturn = (INT16)(cmd->angleturn + angleturn[tspeed]);
+		ticcmd_centerviewdown[forplayer] = true;
 	}
-	if (twodlevel
-		|| (player->mo && (player->mo->flags2 & MF2_TWOD))
-		|| (!demoplayback && (player->pflags & PF_SLIDING)))
-			forcefullinput = true;
-	if (twodlevel
-		|| (player->mo && (player->mo->flags2 & MF2_TWOD))
-		|| player->climbing
-		|| (player->powers[pw_carry] == CR_NIGHTSMODE)
-		|| (player->pflags & (PF_SLIDING|PF_FORCESTRAFE))) // Analog
-			forcestrafe = true;
-	if (forcestrafe) // Analog
+	else if (ticcmd_centerviewdown[forplayer])
 	{
-		if (turnright)
-			side += sidemove[speed];
-		if (turnleft)
-			side -= sidemove[speed];
-
-		if (analogjoystickmove && lookjoystickvector.xaxis != 0)
+		if (controlstyle == CS_SIMPLE)
 		{
-			// JOYAXISRANGE is supposed to be 1023 (divide by 1024)
-			side += ((lookjoystickvector.xaxis * sidemove[1]) >> 10);
+			P_SetTarget(&ticcmd_ztargetfocus[forplayer], NULL);
+			CV_SetValue(&cv_directionchar[forplayer], 1);
 		}
-	}
-	else if (cv_analog2.value) // Analog
-	{
-		if (turnright)
-			cmd->buttons |= BT_CAMRIGHT;
-		if (turnleft)
-			cmd->buttons |= BT_CAMLEFT;
-	}
-	else
-	{
-		if (turnright)
-			cmd->angleturn = (INT16)(cmd->angleturn - (((angleturn[tspeed]<<FRACBITS) * cv_cam2_turnmultiplier.value)>>FRACBITS));
-		else if (turnleft)
-			cmd->angleturn = (INT16)(cmd->angleturn + (((angleturn[tspeed]<<FRACBITS) * cv_cam2_turnmultiplier.value)>>FRACBITS));
 
-		if (analogjoystickmove && lookjoystickvector.xaxis != 0)
-		{
-			// JOYAXISRANGE should be 1023 (divide by 1024)
-			cmd->angleturn = (INT16)(cmd->angleturn - ((((lookjoystickvector.xaxis * angleturn[1]) >> 10) * cv_cam2_turnmultiplier.value)>>FRACBITS)); // ANALOG!
-		}
+		ticcmd_centerviewdown[forplayer] = false;
 	}
 
-	strafeaxis = Joy2Axis(AXISSTRAFE);
-	moveaxis = Joy2Axis(AXISMOVE);
-	movejoystickvector.xaxis = strafeaxis;
-	movejoystickvector.yaxis = moveaxis;
-	G_HandleAxisDeadZone(1, &movejoystickvector);
-
-	if (gamepadjoystickmove && movejoystickvector.xaxis != 0)
-	{
-		if (movejoystickvector.xaxis > 0)
-			side += sidemove[speed];
-		else if (movejoystickvector.xaxis < 0)
-			side -= sidemove[speed];
-	}
-	else if (analogjoystickmove && movejoystickvector.xaxis != 0)
+	if (ticcmd_ztargetfocus[forplayer])
 	{
-		// JOYAXISRANGE is supposed to be 1023 (divide by 1024)
-		side += ((movejoystickvector.xaxis * sidemove[1]) >> 10);
-	}
+		if (
+			P_MobjWasRemoved(ticcmd_ztargetfocus[forplayer]) ||
+			!ticcmd_ztargetfocus[forplayer]->health ||
+			(ticcmd_ztargetfocus[forplayer]->flags2 & MF2_FRET) ||
+			(ticcmd_ztargetfocus[forplayer]->type == MT_EGGMOBILE3 && !ticcmd_ztargetfocus[forplayer]->movecount) // Sea Egg is moving around underground and shouldn't be tracked
+		)
+			P_SetTarget(&ticcmd_ztargetfocus[forplayer], NULL);
+		else
+		{
+			mobj_t *newtarget = NULL;
+			if (zchange[forplayer])
+			{
+				if (!turnleft && !turnright && abs(cmd->angleturn) < angleturn[0])
+					zchange[forplayer] = false;
+			}
+			else if (turnleft || cmd->angleturn > angleturn[0])
+			{
+				zchange[forplayer] = true;
+				newtarget = P_LookForFocusTarget(player, ticcmd_ztargetfocus[forplayer], 1, cv_cam_lockonboss[forplayer].value);
+			}
+			else if (turnright || cmd->angleturn < -angleturn[0])
+			{
+				zchange[forplayer] = true;
+				newtarget = P_LookForFocusTarget(player, ticcmd_ztargetfocus[forplayer], -1, cv_cam_lockonboss[forplayer].value);
+			}
 
-	// forward with key or button
-	if (movefkey || (gamepadjoystickmove && movejoystickvector.yaxis < 0)
-		|| ((player->powers[pw_carry] == CR_NIGHTSMODE)
-			&& (PLAYER2INPUTDOWN(gc_lookup) || (gamepadjoystickmove && lookjoystickvector.yaxis > 0))))
-		forward = forwardmove[speed];
-	if (movebkey || (gamepadjoystickmove && movejoystickvector.yaxis > 0)
-		|| ((player->powers[pw_carry] == CR_NIGHTSMODE)
-			&& (PLAYER2INPUTDOWN(gc_lookdown) || (gamepadjoystickmove && lookjoystickvector.yaxis < 0))))
-		forward -= forwardmove[speed];
+			if (newtarget)
+				P_SetTarget(&ticcmd_ztargetfocus[forplayer], newtarget);
 
-	if (analogjoystickmove && movejoystickvector.yaxis != 0)
-		forward -= ((movejoystickvector.yaxis * forwardmove[1]) >> 10); // ANALOG!
+			// I assume this is netgame-safe because gunslinger spawns this for only the local player...... *sweats intensely*
+			newtarget = P_SpawnMobj(ticcmd_ztargetfocus[forplayer]->x, ticcmd_ztargetfocus[forplayer]->y, ticcmd_ztargetfocus[forplayer]->z, MT_LOCKON); // positioning, flip handled in P_SceneryThinker
+			P_SetTarget(&newtarget->target, ticcmd_ztargetfocus[forplayer]);
 
-	// some people strafe left & right with mouse buttons
-	// those people are (still) weird
-	if (straferkey)
-		side += sidemove[speed];
-	if (strafelkey)
-		side -= sidemove[speed];
+			if (P_AproxDistance(
+				player->mo->x - ticcmd_ztargetfocus[forplayer]->x,
+				player->mo->y - ticcmd_ztargetfocus[forplayer]->y
+			) > 50*player->mo->scale)
+			{
+				INT32 anglediff = R_PointToAngle2(player->mo->x, player->mo->y, ticcmd_ztargetfocus[forplayer]->x, ticcmd_ztargetfocus[forplayer]->y) - *myangle;
+				const INT32 maxturn = ANG10/2;
+				anglediff /= 4;
 
-	if (PLAYER2INPUTDOWN(gc_weaponnext))
-		cmd->buttons |= BT_WEAPONNEXT; // Next Weapon
-	if (PLAYER2INPUTDOWN(gc_weaponprev))
-		cmd->buttons |= BT_WEAPONPREV; // Previous Weapon
+				if (anglediff > maxturn)
+					anglediff = maxturn;
+				else if (anglediff < -maxturn)
+					anglediff = -maxturn;
 
-	//use the four avaliable bits to determine the weapon.
-	cmd->buttons &= ~BT_WEAPONMASK;
-	for (i = 0; i < NUM_WEAPONS; ++i)
-		if (PLAYER2INPUTDOWN(gc_wepslot1 + i))
-		{
-			cmd->buttons |= (UINT16)(i + 1);
-			break;
+				*myangle += anglediff;
+			}
 		}
+	}
 
-	// fire with any button/key
-	axis = Joy2Axis(AXISFIRE);
-	if (PLAYER2INPUTDOWN(gc_fire) || (cv_usejoystick2.value && axis > 0))
-		cmd->buttons |= BT_ATTACK;
-
-	// fire normal with any button/key
-	axis = Joy2Axis(AXISFIRENORMAL);
-	if (PLAYER2INPUTDOWN(gc_firenormal) || (cv_usejoystick2.value && axis > 0))
-		cmd->buttons |= BT_FIRENORMAL;
-
-	if (PLAYER2INPUTDOWN(gc_tossflag))
-		cmd->buttons |= BT_TOSSFLAG;
-
-	// Lua scriptable buttons
-	if (PLAYER2INPUTDOWN(gc_custom1))
-		cmd->buttons |= BT_CUSTOM1;
-	if (PLAYER2INPUTDOWN(gc_custom2))
-		cmd->buttons |= BT_CUSTOM2;
-	if (PLAYER2INPUTDOWN(gc_custom3))
-		cmd->buttons |= BT_CUSTOM3;
-
-	// use with any button/key
-	axis = Joy2Axis(AXISSPIN);
-	if (PLAYER2INPUTDOWN(gc_use) || (cv_usejoystick2.value && axis > 0))
-		cmd->buttons |= BT_USE;
+	if (ticcmd_centerviewdown[forplayer] && controlstyle == CS_SIMPLE)
+		controlstyle = CS_LEGACY;
 
-	if (PLAYER2INPUTDOWN(gc_camreset))
+	if (PLAYERINPUTDOWN(ssplayer, gc_camreset))
 	{
-		if (camera2.chase && !resetdown)
-			P_ResetCamera(&players[secondarydisplayplayer], &camera2);
-		resetdown = true;
+		if (thiscam->chase && !resetdown[forplayer])
+			P_ResetCamera(&players[ssplayer == 1 ? displayplayer : secondarydisplayplayer], thiscam);
+
+		resetdown[forplayer] = true;
 	}
 	else
-		resetdown = false;
+		resetdown[forplayer] = false;
+
 
 	// jump button
-	axis = Joy2Axis(AXISJUMP);
-	if (PLAYER2INPUTDOWN(gc_jump) || (cv_usejoystick2.value && axis > 0))
+	axis = PlayerJoyAxis(ssplayer, AXISJUMP);
+	if (PLAYERINPUTDOWN(ssplayer, gc_jump) || (usejoystick && axis > 0))
 		cmd->buttons |= BT_JUMP;
 
 	// player aiming shit, ahhhh...
 	{
-		INT32 player_invert = cv_invertmouse2.value ? -1 : 1;
+		INT32 player_invert = invertmouse ? -1 : 1;
 		INT32 screen_invert =
 			(player->mo && (player->mo->eflags & MFE_VERTICALFLIP)
-			 && (!camera2.chase || player->pflags & PF_FLIPCAM)) //because chasecam's not inverted
+			 && (!thiscam->chase || player->pflags & PF_FLIPCAM)) //because chasecam's not inverted
 			 ? -1 : 1; // set to -1 or 1 to multiply
+		 INT32 configlookaxis = ssplayer == 1 ? cv_lookaxis.value : cv_lookaxis2.value;
 
 		// mouse look stuff (mouse look is not the same as mouse aim)
 		if (mouseaiming)
 		{
-			keyboard_look = false;
+			keyboard_look[forplayer] = false;
 
 			// looking up/down
-			localaiming2 += (mlook2y<<19)*player_invert*screen_invert;
+			*myaiming += (*mly<<19)*player_invert*screen_invert;
 		}
 
-		if (analogjoystickmove && joyaiming && lookjoystickvector.yaxis != 0 && cv_lookaxis2.value != 0)
-			localaiming2 += (lookjoystickvector.yaxis<<16) * screen_invert;
+		if (analogjoystickmove && joyaiming[forplayer] && lookjoystickvector.yaxis != 0 && configlookaxis != 0)
+			*myaiming += (lookjoystickvector.yaxis<<16) * screen_invert;
 
 		// spring back if not using keyboard neither mouselookin'
-		if (!keyboard_look && cv_lookaxis2.value == 0 && !joyaiming && !mouseaiming)
-			localaiming2 = 0;
+		if (!keyboard_look[forplayer] && configlookaxis == 0 && !joyaiming[forplayer] && !mouseaiming)
+			*myaiming = 0;
 
 		if (!(player->powers[pw_carry] == CR_NIGHTSMODE))
 		{
-			if (PLAYER2INPUTDOWN(gc_lookup) || (gamepadjoystickmove && lookjoystickvector.yaxis > 0))
+			if (PLAYERINPUTDOWN(ssplayer, gc_lookup) || (gamepadjoystickmove && lookjoystickvector.yaxis < 0))
 			{
-				localaiming2 += KB_LOOKSPEED * screen_invert;
-				keyboard_look = true;
+				*myaiming += KB_LOOKSPEED * screen_invert;
+				keyboard_look[forplayer] = true;
 			}
-			else if (PLAYER2INPUTDOWN(gc_lookdown) || (gamepadjoystickmove && lookjoystickvector.yaxis < 0))
+			else if (PLAYERINPUTDOWN(ssplayer, gc_lookdown) || (gamepadjoystickmove && lookjoystickvector.yaxis > 0))
 			{
-				localaiming2 -= KB_LOOKSPEED * screen_invert;
-				keyboard_look = true;
+				*myaiming -= KB_LOOKSPEED * screen_invert;
+				keyboard_look[forplayer] = true;
 			}
-			else if (PLAYER2INPUTDOWN(gc_centerview))
-				localaiming2 = 0;
+			else if (ticcmd_centerviewdown[forplayer])
+				*myaiming = 0;
 		}
 
 		// accept no mlook for network games
 		if (!cv_allowmlook.value)
-			localaiming2 = 0;
+			*myaiming = 0;
 
-		cmd->aiming = G_ClipAimingPitch(&localaiming2);
+		cmd->aiming = G_ClipAimingPitch(myaiming);
 	}
 
-	if (!mouseaiming && cv_mousemove2.value)
-		forward += mouse2y;
+	if (!mouseaiming && mousemove)
+		forward += *my;
 
-	if (player->climbing
-		|| (player->pflags & PF_SLIDING)) // Analog for mouse
-		side += mouse2x*2;
-	else if (cv_analog2.value)
+	if ((!demoplayback && (player->pflags & PF_SLIDING))) // Analog for mouse
+		side += *mx*2;
+	else if (controlstyle == CS_LMAOGALOG)
 	{
-		if (mouse2x)
+		if (*mx)
 		{
-			if (mouse2x > 0)
+			if (*mx > 0)
 				cmd->buttons |= BT_CAMRIGHT;
 			else
 				cmd->buttons |= BT_CAMLEFT;
 		}
 	}
 	else
-		cmd->angleturn = (INT16)(cmd->angleturn - (mouse2x*8));
+		cmd->angleturn = (INT16)(cmd->angleturn - (*mx*8));
 
-	mouse2x = mouse2y = mlook2y = 0;
+	*mx = *my = *mly = 0;
 
 	if (forward > MAXPLMOVE)
 		forward = MAXPLMOVE;
@@ -1697,7 +1596,7 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 	}
 
 	//Silly hack to make 2d mode *somewhat* playable with no chasecam.
-	if ((twodlevel || (player->mo && player->mo->flags2 & MF2_TWOD)) && !camera2.chase)
+	if ((twodlevel || (player->mo && player->mo->flags2 & MF2_TWOD)) && !thiscam->chase)
 	{
 		INT32 temp = forward;
 		forward = side;
@@ -1707,11 +1606,11 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 	cmd->forwardmove = (SINT8)(cmd->forwardmove + forward);
 	cmd->sidemove = (SINT8)(cmd->sidemove + side);
 
-	if (player->bot == 1) {
+	if (player->bot == 1) { // Tailsbot for P2
 		if (!player->powers[pw_tailsfly] && (cmd->forwardmove || cmd->sidemove || cmd->buttons))
 		{
 			player->bot = 2; // A player-controlled bot. Returns to AI when it respawns.
-			CV_SetValue(&cv_analog2, true);
+			CV_SetValue(&cv_analog[1], true);
 		}
 		else
 		{
@@ -1720,8 +1619,10 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 		}
 		B_HandleFlightIndicator(player);
 	}
+	else if (player->bot == 2)
+		*myangle = localangle; // Fix offset angle for P2-controlled Tailsbot when P2's controls are set to non-Legacy
 
-	if (cv_analog2.value) {
+	if (controlstyle == CS_LMAOGALOG) {
 		if (player->awayviewtics)
 			cmd->angleturn = (INT16)(player->awayviewmobj->angle >> 16);
 		else
@@ -1729,8 +1630,95 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 	}
 	else
 	{
-		localangle2 += (cmd->angleturn<<16);
-		cmd->angleturn = (INT16)(localangle2 >> 16);
+		*myangle += (cmd->angleturn<<16);
+		cmd->angleturn = (INT16)(*myangle >> 16);
+
+		// Adjust camera angle by player input
+		if (controlstyle == CS_SIMPLE && !forcestrafe && thiscam->chase && !turnheld[forplayer] && !ticcmd_centerviewdown[forplayer] && !player->climbing && player->powers[pw_carry] != CR_MINECART)
+		{
+			fixed_t camadjustfactor = cv_cam_turnfacinginput[forplayer].value;
+
+			if (camadjustfactor)
+			{
+				fixed_t sine = FINESINE((R_PointToAngle2(0, 0, player->rmomx, player->rmomy) - localangle)>>ANGLETOFINESHIFT);
+				fixed_t factor;
+
+				if ((sine > 0) == (cmd->sidemove > 0))
+					sine = 0; // Prevent jerking right when braking from going left, or vice versa
+
+				factor = min(40, FixedMul(player->speed, abs(sine))*2 / FRACUNIT);
+
+				*myangle -= cmd->sidemove * factor * camadjustfactor;
+			}
+
+			if (ticcmd_centerviewdown[forplayer] && (cv_cam_lockedinput[forplayer].value || (player->pflags & PF_STARTDASH)))
+				cmd->sidemove = 0;
+		}
+
+		// Adjust camera angle to face player direction, depending on circumstances
+		// Nothing happens if cam left/right are held, so you can hold both to lock the camera in one direction
+		if (controlstyle == CS_SIMPLE && !forcestrafe && thiscam->chase && !turnheld[forplayer] && !ticcmd_centerviewdown[forplayer] && player->powers[pw_carry] != CR_MINECART)
+		{
+			fixed_t camadjustfactor;
+			boolean alt = false; // Reduce intensity on diagonals and prevent backwards movement from turning the camera
+
+			if (player->pflags & PF_GLIDING)
+				camadjustfactor = cv_cam_turnfacingability[forplayer].value/4;
+			else if (player->pflags & PF_STARTDASH)
+				camadjustfactor = cv_cam_turnfacingspindash[forplayer].value/4;
+			else
+			{
+				alt = true;
+				camadjustfactor = cv_cam_turnfacing[forplayer].value/8;
+			}
+
+			camadjustfactor = FixedMul(camadjustfactor, max(FRACUNIT - player->speed, min(player->speed/18, FRACUNIT)));
+
+			camadjustfactor = FixedMul(camadjustfactor, tta_factor[forplayer]);
+
+			if (tta_factor[forplayer] < FRACUNIT && (cmd->forwardmove || cmd->sidemove || tta_factor[forplayer] >= FRACUNIT/3))
+				tta_factor[forplayer] += FRACUNIT>>5;
+			else if (tta_factor[forplayer] && tta_factor[forplayer] < FRACUNIT/3)
+				tta_factor[forplayer] -= FRACUNIT>>5;
+
+			if (camadjustfactor)
+			{
+				angle_t controlangle;
+				INT32 anglediff;
+
+				if ((cmd->forwardmove || cmd->sidemove) && !(player->pflags & PF_SPINNING))
+					controlangle = (cmd->angleturn<<16) + R_PointToAngle2(0, 0, cmd->forwardmove << FRACBITS, -cmd->sidemove << FRACBITS);
+				else
+					controlangle = player->drawangle + drawangleoffset;
+
+				anglediff = controlangle - *myangle;
+
+				if (alt)
+				{
+					fixed_t sine = FINESINE((angle_t) (anglediff)>>ANGLETOFINESHIFT);
+					sine = abs(sine);
+
+					if (abs(anglediff) > ANGLE_90)
+						sine = max(0, sine*3 - 2*FRACUNIT); // At about 135 degrees, this will stop turning
+
+					anglediff = FixedMul(anglediff, sine);
+				}
+
+				*myangle += FixedMul(anglediff, camadjustfactor);
+			}
+		}
+	}
+
+	//Reset away view if a command is given.
+	if (ssplayer == 1 && (cmd->forwardmove || cmd->sidemove || cmd->buttons)
+		&& displayplayer != consoleplayer)
+	{
+#ifdef HAVE_BLUA
+		// Call ViewpointSwitch hooks here.
+		// The viewpoint was forcibly changed.
+		LUAh_ViewpointSwitch(player, &players[displayplayer], true);
+#endif
+		displayplayer = consoleplayer;
 	}
 }
 
@@ -1739,20 +1727,20 @@ void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics)
 // fudging with it.
 static void UserAnalog_OnChange(void)
 {
-	if (cv_useranalog.value)
-		CV_SetValue(&cv_analog, 1);
+	if (cv_useranalog[0].value)
+		CV_SetValue(&cv_analog[0], 1);
 	else
-		CV_SetValue(&cv_analog, 0);
+		CV_SetValue(&cv_analog[0], 0);
 }
 
 static void UserAnalog2_OnChange(void)
 {
 	if (botingame)
 		return;
-	if (cv_useranalog2.value)
-		CV_SetValue(&cv_analog2, 1);
+	if (cv_useranalog[1].value)
+		CV_SetValue(&cv_analog[1], 1);
 	else
-		CV_SetValue(&cv_analog2, 0);
+		CV_SetValue(&cv_analog[1], 0);
 }
 
 static void Analog_OnChange(void)
@@ -1762,8 +1750,8 @@ static void Analog_OnChange(void)
 
 	// cameras are not initialized at this point
 
-	if (!cv_chasecam.value && cv_analog.value) {
-		CV_SetValue(&cv_analog, 0);
+	if (!cv_chasecam.value && cv_analog[0].value) {
+		CV_SetValue(&cv_analog[0], 0);
 		return;
 	}
 
@@ -1777,8 +1765,8 @@ static void Analog2_OnChange(void)
 
 	// cameras are not initialized at this point
 
-	if (!cv_chasecam2.value && cv_analog2.value) {
-		CV_SetValue(&cv_analog2, 0);
+	if (!cv_chasecam2.value && cv_analog[1].value) {
+		CV_SetValue(&cv_analog[1], 0);
 		return;
 	}
 
@@ -6310,12 +6298,12 @@ void G_BeginRecording(void)
 			buf |= 0x01;
 			pflags |= PF_FLIPCAM;
 		}
-		if (cv_analog.value)
+		if (cv_analog[0].value)
 		{
 			buf |= 0x02;
 			pflags |= PF_ANALOGMODE;
 		}
-		if (cv_directionchar.value)
+		if (cv_directionchar[0].value)
 		{
 			buf |= 0x04;
 			pflags |= PF_DIRECTIONCHAR;
diff --git a/src/g_game.h b/src/g_game.h
index cb51faaca116975402c02d9fe4b6214a43e99a4a..a589a89177f7cd1c362ccc6227604e20bcc16f88 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -68,15 +68,37 @@ extern consvar_t cv_chatwidth, cv_chatnotifications, cv_chatheight, cv_chattime,
 extern consvar_t cv_crosshair, cv_crosshair2;
 extern consvar_t cv_invertmouse, cv_alwaysfreelook, cv_chasefreelook, cv_mousemove;
 extern consvar_t cv_invertmouse2, cv_alwaysfreelook2, cv_chasefreelook2, cv_mousemove2;
-extern consvar_t cv_useranalog, cv_useranalog2;
-extern consvar_t cv_analog, cv_analog2;
-extern consvar_t cv_directionchar, cv_directionchar2;
+
+extern consvar_t cv_useranalog[2], cv_analog[2];
+extern consvar_t cv_directionchar[2];
+
+typedef enum {
+	CS_LEGACY,
+	CS_LMAOGALOG,
+	CS_STANDARD,
+	CS_SIMPLE = CS_LMAOGALOG|CS_STANDARD,
+} controlstyle_e;
+#define G_ControlStyle(ssplayer) (cv_directionchar[(ssplayer)-1].value == 3 ? CS_LMAOGALOG : ((cv_analog[(ssplayer)-1].value ? CS_LMAOGALOG : 0) | (cv_directionchar[(ssplayer)-1].value ? CS_STANDARD : 0)))
+#define P_ControlStyle(player) ((((player)->pflags & PF_ANALOGMODE) ? CS_LMAOGALOG : 0) | (((player)->pflags & PF_DIRECTIONCHAR) ? CS_STANDARD : 0))
+
 extern consvar_t cv_autobrake, cv_autobrake2;
-extern consvar_t cv_deadzone, cv_deadzone2;
-extern consvar_t cv_sideaxis,cv_turnaxis,cv_moveaxis,cv_lookaxis,cv_jumpaxis,cv_spinaxis,cv_fireaxis,cv_firenaxis;
-extern consvar_t cv_sideaxis2,cv_turnaxis2,cv_moveaxis2,cv_lookaxis2,cv_jumpaxis2,cv_spinaxis2,cv_fireaxis2,cv_firenaxis2;
+extern consvar_t cv_sideaxis,cv_turnaxis,cv_moveaxis,cv_lookaxis,cv_jumpaxis,cv_spinaxis,cv_fireaxis,cv_firenaxis,cv_deadzone,cv_digitaldeadzone;
+extern consvar_t cv_sideaxis2,cv_turnaxis2,cv_moveaxis2,cv_lookaxis2,cv_jumpaxis2,cv_spinaxis2,cv_fireaxis2,cv_firenaxis2,cv_deadzone2,cv_digitaldeadzone2;
 extern consvar_t cv_ghost_bestscore, cv_ghost_besttime, cv_ghost_bestrings, cv_ghost_last, cv_ghost_guest;
 
+// hi here's some new controls
+extern consvar_t cv_cam_shiftfacing[2], cv_cam_turnfacing[2],
+	cv_cam_turnfacingability[2], cv_cam_turnfacingspindash[2], cv_cam_turnfacinginput[2],
+	cv_cam_centertoggle[2], cv_cam_lockedinput[2], cv_cam_lockonboss[2];
+
+typedef enum
+{
+	LOCK_BOSS = 1<<0,
+	LOCK_ENEMY = 1<<1,
+	LOCK_INTERESTS = 1<<2,
+} lockassist_e;
+
+
 // mouseaiming (looking up/down with the mouse or keyboard)
 #define KB_LOOKSPEED (1<<25)
 #define MAXPLMOVE (50)
@@ -84,8 +106,10 @@ extern consvar_t cv_ghost_bestscore, cv_ghost_besttime, cv_ghost_bestrings, cv_g
 
 // build an internal map name MAPxx from map number
 const char *G_BuildMapName(INT32 map);
-void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics);
-void G_BuildTiccmd2(ticcmd_t *cmd, INT32 realtics);
+
+extern boolean ticcmd_centerviewdown[2]; // For simple controls, lock the camera behind the player
+extern mobj_t *ticcmd_ztargetfocus[2]; // Locking onto an object?
+void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer);
 
 // copy ticcmd_t to and fro the normal way
 ticcmd_t *G_CopyTiccmd(ticcmd_t* dest, const ticcmd_t* src, const size_t n);
diff --git a/src/g_input.h b/src/g_input.h
index d089332c5355305ce5b350ed0ab5fc9d72a0302d..f7f952d729aa092e8f8323ce9beb2f84c2aa2790 100644
--- a/src/g_input.h
+++ b/src/g_input.h
@@ -132,6 +132,7 @@ extern INT32 gamecontroldefault[num_gamecontrolschemes][num_gamecontrols][2]; //
 extern INT32 gamecontrolbisdefault[num_gamecontrolschemes][num_gamecontrols][2];
 #define PLAYER1INPUTDOWN(gc) (gamekeydown[gamecontrol[gc][0]] || gamekeydown[gamecontrol[gc][1]])
 #define PLAYER2INPUTDOWN(gc) (gamekeydown[gamecontrolbis[gc][0]] || gamekeydown[gamecontrolbis[gc][1]])
+#define PLAYERINPUTDOWN(p, gc) ((p) == 2 ? PLAYER2INPUTDOWN(gc) : PLAYER1INPUTDOWN(gc))
 
 #define num_gcl_tutorial_check 6
 #define num_gcl_tutorial_used 8
diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index 59504be73f1b43a5913b948129b9ab518733476b..b998b3360f20d187278d05e840f6ee5d77f8809b 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -127,11 +127,11 @@ static void HWR_DrawColumnInCache(const column_t *patchcol, UINT8 *block, GLMipm
 			if (mipmap->colormap)
 				texel = mipmap->colormap[texel];
 
-			// transparent pixel
-			if (texel == HWR_PATCHES_CHROMAKEY_COLORINDEX)
+			// If the mipmap is chromakeyed, check if the texel's color
+			// is equivalent to the chroma key's color index.
+			alpha = 0xff;
+			if ((mipmap->flags & TF_CHROMAKEYED) && (texel == HWR_PATCHES_CHROMAKEY_COLORINDEX))
 				alpha = 0x00;
-			else
-				alpha = 0xff;
 
 			// hope compiler will get this switch out of the loops (dreams...)
 			// gcc do it ! but vcc not ! (why don't use cygwin gcc for win32 ?)
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index b9fd60fe48899381ba8e44a773a056a6b45b86ea..12dc28cb7105d17160c4fb663f004467a096472e 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -5503,7 +5503,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	spriteinfo_t *sprinfo;
 	size_t lumpoff;
 	unsigned rot;
-	UINT8 flip;
+	UINT16 flip;
 	boolean vflip = (!(thing->eflags & MFE_VERTICALFLIP) != !(thing->frame & FF_VERTICALFLIP));
 
 	angle_t ang;
@@ -5589,12 +5589,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 		flip = sprframe->flip; // Will only be 0x00 or 0xFF
 
 		if (papersprite && ang < ANGLE_180)
-		{
-			if (flip)
-				flip = 0;
-			else
-				flip = 255;
-		}
+			flip ^= 0xFFFF;
 	}
 	else
 	{
@@ -5603,6 +5598,11 @@ static void HWR_ProjectSprite(mobj_t *thing)
 			rot = 6; // F7 slot
 		else if ((sprframe->rotate & SRF_LEFT) && (ang >= ANGLE_180)) // See from left
 			rot = 2; // F3 slot
+		else if (sprframe->rotate & SRF_3DGE) // 16-angle mode
+		{
+			rot = (ang+ANGLE_180+ANGLE_11hh)>>28;
+			rot = ((rot & 1)<<3)|(rot>>1);
+		}
 		else // Normal behaviour
 			rot = (ang+ANGLE_202h)>>29;
 
@@ -5611,12 +5611,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 		flip = sprframe->flip & (1<<rot);
 
 		if (papersprite && ang < ANGLE_180)
-		{
-			if (flip)
-				flip = 0;
-			else
-				flip = 1<<rot;
-		}
+			flip ^= (1<<rot);
 	}
 
 	if (thing->skin && ((skin_t *)thing->skin)->flags & SF_HIRES)
@@ -5631,7 +5626,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	if (thing->rollangle)
 	{
 		rollangle = R_GetRollAngle(thing->rollangle);
-		if (!sprframe->rotsprite.cached[rot])
+		if (!(sprframe->rotsprite.cached & (1<<rot)))
 			R_CacheRotSprite(thing->sprite, (thing->frame & FF_FRAMEMASK), sprinfo, sprframe, rot, flip);
 		rotsprite = sprframe->rotsprite.patch[rot][rollangle];
 		if (rotsprite != NULL)
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 3800b6ad98ff9b89af7ee38a7c328a87abcbf580..3f0575dd909c60ec3d34b4b830fb70efebb8dcec 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -1108,7 +1108,7 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 	{
 		sector_t *sector = spr->mobj->subsector->sector;
 		UINT8 lightlevel = 255;
-		extracolormap_t *colormap = sector->extra_colormap;
+		extracolormap_t *colormap = NULL;
 
 		if (sector->numlights)
 		{
@@ -1145,6 +1145,7 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 		INT32 durs = spr->mobj->state->tics;
 		INT32 tics = spr->mobj->tics;
 		//mdlframe_t *next = NULL;
+		const boolean papersprite = (spr->mobj->frame & FF_PAPERSPRITE);
 		const UINT8 flip = (UINT8)(!(spr->mobj->eflags & MFE_VERTICALFLIP) != !(spr->mobj->frame & FF_VERTICALFLIP));
 		spritedef_t *sprdef;
 		spriteframe_t *sprframe;
@@ -1361,14 +1362,12 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 
 		sprframe = &sprdef->spriteframes[spr->mobj->frame & FF_FRAMEMASK];
 
-		if (sprframe->rotate)
+		if (sprframe->rotate || papersprite)
 		{
 			fixed_t anglef = AngleFixed(spr->mobj->angle);
 
 			if (spr->mobj->player)
 				anglef = AngleFixed(spr->mobj->player->drawangle);
-			else
-				anglef = AngleFixed(spr->mobj->angle);
 
 			p.angley = FIXED_TO_FLOAT(anglef);
 		}
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 97b3b98be576753f7b815ce3e49d7c3f825e319e..327dbd3df2ac6e64a446c15e16cfe331c07fe981 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -2273,14 +2273,30 @@ EXPORT void HWRAPI(SetTransform) (FTransform *stransform)
 
 EXPORT INT32  HWRAPI(GetTextureUsed) (void)
 {
-	FTextureInfo*   tmp = gr_cachehead;
-	INT32             res = 0;
+	FTextureInfo *tmp = gr_cachehead;
+	INT32 res = 0;
 
 	while (tmp)
 	{
-		res += tmp->height*tmp->width*(screen_depth/8);
+		// Figure out the correct bytes-per-pixel for this texture
+		// I don't know which one the game actually _uses_ but this
+		// follows format2bpp in hw_cache.c
+		int bpp = 1;
+		int format = tmp->grInfo.format;
+		if (format == GR_RGBA)
+			bpp = 4;
+		else if (format == GR_TEXFMT_RGB_565
+			|| format == GR_TEXFMT_ARGB_1555
+			|| format == GR_TEXFMT_ARGB_4444
+			|| format == GR_TEXFMT_ALPHA_INTENSITY_88
+			|| format == GR_TEXFMT_AP_88)
+			bpp = 2;
+
+		// Add it up!
+		res += tmp->height*tmp->width*bpp;
 		tmp = tmp->nextmipmap;
 	}
+
 	return res;
 }
 
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index f558b8c0c99fc0019695124ce55079b8761a655d..c6a92487c9f9accdfc5c03d82906daa81f54233f 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -2201,10 +2201,14 @@ void HU_Drawer(void)
 		return;
 
 	// draw the crosshair, not when viewing demos nor with chasecam
-	if (!automapactive && cv_crosshair.value && !demoplayback && !camera.chase && !players[displayplayer].spectator)
+	if (!automapactive && cv_crosshair.value && !demoplayback &&
+		(!camera.chase || ticcmd_ztargetfocus[0])
+	&& !players[displayplayer].spectator)
 		HU_DrawCrosshair();
 
-	if (!automapactive && cv_crosshair2.value && !demoplayback && !camera2.chase && !players[secondarydisplayplayer].spectator)
+	if (!automapactive && cv_crosshair2.value && !demoplayback &&
+		(!camera2.chase || ticcmd_ztargetfocus[1])
+	&& !players[secondarydisplayplayer].spectator)
 		HU_DrawCrosshair2();
 
 	// draw desynch text
diff --git a/src/info.c b/src/info.c
index 30f3e96d097f79017ef30894ea6b6c30e80f55a1..5f1cb8fbe9adafe787d94a77d07dbd69e942056b 100644
--- a/src/info.c
+++ b/src/info.c
@@ -3565,7 +3565,7 @@ state_t states[NUMSTATES] =
 	{SPR_PUMA, FF_FULLBRIGHT|FF_TRANS60|8, 3,       {NULL},        0, 0, S_NULL},         // S_PUMATRAIL4
 
 	// Hammer
-	{SPR_HAMM, FF_ANIMATE, -1, {NULL}, 4, 3, S_NULL}, // S_HAMMER
+	{SPR_HAMM, FF_ANIMATE, -1, {NULL}, 3, 3, S_NULL}, // S_HAMMER
 
 	// Koopa
 	{SPR_KOOP, 0, -1, {NULL}, 0, 0, S_NULL},   // S_KOOPA1
diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c
index 55854082909bfa43c097b586e58f42c2be3f5a95..9c426fad3d92a0478885ab87fc099b92ea0587be 100644
--- a/src/lua_consolelib.c
+++ b/src/lua_consolelib.c
@@ -118,12 +118,12 @@ void COM_Lua_f(void)
 
 	lua_rawgeti(gL, -1, 2); // push flags from command info table
 	if (lua_isboolean(gL, -1))
-		flags = (lua_toboolean(gL, -1) ? 1 : 0);
+		flags = (lua_toboolean(gL, -1) ? COM_ADMIN : 0);
 	else
 		flags = (UINT8)lua_tointeger(gL, -1);
 	lua_pop(gL, 1); // pop flags
 
-	if (flags & 2) // flag 2: splitscreen player command.
+	if (flags & COM_SPLITSCREEN) // flag 2: splitscreen player command.
 	{
 		if (!splitscreen)
 		{
@@ -133,12 +133,12 @@ void COM_Lua_f(void)
 		playernum = secondarydisplayplayer;
 	}
 
-	if (netgame)
+	if (netgame && !( flags & COM_LOCAL ))/* don't send local commands */
 	{ // Send the command through the network
 		UINT8 argc;
 		lua_pop(gL, 1); // pop command info table
 
-		if (flags & 1 && !server && !IsPlayerAdmin(playernum)) // flag 1: only server/admin can use this command.
+		if (flags & COM_ADMIN && !server && !IsPlayerAdmin(playernum)) // flag 1: only server/admin can use this command.
 		{
 			CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
 			return;
@@ -158,7 +158,7 @@ void COM_Lua_f(void)
 		WRITEUINT8(p, argc);
 		for (i = 0; i < argc; i++)
 			WRITESTRINGN(p, COM_Argv(i), 255);
-		if (flags & 2)
+		if (flags & COM_SPLITSCREEN)
 			SendNetXCmd2(XD_LUACMD, buf, p-buf);
 		else
 			SendNetXCmd(XD_LUACMD, buf, p-buf);
@@ -192,7 +192,15 @@ static int lib_comAddCommand(lua_State *L)
 	if (lua_gettop(L) >= 3)
 	{ // For the third argument, only take a boolean or a number.
 		lua_settop(L, 3);
-		if (lua_type(L, 3) != LUA_TBOOLEAN)
+		if (lua_type(L, 3) == LUA_TBOOLEAN)
+		{
+			CONS_Alert(CONS_WARNING,
+					"Using a boolean for admin commands is "
+					"deprecated and will be removed.\n"
+					"Use \"COM_ADMIN\" instead.\n"
+			);
+		}
+		else
 			luaL_checktype(L, 3, LUA_TNUMBER);
 	}
 	else
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 58af64359e00abfbd309565aade645280f68a3b2..f451944e3d95f6c6b40c990f6c120e6ee196c965 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -468,11 +468,11 @@ static int libd_getSpritePatch(lua_State *L)
 
 	// convert WAD editor angle numbers (1-8) to internal angle numbers (0-7)
 	// keep 0 the same since we'll make it default to angle 1 (which is internally 0)
-	// in case somebody didn't know that angle 0 really just maps all 8 angles to the same patch
+	// in case somebody didn't know that angle 0 really just maps all 8/16 angles to the same patch
 	if (angle != 0)
 		angle--;
 
-	if (angle >= 8) // out of range?
+	if (angle >= ((sprframe->rotate & SRF_3DGE) ? 16 : 8)) // out of range?
 		return 0;
 
 	// push both the patch and it's "flip" value
@@ -563,11 +563,11 @@ static int libd_getSprite2Patch(lua_State *L)
 
 	// convert WAD editor angle numbers (1-8) to internal angle numbers (0-7)
 	// keep 0 the same since we'll make it default to angle 1 (which is internally 0)
-	// in case somebody didn't know that angle 0 really just maps all 8 angles to the same patch
+	// in case somebody didn't know that angle 0 really just maps all 8/16 angles to the same patch
 	if (angle != 0)
 		angle--;
 
-	if (angle >= 8) // out of range?
+	if (angle >= ((sprframe->rotate & SRF_3DGE) ? 16 : 8)) // out of range?
 		return 0;
 
 	// push both the patch and it's "flip" value
diff --git a/src/m_anigif.c b/src/m_anigif.c
index f761db143b0cb016ee0395e9f045c8cdeb49d444..32fc2746da54caa831cda304053c73a2519b55db 100644
--- a/src/m_anigif.c
+++ b/src/m_anigif.c
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2013-2016 by Matthew "Inuyasha" Walsh.
+// Copyright (C) 2013-2016 by Matthew "Kaito Sinclaire" Walsh.
 // Copyright (C) 2013      by "Ninji".
 // Copyright (C) 2013-2019 by Sonic Team Junior.
 //
diff --git a/src/m_anigif.h b/src/m_anigif.h
index 592d2cf19a8d2f1aceb8f26336e272fdbbfa5c87..9bdf2cc7f22701c6a2a35eff4580ba299379a565 100644
--- a/src/m_anigif.h
+++ b/src/m_anigif.h
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2013-2016 by Matthew "Inuyasha" Walsh.
+// Copyright (C) 2013-2016 by Matthew "Kaito Sinclaire" Walsh.
 // Copyright (C) 2013-2019 by Sonic Team Junior.
 //
 // This program is free software distributed under the
diff --git a/src/m_cheat.c b/src/m_cheat.c
index 58bb2962c7ae60f966146c583297bb8cb242ad30..c284acf3edb87438c475694a84a7c7618683e03d 100644
--- a/src/m_cheat.c
+++ b/src/m_cheat.c
@@ -1304,7 +1304,7 @@ void OP_ObjectplaceMovement(player_t *player)
 {
 	ticcmd_t *cmd = &player->cmd;
 
-	if (!player->climbing && (netgame || !cv_analog.value || (player->pflags & PF_SPINNING)))
+	if (!player->climbing && (netgame || !cv_analog[0].value || (player->pflags & PF_SPINNING)))
 		player->drawangle = player->mo->angle = (cmd->angleturn<<16 /* not FRACBITS */);
 
 	ticruned++;
diff --git a/src/m_cond.c b/src/m_cond.c
index 1e761fdb4a5134d0f8102e02083725a673b26c0e..08f3fe03874294e83dd71fc750a5670f69e0eb54 100644
--- a/src/m_cond.c
+++ b/src/m_cond.c
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2012-2016 by Matthew "Inuyasha" Walsh.
+// Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh.
 // Copyright (C) 2012-2019 by Sonic Team Junior.
 //
 // This program is free software distributed under the
diff --git a/src/m_cond.h b/src/m_cond.h
index 3ea77145d4651f4081b088431ad505144ddff222..f4f017787c7611d71802049243fb9725ee4baf70 100644
--- a/src/m_cond.h
+++ b/src/m_cond.h
@@ -1,6 +1,6 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
-// Copyright (C) 2012-2016 by Matthew "Inuyasha" Walsh.
+// Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh.
 // Copyright (C) 2012-2019 by Sonic Team Junior.
 //
 // This program is free software distributed under the
diff --git a/src/m_menu.c b/src/m_menu.c
index 63ab214386975032946852238345988e33d67f00..1dcd4ef0f767a0776a7c64dc5ef20eadbaa24c86 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 2011-2016 by Matthew "Inuyasha" Walsh.
+// Copyright (C) 2011-2016 by Matthew "Kaito Sinclaire" Walsh.
 // Copyright (C) 1999-2019 by Sonic Team Junior.
 //
 // This program is free software distributed under the
@@ -298,11 +298,14 @@ menu_t OP_MPControlsDef, OP_MiscControlsDef;
 menu_t OP_P1ControlsDef, OP_P2ControlsDef, OP_MouseOptionsDef;
 menu_t OP_Mouse2OptionsDef, OP_Joystick1Def, OP_Joystick2Def;
 menu_t OP_CameraOptionsDef, OP_Camera2OptionsDef;
+menu_t OP_PlaystyleDef;
 static void M_VideoModeMenu(INT32 choice);
 static void M_Setup1PControlsMenu(INT32 choice);
 static void M_Setup2PControlsMenu(INT32 choice);
 static void M_Setup1PJoystickMenu(INT32 choice);
 static void M_Setup2PJoystickMenu(INT32 choice);
+static void M_Setup1PPlaystyleMenu(INT32 choice);
+static void M_Setup2PPlaystyleMenu(INT32 choice);
 static void M_AssignJoystick(INT32 choice);
 static void M_ChangeControl(INT32 choice);
 
@@ -348,6 +351,9 @@ static void M_DrawLevelStats(void);
 static void M_DrawTimeAttackMenu(void);
 static void M_DrawNightsAttackMenu(void);
 static void M_DrawSetupChoosePlayerMenu(void);
+static void M_DrawControlsDefMenu(void);
+static void M_DrawCameraOptionsMenu(void);
+static void M_DrawPlaystyleMenu(void);
 static void M_DrawControl(void);
 static void M_DrawMainVideoMenu(void);
 static void M_DrawVideoMode(void);
@@ -375,6 +381,7 @@ static void M_HandleSoundTest(INT32 choice);
 static void M_HandleImageDef(INT32 choice);
 static void M_HandleLoadSave(INT32 choice);
 static void M_HandleLevelStats(INT32 choice);
+static void M_HandlePlaystyleMenu(INT32 choice);
 #ifndef NONET
 static boolean M_CancelConnect(void);
 static void M_HandleConnectIP(INT32 choice);
@@ -1040,9 +1047,8 @@ static menuitem_t OP_P1ControlsMenu[] =
 
 	{IT_SUBMENU | IT_STRING, NULL, "Camera Options...", &OP_CameraOptionsDef,	50},
 
-	//{IT_STRING  | IT_CVAR, NULL, "Analog Control", &cv_useranalog,  100},
-	{IT_STRING  | IT_CVAR, NULL, "Character angle", &cv_directionchar,  70},
-	{IT_STRING  | IT_CVAR, NULL, "Automatic braking", &cv_autobrake,  80},
+	{IT_STRING  | IT_CVAR, NULL, "Automatic braking", &cv_autobrake,  70},
+	{IT_CALL    | IT_STRING, NULL, "Play Style...", M_Setup1PPlaystyleMenu, 80},
 };
 
 static menuitem_t OP_P2ControlsMenu[] =
@@ -1053,9 +1059,8 @@ static menuitem_t OP_P2ControlsMenu[] =
 
 	{IT_SUBMENU | IT_STRING, NULL, "Camera Options...", &OP_Camera2OptionsDef,	50},
 
-	//{IT_STRING  | IT_CVAR, NULL, "Analog Control", &cv_useranalog2,  100},
-	{IT_STRING  | IT_CVAR, NULL, "Character angle", &cv_directionchar2,  70},
-	{IT_STRING  | IT_CVAR, NULL, "Automatic braking", &cv_autobrake2,  80},
+	{IT_STRING  | IT_CVAR, NULL, "Automatic braking", &cv_autobrake2,  70},
+	{IT_CALL    | IT_STRING, NULL, "Play Style...", M_Setup2PPlaystyleMenu, 80},
 };
 
 static menuitem_t OP_ChangeControlsMenu[] =
@@ -1127,8 +1132,8 @@ static menuitem_t OP_Joystick1Menu[] =
 
 	{IT_STRING | IT_CVAR, NULL, "First-Person Vert-Look", &cv_alwaysfreelook, 120},
 	{IT_STRING | IT_CVAR, NULL, "Third-Person Vert-Look", &cv_chasefreelook,  130},
-	{IT_STRING | IT_CVAR | IT_CV_FLOATSLIDER,
-	                      NULL, "Deadzone",               &cv_deadzone,       140 },
+	{IT_STRING | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Analog Deadzone", &cv_deadzone, 140},
+	{IT_STRING | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Digital Deadzone", &cv_digitaldeadzone, 150},
 };
 
 static menuitem_t OP_Joystick2Menu[] =
@@ -1145,8 +1150,8 @@ static menuitem_t OP_Joystick2Menu[] =
 
 	{IT_STRING | IT_CVAR, NULL, "First-Person Vert-Look", &cv_alwaysfreelook2,120},
 	{IT_STRING | IT_CVAR, NULL, "Third-Person Vert-Look", &cv_chasefreelook2, 130},
-	{IT_STRING | IT_CVAR | IT_CV_FLOATSLIDER,
-	                      NULL, "Deadzone",               &cv_deadzone2,      140 },
+	{IT_STRING | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Analog Deadzone", &cv_deadzone2,140},
+	{IT_STRING | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Digital Deadzone", &cv_digitaldeadzone2,150},
 };
 
 static menuitem_t OP_JoystickSetMenu[1+MAX_JOYSTICKS];
@@ -1183,32 +1188,98 @@ static menuitem_t OP_Mouse2OptionsMenu[] =
 
 static menuitem_t OP_CameraOptionsMenu[] =
 {
-	{IT_STRING  | IT_CVAR, NULL, "Third-person Camera"  , &cv_chasecam , 10},
-	{IT_STRING  | IT_CVAR, NULL, "Flip Camera with Gravity"  , &cv_flipcam , 20},
-	{IT_STRING  | IT_CVAR, NULL, "Orbital Looking"  , &cv_cam_orbit , 30},
-	{IT_STRING  | IT_CVAR, NULL, "Downhill Slope Adjustment", &cv_cam_adjust, 40},
+	{IT_HEADER,            NULL, "General Toggles", NULL, 0},
+	{IT_STRING  | IT_CVAR, NULL, "Third-person Camera"  , &cv_chasecam , 6},
+	{IT_STRING  | IT_CVAR, NULL, "Flip Camera with Gravity"  , &cv_flipcam , 11},
+	{IT_STRING  | IT_CVAR, NULL, "Orbital Looking"  , &cv_cam_orbit , 16},
+	{IT_STRING  | IT_CVAR, NULL, "Downhill Slope Adjustment", &cv_cam_adjust, 21},
 
-	{IT_STRING  | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Distance", &cv_cam_dist, 60},
-	{IT_STRING  | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Height", &cv_cam_height, 70},
-	{IT_STRING  | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Camera Spacial Speed", &cv_cam_speed, 80},
-	{IT_STRING  | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Camera Turning Speed", &cv_cam_turnmultiplier, 90},
+	{IT_HEADER,                                NULL, "Camera Positioning", NULL, 30},
+	{IT_STRING  | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Distance", &cv_cam_savedist[0][0], 36},
+	{IT_STRING  | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Height", &cv_cam_saveheight[0][0], 41},
+	{IT_STRING  | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Camera Spacial Speed", &cv_cam_speed, 46},
+	{IT_STRING  | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Rotation Speed", &cv_cam_turnmultiplier, 51},
 
-	{IT_STRING  | IT_CVAR, NULL, "Crosshair", &cv_crosshair, 100},
+	{IT_HEADER,            NULL, "Display Options", NULL, 60},
+	{IT_STRING  | IT_CVAR, NULL, "Crosshair", &cv_crosshair, 66},
 };
 
 static menuitem_t OP_Camera2OptionsMenu[] =
 {
-	{IT_STRING  | IT_CVAR, NULL, "Third-person Camera"  , &cv_chasecam2 , 10},
-	{IT_STRING  | IT_CVAR, NULL, "Flip Camera with Gravity"  , &cv_flipcam2 , 20},
-	{IT_STRING  | IT_CVAR, NULL, "Orbital Looking"  , &cv_cam2_orbit , 30},
-	{IT_STRING  | IT_CVAR, NULL, "Downhill Slope Adjustment", &cv_cam2_adjust, 40},
+	{IT_HEADER,            NULL, "General Toggles", NULL, 0},
+	{IT_STRING  | IT_CVAR, NULL, "Third-person Camera"  , &cv_chasecam2 , 6},
+	{IT_STRING  | IT_CVAR, NULL, "Flip Camera with Gravity"  , &cv_flipcam2 , 11},
+	{IT_STRING  | IT_CVAR, NULL, "Orbital Looking"  , &cv_cam2_orbit , 16},
+	{IT_STRING  | IT_CVAR, NULL, "Downhill Slope Adjustment", &cv_cam2_adjust, 21},
 
-	{IT_STRING  | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Distance", &cv_cam2_dist, 60},
-	{IT_STRING  | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Height", &cv_cam2_height, 70},
-	{IT_STRING  | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Camera Spacial Speed", &cv_cam2_speed, 80},
-	{IT_STRING  | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Camera Turning Speed", &cv_cam2_turnmultiplier, 90},
+	{IT_HEADER,                                NULL, "Camera Positioning", NULL, 30},
+	{IT_STRING  | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Distance", &cv_cam_savedist[0][1], 36},
+	{IT_STRING  | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Height", &cv_cam_saveheight[0][1], 41},
+	{IT_STRING  | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Camera Spacial Speed", &cv_cam2_speed, 46},
+	{IT_STRING  | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Rotation Speed", &cv_cam2_turnmultiplier, 51},
 
-	{IT_STRING  | IT_CVAR, NULL, "Crosshair", &cv_crosshair2, 100},
+	{IT_HEADER,            NULL, "Display Options", NULL, 60},
+	{IT_STRING  | IT_CVAR, NULL, "Crosshair", &cv_crosshair2, 66},
+};
+
+static menuitem_t OP_CameraExtendedOptionsMenu[] =
+{
+	{IT_HEADER,            NULL, "General Toggles", NULL, 0},
+	{IT_STRING  | IT_CVAR, NULL, "Third-person Camera"  , &cv_chasecam , 6},
+	{IT_STRING  | IT_CVAR, NULL, "Flip Camera with Gravity"  , &cv_flipcam , 11},
+	{IT_STRING  | IT_CVAR, NULL, "Orbital Looking"  , &cv_cam_orbit , 16},
+	{IT_STRING  | IT_CVAR, NULL, "Downhill Slope Adjustment", &cv_cam_adjust, 21},
+
+	{IT_HEADER,                                NULL, "Camera Positioning", NULL, 30},
+	{IT_STRING  | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Distance", &cv_cam_savedist[1][0], 36},
+	{IT_STRING  | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Height", &cv_cam_saveheight[1][0], 41},
+	{IT_STRING  | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Camera Spacial Speed", &cv_cam_speed, 46},
+	{IT_STRING  | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Rotation Speed", &cv_cam_turnmultiplier, 51},
+
+	{IT_HEADER,                           NULL, "Automatic Camera Options", NULL, 60},
+	{IT_STRING  | IT_CVAR | IT_CV_SLIDER, NULL, "Shift to player angle", &cv_cam_shiftfacing[0],  66},
+	{IT_STRING  | IT_CVAR | IT_CV_SLIDER, NULL, "Turn to player angle", &cv_cam_turnfacing[0],  71},
+	{IT_STRING  | IT_CVAR | IT_CV_SLIDER, NULL, "Turn to ability", &cv_cam_turnfacingability[0],  76},
+	{IT_STRING  | IT_CVAR | IT_CV_SLIDER, NULL, "Turn to spindash", &cv_cam_turnfacingspindash[0],  81},
+	{IT_STRING  | IT_CVAR | IT_CV_SLIDER, NULL, "Turn to input", &cv_cam_turnfacinginput[0],  86},
+
+	{IT_HEADER,            NULL, "Locked Camera Options", NULL, 95},
+	{IT_STRING  | IT_CVAR, NULL, "Lock button behavior", &cv_cam_centertoggle[0],  101},
+	{IT_STRING  | IT_CVAR, NULL, "Sideways movement", &cv_cam_lockedinput[0],  106},
+	{IT_STRING  | IT_CVAR, NULL, "Targeting assist", &cv_cam_lockonboss[0],  111},
+
+	{IT_HEADER,            NULL, "Display Options", NULL, 120},
+	{IT_STRING  | IT_CVAR, NULL, "Crosshair", &cv_crosshair, 126},
+};
+
+static menuitem_t OP_Camera2ExtendedOptionsMenu[] =
+{
+	{IT_HEADER,            NULL, "General Toggles", NULL, 0},
+	{IT_STRING  | IT_CVAR, NULL, "Third-person Camera"  , &cv_chasecam2 , 6},
+	{IT_STRING  | IT_CVAR, NULL, "Flip Camera with Gravity"  , &cv_flipcam2 , 11},
+	{IT_STRING  | IT_CVAR, NULL, "Orbital Looking"  , &cv_cam2_orbit , 16},
+	{IT_STRING  | IT_CVAR, NULL, "Downhill Slope Adjustment", &cv_cam2_adjust, 21},
+
+	{IT_HEADER,                                NULL, "Camera Positioning", NULL, 30},
+	{IT_STRING  | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Distance", &cv_cam_savedist[1][1], 36},
+	{IT_STRING  | IT_CVAR | IT_CV_INTEGERSTEP, NULL, "Camera Height", &cv_cam_saveheight[1][1], 41},
+	{IT_STRING  | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Camera Spacial Speed", &cv_cam2_speed, 46},
+	{IT_STRING  | IT_CVAR | IT_CV_FLOATSLIDER, NULL, "Rotation Speed", &cv_cam2_turnmultiplier, 51},
+
+	{IT_HEADER,                           NULL, "Automatic Camera Options", NULL, 60},
+	{IT_STRING  | IT_CVAR | IT_CV_SLIDER, NULL, "Shift to player angle", &cv_cam_shiftfacing[1],  66},
+	{IT_STRING  | IT_CVAR | IT_CV_SLIDER, NULL, "Turn to player angle", &cv_cam_turnfacing[1],  71},
+	{IT_STRING  | IT_CVAR | IT_CV_SLIDER, NULL, "Turn to ability", &cv_cam_turnfacingability[1],  76},
+	{IT_STRING  | IT_CVAR | IT_CV_SLIDER, NULL, "Turn to spindash", &cv_cam_turnfacingspindash[1],  81},
+	{IT_STRING  | IT_CVAR | IT_CV_SLIDER, NULL, "Turn to input", &cv_cam_turnfacinginput[1],  86},
+
+	{IT_HEADER,            NULL, "Locked Camera Options", NULL, 95},
+	{IT_STRING  | IT_CVAR, NULL, "Lock button behavior", &cv_cam_centertoggle[1],  101},
+	{IT_STRING  | IT_CVAR, NULL, "Sideways movement", &cv_cam_lockedinput[1],  106},
+	{IT_STRING  | IT_CVAR, NULL, "Targeting assist", &cv_cam_lockonboss[1],  111},
+
+	{IT_HEADER,            NULL, "Display Options", NULL, 120},
+	{IT_STRING  | IT_CVAR, NULL, "Crosshair", &cv_crosshair2, 126},
 };
 
 static menuitem_t OP_VideoOptionsMenu[] =
@@ -1927,12 +1998,24 @@ menu_t OP_MainDef = DEFAULTMENUSTYLE(
 menu_t OP_ChangeControlsDef = CONTROLMENUSTYLE(
 	MN_OP_MAIN + (MN_OP_CHANGECONTROLS << 12), // second level (<<6) set on runtime
 	OP_ChangeControlsMenu, &OP_MainDef);
-menu_t OP_P1ControlsDef = DEFAULTMENUSTYLE(
+
+menu_t OP_P1ControlsDef = {
 	MN_OP_MAIN + (MN_OP_P1CONTROLS << 6),
-	"M_CONTRO", OP_P1ControlsMenu, &OP_MainDef, 50, 30);
-menu_t OP_P2ControlsDef = DEFAULTMENUSTYLE(
+	"M_CONTRO",
+	sizeof(OP_P1ControlsMenu)/sizeof(menuitem_t),
+	&OP_MainDef,
+	OP_P1ControlsMenu,
+	M_DrawControlsDefMenu,
+	50, 30, 0, NULL};
+menu_t OP_P2ControlsDef = {
 	MN_OP_MAIN + (MN_OP_P2CONTROLS << 6),
-	"M_CONTRO", OP_P2ControlsMenu, &OP_MainDef, 50, 30);
+	"M_CONTRO",
+	sizeof(OP_P2ControlsMenu)/sizeof(menuitem_t),
+	&OP_MainDef,
+	OP_P2ControlsMenu,
+	M_DrawControlsDefMenu,
+	50, 30, 0, NULL};
+
 menu_t OP_MouseOptionsDef = DEFAULTMENUSTYLE(
 	MN_OP_MAIN + (MN_OP_P1CONTROLS << 6) + (MN_OP_P1MOUSE << 12),
 	"M_CONTRO", OP_MouseOptionsMenu, &OP_P1ControlsDef, 35, 30);
@@ -1957,12 +2040,41 @@ menu_t OP_JoystickSetDef =
 	0,
 	NULL
 };
-menu_t OP_CameraOptionsDef = DEFAULTMENUSTYLE(
+
+menu_t OP_CameraOptionsDef = {
 	MN_OP_MAIN + (MN_OP_P1CONTROLS << 6) + (MN_OP_P1CAMERA << 12),
-	"M_CONTRO", OP_CameraOptionsMenu, &OP_P1ControlsDef, 35, 30);
-menu_t OP_Camera2OptionsDef = DEFAULTMENUSTYLE(
+	"M_CONTRO",
+	sizeof (OP_CameraOptionsMenu)/sizeof (menuitem_t),
+	&OP_P1ControlsDef,
+	OP_CameraOptionsMenu,
+	M_DrawCameraOptionsMenu,
+	35, 30,
+	0,
+	NULL
+};
+menu_t OP_Camera2OptionsDef = {
 	MN_OP_MAIN + (MN_OP_P2CONTROLS << 6) + (MN_OP_P2CAMERA << 12),
-	"M_CONTRO", OP_Camera2OptionsMenu, &OP_P2ControlsDef, 35, 30);
+	"M_CONTRO",
+	sizeof (OP_Camera2OptionsMenu)/sizeof (menuitem_t),
+	&OP_P2ControlsDef,
+	OP_Camera2OptionsMenu,
+	M_DrawCameraOptionsMenu,
+	35, 30,
+	0,
+	NULL
+};
+
+static menuitem_t OP_PlaystyleMenu[] = {{IT_KEYHANDLER | IT_NOTHING, NULL, "", M_HandlePlaystyleMenu, 0}};
+
+menu_t OP_PlaystyleDef = {
+	MN_OP_MAIN + (MN_OP_P1CONTROLS << 6) + (MN_OP_PLAYSTYLE << 12),
+	NULL,
+	1,
+	&OP_P1ControlsDef,
+	OP_PlaystyleMenu,
+	M_DrawPlaystyleMenu,
+	0, 0, 0, NULL
+};
 
 
 menu_t OP_VideoOptionsDef =
@@ -3079,7 +3191,7 @@ boolean M_Responder(event_t *ev)
 		}
 		else if (ev->type == ev_joystick  && ev->data1 == 0 && joywait < I_GetTime())
 		{
-			const INT32 jdeadzone = (JOYAXISRANGE * cv_deadzone.value) / FRACUNIT;
+			const INT32 jdeadzone = (JOYAXISRANGE * cv_digitaldeadzone.value) / FRACUNIT;
 			if (ev->data3 != INT32_MAX)
 			{
 				if (Joystick.bGamepadStyle || abs(ev->data3) > jdeadzone)
@@ -4197,6 +4309,102 @@ static void M_DrawGenericMenu(void)
 	}
 }
 
+const char *PlaystyleNames[4] = {"Legacy", "Standard", "Simple", "Old Analog??"};
+const char *PlaystyleDesc[4] = {
+	// Legacy
+	"The play style used for\n"
+	"old-school SRB2.\n"
+	"\n"
+	"This play style is identical\n"
+	"to Standard, except that the\n"
+	"player always looks in the\n"
+	"direction of the camera."
+	,
+
+	// Standard
+	"The default play style,\n"
+	"designed for full control\n"
+	"with a keyboard and mouse.\n"
+	"\n"
+	"The camera rotates only when\n"
+	"you tell it to. The player\n"
+	"looks in the direction they're\n"
+	"moving, but acts in the direction\n"
+	"the camera is facing.\n"
+	"\n"
+	"Mastery of this play style will\n"
+	"open up the highest level of play!"
+	,
+
+	// Simple
+	"A play style designed for\n"
+	"gamepads and hassle-free play.\n"
+	"\n"
+	"The camera rotates automatically\n"
+	"as you move, and the player faces\n"
+	"and acts in the direction\n"
+	"they're moving.\n"
+	"\n"
+	"Hold \x82" "Center View\x80 to lock the\n"
+	"camera behind the player!\n"
+	,
+
+	// Old Analog
+	"I see.\n"
+	"\n"
+	"You really liked the old analog mode,\n"
+	"so when 2.2 came out, you opened up\n"
+	"your config file and brought it back.\n"
+	"\n"
+	"That's absolutely valid, but I implore\n"
+	"you to try the new Simple play style\n"
+	"instead!"
+};
+
+static UINT8 playstyle_activeplayer = 0, playstyle_currentchoice = 0;
+
+static void M_DrawControlsDefMenu(void)
+{
+	UINT8 opt = 0;
+
+	M_DrawGenericMenu();
+
+	if (currentMenu == &OP_P1ControlsDef)
+	{
+		opt = cv_directionchar[0].value ? 1 : 0;
+		opt = playstyle_currentchoice = cv_useranalog[0].value ? 3 - opt : opt;
+
+		if (opt == 2)
+		{
+			OP_CameraOptionsDef.menuitems = OP_CameraExtendedOptionsMenu;
+			OP_CameraOptionsDef.numitems = sizeof (OP_CameraExtendedOptionsMenu) / sizeof (menuitem_t);
+		}
+		else
+		{
+			OP_CameraOptionsDef.menuitems = OP_CameraOptionsMenu;
+			OP_CameraOptionsDef.numitems = sizeof (OP_CameraOptionsMenu) / sizeof (menuitem_t);
+		}
+	}
+	else
+	{
+		opt = cv_directionchar[1].value ? 1 : 0;
+		opt = playstyle_currentchoice = cv_useranalog[1].value ? 3 - opt : opt;
+
+		if (opt == 2)
+		{
+			OP_Camera2OptionsDef.menuitems = OP_Camera2ExtendedOptionsMenu;
+			OP_Camera2OptionsDef.numitems = sizeof (OP_Camera2ExtendedOptionsMenu) / sizeof (menuitem_t);
+		}
+		else
+		{
+			OP_Camera2OptionsDef.menuitems = OP_Camera2OptionsMenu;
+			OP_Camera2OptionsDef.numitems = sizeof (OP_Camera2OptionsMenu) / sizeof (menuitem_t);
+		}
+	}
+
+	V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + 80, V_YELLOWMAP, PlaystyleNames[opt]);
+}
+
 #define scrollareaheight 72
 
 // note that alphakey is multiplied by 2 for scrolling menus to allow greater usage in UINT8 range.
@@ -4208,7 +4416,9 @@ static void M_DrawGenericScrollMenu(void)
 	x = currentMenu->x;
 	y = currentMenu->y;
 
-	if ((currentMenu->menuitems[itemOn].alphaKey*2 - currentMenu->menuitems[0].alphaKey*2) <= scrollareaheight)
+	if (currentMenu->menuitems[currentMenu->numitems-1].alphaKey < scrollareaheight)
+		tempcentery = currentMenu->y; // Not tall enough to scroll, but this thinker is used in case it becomes so
+	else if ((currentMenu->menuitems[itemOn].alphaKey*2 - currentMenu->menuitems[0].alphaKey*2) <= scrollareaheight)
 		tempcentery = currentMenu->y - currentMenu->menuitems[0].alphaKey*2;
 	else if ((currentMenu->menuitems[currentMenu->numitems-1].alphaKey*2 - currentMenu->menuitems[itemOn].alphaKey*2) <= scrollareaheight)
 		tempcentery = currentMenu->y - currentMenu->menuitems[currentMenu->numitems-1].alphaKey*2 + 2*scrollareaheight;
@@ -7644,7 +7854,7 @@ void M_TutorialSaveControlResponse(INT32 ch)
 		CV_Set(&cv_usemouse, cv_usemouse.defaultvalue);
 		CV_Set(&cv_alwaysfreelook, cv_alwaysfreelook.defaultvalue);
 		CV_Set(&cv_mousemove, cv_mousemove.defaultvalue);
-		CV_Set(&cv_analog, cv_analog.defaultvalue);
+		CV_Set(&cv_analog[0], cv_analog[0].defaultvalue);
 		S_StartSound(NULL, sfx_itemup);
 	}
 	else
@@ -7662,13 +7872,13 @@ static void M_TutorialControlResponse(INT32 ch)
 			tutorialusemouse = cv_usemouse.value;
 			tutorialfreelook = cv_alwaysfreelook.value;
 			tutorialmousemove = cv_mousemove.value;
-			tutorialanalog = cv_analog.value;
+			tutorialanalog = cv_analog[0].value;
 
 			G_CopyControls(gamecontrol, gamecontroldefault[tutorialgcs], gcl_tutorial_full, num_gcl_tutorial_full);
 			CV_Set(&cv_usemouse, cv_usemouse.defaultvalue);
 			CV_Set(&cv_alwaysfreelook, cv_alwaysfreelook.defaultvalue);
 			CV_Set(&cv_mousemove, cv_mousemove.defaultvalue);
-			CV_Set(&cv_analog, cv_analog.defaultvalue);
+			CV_Set(&cv_analog[0], cv_analog[0].defaultvalue);
 
 			//S_StartSound(NULL, sfx_itemup);
 		}
@@ -11575,6 +11785,88 @@ static void M_ChangeControl(INT32 choice)
 	M_StartMessage(tmp, M_ChangecontrolResponse, MM_EVENTHANDLER);
 }
 
+static void M_Setup1PPlaystyleMenu(INT32 choice)
+{
+	(void)choice;
+
+	playstyle_activeplayer = 0;
+	OP_PlaystyleDef.prevMenu = &OP_P1ControlsDef;
+	M_SetupNextMenu(&OP_PlaystyleDef);
+}
+
+static void M_Setup2PPlaystyleMenu(INT32 choice)
+{
+	(void)choice;
+
+	playstyle_activeplayer = 1;
+	OP_PlaystyleDef.prevMenu = &OP_P2ControlsDef;
+	M_SetupNextMenu(&OP_PlaystyleDef);
+}
+
+static void M_DrawPlaystyleMenu(void)
+{
+	size_t i;
+
+	for (i = 0; i < 4; i++)
+	{
+		if (i != 3)
+			V_DrawCenteredString((i+1)*BASEVIDWIDTH/4, 20, (i == playstyle_currentchoice) ? V_YELLOWMAP : 0, PlaystyleNames[i]);
+
+		if (i == playstyle_currentchoice)
+		{
+			V_DrawScaledPatch((i+1)*BASEVIDWIDTH/4 - 8, 10, 0, W_CachePatchName("M_CURSOR", PU_CACHE));
+			V_DrawString(30, 50, V_ALLOWLOWERCASE, PlaystyleDesc[i]);
+		}
+	}
+}
+
+static void M_HandlePlaystyleMenu(INT32 choice)
+{
+	switch (choice)
+	{
+	case KEY_ESCAPE:
+	case KEY_BACKSPACE:
+		M_SetupNextMenu(currentMenu->prevMenu);
+		break;
+
+	case KEY_ENTER:
+		S_StartSound(NULL, sfx_menu1);
+		CV_SetValue((playstyle_activeplayer ? &cv_directionchar[1] : &cv_directionchar[0]), playstyle_currentchoice ? 1 : 0);
+		CV_SetValue((playstyle_activeplayer ? &cv_useranalog[1] : &cv_useranalog[0]), playstyle_currentchoice/2);
+
+		if (playstyle_activeplayer)
+			CV_UpdateCam2Dist();
+		else
+			CV_UpdateCamDist();
+
+		M_SetupNextMenu(currentMenu->prevMenu);
+		break;
+
+	case KEY_LEFTARROW:
+		S_StartSound(NULL, sfx_menu1);
+		playstyle_currentchoice = (playstyle_currentchoice+2)%3;
+		break;
+
+	case KEY_RIGHTARROW:
+		S_StartSound(NULL, sfx_menu1);
+		playstyle_currentchoice = (playstyle_currentchoice+1)%3;
+		break;
+	}
+}
+
+static void M_DrawCameraOptionsMenu(void)
+{
+	M_DrawGenericScrollMenu();
+
+	if (gamestate == GS_LEVEL && (paused || P_AutoPause()))
+	{
+		if (currentMenu == &OP_Camera2OptionsDef && splitscreen && camera2.chase)
+			P_MoveChaseCamera(&players[secondarydisplayplayer], &camera2, false);
+		if (currentMenu == &OP_CameraOptionsDef && camera.chase)
+			P_MoveChaseCamera(&players[displayplayer], &camera, false);
+	}
+}
+
 // ===============
 // VIDEO MODE MENU
 // ===============
diff --git a/src/m_menu.h b/src/m_menu.h
index 48a4ba6f0c8346f530f0b4efa0c27cb6f21093bc..a563a18dcf4e08e909831a765609bea8ac1f7bc0 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 2011-2016 by Matthew "Inuyasha" Walsh.
+// Copyright (C) 2011-2016 by Matthew "Kaito Sinclaire" Walsh.
 // Copyright (C) 1999-2019 by Sonic Team Junior.
 //
 // This program is free software distributed under the
@@ -81,6 +81,8 @@ typedef enum
 	MN_OP_P2JOYSTICK,
 	MN_OP_P2CAMERA,
 
+	MN_OP_PLAYSTYLE,
+
 	MN_OP_VIDEO,
 	MN_OP_VIDEOMODE,
 	MN_OP_COLOR,
diff --git a/src/m_misc.c b/src/m_misc.c
index edb24ab1ec9ce3996f34eafad1bd1909663a2a28..a5091c257294fea8b84ca5b5d19f4586de9cbf87 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -600,12 +600,12 @@ void M_SaveConfig(const char *filename)
 		CV_SetValue(&cv_usemouse, tutorialusemouse);
 		CV_SetValue(&cv_alwaysfreelook, tutorialfreelook);
 		CV_SetValue(&cv_mousemove, tutorialmousemove);
-		CV_SetValue(&cv_analog, tutorialanalog);
+		CV_SetValue(&cv_analog[0], tutorialanalog);
 		CV_SaveVariables(f);
 		CV_Set(&cv_usemouse, cv_usemouse.defaultvalue);
 		CV_Set(&cv_alwaysfreelook, cv_alwaysfreelook.defaultvalue);
 		CV_Set(&cv_mousemove, cv_mousemove.defaultvalue);
-		CV_Set(&cv_analog, cv_analog.defaultvalue);
+		CV_Set(&cv_analog[0], cv_analog[0].defaultvalue);
 	}
 	else
 		CV_SaveVariables(f);
diff --git a/src/m_random.c b/src/m_random.c
index 8a7b62b196ce8f944c66adf59232138692dbd0c1..8fd0e1e4e011d21566611db643763063bc28c668 100644
--- a/src/m_random.c
+++ b/src/m_random.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 2012-2016 by Matthew "Inuyasha" Walsh.
+// Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh.
 // Copyright (C) 1999-2019 by Sonic Team Junior.
 //
 // This program is free software distributed under the
diff --git a/src/m_random.h b/src/m_random.h
index fb14249bca4266cc42b4f8e4fa3db8c82b9d14f6..5efd3e02ca9cf96f2cd86574c9eca5d788cbc444 100644
--- a/src/m_random.h
+++ b/src/m_random.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 2012-2016 by Matthew "Inuyasha" Walsh.
+// Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh.
 // Copyright (C) 1999-2019 by Sonic Team Junior.
 //
 // This program is free software distributed under the
diff --git a/src/p_enemy.c b/src/p_enemy.c
index ce201844ff74c9b093b591d52b2a052686c4e586..bd7b81d40fd8c5ff305360b200bb07c6491e2e42 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -14603,12 +14603,9 @@ void A_RolloutRock(mobj_t *actor)
 	if (!actor->tracer || P_MobjWasRemoved(actor->tracer) || !actor->tracer->health)
 		actor->flags |= MF_PUSHABLE;
 
-	if (!(actor->flags & MF_PUSHABLE)) // if being ridden, don't disappear
-		actor->fuse = 0;
-	else if (!actor->fuse && actor->movecount == 1) // otherwise if rock has moved, set its fuse
+	if (!(actor->flags & MF_PUSHABLE) || (actor->movecount != 1)) // if being ridden or haven't moved, don't disappear
 		actor->fuse = actor->info->painchance;
-
-	if (actor->fuse && actor->fuse < 2*TICRATE)
+	else if (actor->fuse < 2*TICRATE)
 		actor->flags2 ^= MF2_DONTDRAW;
 
 }
diff --git a/src/p_local.h b/src/p_local.h
index 88deb0942ffa0a49b6244873edc59e7c87f8c030..a5f3d313ce25c71fc50a3695ea8eafd7a01831f8 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -120,6 +120,10 @@ extern consvar_t cv_cam_speed, cv_cam_rotate, cv_cam_rotspeed, cv_cam_turnmultip
 extern consvar_t cv_cam2_dist, cv_cam2_still, cv_cam2_height;
 extern consvar_t cv_cam2_speed, cv_cam2_rotate, cv_cam2_rotspeed, cv_cam2_turnmultiplier, cv_cam2_orbit, cv_cam2_adjust;
 
+extern consvar_t cv_cam_savedist[2][2], cv_cam_saveheight[2][2];
+void CV_UpdateCamDist(void);
+void CV_UpdateCam2Dist(void);
+
 extern fixed_t t_cam_dist, t_cam_height, t_cam_rotate;
 extern fixed_t t_cam2_dist, t_cam2_height, t_cam2_rotate;
 
@@ -181,16 +185,14 @@ fixed_t P_ReturnThrustX(mobj_t *mo, angle_t angle, fixed_t move);
 fixed_t P_ReturnThrustY(mobj_t *mo, angle_t angle, fixed_t move);
 void P_InstaThrustEvenIn2D(mobj_t *mo, angle_t angle, fixed_t move);
 
+mobj_t *P_LookForFocusTarget(player_t *player, mobj_t *exclude, SINT8 direction, UINT8 lockonflags);
+
 mobj_t *P_LookForEnemies(player_t *player, boolean nonenemies, boolean bullet);
 void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius);
 boolean P_HomingAttack(mobj_t *source, mobj_t *enemy); /// \todo doesn't belong in p_user
 boolean P_SuperReady(player_t *player);
 void P_DoJump(player_t *player, boolean soundandstate);
-#if 0
-boolean P_AnalogMove(player_t *player);
-#else
-#define P_AnalogMove(player) (player->pflags & PF_ANALOGMODE)
-#endif
+#define P_AnalogMove(player) (P_ControlStyle(player) == CS_LMAOGALOG)
 boolean P_TransferToNextMare(player_t *player);
 UINT8 P_FindLowestMare(void);
 void P_FindEmerald(void);
diff --git a/src/p_map.c b/src/p_map.c
index a522418f7d13f05b3a318972eef73ad71eb4c4ad..40fee7b46f8ccc7cfe27fc6a8639d3887efbd4c3 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -377,7 +377,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 		{
 			object->angle = object->player->drawangle = spring->angle;
 
-			if (!demoplayback || P_AnalogMove(object->player))
+			if (!demoplayback || P_ControlStyle(object->player) == CS_LMAOGALOG)
 			{
 				if (object->player == &players[consoleplayer])
 					localangle = spring->angle;
@@ -426,7 +426,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
 				object->player->pflags |= pflags;
 				object->player->secondjump = secondjump;
 			}
-			else if (object->player->dashmode >= 3*TICRATE)
+			else if (object->player->dashmode >= DASHMODE_THRESHOLD)
 				P_SetPlayerMobjState(object, S_PLAY_DASH);
 			else if (P_IsObjectOnGround(object) && horizspeed >= FixedMul(object->player->runspeed, object->scale))
 				P_SetPlayerMobjState(object, S_PLAY_RUN);
@@ -632,7 +632,7 @@ static void P_DoTailsCarry(player_t *sonic, player_t *tails)
 		&& P_MobjFlip(tails->mo)*sonic->mo->momz <= 0)
 	{
 		if (sonic-players == consoleplayer && botingame)
-			CV_SetValue(&cv_analog2, false);
+			CV_SetValue(&cv_analog[1], false);
 		P_ResetPlayer(sonic);
 		P_SetTarget(&sonic->mo->tracer, tails->mo);
 		sonic->powers[pw_carry] = CR_PLAYER;
@@ -644,7 +644,7 @@ static void P_DoTailsCarry(player_t *sonic, player_t *tails)
 	}
 	else {
 		if (sonic-players == consoleplayer && botingame)
-			CV_SetValue(&cv_analog2, true);
+			CV_SetValue(&cv_analog[1], true);
 		P_SetTarget(&sonic->mo->tracer, NULL);
 		sonic->powers[pw_carry] = CR_NONE;
 	}
@@ -806,7 +806,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 
 	// SF_DASHMODE users destroy spikes and monitors, CA_TWINSPIN users and CA2_MELEE users destroy spikes.
 	if ((tmthing->player)
-		&& (((tmthing->player->charflags & SF_DASHMODE) && (tmthing->player->dashmode >= 3*TICRATE)
+		&& ((((tmthing->player->charflags & (SF_DASHMODE|SF_MACHINE)) == (SF_DASHMODE|SF_MACHINE)) && (tmthing->player->dashmode >= DASHMODE_THRESHOLD)
 		&& (thing->flags & (MF_MONITOR)
 		|| (thing->type == MT_SPIKE
 		|| thing->type == MT_WALLSPIKE)))
@@ -1327,7 +1327,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 
 			thing->angle = tmthing->angle;
 
-			if (!demoplayback || P_AnalogMove(thing->player))
+			if (!demoplayback || P_ControlStyle(thing->player) == CS_LMAOGALOG)
 			{
 				if (thing->player == &players[consoleplayer])
 					localangle = thing->angle;
@@ -1626,7 +1626,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 	}
 	else if (thing->player) {
 		if (thing->player-players == consoleplayer && botingame)
-			CV_SetValue(&cv_analog2, true);
+			CV_SetValue(&cv_analog[1], true);
 		if (thing->player->powers[pw_carry] == CR_PLAYER)
 		{
 			P_SetTarget(&thing->tracer, NULL);
@@ -2005,7 +2005,7 @@ static boolean PIT_CheckLine(line_t *ld)
 
 	if (lowfloor < tmdropoffz)
 		tmdropoffz = lowfloor;
-	
+
 	return true;
 }
 
@@ -3509,7 +3509,7 @@ isblocking:
 			&& canclimb)
 			{
 				slidemo->angle = climbangle;
-				/*if (!demoplayback || P_AnalogMove(slidemo->player))
+				/*if (!demoplayback || P_ControlStyle(slidemo->player) == CS_LMAOGALOG)
 				{
 					if (slidemo->player == &players[consoleplayer])
 						localangle = slidemo->angle;
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 3d8d81d9b65adee2be7012c10e103b0c9e323908..7d83fffb063c2acc8b72803a56721dd16351f617 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -1892,7 +1892,7 @@ void P_XYMovement(mobj_t *mo)
 #endif
 
 	// Pushables can break some blocks
-	if (CheckForBustableBlocks && mo->flags & MF_PUSHABLE)
+	if (CheckForBustableBlocks && ((mo->flags & MF_PUSHABLE) || ((mo->info->flags & MF_PUSHABLE) && mo->fuse)))
 		P_PushableCheckBustables(mo);
 
 	if (!P_TryMove(mo, mo->x + xmove, mo->y + ymove, true)
@@ -3298,7 +3298,7 @@ boolean P_CanRunOnWater(player_t *player, ffloor_t *rover)
 		*rover->topheight;
 
 	if (!player->powers[pw_carry] && !player->homing
-		&& ((player->powers[pw_super] || player->charflags & SF_RUNONWATER || player->dashmode >= 3*TICRATE) && player->mo->ceilingz-topheight >= player->mo->height)
+		&& ((player->powers[pw_super] || player->charflags & SF_RUNONWATER || player->dashmode >= DASHMODE_THRESHOLD) && player->mo->ceilingz-topheight >= player->mo->height)
 		&& (rover->flags & FF_SWIMMABLE) && !(player->pflags & PF_SPINNING) && player->speed > FixedMul(player->runspeed, player->mo->scale)
 		&& !(player->pflags & PF_SLIDING)
 		&& abs(player->mo->z - topheight) < FixedMul(30*FRACUNIT, player->mo->scale))
@@ -7286,7 +7286,7 @@ static void P_FlameJetSceneryThink(mobj_t *mobj)
 	if (!(mobj->flags2 & MF2_FIRING))
 		return;
 
-	if ((leveltime & 3) == 0)
+	if ((leveltime & 3) != 0)
 		return;
 
 	// Wave the flames back and forth. Reactiontime determines which direction it's going.
@@ -7325,7 +7325,7 @@ static void P_VerticalFlameJetSceneryThink(mobj_t *mobj)
 	if (!(mobj->flags2 & MF2_FIRING))
 		return;
 
-	if ((leveltime & 3) == 0)
+	if ((leveltime & 3) != 0)
 		return;
 
 	// Wave the flames back and forth. Reactiontime determines which direction it's going.
@@ -7977,7 +7977,7 @@ static void P_MobjSceneryThink(mobj_t *mobj)
 		mobj->x = mobj->extravalue1 + P_ReturnThrustX(mobj, mobj->movedir, mobj->cvmem*mobj->scale);
 		mobj->y = mobj->extravalue2 + P_ReturnThrustY(mobj, mobj->movedir, mobj->cvmem*mobj->scale);
 		P_SetThingPosition(mobj);
-		
+
 		if (!mobj->fuse)
 		{
 #ifdef HAVE_BLUA
diff --git a/src/p_polyobj.c b/src/p_polyobj.c
index 36a9ba61356a07aec6ab7e118890ec33183c39a7..cd0a44bb4a78e913addbd4e91a64cc1791c30b2a 100644
--- a/src/p_polyobj.c
+++ b/src/p_polyobj.c
@@ -1351,9 +1351,9 @@ static void Polyobj_rotateThings(polyobj_t *po, vertex_t origin, angle_t delta,
 					if (turnthings == 2 || (turnthings == 1 && !mo->player)) {
 						mo->angle += delta;
 						if (mo->player == &players[consoleplayer])
-							localangle = mo->angle;
+							localangle += delta;
 						else if (mo->player == &players[secondarydisplayplayer])
-							localangle2 = mo->angle;
+							localangle2 += delta;
 					}
 				}
 			}
diff --git a/src/p_setup.c b/src/p_setup.c
index 322e2d3b87eee637f515ef32a978b51c8cd1ff3b..189771d9e86397412122c1f9d05125dc0e013e95 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -1127,7 +1127,6 @@ static void P_LoadSidedefs(UINT8 *data)
 		// Special info stored in texture fields!
 		switch (sd->special)
 		{
-			case 63: // Fake floor/ceiling planes
 			case 606: //SoM: 4/4/2000: Just colormap transfer
 			case 447: // Change colormap of tagged sectors! -- Monster Iestyn 14/06/18
 			case 455: // Fade colormaps! mazmazz 9/12/2018 (:flag_us:)
@@ -2857,7 +2856,7 @@ static void P_InitLevelSettings(void)
 	}
 
 	if (botingame)
-		CV_SetValue(&cv_analog2, true);
+		CV_SetValue(&cv_analog[1], true);
 }
 
 // Respawns all the mapthings and mobjs in the map from the already loaded map data.
@@ -3168,26 +3167,26 @@ static void P_InitCamera(void)
 		if (!cv_cam2_rotate.changed)
 			CV_Set(&cv_cam2_rotate, cv_cam2_rotate.defaultvalue);
 
-		if (!cv_analog.changed)
-			CV_SetValue(&cv_analog, 0);
-		if (!cv_analog2.changed)
-			CV_SetValue(&cv_analog2, 0);
+		if (!cv_analog[0].changed)
+			CV_SetValue(&cv_analog[0], 0);
+		if (!cv_analog[1].changed)
+			CV_SetValue(&cv_analog[1], 0);
 
 		displayplayer = consoleplayer; // Start with your OWN view, please!
 	}
 
 	if (twodlevel)
 	{
-		CV_SetValue(&cv_analog, false);
-		CV_SetValue(&cv_analog2, false);
+		CV_SetValue(&cv_analog[0], false);
+		CV_SetValue(&cv_analog[1], false);
 	}
 	else
 	{
-		if (cv_useranalog.value)
-			CV_SetValue(&cv_analog, true);
+		if (cv_useranalog[0].value)
+			CV_SetValue(&cv_analog[0], true);
 
-		if ((splitscreen && cv_useranalog2.value) || botingame)
-			CV_SetValue(&cv_analog2, true);
+		if ((splitscreen && cv_useranalog[1].value) || botingame)
+			CV_SetValue(&cv_analog[1], true);
 	}
 }
 
@@ -3434,6 +3433,9 @@ boolean P_LoadLevel(boolean fromnetsave)
 		/*if (!cv_cam_speed.changed)
 			CV_Set(&cv_cam_speed, cv_cam_speed.defaultvalue);*/
 
+		CV_UpdateCamDist();
+		CV_UpdateCam2Dist();
+
 		if (!cv_chasecam.changed)
 			CV_SetValue(&cv_chasecam, chase);
 
diff --git a/src/p_spec.c b/src/p_spec.c
index ba0a7216e61b413de39474acb3be243a6a9e706c..1fae65608f18ded1a59c2ef2c03eef8d1c5aa0c0 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -1888,7 +1888,7 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
 		while (node)
 		{
 			mo = node->m_thing;
-			if (mo->flags & MF_PUSHABLE)
+			if ((mo->flags & MF_PUSHABLE) || ((mo->info->flags & MF_PUSHABLE) && mo->fuse))
 				numpush++;
 			node = node->m_thinglist_next;
 		}
@@ -4615,7 +4615,7 @@ DoneSection2:
 
 				player->mo->angle = player->drawangle = lineangle;
 
-				if (!demoplayback || P_AnalogMove(player))
+				if (!demoplayback || P_ControlStyle(player) == CS_LMAOGALOG)
 				{
 					if (player == &players[consoleplayer])
 						localangle = player->mo->angle;
@@ -8975,7 +8975,8 @@ void T_Pusher(pusher_t *p)
 			|| thing->type == MT_EXTRALARGEBUBBLE))
 			continue;
 
-		if (!(thing->flags & MF_PUSHABLE) && !(thing->type == MT_PLAYER
+		if (!((thing->flags & MF_PUSHABLE) || ((thing->info->flags & MF_PUSHABLE) && thing->fuse))
+			&& !(thing->type == MT_PLAYER
 			|| thing->type == MT_SMALLBUBBLE
 			|| thing->type == MT_MEDIUMBUBBLE
 			|| thing->type == MT_EXTRALARGEBUBBLE
@@ -9128,7 +9129,7 @@ void T_Pusher(pusher_t *p)
 				thing->player->pflags |= PF_SLIDING;
 				thing->angle = R_PointToAngle2 (0, 0, xspeed<<(FRACBITS-PUSH_FACTOR), yspeed<<(FRACBITS-PUSH_FACTOR));
 
-				if (!demoplayback || P_AnalogMove(thing->player))
+				if (!demoplayback || P_ControlStyle(thing->player) == CS_LMAOGALOG)
 				{
 					if (thing->player == &players[consoleplayer])
 					{
diff --git a/src/p_user.c b/src/p_user.c
index d0b49000fb61adf01b723dde4a11c7e80a176b36..b40e647b6e6dc838d90d0af4b0fc1837531e5097 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -42,6 +42,8 @@
 #include "b_bot.h"
 // Objectplace
 #include "m_cheat.h"
+// Thok camera snap (ctrl-f "chalupa")
+#include "g_input.h"
 
 #ifdef HW3SOUND
 #include "hardware/hw3sound.h"
@@ -347,11 +349,11 @@ void P_GiveEmerald(boolean spawnObj)
 				continue;
 			P_SetTarget(&emmo->target, players[i].mo);
 			P_SetMobjState(emmo, mobjinfo[MT_GOTEMERALD].meleestate + em);
-			
+
 			// Make sure we're not being carried before our tracer is changed
 			if (players[i].powers[pw_carry] != CR_NIGHTSMODE)
 				players[i].powers[pw_carry] = CR_NONE;
-			
+
 			P_SetTarget(&players[i].mo->tracer, emmo);
 
 			if (pnum == 255)
@@ -1063,7 +1065,7 @@ void P_ResetPlayer(player_t *player)
 	player->onconveyor = 0;
 	player->skidtime = 0;
 	if (player-players == consoleplayer && botingame)
-		CV_SetValue(&cv_analog2, true);
+		CV_SetValue(&cv_analog[1], true);
 }
 
 // P_PlayerCanDamage
@@ -3578,7 +3580,7 @@ static void P_DoClimbing(player_t *player)
 	}
 
 #define CLIMBCONEMAX FixedAngle(90*FRACUNIT)
-	if (!demoplayback || P_AnalogMove(player))
+	if (!demoplayback || P_ControlStyle(player) == CS_LMAOGALOG)
 	{
 		if (player == &players[consoleplayer])
 		{
@@ -4393,7 +4395,7 @@ void P_DoJump(player_t *player, boolean soundandstate)
 
 		player->drawangle = player->mo->angle = player->mo->angle - ANGLE_180; // Turn around from the wall you were climbing.
 
-		if (!demoplayback || P_AnalogMove(player))
+		if (!demoplayback || P_ControlStyle(player) == CS_LMAOGALOG)
 		{
 			if (player == &players[consoleplayer])
 				localangle = player->mo->angle; // Adjust the local control angle.
@@ -4437,7 +4439,7 @@ void P_DoJump(player_t *player, boolean soundandstate)
 			player->powers[pw_carry] = CR_NONE;
 			P_SetTarget(&player->mo->tracer, NULL);
 			if (player-players == consoleplayer && botingame)
-				CV_SetValue(&cv_analog2, true);
+				CV_SetValue(&cv_analog[1], true);
 		}
 		else if (player->powers[pw_carry] == CR_GENERIC)
 		{
@@ -4641,7 +4643,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 
 					if (player->dashspeed > player->maxdash)
 						player->dashspeed = player->maxdash;
-					
+
 					if (player->dashspeed < player->maxdash && player->mindash != player->maxdash)
 					{
 #define chargecalculation (6*(player->dashspeed - player->mindash))/(player->maxdash - player->mindash)
@@ -4724,7 +4726,7 @@ static void P_DoSpinAbility(player_t *player, ticcmd_t *cmd)
 							{
 								player->mo->angle = R_PointToAngle2(player->mo->x, player->mo->y, lockon->x, lockon->y);
 								bullet = P_SpawnPointMissile(player->mo, lockon->x, lockon->y, zpos(lockon), player->revitem, player->mo->x, player->mo->y, zpos(player->mo));
-								if (!demoplayback || P_AnalogMove(player))
+								if (!demoplayback || P_ControlStyle(player) == CS_LMAOGALOG)
 								{
 									if (player == &players[consoleplayer])
 										localangle = player->mo->angle;
@@ -5353,6 +5355,16 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 
 						player->pflags &= ~(PF_SPINNING|PF_STARTDASH);
 						player->pflags |= PF_THOKKED;
+
+						// Change localangle to match for simple controls? (P.S. chalupa)
+						// disabled because it seemed to disorient people and Z-targeting exists now
+						/*if (!demoplayback)
+						{
+							if (player == &players[consoleplayer] && cv_cam_turnfacingability[0].value > 0 && !(PLAYER1INPUTDOWN(gc_turnleft) || PLAYER1INPUTDOWN(gc_turnright)))
+								localangle = player->mo->angle;
+							else if (player == &players[secondarydisplayplayer] && cv_cam_turnfacingability[1].value > 0 && !(PLAYER2INPUTDOWN(gc_turnleft) || PLAYER2INPUTDOWN(gc_turnright)))
+								localangle2 = player->mo->angle;
+						}*/
 					}
 					break;
 
@@ -5607,13 +5619,6 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 	}
 }
 
-#if 0
-boolean P_AnalogMove(player_t *player)
-{
-	return player->pflags & PF_ANALOGMODE;
-}
-#endif
-
 //
 // P_GetPlayerControlDirection
 //
@@ -5652,7 +5657,7 @@ INT32 P_GetPlayerControlDirection(player_t *player)
 		origtempangle = tempangle = 0; // relative to the axis rather than the player!
 		controlplayerdirection = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy);
 	}
-	else if (P_AnalogMove(player) && thiscam->chase)
+	else if ((P_ControlStyle(player) & CS_LMAOGALOG) && thiscam->chase)
 	{
 		if (player->awayviewtics)
 			origtempangle = tempangle = player->awayviewmobj->angle;
@@ -5878,7 +5883,7 @@ static void P_3dMovement(player_t *player)
 	INT32 mforward = 0, mbackward = 0;
 	angle_t dangle; // replaces old quadrants bits
 	fixed_t normalspd = FixedMul(player->normalspeed, player->mo->scale);
-	boolean analogmove = false;
+	controlstyle_e controlstyle;
 	boolean spin = ((onground = P_IsObjectOnGround(player->mo)) && (player->pflags & (PF_SPINNING|PF_THOKKED)) == PF_SPINNING && (player->rmomx || player->rmomy) && !(player->pflags & PF_STARTDASH));
 	fixed_t oldMagnitude, newMagnitude;
 #ifdef ESLOPE
@@ -5891,7 +5896,7 @@ static void P_3dMovement(player_t *player)
 	// Get the old momentum; this will be needed at the end of the function! -SH
 	oldMagnitude = R_PointToDist2(player->mo->momx - player->cmomx, player->mo->momy - player->cmomy, 0, 0);
 
-	analogmove = P_AnalogMove(player);
+	controlstyle = P_ControlStyle(player);
 
 	cmd = &player->cmd;
 
@@ -5918,7 +5923,7 @@ static void P_3dMovement(player_t *player)
 		}
 	}
 
-	if (analogmove)
+	if (controlstyle & CS_LMAOGALOG)
 	{
 		movepushangle = (cmd->angleturn<<16 /* not FRACBITS */);
 	}
@@ -6064,7 +6069,7 @@ static void P_3dMovement(player_t *player)
 				P_SetObjectMomZ(player->mo, FixedDiv(cmd->forwardmove*FRACUNIT, 15*FRACUNIT>>1), false);
 		}
 	}
-	else if (!analogmove
+	else if (!(controlstyle == CS_LMAOGALOG)
 		&& cmd->forwardmove != 0 && !(player->pflags & PF_GLIDING || player->exiting
 		|| (P_PlayerInPain(player) && !onground)))
 	{
@@ -6103,7 +6108,7 @@ static void P_3dMovement(player_t *player)
 			P_InstaThrust(player->mo, player->mo->angle-ANGLE_90, FixedDiv(cmd->sidemove*player->mo->scale, 15*FRACUNIT>>1));
 	}
 	// Analog movement control
-	else if (analogmove)
+	else if (controlstyle == CS_LMAOGALOG)
 	{
 		if (!(player->pflags & PF_GLIDING || player->exiting || P_PlayerInPain(player)))
 		{
@@ -8114,8 +8119,33 @@ static void P_MovePlayer(player_t *player)
 		P_2dMovement(player);
 	else
 	{
-		if (!player->climbing && (!P_AnalogMove(player)))
-			player->mo->angle = (cmd->angleturn<<16 /* not FRACBITS */);
+		if (!player->climbing)
+		{
+			switch (P_ControlStyle(player))
+			{
+			case CS_LEGACY:
+			case CS_STANDARD:
+				player->mo->angle = (cmd->angleturn<<16 /* not FRACBITS */);
+				break;
+
+			case CS_LMAOGALOG:
+				break; // handled elsewhere
+
+			case CS_SIMPLE:
+				if (cmd->forwardmove || cmd->sidemove)
+				{
+					angle_t controlangle = R_PointToAngle2(0, 0, cmd->forwardmove << FRACBITS, -cmd->sidemove << FRACBITS);
+					player->mo->angle = (cmd->angleturn<<16 /* not FRACBITS */) + controlangle;
+				}
+				else
+				{
+					angle_t drawangleoffset = (player->powers[pw_carry] == CR_ROLLOUT) ? ANGLE_180 : 0;
+					player->mo->angle = player->drawangle + drawangleoffset;
+				}
+
+				break;
+			}
+		}
 
 		ticruned++;
 		if ((cmd->angleturn & TICCMD_RECEIVED) == 0)
@@ -8241,7 +8271,7 @@ static void P_MovePlayer(player_t *player)
 	if (player->pflags & PF_GLIDING)
 	{
 		mobj_t *mo = player->mo; // seriously why isn't this at the top of the function hngngngng
-		fixed_t leeway = !P_AnalogMove(player) ? FixedAngle(cmd->sidemove*(FRACUNIT)) : 0;
+		fixed_t leeway = (P_ControlStyle(player) != CS_LMAOGALOG) ? FixedAngle(cmd->sidemove*(FRACUNIT)) : 0;
 		fixed_t glidespeed = player->actionspd;
 		fixed_t momx = mo->momx - player->cmomx, momy = mo->momy - player->cmomy;
 		angle_t angle, moveangle = R_PointToAngle2(0, 0, momx, momy);
@@ -8513,7 +8543,7 @@ static void P_MovePlayer(player_t *player)
 	//////////////////
 
 	// This really looks like it should be moved to P_3dMovement. -Red
-	if (P_AnalogMove(player)
+	if (P_ControlStyle(player) == CS_LMAOGALOG
 		&& (cmd->forwardmove != 0 || cmd->sidemove != 0) && !player->climbing && !twodlevel && !(player->mo->flags2 & MF2_TWOD))
 	{
 		// If travelling slow enough, face the way the controls
@@ -9117,6 +9147,132 @@ void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius)
 	}
 }
 
+//
+// P_LookForFocusTarget
+// Looks for a target for a player to focus on, for Z-targeting etc.
+// exclude would be the current focus target, to ignore.
+// direction, if set, requires the target to be to the left (1) or right (-1) of the angle
+// mobjflags can be used to limit the flags of objects that can be focused
+//
+mobj_t *P_LookForFocusTarget(player_t *player, mobj_t *exclude, SINT8 direction, UINT8 lockonflags)
+{
+	mobj_t *mo;
+	thinker_t *think;
+	mobj_t *closestmo = NULL;
+	const fixed_t maxdist = 2560*player->mo->scale;
+	const angle_t span = ANGLE_45;
+	fixed_t dist, closestdist = 0;
+	angle_t dangle, closestdangle = 0;
+
+	for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
+	{
+		if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
+
+		mo = (mobj_t *)think;
+
+		if (mo->flags & MF_NOCLIPTHING)
+			continue;
+
+		if (mo == player->mo || mo == exclude)
+			continue;
+
+		if (mo->health <= 0) // dead
+			continue;
+
+		switch (mo->type)
+		{
+		case MT_TNTBARREL:
+			if (lockonflags & LOCK_INTERESTS)
+				break;
+			/*fallthru*/
+		case MT_PLAYER: // Don't chase other players!
+		case MT_DETON:
+			continue; // Don't be STUPID, Sonic!
+
+		case MT_FAKEMOBILE:
+			if (!(lockonflags & LOCK_BOSS))
+				continue;
+			break;
+
+		case MT_EGGSHIELD:
+			if (!(lockonflags & LOCK_ENEMY))
+				continue;
+			break;
+
+		case MT_EGGSTATUE:
+			if (tutorialmode)
+				break; // Always focus egg statue in the tutorial
+			/*fallthru*/
+		default:
+
+			if ((lockonflags & LOCK_BOSS) && ((mo->flags & (MF_BOSS|MF_SHOOTABLE)) == (MF_BOSS|MF_SHOOTABLE))) // allows if it has the flags desired XOR it has the invert aimable flag
+			{
+				if (mo->flags2 & MF2_FRET)
+					continue;
+				break;
+			}
+
+			if ((lockonflags & LOCK_ENEMY) && (!((mo->flags & (MF_ENEMY|MF_SHOOTABLE)) == (MF_ENEMY|MF_SHOOTABLE)) != !(mo->flags2 & MF2_INVERTAIMABLE))) // allows if it has the flags desired XOR it has the invert aimable flag
+				break;
+
+			if ((lockonflags & LOCK_INTERESTS) && (mo->flags & (MF_PUSHABLE|MF_MONITOR))) // allows if it has the flags desired XOR it has the invert aimable flag
+				break;
+
+			continue; // not a valid object
+		}
+
+		{
+			fixed_t zdist = (player->mo->z + player->mo->height/2) - (mo->z + mo->height/2);
+			dist = P_AproxDistance(player->mo->x-mo->x, player->mo->y-mo->y);
+
+			if (abs(zdist) > dist)
+				continue; // Don't home outside of desired angle!
+
+			dist = P_AproxDistance(dist, zdist);
+			if (dist > maxdist)
+				continue; // out of range
+		}
+
+		if ((twodlevel || player->mo->flags2 & MF2_TWOD)
+		&& abs(player->mo->y-mo->y) > player->mo->radius)
+			continue; // not in your 2d plane
+
+		dangle = R_PointToAngle2(player->mo->x, player->mo->y, mo->x, mo->y) - (
+			!exclude ? player->mo->angle : R_PointToAngle2(player->mo->x, player->mo->y, exclude->x, exclude->y)
+		);
+
+		if (direction)
+		{
+			if (direction == 1 && dangle > ANGLE_180)
+				continue; // To the right of the player
+			if (direction == -1 && dangle < ANGLE_180)
+				continue; // To the left of the player
+		}
+
+		if (dangle > ANGLE_180)
+			dangle = InvAngle(dangle);
+
+		if (dangle > span)
+			continue; // behind back
+
+		// Inflate dist by angle difference to bias toward objects at a closer angle
+		dist = FixedDiv(dist, FINECOSINE(dangle>>ANGLETOFINESHIFT)*3);
+
+		if (closestmo && (exclude ? (dangle > closestdangle) : (dist > closestdist)))
+			continue;
+
+		if (!P_CheckSight(player->mo, mo))
+			continue; // out of sight
+
+		closestmo = mo;
+		closestdist = dist;
+		closestdangle = dangle;
+	}
+
+	return closestmo;
+}
+
 //
 // P_LookForEnemies
 // Looks for something you can hit - Used for homing attack
@@ -9232,7 +9388,7 @@ boolean P_HomingAttack(mobj_t *source, mobj_t *enemy) // Home in on your target
 	if (source->player)
 	{
 		source->player->drawangle = source->angle;
-		if (!demoplayback || P_AnalogMove(source->player))
+		if (!demoplayback || P_ControlStyle(source->player) == CS_LMAOGALOG)
 		{
 			if (source->player == &players[consoleplayer])
 				localangle = source->angle;
@@ -9584,12 +9740,12 @@ static void CV_CamRotate2_OnChange(void)
 }
 
 static CV_PossibleValue_t CV_CamSpeed[] = {{0, "MIN"}, {1*FRACUNIT, "MAX"}, {0, NULL}};
-static CV_PossibleValue_t rotation_cons_t[] = {{1, "MIN"}, {45, "MAX"}, {0, NULL}};
+static CV_PossibleValue_t rotation_cons_t[] = {{1, "MIN"}, {25, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t CV_CamRotate[] = {{-720, "MIN"}, {720, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t multiplier_cons_t[] = {{0, "MIN"}, {3*FRACUNIT, "MAX"}, {0, NULL}};
 
-consvar_t cv_cam_dist = {"cam_dist", "160", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam_height = {"cam_height", "25", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam_dist = {"cam_curdist", "160", CV_FLOAT, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam_height = {"cam_curheight", "25", CV_FLOAT, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_still = {"cam_still", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_speed = {"cam_speed", "0.3", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_rotate = {"cam_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate_OnChange, 0, NULL, NULL, 0, 0, NULL};
@@ -9597,8 +9753,8 @@ consvar_t cv_cam_rotspeed = {"cam_rotspeed", "10", CV_SAVE, rotation_cons_t, NUL
 consvar_t cv_cam_turnmultiplier = {"cam_turnmultiplier", "1.0", CV_FLOAT|CV_SAVE, multiplier_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_orbit = {"cam_orbit", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam_adjust = {"cam_adjust", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam2_dist = {"cam2_dist", "160", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
-consvar_t cv_cam2_height = {"cam2_height", "25", CV_FLOAT|CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam2_dist = {"cam2_curdist", "160", CV_FLOAT, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_cam2_height = {"cam2_curheight", "25", CV_FLOAT, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_still = {"cam2_still", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_speed = {"cam2_speed", "0.3", CV_FLOAT|CV_SAVE, CV_CamSpeed, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_rotate = {"cam2_rotate", "0", CV_CALL|CV_NOINIT, CV_CamRotate, CV_CamRotate2_OnChange, 0, NULL, NULL, 0, 0, NULL};
@@ -9607,6 +9763,42 @@ consvar_t cv_cam2_turnmultiplier = {"cam2_turnmultiplier", "1.0", CV_FLOAT|CV_SA
 consvar_t cv_cam2_orbit = {"cam2_orbit", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_cam2_adjust = {"cam2_adjust", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
+// [standard vs simple][p1 or p2]
+consvar_t cv_cam_savedist[2][2] = {
+	{ // standard
+		{"cam_dist", "160", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCamDist, 0, NULL, NULL, 0, 0, NULL},
+		{"cam2_dist", "160", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCam2Dist, 0, NULL, NULL, 0, 0, NULL}
+	},
+	{ // simple
+		{"cam_simpledist", "224", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCamDist, 0, NULL, NULL, 0, 0, NULL},
+		{"cam2_simpledist", "224", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCam2Dist, 0, NULL, NULL, 0, 0, NULL}
+
+	}
+};
+consvar_t cv_cam_saveheight[2][2] = {
+	{ // standard
+		{"cam_height", "25", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCamDist, 0, NULL, NULL, 0, 0, NULL},
+		{"cam2_height", "25", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCam2Dist, 0, NULL, NULL, 0, 0, NULL}
+	},
+	{ // simple
+		{"cam_simpleheight", "48", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCamDist, 0, NULL, NULL, 0, 0, NULL},
+		{"cam2_simpleheight", "48", CV_FLOAT|CV_SAVE|CV_CALL, NULL, CV_UpdateCam2Dist, 0, NULL, NULL, 0, 0, NULL}
+
+	}
+};
+
+void CV_UpdateCamDist(void)
+{
+	CV_Set(&cv_cam_dist, va("%f", FIXED_TO_FLOAT(cv_cam_savedist[cv_useranalog[0].value][0].value)));
+	CV_Set(&cv_cam_height, va("%f", FIXED_TO_FLOAT(cv_cam_saveheight[cv_useranalog[0].value][0].value)));
+}
+
+void CV_UpdateCam2Dist(void)
+{
+	CV_Set(&cv_cam2_dist, va("%f", FIXED_TO_FLOAT(cv_cam_savedist[cv_useranalog[1].value][1].value)));
+	CV_Set(&cv_cam2_height, va("%f", FIXED_TO_FLOAT(cv_cam_saveheight[cv_useranalog[1].value][1].value)));
+}
+
 fixed_t t_cam_dist = -42;
 fixed_t t_cam_height = -42;
 fixed_t t_cam_rotate = -42;
@@ -9640,8 +9832,14 @@ void P_ResetCamera(player_t *player, camera_t *thiscam)
 	thiscam->y = y;
 	thiscam->z = z;
 
-	if (!(thiscam == &camera && (cv_cam_still.value || cv_analog.value))
-	&& !(thiscam == &camera2 && (cv_cam2_still.value || cv_analog2.value)))
+	if ((thiscam == &camera && G_ControlStyle(1) == CS_SIMPLE)
+	|| (thiscam == &camera2 && G_ControlStyle(2) == CS_SIMPLE))
+	{
+		thiscam->angle = (thiscam == &camera) ? localangle : localangle2;
+		thiscam->aiming = (thiscam == &camera) ? localaiming : localaiming2;
+	}
+	else if (!(thiscam == &camera && (cv_cam_still.value || cv_analog[0].value))
+	&& !(thiscam == &camera2 && (cv_cam2_still.value || cv_analog[1].value)))
 	{
 		thiscam->angle = player->mo->angle;
 		thiscam->aiming = 0;
@@ -9666,6 +9864,9 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	subsector_t *newsubsec;
 	fixed_t f1, f2;
 
+	static fixed_t camsideshift[2] = {0, 0};
+	fixed_t shiftx = 0, shifty = 0;
+
 	// We probably shouldn't move the camera if there is no player or player mobj somehow
 	if (!player || !player->mo)
 		return true;
@@ -9756,7 +9957,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	}
 	else
 	{
-		focusangle = mo->angle;
+		focusangle = player->cmd.angleturn << 16;
 		focusaiming = player->aiming;
 	}
 
@@ -9796,7 +9997,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 		camheight = FixedMul(camheight, player->camerascale);
 
 #ifdef REDSANALOG
-	if (P_AnalogMove(player) && (player->cmd.buttons & (BT_CAMLEFT|BT_CAMRIGHT)) == (BT_CAMLEFT|BT_CAMRIGHT)) {
+	if (P_ControlStyle(player) == CS_LMAOGALOG && (player->cmd.buttons & (BT_CAMLEFT|BT_CAMRIGHT)) == (BT_CAMLEFT|BT_CAMRIGHT)) {
 		camstill = true;
 
 		if (camspeed < 4*FRACUNIT/5)
@@ -9826,7 +10027,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 				angle = R_PointToAngle2(mo->x, mo->y, mo->target->x, mo->target->y);
 		}
 	}
-	else if (P_AnalogMove(player) && !sign) // Analog
+	else if (P_ControlStyle(player) == CS_LMAOGALOG && !sign) // Analog
 		angle = R_PointToAngle2(thiscam->x, thiscam->y, mo->x, mo->y);
 	else if (demoplayback)
 	{
@@ -9843,14 +10044,14 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	else
 		angle = focusangle + FixedAngle(camrotate*FRACUNIT);
 
-	if (!resetcalled && (cv_analog.value || demoplayback) && ((thiscam == &camera && t_cam_rotate != -42) || (thiscam == &camera2
+	if (!resetcalled && (cv_analog[0].value || demoplayback) && ((thiscam == &camera && t_cam_rotate != -42) || (thiscam == &camera2
 		&& t_cam2_rotate != -42)))
 	{
 		angle = FixedAngle(camrotate*FRACUNIT);
 		thiscam->angle = angle;
 	}
 
-	if ((((thiscam == &camera) && cv_analog.value) || ((thiscam != &camera) && cv_analog2.value) || demoplayback) && !sign && !objectplacing && !(twodlevel || (mo->flags2 & MF2_TWOD)) && (player->powers[pw_carry] != CR_NIGHTSMODE) && displayplayer == consoleplayer)
+	if ((((thiscam == &camera) && cv_analog[0].value) || ((thiscam != &camera) && cv_analog[1].value) || demoplayback) && !sign && !objectplacing && !(twodlevel || (mo->flags2 & MF2_TWOD)) && (player->powers[pw_carry] != CR_NIGHTSMODE) && displayplayer == consoleplayer)
 	{
 #ifdef REDSANALOG
 		if ((player->cmd.buttons & (BT_CAMLEFT|BT_CAMRIGHT)) == (BT_CAMLEFT|BT_CAMRIGHT)); else
@@ -9871,6 +10072,29 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 		}
 	}
 
+	if (G_ControlStyle((thiscam == &camera) ? 1 : 2) == CS_SIMPLE && !sign)
+	{
+		// Shift the camera slightly to the sides depending on the player facing direction
+		UINT8 forplayer = (thiscam == &camera) ? 0 : 1;
+		fixed_t shift = FixedMul(FINESINE((player->mo->angle - angle) >> ANGLETOFINESHIFT), cv_cam_shiftfacing[forplayer].value);
+
+		if (player->powers[pw_carry] == CR_NIGHTSMODE)
+		{
+			fixed_t cos = FINECOSINE((angle_t) (player->flyangle * ANG1)>>ANGLETOFINESHIFT);
+			shift = FixedMul(shift, min(FRACUNIT, player->speed*abs(cos)/6000));
+			shift += FixedMul(camsideshift[forplayer] - shift, FRACUNIT-(camspeed>>2));
+		}
+		else if (ticcmd_centerviewdown[(thiscam == &camera) ? 0 : 1])
+			shift = FixedMul(camsideshift[forplayer], FRACUNIT-camspeed);
+		else
+			shift += FixedMul(camsideshift[forplayer] - shift, FRACUNIT-(camspeed>>3));
+		camsideshift[forplayer] = shift;
+
+		shift = FixedMul(shift, camdist);
+		shiftx = -FixedMul(FINESINE(angle>>ANGLETOFINESHIFT), shift);
+		shifty = FixedMul(FINECOSINE(angle>>ANGLETOFINESHIFT), shift);
+	}
+
 	// sets ideal cam pos
 	if (twodlevel || (mo->flags2 & MF2_TWOD))
 		dist = 480<<FRACBITS;
@@ -9893,7 +10117,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 			camheight = mo->scale << 7;
 			camspeed = FRACUNIT/12;
 		}
-		else if (P_AnalogMove(player)) // x1.2 dist for analog
+		else if (P_ControlStyle(player) == CS_LMAOGALOG) // x1.2 dist for analog
 		{
 			dist = FixedMul(dist, 6*FRACUNIT/5);
 			camheight = FixedMul(camheight, 6*FRACUNIT/5);
@@ -10217,8 +10441,8 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	}
 	else
 	{
-		viewpointx = mo->x + FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
-		viewpointy = mo->y + FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+		viewpointx = mo->x + shiftx + FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+		viewpointy = mo->y + shifty + FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
 	}
 
 	if (!camstill && !resetcalled && !paused)
@@ -10259,6 +10483,9 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 		}
 		else
 			thiscam->momz = FixedMul(z - thiscam->z, camspeed);
+
+		thiscam->momx += FixedMul(shiftx, camspeed);
+		thiscam->momy += FixedMul(shifty, camspeed);
 	}
 
 	// compute aming to look the viewed point
@@ -10806,7 +11033,7 @@ static void P_MinecartThink(player_t *player)
 		else if (angdiff > ANGLE_180 && angdiff < InvAngle(MINECARTCONEMAX))
 			player->mo->angle = minecart->angle - MINECARTCONEMAX;
 
-		if (angdiff + minecart->angle != player->mo->angle && (!demoplayback || P_AnalogMove(player)))
+		if (angdiff + minecart->angle != player->mo->angle && (!demoplayback || P_ControlStyle(player) == CS_LMAOGALOG))
 		{
 			if (player == &players[consoleplayer])
 				localangle = player->mo->angle;
@@ -10885,7 +11112,7 @@ static void P_MinecartThink(player_t *player)
 			else
 				minecart->angle = targetangle + ANGLE_180;
 			angdiff = (minecart->angle - prevangle);
-			if (angdiff && (!demoplayback || P_AnalogMove(player)))  // maintain relative angle on turns
+			if (angdiff && (!demoplayback || P_ControlStyle(player) == CS_LMAOGALOG))  // maintain relative angle on turns
 			{
 				player->mo->angle += angdiff;
 				if (player == &players[consoleplayer])
@@ -11660,7 +11887,7 @@ void P_PlayerThink(player_t *player)
 		player->mo->reactiontime--;
 	else if (player->powers[pw_carry] == CR_MINECART)
 	{
-		if (!P_AnalogMove(player))
+		if (P_ControlStyle(player) != CS_LMAOGALOG)
 			player->mo->angle = (cmd->angleturn << 16 /* not FRACBITS */);
 
 		ticruned++;
@@ -11673,7 +11900,7 @@ void P_PlayerThink(player_t *player)
 	{
 		if (player->powers[pw_carry] == CR_ROPEHANG)
 		{
-			if (!P_AnalogMove(player))
+			if (P_ControlStyle(player) != CS_LMAOGALOG)
 				player->mo->angle = (cmd->angleturn<<16 /* not FRACBITS */);
 
 			ticruned++;
@@ -11721,7 +11948,7 @@ void P_PlayerThink(player_t *player)
 			;
 		else if (!(player->pflags & PF_DIRECTIONCHAR)
 		|| (player->climbing) // stuff where the direction is forced at all times
-		|| (P_AnalogMove(player) || twodlevel || player->mo->flags2 & MF2_TWOD) // keep things synchronised up there, since the camera IS seperate from player motion when that happens
+		|| (twodlevel || player->mo->flags2 & MF2_TWOD) // keep things synchronised up there, since the camera IS seperate from player motion when that happens
 		|| G_RingSlingerGametype()) // no firing rings in directions your player isn't aiming
 			player->drawangle = player->mo->angle;
 		else if (P_PlayerInPain(player))
@@ -11752,7 +11979,7 @@ void P_PlayerThink(player_t *player)
 				case CR_ROLLOUT:
 					if (cmd->forwardmove || cmd->sidemove) // only when you're pressing movement keys
 					{ // inverse direction!
-						diff = ((player->mo->angle + R_PointToAngle2(0, 0, -cmd->forwardmove<<FRACBITS, cmd->sidemove<<FRACBITS)) - player->drawangle);
+						diff = (((player->cmd.angleturn<<16) + R_PointToAngle2(0, 0, -cmd->forwardmove<<FRACBITS, cmd->sidemove<<FRACBITS)) - player->drawangle);
 						factor = 4;
 					}
 					break;
@@ -11809,7 +12036,7 @@ void P_PlayerThink(player_t *player)
 			}
 			else if (cmd->forwardmove || cmd->sidemove) // only when you're pressing movement keys
 			{
-				diff = ((player->mo->angle + R_PointToAngle2(0, 0, cmd->forwardmove<<FRACBITS, -cmd->sidemove<<FRACBITS)) - player->drawangle);
+				diff = ((player->mo->angle + ((player->pflags & PF_ANALOGMODE) ? 0 : R_PointToAngle2(0, 0, cmd->forwardmove<<FRACBITS, -cmd->sidemove<<FRACBITS))) - player->drawangle);
 				factor = 4;
 			}
 			else if (player->rmomx || player->rmomy)
@@ -12422,7 +12649,7 @@ void P_PlayerAfterThink(player_t *player)
 				{
 					player->mo->angle = tails->angle;
 
-					if (!demoplayback || P_AnalogMove(player))
+					if (!demoplayback || P_ControlStyle(player) == CS_LMAOGALOG)
 					{
 						if (player == &players[consoleplayer])
 							localangle = player->mo->angle;
@@ -12445,7 +12672,7 @@ void P_PlayerAfterThink(player_t *player)
 					P_SetTarget(&player->mo->tracer, NULL);
 
 				if (player-players == consoleplayer && botingame)
-					CV_SetValue(&cv_analog2, (player->powers[pw_carry] != CR_PLAYER));
+					CV_SetValue(&cv_analog[1], (player->powers[pw_carry] != CR_PLAYER));
 				break;
 			}
 			case CR_GENERIC: // being carried by some generic item
@@ -12511,7 +12738,7 @@ void P_PlayerAfterThink(player_t *player)
 						macecenter->angle += cmd->sidemove<<ANGLETOFINESHIFT;
 						player->mo->angle += cmd->sidemove<<ANGLETOFINESHIFT; // 2048 --> ANGLE_MAX
 
-						if (!demoplayback || P_AnalogMove(player))
+						if (!demoplayback || P_ControlStyle(player) == CS_LMAOGALOG)
 						{
 							if (player == &players[consoleplayer])
 								localangle = player->mo->angle; // Adjust the local control angle.
diff --git a/src/r_data.c b/src/r_data.c
index db791ab169d080bb5aba60212c9625f2bb1384e2..6ba40206c1a78a8644e270228422d6b17ddbc2dd 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -1254,14 +1254,29 @@ void R_PrecacheLevel(void)
 		for (j = 0; j < sprites[i].numframes; j++)
 		{
 			sf = &sprites[i].spriteframes[j];
-			for (k = 0; k < 8; k++)
+#define cacheang(a) {\
+		lump = sf->lumppat[a];\
+		if (devparm)\
+			spritememory += W_LumpLength(lump);\
+		W_CachePatchNum(lump, PU_PATCH);\
+	}
+			// see R_InitSprites for more about lumppat,lumpid
+			switch (sf->rotate)
 			{
-				// see R_InitSprites for more about lumppat,lumpid
-				lump = sf->lumppat[k];
-				if (devparm)
-					spritememory += W_LumpLength(lump);
-				W_CachePatchNum(lump, PU_PATCH);
+				case SRF_SINGLE:
+					cacheang(0);
+					break;
+				case SRF_2D:
+					cacheang(2);
+					cacheang(6);
+					break;
+				default:
+					k = (sf->rotate & SRF_3DGE ? 16 : 8);
+					while (k--)
+						cacheang(k);
+					break;
 			}
+#undef cacheang
 		}
 	}
 	free(spritepresent);
diff --git a/src/r_defs.h b/src/r_defs.h
index 176f33c4f9a22adf72e2bf414ae7df79b428a695..52b9bea951d9ad0c914157cf8850c968794dafd1 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -727,8 +727,8 @@ typedef struct
 #ifdef ROTSPRITE
 typedef struct
 {
-	patch_t *patch[8][ROTANGLES];
-	boolean cached[8];
+	patch_t *patch[16][ROTANGLES];
+	UINT16 cached;
 } rotsprite_t;
 #endif/*ROTSPRITE*/
 
@@ -736,9 +736,11 @@ typedef enum
 {
 	SRF_SINGLE      = 0,   // 0-angle for all rotations
 	SRF_3D          = 1,   // Angles 1-8
-	SRF_LEFT        = 2,   // Left side uses single patch
-	SRF_RIGHT       = 4,   // Right side uses single patch
-	SRF_2D          = SRF_LEFT|SRF_RIGHT, // 6
+	SRF_3DGE        = 2,   // 3DGE, ZDoom and Doom Legacy all have 16-angle support. Why not us?
+	SRF_3DMASK      = SRF_3D|SRF_3DGE, // 3
+	SRF_LEFT        = 4,   // Left side uses single patch
+	SRF_RIGHT       = 8,   // Right side uses single patch
+	SRF_2D          = SRF_LEFT|SRF_RIGHT, // 12
 	SRF_NONE        = 0xff // Initial value
 } spriterotateflags_t;     // SRF's up!
 
@@ -764,7 +766,7 @@ typedef struct patchinfo_s patchinfo_t;
 // Sprites are patches with a special naming convention so they can be
 //  recognized by R_InitSprites.
 // The base name is NNNNFx or NNNNFxFx, with x indicating the rotation,
-//  x = 0, 1-8, L/R
+//  x = 0, 1-8, 9+A-G, L/R
 // The sprite and frame specified by a thing_t is range checked at run time.
 // A sprite is a patch_t that is assumed to represent a three dimensional
 //  object and may have multiple rotations predrawn.
@@ -781,12 +783,12 @@ typedef struct
 	//  name eight times.
 	UINT8 rotate; // see spriterotateflags_t above
 
-	// Lump to use for view angles 0-7.
-	lumpnum_t lumppat[8]; // lump number 16 : 16 wad : lump
-	size_t lumpid[8]; // id in the spriteoffset, spritewidth, etc. tables
+	// Lump to use for view angles 0-7/15.
+	lumpnum_t lumppat[16]; // lump number 16 : 16 wad : lump
+	size_t lumpid[16]; // id in the spriteoffset, spritewidth, etc. tables
 
-	// Flip bits (1 = flip) to use for view angles 0-7.
-	UINT8 flip;
+	// Flip bits (1 = flip) to use for view angles 0-7/15.
+	UINT16 flip;
 
 #ifdef ROTSPRITE
 	rotsprite_t rotsprite;
diff --git a/src/r_main.c b/src/r_main.c
index 22061b407bc7a7fd4e52acbb48061b09cc6eb566..a2674b5431eb12a8fccec14221dbf54d51c2b9a5 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -187,20 +187,20 @@ void SplitScreen_OnChange(void)
 
 static void ChaseCam_OnChange(void)
 {
-	if (!cv_chasecam.value || !cv_useranalog.value)
-		CV_SetValue(&cv_analog, 0);
+	if (!cv_chasecam.value || !cv_useranalog[0].value)
+		CV_SetValue(&cv_analog[0], 0);
 	else
-		CV_SetValue(&cv_analog, 1);
+		CV_SetValue(&cv_analog[0], 1);
 }
 
 static void ChaseCam2_OnChange(void)
 {
 	if (botingame)
 		return;
-	if (!cv_chasecam2.value || !cv_useranalog2.value)
-		CV_SetValue(&cv_analog2, 0);
+	if (!cv_chasecam2.value || !cv_useranalog[1].value)
+		CV_SetValue(&cv_analog[1], 0);
 	else
-		CV_SetValue(&cv_analog2, 1);
+		CV_SetValue(&cv_analog[1], 1);
 }
 
 static void FlipCam_OnChange(void)
@@ -1234,6 +1234,16 @@ void R_RegisterEngineStuff(void)
 	CV_RegisterVar(&cv_cam2_orbit);
 	CV_RegisterVar(&cv_cam2_adjust);
 
+	CV_RegisterVar(&cv_cam_savedist[0][0]);
+	CV_RegisterVar(&cv_cam_savedist[0][1]);
+	CV_RegisterVar(&cv_cam_savedist[1][0]);
+	CV_RegisterVar(&cv_cam_savedist[1][1]);
+
+	CV_RegisterVar(&cv_cam_saveheight[0][0]);
+	CV_RegisterVar(&cv_cam_saveheight[0][1]);
+	CV_RegisterVar(&cv_cam_saveheight[1][0]);
+	CV_RegisterVar(&cv_cam_saveheight[1][1]);
+
 	CV_RegisterVar(&cv_showhud);
 	CV_RegisterVar(&cv_translucenthud);
 
diff --git a/src/r_patch.h b/src/r_patch.h
new file mode 100644
index 0000000000000000000000000000000000000000..e743a7d097c057f2b9d3892a72f2b96622c2b176
--- /dev/null
+++ b/src/r_patch.h
@@ -0,0 +1,74 @@
+// SONIC ROBO BLAST 2
+//-----------------------------------------------------------------------------
+// Copyright (C) 1993-1996 by id Software, Inc.
+// Copyright (C) 2018-2019 by Jaime "Lactozilla" Passos.
+// Copyright (C) 2019      by Sonic Team Junior.
+//
+// This program is free software distributed under the
+// terms of the GNU General Public License, version 2.
+// See the 'LICENSE' file for more details.
+//-----------------------------------------------------------------------------
+/// \file  r_patch.h
+/// \brief Patch generation.
+
+#ifndef __R_PATCH__
+#define __R_PATCH__
+
+#include "r_defs.h"
+#include "doomdef.h"
+
+// Structs
+typedef enum
+{
+	ROTAXIS_X, // Roll (the default)
+	ROTAXIS_Y, // Pitch
+	ROTAXIS_Z  // Yaw
+} rotaxis_t;
+
+typedef struct
+{
+	INT32 x, y;
+	rotaxis_t rotaxis;
+} spriteframepivot_t;
+
+typedef struct
+{
+	spriteframepivot_t pivot[64];
+	boolean available;
+} spriteinfo_t;
+
+// Conversions between patches / flats / textures...
+boolean R_CheckIfPatch(lumpnum_t lump);
+void R_TextureToFlat(size_t tex, UINT8 *flat);
+void R_PatchToFlat(patch_t *patch, UINT8 *flat);
+void R_PatchToMaskedFlat(patch_t *patch, UINT16 *raw, boolean flip);
+patch_t *R_FlatToPatch(UINT8 *raw, UINT16 width, UINT16 height, UINT16 leftoffset, UINT16 topoffset, size_t *destsize, boolean transparency);
+patch_t *R_MaskedFlatToPatch(UINT16 *raw, UINT16 width, UINT16 height, UINT16 leftoffset, UINT16 topoffset, size_t *destsize);
+
+// Portable Network Graphics
+boolean R_IsLumpPNG(const UINT8 *d, size_t s);
+#define W_ThrowPNGError(lumpname, wadfilename) I_Error("W_Wad: Lump \"%s\" in file \"%s\" is a .png - please convert to either Doom or Flat (raw) image format.", lumpname, wadfilename); // Fears Of LJ Sonic
+
+#ifndef NO_PNG_LUMPS
+UINT8 *R_PNGToFlat(UINT16 *width, UINT16 *height, UINT8 *png, size_t size);
+patch_t *R_PNGToPatch(const UINT8 *png, size_t size, size_t *destsize);
+boolean R_PNGDimensions(UINT8 *png, INT16 *width, INT16 *height, size_t size);
+#endif
+
+// SpriteInfo
+extern spriteinfo_t spriteinfo[NUMSPRITES];
+void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps);
+void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum);
+
+// Sprite rotation
+#ifdef ROTSPRITE
+INT32 R_GetRollAngle(angle_t rollangle);
+void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, spriteframe_t *sprframe, INT32 rot, UINT8 flip);
+void R_FreeSingleRotSprite(spritedef_t *spritedef);
+void R_FreeSkinRotSprite(size_t skinnum);
+extern fixed_t rollcosang[ROTANGLES];
+extern fixed_t rollsinang[ROTANGLES];
+void R_FreeAllRotSprite(void);
+#endif
+
+#endif // __R_PATCH__
diff --git a/src/r_picformats.c b/src/r_picformats.c
index c7e54cf4ee684113e706d38d16ac622751f36b11..2d39770543b82869c6c30d228a596dc3b039d9fb 100644
--- a/src/r_picformats.c
+++ b/src/r_picformats.c
@@ -1469,7 +1469,7 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 #define ROTSPRITE_XCENTER (newwidth / 2)
 #define ROTSPRITE_YCENTER (newheight / 2)
 
-	if (!sprframe->rotsprite.cached[rot])
+	if (!(sprframe->rotsprite.cached & (1<<rot)))
 	{
 		INT32 dx, dy;
 		INT32 px, py;
@@ -1643,7 +1643,7 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 		}
 
 		// This rotation is cached now
-		sprframe->rotsprite.cached[rot] = true;
+		sprframe->rotsprite.cached |= (1<<rot);
 
 		// free image data
 		Z_Free(patch);
@@ -1667,9 +1667,9 @@ void R_FreeSingleRotSprite(spritedef_t *spritedef)
 	for (frame = 0; frame < spritedef->numframes; frame++)
 	{
 		spriteframe_t *sprframe = &spritedef->spriteframes[frame];
-		for (rot = 0; rot < 8; rot++)
+		for (rot = 0; rot < 16; rot++)
 		{
-			if (sprframe->rotsprite.cached[rot])
+			if (sprframe->rotsprite.cached & (1<<rot))
 			{
 				for (ang = 0; ang < ROTANGLES; ang++)
 				{
@@ -1700,7 +1700,7 @@ void R_FreeSingleRotSprite(spritedef_t *spritedef)
 						Z_Free(rotsprite);
 					}
 				}
-				sprframe->rotsprite.cached[rot] = false;
+				sprframe->rotsprite.cached &= ~(1<<rot);
 			}
 		}
 	}
diff --git a/src/r_things.c b/src/r_things.c
index 0f1dc87cd358f4f21c0802fd6c48609ff5964b76..9e8466942d5bca43939c71af97ce43dc673d213f 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -105,24 +105,21 @@ static void R_InstallSpriteLump(UINT16 wad,            // graphics patch
                                 UINT8 rotation,
                                 UINT8 flipped)
 {
-	char cn = R_Frame2Char(frame); // for debugging
+	char cn = R_Frame2Char(frame), cr = R_Rotation2Char(rotation); // for debugging
 
 	INT32 r, ang;
 	lumpnum_t lumppat = wad;
 	lumppat <<= 16;
 	lumppat += lump;
 
-	if (frame >= 64 || !(R_ValidSpriteAngle(rotation)))
-		I_Error("R_InstallSpriteLump: Bad frame characters in lump %s", W_CheckNameForNum(lumppat));
-
 	if (maxframe ==(size_t)-1 || frame > maxframe)
 		maxframe = frame;
 
 	// rotsprite
 #ifdef ROTSPRITE
-	for (r = 0; r < 8; r++)
+	sprtemp[frame].rotsprite.cached = 0;
+	for (r = 0; r < 16; r++)
 	{
-		sprtemp[frame].rotsprite.cached[r] = false;
 		for (ang = 0; ang < ROTANGLES; ang++)
 			sprtemp[frame].rotsprite.patch[r][ang] = NULL;
 	}
@@ -133,16 +130,16 @@ static void R_InstallSpriteLump(UINT16 wad,            // graphics patch
 		// the lump should be used for all rotations
 		if (sprtemp[frame].rotate == SRF_SINGLE)
 			CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s frame %c has multiple rot = 0 lump\n", spritename, cn);
-		else if (sprtemp[frame].rotate != SRF_NONE) // Let's bundle 1-8 and L/R rotations into one debug message.
+		else if (sprtemp[frame].rotate != SRF_NONE) // Let's bundle 1-8/16 and L/R rotations into one debug message.
 			CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s frame %c has rotations and a rot = 0 lump\n", spritename, cn);
 
 		sprtemp[frame].rotate = SRF_SINGLE;
-		for (r = 0; r < 8; r++)
+		for (r = 0; r < 16; r++)
 		{
 			sprtemp[frame].lumppat[r] = lumppat;
 			sprtemp[frame].lumpid[r] = lumpid;
 		}
-		sprtemp[frame].flip = flipped ? 0xFF : 0; // 11111111 in binary
+		sprtemp[frame].flip = flipped ? 0xFFFF : 0; // 1111111111111111 in binary
 		return;
 	}
 
@@ -151,54 +148,67 @@ static void R_InstallSpriteLump(UINT16 wad,            // graphics patch
 		UINT8 rightfactor = ((rotation == ROT_R) ? 4 : 0);
 
 		// the lump should be used for half of all rotations
-		if (sprtemp[frame].rotate == SRF_SINGLE)
+		if (sprtemp[frame].rotate == SRF_NONE)
+			sprtemp[frame].rotate = SRF_SINGLE;
+		else if (sprtemp[frame].rotate == SRF_SINGLE)
 			CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s frame %c has L/R rotations and a rot = 0 lump\n", spritename, cn);
 		else if (sprtemp[frame].rotate == SRF_3D)
 			CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s frame %c has both L/R and 1-8 rotations\n", spritename, cn);
+		else if (sprtemp[frame].rotate == SRF_3DGE)
+			CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s frame %c has both L/R and 1-G rotations\n", spritename, cn);
 		else if ((sprtemp[frame].rotate & SRF_LEFT) && (rotation == ROT_L))
 			CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s frame %c has multiple L rotations\n", spritename, cn);
 		else if ((sprtemp[frame].rotate & SRF_RIGHT) && (rotation == ROT_R))
 			CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s frame %c has multiple R rotations\n", spritename, cn);
 
-		if (sprtemp[frame].rotate == SRF_NONE)
-			sprtemp[frame].rotate = SRF_SINGLE;
-
 		sprtemp[frame].rotate |= ((rotation == ROT_R) ? SRF_RIGHT : SRF_LEFT);
-		if (sprtemp[frame].rotate == (SRF_3D|SRF_2D))
-			sprtemp[frame].rotate = SRF_2D; // SRF_3D|SRF_2D being enabled at the same time doesn't HURT in the current sprite angle implementation, but it DOES mean more to check in some of the helper functions. Let's not allow this scenario to happen.
+		if ((sprtemp[frame].rotate & SRF_2D) == SRF_2D)
+			sprtemp[frame].rotate &= ~SRF_3DMASK; // SRF_3D|SRF_2D being enabled at the same time doesn't HURT in the current sprite angle implementation, but it DOES mean more to check in some of the helper functions. Let's not allow this scenario to happen.
 
-		for (r = 0; r < 4; r++) // Thanks to R_PrecacheLevel, we can't leave sprtemp[*].lumppat[*] == LUMPERROR... so we load into the front/back angle too.
+		// load into every relevant angle, including the front one
+		for (r = 0; r < 4; r++)
 		{
 			sprtemp[frame].lumppat[r + rightfactor] = lumppat;
 			sprtemp[frame].lumpid[r + rightfactor] = lumpid;
+			sprtemp[frame].lumppat[r + rightfactor + 8] = lumppat;
+			sprtemp[frame].lumpid[r + rightfactor + 8] = lumpid;
+
 		}
 
 		if (flipped)
-			sprtemp[frame].flip |= (0x0F<<rightfactor); // 00001111 or 11110000 in binary, depending on rotation being ROT_L or ROT_R
+			sprtemp[frame].flip |= (0x0F0F<<rightfactor); // 0000111100001111 or 1111000011110000 in binary, depending on rotation being ROT_L or ROT_R
 		else
-			sprtemp[frame].flip &= ~(0x0F<<rightfactor); // ditto
+			sprtemp[frame].flip &= ~(0x0F0F<<rightfactor); // ditto
 
 		return;
 	}
 
-	// the lump is only used for one rotation
-	if (sprtemp[frame].rotate == SRF_SINGLE)
-		CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s frame %c has 1-8 rotations and a rot = 0 lump\n", spritename, cn);
-	else if ((sprtemp[frame].rotate != SRF_3D) && (sprtemp[frame].rotate != SRF_NONE))
-		CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s frame %c has both L/R and 1-8 rotations\n", spritename, cn);
+	if (sprtemp[frame].rotate == SRF_NONE)
+		sprtemp[frame].rotate = SRF_SINGLE;
+	else if (sprtemp[frame].rotate == SRF_SINGLE)
+		CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s frame %c has 1-8/G rotations and a rot = 0 lump\n", spritename, cn);
+	else if (sprtemp[frame].rotate & SRF_2D)
+		CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s frame %c has both L/R and 1-8/G rotations\n", spritename, cn);
 
 	// make 0 based
 	rotation--;
 
-	if (rotation == 0 || rotation == 4) // Front or back...
-		sprtemp[frame].rotate = SRF_3D; // Prevent L and R changeover
-	else if (rotation > 3) // Right side
-		sprtemp[frame].rotate = (SRF_3D | (sprtemp[frame].rotate & SRF_LEFT)); // Continue allowing L frame changeover
-	else // if (rotation <= 3) // Left side
-		sprtemp[frame].rotate = (SRF_3D | (sprtemp[frame].rotate & SRF_RIGHT)); // Continue allowing R frame changeover
+	{
+		// SRF_3D|SRF_3DGE being enabled at the same time doesn't HURT in the current sprite angle implementation, but it DOES mean more to check in some of the helper functions. Let's not allow this scenario to happen.
+		UINT8 threedrot = (rotation > 7) ? SRF_3DGE : (sprtemp[frame].rotate & SRF_3DMASK);
+		if (!threedrot)
+			threedrot = SRF_3D;
+
+		if (rotation == 0 || rotation == 4) // Front or back...
+			sprtemp[frame].rotate = threedrot; // Prevent L and R changeover
+		else if ((rotation & 7) > 3) // Right side
+			sprtemp[frame].rotate = (threedrot | (sprtemp[frame].rotate & SRF_LEFT)); // Continue allowing L frame changeover
+		else // if ((rotation & 7) <= 3) // Left side
+			sprtemp[frame].rotate = (threedrot | (sprtemp[frame].rotate & SRF_RIGHT)); // Continue allowing R frame changeover
+	}
 
 	if (sprtemp[frame].lumppat[rotation] != LUMPERROR)
-		CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s: %c%c has two lumps mapped to it\n", spritename, cn, '1'+rotation);
+		CONS_Debug(DBG_SETUP, "R_InitSprites: Sprite %s: %c%c has two lumps mapped to it\n", spritename, cn, cr);
 
 	// lumppat & lumpid are the same for original Doom, but different
 	// when using sprites in pwad : the lumppat points the new graphics
@@ -259,9 +269,9 @@ static boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef,
 		if (memcmp(lumpinfo[l].name,sprname,4)==0)
 		{
 			frame = R_Char2Frame(lumpinfo[l].name[4]);
-			rotation = (UINT8)(lumpinfo[l].name[5] - '0');
+			rotation = R_Char2Rotation(lumpinfo[l].name[5]);
 
-			if (frame >= 64 || !(R_ValidSpriteAngle(rotation))) // Give an actual NAME error -_-...
+			if (frame >= 64 || rotation == 255) // Give an actual NAME error -_-...
 			{
 				CONS_Alert(CONS_WARNING, M_GetText("Bad sprite name: %s\n"), W_CheckNameForNumPwad(wadnum,l));
 				continue;
@@ -312,7 +322,13 @@ static boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef,
 			if (lumpinfo[l].name[6])
 			{
 				frame = R_Char2Frame(lumpinfo[l].name[6]);
-				rotation = (UINT8)(lumpinfo[l].name[7] - '0');
+				rotation = R_Char2Rotation(lumpinfo[l].name[7]);
+
+				if (frame >= 64 || rotation == 255) // Give an actual NAME error -_-...
+				{
+					CONS_Alert(CONS_WARNING, M_GetText("Bad sprite name: %s\n"), W_CheckNameForNumPwad(wadnum,l));
+					continue;
+				}
 				R_InstallSpriteLump(wadnum, l, numspritelumps, frame, rotation, 1);
 			}
 
@@ -373,18 +389,19 @@ static boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef,
 			case SRF_2D: // both Left and Right rotations
 			// we test to see whether the left and right slots are present
 			if ((sprtemp[frame].lumppat[2] == LUMPERROR) || (sprtemp[frame].lumppat[6] == LUMPERROR))
-				I_Error("R_AddSingleSpriteDef: Sprite %.4s frame %c is missing rotations",
+				I_Error("R_AddSingleSpriteDef: Sprite %.4s frame %c is missing rotations (L-R mode)",
 				sprname, R_Frame2Char(frame));
 			break;
 
 			default:
-			// must have all 8 frames
-			for (rotation = 0; rotation < 8; rotation++)
+			// must have all 8/16 frames
+				rotation = ((sprtemp[frame].rotate & SRF_3DGE) ? 16 : 8);
+				while (rotation--)
 				// we test the patch lump, or the id lump whatever
 				// if it was not loaded the two are LUMPERROR
 				if (sprtemp[frame].lumppat[rotation] == LUMPERROR)
-					I_Error("R_AddSingleSpriteDef: Sprite %.4s frame %c is missing rotations",
-					        sprname, R_Frame2Char(frame));
+					I_Error("R_AddSingleSpriteDef: Sprite %.4s frame %c is missing rotations (1-%c mode)",
+					        sprname, R_Frame2Char(frame), ((sprtemp[frame].rotate & SRF_3DGE) ? 'G' : '8'));
 			break;
 		}
 	}
@@ -1098,7 +1115,7 @@ static void R_ProjectSprite(mobj_t *thing)
 	size_t lump;
 
 	size_t rot;
-	UINT8 flip;
+	UINT16 flip;
 	boolean vflip = (!(thing->eflags & MFE_VERTICALFLIP) != !(thing->frame & FF_VERTICALFLIP));
 
 	INT32 lindex;
@@ -1228,7 +1245,7 @@ static void R_ProjectSprite(mobj_t *thing)
 		// use single rotation for all views
 		rot = 0;                        //Fab: for vis->patch below
 		lump = sprframe->lumpid[0];     //Fab: see note above
-		flip = sprframe->flip; // Will only be 0x00 or 0xFF
+		flip = sprframe->flip; 			// Will only be 0 or 0xFFFF
 	}
 	else
 	{
@@ -1239,6 +1256,11 @@ static void R_ProjectSprite(mobj_t *thing)
 			rot = 6; // F7 slot
 		else if ((sprframe->rotate & SRF_LEFT) && (ang >= ANGLE_180)) // See from left
 			rot = 2; // F3 slot
+		else if (sprframe->rotate & SRF_3DGE) // 16-angle mode
+		{
+			rot = (ang+ANGLE_180+ANGLE_11hh)>>28;
+			rot = ((rot & 1)<<3)|(rot>>1);
+		}
 		else // Normal behaviour
 			rot = (ang+ANGLE_202h)>>29;
 
@@ -1261,7 +1283,7 @@ static void R_ProjectSprite(mobj_t *thing)
 	if (thing->rollangle)
 	{
 		rollangle = R_GetRollAngle(thing->rollangle);
-		if (!sprframe->rotsprite.cached[rot])
+		if (!(sprframe->rotsprite.cached & (1<<rot)))
 			R_CacheRotSprite(thing->sprite, (thing->frame & FF_FRAMEMASK), sprinfo, sprframe, rot, flip);
 		rotsprite = sprframe->rotsprite.patch[rot][rollangle];
 		if (rotsprite != NULL)
diff --git a/src/r_things.h b/src/r_things.h
index 706d98c4e9dcd1bfa3a2553ae11fbc59a4074591..c60df7c28cf1c811f21c19ea0123d754094d40d0 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -20,10 +20,6 @@
 #include "r_portal.h"
 #include "r_defs.h"
 
-// "Left" and "Right" character symbols for additional rotation functionality
-#define ROT_L ('L' - '0')
-#define ROT_R ('R' - '0')
-
 // number of sprite lumps for spritewidth,offset,topoffset lookup tables
 // Fab: this is a hack : should allocate the lookup tables per sprite
 #define MAXVISSPRITES 2048 // added 2-2-98 was 128
@@ -270,7 +266,7 @@ FUNCMATH FUNCINLINE static ATTRINLINE UINT8 R_Char2Frame(char cn)
 	if (cn == '+') return '\\' - 'A'; // PK3 can't use backslash, so use + instead
 	return cn - 'A';
 #else
-	if (cn >= 'A' && cn <= 'Z') return cn - 'A';
+	if (cn >= 'A' && cn <= 'Z') return (cn - 'A');
 	if (cn >= '0' && cn <= '9') return (cn - '0') + 26;
 	if (cn >= 'a' && cn <= 'z') return (cn - 'a') + 36;
 	if (cn == '!') return 62;
@@ -279,9 +275,26 @@ FUNCMATH FUNCINLINE static ATTRINLINE UINT8 R_Char2Frame(char cn)
 #endif
 }
 
-FUNCMATH FUNCINLINE static ATTRINLINE boolean R_ValidSpriteAngle(UINT8 rotation)
+// "Left" and "Right" character symbols for additional rotation functionality
+#define ROT_L 17
+#define ROT_R 18
+
+FUNCMATH FUNCINLINE static ATTRINLINE char R_Rotation2Char(UINT8 rot)
 {
-	return ((rotation <= 8) || (rotation == ROT_L) || (rotation == ROT_R));
+	if (rot <=     9) return '0' + rot;
+	if (rot <=    16) return 'A' + (rot - 10);
+	if (rot == ROT_L) return 'L';
+	if (rot == ROT_R) return 'R';
+	return '\xFF';
+}
+
+FUNCMATH FUNCINLINE static ATTRINLINE UINT8 R_Char2Rotation(char cn)
+{
+	if (cn >= '0' && cn <= '9') return (cn - '0');
+	if (cn >= 'A' && cn <= 'G') return (cn - 'A') + 10;
+	if (cn == 'L') return ROT_L;
+	if (cn == 'R') return ROT_R;
+	return 255;
 }
 
 #endif //__R_THINGS__
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 9a1505ce3129cd7aa189020632cbba1986f1958b..68803b6651f7492d4127c079a8f6d300586e2db2 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -1179,10 +1179,20 @@ static void ST_drawInput(void)
 				"AUTOBRAKE");
 			y -= 8;
 		}
-		if (stplyr->pflags & PF_ANALOGMODE)
+		switch (P_ControlStyle(stplyr))
 		{
+		case CS_LMAOGALOG:
 			V_DrawThinString(x, y, hudinfo[HUD_LIVES].f, "ANALOG");
 			y -= 8;
+			break;
+
+		case CS_SIMPLE:
+			V_DrawThinString(x, y, hudinfo[HUD_LIVES].f, "SIMPLE");
+			y -= 8;
+			break;
+
+		default:
+			break;
 		}
 	}
 	if (!demosynced) // should always be last, so it doesn't push anything else around
diff --git a/src/w_wad.c b/src/w_wad.c
index 8de4876d9474400bec7fa5149afd5cdbf2d49f29..44a4854f8730a8584455671772e3ceac6b2cab62 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -1465,6 +1465,21 @@ boolean W_IsPatchCached(lumpnum_t lumpnum, void *ptr)
 	return W_IsPatchCachedPWAD(WADFILENUM(lumpnum),LUMPNUM(lumpnum), ptr);
 }
 
+void W_FlushCachedPatches(void)
+{
+	if (needpatchflush)
+	{
+		Z_FreeTag(PU_CACHE);
+		Z_FreeTag(PU_PATCH);
+		Z_FreeTag(PU_HUDGFX);
+		Z_FreeTag(PU_HWRPATCHINFO);
+		Z_FreeTag(PU_HWRMODELTEXTURE);
+		Z_FreeTag(PU_HWRCACHE);
+		Z_FreeTags(PU_HWRCACHE_UNLOCKED, PU_HWRPATCHINFO_UNLOCKED);
+	}
+	needpatchflush = false;
+}
+
 // ==========================================================================
 // W_CacheLumpName
 // ==========================================================================
@@ -1488,22 +1503,6 @@ void *W_CacheLumpName(const char *name, INT32 tag)
 // Cache a patch into heap memory, convert the patch format as necessary
 //
 
-void W_FlushCachedPatches(void)
-{
-	if (needpatchflush)
-	{
-		Z_FreeTag(PU_CACHE);
-		Z_FreeTag(PU_PATCH);
-		Z_FreeTag(PU_HUDGFX);
-		Z_FreeTag(PU_HWRPATCHINFO);
-		Z_FreeTag(PU_HWRMODELTEXTURE);
-		Z_FreeTag(PU_HWRCACHE);
-		Z_FreeTags(PU_HWRCACHE_UNLOCKED, PU_HWRPATCHINFO_UNLOCKED);
-	}
-	needpatchflush = false;
-}
-
-// Software-only compile cache the data without conversion
 void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
 {
 #ifdef HWRENDER