diff --git a/src/netcode/d_clisrv.c b/src/netcode/d_clisrv.c
index 506de8c0289aea37e50f429c6f4a80dc7bff6d8d..1658856b68f965df310d2ef6a056e325e332a3e1 100644
--- a/src/netcode/d_clisrv.c
+++ b/src/netcode/d_clisrv.c
@@ -83,6 +83,7 @@ UINT8 playernode[MAXPLAYERS];
 UINT16 pingmeasurecount = 1;
 UINT32 realpingtable[MAXPLAYERS]; //the base table of ping where an average will be sent to everyone.
 UINT32 playerpingtable[MAXPLAYERS]; //table of player latency values.
+static INT32 pingtimeout[MAXPLAYERS];
 tic_t servermaxping = 800; // server's max ping. Defaults to 800
 
 tic_t maketic;
@@ -120,137 +121,6 @@ void ResetNode(INT32 node)
 	netnodes[node].player2 = -1;
 }
 
-//
-// CL_ClearPlayer
-//
-// Clears the player data so that a future client can use this slot
-//
-void CL_ClearPlayer(INT32 playernum)
-{
-	if (players[playernum].mo)
-		P_RemoveMobj(players[playernum].mo);
-	memset(&players[playernum], 0, sizeof (player_t));
-	memset(playeraddress[playernum], 0, sizeof(*playeraddress));
-}
-
-static void UnlinkPlayerFromNode(INT32 playernum)
-{
-	INT32 node = playernode[playernum];
-
-	if (node == UINT8_MAX)
-		return;
-
-	playernode[playernum] = UINT8_MAX;
-
-	netnodes[node].numplayers--;
-	if (netnodes[node].numplayers <= 0)
-	{
-		netnodes[node].ingame = false;
-		Net_CloseConnection(node);
-		ResetNode(node);
-	}
-}
-
-// If in a special stage, redistribute the player's
-// spheres across the remaining players.
-// I feel like this shouldn't even be in this file at all, but well.
-static void RedistributeSpecialStageSpheres(INT32 playernum)
-{
-	if (!G_IsSpecialStage(gamemap) || D_NumPlayers() <= 1)
-		return;
-
-	INT32 count = D_NumPlayers() - 1;
-	INT32 spheres = players[playernum].spheres;
-	INT32 rings = players[playernum].rings;
-
-	while (spheres || rings)
-	{
-		INT32 sincrement = max(spheres / count, 1);
-		INT32 rincrement = max(rings / count, 1);
-
-		INT32 i, n;
-		for (i = 0; i < MAXPLAYERS; i++)
-		{
-			if (!playeringame[i] || i == playernum)
-				continue;
-
-			n = min(spheres, sincrement);
-			P_GivePlayerSpheres(&players[i], n);
-			spheres -= n;
-
-			n = min(rings, rincrement);
-			P_GivePlayerRings(&players[i], n);
-			rings -= n;
-		}
-	}
-}
-
-//
-// CL_RemovePlayer
-//
-// Removes a player from the current game
-//
-void CL_RemovePlayer(INT32 playernum, kickreason_t reason)
-{
-	// Sanity check: exceptional cases (i.e. c-fails) can cause multiple
-	// kick commands to be issued for the same player.
-	if (!playeringame[playernum])
-		return;
-
-	if (server)
-		UnlinkPlayerFromNode(playernum);
-
-	if (gametyperules & GTR_TEAMFLAGS)
-		P_PlayerFlagBurst(&players[playernum], false); // Don't take the flag with you!
-
-	RedistributeSpecialStageSpheres(playernum);
-
-	LUA_HookPlayerQuit(&players[playernum], reason); // Lua hook for player quitting
-
-	// don't look through someone's view who isn't there
-	if (playernum == displayplayer)
-	{
-		// Call ViewpointSwitch hooks here.
-		// The viewpoint was forcibly changed.
-		LUA_HookViewpointSwitch(&players[consoleplayer], &players[consoleplayer], true);
-		displayplayer = consoleplayer;
-	}
-
-	// Reset player data
-	CL_ClearPlayer(playernum);
-
-	// remove avatar of player
-	playeringame[playernum] = false;
-	while (!playeringame[doomcom->numslots-1] && doomcom->numslots > 1)
-		doomcom->numslots--;
-
-	// Reset the name
-	sprintf(player_names[playernum], "Player %d", playernum+1);
-
-	player_name_changes[playernum] = 0;
-
-	if (IsPlayerAdmin(playernum))
-	{
-		RemoveAdminPlayer(playernum); // don't stay admin after you're gone
-	}
-
-	LUA_InvalidatePlayer(&players[playernum]);
-
-	if (G_TagGametype()) //Check if you still have a game. Location flexible. =P
-		P_CheckSurvivors();
-	else if (gametyperules & GTR_RACE)
-		P_CheckRacers();
-}
-
-void CL_HandleTimeout(void)
-{
-	LUA_HookBool(false, HOOK(GameQuit));
-	D_QuitNetGame();
-	CL_Reset();
-	D_StartTitle();
-	M_StartMessage(M_GetText("Server Timeout\n\nPress Esc\n"), NULL, MM_NOTHING);
-}
-
 void CL_Reset(void)
 {
 	if (metalrecording)
@@ -290,93 +160,241 @@ void CL_Reset(void)
 	// D_StartTitle should get done now, but the calling function will handle it
 }
 
-SINT8 nametonum(const char *name)
+//
+// CL_ClearPlayer
+//
+// Clears the player data so that a future client can use this slot
+//
+void CL_ClearPlayer(INT32 playernum)
 {
-	INT32 playernum, i;
-
-	if (!strcmp(name, "0"))
-		return 0;
-
-	playernum = (SINT8)atoi(name);
-
-	if (playernum < 0 || playernum >= MAXPLAYERS)
-		return -1;
-
-	if (playernum)
-	{
-		if (playeringame[playernum])
-			return (SINT8)playernum;
-		else
-			return -1;
-	}
-
-	for (i = 0; i < MAXPLAYERS; i++)
-		if (playeringame[i] && !stricmp(player_names[i], name))
-			return (SINT8)i;
-
-	CONS_Printf(M_GetText("There is no player named \"%s\"\n"), name);
-
-	return -1;
+	if (players[playernum].mo)
+		P_RemoveMobj(players[playernum].mo);
+	memset(&players[playernum], 0, sizeof (player_t));
+	memset(playeraddress[playernum], 0, sizeof(*playeraddress));
 }
 
