diff --git a/src/deh_tables.c b/src/deh_tables.c
index 11d8b1a0147aebf3e1dfb3cb5251324ebd76d6e9..4a3467f783a1db51b08fd6536b68a70a6ee549e1 100644
--- a/src/deh_tables.c
+++ b/src/deh_tables.c
@@ -5694,7 +5694,9 @@ struct int_const_s const INT_CONST[] = {
 	{"GC_SYSTEMMENU",GC_SYSTEMMENU},
 	{"GC_SCREENSHOT",GC_SCREENSHOT},
 	{"GC_RECORDGIF",GC_RECORDGIF},
-	{"GC_VIEWPOINT",GC_VIEWPOINT},
+	{"GC_VIEWPOINTNEXT",GC_VIEWPOINTNEXT},
+	{"GC_VIEWPOINT",GC_VIEWPOINTNEXT}, // Alias for retrocompatibility. Remove for the next major version
+	{"GC_VIEWPOINTPREV",GC_VIEWPOINTPREV},
 	{"GC_CUSTOM1",GC_CUSTOM1},
 	{"GC_CUSTOM2",GC_CUSTOM2},
 	{"GC_CUSTOM3",GC_CUSTOM3},
diff --git a/src/g_game.c b/src/g_game.c
index 349d9055863135467af8d604c2dbc6f9240dda43..74bc4271124ca93996f1665e8b4f9b849c8eede2 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1959,6 +1959,85 @@ INT32 pausedelay = 0;
 boolean pausebreakkey = false;
 static INT32 camtoggledelay, camtoggledelay2 = 0;
 
+static boolean ViewpointSwitchResponder(event_t *ev)
+{
+	// ViewpointSwitch Lua hook.
+	UINT8 canSwitchView = 0;
+
+	INT32 direction = 0;
+	if (ev->key == KEY_F12 || ev->key == gamecontrol[GC_VIEWPOINTNEXT][0] || ev->key == gamecontrol[GC_VIEWPOINTNEXT][1])
+		direction = 1;
+	if (ev->key == gamecontrol[GC_VIEWPOINTPREV][0] || ev->key == gamecontrol[GC_VIEWPOINTPREV][1])
+		direction = -1;
+	// This enabled reverse-iterating with shift+F12, sadly I had to
+	// disable this in case your shift key is bound to a control =((
+	//if (shiftdown)
+	//	direction = -direction;
+
+	// allow spy mode changes even during the demo
+	if (!(gamestate == GS_LEVEL && ev->type == ev_keydown && direction != 0))
+		return false;
+
+	if (splitscreen || !netgame)
+	{
+		displayplayer = consoleplayer;
+		return false;
+	}
+
+	// spy mode
+	do
+	{
+		// Wrap in both directions
+		displayplayer += direction;
+		displayplayer = (displayplayer + MAXPLAYERS) % MAXPLAYERS;
+
+		if (!playeringame[displayplayer])
+			continue;
+
+		// Call ViewpointSwitch hooks here.
+		canSwitchView = LUA_HookViewpointSwitch(&players[consoleplayer], &players[displayplayer], false);
+		if (canSwitchView == 1) // Set viewpoint to this player
+			break;
+		else if (canSwitchView == 2) // Skip this player
+			continue;
+
+		if (players[displayplayer].spectator)
+			continue;
+
+		if (G_GametypeHasTeams())
+		{
+			if (players[consoleplayer].ctfteam
+				&& players[displayplayer].ctfteam != players[consoleplayer].ctfteam)
+				continue;
+		}
+		else if (gametyperules & GTR_HIDEFROZEN)
+		{
+			if (players[consoleplayer].pflags & PF_TAGIT)
+				continue;
+		}
+		// Other Tag-based gametypes?
+		else if (G_TagGametype())
+		{
+			if (!players[consoleplayer].spectator
+				&& (players[consoleplayer].pflags & PF_TAGIT) != (players[displayplayer].pflags & PF_TAGIT))
+				continue;
+		}
+		else if (G_GametypeHasSpectators() && G_RingSlingerGametype())
+		{
+			if (!players[consoleplayer].spectator)
+				continue;
+		}
+
+		break;
+	} while (displayplayer != consoleplayer);
+
+	// change statusbar also if playing back demo
+	if (singledemo)
+		ST_changeDemoView();
+
+	return true;
+}
+
 //
 // G_Responder
 // Get info needed to make ticcmd_ts for the players.
@@ -2043,74 +2122,8 @@ boolean G_Responder(event_t *ev)
 		if (HU_Responder(ev))
 			return true; // chat ate the event
 
-	// allow spy mode changes even during the demo
-	if (gamestate == GS_LEVEL && ev->type == ev_keydown
-		&& (ev->key == KEY_F12 || ev->key == gamecontrol[GC_VIEWPOINT][0] || ev->key == gamecontrol[GC_VIEWPOINT][1]))
-	{
-		// ViewpointSwitch Lua hook.
-		UINT8 canSwitchView = 0;
-
-		if (splitscreen || !netgame)
-			displayplayer = consoleplayer;
-		else
-		{
-			// spy mode
-			do
-			{
-				displayplayer++;
-				if (displayplayer == MAXPLAYERS)
-					displayplayer = 0;
-
-				if (!playeringame[displayplayer])
-					continue;
-
-				// Call ViewpointSwitch hooks here.
-				canSwitchView = LUA_HookViewpointSwitch(&players[consoleplayer], &players[displayplayer], false);
-				if (canSwitchView == 1) // Set viewpoint to this player
-					break;
-				else if (canSwitchView == 2) // Skip this player
-					continue;
-
-				if (players[displayplayer].spectator)
-					continue;
-
-				if (G_GametypeHasTeams())
-				{
-					if (players[consoleplayer].ctfteam
-					 && players[displayplayer].ctfteam != players[consoleplayer].ctfteam)
-						continue;
-				}
-				else if (gametyperules & GTR_HIDEFROZEN)
-				{
-					if (players[consoleplayer].pflags & PF_TAGIT)
-						continue;
-				}
-				// Other Tag-based gametypes?
-				else if (G_TagGametype())
-				{
-					if (!players[consoleplayer].spectator
-					 && (players[consoleplayer].pflags & PF_TAGIT) != (players[displayplayer].pflags & PF_TAGIT))
-						continue;
-				}
-				else if (G_GametypeHasSpectators() && G_RingSlingerGametype())
-				{
-					if (!players[consoleplayer].spectator)
-						continue;
-				}
-
-				break;
-			} while (displayplayer != consoleplayer);
-
-			// change statusbar also if playing back demo
-			if (singledemo)
-				ST_changeDemoView();
-
-			// tell who's the view
-			CONS_Printf(M_GetText("Viewpoint: %s\n"), player_names[displayplayer]);
-
-			return true;
-		}
-	}
+	if (ViewpointSwitchResponder(ev))
+		return true;
 
 	// update keys current state
 	G_MapEventsToControls(ev);
diff --git a/src/g_input.c b/src/g_input.c
index 7bb2e799da1379b2ea06238aa273a0d6e1937608..79bd2a4a291b37abedfbbed2c41a2145810f50a9 100644
--- a/src/g_input.c
+++ b/src/g_input.c
@@ -593,7 +593,8 @@ static const char *gamecontrolname[NUM_GAMECONTROLS] =
 	"systemmenu",
 	"screenshot",
 	"recordgif",
-	"viewpoint",
+	"viewpoint", // Rename this to "viewpointnext" for the next major version
+	"viewpointprev",
 	"custom1",
 	"custom2",
 	"custom3",
@@ -713,61 +714,61 @@ void G_DefineDefaultControls(void)
 
 	for (i = 1; i < num_gamecontrolschemes; i++) // skip gcs_custom (0)
 	{
-		gamecontroldefault[i][GC_WEAPONNEXT ][0] = KEY_MOUSEWHEELUP+0;
-		gamecontroldefault[i][GC_WEAPONPREV ][0] = KEY_MOUSEWHEELDOWN+0;
-		gamecontroldefault[i][GC_WEPSLOT1   ][0] = '1';
-		gamecontroldefault[i][GC_WEPSLOT2   ][0] = '2';
-		gamecontroldefault[i][GC_WEPSLOT3   ][0] = '3';
-		gamecontroldefault[i][GC_WEPSLOT4   ][0] = '4';
-		gamecontroldefault[i][GC_WEPSLOT5   ][0] = '5';
-		gamecontroldefault[i][GC_WEPSLOT6   ][0] = '6';
-		gamecontroldefault[i][GC_WEPSLOT7   ][0] = '7';
-		gamecontroldefault[i][GC_WEPSLOT8   ][0] = '8';
-		gamecontroldefault[i][GC_WEPSLOT9   ][0] = '9';
-		gamecontroldefault[i][GC_WEPSLOT10  ][0] = '0';
-		gamecontroldefault[i][GC_TOSSFLAG   ][0] = '\'';
-		gamecontroldefault[i][GC_CAMTOGGLE  ][0] = 'v';
-		gamecontroldefault[i][GC_CAMRESET   ][0] = 'r';
-		gamecontroldefault[i][GC_TALKKEY    ][0] = 't';
-		gamecontroldefault[i][GC_TEAMKEY    ][0] = 'y';
-		gamecontroldefault[i][GC_SCORES     ][0] = KEY_TAB;
-		gamecontroldefault[i][GC_CONSOLE    ][0] = KEY_CONSOLE;
-		gamecontroldefault[i][GC_PAUSE      ][0] = 'p';
-		gamecontroldefault[i][GC_SCREENSHOT ][0] = KEY_F8;
-		gamecontroldefault[i][GC_RECORDGIF  ][0] = KEY_F9;
-		gamecontroldefault[i][GC_VIEWPOINT  ][0] = KEY_F12;
+		gamecontroldefault[i][GC_WEAPONNEXT   ][0] = KEY_MOUSEWHEELUP+0;
+		gamecontroldefault[i][GC_WEAPONPREV   ][0] = KEY_MOUSEWHEELDOWN+0;
+		gamecontroldefault[i][GC_WEPSLOT1     ][0] = '1';
+		gamecontroldefault[i][GC_WEPSLOT2     ][0] = '2';
+		gamecontroldefault[i][GC_WEPSLOT3     ][0] = '3';
+		gamecontroldefault[i][GC_WEPSLOT4     ][0] = '4';
+		gamecontroldefault[i][GC_WEPSLOT5     ][0] = '5';
+		gamecontroldefault[i][GC_WEPSLOT6     ][0] = '6';
+		gamecontroldefault[i][GC_WEPSLOT7     ][0] = '7';
+		gamecontroldefault[i][GC_WEPSLOT8     ][0] = '8';
+		gamecontroldefault[i][GC_WEPSLOT9     ][0] = '9';
+		gamecontroldefault[i][GC_WEPSLOT10    ][0] = '0';
+		gamecontroldefault[i][GC_TOSSFLAG     ][0] = '\'';
+		gamecontroldefault[i][GC_CAMTOGGLE    ][0] = 'v';
+		gamecontroldefault[i][GC_CAMRESET     ][0] = 'r';
+		gamecontroldefault[i][GC_TALKKEY      ][0] = 't';
+		gamecontroldefault[i][GC_TEAMKEY      ][0] = 'y';
+		gamecontroldefault[i][GC_SCORES       ][0] = KEY_TAB;
+		gamecontroldefault[i][GC_CONSOLE      ][0] = KEY_CONSOLE;
+		gamecontroldefault[i][GC_PAUSE        ][0] = 'p';
+		gamecontroldefault[i][GC_SCREENSHOT   ][0] = KEY_F8;
+		gamecontroldefault[i][GC_RECORDGIF    ][0] = KEY_F9;
+		gamecontroldefault[i][GC_VIEWPOINTNEXT][0] = KEY_F12;
 
 		// Gamepad controls -- same for both schemes
-		gamecontroldefault[i][GC_JUMP       ][1] = KEY_JOY1+0; // A
-		gamecontroldefault[i][GC_SPIN       ][1] = KEY_JOY1+2; // X
-		gamecontroldefault[i][GC_CUSTOM1    ][1] = KEY_JOY1+1; // B
-		gamecontroldefault[i][GC_CUSTOM2    ][1] = KEY_JOY1+3; // Y
-		gamecontroldefault[i][GC_CUSTOM3    ][1] = KEY_JOY1+8; // Left Stick
-		gamecontroldefault[i][GC_CENTERVIEW ][1] = KEY_JOY1+9; // Right Stick
-		gamecontroldefault[i][GC_WEAPONPREV ][1] = KEY_JOY1+4; // LB
-		gamecontroldefault[i][GC_WEAPONNEXT ][1] = KEY_JOY1+5; // RB
-		gamecontroldefault[i][GC_SCREENSHOT ][1] = KEY_JOY1+6; // Back
-		gamecontroldefault[i][GC_SYSTEMMENU ][0] = KEY_JOY1+7; // Start
-		gamecontroldefault[i][GC_CAMTOGGLE  ][1] = KEY_HAT1+0; // D-Pad Up
-		gamecontroldefault[i][GC_VIEWPOINT  ][1] = KEY_HAT1+1; // D-Pad Down
-		gamecontroldefault[i][GC_TOSSFLAG   ][1] = KEY_HAT1+2; // D-Pad Left
-		gamecontroldefault[i][GC_SCORES     ][1] = KEY_HAT1+3; // D-Pad Right
+		gamecontroldefault[i][GC_JUMP         ][1] = KEY_JOY1+0; // A
+		gamecontroldefault[i][GC_SPIN         ][1] = KEY_JOY1+2; // X
+		gamecontroldefault[i][GC_CUSTOM1      ][1] = KEY_JOY1+1; // B
+		gamecontroldefault[i][GC_CUSTOM2      ][1] = KEY_JOY1+3; // Y
+		gamecontroldefault[i][GC_CUSTOM3      ][1] = KEY_JOY1+8; // Left Stick
+		gamecontroldefault[i][GC_CENTERVIEW   ][1] = KEY_JOY1+9; // Right Stick
+		gamecontroldefault[i][GC_WEAPONPREV   ][1] = KEY_JOY1+4; // LB
+		gamecontroldefault[i][GC_WEAPONNEXT   ][1] = KEY_JOY1+5; // RB
+		gamecontroldefault[i][GC_SCREENSHOT   ][1] = KEY_JOY1+6; // Back
+		gamecontroldefault[i][GC_SYSTEMMENU   ][0] = KEY_JOY1+7; // Start
+		gamecontroldefault[i][GC_CAMTOGGLE    ][1] = KEY_HAT1+0; // D-Pad Up
+		gamecontroldefault[i][GC_VIEWPOINTNEXT][1] = KEY_HAT1+1; // D-Pad Down
+		gamecontroldefault[i][GC_TOSSFLAG     ][1] = KEY_HAT1+2; // D-Pad Left
+		gamecontroldefault[i][GC_SCORES       ][1] = KEY_HAT1+3; // D-Pad Right
 
 		// Second player controls only have joypad defaults
-		gamecontrolbisdefault[i][GC_JUMP       ][1] = KEY_2JOY1+0; // A
-		gamecontrolbisdefault[i][GC_SPIN       ][1] = KEY_2JOY1+2; // X
-		gamecontrolbisdefault[i][GC_CUSTOM1    ][1] = KEY_2JOY1+1; // B
-		gamecontrolbisdefault[i][GC_CUSTOM2    ][1] = KEY_2JOY1+3; // Y
-		gamecontrolbisdefault[i][GC_CUSTOM3    ][1] = KEY_2JOY1+8; // Left Stick
-		gamecontrolbisdefault[i][GC_CENTERVIEW ][1] = KEY_2JOY1+9; // Right Stick
-		gamecontrolbisdefault[i][GC_WEAPONPREV ][1] = KEY_2JOY1+4; // LB
-		gamecontrolbisdefault[i][GC_WEAPONNEXT ][1] = KEY_2JOY1+5; // RB
-		gamecontrolbisdefault[i][GC_SCREENSHOT ][1] = KEY_2JOY1+6; // Back
-		//gamecontrolbisdefault[i][GC_SYSTEMMENU ][0] = KEY_2JOY1+7; // Start
-		gamecontrolbisdefault[i][GC_CAMTOGGLE  ][1] = KEY_2HAT1+0; // D-Pad Up
-		gamecontrolbisdefault[i][GC_VIEWPOINT  ][1] = KEY_2HAT1+1; // D-Pad Down
-		gamecontrolbisdefault[i][GC_TOSSFLAG   ][1] = KEY_2HAT1+2; // D-Pad Left
-		//gamecontrolbisdefault[i][GC_SCORES     ][1] = KEY_2HAT1+3; // D-Pad Right
+		gamecontrolbisdefault[i][GC_JUMP        ][1] = KEY_2JOY1+0; // A
+		gamecontrolbisdefault[i][GC_SPIN        ][1] = KEY_2JOY1+2; // X
+		gamecontrolbisdefault[i][GC_CUSTOM1     ][1] = KEY_2JOY1+1; // B
+		gamecontrolbisdefault[i][GC_CUSTOM2     ][1] = KEY_2JOY1+3; // Y
+		gamecontrolbisdefault[i][GC_CUSTOM3     ][1] = KEY_2JOY1+8; // Left Stick
+		gamecontrolbisdefault[i][GC_CENTERVIEW  ][1] = KEY_2JOY1+9; // Right Stick
+		gamecontrolbisdefault[i][GC_WEAPONPREV  ][1] = KEY_2JOY1+4; // LB
+		gamecontrolbisdefault[i][GC_WEAPONNEXT  ][1] = KEY_2JOY1+5; // RB
+		gamecontrolbisdefault[i][GC_SCREENSHOT  ][1] = KEY_2JOY1+6; // Back
+		//gamecontrolbisdefault[i][GC_SYSTEMMENU   ][0] = KEY_2JOY1+7; // Start
+		gamecontrolbisdefault[i][GC_CAMTOGGLE    ][1] = KEY_2HAT1+0; // D-Pad Up
+		gamecontrolbisdefault[i][GC_VIEWPOINTNEXT][1] = KEY_2HAT1+1; // D-Pad Down
+		gamecontrolbisdefault[i][GC_TOSSFLAG     ][1] = KEY_2HAT1+2; // D-Pad Left
+		//gamecontrolbisdefault[i][GC_SCORES       ][1] = KEY_2HAT1+3; // D-Pad Right
 	}
 }
 
