diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 9e519d61f896cfe4d1430671b00c918e8912ca42..4b47d4a56fbff9d1f83d509a3ad9f6fe800d589a 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -97,7 +97,7 @@ SINT8 nodetoplayer[MAXNETNODES];
 SINT8 nodetoplayer2[MAXNETNODES]; // say the numplayer for this node if any (splitscreen)
 SINT8 nodetoplayer3[MAXNETNODES]; // say the numplayer for this node if any (splitscreen3)
 SINT8 nodetoplayer4[MAXNETNODES]; // say the numplayer for this node if any (splitscreen4)
-UINT8 playerpernode[MAXNETNODES]; // used specialy for scplitscreen
+UINT8 playerpernode[MAXNETNODES]; // used specialy for splitscreen
 boolean nodeingame[MAXNETNODES]; // set false as nodes leave game
 static tic_t nettics[MAXNETNODES]; // what tic the client have received
 static tic_t supposedtics[MAXNETNODES]; // nettics prevision for smaller packet
@@ -269,6 +269,38 @@ void SendNetXCmd2(netxcmd_t id, const void *param, size_t nparam)
 	}
 }
 
+void SendNetXCmd3(netxcmd_t id, const void *param, size_t nparam)
+{
+	if (localtextcmd3[0]+2+nparam > MAXTEXTCMD)
+	{
+		I_Error("No more place in the buffer for netcmd %d\n",id);
+		return;
+	}
+	localtextcmd3[0]++;
+	localtextcmd3[localtextcmd3[0]] = (UINT8)id;
+	if (param && nparam)
+	{
+		M_Memcpy(&localtextcmd3[localtextcmd3[0]+1], param, nparam);
+		localtextcmd3[0] = (UINT8)(localtextcmd3[0] + (UINT8)nparam);
+	}
+}
+
+void SendNetXCmd4(netxcmd_t id, const void *param, size_t nparam)
+{
+	if (localtextcmd4[0]+2+nparam > MAXTEXTCMD)
+	{
+		I_Error("No more place in the buffer for netcmd %d\n",id);
+		return;
+	}
+	localtextcmd4[0]++;
+	localtextcmd4[localtextcmd4[0]] = (UINT8)id;
+	if (param && nparam)
+	{
+		M_Memcpy(&localtextcmd4[localtextcmd4[0]+1], param, nparam);
+		localtextcmd4[0] = (UINT8)(localtextcmd4[0] + (UINT8)nparam);
+	}
+}
+
 UINT8 GetFreeXCmdSize(void)
 {
 	// -1 for the size and another -1 for the ID.
@@ -2708,9 +2740,9 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 	// Is playernum authorized to make this kick?
 	if (playernum != serverplayer && !IsPlayerAdmin(playernum)
 		&& !(playerpernode[playernode[playernum]] >= 2
-		&& nodetoplayer2[playernode[playernum]] == pnum
-		&& nodetoplayer3[playernode[playernum]] == pnum
-		&& nodetoplayer4[playernode[playernum]] == pnum))
+		&& (nodetoplayer2[playernode[playernum]] == pnum
+		|| nodetoplayer3[playernode[playernum]] == pnum
+		|| nodetoplayer4[playernode[playernum]] == pnum)))
 	{
 		// We received a kick command from someone who isn't the
 		// server or admin, and who isn't in splitscreen removing
@@ -3115,24 +3147,22 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 	if (node == mynode)
 	{
 		playernode[newplayernum] = 0; // for information only
-		if (splitscreenplayer == 0)
-		{
-			consoleplayer = newplayernum;
-			displayplayer = newplayernum;
-			secondarydisplayplayer = newplayernum;
-			thirddisplayplayer = newplayernum;
-			fourthdisplayplayer = newplayernum;
-			DEBFILE("spawning me\n");
-			// Apply player flags as soon as possible!
-			players[newplayernum].pflags &= ~(PF_FLIPCAM|PF_ANALOGMODE);
-			if (cv_flipcam.value)
-				players[newplayernum].pflags |= PF_FLIPCAM;
-			if (cv_analog.value)
-				players[newplayernum].pflags |= PF_ANALOGMODE;
-		}
-		else
+		if (splitscreenplayer)
 		{
-			if (splitscreenplayer == 2)
+			if (splitscreenplayer == 1)
+			{
+				secondarydisplayplayer = newplayernum;
+				DEBFILE("spawning my brother\n");
+				if (botingame)
+					players[newplayernum].bot = 1;
+				// Same goes for player 2 when relevant
+				players[newplayernum].pflags &= ~(PF_FLIPCAM|PF_ANALOGMODE);
+				if (cv_flipcam2.value)
+					players[newplayernum].pflags |= PF_FLIPCAM;
+				if (cv_analog2.value)
+					players[newplayernum].pflags |= PF_ANALOGMODE;
+			}
+			else if (splitscreenplayer == 2)
 			{
 				thirddisplayplayer = newplayernum;
 				DEBFILE("spawning my sister\n");
@@ -3152,19 +3182,21 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 				if (cv_analog4.value)
 					players[newplayernum].pflags |= PF_ANALOGMODE;
 			}
-			else
-			{
-				secondarydisplayplayer = newplayernum;
-				DEBFILE("spawning my brother\n");
-				if (botingame)
-					players[newplayernum].bot = 1;
-				// Same goes for player 2 when relevant
-				players[newplayernum].pflags &= ~(PF_FLIPCAM|PF_ANALOGMODE);
-				if (cv_flipcam2.value)
-					players[newplayernum].pflags |= PF_FLIPCAM;
-				if (cv_analog2.value)
-					players[newplayernum].pflags |= PF_ANALOGMODE;
-			}
+		}
+		else
+		{
+			consoleplayer = newplayernum;
+			displayplayer = newplayernum;
+			secondarydisplayplayer = newplayernum;
+			thirddisplayplayer = newplayernum;
+			fourthdisplayplayer = newplayernum;
+			DEBFILE("spawning me\n");
+			// Apply player flags as soon as possible!
+			players[newplayernum].pflags &= ~(PF_FLIPCAM|PF_ANALOGMODE);
+			if (cv_flipcam.value)
+				players[newplayernum].pflags |= PF_FLIPCAM;
+			if (cv_analog.value)
+				players[newplayernum].pflags |= PF_ANALOGMODE;
 		}
 		D_SendPlayerConfig();
 		addedtogame = true;
@@ -3233,7 +3265,7 @@ static boolean SV_AddWaitingPlayers(void)
 				nodetoplayer3[node] = newplayernum;
 				buf[1] += MAXPLAYERS*2;
 			}
-			else if (playerpernode[node] < 4)
+			else
 			{
 				nodetoplayer4[node] = newplayernum;
 				buf[1] += MAXPLAYERS*3;
@@ -3752,6 +3784,7 @@ FILESTAMP
 			realend = ExpandTics(netbuffer->u.clientpak.resendfrom);
 
 			if (netbuffer->packettype == PT_CLIENTMIS || netbuffer->packettype == PT_CLIENT2MIS
+				|| netbuffer->packettype == PT_CLIENT3MIS || netbuffer->packettype == PT_CLIENT4MIS
 				|| netbuffer->packettype == PT_NODEKEEPALIVEMIS
 				|| supposedtics[node] < realend)
 			{
@@ -3800,20 +3833,26 @@ FILESTAMP
 			if (((netbuffer->packettype == PT_CLIENT2CMD || netbuffer->packettype == PT_CLIENT2MIS)
 				|| (netbuffer->packettype == PT_CLIENT3CMD || netbuffer->packettype == PT_CLIENT3MIS)
 				|| (netbuffer->packettype == PT_CLIENT4CMD || netbuffer->packettype == PT_CLIENT4MIS))
-				&& nodetoplayer2[node] >= 0)
+				&& (nodetoplayer2[node] >= 0))
+			{
 				G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][(UINT8)nodetoplayer2[node]],
 					&netbuffer->u.client2pak.cmd2, 1);
-	
+			}
+
 			if (((netbuffer->packettype == PT_CLIENT3CMD || netbuffer->packettype == PT_CLIENT3MIS)
 				|| (netbuffer->packettype == PT_CLIENT4CMD || netbuffer->packettype == PT_CLIENT4MIS))
-				&& nodetoplayer3[node] >= 0)
+				&& (nodetoplayer3[node] >= 0))
+			{
 				G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][(UINT8)nodetoplayer3[node]],
 					&netbuffer->u.client3pak.cmd3, 1);