-static void Got_KickCmd(UINT8 **p, INT32 playernum)
+// Xcmd XD_ADDPLAYER
+static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 {
-	INT32 pnum, msg;
-	char buf[3 + MAX_REASONLENGTH];
-	char *reason = buf;
-	kickreason_t kickreason = KR_KICK;
-	boolean keepbody;
-
-	pnum = READUINT8(*p);
-	msg = READUINT8(*p);
-	keepbody = (msg & KICK_MSG_KEEP_BODY) != 0;
-	msg &= ~KICK_MSG_KEEP_BODY;
+	INT16 node, newplayernum;
+	boolean splitscreenplayer;
+	boolean rejoined;
+	player_t *newplayer;
 
-	if (pnum == serverplayer && IsPlayerAdmin(playernum))
+	if (playernum != serverplayer && !IsPlayerAdmin(playernum))
 	{
-		CONS_Printf(M_GetText("Server is being shut down remotely. Goodbye!\n"));
-
+		// protect against hacked/buggy client
+		CONS_Alert(CONS_WARNING, M_GetText("Illegal add player command received from %s\n"), player_names[playernum]);
 		if (server)
-			COM_BufAddText("quit\n");
-
+			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
 		return;
 	}
 
-	// Is playernum authorized to make this kick?
-	if (playernum != serverplayer && !IsPlayerAdmin(playernum)
-		&& !(playernode[playernum] != UINT8_MAX && netnodes[playernode[playernum]].numplayers == 2
-		&& netnodes[playernode[playernum]].player2 == pnum))
+	node = READUINT8(*p);
+	newplayernum = READUINT8(*p);
+	splitscreenplayer = newplayernum & 0x80;
+	newplayernum &= ~0x80;
+
+	rejoined = playeringame[newplayernum];
+
+	if (!rejoined)
 	{
-		// We received a kick command from someone who isn't the
-		// server or admin, and who isn't in splitscreen removing
-		// player 2. Thus, it must be someone with a modified
-		// binary, trying to kick someone but without having
-		// authorization.
+		// 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)
+			CL_ClearPlayer(newplayernum);
+		playeringame[newplayernum] = true;
+		G_AddPlayer(newplayernum);
+		if (newplayernum+1 > doomcom->numslots)
+			doomcom->numslots = (INT16)(newplayernum+1);
 
-		// We deal with this by changing the kick reason to
-		// "consistency failure" and kicking the offending user
-		// instead.
+		if (server && I_GetNodeAddress)
+		{
+			const char *address = I_GetNodeAddress(node);
+			char *port = NULL;
+			if (address) // MI: fix msvcrt.dll!_mbscat crash?
+			{
+				strcpy(playeraddress[newplayernum], address);
+				port = strchr(playeraddress[newplayernum], ':');
+				if (port)
+					*port = '\0';
+			}
+		}
+	}
 
-		// 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.
+	newplayer = &players[newplayernum];
 
-		CONS_Alert(CONS_WARNING, M_GetText("Illegal kick command received from %s for player %d\n"), player_names[playernum], pnum);
+	newplayer->jointime = 0;
+	newplayer->quittime = 0;
 
-		// In debug, print a longer message with more details.
-		// TODO Callum: Should we translate this?
-/*
-		CONS_Debug(DBG_NETPLAY,
-			"So, you must be asking, why is this an illegal kick?\n"
-			"Well, let's take a look at the facts, shall we?\n"
-			"\n"
-			"playernum (this is the guy who did it), he's %d.\n"
-			"pnum (the guy he's trying to kick) is %d.\n"
-			"playernum's node is %d.\n"
-			"That node has %d players.\n"
-			"Player 2 on that node is %d.\n"
+	READSTRINGN(*p, player_names[newplayernum], MAXPLAYERNAME);
+
+	// the server is creating my player
+	if (node == mynode)
+	{
+		playernode[newplayernum] = 0; // for information only
+		if (!splitscreenplayer)
+		{
+			consoleplayer = newplayernum;
+			displayplayer = newplayernum;
+			secondarydisplayplayer = newplayernum;
+			DEBFILE("spawning me\n");
+			ticcmd_oldangleturn[0] = newplayer->oldrelangleturn;
+		}
+		else
+		{
+			secondarydisplayplayer = newplayernum;
+			DEBFILE("spawning my brother\n");
+			if (botingame)
+				newplayer->bot = 1;
+			ticcmd_oldangleturn[1] = newplayer->oldrelangleturn;
+		}
+		P_ForceLocalAngle(newplayer, (angle_t)(newplayer->angleturn << 16));
+		D_SendPlayerConfig();
+		addedtogame = true;
+
+		if (rejoined)
+		{
+			if (newplayer->mo)
+			{
+				newplayer->viewheight = 41*newplayer->height/48;
+
+				if (newplayer->mo->eflags & MFE_VERTICALFLIP)
+					newplayer->viewz = newplayer->mo->z + newplayer->mo->height - newplayer->viewheight;
+				else
+					newplayer->viewz = newplayer->mo->z + newplayer->viewheight;
+			}
+
+			// wake up the status bar
+			ST_Start();
+			// wake up the heads up text
+			HU_Start();
+
+			if (camera.chase && !splitscreenplayer)
+				P_ResetCamera(newplayer, &camera);
+			if (camera2.chase && splitscreenplayer)
+				P_ResetCamera(newplayer, &camera2);
+		}
+	}
+
+	if (netgame)
+	{
+		char joinmsg[256];
+
+		if (rejoined)
+			strcpy(joinmsg, M_GetText("\x82*%s has rejoined the game (player %d)"));
+		else
+			strcpy(joinmsg, M_GetText("\x82*%s has joined the game (player %d)"));
+		strcpy(joinmsg, va(joinmsg, player_names[newplayernum], newplayernum));
+
+		// Merge join notification + IP to avoid clogging console/chat
+		if (server && cv_showjoinaddress.value && I_GetNodeAddress)
+		{
+			const char *address = I_GetNodeAddress(node);
+			if (address)
+				strcat(joinmsg, va(" (%s)", address));
+		}
+
+		HU_AddChatText(joinmsg, false);
+	}
+
+	if (server && multiplayer && motd[0] != '\0')
+		COM_BufAddText(va("sayto %d %s\n", newplayernum, motd));
+
+	if (!rejoined)
+		LUA_HookInt(newplayernum, HOOK(PlayerJoin));
+}
+
+static void UnlinkPlayerFromNode(INT32 playernum)
+{
+	INT32 node = playernode[playernum];
+
+	if (node == UINT8_MAX)
+		return;
+
+	playernode[playernum] = UINT8_MAX;
+
+	netnodes[node].numplayers--;
+	if (netnodes[node].numplayers <= 0)
+	{
+		netnodes[node].ingame = false;
+		Net_CloseConnection(node);
+		ResetNode(node);
+	}
+}
+
+static void PT_ClientQuit(SINT8 node, INT32 netconsole)
+{
+	if (client)
+		return;
+
+	if (netnodes[node].ingame && netconsole != -1 && playeringame[netconsole])
+		SendKicksForNode(node, KICK_MSG_PLAYER_QUIT | KICK_MSG_KEEP_BODY);
+
+	Net_CloseConnection(node);
+	netnodes[node].ingame = false;
+	netnodes[node].player = -1;
+	netnodes[node].player2 = -1;
+}
+
+static void Got_KickCmd(UINT8 **p, INT32 playernum)
+{
+	INT32 pnum, msg;
+	char buf[3 + MAX_REASONLENGTH];
+	char *reason = buf;
+	kickreason_t kickreason = KR_KICK;
+	boolean keepbody;
+
+	pnum = READUINT8(*p);
+	msg = READUINT8(*p);
+	keepbody = (msg & KICK_MSG_KEEP_BODY) != 0;
+	msg &= ~KICK_MSG_KEEP_BODY;
+
+	if (pnum == serverplayer && IsPlayerAdmin(playernum))
+	{
+		CONS_Printf(M_GetText("Server is being shut down remotely. Goodbye!\n"));
+
+		if (server)
+			COM_BufAddText("quit\n");
+
+		return;
+	}
+
+	// Is playernum authorized to make this kick?
+	if (playernum != serverplayer && !IsPlayerAdmin(playernum)
+		&& !(playernode[playernum] != UINT8_MAX && netnodes[playernode[playernum]].numplayers == 2
+		&& netnodes[playernode[playernum]].player2 == pnum))
+	{
+		// We received a kick command from someone who isn't the
+		// server or admin, and who isn't in splitscreen removing
+		// player 2. Thus, it must be someone with a modified
+		// binary, trying to kick someone but without having
+		// authorization.
+
+		// We deal with this by changing the kick reason to
+		// "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.
+		// TODO Callum: Should we translate this?
+/*
+		CONS_Debug(DBG_NETPLAY,
+			"So, you must be asking, why is this an illegal kick?\n"
+			"Well, let's take a look at the facts, shall we?\n"
+			"\n"
+			"playernum (this is the guy who did it), he's %d.\n"
+			"pnum (the guy he's trying to kick) is %d.\n"
+			"playernum's node is %d.\n"
+			"That node has %d players.\n"
+			"Player 2 on that node is %d.\n"
 			"pnum's node is %d.\n"
 			"That node has %d players.\n"
 			"Player 2 on that node is %d.\n"
@@ -510,116 +528,95 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 		CL_RemovePlayer(pnum, kickreason);
 }
 
-static void Got_AddPlayer(UINT8 **p, INT32 playernum);
-
-// called one time at init
-void D_ClientServerInit(void)
+// If in a special stage, redistribute the player's
+// spheres across the remaining players.
+// I feel like this shouldn't even be in this file at all, but well.
+static void RedistributeSpecialStageSpheres(INT32 playernum)
 {
-	DEBFILE(va("- - -== SRB2 v%d.%.2d.%d "VERSIONSTRING" debugfile ==- - -\n",
-		VERSION/100, VERSION%100, SUBVERSION));
+	if (!G_IsSpecialStage(gamemap) || D_NumPlayers() <= 1)
+		return;
 
-	COM_AddCommand("getplayernum", Command_GetPlayerNum);
-	COM_AddCommand("kick", Command_Kick);
-	COM_AddCommand("ban", Command_Ban);
-	COM_AddCommand("banip", Command_BanIP);
-	COM_AddCommand("clearbans", Command_ClearBans);
-	COM_AddCommand("showbanlist", Command_ShowBan);
-	COM_AddCommand("reloadbans", Command_ReloadBan);
-	COM_AddCommand("connect", Command_connect);
-	COM_AddCommand("nodes", Command_Nodes);
-	COM_AddCommand("resendgamestate", Command_ResendGamestate);
-#ifdef PACKETDROP
-	COM_AddCommand("drop", Command_Drop);
-	COM_AddCommand("droprate", Command_Droprate);
-#endif
-#ifdef _DEBUG
-	COM_AddCommand("numnodes", Command_Numnodes);
-#endif
+	INT32 count = D_NumPlayers() - 1;
+	INT32 spheres = players[playernum].spheres;
+	INT32 rings = players[playernum].rings;
 
-	RegisterNetXCmd(XD_KICK, Got_KickCmd);
-	RegisterNetXCmd(XD_ADDPLAYER, Got_AddPlayer);
-#ifdef DUMPCONSISTENCY
-	CV_RegisterVar(&cv_dumpconsistency);
-#endif
-	Ban_Load_File(false);
+	while (spheres || rings)
+	{
+		INT32 sincrement = max(spheres / count, 1);
+		INT32 rincrement = max(rings / count, 1);
 
-	gametic = 0;
-	localgametic = 0;
+		INT32 i, n;
+		for (i = 0; i < MAXPLAYERS; i++)
+		{
+			if (!playeringame[i] || i == playernum)
+				continue;
 
-	// do not send anything before the real begin
-	SV_StopServer();
-	SV_ResetServer();
-	if (dedicated)
-		SV_SpawnServer();
+			n = min(spheres, sincrement);
+			P_GivePlayerSpheres(&players[i], n);
+			spheres -= n;
+
+			n = min(rings, rincrement);
+			P_GivePlayerRings(&players[i], n);
+			rings -= n;
+		}
+	}
 }
 
-void SV_ResetServer(void)
+//
+// CL_RemovePlayer
+//
+// Removes a player from the current game
+//
+void CL_RemovePlayer(INT32 playernum, kickreason_t reason)
 {
-	INT32 i;
-
-	// +1 because this command will be executed in com_executebuffer in
-	// tryruntic so gametic will be incremented, anyway maketic > gametic
-	// is not an issue
-
-	maketic = gametic + 1;
-	neededtic = maketic;
-	tictoclear = maketic;
-
-	joindelay = 0;
+	// Sanity check: exceptional cases (i.e. c-fails) can cause multiple
+	// kick commands to be issued for the same player.
+	if (!playeringame[playernum])
+		return;
 
-	for (i = 0; i < MAXNETNODES; i++)
-		ResetNode(i);
+	if (server)
+		UnlinkPlayerFromNode(playernum);
 
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		LUA_InvalidatePlayer(&players[i]);
-		playeringame[i] = false;
-		playernode[i] = UINT8_MAX;
-		memset(playeraddress[i], 0, sizeof(*playeraddress));
-		sprintf(player_names[i], "Player %d", i + 1);
-		adminplayers[i] = -1; // Populate the entire adminplayers array with -1.
-	}
+	if (gametyperules & GTR_TEAMFLAGS)
+		P_PlayerFlagBurst(&players[playernum], false); // Don't take the flag with you!
 
-	memset(player_name_changes, 0, sizeof player_name_changes);
+	RedistributeSpecialStageSpheres(playernum);
 
-	mynode = 0;
-	cl_packetmissed = false;
-	cl_redownloadinggamestate = false;
+	LUA_HookPlayerQuit(&players[playernum], reason); // Lua hook for player quitting
 
-	if (dedicated)
+	// don't look through someone's view who isn't there
+	if (playernum == displayplayer)
 	{
-		netnodes[0].ingame = true;
-		serverplayer = 0;
+		// Call ViewpointSwitch hooks here.
+		// The viewpoint was forcibly changed.
+		LUA_HookViewpointSwitch(&players[consoleplayer], &players[consoleplayer], true);
+		displayplayer = consoleplayer;
 	}
-	else
-		serverplayer = consoleplayer;
-
-	if (server)
-		servernode = 0;
 
-	doomcom->numslots = 0;
+	// Reset player data
+	CL_ClearPlayer(playernum);
 
-	// clear server_context
-	memset(server_context, '-', 8);
+	// remove avatar of player
+	playeringame[playernum] = false;
+	while (!playeringame[doomcom->numslots-1] && doomcom->numslots > 1)
+		doomcom->numslots--;
 
-	CV_RevertNetVars();
+	// Reset the name
+	sprintf(player_names[playernum], "Player %d", playernum+1);
 
-	DEBFILE("\n-=-=-=-=-=-=-= Server Reset =-=-=-=-=-=-=-\n\n");
-}
+	player_name_changes[playernum] = 0;
 
-static inline void SV_GenContext(void)
-{
-	UINT8 i;
-	// generate server_context, as exactly 8 bytes of randomly mixed A-Z and a-z
-	// (hopefully M_Random is initialized!! if not this will be awfully silly!)
-	for (i = 0; i < 8; i++)
+	if (IsPlayerAdmin(playernum))
 	{
-		const char a = M_RandomKey(26*2);
-		if (a < 26) // uppercase
-			server_context[i] = 'A'+a;
-		else // lowercase
-			server_context[i] = 'a'+(a-26);
+		RemoveAdminPlayer(playernum); // don't stay admin after you're gone
 	}
+
+	LUA_InvalidatePlayer(&players[playernum]);
+
+	if (G_TagGametype()) //Check if you still have a game. Location flexible. =P
+		P_CheckSurvivors();
+	else if (gametyperules & GTR_RACE)
+		P_CheckRacers();
 }
 
 //
@@ -633,182 +630,59 @@ void D_QuitNetGame(void)
 	I_UpdateMouseGrab();
 
 	if (!netgame || !netbuffer)
-		return;
-
-	DEBFILE("===========================================================================\n"
-	        "                  Quitting Game, closing connection\n"
-	        "===========================================================================\n");
-
-	// abort send/receive of files
-	CloseNetFile();
-	RemoveAllLuaFileTransfers();
-	waitingforluafiletransfer = false;
-	waitingforluafilecommand = false;
-
-	if (server)
-	{
-		INT32 i;
-
-		netbuffer->packettype = PT_SERVERSHUTDOWN;
-		for (i = 0; i < MAXNETNODES; i++)
-			if (netnodes[i].ingame)
-				HSendPacket(i, true, 0, 0);
-#ifdef MASTERSERVER
-		if (serverrunning && ms_RoomId > 0)
-			UnregisterServer();
-#endif
-	}
-	else if (servernode > 0 && servernode < MAXNETNODES && netnodes[(UINT8)servernode].ingame)
-	{
-		netbuffer->packettype = PT_CLIENTQUIT;
-		HSendPacket(servernode, true, 0, 0);
-	}
-
-	D_CloseConnection();
-	ClearAdminPlayers();
-
-	DEBFILE("===========================================================================\n"
-	        "                         Log finish\n"
-	        "===========================================================================\n");
-#ifdef DEBUGFILE
-	if (debugfile)
-	{
-		fclose(debugfile);
-		debugfile = NULL;
-	}
-#endif
-}
-
-// Xcmd XD_ADDPLAYER
-static void Got_AddPlayer(UINT8 **p, INT32 playernum)
-{
-	INT16 node, newplayernum;
-	boolean splitscreenplayer;
-	boolean rejoined;
-	player_t *newplayer;
-
-	if (playernum != serverplayer && !IsPlayerAdmin(playernum))
-	{
-		// protect against hacked/buggy client
-		CONS_Alert(CONS_WARNING, M_GetText("Illegal add player command received from %s\n"), player_names[playernum]);
-		if (server)
-			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
-		return;
-	}
-
-	node = READUINT8(*p);
-	newplayernum = READUINT8(*p);
-	splitscreenplayer = newplayernum & 0x80;
-	newplayernum &= ~0x80;
-
-	rejoined = playeringame[newplayernum];
-
-	if (!rejoined)
-	{
-		// 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)
-			CL_ClearPlayer(newplayernum);
-		playeringame[newplayernum] = true;
-		G_AddPlayer(newplayernum);
-		if (newplayernum+1 > doomcom->numslots)
-			doomcom->numslots = (INT16)(newplayernum+1);
-
-		if (server && I_GetNodeAddress)
-		{
-			const char *address = I_GetNodeAddress(node);
-			char *port = NULL;
-			if (address) // MI: fix msvcrt.dll!_mbscat crash?
-			{
-				strcpy(playeraddress[newplayernum], address);
-				port = strchr(playeraddress[newplayernum], ':');
-				if (port)
-					*port = '\0';
-			}
-		}
-	}
-
-	newplayer = &players[newplayernum];
-
-	newplayer->jointime = 0;
-	newplayer->quittime = 0;
-
-	READSTRINGN(*p, player_names[newplayernum], MAXPLAYERNAME);
-
-	// the server is creating my player
-	if (node == mynode)
-	{
-		playernode[newplayernum] = 0; // for information only
-		if (!splitscreenplayer)
-		{
-			consoleplayer = newplayernum;
-			displayplayer = newplayernum;
-			secondarydisplayplayer = newplayernum;
-			DEBFILE("spawning me\n");
-			ticcmd_oldangleturn[0] = newplayer->oldrelangleturn;
-		}
-		else
-		{
-			secondarydisplayplayer = newplayernum;
-			DEBFILE("spawning my brother\n");
-			if (botingame)
-				newplayer->bot = 1;
-			ticcmd_oldangleturn[1] = newplayer->oldrelangleturn;
-		}
-		P_ForceLocalAngle(newplayer, (angle_t)(newplayer->angleturn << 16));
-		D_SendPlayerConfig();
-		addedtogame = true;
-
-		if (rejoined)
-		{
-			if (newplayer->mo)
-			{
-				newplayer->viewheight = 41*newplayer->height/48;
-
-				if (newplayer->mo->eflags & MFE_VERTICALFLIP)
-					newplayer->viewz = newplayer->mo->z + newplayer->mo->height - newplayer->viewheight;
-				else
-					newplayer->viewz = newplayer->mo->z + newplayer->viewheight;
-			}
-
-			// wake up the status bar
-			ST_Start();
-			// wake up the heads up text
-			HU_Start();
+		return;
 
-			if (camera.chase && !splitscreenplayer)
-				P_ResetCamera(newplayer, &camera);
-			if (camera2.chase && splitscreenplayer)
-				P_ResetCamera(newplayer, &camera2);
-		}
-	}
+	DEBFILE("===========================================================================\n"
+	        "                  Quitting Game, closing connection\n"
+	        "===========================================================================\n");
 
-	if (netgame)
+	// abort send/receive of files
+	CloseNetFile();
+	RemoveAllLuaFileTransfers();
+	waitingforluafiletransfer = false;
+	waitingforluafilecommand = false;
+
+	if (server)
 	{
-		char joinmsg[256];
+		INT32 i;
 
-		if (rejoined)
-			strcpy(joinmsg, M_GetText("\x82*%s has rejoined the game (player %d)"));
-		else
-			strcpy(joinmsg, M_GetText("\x82*%s has joined the game (player %d)"));
-		strcpy(joinmsg, va(joinmsg, player_names[newplayernum], newplayernum));
+		netbuffer->packettype = PT_SERVERSHUTDOWN;
+		for (i = 0; i < MAXNETNODES; i++)
+			if (netnodes[i].ingame)
+				HSendPacket(i, true, 0, 0);
+#ifdef MASTERSERVER
+		if (serverrunning && ms_RoomId > 0)
+			UnregisterServer();
+#endif
+	}
+	else if (servernode > 0 && servernode < MAXNETNODES && netnodes[(UINT8)servernode].ingame)
+	{
+		netbuffer->packettype = PT_CLIENTQUIT;
+		HSendPacket(servernode, true, 0, 0);
+	}
 
-		// Merge join notification + IP to avoid clogging console/chat
-		if (server && cv_showjoinaddress.value && I_GetNodeAddress)
-		{
-			const char *address = I_GetNodeAddress(node);
-			if (address)
-				strcat(joinmsg, va(" (%s)", address));
-		}
+	D_CloseConnection();
+	ClearAdminPlayers();
 
-		HU_AddChatText(joinmsg, false);
+	DEBFILE("===========================================================================\n"
+	        "                         Log finish\n"
+	        "===========================================================================\n");
+#ifdef DEBUGFILE
+	if (debugfile)
+	{
+		fclose(debugfile);
+		debugfile = NULL;
 	}
+#endif
+}
 
-	if (server && multiplayer && motd[0] != '\0')
-		COM_BufAddText(va("sayto %d %s\n", newplayernum, motd));
-
-	if (!rejoined)
-		LUA_HookInt(newplayernum, HOOK(PlayerJoin));
+void CL_HandleTimeout(void)
+{
+	LUA_HookBool(false, HOOK(GameQuit));
+	D_QuitNetGame();
+	CL_Reset();
+	D_StartTitle();
+	M_StartMessage(M_GetText("Server Timeout\n\nPress Esc\n"), NULL, MM_NOTHING);
 }
 
 void CL_AddSplitscreenPlayer(void)
@@ -825,10 +699,73 @@ void CL_RemoveSplitscreenPlayer(void)
 	SendKick(secondarydisplayplayer, KICK_MSG_PLAYER_QUIT);
 }
 
-// is there a game running
-boolean Playing(void)
+void SV_ResetServer(void)
 {
-	return (server && serverrunning) || (client && cl_mode == CL_CONNECTED);
+	INT32 i;
+
+	// +1 because this command will be executed in com_executebuffer in
+	// tryruntic so gametic will be incremented, anyway maketic > gametic
+	// is not an issue
+
+	maketic = gametic + 1;
+	neededtic = maketic;
+	tictoclear = maketic;
+
+	joindelay = 0;
+
+	for (i = 0; i < MAXNETNODES; i++)
+		ResetNode(i);
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		LUA_InvalidatePlayer(&players[i]);
+		playeringame[i] = false;
+		playernode[i] = UINT8_MAX;
+		memset(playeraddress[i], 0, sizeof(*playeraddress));
+		sprintf(player_names[i], "Player %d", i + 1);
+		adminplayers[i] = -1; // Populate the entire adminplayers array with -1.
+	}
+
+	memset(player_name_changes, 0, sizeof player_name_changes);
+
+	mynode = 0;
+	cl_packetmissed = false;
+	cl_redownloadinggamestate = false;
+
+	if (dedicated)
+	{
+		netnodes[0].ingame = true;
+		serverplayer = 0;
+	}
+	else
+		serverplayer = consoleplayer;
+
+	if (server)
+		servernode = 0;
+
+	doomcom->numslots = 0;
+
+	// clear server_context
+	memset(server_context, '-', 8);
+
+	CV_RevertNetVars();
+
+	DEBFILE("\n-=-=-=-=-=-=-= Server Reset =-=-=-=-=-=-=-\n\n");
+}
+
+static inline void SV_GenContext(void)
+{
+	UINT8 i;
+	// generate server_context, as exactly 8 bytes of randomly mixed A-Z and a-z
+	// (hopefully M_Random is initialized!! if not this will be awfully silly!)
+	for (i = 0; i < 8; i++)
+	{
+		const char a = M_RandomKey(26*2);
+		if (a < 26) // uppercase
+			server_context[i] = 'A'+a;
+		else // lowercase
+			server_context[i] = 'a'+(a-26);
+	}
 }
 
 void SV_SpawnServer(void)
@@ -860,6 +797,21 @@ void SV_SpawnServer(void)
 	}
 }
 
+// called at singleplayer start and stopdemo
+void SV_StartSinglePlayerServer(void)
+{
+	server = true;
+	netgame = false;
+	multiplayer = false;
+	G_SetGametype(GT_COOP);
+
+	// no more tic the game with this settings!
+	SV_StopServer();
+
+	if (splitscreen)
+		multiplayer = true;
+}
+
 void SV_StopServer(void)
 {
 	tic_t i;
@@ -881,21 +833,6 @@ void SV_StopServer(void)
 	serverrunning = false;
 }
 
-// called at singleplayer start and stopdemo
-void SV_StartSinglePlayerServer(void)
-{
-	server = true;
-	netgame = false;
-	multiplayer = false;
-	G_SetGametype(GT_COOP);
-
-	// no more tic the game with this settings!
-	SV_StopServer();
-
-	if (splitscreen)
-		multiplayer = true;
-}
-
 /** Called when a PT_SERVERSHUTDOWN packet is received
   *
   * \param node The packet sender (should be the server)
@@ -933,45 +870,119 @@ static void PT_Login(SINT8 node, INT32 netconsole)
 		return;
 	}
 
-	// Do the final pass to compare with the sent md5
-	D_MD5PasswordPass(adminpassmd5, 16, va("PNUM%02d", netconsole), &finalmd5);
-
-	if (!memcmp(netbuffer->u.md5sum, finalmd5, 16))
+	// Do the final pass to compare with the sent md5
+	D_MD5PasswordPass(adminpassmd5, 16, va("PNUM%02d", netconsole), &finalmd5);
+
+	if (!memcmp(netbuffer->u.md5sum, finalmd5, 16))
+	{
+		CONS_Printf(M_GetText("%s passed authentication.\n"), player_names[netconsole]);
+		COM_BufInsertText(va("promote %d\n", netconsole)); // do this immediately
+	}
+	else
+		CONS_Printf(M_GetText("Password from %s failed.\n"), player_names[netconsole]);
+#else
+	(void)netconsole;
+#endif
+}
+
+static void PT_AskLuaFile(SINT8 node)
+{
+	if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_ASKED)
+		AddLuaFileToSendQueue(node, luafiletransfers->realfilename);
+}
+
+static void PT_HasLuaFile(SINT8 node)
+{
+	if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_SENDING)
+		SV_HandleLuaFileSent(node);
+}
+
+static void PT_SendingLuaFile(SINT8 node)
+{
+	(void)node;
+
+	if (client)
+		CL_PrepareDownloadLuaFile();
+}
+
+/*
+Ping Update except better:
+We call this once per second and check for people's pings. If their ping happens to be too high, we increment some timer and kick them out.
+If they're not lagging, decrement the timer by 1. Of course, reset all of this if they leave.
+*/
+
+static inline void PingUpdate(void)
+{
+	INT32 i;
+	boolean laggers[MAXPLAYERS];
+	UINT8 numlaggers = 0;
+	memset(laggers, 0, sizeof(boolean) * MAXPLAYERS);
+
+	netbuffer->packettype = PT_PING;
+
+	//check for ping limit breakage.
+	if (cv_maxping.value)
+	{
+		for (i = 1; i < MAXPLAYERS; i++)
+		{
+			if (playeringame[i] && !players[i].quittime
+			&& (realpingtable[i] / pingmeasurecount > (unsigned)cv_maxping.value))
+			{
+				if (players[i].jointime > 30 * TICRATE)
+					laggers[i] = true;
+				numlaggers++;
+			}
+			else
+				pingtimeout[i] = 0;
+		}
+
+		//kick lagging players... unless everyone but the server's ping sucks.
+		//in that case, it is probably the server's fault.
+		if (numlaggers < D_NumPlayers() - 1)
+		{
+			for (i = 1; i < MAXPLAYERS; i++)
+			{
+				if (playeringame[i] && laggers[i])
+				{
+					pingtimeout[i]++;
+					// ok your net has been bad for too long, you deserve to die.
+					if (pingtimeout[i] > cv_pingtimeout.value)
+					{
+						pingtimeout[i] = 0;
+						SendKick(i, KICK_MSG_PING_HIGH | KICK_MSG_KEEP_BODY);
+					}
+				}
+				/*
+					you aren't lagging,
+					but you aren't free yet.
+					In case you'll keep spiking,
+					we just make the timer go back down. (Very unstable net must still get kicked).
+				*/
+				else
+					pingtimeout[i] = (pingtimeout[i] == 0 ? 0 : pingtimeout[i]-1);
+			}
+		}
+	}
+
+	//make the ping packet and clear server data for next one
+	for (i = 0; i < MAXPLAYERS; i++)
 	{
-		CONS_Printf(M_GetText("%s passed authentication.\n"), player_names[netconsole]);
-		COM_BufInsertText(va("promote %d\n", netconsole)); // do this immediately
+		netbuffer->u.pingtable[i] = realpingtable[i] / pingmeasurecount;
+		//server takes a snapshot of the real ping for display.
+		//otherwise, pings fluctuate a lot and would be odd to look at.
+		playerpingtable[i] = realpingtable[i] / pingmeasurecount;
+		realpingtable[i] = 0; //Reset each as we go.
 	}
-	else
-		CONS_Printf(M_GetText("Password from %s failed.\n"), player_names[netconsole]);
-#else
-	(void)netconsole;
-#endif
-}
-
-static void PT_ClientQuit(SINT8 node, INT32 netconsole)
-{
-	if (client)
-		return;
 
-	if (netnodes[node].ingame && netconsole != -1 && playeringame[netconsole])
-		SendKicksForNode(node, KICK_MSG_PLAYER_QUIT | KICK_MSG_KEEP_BODY);
-
-	Net_CloseConnection(node);
-	netnodes[node].ingame = false;
-	netnodes[node].player = -1;
-	netnodes[node].player2 = -1;
-}
+	// send the server's maxping as last element of our ping table. This is useful to let us know when we're about to get kicked.
+	netbuffer->u.pingtable[MAXPLAYERS] = cv_maxping.value;
 
-static void PT_AskLuaFile(SINT8 node)
-{
-	if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_ASKED)
-		AddLuaFileToSendQueue(node, luafiletransfers->realfilename);
-}
+	//send out our ping packets
+	for (i = 0; i < MAXNETNODES; i++)
+		if (netnodes[i].ingame)
+			HSendPacket(i, true, 0, sizeof(INT32) * (MAXPLAYERS+1));
 
