From 1d6215030eb02a611a0f20aff44055264cf41240 Mon Sep 17 00:00:00 2001
From: TehRealSalt <tehrealsalt@gmail.com>
Date: Sun, 30 Sep 2018 00:51:03 -0400
Subject: [PATCH] Online splitscreen

It WORKS, including kicking players in splitscreen
---
 src/d_clisrv.c | 112 ++++++++++++++----------------------------------
 src/d_netcmd.c | 114 +++++++++++++++++++++++++++++++++++++++++++++----
 src/r_main.c   |  10 -----
 3 files changed, 136 insertions(+), 100 deletions(-)

diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 63393690..3c934e3b 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -2419,14 +2419,15 @@ static void CL_RemovePlayer(INT32 playernum)
 	if (server && !demoplayback)
 	{
 		INT32 node = playernode[playernum];
+		//playerpernode[node] = 0; // It'd be better to remove them all at once, but ghosting happened, so continue to let CL_RemovePlayer do it one-by-one
 		playerpernode[node]--;
 		if (playerpernode[node] <= 0)
 		{
 			// If a resynch was in progress, well, it no longer needs to be.
 			SV_InitResynchVars(playernode[playernum]);
 
-			nodeingame[playernode[playernum]] = false;
-			Net_CloseConnection(playernode[playernum]);
+			nodeingame[node] = false;
+			Net_CloseConnection(node);
 			ResetNode(node);
 		}
 	}
@@ -2761,11 +2762,7 @@ 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)))
+	if (playernum != serverplayer && !IsPlayerAdmin(playernum))
 	{
 		// We received a kick command from someone who isn't the
 		// server or admin, and who isn't in splitscreen removing
@@ -2777,12 +2774,6 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 		// "consistency failure" and kicking the offending user
 		// instead.
 
-		// Note: Splitscreen in netgames is broken because of
-		// this. Only the server has any idea of which players
-		// are using splitscreen on the same computer, so
-		// clients cannot always determine if a kick is
-		// legitimate.
-
 		CONS_Alert(CONS_WARNING, M_GetText("Illegal kick command received from %s for player %d\n"), player_names[playernum], pnum);
 
 		// In debug, print a longer message with more details.
@@ -2892,7 +2883,7 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 			break;
 	}
 
-	if (pnum == consoleplayer)
+	if (playernode[pnum] == playernode[consoleplayer])
 	{
 #ifdef DUMPCONSISTENCY
 		if (msg == KICK_MSG_CON_FAIL) SV_SavedGame();
@@ -2916,7 +2907,18 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 			M_StartMessage(M_GetText("You have been kicked by the server\n\nPress ESC\n"), NULL, MM_NOTHING);
 	}
 	else
-		CL_RemovePlayer(pnum);
+	{
+		UINT8 splitnode = playernode[pnum];
+		// Can't tell which player pnum is on the node from a glance, so we have to convert to node, then check each player on the node
+		if (nodetoplayer[splitnode] != -1)
+			CL_RemovePlayer(nodetoplayer[splitnode]);
+		if (nodetoplayer2[splitnode] != -1)
+			CL_RemovePlayer(nodetoplayer2[splitnode]);
+		if (nodetoplayer3[splitnode] != -1)
+			CL_RemovePlayer(nodetoplayer3[splitnode]);
+		if (nodetoplayer4[splitnode] != -1)
+			CL_RemovePlayer(nodetoplayer4[splitnode]);
+	}
 }
 
 consvar_t cv_allownewplayer = {"allowjoin", "On", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL	};
@@ -3156,7 +3158,7 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 
 	// Clear player before joining, lest some things get set incorrectly
 	// HACK: don't do this for splitscreen, it relies on preset values
-	if (!splitscreen && !botingame)
+	//if (!splitscreen && !botingame)
 		CL_ClearPlayer(newplayernum);
 	playeringame[newplayernum] = true;
 	G_AddPlayer(newplayernum);
