diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 7e9dcf8efae6d150a74f35949ea59e0195b5982a..ad82e7a00e9604993de4119433997df6003b22a7 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -72,14 +72,21 @@
 #define MAX_REASONLENGTH 30
 
 boolean server = true; // true or false but !server == client
+#define client (!server)
 boolean nodownload = false;
 static boolean serverrunning = false;
 INT32 serverplayer = 0;
 char motd[254], server_context[8]; // Message of the Day, Unique Context (even without Mumble support)
 
-// server specific vars
+// Server specific vars
 UINT8 playernode[MAXPLAYERS];
 
+// Minimum timeout for sending the savegame
+// The actual timeout will be longer depending on the savegame length
+tic_t jointimeout = (10*TICRATE);
+static boolean sendingsavegame[MAXNETNODES]; // Are we sending the savegame?
+static tic_t freezetimeout[MAXNETNODES]; // Until when can this node freeze the server before getting a timeout?
+
 #ifdef NEWPING
 UINT16 pingmeasurecount = 1;
 UINT32 realpingtable[MAXPLAYERS]; //the base table of ping where an average will be sent to everyone.
@@ -108,7 +115,7 @@ static UINT8 resynch_local_inprogress = false; // WE are desynched and getting p
 static UINT8 player_joining = false;
 UINT8 hu_resynching = 0;
 
-// client specific
+// Client specific
 static ticcmd_t localcmds;
 static ticcmd_t localcmds2;
 static boolean cl_packetmissed;
@@ -404,7 +411,7 @@ static void ExtraDataTicker(void)
 	// If you are a client, you can safely forget the net commands for this tic
 	// If you are the server, you need to remember them until every client has been aknowledged,
 	// because if you need to resend a PT_SERVERTICS packet, you need to put the commands in it
-	if (!server)
+	if (client)
 		D_FreeTextcmd(gametic);
 }
 
@@ -1038,6 +1045,9 @@ static void SV_AcknowledgeResynchAck(INT32 node, UINT8 rsg)
 		resynch_status[node] &= ~(1<<rsg);
 		--resynch_score[node]; // unpenalize
 	}
+
+	// Don't let resynch cause a timeout
+	freezetimeout[node] = I_GetTime() + connectiontimeout;
 }
 // -----------------------------------------------------------------
 // end resynch
@@ -1131,12 +1141,17 @@ static inline void CL_DrawConnectionStatus(void)
 		{
 #ifdef JOININGAME
 			case CL_DOWNLOADSAVEGAME:
-				cltext = M_GetText("Downloading game state...");
-				Net_GetNetStat();
-				V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
-					va(" %4uK",fileneeded[lastfilenum].currentsize>>10));
-				V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
-					va("%3.1fK/s ", ((double)getbps)/1024));
+				if (lastfilenum != -1)
+				{
+					cltext = M_GetText("Downloading game state...");
+					Net_GetNetStat();
+					V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
+						va(" %4uK",fileneeded[lastfilenum].currentsize>>10));
+					V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
+						va("%3.1fK/s ", ((double)getbps)/1024));
+				}
+				else
+					cltext = M_GetText("Waiting to download game state...");
 				break;
 #endif
 			case CL_ASKJOIN:
@@ -1151,25 +1166,31 @@ static inline void CL_DrawConnectionStatus(void)
 	}
 	else
 	{
-		INT32 dldlength;
-		static char tempname[32];
-
-		Net_GetNetStat();
-		dldlength = (INT32)((fileneeded[lastfilenum].currentsize/(double)fileneeded[lastfilenum].totalsize) * 256);
-		if (dldlength > 256)
-			dldlength = 256;
-		V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, 256, 8, 175);
-		V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, dldlength, 8, 160);
-
-		memset(tempname, 0, sizeof(tempname));
-		nameonly(strncpy(tempname, fileneeded[lastfilenum].filename, 31));
-
-		V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-32, V_YELLOWMAP,
-			va(M_GetText("Downloading \"%s\""), tempname));
-		V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
-			va(" %4uK/%4uK",fileneeded[lastfilenum].currentsize>>10,fileneeded[lastfilenum].totalsize>>10));
-		V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
-			va("%3.1fK/s ", ((double)getbps)/1024));
+		if (lastfilenum != -1)
+		{
+			INT32 dldlength;
+			static char tempname[32];
+
+			Net_GetNetStat();
+			dldlength = (INT32)((fileneeded[lastfilenum].currentsize/(double)fileneeded[lastfilenum].totalsize) * 256);
+			if (dldlength > 256)
+				dldlength = 256;
+			V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, 256, 8, 175);
+			V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, dldlength, 8, 160);
+
+			memset(tempname, 0, sizeof(tempname));
+			nameonly(strncpy(tempname, fileneeded[lastfilenum].filename, 31));
+
+			V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-32, V_YELLOWMAP,
+				va(M_GetText("Downloading \"%s\""), tempname));
+			V_DrawString(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
+				va(" %4uK/%4uK",fileneeded[lastfilenum].currentsize>>10,fileneeded[lastfilenum].totalsize>>10));
+			V_DrawRightAlignedString(BASEVIDWIDTH/2+128, BASEVIDHEIGHT-24, V_20TRANS|V_MONOSPACE,
+				va("%3.1fK/s ", ((double)getbps)/1024));
+		}
+		else
+			V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-32, V_YELLOWMAP,
+				M_GetText("Waiting to download files..."));
 	}
 }
 #endif
@@ -1458,6 +1479,10 @@ static void SV_SendSaveGame(INT32 node)
 
 	SV_SendRam(node, buffertosend, length, SF_RAM, 0);
 	save_p = NULL;
+
+	// Remember when we started sending the savegame so we can handle timeouts
+	sendingsavegame[node] = true;
+	freezetimeout[node] = I_GetTime() + jointimeout + length / 1024; // 1 extra tic for each kilobyte
 }
 
 #ifdef DUMPCONSISTENCY
@@ -1750,7 +1775,7 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent)
 			return false;
 		}
 
-		if (!server)
+		if (client)
 		{
 			D_ParseFileneeded(serverlist[i].info.fileneedednum,
 				serverlist[i].info.fileneeded);
@@ -1924,7 +1949,7 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic
 		*oldtic = I_GetTime();
 
 #ifdef CLIENT_LOADINGSCREEN
-		if (!server && cl_mode != CL_CONNECTED && cl_mode != CL_ABORTED)
+		if (client && cl_mode != CL_CONNECTED && cl_mode != CL_ABORTED)
 		{
 			F_TitleScreenTicker(true);
 			F_TitleScreenDrawer();
@@ -1966,7 +1991,7 @@ static void CL_ConnectToServer(boolean viams)
 	cl_mode = CL_SEARCHING;
 
 #ifdef CLIENT_LOADINGSCREEN
-	lastfilenum = 0;
+	lastfilenum = -1;
 #endif
 
 #ifdef JOININGAME
@@ -2037,7 +2062,7 @@ static void CL_ConnectToServer(boolean viams)
 					pnumnodes++;
 		}
 	}
-	while (!(cl_mode == CL_CONNECTED && (!server || (server && nodewaited <= pnumnodes))));
+	while (!(cl_mode == CL_CONNECTED && (client || (server && nodewaited <= pnumnodes))));
 
 	DEBFILE(va("Synchronisation Finished\n"));
 