-static void PT_HasLuaFile(SINT8 node)
-{
-	if (server && luafiletransfers && luafiletransfers->nodestatus[node] == LFTNS_SENDING)
-		SV_HandleLuaFileSent(node);
+	pingmeasurecount = 1; //Reset count
 }
 
 static void PT_Ping(SINT8 node, INT32 netconsole)
@@ -997,14 +1008,6 @@ static void PT_Ping(SINT8 node, INT32 netconsole)
 	}
 }
 
-static void PT_SendingLuaFile(SINT8 node)
-{
-	(void)node;
-
-	if (client)
-		CL_PrepareDownloadLuaFile();
-}
-
 /** Handles a packet received from a node that isn't in game
   *
   * \param node The packet sender
@@ -1124,120 +1127,6 @@ void GetPackets(void)
 	}
 }
 
-//
-// NetUpdate
-// Builds ticcmds for console player,
-// sends out a packet
-//
-// no more use random generator, because at very first tic isn't yet synchronized
-// Note: It is called consistAncy on purpose.
-//
-INT16 Consistancy(void)
-{
-	INT32 i;
-	UINT32 ret = 0;
-#ifdef MOBJCONSISTANCY
-	thinker_t *th;
-	mobj_t *mo;
-#endif
-
-	DEBFILE(va("TIC %u ", gametic));
-
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		if (!playeringame[i])
-			ret ^= 0xCCCC;
-		else if (!players[i].mo);
-		else
-		{
-			ret += players[i].mo->x;
-			ret -= players[i].mo->y;
-			ret += players[i].powers[pw_shield];
-			ret *= i+1;
-		}
-	}
-	// I give up
-	// Coop desynching enemies is painful
-	if (!G_PlatformGametype())
-		ret += P_GetRandSeed();
-
-#ifdef MOBJCONSISTANCY
-	if (gamestate == GS_LEVEL)
-	{
-		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
-		{
-			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
-				continue;
-
-			mo = (mobj_t *)th;
-
-			if (mo->flags & (MF_SPECIAL | MF_SOLID | MF_PUSHABLE | MF_BOSS | MF_MISSILE | MF_SPRING | MF_MONITOR | MF_FIRE | MF_ENEMY | MF_PAIN | MF_STICKY))
-			{
-				ret -= mo->type;
-				ret += mo->x;
-				ret -= mo->y;
-				ret += mo->z;
-				ret -= mo->momx;
-				ret += mo->momy;
-				ret -= mo->momz;
-				ret += mo->angle;
-				ret -= mo->flags;
-				ret += mo->flags2;
-				ret -= mo->eflags;
-				if (mo->target)
-				{
-					ret += mo->target->type;
-					ret -= mo->target->x;
-					ret += mo->target->y;
-					ret -= mo->target->z;
-					ret += mo->target->momx;
-					ret -= mo->target->momy;
-					ret += mo->target->momz;
-					ret -= mo->target->angle;
-					ret += mo->target->flags;
-					ret -= mo->target->flags2;
-					ret += mo->target->eflags;
-					ret -= mo->target->state - states;
-					ret += mo->target->tics;
-					ret -= mo->target->sprite;
-					ret += mo->target->frame;
-				}
-				else
-					ret ^= 0x3333;
-				if (mo->tracer && mo->tracer->type != MT_OVERLAY)
-				{
-					ret += mo->tracer->type;
-					ret -= mo->tracer->x;
-					ret += mo->tracer->y;
-					ret -= mo->tracer->z;
-					ret += mo->tracer->momx;
-					ret -= mo->tracer->momy;
-					ret += mo->tracer->momz;
-					ret -= mo->tracer->angle;
-					ret += mo->tracer->flags;
-					ret -= mo->tracer->flags2;
-					ret += mo->tracer->eflags;
-					ret -= mo->tracer->state - states;
-					ret += mo->tracer->tics;
-					ret -= mo->tracer->sprite;
-					ret += mo->tracer->frame;
-				}
-				else
-					ret ^= 0xAAAA;
-				ret -= mo->state - states;
-				ret += mo->tics;
-				ret -= mo->sprite;
-				ret += mo->frame;
-			}
-		}
-	}
-#endif
-
-	DEBFILE(va("Consistancy = %u\n", (ret & 0xFFFF)));
-
-	return (INT16)(ret & 0xFFFF);
-}
-
 boolean TryRunTics(tic_t realtics)
 {
 	// the machine has lagged but it is not so bad
@@ -1291,126 +1180,44 @@ boolean TryRunTics(tic_t realtics)
 			if (timedemo_quit)
 				COM_ImmedExecute("quit");
 			else
-				D_StartTitle();
-		}
-		else
-			// run the count * tics
-			while (neededtic > gametic)
-			{
-				boolean update_stats = !(paused || P_AutoPause());
-
-				DEBFILE(va("============ Running tic %d (local %d)\n", gametic, localgametic));
-
-				if (update_stats)
-					PS_START_TIMING(ps_tictime);
-
-				G_Ticker((gametic % NEWTICRATERATIO) == 0);
-				ExtraDataTicker();
-				gametic++;
-				consistancy[gametic%BACKUPTICS] = Consistancy();
-
-				if (update_stats)
-				{
-					PS_STOP_TIMING(ps_tictime);
-					PS_UpdateTickStats();
-				}
-
-				// Leave a certain amount of tics present in the net buffer as long as we've ran at least one tic this frame.
-				if (client && gamestate == GS_LEVEL && leveltime > 3 && neededtic <= gametic + cv_netticbuffer.value)
-					break;
-			}
-
-		return true;
-	}
-	else
-	{
-		if (realtics)
-			hu_stopped = true;
-
-		return false;
-	}
-}
-
-/*
-Ping Update except better:
-We call this once per second and check for people's pings. If their ping happens to be too high, we increment some timer and kick them out.
-If they're not lagging, decrement the timer by 1. Of course, reset all of this if they leave.
-*/
-
-static INT32 pingtimeout[MAXPLAYERS];
-
-static inline void PingUpdate(void)
-{
-	INT32 i;
-	boolean laggers[MAXPLAYERS];
-	UINT8 numlaggers = 0;
-	memset(laggers, 0, sizeof(boolean) * MAXPLAYERS);
-
-	netbuffer->packettype = PT_PING;
-
-	//check for ping limit breakage.
-	if (cv_maxping.value)
-	{
-		for (i = 1; i < MAXPLAYERS; i++)
-		{
-			if (playeringame[i] && !players[i].quittime
-			&& (realpingtable[i] / pingmeasurecount > (unsigned)cv_maxping.value))
-			{
-				if (players[i].jointime > 30 * TICRATE)
-					laggers[i] = true;
-				numlaggers++;
-			}
-			else
-				pingtimeout[i] = 0;
-		}
-
-		//kick lagging players... unless everyone but the server's ping sucks.
-		//in that case, it is probably the server's fault.
-		if (numlaggers < D_NumPlayers() - 1)
-		{
-			for (i = 1; i < MAXPLAYERS; i++)
+				D_StartTitle();
+		}
+		else
+			// run the count * tics
+			while (neededtic > gametic)
 			{
-				if (playeringame[i] && laggers[i])
+				boolean update_stats = !(paused || P_AutoPause());
+
+				DEBFILE(va("============ Running tic %d (local %d)\n", gametic, localgametic));
+
+				if (update_stats)
+					PS_START_TIMING(ps_tictime);
+
+				G_Ticker((gametic % NEWTICRATERATIO) == 0);
+				ExtraDataTicker();
+				gametic++;
+				consistancy[gametic%BACKUPTICS] = Consistancy();
+
+				if (update_stats)
 				{
-					pingtimeout[i]++;
-					// ok your net has been bad for too long, you deserve to die.
-					if (pingtimeout[i] > cv_pingtimeout.value)
-					{
-						pingtimeout[i] = 0;
-						SendKick(i, KICK_MSG_PING_HIGH | KICK_MSG_KEEP_BODY);
-					}
+					PS_STOP_TIMING(ps_tictime);
+					PS_UpdateTickStats();
 				}
-				/*
-					you aren't lagging,
-					but you aren't free yet.
-					In case you'll keep spiking,
-					we just make the timer go back down. (Very unstable net must still get kicked).
-				*/
-				else
-					pingtimeout[i] = (pingtimeout[i] == 0 ? 0 : pingtimeout[i]-1);
+
+				// Leave a certain amount of tics present in the net buffer as long as we've ran at least one tic this frame.
+				if (client && gamestate == GS_LEVEL && leveltime > 3 && neededtic <= gametic + cv_netticbuffer.value)
+					break;
 			}
-		}
-	}
 