+			}
 
 			if ((netbuffer->packettype == PT_CLIENT4CMD || netbuffer->packettype == PT_CLIENT4MIS)
-				&& nodetoplayer4[node] >= 0)
+				&& (nodetoplayer4[node] >= 0))
+			{
 				G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][(UINT8)nodetoplayer4[node]],
 					&netbuffer->u.client4pak.cmd4, 1);
+			}
 
 			// A delay before we check resynching
 			// Used on join or just after a synch fail
@@ -3948,6 +3987,7 @@ FILESTAMP
 					buf[1] = KICK_MSG_PLAYER_QUIT;
 				SendNetXCmd(XD_KICK, &buf, 2);
 				nodetoplayer[node] = -1;
+
 				if (nodetoplayer2[node] != -1 && nodetoplayer2[node] >= 0
 					&& playeringame[(UINT8)nodetoplayer2[node]])
 				{
@@ -3955,6 +3995,7 @@ FILESTAMP
 					SendNetXCmd(XD_KICK, &buf, 2);
 					nodetoplayer2[node] = -1;
 				}
+
 				if (nodetoplayer3[node] != -1 && nodetoplayer3[node] >= 0
 					&& playeringame[(UINT8)nodetoplayer3[node]])
 				{
@@ -3962,6 +4003,7 @@ FILESTAMP
 					SendNetXCmd(XD_KICK, &buf, 2);
 					nodetoplayer3[node] = -1;
 				}