diff --git a/src/g_input.h b/src/g_input.h
index bf6ad39b3d24e8c941f95330a7d0c07a6cf06d09..400e3fd126bb7873a352a8b307fb57972f6115b9 100644
--- a/src/g_input.h
+++ b/src/g_input.h
@@ -96,7 +96,8 @@ typedef enum
 	GC_SYSTEMMENU,
 	GC_SCREENSHOT,
 	GC_RECORDGIF,
-	GC_VIEWPOINT,
+	GC_VIEWPOINTNEXT,
+	GC_VIEWPOINTPREV,
 	GC_CUSTOM1, // Lua scriptable
 	GC_CUSTOM2, // Lua scriptable
 	GC_CUSTOM3, // Lua scriptable
diff --git a/src/m_menu.c b/src/m_menu.c
index 83b788fd567ae04ceb914ebdb509044792d54f1a..6dffe0d510753c15b4bb35ae8b4dc33f13f2ef8a 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1129,10 +1129,11 @@ static menuitem_t OP_ChangeControlsMenu[] =
 	{IT_CALL | IT_STRING2, NULL, "Game Status",
     M_ChangeControl, GC_SCORES      },
 	{IT_CALL | IT_STRING2, NULL, "Pause / Run Retry", M_ChangeControl, GC_PAUSE      },
