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 a4571eddbf26f0a736f8b08653adc850b5e0aeda..8220f5aa3f4391317a660b9935019ef253ff61b8 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -9741,6 +9741,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 c47833187b1af03f7308d3844fbcce51baa22bd0..ccffc8a4934785012b617cd8696dd88c35ad6116 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -122,11 +122,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 ?)
@@ -654,7 +654,7 @@ static void HWR_GenerateTexture(INT32 texnum, GLTexture_t *grtex)
 
 #ifndef NO_PNG_LUMPS
 		if (R_IsLumpPNG((UINT8 *)realpatch, lumplength))
-			realpatch = R_PNGToPatch((UINT8 *)realpatch, lumplength, NULL, false);
+			realpatch = R_PNGToPatch((UINT8 *)realpatch, lumplength, NULL);
 		else
 #endif
 #ifdef WALLFLATS
@@ -698,7 +698,7 @@ void HWR_MakePatch (const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipm
 	// lump is a png so convert it
 	size_t len = W_LumpLengthPwad(grPatch->wadnum, grPatch->lumpnum);
 	if ((patch != NULL) && R_IsLumpPNG((const UINT8 *)patch, len))
-		patch = R_PNGToPatch((const UINT8 *)patch, len, NULL, true);
+		patch = R_PNGToPatch((const UINT8 *)patch, len, NULL);
 #endif
 
 	// don't do it twice (like a cache)
@@ -1324,23 +1324,6 @@ GLPatch_t *HWR_GetCachedGLPatch(lumpnum_t lumpnum)
 	return HWR_GetCachedGLPatchPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum));
 }
 
-#ifdef ROTSPRITE
-GLPatch_t *HWR_GetCachedGLRotSprite(aatree_t *hwrcache, UINT16 rollangle, patch_t *rawpatch)
-{
-	GLPatch_t *grpatch;
-
-	if (!(grpatch = M_AATreeGet(hwrcache, rollangle)))
-	{
-		grpatch = Z_Calloc(sizeof(GLPatch_t), PU_HWRPATCHINFO, NULL);
-		grpatch->rawpatch = rawpatch;
-		grpatch->mipmap = Z_Calloc(sizeof(GLMipmap_t), PU_HWRPATCHINFO, NULL);
-		M_AATreeSet(hwrcache, rollangle, grpatch);
-	}
-
-	return grpatch;
-}
-#endif
-
 // Need to do this because they aren't powers of 2
 static void HWR_DrawFadeMaskInCache(GLMipmap_t *mipmap, INT32 pblockwidth, INT32 pblockheight,
 	lumpnum_t fademasklumpnum, UINT16 fmwidth, UINT16 fmheight)
diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h
index cf98e731739ce59016dd6347f531a1079c59f813..a2bf79817ac15f1b85740dfbe20bd121cd2e880d 100644
--- a/src/hardware/hw_glob.h
+++ b/src/hardware/hw_glob.h
@@ -113,9 +113,6 @@ GLPatch_t *HWR_GetPic(lumpnum_t lumpnum);
 void HWR_SetPalette(RGBA_t *palette);
 GLPatch_t *HWR_GetCachedGLPatchPwad(UINT16 wad, UINT16 lump);
 GLPatch_t *HWR_GetCachedGLPatch(lumpnum_t lumpnum);
-#ifdef ROTSPRITE
-GLPatch_t *HWR_GetCachedGLRotSprite(aatree_t *hwrcache, UINT16 rollangle, patch_t *rawpatch);
-#endif
 void HWR_GetFadeMask(lumpnum_t fademasklumpnum);
 
 // --------
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/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_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 c468e5b3c330f8ac885e61c36420749b1062ec27..b6f5321244ef927f6ce668f856589b2a7c634e4b 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;
@@ -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;
 	}
@@ -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);
@@ -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_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 c61f97d92c6885e5460d606dc2d9450d3a7a66ac..104b4e5a01fbe6af06c42cd58fba3487e3518dcb 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -2850,7 +2850,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.
@@ -3161,26 +3161,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);
 	}
 }
 
