Maelstrom: Rewrote the networking system to support multiple local players and replays

https://github.com/libsdl-org/Maelstrom/commit/c0b4894e0dfc3bb9a67bb864c453f1ea9258107b

From c0b4894e0dfc3bb9a67bb864c453f1ea9258107b Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 20 Nov 2011 01:36:42 -0500
Subject: [PATCH] Rewrote the networking system to support multiple local
 players and replays

---
 game/MaelstromUI.cpp |  10 +-
 game/controls.cpp    |  95 ++++++---
 game/game.cpp        |  87 +++++----
 game/gameinfo.h      |   8 +
 game/netplay.cpp     | 456 +++++++++++++++++++++----------------------
 game/netplay.h       |   5 +-
 game/packet.h        |  33 ++--
 game/player.cpp      | 207 ++++++++++++--------
 game/player.h        |  10 +-
 9 files changed, 512 insertions(+), 399 deletions(-)

diff --git a/game/MaelstromUI.cpp b/game/MaelstromUI.cpp
index 45537be5..4dc39f9c 100644
--- a/game/MaelstromUI.cpp
+++ b/game/MaelstromUI.cpp
@@ -306,13 +306,19 @@ UIElementControlButton::HandleEvent(const SDL_Event &event)
 void
 UIElementControlButton::OnMouseDown()
 {
-	OurShip->SetControl(m_control, 1);
+	Player *player = GetControlPlayer(CONTROL_LOCAL);
+	if (player) {
+		player->SetControl(m_control, true);
+	}
 }
 
 void
 UIElementControlButton::OnMouseUp()
 {
-	OurShip->SetControl(m_control, 0);
+	Player *player = GetControlPlayer(CONTROL_LOCAL);
+	if (player) {
+		player->SetControl(m_control, false);
+	}
 }
 
 //////////////////////////////////////////////////////////////////////////////
diff --git a/game/controls.cpp b/game/controls.cpp
index b10d3a13..eff5cf58 100644
--- a/game/controls.cpp
+++ b/game/controls.cpp
@@ -234,8 +234,25 @@ ControlsDialogDelegate::SetKeycode(int index, SDL_Keycode keycode)
 	}
 }
 