-	{IT_CALL | IT_STRING2, NULL, "Screenshot",            M_ChangeControl, GC_SCREENSHOT },
-	{IT_CALL | IT_STRING2, NULL, "Toggle GIF Recording",  M_ChangeControl, GC_RECORDGIF  },
-	{IT_CALL | IT_STRING2, NULL, "Open/Close Menu (ESC)", M_ChangeControl, GC_SYSTEMMENU },
-	{IT_CALL | IT_STRING2, NULL, "Change Viewpoint",      M_ChangeControl, GC_VIEWPOINT  },
+	{IT_CALL | IT_STRING2, NULL, "Screenshot",            M_ChangeControl, GC_SCREENSHOT    },
+	{IT_CALL | IT_STRING2, NULL, "Toggle GIF Recording",  M_ChangeControl, GC_RECORDGIF     },
+	{IT_CALL | IT_STRING2, NULL, "Open/Close Menu (ESC)", M_ChangeControl, GC_SYSTEMMENU    },
+	{IT_CALL | IT_STRING2, NULL, "Next Viewpoint",        M_ChangeControl, GC_VIEWPOINTNEXT },
+	{IT_CALL | IT_STRING2, NULL, "Prev Viewpoint",        M_ChangeControl, GC_VIEWPOINTPREV },
 	{IT_CALL | IT_STRING2, NULL, "Console",          M_ChangeControl, GC_CONSOLE     },
 	{IT_HEADER, NULL, "Multiplayer", NULL, 0},
 	{IT_SPACE, NULL, NULL, NULL, 0}, // padding