@@ -3427,6 +3427,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 00a71602ba21cb94c9a1e179557131e3866930cb..cc0b185adb960965df9a690146ea9a949780f4d2 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -4614,7 +4614,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;
@@ -9127,7 +9127,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..c5024d7ec34e4676eaccc26f4122b3b0afc88d67 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"
@@ -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 26fc0043c59beb28956a5e180b97cfebc460d5bf..db18a8833ab8eaa186605593ab62fee5cb7fa56a 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -571,7 +571,7 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 
 #ifndef NO_PNG_LUMPS
 		if (R_IsLumpPNG((UINT8 *)realpatch, lumplength))
-			realpatch = R_PNGToPatch((UINT8 *)realpatch, lumplength, NULL, false);
+			realpatch = R_PNGToPatch((UINT8 *)realpatch, lumplength, NULL);
 		else
 #endif
 #ifdef WALLFLATS
diff --git a/src/r_defs.h b/src/r_defs.h
index a841bca081adce50c767f4dabc0f788471bd9912..52b9bea951d9ad0c914157cf8850c968794dafd1 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -729,9 +729,6 @@ typedef struct
 {
 	patch_t *patch[16][ROTANGLES];
 	UINT16 cached;
-#ifdef HWRENDER
-	aatree_t *hardware_patch[16];
-#endif/*HWRENDER*/
 } rotsprite_t;
 #endif/*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.c b/src/r_patch.c
index 7fb764d221a1dcb7537a5d045ff7821ff0e0381b..b4127f825e0a97d5164d5e3f1a009354259559d3 100644
--- a/src/r_patch.c
+++ b/src/r_patch.c
@@ -2,8 +2,8 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 2005-2009 by Andrey "entryway" Budko.
-// Copyright (C) 2018-2019 by Jaime "Lactozilla" Passos.
-// Copyright (C) 2019      by Sonic Team Junior.
+// Copyright (C) 2018-2020 by Jaime "Lactozilla" Passos.
+// Copyright (C) 2019-2020 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -199,11 +199,15 @@ void R_PatchToFlat(patch_t *patch, UINT8 *flat)
 }
 
 //
-// R_PatchToFlat_16bpp
+// R_PatchToMaskedFlat
 //
-// Convert a patch to a 16-bit flat.
+// Convert a patch to a masked flat.
+// Now, what is a "masked" flat anyway?
+// It means the flat uses two bytes to store image data.
+// The upper byte is used to store the transparent pixel,
+// and the lower byte stores a palette index.
 //