-	//make the ping packet and clear server data for next one
-	for (i = 0; i < MAXPLAYERS; i++)
-	{
-		netbuffer->u.pingtable[i] = realpingtable[i] / pingmeasurecount;
-		//server takes a snapshot of the real ping for display.
-		//otherwise, pings fluctuate a lot and would be odd to look at.
-		playerpingtable[i] = realpingtable[i] / pingmeasurecount;
-		realpingtable[i] = 0; //Reset each as we go.
+		return true;
 	}
+	else
+	{
+		if (realtics)
+			hu_stopped = true;
 
-	// send the server's maxping as last element of our ping table. This is useful to let us know when we're about to get kicked.
-	netbuffer->u.pingtable[MAXPLAYERS] = cv_maxping.value;
-
-	//send out our ping packets
-	for (i = 0; i < MAXNETNODES; i++)
-		if (netnodes[i].ingame)
-			HSendPacket(i, true, 0, sizeof(INT32) * (MAXPLAYERS+1));
-
-	pingmeasurecount = 1; //Reset count
+		return false;
+	}
 }
 
 void NetUpdate(void)
@@ -1537,6 +1344,82 @@ void NetUpdate(void)
 	FileSendTicker();
 }
 
+// called one time at init
+void D_ClientServerInit(void)
+{
+	DEBFILE(va("- - -== SRB2 v%d.%.2d.%d "VERSIONSTRING" debugfile ==- - -\n",
+		VERSION/100, VERSION%100, SUBVERSION));
+
+	COM_AddCommand("getplayernum", Command_GetPlayerNum);
+	COM_AddCommand("kick", Command_Kick);
+	COM_AddCommand("ban", Command_Ban);
+	COM_AddCommand("banip", Command_BanIP);
+	COM_AddCommand("clearbans", Command_ClearBans);
+	COM_AddCommand("showbanlist", Command_ShowBan);
+	COM_AddCommand("reloadbans", Command_ReloadBan);
+	COM_AddCommand("connect", Command_connect);
+	COM_AddCommand("nodes", Command_Nodes);
+	COM_AddCommand("resendgamestate", Command_ResendGamestate);
+#ifdef PACKETDROP
+	COM_AddCommand("drop", Command_Drop);
+	COM_AddCommand("droprate", Command_Droprate);
+#endif
+#ifdef _DEBUG
+	COM_AddCommand("numnodes", Command_Numnodes);
+#endif
+
+	RegisterNetXCmd(XD_KICK, Got_KickCmd);
+	RegisterNetXCmd(XD_ADDPLAYER, Got_AddPlayer);
+#ifdef DUMPCONSISTENCY
+	CV_RegisterVar(&cv_dumpconsistency);
+#endif
+	Ban_Load_File(false);
+
+	gametic = 0;
+	localgametic = 0;
+
+	// do not send anything before the real begin
+	SV_StopServer();
+	SV_ResetServer();
+	if (dedicated)
+		SV_SpawnServer();
+}
+
+SINT8 nametonum(const char *name)
+{
+	INT32 playernum, i;
+
+	if (!strcmp(name, "0"))
+		return 0;
+
+	playernum = (SINT8)atoi(name);
+
+	if (playernum < 0 || playernum >= MAXPLAYERS)
+		return -1;
+
+	if (playernum)
+	{
+		if (playeringame[playernum])
+			return (SINT8)playernum;
+		else
+			return -1;
+	}
+
+	for (i = 0; i < MAXPLAYERS; i++)
+		if (playeringame[i] && !stricmp(player_names[i], name))
+			return (SINT8)i;
+
+	CONS_Printf(M_GetText("There is no player named \"%s\"\n"), name);
+
+	return -1;
+}
+
+// is there a game running
+boolean Playing(void)
+{
+	return (server && serverrunning) || (client && cl_mode == CL_CONNECTED);
+}
+
 /** Returns the number of players playing.
   * \return Number of players. Can be zero if we're running a ::dedicated
   *         server.
@@ -1551,6 +1434,120 @@ INT32 D_NumPlayers(void)
 	return num;
 }
 
+//
+// NetUpdate
+// Builds ticcmds for console player,
+// sends out a packet
+//
+// no more use random generator, because at very first tic isn't yet synchronized
+// Note: It is called consistAncy on purpose.
+//
+INT16 Consistancy(void)
+{
+	INT32 i;
+	UINT32 ret = 0;
+#ifdef MOBJCONSISTANCY
+	thinker_t *th;
+	mobj_t *mo;
+#endif
+
+	DEBFILE(va("TIC %u ", gametic));
+
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		if (!playeringame[i])
+			ret ^= 0xCCCC;
+		else if (!players[i].mo);
+		else
+		{
+			ret += players[i].mo->x;
+			ret -= players[i].mo->y;
+			ret += players[i].powers[pw_shield];
+			ret *= i+1;
+		}
+	}
+	// I give up
+	// Coop desynching enemies is painful
+	if (!G_PlatformGametype())
+		ret += P_GetRandSeed();
+
+#ifdef MOBJCONSISTANCY
+	if (gamestate == GS_LEVEL)
+	{
+		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+		{
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+				continue;
+
+			mo = (mobj_t *)th;
+
+			if (mo->flags & (MF_SPECIAL | MF_SOLID | MF_PUSHABLE | MF_BOSS | MF_MISSILE | MF_SPRING | MF_MONITOR | MF_FIRE | MF_ENEMY | MF_PAIN | MF_STICKY))
+			{
+				ret -= mo->type;
+				ret += mo->x;
+				ret -= mo->y;
+				ret += mo->z;
+				ret -= mo->momx;
+				ret += mo->momy;
+				ret -= mo->momz;
+				ret += mo->angle;
+				ret -= mo->flags;
+				ret += mo->flags2;
+				ret -= mo->eflags;
+				if (mo->target)
+				{
+					ret += mo->target->type;
+					ret -= mo->target->x;
+					ret += mo->target->y;
+					ret -= mo->target->z;
+					ret += mo->target->momx;
+					ret -= mo->target->momy;
+					ret += mo->target->momz;
+					ret -= mo->target->angle;
+					ret += mo->target->flags;
+					ret -= mo->target->flags2;
+					ret += mo->target->eflags;
+					ret -= mo->target->state - states;
+					ret += mo->target->tics;
+					ret -= mo->target->sprite;
+					ret += mo->target->frame;
+				}
+				else
+					ret ^= 0x3333;
+				if (mo->tracer && mo->tracer->type != MT_OVERLAY)
+				{
+					ret += mo->tracer->type;
+					ret -= mo->tracer->x;
+					ret += mo->tracer->y;
+					ret -= mo->tracer->z;
+					ret += mo->tracer->momx;
+					ret -= mo->tracer->momy;
+					ret += mo->tracer->momz;
+					ret -= mo->tracer->angle;
+					ret += mo->tracer->flags;
+					ret -= mo->tracer->flags2;
+					ret += mo->tracer->eflags;
+					ret -= mo->tracer->state - states;
+					ret += mo->tracer->tics;
+					ret -= mo->tracer->sprite;
+					ret += mo->tracer->frame;
+				}
+				else
+					ret ^= 0xAAAA;
+				ret -= mo->state - states;
+				ret += mo->tics;
+				ret -= mo->sprite;
+				ret += mo->frame;
+			}
+		}
+	}
+#endif
+
+	DEBFILE(va("Consistancy = %u\n", (ret & 0xFFFF)));
+
+	return (INT16)(ret & 0xFFFF);
+}
+
 tic_t GetLag(INT32 node)
 {
 	return gametic - netnodes[node].tic;