@@ -12750,13 +12751,14 @@ static void M_Setup1PControlsMenu(INT32 choice)
 	OP_ChangeControlsMenu[18+5].status = IT_CALL|IT_STRING2;
 	OP_ChangeControlsMenu[18+6].status = IT_CALL|IT_STRING2;
 	//OP_ChangeControlsMenu[18+7].status = IT_CALL|IT_STRING2;
-	OP_ChangeControlsMenu[18+8].status = IT_CALL|IT_STRING2;
+	//OP_ChangeControlsMenu[18+8].status = IT_CALL|IT_STRING2;
+	OP_ChangeControlsMenu[18+9].status = IT_CALL|IT_STRING2;
 	// ...
-	OP_ChangeControlsMenu[27+0].status = IT_HEADER;
-	OP_ChangeControlsMenu[27+1].status = IT_SPACE;
+	OP_ChangeControlsMenu[28+0].status = IT_HEADER;
+	OP_ChangeControlsMenu[28+1].status = IT_SPACE;
 	// ...
-	OP_ChangeControlsMenu[27+2].status = IT_CALL|IT_STRING2;
-	OP_ChangeControlsMenu[27+3].status = IT_CALL|IT_STRING2;
+	OP_ChangeControlsMenu[28+2].status = IT_CALL|IT_STRING2;
+	OP_ChangeControlsMenu[28+3].status = IT_CALL|IT_STRING2;
 
 	OP_ChangeControlsDef.prevMenu = &OP_P1ControlsDef;
 	OP_ChangeControlsDef.menuid &= ~(((1 << MENUBITS) - 1) << MENUBITS); // remove second level