@@ -3256,69 +3258,17 @@ static boolean SV_AddWaitingPlayers(void)
 		{
 			newplayer = true;
 
-			if (netgame)
-				// !!!!!!!!! EXTREMELY SUPER MEGA GIGA ULTRA ULTIMATELY TERRIBLY IMPORTANT !!!!!!!!!
-				//
-				// The line just after that comment is an awful, horrible, terrible, TERRIBLE hack.
-				//
-				// Basically, the fix I did in order to fix the download freezes happens
-				// to cause situations in which a player number does not match
-				// the node number associated to that player.
-				// That is totally normal, there is absolutely *nothing* wrong with that.
-				// Really. Player 7 being tied to node 29, for instance, is totally fine.
-				//
-				// HOWEVER. A few (broken) parts of the netcode do the TERRIBLE mistake
-				// of mixing up the concepts of node and player, resulting in
-				// incorrect handling of cases where a player is tied to a node that has
-				// a different number (which is a totally normal case, or at least should be).
-				// This incorrect handling can go as far as literally
-				// anyone from joining your server at all, forever.
-				//
-				// Given those two facts, there are two options available
-				// in order to let this download freeze fix be:
-				//  1) Fix the broken parts that assume a node is a player or similar bullshit.
-				//  2) Change the part this comment is located at, so that any player who joins
-				//     is given the same number as their associated node.
-				//
-				// No need to say, 1) is by far the obvious best, whereas 2) is a terrible hack.
-				// Unfortunately, after trying 1), I most likely didn't manage to find all
-				// of those broken parts, and thus 2) has become the only safe option that remains.
-				//
-				// So I did this hack.
-				//
-				// If it isn't clear enough, in order to get rid of this ugly hack,
-				// you will have to fix all parts of the netcode that
-				// make a confusion between nodes and players.
-				//
-				// And if it STILL isn't clear enough, a node and a player
-				// is NOT the same thing. Never. NEVER. *NEVER*.
-				//
-				// And if someday you make the terrible mistake of
-				// daring to have the unforgivable idea to try thinking
-				// that a node might possibly be the same as a player,
-				// or that a player should have the same number as its node,
-				// be sure that I will somehow know about it and
-				// hunt you down tirelessly and make you regret it,
-				// even if you live on the other side of the world.
-				//
-				// TODO:            vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
-				// \todo >>>>>>>>>> Remove this horrible hack as soon as possible <<<<<<<<<<
-				// TODO:            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-				//
-				// !!!!!!!!! EXTREMELY SUPER MEGA GIGA ULTRA ULTIMATELY TERRIBLY IMPORTANT !!!!!!!!!
-				newplayernum = node; // OMFG SAY WELCOME TO TEH NEW HACK FOR FIX FIL DOWNLOAD!!1!
-			else // Don't use the hack if we don't have to
-				// search for a free playernum
-				// we can't use playeringame since it is not updated here
-				for (; newplayernum < MAXPLAYERS; newplayernum++)
-				{
-					for (n = 0; n < MAXNETNODES; n++)
-						if (nodetoplayer[n] == newplayernum || nodetoplayer2[n] == newplayernum
-							|| nodetoplayer3[n] == newplayernum || nodetoplayer4[n] == newplayernum)
-							break;
-					if (n == MAXNETNODES)
+			// search for a free playernum
+			// we can't use playeringame since it is not updated here
+			for (; newplayernum < MAXPLAYERS; newplayernum++)
+			{
+				for (n = 0; n < MAXNETNODES; n++)
+					if (nodetoplayer[n] == newplayernum || nodetoplayer2[n] == newplayernum
+						|| nodetoplayer3[n] == newplayernum || nodetoplayer4[n] == newplayernum)
 						break;
-				}
+				if (n == MAXNETNODES)
+					break;
+			}
 
 			// should never happen since we check the playernum
 			// before accepting the join
@@ -3493,7 +3443,7 @@ static void HandleConnect(SINT8 node)
 		SV_SendRefuse(node, M_GetText("The server is not accepting\njoins for the moment"));
 	else if (D_NumPlayers() >= cv_maxplayers.value)
 		SV_SendRefuse(node, va(M_GetText("Maximum players reached: %d"), cv_maxplayers.value));
-	else if (netgame && netbuffer->u.clientcfg.localplayers > 1) // Hacked client?
+	else if (netgame && netbuffer->u.clientcfg.localplayers > 4) // Hacked client?
 		SV_SendRefuse(node, M_GetText("Too many players from\nthis node."));
 	else if (netgame && !netbuffer->u.clientcfg.localplayers) // Stealth join?
 		SV_SendRefuse(node, M_GetText("No players from\nthis node."));
@@ -3979,10 +3929,10 @@ FILESTAMP
 				--resynch_score[node];
 			break;
 		case PT_TEXTCMD:
-		case PT_TEXTCMD2: // splitscreen special
+		case PT_TEXTCMD2:
 		case PT_TEXTCMD3:
 		case PT_TEXTCMD4:
-			if (netbuffer->packettype == PT_TEXTCMD2)
+			if (netbuffer->packettype == PT_TEXTCMD2) // splitscreen special
 				netconsole = nodetoplayer2[node];
 			else if (netbuffer->packettype == PT_TEXTCMD3)
 				netconsole = nodetoplayer3[node];
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 144e4bf9..ea913b06 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1350,16 +1350,23 @@ static void SendNameAndColor(void)
 // splitscreen
 static void SendNameAndColor2(void)
 {
-	INT32 secondplaya;
+	INT32 secondplaya = -1;
+	XBOXSTATIC char buf[MAXPLAYERNAME+2];
+	char *p;
 
 	if (splitscreen < 1 && !botingame)
 		return; // can happen if skin2/color2/name2 changed
 
 	if (secondarydisplayplayer != consoleplayer)
 		secondplaya = secondarydisplayplayer;
-	else // HACK
+	else if (!netgame) // HACK
 		secondplaya = 1;
 
+	if (secondplaya == -1)
+		return;
+
+	p = buf;
+
 	// normal player colors
 	if (G_GametypeHasTeams())
 	{
@@ -1436,21 +1443,53 @@ static void SendNameAndColor2(void)
 		return;
 	}
 
-	// Don't actually send anything because splitscreen isn't actually allowed in netgames anyway!
+	snac2pending++;
+
+	// Don't change name if muted
+	if (cv_mute.value && !(server || IsPlayerAdmin(secondarydisplayplayer)))
+		CV_StealthSet(&cv_playername2, player_names[secondarydisplayplayer]);
+	else // Cleanup name if changing it
+		CleanupPlayerName(secondarydisplayplayer, cv_playername2.zstring);
+
+	// Don't change skin if the server doesn't want you to.
+	if (!CanChangeSkin(secondarydisplayplayer))
+		CV_StealthSet(&cv_skin2, skins[players[secondarydisplayplayer].skin].name);
+
+	// check if player has the skin loaded (cv_skin2 may have
+	// the name of a skin that was available in the previous game)
+	cv_skin2.value = R_SkinAvailable(cv_skin2.string);
+	if (cv_skin2.value < 0)
+	{
+		CV_StealthSet(&cv_skin2, DEFAULTSKIN);
+		cv_skin2.value = 0;
+	}
+
+	// Finally write out the complete packet and send it off.
+	WRITESTRINGN(p, cv_playername2.zstring, MAXPLAYERNAME);
+	WRITEUINT8(p, (UINT8)cv_playercolor2.value);
+	WRITEUINT8(p, (UINT8)cv_skin2.value);
+	SendNetXCmd2(XD_NAMEANDCOLOR, buf, p - buf);
 }
 
 static void SendNameAndColor3(void)
 {
-	INT32 thirdplaya;
+	INT32 thirdplaya = -1;
+	XBOXSTATIC char buf[MAXPLAYERNAME+2];
+	char *p;
 
 	if (splitscreen < 2)
 		return; // can happen if skin3/color3/name3 changed
 
 	if (thirddisplayplayer != consoleplayer)
 		thirdplaya = thirddisplayplayer;
-	else // HACK
+	else if (!netgame) // HACK
 		thirdplaya = 2;
 
+	if (thirdplaya == -1)
+		return;
+
+	p = buf;
+
 	// normal player colors
 	if (G_GametypeHasTeams())
 	{
@@ -1519,21 +1558,53 @@ static void SendNameAndColor3(void)
 		return;
 	}
 
-	// Don't actually send anything because splitscreen isn't actually allowed in netgames anyway!
+	snac3pending++;
+
+	// Don't change name if muted
+	if (cv_mute.value && !(server || IsPlayerAdmin(thirddisplayplayer)))
+		CV_StealthSet(&cv_playername3, player_names[thirddisplayplayer]);
+	else // Cleanup name if changing it
+		CleanupPlayerName(thirddisplayplayer, cv_playername3.zstring);
+
+	// Don't change skin if the server doesn't want you to.
+	if (!CanChangeSkin(thirddisplayplayer))
+		CV_StealthSet(&cv_skin3, skins[players[thirddisplayplayer].skin].name);
+
+	// check if player has the skin loaded (cv_skin3 may have
+	// the name of a skin that was available in the previous game)
+	cv_skin3.value = R_SkinAvailable(cv_skin3.string);
+	if (cv_skin3.value < 0)
+	{
+		CV_StealthSet(&cv_skin3, DEFAULTSKIN);
+		cv_skin3.value = 0;
+	}
+
+	// Finally write out the complete packet and send it off.
+	WRITESTRINGN(p, cv_playername3.zstring, MAXPLAYERNAME);
+	WRITEUINT8(p, (UINT8)cv_playercolor3.value);
+	WRITEUINT8(p, (UINT8)cv_skin3.value);
+	SendNetXCmd3(XD_NAMEANDCOLOR, buf, p - buf);
 }
 
 static void SendNameAndColor4(void)
 {
-	INT32 fourthplaya;
+	INT32 fourthplaya = -1;
+	XBOXSTATIC char buf[MAXPLAYERNAME+2];
+	char *p;
 
 	if (splitscreen < 3)
 		return; // can happen if skin4/color4/name4 changed
 
 	if (fourthdisplayplayer != consoleplayer)
 		fourthplaya = fourthdisplayplayer;
-	else // HACK
+	else if (!netgame) // HACK
 		fourthplaya = 3;
 
+	if (fourthplaya == -1)
+		return;
+
+	p = buf;
+
 	// normal player colors
 	if (G_GametypeHasTeams())
 	{
@@ -1610,7 +1681,32 @@ static void SendNameAndColor4(void)
 		return;
 	}
 
-	// Don't actually send anything because splitscreen isn't actually allowed in netgames anyway!
+	snac4pending++;
+
+	// Don't change name if muted
+	if (cv_mute.value && !(server || IsPlayerAdmin(fourthdisplayplayer)))
+		CV_StealthSet(&cv_playername4, player_names[fourthdisplayplayer]);
+	else // Cleanup name if changing it
+		CleanupPlayerName(fourthdisplayplayer, cv_playername4.zstring);
+
+	// Don't change skin if the server doesn't want you to.
+	if (!CanChangeSkin(fourthdisplayplayer))
+		CV_StealthSet(&cv_skin4, skins[players[fourthdisplayplayer].skin].name);
+
+	// check if player has the skin loaded (cv_skin4 may have
+	// the name of a skin that was available in the previous game)
+	cv_skin4.value = R_SkinAvailable(cv_skin4.string);
+	if (cv_skin4.value < 0)
+	{
+		CV_StealthSet(&cv_skin4, DEFAULTSKIN);
+		cv_skin4.value = 0;
+	}
+
+	// Finally write out the complete packet and send it off.
+	WRITESTRINGN(p, cv_playername4.zstring, MAXPLAYERNAME);
+	WRITEUINT8(p, (UINT8)cv_playercolor4.value);
+	WRITEUINT8(p, (UINT8)cv_skin4.value);
+	SendNetXCmd4(XD_NAMEANDCOLOR, buf, p - buf);
 }
 
 static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
diff --git a/src/r_main.c b/src/r_main.c
index 5990224c..11213a27 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -178,16 +178,6 @@ void SplitScreen_OnChange(void)
 {
 	UINT8 i;
 
-	if (!cv_debug && netgame)
-	{
-		if (splitscreen)
-		{
-			CONS_Alert(CONS_NOTICE, M_GetText("Splitscreen not supported in netplay, sorry!\n"));
-			splitscreen = 0;
-		}
-		return;
-	}
-
 	// recompute screen size
 	R_ExecuteSetViewSize();