+
 				if (nodetoplayer4[node] != -1 && nodetoplayer4[node] >= 0
 					&& playeringame[(UINT8)nodetoplayer4[node]])
 				{
@@ -4326,7 +4368,7 @@ static void CL_SendClientCmd(void)
 	if (gamestate == GS_WAITINGPLAYERS)
 	{
 		// Send PT_NODEKEEPALIVE packet
-		netbuffer->packettype += 4;
+		netbuffer->packettype += 8;
 		packetsize = sizeof (clientcmd_pak) - sizeof (ticcmd_t) - sizeof (INT16);
 		HSendPacket(servernode, false, 0, packetsize);
 	}
@@ -4335,23 +4377,26 @@ static void CL_SendClientCmd(void)
 		G_MoveTiccmd(&netbuffer->u.clientpak.cmd, &localcmds, 1);
 		netbuffer->u.clientpak.consistancy = SHORT(consistancy[gametic%BACKUPTICS]);
 
-		if (splitscreen4)
+		if (splitscreen || botingame) // Send a special packet with 2 cmd for splitscreen
 		{
-			netbuffer->packettype += 6;
-			G_MoveTiccmd(&netbuffer->u.client4pak.cmd2, &localcmds2, 1);
-			G_MoveTiccmd(&netbuffer->u.client4pak.cmd3, &localcmds3, 1);
-			G_MoveTiccmd(&netbuffer->u.client4pak.cmd4, &localcmds4, 1);
+			netbuffer->packettype += 2;
+			G_MoveTiccmd(&netbuffer->u.client2pak.cmd2, &localcmds2, 1);
+			packetsize = sizeof (client2cmd_pak);
 		}
 		else if (splitscreen3)
 		{
 			netbuffer->packettype += 4;
-			G_MoveTiccmd(&netbuffer->u.client3pak.cmd2, &localcmds2, 1);
+			G_MoveTiccmd(&netbuffer->u.client2pak.cmd2, &localcmds2, 1);
 			G_MoveTiccmd(&netbuffer->u.client3pak.cmd3, &localcmds3, 1);
+			packetsize = sizeof (client3cmd_pak);
 		}
-		else if (splitscreen || botingame) // Send a special packet with 2 cmd for splitscreen
+		else if (splitscreen4)
 		{
-			netbuffer->packettype += 2;
+			netbuffer->packettype += 6;
 			G_MoveTiccmd(&netbuffer->u.client2pak.cmd2, &localcmds2, 1);
+			G_MoveTiccmd(&netbuffer->u.client3pak.cmd3, &localcmds3, 1);
+			G_MoveTiccmd(&netbuffer->u.client4pak.cmd4, &localcmds4, 1);
+			packetsize = sizeof (client4cmd_pak);
 		}
 		else
 			packetsize = sizeof (clientcmd_pak);
@@ -4380,6 +4425,26 @@ static void CL_SendClientCmd(void)
 			if (HSendPacket(servernode, true, 0, localtextcmd2[0]+1)) // Send can fail...
 				localtextcmd2[0] = 0;
 		}
+
+		// Send extra data if needed for player 3 (splitscreen3)
+		if (localtextcmd3[0])
+		{
+			netbuffer->packettype = PT_TEXTCMD3;
+			M_Memcpy(netbuffer->u.textcmd, localtextcmd3, localtextcmd3[0]+1);
+			// All extra data have been sent
+			if (HSendPacket(servernode, true, 0, localtextcmd3[0]+1)) // Send can fail...
+				localtextcmd3[0] = 0;
+		}
+
+		// Send extra data if needed for player 4 (splitscreen4)
+		if (localtextcmd4[0])
+		{
+			netbuffer->packettype = PT_TEXTCMD4;
+			M_Memcpy(netbuffer->u.textcmd, localtextcmd4, localtextcmd4[0]+1);
+			// All extra data have been sent
+			if (HSendPacket(servernode, true, 0, localtextcmd4[0]+1)) // Send can fail...
+				localtextcmd4[0] = 0;
+		}
 	}
 }
 
@@ -4558,8 +4623,12 @@ static void SV_Maketic(void)
 				CONS_Debug(DBG_NETPLAY, "Client Misstic %d\n", maketic);
 #endif
 				// copy the old tic
-				for (i = 0; i < playerpernode[j]; i++, player = nodetoplayer2[j])
+				for (i = 0; i < playerpernode[j]; i++)
 				{
+					if (i == 0) player = nodetoplayer[j];
+					else if (i == 1) player = nodetoplayer2[j];
+					else if (i == 2) player = nodetoplayer3[j];
+					else if (i == 3) player = nodetoplayer4[j];
 					netcmds[maketic%BACKUPTICS][player] = netcmds[(maketic-1)%BACKUPTICS][player];
 					netcmds[maketic%BACKUPTICS][player].angleturn &= ~TICCMD_RECEIVED;
 				}
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index 702e5d603bb27baa79fa496eb299ade417c32abe..bc902ac510b8cc5907200fdc478411093dee5d93 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -70,8 +70,8 @@ typedef enum
 
 	PT_TEXTCMD,       // Extra text commands from the client.
 	PT_TEXTCMD2,      // Splitscreen text commands.
-	PT_TEXTCMD3,
-	PT_TEXTCMD4,
+	PT_TEXTCMD3,      // 3P
+	PT_TEXTCMD4,      // 4P
 	PT_CLIENTJOIN,    // Client wants to join; used in start game.
 	PT_NODETIMEOUT,   // Packet sent to self if the connection times out.
 	PT_RESYNCHING,    // Packet sent to resync players.
@@ -506,6 +506,8 @@ void D_ClientServerInit(void);
 void RegisterNetXCmd(netxcmd_t id, void (*cmd_f)(UINT8 **p, INT32 playernum));
 void SendNetXCmd(netxcmd_t id, const void *param, size_t nparam);
 void SendNetXCmd2(netxcmd_t id, const void *param, size_t nparam); // splitsreen player
+void SendNetXCmd3(netxcmd_t id, const void *param, size_t nparam); // splitsreen3 player
+void SendNetXCmd4(netxcmd_t id, const void *param, size_t nparam); // splitsreen4 player
 
 // Create any new ticcmds and broadcast to other players.
 void NetUpdate(void);