@@ -12781,13 +12783,14 @@ static void M_Setup2PControlsMenu(INT32 choice)
 	OP_ChangeControlsMenu[18+5].status = IT_GRAYEDOUT2;
 	OP_ChangeControlsMenu[18+6].status = IT_GRAYEDOUT2;
 	//OP_ChangeControlsMenu[18+7].status = IT_GRAYEDOUT2;
-	OP_ChangeControlsMenu[18+8].status = IT_GRAYEDOUT2;
+	//OP_ChangeControlsMenu[18+8].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[18+9].status = IT_GRAYEDOUT2;
 	// ...
-	OP_ChangeControlsMenu[27+0].status = IT_GRAYEDOUT2;
-	OP_ChangeControlsMenu[27+1].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[28+0].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[28+1].status = IT_GRAYEDOUT2;
 	// ...
-	OP_ChangeControlsMenu[27+2].status = IT_GRAYEDOUT2;
-	OP_ChangeControlsMenu[27+3].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[28+2].status = IT_GRAYEDOUT2;
+	OP_ChangeControlsMenu[28+3].status = IT_GRAYEDOUT2;
 
 	OP_ChangeControlsDef.prevMenu = &OP_P2ControlsDef;
 	OP_ChangeControlsDef.menuid &= ~(((1 << MENUBITS) - 1) << MENUBITS); // remove second level