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.)