diff --git a/src/d_main.c b/src/d_main.c
index 367bd01290070c1ae94d9866202b2010ddd589b3..6e469aada31269bf9bd9af501e05f3b290043e97 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -401,7 +401,7 @@ static void D_Display(void)
 			}
 
 			// render the second screen
-			if (splitscreen && players[secondarydisplayplayer].mo)
+			if ((splitscreen || splitscreen3 || splitscreen4) && players[secondarydisplayplayer].mo)
 			{
 #ifdef HWRENDER
 				if (rendermode != render_soft)
@@ -410,6 +410,8 @@ static void D_Display(void)
 #endif
 				if (rendermode != render_none)
 				{
+					if (splitscreen3 || splitscreen4)
+						viewwindowx = vid.width / 2;
 					viewwindowy = vid.height / 2;
 					M_Memcpy(ylookup, ylookup2, viewheight*sizeof (ylookup[0]));
 
diff --git a/src/d_net.c b/src/d_net.c
index 5c3f644d47d5cd5c1f2847d51fa4aebde838ecc0..5437af56db0595c405ad5194d133e2d932bd607b 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -818,6 +818,10 @@ static const char *packettypename[NUMPACKETTYPE] =
 	"CLIENTMIS",
 	"CLIENT2CMD",
 	"CLIENT2MIS",
+	"CLIENT3CMD",
+	"CLIENT3MIS",
+	"CLIENT4CMD",
+	"CLIENT4MIS",
 	"NODEKEEPALIVE",
 	"NODEKEEPALIVEMIS",
 	"SERVERTICS",
@@ -837,6 +841,8 @@ static const char *packettypename[NUMPACKETTYPE] =
 	"FILEFRAGMENT",
 	"TEXTCMD",
 	"TEXTCMD2",
+	"TEXTCMD3",
+	"TEXTCMD4",
 	"CLIENTJOIN",
 	"NODETIMEOUT",
 	"RESYNCHING",
diff --git a/src/d_net.h b/src/d_net.h
index cb70b6dfb648e27ab4673a3c092c5ef541053b12..45fd86832c3b995ba96ea729657888b97b9b55b5 100644
--- a/src/d_net.h
+++ b/src/d_net.h
@@ -21,7 +21,7 @@
 // Max computers in a game
 #define MAXNETNODES 16
 #define BROADCASTADDR MAXNETNODES
-#define MAXSPLITSCREENPLAYERS 2 // Max number of players on a single computer
+#define MAXSPLITSCREENPLAYERS 4 // Max number of players on a single computer
 
 #define STATLENGTH (TICRATE*2)
 
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 6a454f46fa907d3b5304d12e41e8e9d87b7cdc3f..2c2e22182b787ec9f932daa07757457d9e88eabc 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -178,8 +178,8 @@ static void Command_Archivetest_f(void);
 
 void SendWeaponPref(void);
 void SendWeaponPref2(void);
-//void SendWeaponPref3(void);
-//void SendWeaponPref4(void);
+void SendWeaponPref3(void);
+void SendWeaponPref4(void);
 
 static CV_PossibleValue_t usemouse_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Force"}, {0, NULL}};
 #if (defined (__unix__) && !defined (MSDOS)) || defined(__APPLE__) || defined (UNIXCOMMON)
@@ -772,16 +772,28 @@ void D_RegisterClientCommands(void)
 	// g_input.c
 	CV_RegisterVar(&cv_sideaxis);
 	CV_RegisterVar(&cv_sideaxis2);
+	CV_RegisterVar(&cv_sideaxis3);
+	CV_RegisterVar(&cv_sideaxis4);
 	CV_RegisterVar(&cv_turnaxis);
 	CV_RegisterVar(&cv_turnaxis2);
+	CV_RegisterVar(&cv_turnaxis3);
+	CV_RegisterVar(&cv_turnaxis4);
 	CV_RegisterVar(&cv_moveaxis);
 	CV_RegisterVar(&cv_moveaxis2);
+	CV_RegisterVar(&cv_moveaxis3);
+	CV_RegisterVar(&cv_moveaxis4);
 	CV_RegisterVar(&cv_lookaxis);
 	CV_RegisterVar(&cv_lookaxis2);
+	CV_RegisterVar(&cv_lookaxis3);
+	CV_RegisterVar(&cv_lookaxis4);
 	CV_RegisterVar(&cv_fireaxis);
 	CV_RegisterVar(&cv_fireaxis2);
+	CV_RegisterVar(&cv_fireaxis3);
+	CV_RegisterVar(&cv_fireaxis4);
 	CV_RegisterVar(&cv_firenaxis);
 	CV_RegisterVar(&cv_firenaxis2);
+	CV_RegisterVar(&cv_firenaxis3);
+	CV_RegisterVar(&cv_firenaxis4);
 
 	// WARNING: the order is important when initialising mouse2
 	// we need the mouse2port
@@ -1689,7 +1701,7 @@ void SendWeaponPref3(void)
 		buf[0] |= 1;
 	if (players[thirddisplayplayer].pflags & PF_ANALOGMODE)
 		buf[0] |= 2;
-	//SendNetXCmd3(XD_WEAPONPREF, buf, 1);
+	SendNetXCmd3(XD_WEAPONPREF, buf, 1);
 }
 
 void SendWeaponPref4(void)
@@ -1701,7 +1713,7 @@ void SendWeaponPref4(void)
 		buf[0] |= 1;
 	if (players[fourthdisplayplayer].pflags & PF_ANALOGMODE)
 		buf[0] |= 2;
-	//SendNetXCmd4(XD_WEAPONPREF, buf, 1);
+	SendNetXCmd4(XD_WEAPONPREF, buf, 1);
 }
 
 static void Got_WeaponPref(UINT8 **cp,INT32 playernum)