@@ -2574,6 +2599,14 @@ static void Command_Kick(void)
 		WRITESINT8(p, pn);
 		if (pn == -1 || pn == 0)
 			return;
+		// Special case if we are trying to kick a player who is downloading the game state:
+		// trigger a timeout instead of kicking them, because a kick would only
+		// take effect after they have finished downloading
+		if (sendingsavegame[playernode[pn]])
+		{
+			Net_ConnectionTimeout(playernode[pn]);
+			return;
+		}
 		if (COM_Argc() == 2)
 		{
 			WRITEUINT8(p, KICK_MSG_GO_AWAY);
@@ -2786,7 +2819,12 @@ consvar_t cv_blamecfail = {"blamecfail", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL
 
 // max file size to send to a player (in kilobytes)
 static CV_PossibleValue_t maxsend_cons_t[] = {{0, "MIN"}, {51200, "MAX"}, {0, NULL}};
-consvar_t cv_maxsend = {"maxsend", "1024", CV_SAVE, maxsend_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_maxsend = {"maxsend", "4096", CV_SAVE, maxsend_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_noticedownload = {"noticedownload", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+
+// Speed of file downloading (in packets per tic)
+static CV_PossibleValue_t downloadspeed_cons_t[] = {{0, "MIN"}, {32, "MAX"}, {0, NULL}};
+consvar_t cv_downloadspeed = {"downloadspeed", "16", CV_SAVE, downloadspeed_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 static void Got_AddPlayer(UINT8 **p, INT32 playernum);
 
@@ -2809,6 +2847,9 @@ void D_ClientServerInit(void)
 	COM_AddCommand("drop", Command_Drop);
 	COM_AddCommand("droprate", Command_Droprate);
 #endif
+#ifdef _DEBUG
+	COM_AddCommand("numnodes", Command_Numnodes);
+#endif
 #endif
 
 	RegisterNetXCmd(XD_KICK, Got_KickCmd);
@@ -2844,6 +2885,7 @@ static void ResetNode(INT32 node)
 	supposedtics[node] = gametic;
 	nodewaiting[node] = 0;
 	playerpernode[node] = 0;
+	sendingsavegame[node] = false;
 }
 
 void SV_ResetServer(void)
@@ -3136,7 +3178,7 @@ void CL_RemoveSplitscreenPlayer(void)
 // is there a game running
 boolean Playing(void)
 {
-	return (server && serverrunning) || (!server && cl_mode == CL_CONNECTED);
+	return (server && serverrunning) || (client && cl_mode == CL_CONNECTED);
 }
 
 boolean SV_SpawnServer(void)
@@ -3399,12 +3441,19 @@ static void HandlePacketFromAwayNode(SINT8 node)
 			}
 			if (cl_mode == CL_WAITJOINRESPONSE)
 			{
+				// Save the reason so it can be displayed after quitting the netgame
+				char *reason = strdup(netbuffer->u.serverrefuse.reason);
+				if (!reason)
+					I_Error("Out of memory!\n");
+
 				D_QuitNetGame();
 				CL_Reset();
 				D_StartTitle();
 
 				M_StartMessage(va(M_GetText("Server refuses connection\n\nReason:\n%s"),
-					netbuffer->u.serverrefuse.reason), NULL, MM_NOTHING);
+					reason), NULL, MM_NOTHING);
+
+				free(reason);
 
 				// Will be reset by caller. Signals refusal.
 				cl_mode = CL_ABORTED;
@@ -3425,7 +3474,7 @@ static void HandlePacketFromAwayNode(SINT8 node)
 			if (cl_mode != CL_WAITJOINRESPONSE)
 				break;
 
-			if (!server)
+			if (client)
 			{
 				maketic = gametic = neededtic = (tic_t)LONG(netbuffer->u.servercfg.gametic);
 				gametype = netbuffer->u.servercfg.gametype;
@@ -3553,7 +3602,7 @@ FILESTAMP
 		case PT_CLIENT2MIS:
 		case PT_NODEKEEPALIVE:
 		case PT_NODEKEEPALIVEMIS:
-			if (!server)
+			if (client)
 				break;
 
 			// Ignore tics from those not synched
@@ -3586,6 +3635,13 @@ FILESTAMP
 				|| netbuffer->packettype == PT_NODEKEEPALIVEMIS)
 				break;
 
+			// If a client sends a ticcmd it should mean they are done receiving the savegame
+			sendingsavegame[node] = false;
+
+			// As long as clients send valid ticcmds, the server can keep running, so reset the timeout
+			/// \todo Use a separate cvar for that kind of timeout?
+			freezetimeout[node] = I_GetTime() + connectiontimeout;
+
 			// Copy ticcmd
 			G_MoveTiccmd(&netcmds[maketic%BACKUPTICS][netconsole], &netbuffer->u.clientpak.cmd, 1);
 
@@ -3652,7 +3708,7 @@ FILESTAMP
 		case PT_TEXTCMD2: // splitscreen special
 			netconsole = nodetoplayer2[node];
 		case PT_TEXTCMD:
-			if (!server)
+			if (client)
 				break;
 
 			if (netconsole < 0 || netconsole >= MAXPLAYERS)
@@ -3696,7 +3752,7 @@ FILESTAMP
 			break;
 		case PT_NODETIMEOUT:
 		case PT_CLIENTQUIT:
-			if (!server)
+			if (client)
 				break;
 
 			// nodeingame will be put false in the execution of kick command
@@ -3728,7 +3784,7 @@ FILESTAMP
 			// Only accept PT_RESYNCHEND from the server.
 			if (node != servernode)
 			{
-				CONS_Alert(CONS_WARNING, M_GetText("%s recieved from non-host %d\n"), "PT_RESYNCHEND", node);
+				CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_RESYNCHEND", node);
 
 				if (server)
 				{
@@ -3806,13 +3862,20 @@ FILESTAMP
 				neededtic = realend;
 			}
 			else
+			{
 				DEBFILE(va("frame not in bound: %u\n", neededtic));
+				/*if (realend < neededtic - 2 * TICRATE || neededtic + 2 * TICRATE < realstart)
+					I_Error("Received an out of order PT_SERVERTICS packet!\n"
+							"Got tics %d-%d, needed tic %d\n\n"
+							"Please report this crash on the Master Board,\n"
+							"IRC or Discord so it can be fixed.\n", (INT32)realstart, (INT32)realend, (INT32)neededtic);*/
+			}
 			break;
 		case PT_RESYNCHING:
 			// Only accept PT_RESYNCHING from the server.
 			if (node != servernode)
 			{
-				CONS_Alert(CONS_WARNING, M_GetText("%s recieved from non-host %d\n"), "PT_RESYNCHING", node);
+				CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_RESYNCHING", node);
 
 				if (server)
 				{
@@ -3832,7 +3895,7 @@ FILESTAMP
 			// Only accept PT_PING from the server.
 			if (node != servernode)
 			{
-				CONS_Alert(CONS_WARNING, M_GetText("%s recieved from non-host %d\n"), "PT_PING", node);
+				CONS_Alert(CONS_WARNING, M_GetText("%s received from non-host %d\n"), "PT_PING", node);
 
 				if (server)
 				{
@@ -3846,7 +3909,7 @@ FILESTAMP
 			}
 
 			//Update client ping table from the server.
-			if (!server)
+			if (client)
 			{
 				INT32 i;
 				for (i = 0; i < MAXNETNODES; i++)
@@ -3859,7 +3922,7 @@ FILESTAMP
 		case PT_SERVERCFG:
 			break;
 		case PT_FILEFRAGMENT:
-			if (!server)
+			if (client)
 				Got_Filetxpak();
 			break;
 		default:
@@ -3889,17 +3952,18 @@ FILESTAMP
 			HandleConnect(node);
 			continue;
 		}
-		if (netbuffer->packettype == PT_SERVERSHUTDOWN && node == servernode
-			&& !server && cl_mode != CL_SEARCHING)
-		{
-			HandleShutdown(node);
-			continue;
-		}
-		if (netbuffer->packettype == PT_NODETIMEOUT && node == servernode
-			&& !server && cl_mode != CL_SEARCHING)
+		if (node == servernode && client && cl_mode != CL_SEARCHING)
 		{
-			HandleTimeout(node);
-			continue;
+			if (netbuffer->packettype == PT_SERVERSHUTDOWN)
+			{
+				HandleShutdown(node);
+				continue;
+			}
+			if (netbuffer->packettype == PT_NODETIMEOUT)
+			{
+				HandleTimeout(node);
+				continue;
+			}
 		}
 
 #ifndef NONET
@@ -3916,7 +3980,7 @@ FILESTAMP
 		// Packet received from someone already playing
 		if (nodeingame[node])
 			HandlePacketFromPlayer(node);
-		// Packet received from someone trying to join
+		// Packet received from someone not playing
 		else
 			HandlePacketFromAwayNode(node);
 	}
@@ -4047,7 +4111,7 @@ static void CL_SendClientCmd(void)
 
 	if (gamestate == GS_WAITINGPLAYERS)
 	{
-		// send NODEKEEPALIVE packet
+		// Send PT_NODEKEEPALIVE packet
 		netbuffer->packettype += 4;
 		packetsize = sizeof (clientcmd_pak) - sizeof (ticcmd_t) - sizeof (INT16);
 		HSendPacket(servernode, false, 0, packetsize);
@@ -4057,7 +4121,7 @@ static void CL_SendClientCmd(void)
 		G_MoveTiccmd(&netbuffer->u.clientpak.cmd, &localcmds, 1);
 		netbuffer->u.clientpak.consistancy = SHORT(consistancy[gametic%BACKUPTICS]);
 
-		// send a special packet with 2 cmd for splitscreen
+		// Send a special packet with 2 cmd for splitscreen
 		if (splitscreen || botingame)
 		{
 			netbuffer->packettype += 2;
@@ -4072,23 +4136,23 @@ static void CL_SendClientCmd(void)
 
 	if (cl_mode == CL_CONNECTED || dedicated)
 	{
-		// send extra data if needed
+		// Send extra data if needed
 		if (localtextcmd[0])
 		{
 			netbuffer->packettype = PT_TEXTCMD;
 			M_Memcpy(netbuffer->u.textcmd,localtextcmd, localtextcmd[0]+1);
-			// all extra data have been sended
-			if (HSendPacket(servernode, true, 0, localtextcmd[0]+1)) // send can fail...
+			// All extra data have been sent
+			if (HSendPacket(servernode, true, 0, localtextcmd[0]+1)) // Send can fail...
 				localtextcmd[0] = 0;
 		}
 
-		// send extra data if needed for player 2 (splitscreen)
+		// Send extra data if needed for player 2 (splitscreen)
 		if (localtextcmd2[0])
 		{
 			netbuffer->packettype = PT_TEXTCMD2;
 			M_Memcpy(netbuffer->u.textcmd, localtextcmd2, localtextcmd2[0]+1);
-			// all extra data have been sended
-			if (HSendPacket(servernode, true, 0, localtextcmd2[0]+1)) // send can fail...
+			// All extra data have been sent
+			if (HSendPacket(servernode, true, 0, localtextcmd2[0]+1)) // Send can fail...
 				localtextcmd2[0] = 0;
 		}
 	}
@@ -4352,7 +4416,7 @@ static inline void PingUpdate(void)
 	//check for ping limit breakage.
 	if (cv_maxping.value)
 	{
-		for (i = 1; i < MAXNETNODES; i++)
+		for (i = 1; i < MAXPLAYERS; i++)
 		{
 			if (playeringame[i] && (realpingtable[i] / pingmeasurecount > (unsigned)cv_maxping.value))
 			{
@@ -4366,7 +4430,7 @@ static inline void PingUpdate(void)
 		//in that case, it is probably the server's fault.
 		if (numlaggers < D_NumPlayers() - 1)
 		{
-			for (i = 1; i < MAXNETNODES; i++)
+			for (i = 1; i < MAXPLAYERS; i++)
 			{
 				if (playeringame[i] && laggers[i])
 				{
@@ -4381,7 +4445,7 @@ static inline void PingUpdate(void)
 	}
 
 	//make the ping packet and clear server data for next one
-	for (i = 0; i < MAXNETNODES; i++)
+	for (i = 0; i < MAXPLAYERS; i++)
 	{
 		netbuffer->u.pingtable[i] = realpingtable[i] / pingmeasurecount;
 		//server takes a snapshot of the real ping for display.
@@ -4391,7 +4455,7 @@ static inline void PingUpdate(void)
 	}
 
 	//send out our ping packets
-	for (i = 0; i < MAXNETNODES; i++)
+	for (i = 0; i < MAXPLAYERS; i++)
 		if (playeringame[i])
 			HSendPacket(i, true, 0, sizeof(INT32) * MAXPLAYERS);
 
@@ -4440,7 +4504,7 @@ void NetUpdate(void)
 	}
 #endif
 
-	if (!server)
+	if (client)
 		maketic = neededtic;
 
 	Local_Maketic(realtics); // make local tic, and call menu?
@@ -4455,7 +4519,7 @@ FILESTAMP
 
 	MasterClient_Ticker(); // Acking the Master Server
 
-	if (!server)
+	if (client)
 	{
 		if (!resynch_local_inprogress)
 			CL_SendClientCmd(); // Send tic cmd
@@ -4505,6 +4569,11 @@ FILESTAMP
 		}
 	}
 	Net_AckTicker();
+	// Handle timeouts to prevent definitive freezes from happenning
+	if (server)
+		for (i = 1; i < MAXNETNODES; i++)
+			if (nodeingame[i] && freezetimeout[i] < I_GetTime())
+				Net_ConnectionTimeout(i);
 	nowtime /= NEWTICRATERATIO;
 	if (nowtime > resptime)
 	{
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index fe80be1be5b8193ab6ad2f39c279ba795253b201..413acade73bea885c29ea7701afc9ae8312d8e96 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -80,6 +80,9 @@ typedef enum
 void Command_Drop(void);
 void Command_Droprate(void);
 #endif
+#ifdef _DEBUG
+void Command_Numnodes(void);
+#endif
 
 #if defined(_MSC_VER)
 #pragma pack(1)
@@ -442,6 +445,7 @@ extern consvar_t cv_playbackspeed;
 #define KICK_MSG_CUSTOM_BAN  8
 
 extern boolean server;
+#define client (!server)
 extern boolean dedicated; // For dedicated server
 extern UINT16 software_MAXPACKETLENGTH;
 extern boolean acceptnewnode;
@@ -449,13 +453,14 @@ extern SINT8 servernode;
 
 void Command_Ping_f(void);
 extern tic_t connectiontimeout;
+extern tic_t jointimeout;
 #ifdef NEWPING
 extern UINT16 pingmeasurecount;
 extern UINT32 realpingtable[MAXPLAYERS];
 extern UINT32 playerpingtable[MAXPLAYERS];
 #endif
 
-extern consvar_t cv_joinnextround, cv_allownewplayer, cv_maxplayers, cv_resynchattempts, cv_blamecfail, cv_maxsend;
+extern consvar_t cv_joinnextround, cv_allownewplayer, cv_maxplayers, cv_resynchattempts, cv_blamecfail, cv_maxsend, cv_noticedownload, cv_downloadspeed;
 
 // Used in d_net, the only dependence
 tic_t ExpandTics(INT32 low);
diff --git a/src/d_net.c b/src/d_net.c
index 6be1dbe5cd0fe506615828e5a377cfabc6888ca3..fae1ea311dcf87f1f19c4eb9b6781de4e7e26bf4 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -42,7 +42,7 @@
 // Normally maketic >= gametic > 0
 
 #define FORCECLOSE 0x8000
-tic_t connectiontimeout = (15*TICRATE);
+tic_t connectiontimeout = (10*TICRATE);
 
 /// \brief network packet
 doomcom_t *doomcom = NULL;
@@ -62,7 +62,7 @@ INT32 net_bandwidth;
 /// \brief max length per packet
 INT16 hardware_MAXPACKETLENGTH;
 
-void (*I_NetGet)(void) = NULL;
+boolean (*I_NetGet)(void) = NULL;
 void (*I_NetSend)(void) = NULL;
 boolean (*I_NetCanSend)(void) = NULL;
 boolean (*I_NetCanGet)(void) = NULL;
@@ -142,7 +142,7 @@ typedef struct
 	UINT8 destinationnode; // The node to send the ack to
 	tic_t senttime; // The time when the ack was sent
 	UINT16 length; // The packet size
-	UINT16 resentnum; // The number of
+	UINT16 resentnum; // The number of times the ack has been resent
 	union {
 		SINT8 raw[MAXPACKETLENGTH];
 		doomdata_t data;
@@ -152,11 +152,12 @@ typedef struct
 
 typedef enum
 {
-	CLOSE = 1, // flag is set when connection is closing
+	NF_CLOSE = 1, // Flag is set when connection is closing
+	NF_TIMEOUT = 2, // Flag is set when the node got a timeout
 } node_flags_t;
 
 #ifndef NONET
-// table of packet that was not acknowleged can be resend (the sender window)
+// Table of packets that were not acknowleged can be resent (the sender window)
 static ackpak_t ackpak[MAXACKPACKETS];
 #endif
 
@@ -274,6 +275,38 @@ static boolean GetFreeAcknum(UINT8 *freeack, boolean lowtimer)
 	return false;
 }
 
+/** Counts how many acks are free
+  *
+  * \param urgent True if the type of the packet meant to
+  *               use an ack is lower than PT_CANFAIL
+  *               If for some reason you don't want use it
+  *               for any packet type in particular,
+  *               just set to false
+  * \return The number of free acks
+  *
+  */
+INT32 Net_GetFreeAcks(boolean urgent)
+{
+	INT32 i, numfreeslot = 0;
+	INT32 n = 0; // Number of free acks found
+
+	for (i = 0; i < MAXACKPACKETS; i++)
+		if (!ackpak[i].acknum)
+		{
+			// For low priority packets, make sure to let freeslots so urgent packets can be sent
+			if (!urgent)
+			{
+				numfreeslot++;
+				if (numfreeslot <= URGENTFREESLOTNUM)
+					continue;
+			}
+
+			n++;
+		}
+
+	return n;
+}
+
 // Get a ack to send in the queue of this node
 static UINT8 GetAcktosend(INT32 node)
 {
@@ -298,7 +331,7 @@ static void RemoveAck(INT32 i)
 	DEBFILE(va("Remove ack %d\n",ackpak[i].acknum));
 #endif
 	ackpak[i].acknum = 0;
-	if (nodes[node].flags & CLOSE)
+	if (nodes[node].flags & NF_CLOSE)
 		Net_CloseConnection(node);
 }
 
@@ -452,8 +485,13 @@ static void GotAcks(void)
 }
 #endif
 
-static inline void Net_ConnectionTimeout(INT32 node)
+void Net_ConnectionTimeout(INT32 node)
 {
+	// Don't timeout several times
+	if (nodes[node].flags & NF_TIMEOUT)
+		return;
+	nodes[node].flags |= NF_TIMEOUT;
+
 	// Send a very special packet to self (hack the reboundstore queue)
 	// Main code will handle it
 	reboundstore[rebound_head].packettype = PT_NODETIMEOUT;
@@ -484,7 +522,7 @@ void Net_AckTicker(void)
 		if (ackpak[i].acknum && ackpak[i].senttime + node->timeout < I_GetTime())
 #endif
 		{
-			if (ackpak[i].resentnum > 10 && (node->flags & CLOSE))
+			if (ackpak[i].resentnum > 10 && (node->flags & NF_CLOSE))
 			{
 				DEBFILE(va("ack %d sent 10 times so connection is supposed lost: node %d\n",
 					i, nodei));
@@ -520,7 +558,7 @@ void Net_AckTicker(void)
 			if (nodes[i].lasttimeacktosend_sent + ACKTOSENDTIMEOUT < I_GetTime())
 				Net_SendAcks(i);
 
-			if (!(nodes[i].flags & CLOSE)
+			if (!(nodes[i].flags & NF_CLOSE)
 				&& nodes[i].lasttimepacketreceived + connectiontimeout < I_GetTime())
 			{
 				Net_ConnectionTimeout(i);
@@ -678,7 +716,7 @@ void Net_CloseConnection(INT32 node)
 	if (!node)
 		return;
 
-	nodes[node].flags |= CLOSE;
+	nodes[node].flags |= NF_CLOSE;
 
 	// try to Send ack back (two army problem)
 	if (GetAcktosend(node))
@@ -813,18 +851,20 @@ static void DebugPrintpacket(const char *header)
 		case PT_SERVERTICS:
 		{
 			servertics_pak *serverpak = &netbuffer->u.serverpak;
-			ticcmd_t *cmd = &serverpak->cmds[serverpak->numslots * serverpak->numtics];
-			size_t ntxtcmd = &((UINT8 *)netbuffer)[doomcom->datalength] - (UINT8 *)cmd;
+			UINT8 *cmd = (UINT8 *)(&serverpak->cmds[serverpak->numslots * serverpak->numtics]);
+			size_t ntxtcmd = &((UINT8 *)netbuffer)[doomcom->datalength] - cmd;
 
 			fprintf(debugfile, "    firsttic %u ply %d tics %d ntxtcmd %s\n    ",
 				(UINT32)ExpandTics(serverpak->starttic), serverpak->numslots, serverpak->numtics, sizeu1(ntxtcmd));
-			fprintfstring((char *)cmd, 3);
+			/// \todo Display more readable information about net commands
+			fprintfstringnewline((char *)cmd, ntxtcmd);
+			/*fprintfstring((char *)cmd, 3);
 			if (ntxtcmd > 4)
 			{
-				fprintf(debugfile, "[%s]", netxcmdnames[*(((UINT8 *)cmd) + 3) - 1]);
+				fprintf(debugfile, "[%s]", netxcmdnames[*((cmd) + 3) - 1]);
 				fprintfstring(((char *)cmd) + 4, ntxtcmd - 4);
 			}
-			fprintf(debugfile, "\n");
+			fprintf(debugfile, "\n");*/
 			break;
 		}
 		case PT_CLIENTCMD:
@@ -891,7 +931,7 @@ void Command_Drop(void)
 	if (COM_Argc() < 2)
 	{
 		CONS_Printf("drop <packettype> [quantity]: drop packets\n"
-					"drop reset: cancel all packet drops");
+					"drop reset: cancel all packet drops\n");
 		return;
 	}
 
@@ -1067,6 +1107,8 @@ boolean HSendPacket(INT32 node, boolean reliable, UINT8 acknum, size_t packetlen
 //
 boolean HGetPacket(void)
 {
+	//boolean nodejustjoined;
+
 	// Get a packet from self
 	if (rebound_tail != rebound_head)
 	{
@@ -1092,9 +1134,10 @@ boolean HGetPacket(void)
 
 	while(true)
 	{
+		//nodejustjoined = I_NetGet();
 		I_NetGet();
 
-		if (doomcom->remotenode == -1)
+		if (doomcom->remotenode == -1) // No packet received
 			return false;
 
 		getbytes += packetheaderlength + doomcom->datalength; // For stat
@@ -1110,6 +1153,7 @@ boolean HGetPacket(void)
 		if (netbuffer->checksum != NetbufferChecksum())
 		{
 			DEBFILE("Bad packet checksum\n");
+			//Net_CloseConnection(nodejustjoined ? (doomcom->remotenode | FORCECLOSE) : doomcom->remotenode);
 			Net_CloseConnection(doomcom->remotenode);
 			continue;
 		}
@@ -1119,11 +1163,26 @@ boolean HGetPacket(void)
 			DebugPrintpacket("GET");
 #endif
 
-		// proceed the ack and ackreturn field
+		/*// If a new node sends an unexpected packet, just ignore it
+		if (nodejustjoined && server
+			&& !(netbuffer->packettype == PT_ASKINFO
+				|| netbuffer->packettype == PT_SERVERINFO
+				|| netbuffer->packettype == PT_PLAYERINFO
+				|| netbuffer->packettype == PT_REQUESTFILE
+				|| netbuffer->packettype == PT_ASKINFOVIAMS
+				|| netbuffer->packettype == PT_CLIENTJOIN))
+		{
+			DEBFILE(va("New node sent an unexpected %s packet\n", packettypename[netbuffer->packettype]));
+			//CONS_Alert(CONS_NOTICE, "New node sent an unexpected %s packet\n", packettypename[netbuffer->packettype]);
+			Net_CloseConnection(doomcom->remotenode | FORCECLOSE);
+			continue;
+		}*/
+
+		// Proceed the ack and ackreturn field
 		if (!Processackpak())
 			continue; // discarded (duplicated)
 
-		// a packet with just ackreturn
+		// A packet with just ackreturn
 		if (netbuffer->packettype == PT_NOTHING)
 		{
 			GotAcks();
@@ -1136,9 +1195,10 @@ boolean HGetPacket(void)
 	return true;
 }
 
-static void Internal_Get(void)
+static boolean Internal_Get(void)
 {
 	doomcom->remotenode = -1;
+	return false;
 }
 
 FUNCNORETURN static ATTRNORETURN void Internal_Send(void)
@@ -1223,7 +1283,7 @@ boolean D_CheckNetGame(void)
 
 	if (netgame)
 		ret = true;
-	if (!server && netgame)
+	if (client && netgame)
 		netgame = false;
 	server = true; // WTF? server always true???
 		// no! The deault mode is server. Client is set elsewhere
diff --git a/src/d_net.h b/src/d_net.h
index 190e07a60ecbaec72517d5ddf408dc0fae8d7219..84814ce39327573a176716a02eec9d77bab52643 100644
--- a/src/d_net.h
+++ b/src/d_net.h
@@ -39,6 +39,7 @@ extern SINT8 nodetoplayer2[MAXNETNODES]; // Say the numplayer for this node if a
 extern UINT8 playerpernode[MAXNETNODES]; // Used specially for splitscreen
 extern boolean nodeingame[MAXNETNODES]; // Set false as nodes leave game
 
+INT32 Net_GetFreeAcks(boolean urgent);
 void Net_AckTicker(void);
 
 // If reliable return true if packet sent, 0 else
@@ -53,6 +54,7 @@ boolean D_CheckNetGame(void);
 void D_CloseConnection(void);
 void Net_UnAcknowledgePacket(INT32 node);
 void Net_CloseConnection(INT32 node);
+void Net_ConnectionTimeout(INT32 node);
 void Net_AbortPacketType(UINT8 packettype);
 void Net_SendAcks(INT32 node);
 void Net_WaitAllAckReceived(UINT32 timeout);
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index d70805e1cd997ec806540d2b7437a0359a5437d9..8e5f25177da949fb95ac37e88fd716bdd57aabe7 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -82,6 +82,7 @@ static void AutoBalance_OnChange(void);
 static void TeamScramble_OnChange(void);
 
 static void NetTimeout_OnChange(void);
+static void JoinTimeout_OnChange(void);
 
 static void Ringslinger_OnChange(void);
 static void Gravity_OnChange(void);
@@ -340,7 +341,9 @@ consvar_t cv_killingdead = {"killingdead", "Off", CV_NETVAR, CV_OnOff, NULL, 0,
 
 consvar_t cv_netstat = {"netstat", "Off", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; // show bandwidth statistics
 static CV_PossibleValue_t nettimeout_cons_t[] = {{TICRATE/7, "MIN"}, {60*TICRATE, "MAX"}, {0, NULL}};
-consvar_t cv_nettimeout = {"nettimeout", "525", CV_CALL|CV_SAVE, nettimeout_cons_t, NetTimeout_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_nettimeout = {"nettimeout", "350", CV_CALL|CV_SAVE, nettimeout_cons_t, NetTimeout_OnChange, 0, NULL, NULL, 0, 0, NULL};
+static CV_PossibleValue_t jointimeout_cons_t[] = {{5*TICRATE, "MIN"}, {60*TICRATE, "MAX"}, {0, NULL}};
+consvar_t cv_jointimeout = {"jointimeout", "350", CV_CALL|CV_SAVE, jointimeout_cons_t, JoinTimeout_OnChange, 0, NULL, NULL, 0, 0, NULL};
 #ifdef NEWPING
 consvar_t cv_maxping = {"maxping", "0", CV_SAVE, CV_Unsigned, NULL, 0, NULL, NULL, 0, 0, NULL};
 #endif
@@ -546,9 +549,12 @@ void D_RegisterServerCommands(void)
 	// d_clisrv
 	CV_RegisterVar(&cv_maxplayers);
 	CV_RegisterVar(&cv_maxsend);
+	CV_RegisterVar(&cv_noticedownload);
+	CV_RegisterVar(&cv_downloadspeed);
 
 	COM_AddCommand("ping", Command_Ping_f);
 	CV_RegisterVar(&cv_nettimeout);
+	CV_RegisterVar(&cv_jointimeout);
 
 	CV_RegisterVar(&cv_skipmapcheck);
 	CV_RegisterVar(&cv_sleep);
@@ -1005,7 +1011,7 @@ UINT8 CanChangeSkin(INT32 playernum)
 		return true;
 
 	// Force skin in effect.
-	if (!server && (cv_forceskin.value != -1) && !(adminplayer == playernum && serverplayer == -1))
+	if (client && (cv_forceskin.value != -1) && !(adminplayer == playernum && serverplayer == -1))
 		return false;
 
 	// Can change skin in intermission and whatnot.
@@ -1616,7 +1622,7 @@ static void Command_Map_f(void)
 		return;
 	}
 
-	if (!server && !(adminplayer == consoleplayer))
+	if (client && !(adminplayer == consoleplayer))
 	{
 		CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
 		return;
@@ -1943,7 +1949,7 @@ static void Got_Suicide(UINT8 **cp, INT32 playernum)
 	// You can't suicide someone else.  Nice try, there.
 	if (suicideplayer != playernum || (!G_PlatformGametype()))
 	{
-		CONS_Alert(CONS_WARNING, M_GetText("Illegal suicide command recieved from %s\n"), player_names[playernum]);
+		CONS_Alert(CONS_WARNING, M_GetText("Illegal suicide command received from %s\n"), player_names[playernum]);
 		if (server)
 		{
 			XBOXSTATIC UINT8 buf[2];
@@ -2658,7 +2664,7 @@ static void Command_Changepassword_f(void)
 	// If we have no MD5 support then completely disable XD_LOGIN responses for security.
 	CONS_Alert(CONS_NOTICE, "Remote administration commands are not supported in this build.\n");
 #else
-	if (!server) // cannot change remotely
+	if (client) // cannot change remotely
 	{
 		CONS_Printf(M_GetText("Only the server can use this.\n"));
 		return;
@@ -2717,7 +2723,7 @@ static void Got_Login(UINT8 **cp, INT32 playernum)
 
 	READMEM(*cp, sentmd5, 16);
 
-	if (!server)
+	if (client)
 		return;
 
 	// Do the final pass to compare with the sent md5
@@ -2739,7 +2745,7 @@ static void Command_Verify_f(void)
 	char *temp;
 	INT32 playernum;
 
-	if (!server)
+	if (client)
 	{
 		CONS_Printf(M_GetText("Only the server can use this.\n"));
 		return;
@@ -2823,7 +2829,7 @@ static void Command_MotD_f(void)
 			return;
 		}
 
-	if ((netgame || multiplayer) && !server)
+	if ((netgame || multiplayer) && client)
 		SendNetXCmd(XD_SETMOTD, mymotd, sizeof(motd));
 	else
 	{
@@ -3080,7 +3086,7 @@ static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum)
 	READMEM(*cp, md5sum, 16);
 
 	// Only the server processes this message.
-	if (!server)
+	if (client)
 		return;
 
 	// Disallow non-printing characters and semicolons.
@@ -3347,6 +3353,11 @@ static void NetTimeout_OnChange(void)
 	connectiontimeout = (tic_t)cv_nettimeout.value;
 }
 
+static void JoinTimeout_OnChange(void)
+{
+	jointimeout = (tic_t)cv_jointimeout.value;
+}
+
 UINT32 timelimitintics = 0;
 
 /** Deals with a timelimit change by printing the change to the console.
diff --git a/src/d_netfil.c b/src/d_netfil.c
index 4e3d70fa956f052f70eea5f999e32d3d98af65f9..bf4e5987825b2ca40c9fb91f4b28f42aef096e12 100644
--- a/src/d_netfil.c
+++ b/src/d_netfil.c
@@ -97,7 +97,7 @@ char downloaddir[256] = "DOWNLOAD";
 
 #ifdef CLIENT_LOADINGSCREEN
 // for cl loading screen
-INT32 lastfilenum = 0;
+INT32 lastfilenum = -1;
 #endif
 
 /** Fills a serverinfo packet with information about wad files loaded.
@@ -487,6 +487,9 @@ static void SV_SendFile(INT32 node, const char *filename, UINT8 fileid)
 	INT32 i;
 	char wadfilename[MAX_WADPATH];
 
+	if (cv_noticedownload.value)
+		CONS_Printf("Sending file \"%s\" to node %d\n", filename, node);
+
 	// Find the last file in the list and set a pointer to its "next" field
 	q = &transfer[node].txlist;
 	while (*q)
@@ -609,6 +612,8 @@ static void SV_EndFileSend(INT32 node)
 	switch (p->ram)
 	{
 		case SF_FILE: // It's a file, close it and free its filename
+			if (cv_noticedownload.value)
+				CONS_Printf("Ending file transfer for node %d\n", node);
 			if (transfer[node].currentfile)
 				fclose(transfer[node].currentfile);
 			free(p->id.filename);
@@ -636,6 +641,9 @@ static void SV_EndFileSend(INT32 node)
 
 /** Handles file transmission
   *
+  * \todo Use an acknowledging method more adapted to file transmission
+  *       The current download speed suffers from lack of ack packets,
+  *       especially when the one downloading has high latency
   *
   */
 void SV_FileSendTicker(void)
@@ -644,17 +652,38 @@ void SV_FileSendTicker(void)
 	filetx_pak *p;
 	size_t size;
 	filetx_t *f;
-	INT32 packetsent = PACKETPERTIC, ram, i;
+	INT32 packetsent, ram, i, j;
+	INT32 maxpacketsent;
 
-	if (!filestosend)
+	if (!filestosend) // No file to send
 		return;
-	if (!packetsent)
-		packetsent++;
+
+	if (cv_downloadspeed.value) // New (and experimental) behavior
+	{
+		packetsent = cv_downloadspeed.value;
+		// Don't send more packets than we have free acks
+#ifndef NONET
+		maxpacketsent = Net_GetFreeAcks(false) - 5; // Let 5 extra acks just in case
+#else
+		maxpacketsent = 1;
+#endif
+		if (packetsent > maxpacketsent && maxpacketsent > 0) // Send at least one packet
+			packetsent = maxpacketsent;
+	}
+	else // Old behavior
+	{
+		packetsent = PACKETPERTIC;
+		if (!packetsent)
+			packetsent = 1;
+	}
+
+	netbuffer->packettype = PT_FILEFRAGMENT;
+
 	// (((sendbytes-nowsentbyte)*TICRATE)/(I_GetTime()-starttime)<(UINT32)net_bandwidth)
 	while (packetsent-- && filestosend != 0)
 	{
-		for (i = currentnode, ram = 0; ram < MAXNETNODES;
-			i = (i+1) % MAXNETNODES, ram++)
+		for (i = currentnode, j = 0; j < MAXNETNODES;
+			i = (i+1) % MAXNETNODES, j++)
 		{
 			if (transfer[i].txlist)
 				goto found;
@@ -713,7 +742,6 @@ void SV_FileSendTicker(void)
 			p->position |= LONG(0x80000000);
 		p->fileid = f->fileid;
 		p->size = SHORT((UINT16)size);
-		netbuffer->packettype = PT_FILEFRAGMENT;
 
 		// Send the packet
 		if (HSendPacket(i, true, 0, FILETXHEADER + size)) // Reliable SEND
@@ -735,27 +763,40 @@ void SV_FileSendTicker(void)
 void Got_Filetxpak(void)
 {
 	INT32 filenum = netbuffer->u.filetxpak.fileid;
+	fileneeded_t *file = &fileneeded[filenum];
+	char *filename = file->filename;
 	static INT32 filetime = 0;
 
+	if (!(strcmp(filename, "srb2.srb")
+		&& strcmp(filename, "srb2.wad")
+		&& strcmp(filename, "zones.dta")
+		&& strcmp(filename, "player.dta")
+		&& strcmp(filename, "rings.dta")
+		&& strcmp(filename, "patch.dta")
+		&& strcmp(filename, "music.dta")
+		))
+		I_Error("Tried to download \"%s\"", filename);
+
 	if (filenum >= fileneedednum)
 	{
 		DEBFILE(va("fileframent not needed %d>%d\n", filenum, fileneedednum));
+		//I_Error("Received an unneeded file fragment (file id received: %d, file id needed: %d)\n", filenum, fileneedednum);
 		return;
 	}
 
-	if (fileneeded[filenum].status == FS_REQUESTED)
+	if (file->status == FS_REQUESTED)
 	{
-		if (fileneeded[filenum].file)
+		if (file->file)
 			I_Error("Got_Filetxpak: already open file\n");
-		fileneeded[filenum].file = fopen(fileneeded[filenum].filename, "wb");
-		if (!fileneeded[filenum].file)
-			I_Error("Can't create file %s: %s", fileneeded[filenum].filename, strerror(errno));
-		CONS_Printf("\r%s...\n",fileneeded[filenum].filename);
-		fileneeded[filenum].currentsize = 0;
-		fileneeded[filenum].status = FS_DOWNLOADING;
+		file->file = fopen(filename, "wb");
+		if (!file->file)
+			I_Error("Can't create file %s: %s", filename, strerror(errno));
+		CONS_Printf("\r%s...\n",filename);
+		file->currentsize = 0;
+		file->status = FS_DOWNLOADING;
 	}
 
-	if (fileneeded[filenum].status == FS_DOWNLOADING)
+	if (file->status == FS_DOWNLOADING)
 	{
 		UINT32 pos = LONG(netbuffer->u.filetxpak.position);
 		UINT16 size = SHORT(netbuffer->u.filetxpak.size);
@@ -764,27 +805,47 @@ void Got_Filetxpak(void)
 		if (pos & 0x80000000)
 		{
 			pos &= ~0x80000000;
-			fileneeded[filenum].totalsize = pos + size;
+			file->totalsize = pos + size;
 		}
 		// We can receive packet in the wrong order, anyway all os support gaped file
-		fseek(fileneeded[filenum].file, pos, SEEK_SET);
-		if (fwrite(netbuffer->u.filetxpak.data,size,1,fileneeded[filenum].file) != 1)
-			I_Error("Can't write to %s: %s\n",fileneeded[filenum].filename, strerror(ferror(fileneeded[filenum].file)));
-		fileneeded[filenum].currentsize += size;
+		fseek(file->file, pos, SEEK_SET);
+		if (fwrite(netbuffer->u.filetxpak.data,size,1,file->file) != 1)
+			I_Error("Can't write to %s: %s\n",filename, strerror(ferror(file->file)));
+		file->currentsize += size;
 
 		// Finished?
-		if (fileneeded[filenum].currentsize == fileneeded[filenum].totalsize)
+		if (file->currentsize == file->totalsize)
 		{
-			fclose(fileneeded[filenum].file);
-			fileneeded[filenum].file = NULL;
-			fileneeded[filenum].status = FS_FOUND;
+			fclose(file->file);
+			file->file = NULL;
+			file->status = FS_FOUND;
 			CONS_Printf(M_GetText("Downloading %s...(done)\n"),
-				fileneeded[filenum].filename);
+				filename);
 		}
 	}
 	else
-		I_Error("Received a file not requested\n");
-
+	{
+		const char *s;
+		switch(file->status)
+		{
+		case FS_NOTFOUND:
+			s = "FS_NOTFOUND";
+			break;
+		case FS_FOUND:
+			s = "FS_FOUND";
+			break;
+		case FS_OPEN:
+			s = "FS_OPEN";
+			break;
+		case FS_MD5SUMBAD:
+			s = "FS_MD5SUMBAD";
+			break;
+		default:
+			s = "unknown";
+			break;
+		}
+		I_Error("Received a file not requested (file id: %d, file status: %s)\n", filenum, s);
+	}
 	// Send ack back quickly
 	if (++filetime == 3)
 	{
@@ -797,12 +858,23 @@ void Got_Filetxpak(void)
 #endif
 }
 
-/** Cancels all file requests for a node
+/** \brief Checks if a node is downloading a file
  *
- * \param node The destination
- * \sa SV_EndFileSend
+ * \param node The node to check for
+ * \return True if the node is downloading a file
  *
  */
+boolean SV_SendingFile(INT32 node)
+{
+	return transfer[node].txlist != NULL;
+}
+
+/** Cancels all file requests for a node
+  *
+  * \param node The destination
+  * \sa SV_EndFileSend
+  *
+  */
 void SV_AbortSendFiles(INT32 node)
 {
 	while (transfer[node].txlist)
diff --git a/src/d_netfil.h b/src/d_netfil.h
index e82b57d67ea7a9ec75fd29655ac82c5fef6a6873..c9085a5b0ecdc38ed3fb8a0ecb9278ef37369749 100644
--- a/src/d_netfil.h
+++ b/src/d_netfil.h
@@ -65,6 +65,7 @@ void SV_SendRam(INT32 node, void *data, size_t size, freemethod_t freemethod,
 
 void SV_FileSendTicker(void);
 void Got_Filetxpak(void);
+boolean SV_SendingFile(INT32 node);
 
 boolean CL_CheckDownloadable(void);
 boolean CL_SendRequestFile(void);
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index a575669100bfd922f37395c7ea12ee00c20020ad..646bdcbad6d0ba9299fea195e735a67c3f9d22c9 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -470,7 +470,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum)
 	boolean action = false;
 	char *ptr;
 
-	CONS_Debug(DBG_NETPLAY,"Recieved SAY cmd from Player %d (%s)\n", playernum+1, player_names[playernum]);
+	CONS_Debug(DBG_NETPLAY,"Received SAY cmd from Player %d (%s)\n", playernum+1, player_names[playernum]);
 
 	target = READSINT8(*p);
 	flags = READUINT8(*p);
@@ -1102,7 +1102,19 @@ void HU_Drawer(void)
 
 	// draw desynch text
 	if (hu_resynching)
-		V_DrawCenteredString(BASEVIDWIDTH/2, 180, V_YELLOWMAP, "Resynching...");
+	{
+		static UINT32 resynch_ticker = 0;
+		char resynch_text[14];
+		UINT32 i;
+
+		// Animate the dots
+		resynch_ticker++;
+		strcpy(resynch_text, "Resynching");
+		for (i = 0; i < (resynch_ticker / 16) % 4; i++)
+			strcat(resynch_text, ".");
+
+		V_DrawCenteredString(BASEVIDWIDTH/2, 180, V_YELLOWMAP | V_ALLOWLOWERCASE, resynch_text);
+	}
 }
 
 //======================================================================
diff --git a/src/i_net.h b/src/i_net.h
index e378f5723bffd37c33398120c040ecc771fdc876..2bfa5eac7d14a1f567a99f406aaec5afdcee23d7 100644
--- a/src/i_net.h
+++ b/src/i_net.h
@@ -85,7 +85,7 @@ extern doomcom_t *doomcom;
 
 /**	\brief return packet in doomcom struct
 */
-extern void (*I_NetGet)(void);
+extern boolean (*I_NetGet)(void);
 
 /**	\brief ask to driver if there is data waiting
 */
diff --git a/src/i_tcp.c b/src/i_tcp.c
index f6212458b889f9ff3a70321e54bd9aa7c8951f42..c65a536a8577bef3e132b15d4eaab4ad6aa4374f 100644
--- a/src/i_tcp.c
+++ b/src/i_tcp.c
@@ -179,6 +179,7 @@ static UINT8 UPNP_support = TRUE;
 #include "i_system.h"
 #include "i_net.h"
 #include "d_net.h"
+#include "d_netfil.h"
 #include "i_tcp.h"
 #include "m_argv.h"
 
@@ -482,37 +483,96 @@ static boolean SOCK_cmpaddr(mysockaddr_t *a, mysockaddr_t *b, UINT8 mask)
 		return false;
 }
 
+// This is a hack. For some reason, nodes aren't being freed properly.
+// This goes through and cleans up what nodes were supposed to be freed.
+/** \warning This function causes the file downloading to stop if someone joins.
+  *          How? Because it removes nodes that are connected but not in game,
+  *          which is exactly what clients downloading a file are.
+  */
+static void cleanupnodes(void)
+{
+	SINT8 j;
+
+	if (!Playing())
+		return;
+
+	// Why can't I start at zero?
+	for (j = 1; j < MAXNETNODES; j++)
+		//if (!(nodeingame[j] || SV_SendingFile(j)))
+		if (!nodeingame[j])
+			nodeconnected[j] = false;
+}
+
 static SINT8 getfreenode(void)
 {
 	SINT8 j;
 
+	cleanupnodes();
+
 	for (j = 0; j < MAXNETNODES; j++)
 		if (!nodeconnected[j])
 		{
 			nodeconnected[j] = true;
 			return j;
 		}
+
+	/** \warning No free node? Just in case a node might not have been freed properly,
+	  *          look if there are connected nodes that aren't in game, and forget them.
+	  *          It's dirty, and might result in a poor guy having to restart
+	  *          downloading a needed wad, but it's better than not letting anyone join...
+	  */
+	/*I_Error("No more free nodes!!1!11!11!!1111\n");
+	for (j = 1; j < MAXNETNODES; j++)
+		if (!nodeingame[j])
+			return j;*/
+
 	return -1;
 }
 
-// This is a hack. For some reason, nodes aren't being freed properly.
-// This goes through and cleans up what nodes were supposed to be freed.
-static void cleanupnodes(void)
+#ifdef _DEBUG
+void Command_Numnodes(void)
 {
-	SINT8 j;
+	INT32 connected = 0;
+	INT32 ingame = 0;
+	INT32 i;
 
-	if (!Playing())
-		return;
+	for (i = 1; i < MAXNETNODES; i++)
+	{
+		if (!(nodeconnected[i] || nodeingame[i]))
+			continue;
 
-	// Why can't I start at zero?
-	for (j = 1; j < MAXNETNODES; j++)
-		if (!nodeingame[j])
-			nodeconnected[j] = false;
+		if (nodeconnected[i])
+			connected++;
+		if (nodeingame[i])
+			ingame++;
+
+		CONS_Printf("%2d - ", i);
+		if (nodetoplayer[i] != -1)
+			CONS_Printf("player %.2d", nodetoplayer[i]);
+		else
+			CONS_Printf("         ");
+		if (nodeconnected[i])
+			CONS_Printf(" - connected");
+		else
+			CONS_Printf(" -          ");
+		if (nodeingame[i])
+			CONS_Printf(" - ingame");
+		else
+			CONS_Printf(" -       ");
+		CONS_Printf(" - %s\n", I_GetNodeAddress(i));
+	}
+
+	CONS_Printf("\n"
+				"Connected: %d\n"
+				"Ingame:    %d\n",
+				connected, ingame);
 }
 #endif
+#endif
 
 #ifndef NONET
-static void SOCK_Get(void)
+// Returns true if a packet was received from a new node, false in all other cases
+static boolean SOCK_Get(void)
 {
 	size_t i, n;
 	int j;
@@ -535,13 +595,12 @@ static void SOCK_Get(void)
 					doomcom->remotenode = (INT16)j; // good packet from a game player
 					doomcom->datalength = (INT16)c;
 					nodesocket[j] = mysockets[n];
-					return;
+					return false;
 				}
 			}
 			// not found
 
 			// find a free slot
-			cleanupnodes();
 			j = getfreenode();
 			if (j > 0)
 			{
@@ -564,14 +623,15 @@ static void SOCK_Get(void)
 				}
 				if (i == numbans)
 					SOCK_bannednode[j] = false;
-				return;
+				return true;
 			}
 			else
 				DEBFILE("New node detected: No more free slots\n");
-
 		}
 	}
+
 	doomcom->remotenode = -1; // no packet
+	return false;
 }
 #endif
 
@@ -1256,7 +1316,6 @@ static SINT8 SOCK_NetMakeNodewPort(const char *address, const char *port)
 	gaie = I_getaddrinfo(address, port, &hints, &ai);
 	if (gaie == 0)
 	{
-		cleanupnodes();
 		newnode = getfreenode();
 	}
 	if (newnode == -1)
diff --git a/src/p_inter.c b/src/p_inter.c
index cf5512a185a610a20e8dab4644fb30567e8d8edc..4892d977519a3c31f57b83bbad2f345adba4f01e 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -1684,7 +1684,7 @@ void P_CheckTimeLimit(void)
 		return;
 
 	//Tagmode round end but only on the tic before the
-	//XD_EXITLEVEL packet is recieved by all players.
+	//XD_EXITLEVEL packet is received by all players.
 	if (G_TagGametype())
 	{
 		if (leveltime == (timelimitintics + 1))
@@ -1695,7 +1695,7 @@ void P_CheckTimeLimit(void)
 				 || (players[i].pflags & PF_TAGGED) || (players[i].pflags & PF_TAGIT))
 					continue;
 
-				CONS_Printf(M_GetText("%s recieved double points for surviving the round.\n"), player_names[i]);
+				CONS_Printf(M_GetText("%s received double points for surviving the round.\n"), player_names[i]);
 				P_AddPlayerScore(&players[i], players[i].score);
 			}
 		}