+static Player *GetLocalPlayer()
+{
+	return GetControlPlayer(CONTROL_LOCAL);
+}
+
+static Player *GetKeyboardPlayer()
+{
+	return GetControlPlayer(CONTROL_KEYBOARD);
+}
+
+static Player *GetJoystickPlayer(Uint8 which)
+{
+	Uint8 joystickControl = (CONTROL_JOYSTICK1 << which);
+	return GetControlPlayer(joystickControl);
+}
+
 static void HandleEvent(SDL_Event *event)
 {
+	Player *player;
 	SDL_Keycode key;
 
 	if (ui->HandleEvent(*event)) {
@@ -247,25 +264,29 @@ static void HandleEvent(SDL_Event *event)
 		/* -- Handle joystick axis motion */
 		case SDL_JOYAXISMOTION:
 			/* X-Axis - rotate right/left */
+			player = GetJoystickPlayer(event->jaxis.which);
+			if (!player) {
+				break;
+			}
 			if ( event->jaxis.axis == 0 ) {
 				if ( event->jaxis.value < -8000 ) {
-					OurShip->SetControl(LEFT_KEY, 1);
-					OurShip->SetControl(RIGHT_KEY, 0);
+					player->SetControl(LEFT_KEY, 1);
+					player->SetControl(RIGHT_KEY, 0);
 				} else
 				if ( event->jaxis.value > 8000 ) {
-					OurShip->SetControl(RIGHT_KEY, 1);
-					OurShip->SetControl(LEFT_KEY, 0);
+					player->SetControl(RIGHT_KEY, 1);
+					player->SetControl(LEFT_KEY, 0);
 				} else {
-					OurShip->SetControl(LEFT_KEY, 0);
-					OurShip->SetControl(RIGHT_KEY, 0);
+					player->SetControl(LEFT_KEY, 0);
+					player->SetControl(RIGHT_KEY, 0);
 				}
 			} else
 			/* Y-Axis - accelerate */
 			if ( event->jaxis.axis == 1 ) {
 				if ( event->jaxis.value < -8000 ) {
-					OurShip->SetControl(THRUST_KEY, 1);
+					player->SetControl(THRUST_KEY, 1);
 				} else {
-					OurShip->SetControl(THRUST_KEY, 0);
+					player->SetControl(THRUST_KEY, 0);
 				}
 			}
 			break;
@@ -273,19 +294,23 @@ static void HandleEvent(SDL_Event *event)
 		/* -- Handle joystick button presses/releases */
 		case SDL_JOYBUTTONDOWN:
 		case SDL_JOYBUTTONUP:
+			player = GetJoystickPlayer(event->jbutton.which);
+			if (!player) {
+				break;
+			}
 			if ( event->jbutton.state == SDL_PRESSED ) {
 				if ( event->jbutton.button == 0 ) {
-					OurShip->SetControl(FIRE_KEY, 1);
+					player->SetControl(FIRE_KEY, 1);
 				} else
 				if ( event->jbutton.button == 1 ) {
-					OurShip->SetControl(SHIELD_KEY, 1);
+					player->SetControl(SHIELD_KEY, 1);
 				}
 			} else {
 				if ( event->jbutton.button == 0 ) {
-					OurShip->SetControl(FIRE_KEY, 0);
+					player->SetControl(FIRE_KEY, 0);
 				} else
 				if ( event->jbutton.button == 1 ) {
-					OurShip->SetControl(SHIELD_KEY, 0);
+					player->SetControl(SHIELD_KEY, 0);
 				}
 			}
 			break;
@@ -294,6 +319,10 @@ static void HandleEvent(SDL_Event *event)
 		/* -- Handle key presses/releases */
 		case SDL_KEYDOWN:
 			/* -- Handle ALT-ENTER hotkey */
+			player = GetKeyboardPlayer();
+			if (!player) {
+				break;
+			}
 	                if ( (event->key.keysym.sym == SDLK_RETURN) &&
 			     (event->key.keysym.mod & KMOD_ALT) ) {
 				screen->ToggleFullScreen();
@@ -301,35 +330,39 @@ static void HandleEvent(SDL_Event *event)
 			}
 		case SDL_KEYUP:
 			/* -- Handle normal key bindings */
+			player = GetKeyboardPlayer();
+			if (!player) {
+				break;
+			}
 			key = event->key.keysym.sym;
 			if ( event->key.state == SDL_PRESSED ) {
 				/* Check for various control keys */
 				if ( key == controls.gFireControl )
-					OurShip->SetControl(FIRE_KEY, 1);
+					player->SetControl(FIRE_KEY, 1);
 				else if ( key == controls.gTurnRControl )
-					OurShip->SetControl(RIGHT_KEY, 1);
+					player->SetControl(RIGHT_KEY, 1);
 				else if ( key == controls.gTurnLControl )
-					OurShip->SetControl(LEFT_KEY, 1);
+					player->SetControl(LEFT_KEY, 1);
 				else if ( key == controls.gShieldControl )
-					OurShip->SetControl(SHIELD_KEY, 1);
+					player->SetControl(SHIELD_KEY, 1);
 				else if ( key == controls.gThrustControl )
-					OurShip->SetControl(THRUST_KEY, 1);
+					player->SetControl(THRUST_KEY, 1);
 				else if ( key == controls.gPauseControl )
-					OurShip->SetControl(PAUSE_KEY, 1);
+					player->SetControl(PAUSE_KEY, 1);
 				else if ( key == controls.gQuitControl )
-					OurShip->SetControl(ABORT_KEY, 1);
+					player->SetControl(ABORT_KEY, 1);
 			} else {
 				/* Update control key status */
 				if ( key == controls.gFireControl )
-					OurShip->SetControl(FIRE_KEY, 0);
+					player->SetControl(FIRE_KEY, 0);
 				else if ( key == controls.gTurnRControl )
-					OurShip->SetControl(RIGHT_KEY, 0);
+					player->SetControl(RIGHT_KEY, 0);
 				else if ( key == controls.gTurnLControl )
-					OurShip->SetControl(LEFT_KEY, 0);
+					player->SetControl(LEFT_KEY, 0);
 				else if ( key == controls.gShieldControl )
-					OurShip->SetControl(SHIELD_KEY, 0);
+					player->SetControl(SHIELD_KEY, 0);
 				else if ( key == controls.gThrustControl )
-					OurShip->SetControl(THRUST_KEY, 0);
+					player->SetControl(THRUST_KEY, 0);
 				else if ( key == SDLK_F1 ) {
 					/* Special key --
 						Switch displayed player
@@ -346,15 +379,23 @@ static void HandleEvent(SDL_Event *event)
 			break;
 
 		case SDL_WINDOWEVENT:
+			player = GetLocalPlayer();
+			if (!player) {
+				break;
+			}
 			if (event->window.event == SDL_WINDOWEVENT_MINIMIZED) {
-				OurShip->SetControl(MINIMIZE_KEY, 1);
+				player->SetControl(MINIMIZE_KEY, 1);
 			} if (event->window.event == SDL_WINDOWEVENT_RESTORED) {
-				OurShip->SetControl(MINIMIZE_KEY, 0);
+				player->SetControl(MINIMIZE_KEY, 0);
 			}
 			break;
 
 		case SDL_QUIT:
-			OurShip->SetControl(ABORT_KEY, 1);
+			player = GetLocalPlayer();
+			if (!player) {
+				break;
+			}
+			player->SetControl(ABORT_KEY, 1);
 			break;
 	}
 }
diff --git a/game/game.cpp b/game/game.cpp
index 797a565c..6c1a6b50 100644
--- a/game/game.cpp
+++ b/game/game.cpp
@@ -59,30 +59,49 @@ int	gWhenEnemy;
 static void DoGameOver(void);
 
 /* ----------------------------------------------------------------- */
-/* -- Start a new game */
+/* -- Setup the players for a new game */
 
-void NewGame(void)
+static bool SetupPlayers(void)
 {
-	/* Make sure we have a valid player list */
 	if ( CheckPlayers() < 0 )
-		return;
+		return false;
+
+	/* Set up the controls for the game */
+	gDisplayed = -1;
+	for (int i = 0; i < MAX_PLAYERS; ++i) {
+		if (gGameInfo.IsValidPlayer(i)) {
+			if (gGameInfo.IsLocalPlayer(i)) {
+				if (gDisplayed < 0) {
+					gDisplayed = i;
+				}
+			}
+			gPlayers[i]->SetControlType(gGameInfo.GetPlayer(i)->controlMask);
+		} else {
+			gPlayers[i]->SetControlType(CONTROL_NONE);
+		}
+	}
+	return true;
+}
 
+/* ----------------------------------------------------------------- */
+/* -- Start a new game */
+
+void NewGame(void)
+{
 	/* Start up the random number generator */
 	SeedRandom(gGameInfo.seed);
 
+	/* Make sure we have a valid player list */
+	if ( !SetupPlayers() ) {
+		return;
+	}
+
 	/* Send a "NEW_GAME" packet onto the network */
 	if ( gGameInfo.IsMultiplayer() && gGameInfo.IsHosting() ) {
 		if ( Send_NewGame() < 0) {
 			return;
 		}
 	}
-	for (int i = 0; i < MAX_PLAYERS; ++i) {
-		if (gGameInfo.IsValidPlayer(i)) {
-			gPlayers[i]->SetControlType(gGameInfo.GetPlayer(i)->controlMask);
-		} else {
-			gPlayers[i]->SetControlType(CONTROL_NONE);
-		}
-	}
 
 	ui->ShowPanel(PANEL_GAME);
 #ifdef USE_TOUCHCONTROL
@@ -407,8 +426,7 @@ GamePanelDelegate::DrawStatus(Bool first)
 	if ( gGameInfo.IsMultiplayer() ) {
 		char caption[BUFSIZ];
 
-		sprintf(caption, "You are player %d --- displaying player %d",
-					gOurPlayer+1, gDisplayed+1);
+		sprintf(caption, "Displaying player %d", gDisplayed+1);
 		if (m_multiplayerCaption) {
 			m_multiplayerCaption->SetText(caption);
 		}
@@ -497,7 +515,7 @@ GamePanelDelegate::DrawStatus(Bool first)
 		if ((Score - lastLife[i]) >= NEW_LIFE) {
 			gPlayers[i]->IncrLives(1);
 			lastLife[i] = (Score / NEW_LIFE) * NEW_LIFE;
-			if ( i == gOurPlayer )
+			if ( gGameInfo.IsLocalPlayer(i) )
 				sound->PlaySound(gNewLife, 5);
 		}
 	}
@@ -650,24 +668,24 @@ GamePanelDelegate::DoBonus()
 		if (!gPlayers[i]->IsValid()) {
 			continue;
 		}
-		if ( i != gOurPlayer ) {
+		if (i != gDisplayed) {
 			gPlayers[i]->MultBonus();
 			continue;
 		}
 
-		if (OurShip->GetBonusMult() != 1) {
+		if (TheShip->GetBonusMult() != 1) {
 			if (bonus) {
-				sprintf(numbuf, "%-5.1d", OurShip->GetBonus());
+				sprintf(numbuf, "%-5.1d", TheShip->GetBonus());
 				bonus->SetText(numbuf);
 				bonus->Show();
 			}
 			bonus = panel->GetElement<UIElement>("multiplied_bonus");
 
-			OurShip->MultBonus();
+			TheShip->MultBonus();
 			Delay(SOUND_DELAY);
 			sound->PlaySound(gMultiplier, 5);
 
-			sprintf(numbuf, "multiplier%d", OurShip->GetBonusMult());
+			sprintf(numbuf, "multiplier%d", TheShip->GetBonusMult());
 			image = panel->GetElement<UIElement>(numbuf);
 			if (image) {
 				image->Show();
@@ -681,12 +699,12 @@ GamePanelDelegate::DoBonus()
 	sound->PlaySound(gFunk, 5);
 
 	if (bonus) {
-		sprintf(numbuf, "%-5.1d", OurShip->GetBonus());
+		sprintf(numbuf, "%-5.1d", TheShip->GetBonus());
 		bonus->SetText(numbuf);
 		bonus->Show();
 	}
 	if (score) {
-		sprintf(numbuf, "%-5.1d", OurShip->GetScore());
+		sprintf(numbuf, "%-5.1d", TheShip->GetScore());
 		score->SetText(numbuf);
 		score->Show();
 	}
@@ -694,11 +712,11 @@ GamePanelDelegate::DoBonus()
 	Delay(60);
 
 	/* -- Praise them or taunt them as the case may be */
-	if (OurShip->GetBonus() == 0) {
+	if (TheShip->GetBonus() == 0) {
 		Delay(SOUND_DELAY);
 		sound->PlaySound(gNoBonus, 5);
 	}
-	if (OurShip->GetBonus() > 10000) {
+	if (TheShip->GetBonus() > 10000) {
 		Delay(SOUND_DELAY);
 		sound->PlaySound(gPrettyGood, 5);
 	}
@@ -710,7 +728,7 @@ GamePanelDelegate::DoBonus()
 		if (!gPlayers[i]->IsValid()) {
 			continue;
 		}
-		if ( i != gOurPlayer ) {
+		if (i != gDisplayed) {
 			while ( gPlayers[i]->GetBonus() > 500 ) {
 				gPlayers[i]->IncrScore(500);
 				gPlayers[i]->IncrBonus(-500);
@@ -718,25 +736,25 @@ GamePanelDelegate::DoBonus()
 			continue;
 		}
 
-		while (OurShip->GetBonus() > 0) {
+		while (TheShip->GetBonus() > 0) {
 			while ( sound->Playing() )
 				Delay(SOUND_DELAY);
 
 			sound->PlaySound(gBonk, 5);
-			if ( OurShip->GetBonus() >= 500 ) {
-				OurShip->IncrScore(500);
-				OurShip->IncrBonus(-500);
+			if ( TheShip->GetBonus() >= 500 ) {
+				TheShip->IncrScore(500);
+				TheShip->IncrBonus(-500);
 			} else {
-				OurShip->IncrScore(OurShip->GetBonus());
-				OurShip->IncrBonus(-OurShip->GetBonus());
+				TheShip->IncrScore(TheShip->GetBonus());
+				TheShip->IncrBonus(-TheShip->GetBonus());
 			}
 	
 			if (bonus) {
-				sprintf(numbuf, "%-5.1d", OurShip->GetBonus());
+				sprintf(numbuf, "%-5.1d", TheShip->GetBonus());
 				bonus->SetText(numbuf);
 			}
 			if (score) {
-				sprintf(numbuf, "%-5.1d", OurShip->GetScore());
+				sprintf(numbuf, "%-5.1d", TheShip->GetScore());
 				score->SetText(numbuf);
 			}
 
@@ -780,7 +798,6 @@ GamePanelDelegate::NextWave()
 	gEnemySprite = NULL;
 
 	/* -- Initialize some variables */
-	gDisplayed = gOurPlayer;
 	gNumRocks = 0;
 	gShakeTime = 0;
 	gFreezeTime = 0;
@@ -970,7 +987,7 @@ static void DoGameOver(void)
 		Delay(SOUND_DELAY);
 
 	/* -- See if they got a high score */
-	gScore = OurShip->GetScore();
+	gScore = TheShip->GetScore();
 	LoadScores();
 	for ( i = 0; i<10; ++i ) {
 		if ( gScore > (int)hScores[i].score ) {
@@ -1054,7 +1071,7 @@ static void DoGameOver(void)
 				strcpy(hScores[i+1].name, hScores[i].name);
 			}
 			hScores[which].wave = gWave;
-			hScores[which].score = OurShip->GetScore();
+			hScores[which].score = TheShip->GetScore();
 			strcpy(hScores[which].name, handle);
 		}
 
diff --git a/game/gameinfo.h b/game/gameinfo.h
index ae2b9ded..51d08cd9 100644
--- a/game/gameinfo.h
+++ b/game/gameinfo.h
@@ -132,6 +132,14 @@ class GameInfo
 		}
 		return NULL;
 	}
+	int GetNodeIndex(Uint32 nodeID) const {
+		for (int i = 0; i < GetNumNodes(); ++i) {
+			if (nodeID == nodes[i].nodeID) {
+				return i;
+			}
+		}
+		return -1;
+	}
 
 	bool HasNode(Uint32 nodeID) const;
 	bool HasNode(const IPaddress &address) const;
diff --git a/game/netplay.cpp b/game/netplay.cpp
index 3d3d9af4..02b11cc4 100644
--- a/game/netplay.cpp
+++ b/game/netplay.cpp
@@ -31,42 +31,31 @@
 #include "netplay.h"
 #include "protocol.h"
 
+// Set this to 1 for normal debug info, and 2 for verbose packet logging
+//#define DEBUG_NETWORK 1
+
+// Define this to simulate packet loss
+//#define DEBUG_PACKETLOSS 10
+
 
-int   gNumPlayers;
-int   gOurPlayer;
 UDPsocket gNetFD;
 
-static IPaddress      PlayAddr[MAX_PLAYERS];
-static Uint32         NextFrame;
-UDPpacket            *OutBound[2];
-static int            CurrOut;
-/* This is the data offset of a SYNC packet */
-#define PDATA_OFFSET	(1+1+sizeof(Uint32)+sizeof(Uint32))
+static SDLNet_SocketSet SocketSet;
+static Uint32           NextFrame;
 
 /* We keep one packet backlogged for retransmission */
-#define OutBuf		OutBound[CurrOut]->data
-#define OutLen		OutBound[CurrOut]->len
-#define LastBuf		OutBound[!CurrOut]->data
-#define LastLen		OutBound[!CurrOut]->len
-
-static unsigned char *SyncPtrs[2][MAX_PLAYERS];
-static unsigned char  SyncBufs[2][MAX_PLAYERS][BUFSIZ];
-static int            SyncLens[2][MAX_PLAYERS];
-static int            ThisSyncs[2];
-static int            CurrIn;
-static SDLNet_SocketSet SocketSet;
+static DynamicPacket OutBound[2];
+static int           CurrOut;
+#define CurrPacket	OutBound[CurrOut]
+#define LastPacket	OutBound[!CurrOut]
 
 /* We cache one packet if the other player is ahead of us */
-#define SyncPtr		SyncPtrs[CurrIn]
-#define SyncBuf		SyncBufs[CurrIn]
-#define SyncLen		SyncLens[CurrIn]
-#define ThisSync	ThisSyncs[CurrIn]
-#define NextPtr		SyncPtrs[!CurrIn]
-#define NextBuf		SyncBufs[!CurrIn]
-#define NextLen		SyncLens[!CurrIn]
-#define NextSync	ThisSyncs[!CurrIn]
+static DynamicPacket Packet;
+static DynamicPacket CachedPacket[MAX_NODES];
 
-#define TOGGLE(var)	var = !var
+/* When we're done we have our input for the frame */
+static DynamicPacket QueuedInput;
+static DynamicPacket FrameInput;
 
 
 int InitNetData(bool hosting)
@@ -98,32 +87,21 @@ int InitNetData(bool hosting)
 	}
 	SDLNet_UDP_AddSocket(SocketSet, gNetFD);
 
-	/* Create the outbound packets */
-	for ( i=0; i<2; ++i ) {
-		OutBound[i] = SDLNet_AllocPacket(BUFSIZ);
-		if ( OutBound[i] == NULL ) {
-			error("Out of memory (creating network buffers)\n");
-			return(-1);
-		}
-	}
+#ifdef DEBUG_PACKETLOSS
+	SDLNet_UDP_SetPacketLoss(gNetFD, DEBUG_PACKETLOSS);
+#endif
 
 	/* Initialize network game variables */
-	gOurPlayer  = -1;
 	NextFrame = 0;
-	for ( i=0; i<MAX_PLAYERS; ++i ) {
-		SyncPtrs[0][i] = NULL;
-		SyncPtrs[1][i] = NULL;
-	}
-	OutBound[0]->data[0] = SYNC_MSG;
-	OutBound[1]->data[0] = SYNC_MSG;
-	/* Type field, frame sequence, current random seed */
-	OutBound[0]->len = PDATA_OFFSET;
-	OutBound[1]->len = PDATA_OFFSET;
+	OutBound[0].Reset();
+	OutBound[1].Reset();
 	CurrOut = 0;
+	for (i = 0; i < MAX_NODES; ++i) {
+		CachedPacket[i].Reset();
+	}
+	QueuedInput.Reset();
+	FrameInput.Reset();
 
-	ThisSyncs[0] = 0;
-	ThisSyncs[1] = 0;
-	CurrIn = 0;
 	return(0);
 }
 
@@ -142,7 +120,6 @@ void HaltNetData(void)
 	SDLNet_Quit();
 }
 
-/* This MUST be called after command line options have been processed. */
 int CheckPlayers(void)
 {
 	int i;
@@ -152,111 +129,92 @@ int CheckPlayers(void)
 		return(-1);
 	}
 
-	gNumPlayers = gGameInfo.GetNumPlayers();
-	gOurPlayer  = -1;
-	for ( i=0; i<MAX_PLAYERS; ++i ) {
-		if (gGameInfo.IsValidPlayer(i)) {
-			if (gGameInfo.IsLocalPlayer(i)) {
-				if (gOurPlayer < 0) {
-					gOurPlayer = i;
-				}
-			} else {
-				PlayAddr[i] = gGameInfo.GetPlayerAddress(i);
-			}
+	bool foundLocalPlayer = false;
+	for (i = 0; i < MAX_PLAYERS; ++i) {
+		if (gGameInfo.IsLocalPlayer(i)) {
+			foundLocalPlayer = true;
+			break;
 		}
 	}
-	if (gOurPlayer < 0) {
+	if (!foundLocalPlayer) {
 		error("Which player are you?\r\n");
 		return(-1);
 	}
 
-	/* Now, so we can send to ourselves... */
-	PlayAddr[gOurPlayer] = *SDLNet_UDP_GetPeerAddress(gNetFD, -1);
-	if ( ! PlayAddr[gOurPlayer].host ) {
-		SDLNet_ResolveHost(&PlayAddr[gOurPlayer], "127.0.0.1", SDL_SwapBE16(PlayAddr[gOurPlayer].port));
+	/* Bind all of our network nodes to the broadcast channel */
+	for (i = 0; i < MAX_NODES; ++i) {
+		if (gGameInfo.IsNetworkNode(i)) {
+			const GameInfoNode *node = gGameInfo.GetNode(i);
+			SDLNet_UDP_Bind(gNetFD, 0, &node->address);
+			SDLNet_UDP_Bind(gNetFD, 1+i, &node->address);
+		}
 	}
 
-	/* Bind all of our players to the channels */
-	for ( i=0; i<gNumPlayers; ++i ) {
-		SDLNet_UDP_Bind(gNetFD, 0, &PlayAddr[i]);
-		SDLNet_UDP_Bind(gNetFD, i+1, &PlayAddr[i]);
-	}
 	return(0);
 }
 
-void QueueKey(unsigned char Op, unsigned char Type)
+void QueueInput(Uint8 value)
 {
-	/* Drop keys on a full buffer (assumed never to really happen) */
-	if ( OutLen >= (BUFSIZ-2) )
-		return;
-
-//error("Queued key 0x%.2x for frame %d\r\n", Type, NextFrame);
-	OutBuf[OutLen++] = Op;
-	OutBuf[OutLen++] = Type;
+	QueuedInput.Write(value);
 }
 
-/* This function is called every frame, and is used to flush the network
-   buffers, sending sync and keystroke packets.
-   It is called AFTER the keyboard is polled, and BEFORE GetSyncBuf() is
-   called by the player objects.
-
-   Note:  We assume that FastRand() isn't called by an interrupt routine,
-          otherwise we lose consistency.
-*/
-	
-int SyncNetwork(void)
+static bool ProcessSync(int index, DynamicPacket &packet)
 {
-	UDPpacket sent;
-	Uint32 seed, frame;
-	unsigned char buf[BUFSIZ];
-	int index, nleft;
-	int timeout;
+	Uint32 seed;
 
-	/* Set the next inbound packet buffer */
-	TOGGLE(CurrIn);
+	if (!packet.Read(seed) || seed != GetRandSeed()) {
+		/* We're hosed, to correct this we would have to sync the complete game state */
+		error("Error!! \a Frame consistency problem, aborting!!\r\n");
+		return false;
+	}
 
-	/* Set the frame number */
-	frame = NextFrame;
-	SDLNet_Write32(frame, &OutBuf[1]);
-	seed = GetRandSeed();
-	SDLNet_Write32(seed, &OutBuf[1+sizeof(frame)]);
+	// FIXME: Should we validate that the input is for players from this node?
+	//        ... nah... :)
+	FrameInput.Write(packet);
 
-	/* Send the packet to all the players */
-	SDLNet_UDP_Send(gNetFD, 0, OutBound[CurrOut]);
-	for ( nleft=0, index=0; index<gNumPlayers; ++index ) {
-		if ( SyncPtr[index] == NULL ) {
-			++nleft;
+	return true;
+}
+
+static int AwaitSync(int nleft, Uint32 waiting[MAX_NODES])
+{
+	int i;
+	int timeout;
+	Uint32 frame;
+
+	// See if we have cached network packets
+	for (i = 0; i < MAX_NODES; ++i) {
+		if (CachedPacket[i].len > 0 && waiting[i]) {
+			if (!ProcessSync(i, CachedPacket[i])) {
+				return -1;
+			}
+			waiting[i] = 0;
+			--nleft;
 		}
+		CachedPacket[i].Reset();
 	}
-	NextSync = 0;
-
-	/* Get the inbound packet ready for data */
-	sent.data = buf;
-	sent.maxlen = sizeof(buf);
 
 	/* Wait for Ack's */
 	timeout = 0;
 	while ( nleft ) {
 		int ready = SDLNet_CheckSockets(SocketSet, 100);
-		if ( ready < 0 ) {
-			error("Network error: SDLNet_CheckSockets()");
+		if (ready < 0) {
+			error("Network error: SDLNet_CheckSockets()\r\n");
 			return(-1);
 		}
-		if ( ready == 0 ) {
-			error("Timed out waiting for frame %ld\r\n", NextFrame);
-
+		if (ready == 0) {
+#if DEBUG_NETWORK >= 1
+error("Timed out waiting for frame %ld\r\n", NextFrame);
+#endif
 			/* Timeout, resend the sync packet */
-			for ( index=0; index<gNumPlayers; ++index ) {
-				if ( SyncPtr[index] == NULL ) {
-					SDLNet_UDP_Send(gNetFD, index+1, OutBound[CurrOut]);
+			for (i = 0; i < MAX_NODES; ++i) {
+				if (waiting[i]) {
+					SDLNet_UDP_Send(gNetFD, i+1, &CurrPacket);
 				}
 			}
 
 			/* Don't wait forever */
 			++timeout;
 			if ( timeout == (PING_TIMEOUT/100) ) {
-				// Reset our current input state
-				TOGGLE(CurrIn);
 				return(-1);
 			}
 		}
@@ -265,142 +223,173 @@ int SyncNetwork(void)
 		}
 
 		/* We are guaranteed that there is data here */
-		if ( SDLNet_UDP_Recv(gNetFD, &sent) <= 0 ) {
-			error("Network error: SDLNet_UDP_Recv()");
+		Packet.Reset();
+		if ( SDLNet_UDP_Recv(gNetFD, &Packet) <= 0 ) {
+			error("Network error: SDLNet_UDP_Recv()\r\n");
 			return(-1);
 		}
-//error("Received packet!\r\n");
 
 		/* We have a packet! */
-		if ( buf[0] == LOBBY_MSG ) {
+		Uint8 cmd;
+		if (!Packet.Read(cmd)) {
+			error("Received short packet\r\n");
 			continue;
 		}
-		if ( buf[0] == NEW_GAME ) {
-			/* FIXME: Convert this to the DynamicPacket */
-			buf[0] = NEW_GAME_ACK;
-			SDLNet_Write32(gGameInfo.gameID, &buf[1]);
-			SDLNet_Write32(gGameInfo.localID, &buf[4]);
-			sent.len = 9;
-			SDLNet_UDP_Send(gNetFD, -1, &sent);
-//error("NEW_GAME packet!\r\n");
+		if (cmd == LOBBY_MSG ) {
+#if DEBUG_NETWORK >= 2
+error("LOBBY_MSG packet!\r\n");
+#endif
 			continue;
 		}
-		if ( buf[0] != SYNC_MSG ) {
-			error("Unknown packet: 0x%x\n", buf[0]);
+		if (cmd == NEW_GAME ) {
+#if DEBUG_NETWORK >= 1
+error("NEW_GAME packet!\r\n");
+#endif
+			Packet.Reset();
+			Packet.Write((Uint8)NEW_GAME_ACK);
+			Packet.Write(gGameInfo.gameID);
+			Packet.Write(gGameInfo.localID);
+			SDLNet_UDP_Send(gNetFD, -1, &Packet);
 			continue;
 		}
-		if ( sent.channel <= 0 ) {
-			error("Packet from unknown source\n");
+		if (cmd != SYNC_MSG) {
+			error("Unknown packet: 0x%x\r\n", cmd);
 			continue;
 		}
-		index = sent.channel - 1;
 
-		/* Ignore it if it is a duplicate packet */
-		if ( SyncPtr[index] != NULL ) {
+		Uint32 gameID;
+		Uint32 nodeID;
+		if (!Packet.Read(gameID) || !Packet.Read(nodeID)) {
+			error("Received short packet\r\n");
+			continue;
+		}
+		if (gameID != gGameInfo.gameID) {
+			/* This must be for a different game */
+			continue;
+		}
+		int index = gGameInfo.GetNodeIndex(nodeID);
+		if (index < 0) {
+			error("Warning: packet from unknown source\r\n");
 			continue;
 		}
 
 		/* Check the frame number */
-		frame = SDLNet_Read32(&buf[1]);
-#ifdef DEBUG_NETWORK
-//error("Received a packet of frame %lu from player %d\r\n", frame, index+1);
+		if (!Packet.Read(frame)) {
+			error("Received short packet\r\n");
+			continue;
+		}
+#if DEBUG_NETWORK >= 2
+error("Received a packet of frame %lu from player %d\r\n", frame, index+1);
+#endif
+		if (frame == NextFrame) {
+			/* Ignore it if it is a duplicate packet */
+			if (!waiting[index]) {
+#if DEBUG_NETWORK >= 1
+error("Ignoring duplicate packet for frame %lu from player %d\r\n", frame, index+1);
 #endif
-		if ( frame != NextFrame ) {
+				continue;
+			}
+
+			/* Do a consistency check!! */
+			if (!ProcessSync(index, Packet)) {
+				return -1;
+			}
+			waiting[index] = 0;
+			--nleft;
+		} else if (frame == (NextFrame-1)) {
 			/* We kept the last frame cached, so send it */
-			if ( frame == (NextFrame-1) ) {
-#ifdef DEBUG_NETWORK
+#if DEBUG_NETWORK >= 1
 error("Transmitting packet for old frame (%lu)\r\n", frame);
 #endif
-				SDLNet_UDP_Send(gNetFD, sent.channel, OutBound[!CurrOut]);
-			} else if ( frame == (NextFrame+1) ) {
-#ifdef DEBUG_NETWORK
+			LastPacket.address = Packet.address;
+			SDLNet_UDP_Send(gNetFD, -1, &LastPacket);
+		} else if (frame == (NextFrame+1)) {
+#if DEBUG_NETWORK >= 1
 error("Received packet for next frame! (%lu, current = %lu)\r\n",
-						frame, NextFrame);
+					frame, NextFrame);
 #endif
-				/* Send this player our current frame */
-				SDLNet_UDP_Send(gNetFD, sent.channel, OutBound[CurrOut]);
-				/* Cache this frame for next round,
-				   skip consistency check, for now */
-				memcpy(NextBuf[NextSync], &buf[PDATA_OFFSET], sent.len-PDATA_OFFSET);
-				NextPtr[index] = NextBuf[NextSync];
-				NextLen[index] = sent.len-PDATA_OFFSET;
-				++NextSync;
-			}
-#ifdef DEBUG_NETWORK
+			/* Send this player our current frame */
+			CurrPacket.address = Packet.address;
+			SDLNet_UDP_Send(gNetFD, -1, &CurrPacket);
+
+			/* Cache this frame for next round */
+			CachedPacket[index].Reset();
+			CachedPacket[index].Write(Packet);
+			CachedPacket[index].Seek(0);
+		}
+#if DEBUG_NETWORK >= 1
 else
 error("Warning! Received packet for really old frame! (%lu, current = %lu)\r\n",
 							frame, NextFrame);
 #endif
-			/* Go to select, reset timeout */
-			continue;
-		}
+	}
+	return 0;
+}
 
-		/* Do a consistency check!! */
-		Uint32 newseed = SDLNet_Read32(&buf[1+sizeof(frame)]);
-		if ( newseed != seed ) {
-			/* We're hosed, to correct this we would have to sync the complete game state */
-			error(
-"Error!! \a Frame consistency error with player %d!!\r\n", index+1);
-			return(-1);
-		}
+/* This function is called every frame, and is used to flush the network
+   buffers, sending sync and keystroke packets.
+   It is called AFTER the keyboard is polled, and BEFORE GetSyncBuf() is
+   called by the player objects.
 
-		/* Okay, we finally have a valid timely packet */
-		memcpy(SyncBuf[ThisSync], &buf[PDATA_OFFSET], sent.len-PDATA_OFFSET);
-		SyncPtr[index] = SyncBuf[ThisSync];
-		SyncLen[index] = sent.len-PDATA_OFFSET;
-		++ThisSync;
-		--nleft;
-	}
+   Note:  We assume that FastRand() isn't called by an interrupt routine,
+          otherwise we lose consistency.
+*/
+	
+int SyncNetwork(void)
+{
+	int i;
+	int nleft;
+	Uint32 waiting[MAX_NODES];
 
-	/* Set the next outbound packet buffer */
 	++NextFrame;
-	TOGGLE(CurrOut);
-	OutLen = PDATA_OFFSET;
 
-	return(0);
-}
+	// Get the queued input
+	FrameInput.Reset();
+	QueuedInput.Seek(0);
+	FrameInput.Write(QueuedInput);
 
-/* This function retrieves a particular player's network buffer */
-int GetSyncBuf(int index, unsigned char **bufptr)
-{
-	int retlen;
-
-	*bufptr = SyncPtr[index];
-	SyncPtr[index] = NULL;
-	retlen = SyncLen[index];
-	SyncLen[index] = 0;
-#ifdef SERIOUS_DEBUG
-if ( retlen > 0 ) {
-	for ( int i=1; i<retlen; i+=2 ) {
-		error(
-"Keystroke (key = 0x%.2x) for player %d on frame %d!\r\n",
-					(*bufptr)[i], index+1, NextFrame);
+	// See if we need to do network synchronization
+	nleft = 0;
+	for (i = 0; i < gGameInfo.GetNumNodes(); ++i) {
+		if (gGameInfo.IsNetworkNode(i)) {
+			++nleft;
+			waiting[i] = gGameInfo.GetNode(i)->nodeID;
+		} else {
+			waiting[i] = 0;
+		}
 	}
-}
-#endif
-	return(retlen);
-}
-
-inline void SuckPackets(void)
-{
-	UDPpacket sent;
-	unsigned char buf[BUFSIZ];
+	if (nleft > 0) {
+		// Create the sync packet
+		CurrPacket.Reset();
+		CurrPacket.Write((Uint8)SYNC_MSG);
+		CurrPacket.Write(gGameInfo.gameID);
+		CurrPacket.Write(gGameInfo.localID);
+		CurrPacket.Write(NextFrame);
+		CurrPacket.Write(GetRandSeed());
+		QueuedInput.Seek(0);
+		CurrPacket.Write(QueuedInput);
+
+		// Send the packet to all the players
+		SDLNet_UDP_Send(gNetFD, 0, &CurrPacket);
+
+		// Wait for sync packets from them
+		if (AwaitSync(nleft, waiting) < 0) {
+			return -1;
+		}
 
-	sent.data = buf;
-	sent.maxlen = sizeof(buf);
-	while ( SDLNet_UDP_Recv(gNetFD, &sent) ) {
-		/* Keep sucking */ ;
+		CurrOut = !CurrOut;
 	}
+
+	QueuedInput.Reset();
+
+	return(0);
 }
 
-/* Flash an error up on the screen and pause for 3 seconds */
-static void ErrorMessage(const char *message)
+/* This function retrieves the input for the frame */
+int GetSyncBuf(Uint8 **bufptr)
 {
-	/* Display the error message */
-	Message(message);
-
-	/* Wait exactly (almost) 3 seconds */
-	SDL_Delay(3000);
+	*bufptr = FrameInput.data;
+	return FrameInput.len;
 }
 
 /* This function sends a NEW_GAME packet, and waits for all other players
@@ -412,7 +401,7 @@ int Send_NewGame()
 	int  nleft;
 	Uint32 waiting[MAX_NODES];
 	int  i, j;
-	DynamicPacket newgame, packet;
+	DynamicPacket newgame;
 
 	/* Send all the packets */
 	newgame.Write((Uint8)NEW_GAME);
@@ -445,19 +434,14 @@ int Send_NewGame()
 		}
 		Message(message);
 
-		if ( SDLNet_CheckSockets(SocketSet, 100) <= 0 ) {
-			HandleEvents(0);
-			/* Peek at key buffer for Quit key */
-			for ( i=(PDATA_OFFSET+1); i<OutLen; i += 2 ) {
-				if ( OutBuf[i] == ABORT_KEY ) {
-					OutLen = PDATA_OFFSET;
-					return(-1);
-				}
-			}
-			OutLen = PDATA_OFFSET;
-
+		int ready = SDLNet_CheckSockets(SocketSet, 100);
+		if (ready < 0) {
+			error("Network error: SDLNet_CheckSockets()\r\n");
+			return(-1);
+		}
+		if (ready == 0) {
 			for (i = 0; i < MAX_NODES; ++i) {
-				if ( waiting[i] ) {
+				if (waiting[i]) {
 					SDLNet_UDP_Send(gNetFD, i+1, &newgame);
 				}
 			}
@@ -465,9 +449,9 @@ int Send_NewGame()
 		}
 
 		/* We are guaranteed that there is data here */
-		packet.Reset();
-		if ( SDLNet_UDP_Recv(gNetFD, &packet) <= 0 ) {
-			ErrorMessage("Network error receiving packets");
+		Packet.Reset();
+		if ( SDLNet_UDP_Recv(gNetFD, &Packet) <= 0 ) {
+			error("Network error: SDLNet_UDP_Recv()\r\n");
 			return(-1);
 		}
 
@@ -475,11 +459,11 @@ int Send_NewGame()
 		Uint8 cmd;
 		Uint32 gameID;
 		Uint32 nodeID;
-		if (!packet.Read(cmd) || cmd != NEW_GAME_ACK) {
+		if (!Packet.Read(cmd) || cmd != NEW_GAME_ACK) {
 			/* Continue waiting */
 			continue;
 		}
-		if (!packet.Read(gameID) || !packet.Read(nodeID)) {
+		if (!Packet.Read(gameID) || !Packet.Read(nodeID)) {
 			continue;
 		}
 		if (gameID != gGameInfo.gameID) {
diff --git a/game/netplay.h b/game/netplay.h
index 3e891d6a..9ff5ca51 100644
--- a/game/netplay.h
+++ b/game/netplay.h
@@ -26,11 +26,10 @@
 extern int   InitNetData(bool hosting);
 extern void  HaltNetData(void);
 extern int   CheckPlayers(void);
-extern void  QueueKey(unsigned char Op, unsigned char Type);
+extern void  QueueInput(Uint8 value);
 extern int   SyncNetwork(void);
-extern int   GetSyncBuf(int index, unsigned char **bufptr);
+extern int   GetSyncBuf(Uint8 **bufptr);
 extern int   Send_NewGame();
 
 /* Variables from netplay.cpp */
-extern int	gOurPlayer;
 extern UDPsocket gNetFD;
diff --git a/game/packet.h b/game/packet.h
index 30ce17de..1c27e08b 100644
--- a/game/packet.h
+++ b/game/packet.h
@@ -89,14 +89,21 @@ class DynamicPacket : public UDPpacket
 			value = "";
 		}
 
-		size_t len = SDL_strlen(value);
-		if (len > 255) {
-			len = 255;
+		size_t amount = SDL_strlen(value);
+		if (amount > 255) {
+			amount = 255;
 		}
-		Write((Uint8)len);
-		Grow(len);
-		SDL_memcpy(&data[pos], value, len);
-		pos += len;
+		Write((Uint8)amount);
+		Grow(amount);
+		SDL_memcpy(&data[pos], value, amount);
+		pos += amount;
+	}
+	void Write(DynamicPacket &packet) {
+		size_t amount = packet.len - packet.pos;
+		Grow(amount);
+		SDL_memcpy(&data[pos], &packet.data[packet.pos], amount);
+		packet.pos += amount;
+		pos += amount;
 	}
 
 	bool Read(Uint8 &value) {
@@ -123,16 +130,16 @@ cl

(Patch may be truncated, please check the link at the top of this post.)