diff --git a/src/g_game.c b/src/g_game.c
index 65f801095a122b1ed014bb05ba49532cf4afd28c..7e4a4f4b94c9025cd66228e39ccc418094415edd 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -2109,8 +2109,6 @@ void G_Ticker(boolean run)
 
 		if (playeringame[i])
 		{
-			G_CopyTiccmd(cmd, &netcmds[buf][i], 1);
-
 			// SRB2kart
 			// Save the dir the player is holding
 			//  to allow items to be thrown forward or backward.
@@ -2120,6 +2118,8 @@ void G_Ticker(boolean run)
 					players[i].kartstuff[k_throwdir] = -1;
 			else
 					players[i].kartstuff[k_throwdir] = 0;
+
+			G_CopyTiccmd(cmd, &netcmds[buf][i], 1);
 		}
 	}
 
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index e46080e08b6362f4a8f78618445798741ca420a5..69e2a9dd9027ce1976d6aa318c38356c1da9ec69 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -1802,7 +1802,7 @@ static void HU_DrawRankings(void)
 		HU_DrawDualTabRankings(32, 32, tab, scorelines, whiteplayer);
 
 	// draw spectators in a ticker across the bottom
-	if ((!splitscreen || !splitscreen3 || !splitscreen4) && G_GametypeHasSpectators())
+	if (!(splitscreen || splitscreen3 || splitscreen4) && G_GametypeHasSpectators())
 		HU_DrawSpectatorTicker();
 }
 
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 8de5471869f8082dfc811256123a2cade7e0e559..62bb37464f7c2d516d4a10463f394786227411b7 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -6136,7 +6136,7 @@ void P_RunOverlays(void)
 
 		if (!mo->target)
 			continue;