-void R_PatchToFlat_16bpp(patch_t *patch, UINT16 *raw, boolean flip)
+void R_PatchToMaskedFlat(patch_t *patch, UINT16 *raw, boolean flip)
 {
 	fixed_t col, ofs;
 	column_t *column;
@@ -248,6 +252,9 @@ patch_t *R_FlatToPatch(UINT8 *raw, UINT16 width, UINT16 height, UINT16 leftoffse
 	UINT8 *colpointers, *startofspan;
 	size_t size = 0;
 
+	if (!raw)
+		return NULL;
+
 	// Write image size and offset
 	WRITEINT16(imgptr, width);
 	WRITEINT16(imgptr, height);
@@ -347,16 +354,18 @@ patch_t *R_FlatToPatch(UINT8 *raw, UINT16 width, UINT16 height, UINT16 leftoffse
 }
 
 //
-// R_FlatToPatch_16bpp
+// R_MaskedFlatToPatch
 //
-// Convert a 16-bit flat to a patch.
+// Convert a masked flat to a patch.
+// Explanation of "masked" flats in R_PatchToMaskedFlat.
 //
-patch_t *R_FlatToPatch_16bpp(UINT16 *raw, UINT16 width, UINT16 height, size_t *size)
+patch_t *R_MaskedFlatToPatch(UINT16 *raw, UINT16 width, UINT16 height, UINT16 leftoffset, UINT16 topoffset, size_t *destsize)
 {
 	UINT32 x, y;
 	UINT8 *img;
 	UINT8 *imgptr = imgbuf;
 	UINT8 *colpointers, *startofspan;
+	size_t size = 0;
 
 	if (!raw)
 		return NULL;
@@ -364,9 +373,8 @@ patch_t *R_FlatToPatch_16bpp(UINT16 *raw, UINT16 width, UINT16 height, size_t *s
 	// Write image size and offset
 	WRITEINT16(imgptr, width);
 	WRITEINT16(imgptr, height);
-	// no offsets
-	WRITEINT16(imgptr, 0);
-	WRITEINT16(imgptr, 0);
+	WRITEINT16(imgptr, leftoffset);
+	WRITEINT16(imgptr, topoffset);
 
 	// Leave placeholder to column pointers
 	colpointers = imgptr;
@@ -450,9 +458,12 @@ patch_t *R_FlatToPatch_16bpp(UINT16 *raw, UINT16 width, UINT16 height, size_t *s
 		WRITEUINT8(imgptr, 0xFF);
 	}
 
-	*size = imgptr-imgbuf;
-	img = Z_Malloc(*size, PU_STATIC, NULL);
-	memcpy(img, imgbuf, *size);
+	size = imgptr-imgbuf;
+	img = Z_Malloc(size, PU_STATIC, NULL);
+	memcpy(img, imgbuf, size);
+
+	if (destsize != NULL)
+		*destsize = size;
 	return (patch_t *)img;
 }
 
@@ -675,6 +686,41 @@ static UINT8 *PNG_RawConvert(const UINT8 *png, UINT16 *w, UINT16 *h, INT16 *topo
 	return flat;
 }
 
+// Convert a PNG with transparency to a raw image.
+static UINT16 *PNG_MaskedRawConvert(const UINT8 *png, UINT16 *w, UINT16 *h, INT16 *topoffset, INT16 *leftoffset, size_t size)
+{
+	UINT16 *flat;
+	png_uint_32 x, y;
+	png_bytep *row_pointers = PNG_Read(png, w, h, topoffset, leftoffset, size);
+	png_uint_32 width = *w, height = *h;
+	size_t flatsize, i;
+
+	if (!row_pointers)
+		I_Error("PNG_MaskedRawConvert: conversion failed");
+
+	// Convert the image to 16bpp
+	flatsize = (width * height);
+	flat = Z_Malloc(flatsize * sizeof(UINT16), PU_LEVEL, NULL);
+
+	// can't memset here
+	for (i = 0; i < flatsize; i++)
+		flat[i] = 0xFF00;
+
+	for (y = 0; y < height; y++)
+	{
+		png_bytep row = row_pointers[y];
+		for (x = 0; x < width; x++)
+		{
+			png_bytep px = &(row[x * 4]);
+			if ((UINT8)px[3])
+				flat[((y * width) + x)] = NearestColor((UINT8)px[0], (UINT8)px[1], (UINT8)px[2]);
+		}
+	}
+	free(row_pointers);
+
+	return flat;
+}
+
 //
 // R_PNGToFlat
 //
@@ -690,16 +736,16 @@ UINT8 *R_PNGToFlat(UINT16 *width, UINT16 *height, UINT8 *png, size_t size)
 //
 // Convert a PNG to a patch.
 //
-patch_t *R_PNGToPatch(const UINT8 *png, size_t size, size_t *destsize, boolean transparency)
+patch_t *R_PNGToPatch(const UINT8 *png, size_t size, size_t *destsize)
 {
 	UINT16 width, height;
 	INT16 topoffset = 0, leftoffset = 0;
-	UINT8 *raw = PNG_RawConvert(png, &width, &height, &topoffset, &leftoffset, size);
+	UINT16 *raw = PNG_MaskedRawConvert(png, &width, &height, &topoffset, &leftoffset, size);
 
 	if (!raw)
 		I_Error("R_PNGToPatch: conversion failed");
 
-	return R_FlatToPatch(raw, width, height, leftoffset, topoffset, destsize, transparency);
+	return R_MaskedFlatToPatch(raw, width, height, leftoffset, topoffset, destsize);
 }
 
 //
@@ -1150,7 +1196,7 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 	INT32 angle;
 	patch_t *patch;
 	patch_t *newpatch;
-	UINT16 *rawdst;
+	UINT16 *rawsrc, *rawdst;
 	size_t size;
 	INT32 bflip = (flip != 0x00);
 
@@ -1196,6 +1242,17 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 			leftoffset = width - leftoffset;
 		}
 
+		// Draw the sprite to a temporary buffer.
+		size = (width*height);
+		rawsrc = Z_Malloc(size * sizeof(UINT16), PU_STATIC, NULL);
+
+		// can't memset here
+		for (i = 0; i < size; i++)
+			rawsrc[i] = 0xFF00;
+
+		R_PatchToMaskedFlat(patch, rawsrc, bflip);
+
+		// Don't cache angle = 0
 		for (angle = 1; angle < ROTANGLES; angle++)
 		{
 			INT32 newwidth, newheight;
@@ -1287,7 +1344,7 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 			}
 
 			// make patch
-			newpatch = R_FlatToPatch_16bpp(rawdst, newwidth, newheight, &size);
+			newpatch = R_MaskedFlatToPatch(rawdst, newwidth, newheight, 0, 0, &size);
 			{
 				newpatch->leftoffset = (newpatch->width / 2) + (leftoffset - px);
 				newpatch->topoffset = (newpatch->height / 2) + (patch->topoffset - py);
@@ -1303,9 +1360,11 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 #ifdef HWRENDER
 			if (rendermode == render_opengl)
 			{
-				GLPatch_t *grPatch = HWR_GetCachedGLRotSprite(sprframe->rotsprite.hardware_patch[rot], angle, newpatch);
-				HWR_MakePatch(newpatch, grPatch, grPatch->mipmap, false);
+				GLPatch_t *grPatch = Z_Calloc(sizeof(GLPatch_t), PU_HWRPATCHINFO, NULL);
+				grPatch->mipmap = Z_Calloc(sizeof(GLMipmap_t), PU_HWRPATCHINFO, NULL);
+				grPatch->rawpatch = newpatch;
 				sprframe->rotsprite.patch[rot][angle] = (patch_t *)grPatch;
+				HWR_MakePatch(newpatch, grPatch, grPatch->mipmap, false);
 			}
 			else
 #endif // HWRENDER
diff --git a/src/r_patch.h b/src/r_patch.h
index 2961448a0bc10d1401ae5bf39e8dc06f6664a30f..e743a7d097c057f2b9d3892a72f2b96622c2b176 100644
--- a/src/r_patch.h
+++ b/src/r_patch.h
@@ -41,9 +41,9 @@ typedef struct
 boolean R_CheckIfPatch(lumpnum_t lump);
 void R_TextureToFlat(size_t tex, UINT8 *flat);
 void R_PatchToFlat(patch_t *patch, UINT8 *flat);
-void R_PatchToFlat_16bpp(patch_t *patch, UINT16 *raw, boolean flip);
+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_FlatToPatch_16bpp(UINT16 *raw, UINT16 width, UINT16 height, size_t *size);
+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);
@@ -51,7 +51,7 @@ boolean R_IsLumpPNG(const UINT8 *d, size_t s);
 
 #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 transparency);
+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
 
diff --git a/src/r_things.c b/src/r_things.c
index 233d7188a2c9bfef43abf26408c617534bb1c389..d80d74d788674efaf56388394b0867800f6f8f10 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -122,9 +122,6 @@ static void R_InstallSpriteLump(UINT16 wad,            // graphics patch
 	{
 		for (ang = 0; ang < ROTANGLES; ang++)
 			sprtemp[frame].rotsprite.patch[r][ang] = NULL;
-#ifdef HWRENDER
-		sprtemp[frame].rotsprite.hardware_patch[r] = M_AATreeAlloc(AATREE_ZUSER);
-#endif/*HWRENDER*/
 	}
 #endif/*ROTSPRITE*/
 
@@ -294,7 +291,7 @@ static boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef,
 				// lump is a png so convert it
 				if (R_IsLumpPNG((UINT8 *)png, len))
 				{
-					png = R_PNGToPatch((UINT8 *)png, len, NULL, true);
+					png = R_PNGToPatch((UINT8 *)png, len, NULL);
 					M_Memcpy(&patch, png, sizeof(INT16)*4);
 				}
 				Z_Free(png);
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 1aa2814264935ac92aa3c11d425142fe5795836a..dc400987f23a4aaabe98ae66d46dd874d2c54556 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
@@ -1541,7 +1540,7 @@ void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
 			if (R_IsLumpPNG((UINT8 *)lumpdata, len))
 			{
 				size_t newlen;
-				srcdata = R_PNGToPatch((UINT8 *)lumpdata, len, &newlen, true);
+				srcdata = R_PNGToPatch((UINT8 *)lumpdata, len, &newlen);
 				ptr = Z_Realloc(ptr, newlen, tag, &lumpcache[lump]);
 				M_Memcpy(ptr, srcdata, newlen);
 				Z_Free(srcdata);