-		if (!splitscreen /*&& rendermode != render_soft*/)
+		if (!(splitscreen || splitscreen3 || splitscreen4) /*&& rendermode != render_soft*/)
 		{
 			angle_t viewingangle;
 
@@ -6779,8 +6779,7 @@ void P_MobjThinker(mobj_t *mobj)
 					fixed_t scale = mobj->target->scale;
 					mobj->color = mobj->target->color;
 
-					if ((splitscreen || !netgame)
-						|| gametype == GT_RACE
+					if (!netgame || gametype == GT_RACE
 						|| mobj->target->player == &players[displayplayer]
 						|| mobj->target->player->kartstuff[k_balloon] <= 0
 						|| (mobj->target->player->mo->flags2 & MF2_DONTDRAW))
@@ -8096,7 +8095,7 @@ void P_MobjThinker(mobj_t *mobj)
 								CONS_Printf(M_GetText("The %c%s%c has returned to base.\n"), 0x85, M_GetText("Red flag"), 0x80);
 
 							// Assumedly in splitscreen players will be on opposing teams
-							if (players[consoleplayer].ctfteam == 1 || splitscreen)
+							if (players[consoleplayer].ctfteam == 1 || (splitscreen || splitscreen3 || splitscreen4))
 								S_StartSound(NULL, sfx_hoop1);
 
 							redflag = flagmo;
@@ -8107,7 +8106,7 @@ void P_MobjThinker(mobj_t *mobj)
 								CONS_Printf(M_GetText("The %c%s%c has returned to base.\n"), 0x84, M_GetText("Blue flag"), 0x80);
 
 							// Assumedly in splitscreen players will be on opposing teams
-							if (players[consoleplayer].ctfteam == 2 || splitscreen)
+							if (players[consoleplayer].ctfteam == 2 || (splitscreen || splitscreen3 || splitscreen4))
 								S_StartSound(NULL, sfx_hoop1);
 
 							blueflag = flagmo;
diff --git a/src/p_setup.c b/src/p_setup.c
index 6086a346a3d37544cd91b7880efe7ddafd92856f..1c57fd5677ffcb5a4276274c231c545305927cfa 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -2354,10 +2354,20 @@ static void P_ForceCharacter(const char *forcecharskin)
 	if (netgame)
 	{
 		char skincmd[33];
-		if (splitscreen)
+		if (splitscreen || splitscreen3 || splitscreen4)
 		{
 			sprintf(skincmd, "skin2 %s\n", forcecharskin);
 			CV_Set(&cv_skin2, forcecharskin);
+			if (splitscreen3 || splitscreen4)
+			{
+				sprintf(skincmd, "skin3 %s\n", forcecharskin);
+				CV_Set(&cv_skin3, forcecharskin);
+				if (splitscreen4)
+				{
+					sprintf(skincmd, "skin4 %s\n", forcecharskin);
+					CV_Set(&cv_skin4, forcecharskin);
+				}
+			}
 		}
 
 		sprintf(skincmd, "skin %s\n", forcecharskin);
@@ -2920,10 +2930,20 @@ boolean P_SetupLevel(boolean skipprecip)
 		if (!cv_cam2_rotate.changed)
 			CV_Set(&cv_cam2_rotate, cv_cam2_rotate.defaultvalue);
 
+		if (!cv_cam3_rotate.changed)
+			CV_Set(&cv_cam3_rotate, cv_cam3_rotate.defaultvalue);
+
+		if (!cv_cam4_rotate.changed)
+			CV_Set(&cv_cam4_rotate, cv_cam4_rotate.defaultvalue);
+
 		if (!cv_analog.changed)
 			CV_SetValue(&cv_analog, 0);
 		if (!cv_analog2.changed)
 			CV_SetValue(&cv_analog2, 0);
+		if (!cv_analog3.changed)
+			CV_SetValue(&cv_analog3, 0);
+		if (!cv_analog4.changed)
+			CV_SetValue(&cv_analog4, 0);
 
 #ifdef HWRENDER
 		if (rendermode != render_soft && rendermode != render_none)
@@ -2936,13 +2956,21 @@ boolean P_SetupLevel(boolean skipprecip)
 	if (cv_useranalog.value)
 		CV_SetValue(&cv_analog, true);
 
-	if (splitscreen && cv_useranalog2.value)
+	if ((splitscreen || splitscreen3 || splitscreen4) && cv_useranalog2.value)
 		CV_SetValue(&cv_analog2, true);
 	else if (botingame)
 		CV_SetValue(&cv_analog2, true);
 
+	if ((splitscreen3 || splitscreen4) && cv_useranalog3.value)
+		CV_SetValue(&cv_analog3, true);
+
+	if (splitscreen4 && cv_useranalog4.value)
+		CV_SetValue(&cv_analog4, true);
+
 	if (twodlevel)
 	{
+		CV_SetValue(&cv_analog4, false);
+		CV_SetValue(&cv_analog3, false);
 		CV_SetValue(&cv_analog2, false);
 		CV_SetValue(&cv_analog, false);
 	}
@@ -2998,7 +3026,7 @@ boolean P_SetupLevel(boolean skipprecip)
 		savedata.lives = 0;
 	}
 
-	skyVisible = skyVisible1 = skyVisible2 = true; // assume the skybox is visible on level load.
+	skyVisible = skyVisible1 = skyVisible2 = skyVisible3 = skyVisible4 = true; // assume the skybox is visible on level load.
 	if (loadprecip) // uglier hack
 	{ // to make a newly loaded level start on the second frame.
 		INT32 buf = gametic % BACKUPTICS;
diff --git a/src/p_spec.c b/src/p_spec.c
index 693cf2b713092ac955a5ad59fa0bf7eeb6af0b75..feb87b2c816fadee34372b11007e94f8d4a51e4b 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -3898,7 +3898,7 @@ DoneSection2:
 					HU_SetCEchoDuration(5);
 					HU_DoCEcho(va(M_GetText("%s\\captured the blue flag.\\\\\\\\"), player_names[player-players]));
 
-					if (splitscreen || players[consoleplayer].ctfteam == 1)
+					if ((splitscreen || splitscreen3 || splitscreen4) || players[consoleplayer].ctfteam == 1)
 						S_StartSound(NULL, sfx_flgcap);
 					else if (players[consoleplayer].ctfteam == 2)
 						S_StartSound(NULL, sfx_lose);
@@ -3931,7 +3931,7 @@ DoneSection2:
 					HU_SetCEchoDuration(5);
 					HU_DoCEcho(va(M_GetText("%s\\captured the red flag.\\\\\\\\"), player_names[player-players]));
 
-					if (splitscreen || players[consoleplayer].ctfteam == 2)
+					if ((splitscreen || splitscreen3 || splitscreen4) || players[consoleplayer].ctfteam == 2)
 						S_StartSound(NULL, sfx_flgcap);
 					else if (players[consoleplayer].ctfteam == 1)
 						S_StartSound(NULL, sfx_lose);
@@ -4189,7 +4189,7 @@ DoneSection2:
 					if (P_IsLocalPlayer(player))
 					{
 						// SRB2kart 200117
-						if (!splitscreen)
+						if (!(splitscreen || splitscreen3 || splitscreen4))
 						{
 							if (player->kartstuff[k_position] == 1)
 								S_ChangeMusicInternal("karwin", true);
diff --git a/src/p_user.c b/src/p_user.c
index 67a893604468593cae0ec9b138de0ceebd7bde9e..f8649a10d500591d34a87b3452eb385de68f065f 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1663,7 +1663,7 @@ void P_DoPlayerExit(player_t *player)
 
 		if (P_IsLocalPlayer(player) && cv_inttime.value > 0)
 		{
-			if (!splitscreen)
+			if (!(splitscreen || splitscreen3 || splitscreen4))
 			{
 				if (player->kartstuff[k_position] == 1)
 					S_ChangeMusicInternal("karwin", true);
@@ -2388,7 +2388,7 @@ static void P_DoPlayerHeadSigns(player_t *player)
 		{
 			// Spawn a got-flag message over the head of the player that
 			// has it (but not on your own screen if you have the flag).
-			if (splitscreen || player != &players[consoleplayer])
+			if ((splitscreen || splitscreen3 || splitscreen4) || player != &players[consoleplayer])
 			{
 				if (player->gotflag & GF_REDFLAG)
 				{
@@ -8840,7 +8840,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	}
 
 	// Make player translucent if camera is too close (only in single player).
-	/*if (!(multiplayer || netgame) && !splitscreen)
+	/*if (!(multiplayer || netgame) && !(splitscreen || splitscreen3 || splitscreen4))
 	{
 		fixed_t vx = 0, vy = 0;
 		if (player->awayviewtics) {
@@ -9377,7 +9377,7 @@ void P_PlayerThink(player_t *player)
 			player->realtime = 0;
 	}
 
-	if ((netgame || splitscreen) && player->spectator && cmd->buttons & BT_ATTACK && !player->powers[pw_flashing])
+	if ((netgame || (splitscreen || splitscreen3 || splitscreen4)) && player->spectator && cmd->buttons & BT_ATTACK && !player->powers[pw_flashing])
 	{
 		if (P_SpectatorJoinGame(player))
 			return; // player->mo was removed.
@@ -9619,7 +9619,10 @@ void P_PlayerThink(player_t *player)
 	if (!(player->pflags & PF_NIGHTSMODE))
 	{
 		// SRB2kart - fixes boo not flashing when it should. Mega doesn't flash either. Flashing is local.
-		if ((player == &players[displayplayer] || (splitscreen && player == &players[secondarydisplayplayer]))
+		if ((player == &players[displayplayer]
+			|| ((splitscreen || splitscreen3 || splitscreen4) && player == &players[secondarydisplayplayer])
+			|| ((splitscreen3 || splitscreen4) && player == &players[thirddisplayplayer])
+			|| (splitscreen4 && player == &players[fourthdisplayplayer]))
 			&& player->kartstuff[k_bootimer] == 0 && player->kartstuff[k_growshrinktimer] <= 0
 			&& (player->kartstuff[k_comebacktimer] == 0 || (gametype == GT_RACE || player->kartstuff[k_balloon] > 0)))
 		{
diff --git a/src/r_main.c b/src/r_main.c
index 1c9e87447119eb26b11412aca309313ceab4a12d..a4840692d0d714b1e56e01b0b18800f7045e7ae7 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -195,12 +195,12 @@ void SplitScreen_OnChange(void)
 			CL_AddSplitscreenPlayer();
 		else
 			CL_RemoveSplitscreenPlayer(secondarydisplayplayer);
-
+		
 		if (splitscreen3 || splitscreen4)
 			CL_AddSplitscreenPlayer();
 		else
 			CL_RemoveSplitscreenPlayer(thirddisplayplayer);
-
+		
 		if (splitscreen4)
 			CL_AddSplitscreenPlayer();
 		else
diff --git a/src/r_state.h b/src/r_state.h
index 49d0457b262c767621dde508f85f3a481a9feb3d..8436413bbd64e7dec16b52103d1ae1d2cb1ced9f 100644
--- a/src/r_state.h
+++ b/src/r_state.h
@@ -85,7 +85,7 @@ extern side_t *sides;
 extern fixed_t viewx, viewy, viewz;
 extern angle_t viewangle, aimingangle;
 extern boolean viewsky, skyVisible;
-extern boolean skyVisible1, skyVisible2; // saved values of skyVisible for P1 and P2, for splitscreen
+extern boolean skyVisible1, skyVisible2, skyVisible3, skyVisible4; // saved values of skyVisible for P1 and P2, for splitscreen
 extern sector_t *viewsector;
 extern player_t *viewplayer;
 extern UINT8 portalrender;
diff --git a/src/st_stuff.c b/src/st_stuff.c
index cd774f301d40fe5b2ff68adc4f0b3f653072f1f8..c3aee555a3786227095dea6fc74ba2322e902914 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -229,7 +229,7 @@ void ST_doPaletteStuff(void)
 		if (rendermode != render_none)
 		{
 			V_SetPaletteLump(GetPalette()); // Reset the palette
-			if (!splitscreen)
+			if (!(splitscreen || splitscreen3 || splitscreen4))
 				V_SetPalette(palette);
 		}
 	}
@@ -445,7 +445,7 @@ static INT32 SCY(INT32 y)
 	// do not scale to resolution for hardware accelerated
 	// because these modes always scale by default
 	y = SCZ(y); // scale to resolution
-	if (splitscreen)
+	if (splitscreen || splitscreen3 || splitscreen4)
 	{
 		y >>= 1;
 		if (stplyr != &players[displayplayer])
@@ -459,7 +459,7 @@ static INT32 STRINGY(INT32 y)
 	//31/10/99: fixed by Hurdler so it _works_ also in hardware mode
 	// do not scale to resolution for hardware accelerated
 	// because these modes always scale by default
-	if (splitscreen)
+	if (splitscreen || splitscreen3 || splitscreen4)
 	{
 		y >>= 1;
 		if (stplyr != &players[displayplayer])
@@ -472,7 +472,7 @@ static INT32 STRINGY(INT32 y)
 static INT32 SPLITFLAGS(INT32 f)
 {
 	// Pass this V_SNAPTO(TOP|BOTTOM) and it'll trim them to account for splitscreen! -Red
-	if (splitscreen)
+	if (splitscreen || splitscreen3 || splitscreen4)
 	{
 		if (stplyr != &players[displayplayer])
 			f &= ~V_SNAPTOTOP;
@@ -496,7 +496,7 @@ static INT32 SCR(INT32 r)
 	// do not scale to resolution for hardware accelerated
 	// because these modes always scale by default
 	y = FixedMul(r*FRACUNIT, vid.fdupy); // scale to resolution
-	if (splitscreen)
+	if (splitscreen || splitscreen3 || splitscreen4)
 	{
 		y >>= 1;
 		if (stplyr != &players[displayplayer])
@@ -668,7 +668,7 @@ static void ST_drawTime(void)
 		ST_DrawPatchFromHudWS(HUD_TIMECOLON, sbocolon); // Colon
 		ST_DrawPadNumFromHudWS(HUD_SECONDS, seconds, 2); // Seconds
 
-		if (!splitscreen && (cv_timetic.value == 2 || modeattacking)) // there's not enough room for tics in splitscreen, don't even bother trying!
+		if (!(splitscreen || splitscreen3 || splitscreen4) && (cv_timetic.value == 2 || modeattacking)) // there's not enough room for tics in splitscreen, don't even bother trying!
 		{
 			ST_DrawPatchFromHud(HUD_TIMETICCOLON, sboperiod); // Period
 			ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2); // Tics
@@ -700,7 +700,7 @@ static inline void ST_drawRings(void)
 /*
 static void ST_drawLives(void) // SRB2kart - unused.
 {
-	const INT32 v_splitflag = (splitscreen && stplyr == &players[displayplayer] ? V_SPLITSCREEN : 0);
+	const INT32 v_splitflag = ((splitscreen || splitscreen3 || splitscreen4) && stplyr == &players[displayplayer] ? V_SPLITSCREEN : 0);
 
 	if (!stplyr->skincolor)
 		return; // Just joined a server, skin isn't loaded yet!
@@ -838,7 +838,7 @@ static void ST_drawFirstPersonHUD(void)
 
 	if (p)
 	{
-		if (splitscreen)
+		if (splitscreen || splitscreen3 || splitscreen4)
 			V_DrawSmallScaledPatch(312, STRINGY(24), V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, p);
 		else
 			V_DrawScaledPatch(304, 24, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, p);
@@ -848,7 +848,7 @@ static void ST_drawFirstPersonHUD(void)
 	invulntime = player->powers[pw_flashing] ? 1 : player->powers[pw_invulnerability];
 	if (invulntime > 3*TICRATE || (invulntime && leveltime & 1))
 	{
-		if (splitscreen)
+		if (splitscreen || splitscreen3 || splitscreen4)
 			V_DrawSmallScaledPatch(312, STRINGY(24) + 14, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, invincibility);
 		else
 			V_DrawScaledPatch(304, 24 + 28, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, invincibility);
@@ -856,7 +856,7 @@ static void ST_drawFirstPersonHUD(void)
 
 	if (player->powers[pw_sneakers] > 3*TICRATE || (player->powers[pw_sneakers] && leveltime & 1))
 	{
-		if (splitscreen)
+		if (splitscreen || splitscreen3 || splitscreen4)
 			V_DrawSmallScaledPatch(312, STRINGY(24) + 28, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, sneakers);
 		else
 			V_DrawScaledPatch(304, 24 + 56, V_SNAPTORIGHT|V_SNAPTOTOP|V_HUDTRANS, sneakers);
@@ -1620,11 +1620,11 @@ static void ST_drawCTFHUD(void) // SRB2kart - unused.
 static inline void ST_drawTeamName(void)
 {
 	if (stplyr->ctfteam == 1)
-		V_DrawString(256, (splitscreen) ? STRINGY(184) : STRINGY(192), V_HUDTRANSHALF, "RED TEAM");
+		V_DrawString(256, (splitscreen || splitscreen3 || splitscreen4) ? STRINGY(184) : STRINGY(192), V_HUDTRANSHALF, "RED TEAM");
 	else if (stplyr->ctfteam == 2)
-		V_DrawString(248, (splitscreen) ? STRINGY(184) : STRINGY(192), V_HUDTRANSHALF, "BLUE TEAM");
+		V_DrawString(248, (splitscreen || splitscreen3 || splitscreen4) ? STRINGY(184) : STRINGY(192), V_HUDTRANSHALF, "BLUE TEAM");
 	else
-		V_DrawString(244, (splitscreen) ? STRINGY(184) : STRINGY(192), V_HUDTRANSHALF, "SPECTATOR");
+		V_DrawString(244, (splitscreen || splitscreen3 || splitscreen4) ? STRINGY(184) : STRINGY(192), V_HUDTRANSHALF, "SPECTATOR");
 }
 
 /*
@@ -1914,7 +1914,7 @@ static void ST_overlayDrawer(void)
 	)
 		ST_drawLevelTitle();
 
-	if (!hu_showscores && !splitscreen && netgame && displayplayer == consoleplayer)
+	if (!hu_showscores && !(splitscreen || splitscreen3 || splitscreen4) && netgame && displayplayer == consoleplayer)
 	{
 		if (G_GametypeUsesLives() && stplyr->lives <= 0 && countdown != 1)
 			V_DrawCenteredString(BASEVIDWIDTH/2, STRINGY(132), 0, M_GetText("Press F12 to watch another player."));
diff --git a/src/v_video.c b/src/v_video.c
index bda2f5e8869bd033ece9c2284abdb1aa462a6cc2..a5d5f86b973961422fbf36f8ef90e71920e08d1d 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -1977,7 +1977,7 @@ void V_DoPostProcessor(INT32 view, postimg_t type, INT32 param)
 		return;
 #endif
 
-	if (view < 0 || view >= 2 || (view == 1 && !splitscreen))
+	if (view < 0 || view >= 2 || (view == 1 && !(splitscreen || splitscreen3 || splitscreen4)))
 		return;
 
 	if (splitscreen)