summaryrefslogtreecommitdiff
path: root/src/uqm/supermelee
diff options
context:
space:
mode:
Diffstat (limited to 'src/uqm/supermelee')
-rw-r--r--src/uqm/supermelee/Makeinfo5
-rw-r--r--src/uqm/supermelee/buildpick.c221
-rw-r--r--src/uqm/supermelee/buildpick.h25
-rw-r--r--src/uqm/supermelee/loadmele.c826
-rw-r--r--src/uqm/supermelee/loadmele.h67
-rw-r--r--src/uqm/supermelee/melee.c2640
-rw-r--r--src/uqm/supermelee/melee.h144
-rw-r--r--src/uqm/supermelee/meleesetup.c440
-rw-r--r--src/uqm/supermelee/meleesetup.h143
-rw-r--r--src/uqm/supermelee/meleeship.h55
-rw-r--r--src/uqm/supermelee/netplay/FILES50
-rw-r--r--src/uqm/supermelee/netplay/Makeinfo4
-rw-r--r--src/uqm/supermelee/netplay/checkbuf.c145
-rw-r--r--src/uqm/supermelee/netplay/checkbuf.h77
-rw-r--r--src/uqm/supermelee/netplay/checksum.c302
-rw-r--r--src/uqm/supermelee/netplay/checksum.h99
-rw-r--r--src/uqm/supermelee/netplay/crc.c142
-rw-r--r--src/uqm/supermelee/netplay/crc.h60
-rw-r--r--src/uqm/supermelee/netplay/nc_connect.ci300
-rw-r--r--src/uqm/supermelee/netplay/netconnection.c378
-rw-r--r--src/uqm/supermelee/netplay/netconnection.h260
-rw-r--r--src/uqm/supermelee/netplay/netinput.c157
-rw-r--r--src/uqm/supermelee/netplay/netinput.h57
-rw-r--r--src/uqm/supermelee/netplay/netmelee.c740
-rw-r--r--src/uqm/supermelee/netplay/netmelee.h90
-rw-r--r--src/uqm/supermelee/netplay/netmisc.c134
-rw-r--r--src/uqm/supermelee/netplay/netmisc.h77
-rw-r--r--src/uqm/supermelee/netplay/netoptions.c39
-rw-r--r--src/uqm/supermelee/netplay/netoptions.h56
-rw-r--r--src/uqm/supermelee/netplay/netplay.h77
-rw-r--r--src/uqm/supermelee/netplay/netrcv.c193
-rw-r--r--src/uqm/supermelee/netplay/netrcv.h34
-rw-r--r--src/uqm/supermelee/netplay/netsend.c95
-rw-r--r--src/uqm/supermelee/netplay/netsend.h35
-rw-r--r--src/uqm/supermelee/netplay/netstate.c48
-rw-r--r--src/uqm/supermelee/netplay/netstate.h83
-rw-r--r--src/uqm/supermelee/netplay/notify.c118
-rw-r--r--src/uqm/supermelee/netplay/notify.h62
-rw-r--r--src/uqm/supermelee/netplay/notifyall.c146
-rw-r--r--src/uqm/supermelee/netplay/notifyall.h49
-rw-r--r--src/uqm/supermelee/netplay/packet.c263
-rw-r--r--src/uqm/supermelee/netplay/packet.h299
-rw-r--r--src/uqm/supermelee/netplay/packethandlers.c649
-rw-r--r--src/uqm/supermelee/netplay/packethandlers.h56
-rw-r--r--src/uqm/supermelee/netplay/packetq.c149
-rw-r--r--src/uqm/supermelee/netplay/packetq.h59
-rw-r--r--src/uqm/supermelee/netplay/packetsenders.c197
-rw-r--r--src/uqm/supermelee/netplay/packetsenders.h67
-rw-r--r--src/uqm/supermelee/netplay/proto/Makeinfo2
-rw-r--r--src/uqm/supermelee/netplay/proto/npconfirm.c81
-rw-r--r--src/uqm/supermelee/netplay/proto/npconfirm.h36
-rw-r--r--src/uqm/supermelee/netplay/proto/ready.c106
-rw-r--r--src/uqm/supermelee/netplay/proto/ready.h38
-rw-r--r--src/uqm/supermelee/netplay/proto/reset.c166
-rw-r--r--src/uqm/supermelee/netplay/proto/reset.h41
-rw-r--r--src/uqm/supermelee/pickmele.c948
-rw-r--r--src/uqm/supermelee/pickmele.h102
57 files changed, 11932 insertions, 0 deletions
diff --git a/src/uqm/supermelee/Makeinfo b/src/uqm/supermelee/Makeinfo
new file mode 100644
index 0000000..897a1fe
--- /dev/null
+++ b/src/uqm/supermelee/Makeinfo
@@ -0,0 +1,5 @@
+uqm_CFILES="buildpick.c loadmele.c melee.c meleesetup.c pickmele.c"
+uqm_HFILES="buildpick.h loadmele.h melee.h meleesetup.h meleeship.h pickmele.h"
+if [ -n "$uqm_NETPLAY" ]; then
+ uqm_SUBDIRS="$uqm_SUBDIRS netplay"
+fi
diff --git a/src/uqm/supermelee/buildpick.c b/src/uqm/supermelee/buildpick.c
new file mode 100644
index 0000000..3f4731b
--- /dev/null
+++ b/src/uqm/supermelee/buildpick.c
@@ -0,0 +1,221 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "buildpick.h"
+
+#include "../controls.h"
+#include "../colors.h"
+#include "../fmv.h"
+#include "../master.h"
+#include "../setup.h"
+#include "../sounds.h"
+#include "libs/gfxlib.h"
+
+static FRAME BuildPickFrame;
+
+void
+BuildBuildPickFrame (void)
+{
+ STAMP s;
+ RECT r;
+ COUNT i;
+ CONTEXT OldContext = SetContext (OffScreenContext);
+
+ // create team building ship selection box
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = SetAbsFrameIndex (MeleeFrame, 27);
+ // 5x5 grid of ships to pick from
+ GetFrameRect (s.frame, &r);
+
+ BuildPickFrame = CaptureDrawable (CreateDrawable (
+ WANT_PIXMAP, r.extent.width, r.extent.height, 1));
+ SetContextFGFrame (BuildPickFrame);
+ SetFrameHot (s.frame, MAKE_HOT_SPOT (0, 0));
+ DrawStamp (&s);
+
+ for (i = 0; i < NUM_PICK_COLS * NUM_PICK_ROWS; ++i)
+ DrawPickIcon (i, true);
+
+ SetContext (OldContext);
+}
+
+void
+DestroyBuildPickFrame (void)
+{
+ DestroyDrawable (ReleaseDrawable (BuildPickFrame));
+ BuildPickFrame = 0;
+}
+
+// Draw a ship icon in the ship selection popup.
+void
+DrawPickIcon (MeleeShip ship, bool DrawErase)
+{
+ STAMP s;
+ RECT r;
+
+ GetFrameRect (BuildPickFrame, &r);
+
+ s.origin.x = r.corner.x + 20 + (ship % NUM_PICK_COLS) * 18;
+ s.origin.y = r.corner.y + 5 + (ship / NUM_PICK_COLS) * 18;
+ s.frame = GetShipIconsFromIndex (ship);
+ if (DrawErase)
+ { // draw icon
+ DrawStamp (&s);
+ }
+ else
+ { // erase icon
+ Color OldColor;
+
+ OldColor = SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledStamp (&s);
+ SetContextForeGroundColor (OldColor);
+ }
+}
+
+void
+DrawPickFrame (MELEE_STATE *pMS)
+{
+ RECT r, r0, r1, ship_r;
+ STAMP s;
+
+ GetShipBox (&r0, 0, 0, 0),
+ GetShipBox (&r1, 1, NUM_MELEE_ROWS - 1, NUM_MELEE_COLUMNS - 1),
+ BoxUnion (&r0, &r1, &ship_r);
+
+ s.frame = SetAbsFrameIndex (BuildPickFrame, 0);
+ GetFrameRect (s.frame, &r);
+ r.corner.x = -(ship_r.corner.x
+ + ((ship_r.extent.width - r.extent.width) >> 1));
+ if (pMS->side)
+ r.corner.y = -ship_r.corner.y;
+ else
+ r.corner.y = -(ship_r.corner.y
+ + (ship_r.extent.height - r.extent.height));
+ SetFrameHot (s.frame, MAKE_HOT_SPOT (r.corner.x, r.corner.y));
+ s.origin.x = 0;
+ s.origin.y = 0;
+ DrawStamp (&s);
+ DrawMeleeShipStrings (pMS, pMS->currentShip);
+}
+
+void
+GetBuildPickFrameRect (RECT *r)
+{
+ GetFrameRect (BuildPickFrame, r);
+}
+
+static BOOLEAN
+DoPickShip (MELEE_STATE *pMS)
+{
+ DWORD TimeIn = GetTimeCounter ();
+
+ /* Cancel any presses of the Pause key. */
+ GamePaused = FALSE;
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ {
+ pMS->buildPickConfirmed = false;
+ return FALSE;
+ }
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+
+ if (PulsedInputState.menu[KEY_MENU_SELECT] ||
+ PulsedInputState.menu[KEY_MENU_CANCEL])
+ {
+ // Confirm selection or cancel.
+ pMS->buildPickConfirmed = !PulsedInputState.menu[KEY_MENU_CANCEL];
+ return FALSE;
+ }
+
+ if (PulsedInputState.menu[KEY_MENU_SPECIAL]
+ && (pMS->currentShip != MELEE_NONE))
+ {
+ // Show ship spin video.
+ DoShipSpin (pMS->currentShip, (MUSIC_REF) 0);
+ return TRUE;
+ }
+
+ {
+ MeleeShip newSelectedShip;
+
+ newSelectedShip = pMS->currentShip;
+
+ if (PulsedInputState.menu[KEY_MENU_LEFT])
+ {
+ if (newSelectedShip % NUM_PICK_COLS == 0)
+ newSelectedShip += NUM_PICK_COLS;
+ --newSelectedShip;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_RIGHT])
+ {
+ ++newSelectedShip;
+ if (newSelectedShip % NUM_PICK_COLS == 0)
+ newSelectedShip -= NUM_PICK_COLS;
+ }
+
+ if (PulsedInputState.menu[KEY_MENU_UP])
+ {
+ if (newSelectedShip >= NUM_PICK_COLS)
+ newSelectedShip -= NUM_PICK_COLS;
+ else
+ newSelectedShip += NUM_PICK_COLS * (NUM_PICK_ROWS - 1);
+ }
+ else if (PulsedInputState.menu[KEY_MENU_DOWN])
+ {
+ if (newSelectedShip < NUM_PICK_COLS * (NUM_PICK_ROWS - 1))
+ newSelectedShip += NUM_PICK_COLS;
+ else
+ newSelectedShip -= NUM_PICK_COLS * (NUM_PICK_ROWS - 1);
+ }
+
+ if (newSelectedShip != pMS->currentShip)
+ {
+ // A new ship has been selected.
+ DrawPickIcon (pMS->currentShip, true);
+ pMS->currentShip = newSelectedShip;
+ DrawMeleeShipStrings (pMS, newSelectedShip);
+ }
+ }
+
+ Melee_flashSelection (pMS);
+
+ SleepThreadUntil (TimeIn + ONE_SECOND / 30);
+
+ return TRUE;
+}
+
+// Returns true if a ship has been selected, or false if the operation has
+// been cancelled or if the general abort key was pressed (in which case
+// 'GLOBAL (CurrentActivity) & CHECK_ABORT' is true as usual.
+// If a ship was selected, pMS->currentShip is set to the selected ship.
+bool
+BuildPickShip (MELEE_STATE *pMS)
+{
+ FlushInput ();
+
+ if (pMS->currentShip == MELEE_NONE)
+ pMS->currentShip = 0;
+
+ DrawPickFrame (pMS);
+
+ pMS->InputFunc = DoPickShip;
+ DoInput (pMS, FALSE);
+
+ return pMS->buildPickConfirmed;
+}
+
diff --git a/src/uqm/supermelee/buildpick.h b/src/uqm/supermelee/buildpick.h
new file mode 100644
index 0000000..43608e7
--- /dev/null
+++ b/src/uqm/supermelee/buildpick.h
@@ -0,0 +1,25 @@
+#ifndef BUILDPICK_H
+#define BUILDPICK_H
+
+#include "types.h"
+#include "melee.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+void BuildBuildPickFrame (void);
+void DestroyBuildPickFrame (void);
+bool BuildPickShip (MELEE_STATE *pMS);
+void GetBuildPickFrameRect (RECT *r);
+
+void DrawPickFrame (MELEE_STATE *pMS);
+void DrawPickIcon (MeleeShip ship, bool DrawErase);
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* BUILDPICK_H */
+
diff --git a/src/uqm/supermelee/loadmele.c b/src/uqm/supermelee/loadmele.c
new file mode 100644
index 0000000..d5917c3
--- /dev/null
+++ b/src/uqm/supermelee/loadmele.c
@@ -0,0 +1,826 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+// This file handles loading of teams, but the UI and the actual loading.
+
+#define MELEESETUP_INTERNAL
+#include "melee.h"
+
+#include "../controls.h"
+#include "../gameopt.h"
+#include "../gamestr.h"
+#include "../globdata.h"
+#include "../master.h"
+#include "meleesetup.h"
+#include "../save.h"
+#include "../setup.h"
+#include "../sounds.h"
+#include "options.h"
+#include "libs/log.h"
+#include "libs/memlib.h"
+
+
+#define LOAD_TEAM_NAME_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0F, 0x10, 0x1B), 0x00)
+#define LOAD_TEAM_NAME_TEXT_COLOR_HILITE \
+ BUILD_COLOR (MAKE_RGB15 (0x17, 0x18, 0x1D), 0x00)
+
+
+#define LOAD_MELEE_BOX_WIDTH 34
+#define LOAD_MELEE_BOX_HEIGHT 34
+#define LOAD_MELEE_BOX_SPACE 1
+
+
+static void DrawFileStrings (MELEE_STATE *pMS);
+static bool FillFileView (MELEE_STATE *pMS);
+
+
+static bool
+LoadTeamImage (DIRENTRY DirEntry, MeleeTeam *team)
+{
+ const char *fileName;
+ uio_Stream *stream;
+
+ fileName = GetDirEntryAddress (DirEntry);
+
+ stream = uio_fopen (meleeDir, fileName, "rb");
+ if (stream == NULL)
+ return false;
+
+ if (MeleeTeam_deserialize (team, stream) == -1)
+ return false;
+
+ uio_fclose (stream);
+
+ return true;
+}
+
+#if 0 /* Not used */
+static void
+UnindexFleet (MELEE_STATE *pMS, COUNT index)
+{
+ assert (index < pMS->load.numIndices);
+ pMS->load.numIndices--;
+ memmove (&pMS->load.entryIndices[index],
+ &pMS->load.entryIndices[index + 1],
+ (pMS->load.numIndices - index) * sizeof pMS->load.entryIndices[0]);
+}
+#endif
+
+static void
+UnindexFleets (MELEE_STATE *pMS, COUNT index, COUNT count)
+{
+ assert (index + count <= pMS->load.numIndices);
+
+ pMS->load.numIndices -= count;
+ memmove (&pMS->load.entryIndices[index],
+ &pMS->load.entryIndices[index + count],
+ (pMS->load.numIndices - index) * sizeof pMS->load.entryIndices[0]);
+}
+
+static bool
+GetFleetByIndex (MELEE_STATE *pMS, COUNT index, MeleeTeam *result)
+{
+ COUNT firstIndex;
+
+ if (index < pMS->load.preBuiltCount)
+ {
+ MeleeTeam_copy (result, pMS->load.preBuiltList[index]);
+ return true;
+ }
+
+ index -= pMS->load.preBuiltCount;
+ firstIndex = index;
+
+ for ( ; index < pMS->load.numIndices; index++)
+ {
+ DIRENTRY entry = SetAbsDirEntryTableIndex (pMS->load.dirEntries,
+ pMS->load.entryIndices[index]);
+ if (LoadTeamImage (entry, result))
+ break; // Success
+
+ {
+ const char *fileName;
+ fileName = GetDirEntryAddress (entry);
+ log_add (log_Warning, "Warning: File '%s' is not a valid "
+ "SuperMelee team.", fileName);
+ }
+ }
+
+ if (index != firstIndex)
+ UnindexFleets (pMS, firstIndex, index - firstIndex);
+
+ return index < pMS->load.numIndices;
+}
+
+// returns (COUNT) -1 if not found
+static COUNT
+GetFleetIndexByFileName (MELEE_STATE *pMS, const char *fileName)
+{
+ COUNT index;
+
+ for (index = 0; index < pMS->load.numIndices; index++)
+ {
+ DIRENTRY entry = SetAbsDirEntryTableIndex (pMS->load.dirEntries,
+ pMS->load.entryIndices[index]);
+ const char *entryName = GetDirEntryAddress (entry);
+
+ if (strcasecmp ((const char *) entryName, fileName) == 0)
+ return pMS->load.preBuiltCount + index;
+ }
+
+ return (COUNT) -1;
+}
+
+// Auxiliary function for DrawFileStrings
+// If drawShips is set the ships themselves are drawn, in addition to the
+// fleet name and value; if not, only the fleet name and value are drawn.
+// If highlite is set the text is drawn in the color used for highlighting.
+static void
+DrawFileString (const MeleeTeam *team, const POINT *origin,
+ BOOLEAN drawShips, BOOLEAN highlite)
+{
+ SetContextForeGroundColor (highlite ?
+ LOAD_TEAM_NAME_TEXT_COLOR_HILITE : LOAD_TEAM_NAME_TEXT_COLOR);
+
+ // Print the name of the fleet
+ {
+ TEXT Text;
+
+ Text.baseline = *origin;
+ Text.align = ALIGN_LEFT;
+ Text.pStr = MeleeTeam_getTeamName(team);
+ Text.CharCount = (COUNT)~0;
+ font_DrawText (&Text);
+ }
+
+ // Print the value of the fleet
+ {
+ TEXT Text;
+ UNICODE buf[60];
+
+ sprintf (buf, "%u", MeleeTeam_getValue (team));
+ Text.baseline = *origin;
+ Text.baseline.x += NUM_MELEE_COLUMNS *
+ (LOAD_MELEE_BOX_WIDTH + LOAD_MELEE_BOX_SPACE) - 1;
+ Text.align = ALIGN_RIGHT;
+ Text.pStr = buf;
+ Text.CharCount = (COUNT)~0;
+ font_DrawText (&Text);
+ }
+
+ // Draw the ships for the fleet
+ if (drawShips)
+ {
+ STAMP s;
+ FleetShipIndex slotI;
+
+ s.origin.x = origin->x + 1;
+ s.origin.y = origin->y + 4;
+ for (slotI = 0; slotI < MELEE_FLEET_SIZE; slotI++)
+ {
+ BYTE StarShip;
+
+ StarShip = team->ships[slotI];
+ if (StarShip != MELEE_NONE)
+ {
+ s.frame = GetShipIconsFromIndex (StarShip);
+ DrawStamp (&s);
+ s.origin.x += 17;
+ }
+ }
+ }
+}
+
+// returns true if there are any entries in the view, in which case
+// pMS->load.bot gets set to the index just past the bottom entry in the view.
+// returns false if not, in which case, the entire view remains unchanged.
+static bool
+FillFileView (MELEE_STATE *pMS)
+{
+ COUNT viewI;
+
+ for (viewI = 0; viewI < LOAD_TEAM_VIEW_SIZE; viewI++)
+ {
+ bool success = GetFleetByIndex (pMS, pMS->load.top + viewI,
+ pMS->load.view[viewI]);
+ if (!success)
+ break;
+ }
+
+ if (viewI == 0)
+ return false;
+
+ pMS->load.bot = pMS->load.top + viewI;
+ return true;
+}
+
+#define FILE_STRING_ORIGIN_X 5
+#define FILE_STRING_ORIGIN_Y 34
+#define ENTRY_HEIGHT 32
+
+static void
+SelectFileString (MELEE_STATE *pMS, bool hilite)
+{
+ CONTEXT OldContext;
+ POINT origin;
+ COUNT viewI;
+
+ viewI = pMS->load.cur - pMS->load.top;
+
+ OldContext = SetContext (SpaceContext);
+ SetContextFont (MicroFont);
+ BatchGraphics ();
+
+ origin.x = FILE_STRING_ORIGIN_X;
+ origin.y = FILE_STRING_ORIGIN_Y + viewI * ENTRY_HEIGHT;
+ DrawFileString (pMS->load.view[viewI], &origin, FALSE, hilite);
+
+ UnbatchGraphics ();
+ SetContext (OldContext);
+}
+
+static void
+DrawFileStrings (MELEE_STATE *pMS)
+{
+ POINT origin;
+ CONTEXT OldContext;
+
+ origin.x = FILE_STRING_ORIGIN_X;
+ origin.y = FILE_STRING_ORIGIN_Y;
+
+ OldContext = SetContext (SpaceContext);
+ SetContextFont (MicroFont);
+ BatchGraphics ();
+
+ DrawMeleeIcon (28); /* The load team frame */
+
+ if (FillFileView (pMS))
+ {
+ COUNT i;
+ for (i = pMS->load.top; i < pMS->load.bot; i++) {
+ DrawFileString (pMS->load.view[i - pMS->load.top], &origin,
+ TRUE, FALSE);
+ origin.y += ENTRY_HEIGHT;
+ }
+ }
+
+ UnbatchGraphics ();
+ SetContext (OldContext);
+}
+
+static void
+RefocusView (MELEE_STATE *pMS, COUNT index)
+{
+ assert (index < pMS->load.preBuiltCount + pMS->load.numIndices);
+
+ pMS->load.cur = index;
+ if (index <= LOAD_TEAM_VIEW_SIZE / 2)
+ pMS->load.top = 0;
+ else
+ pMS->load.top = index - LOAD_TEAM_VIEW_SIZE / 2;
+}
+
+static void
+flashSelectedTeam (MELEE_STATE *pMS)
+{
+#define FLASH_RATE (ONE_SECOND / 9)
+ static TimeCount NextTime = 0;
+ static int hilite = 0;
+ TimeCount Now = GetTimeCounter ();
+
+ if (Now >= NextTime)
+ {
+ CONTEXT OldContext;
+
+ NextTime = Now + FLASH_RATE;
+ hilite ^= 1;
+
+ OldContext = SetContext (SpaceContext);
+ SelectFileString (pMS, hilite);
+ SetContext (OldContext);
+ }
+}
+
+BOOLEAN
+DoLoadTeam (MELEE_STATE *pMS)
+{
+ DWORD TimeIn = GetTimeCounter ();
+
+ /* Cancel any presses of the Pause key. */
+ GamePaused = FALSE;
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ SetMenuSounds (MENU_SOUND_UP | MENU_SOUND_DOWN | MENU_SOUND_PAGEUP |
+ MENU_SOUND_PAGEDOWN, MENU_SOUND_SELECT);
+
+ if (!pMS->Initialized)
+ {
+ DrawFileStrings (pMS);
+ SelectFileString (pMS, true);
+ pMS->Initialized = TRUE;
+ pMS->InputFunc = DoLoadTeam;
+ return TRUE;
+ }
+
+ if (PulsedInputState.menu[KEY_MENU_SELECT] ||
+ PulsedInputState.menu[KEY_MENU_CANCEL])
+ {
+ if (PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ // Copy the selected fleet to the player.
+ Melee_LocalChange_team (pMS, pMS->side,
+ pMS->load.view[pMS->load.cur - pMS->load.top]);
+ }
+
+ pMS->InputFunc = DoMelee;
+ pMS->LastInputTime = GetTimeCounter ();
+ {
+ RECT r;
+
+ GetFrameRect (SetAbsFrameIndex (MeleeFrame, 28), &r);
+ RepairMeleeFrame (&r);
+ }
+ return TRUE;
+ }
+
+ {
+ COUNT newTop = pMS->load.top;
+ COUNT newIndex = pMS->load.cur;
+
+ if (PulsedInputState.menu[KEY_MENU_UP])
+ {
+ if (newIndex > 0)
+ {
+ newIndex--;
+ if (newIndex < newTop)
+ newTop = (newTop < LOAD_TEAM_VIEW_SIZE) ?
+ 0 : newTop - LOAD_TEAM_VIEW_SIZE;
+ }
+ }
+ else if (PulsedInputState.menu[KEY_MENU_DOWN])
+ {
+ COUNT numEntries = pMS->load.numIndices + pMS->load.preBuiltCount;
+ if (newIndex + 1 < numEntries)
+ {
+ newIndex++;
+ if (newIndex >= pMS->load.bot)
+ newTop = pMS->load.bot;
+ }
+ }
+ else if (PulsedInputState.menu[KEY_MENU_PAGE_UP])
+ {
+ newIndex = (newIndex < LOAD_TEAM_VIEW_SIZE) ?
+ 0 : newIndex - LOAD_TEAM_VIEW_SIZE;
+ newTop = (newTop < LOAD_TEAM_VIEW_SIZE) ?
+ 0 : newTop - LOAD_TEAM_VIEW_SIZE;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_PAGE_DOWN])
+ {
+ COUNT numEntries = pMS->load.numIndices + pMS->load.preBuiltCount;
+ if (newIndex + LOAD_TEAM_VIEW_SIZE < numEntries)
+ {
+ newIndex += LOAD_TEAM_VIEW_SIZE;
+ newTop += LOAD_TEAM_VIEW_SIZE;
+ }
+ else
+ {
+ newIndex = numEntries - 1;
+ if (newTop + LOAD_TEAM_VIEW_SIZE < numEntries &&
+ numEntries > LOAD_TEAM_VIEW_SIZE)
+ newTop = numEntries - LOAD_TEAM_VIEW_SIZE;
+ }
+ }
+
+ if (newIndex != pMS->load.cur)
+ {
+ // The cursor has been moved.
+ if (newTop == pMS->load.top)
+ {
+ // The view itself hasn't changed.
+ SelectFileString (pMS, false);
+ }
+ else
+ {
+ // The view is changed.
+ pMS->load.top = newTop;
+ DrawFileStrings (pMS);
+ }
+ pMS->load.cur = newIndex;
+ }
+ }
+
+ flashSelectedTeam (pMS);
+
+ SleepThreadUntil (TimeIn + ONE_SECOND / 30);
+
+ return TRUE;
+}
+
+static void
+SelectTeamByFileName (MELEE_STATE *pMS, const char *fileName)
+{
+ COUNT index = GetFleetIndexByFileName (pMS, fileName);
+ if (index == (COUNT) -1)
+ return;
+
+ RefocusView (pMS, index);
+}
+
+void
+LoadTeamList (MELEE_STATE *pMS)
+{
+ COUNT i;
+
+ DestroyDirEntryTable (ReleaseDirEntryTable (pMS->load.dirEntries));
+ pMS->load.dirEntries = CaptureDirEntryTable (
+ LoadDirEntryTable (meleeDir, "", ".mle", match_MATCH_SUFFIX));
+
+ if (pMS->load.entryIndices != NULL)
+ HFree (pMS->load.entryIndices);
+ pMS->load.numIndices = GetDirEntryTableCount (pMS->load.dirEntries);
+ pMS->load.entryIndices = HMalloc (pMS->load.numIndices *
+ sizeof pMS->load.entryIndices[0]);
+ for (i = 0; i < pMS->load.numIndices; i++)
+ pMS->load.entryIndices[i] = i;
+}
+
+BOOLEAN
+DoSaveTeam (MELEE_STATE *pMS)
+{
+ STAMP MsgStamp;
+ char file[NAME_MAX];
+ uio_Stream *stream;
+ CONTEXT OldContext;
+ bool saveOk = false;
+
+ snprintf (file, sizeof file, "%s.mle",
+ MeleeSetup_getTeamName (pMS->meleeSetup, pMS->side));
+
+ OldContext = SetContext (ScreenContext);
+ ConfirmSaveLoad (&MsgStamp);
+ // Show the "Saving . . ." message.
+
+ stream = uio_fopen (meleeDir, file, "wb");
+ if (stream != NULL)
+ {
+ saveOk = (MeleeTeam_serialize (&pMS->meleeSetup->teams[pMS->side],
+ stream) == 0);
+ uio_fclose (stream);
+
+ if (!saveOk)
+ uio_unlink (meleeDir, file);
+ }
+
+ pMS->load.top = 0;
+ pMS->load.cur = 0;
+
+ // Undo the screen damage done by the "Saving . . ." message.
+ DrawStamp (&MsgStamp);
+ DestroyDrawable (ReleaseDrawable (MsgStamp.frame));
+ SetContext (OldContext);
+
+ if (!saveOk)
+ SaveProblem ();
+
+ // Update the team list; a previously existing team may have been
+ // deleted when save failed.
+ LoadTeamList (pMS);
+ SelectTeamByFileName (pMS, file);
+
+ return (stream != 0);
+}
+
+static void
+InitPreBuilt (MELEE_STATE *pMS)
+{
+ MeleeTeam **list;
+
+#define PREBUILT_COUNT 15
+ pMS->load.preBuiltList =
+ HMalloc (PREBUILT_COUNT * sizeof (MeleeTeam *));
+ pMS->load.preBuiltCount = PREBUILT_COUNT;
+#undef PREBUILT_COUNT
+
+ {
+ size_t fleetI;
+
+ for (fleetI = 0; fleetI < pMS->load.preBuiltCount; fleetI++)
+ pMS->load.preBuiltList[fleetI] = MeleeTeam_new ();
+ }
+
+ list = pMS->load.preBuiltList;
+
+ {
+ /* "Balanced Team 1" */
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, GAME_STRING (MELEE_STRING_BASE + 4));
+ MeleeTeam_setShip (*list, i++, MELEE_ANDROSYNTH);
+ MeleeTeam_setShip (*list, i++, MELEE_CHMMR);
+ MeleeTeam_setShip (*list, i++, MELEE_DRUUGE);
+ MeleeTeam_setShip (*list, i++, MELEE_URQUAN);
+ MeleeTeam_setShip (*list, i++, MELEE_MELNORME);
+ MeleeTeam_setShip (*list, i++, MELEE_ORZ);
+ MeleeTeam_setShip (*list, i++, MELEE_SPATHI);
+ MeleeTeam_setShip (*list, i++, MELEE_SYREEN);
+ MeleeTeam_setShip (*list, i++, MELEE_UTWIG);
+ list++;
+ }
+
+ {
+ /* "Balanced Team 2" */
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, GAME_STRING (MELEE_STRING_BASE + 5));
+ MeleeTeam_setShip (*list, i++, MELEE_ARILOU);
+ MeleeTeam_setShip (*list, i++, MELEE_CHENJESU);
+ MeleeTeam_setShip (*list, i++, MELEE_EARTHLING);
+ MeleeTeam_setShip (*list, i++, MELEE_KOHR_AH);
+ MeleeTeam_setShip (*list, i++, MELEE_MYCON);
+ MeleeTeam_setShip (*list, i++, MELEE_YEHAT);
+ MeleeTeam_setShip (*list, i++, MELEE_PKUNK);
+ MeleeTeam_setShip (*list, i++, MELEE_SUPOX);
+ MeleeTeam_setShip (*list, i++, MELEE_THRADDASH);
+ MeleeTeam_setShip (*list, i++, MELEE_ZOQFOTPIK);
+ MeleeTeam_setShip (*list, i++, MELEE_SHOFIXTI);
+ list++;
+ }
+
+ {
+ /* "200 points" */
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, GAME_STRING (MELEE_STRING_BASE + 6));
+ MeleeTeam_setShip (*list, i++, MELEE_ANDROSYNTH);
+ MeleeTeam_setShip (*list, i++, MELEE_CHMMR);
+ MeleeTeam_setShip (*list, i++, MELEE_DRUUGE);
+ MeleeTeam_setShip (*list, i++, MELEE_MELNORME);
+ MeleeTeam_setShip (*list, i++, MELEE_EARTHLING);
+ MeleeTeam_setShip (*list, i++, MELEE_KOHR_AH);
+ MeleeTeam_setShip (*list, i++, MELEE_SUPOX);
+ MeleeTeam_setShip (*list, i++, MELEE_ORZ);
+ MeleeTeam_setShip (*list, i++, MELEE_SPATHI);
+ MeleeTeam_setShip (*list, i++, MELEE_ILWRATH);
+ MeleeTeam_setShip (*list, i++, MELEE_VUX);
+ list++;
+ }
+
+ {
+ /* "Behemoth Zenith" */
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, GAME_STRING (MELEE_STRING_BASE + 7));
+ MeleeTeam_setShip (*list, i++, MELEE_CHENJESU);
+ MeleeTeam_setShip (*list, i++, MELEE_CHENJESU);
+ MeleeTeam_setShip (*list, i++, MELEE_CHMMR);
+ MeleeTeam_setShip (*list, i++, MELEE_CHMMR);
+ MeleeTeam_setShip (*list, i++, MELEE_KOHR_AH);
+ MeleeTeam_setShip (*list, i++, MELEE_KOHR_AH);
+ MeleeTeam_setShip (*list, i++, MELEE_URQUAN);
+ MeleeTeam_setShip (*list, i++, MELEE_URQUAN);
+ MeleeTeam_setShip (*list, i++, MELEE_UTWIG);
+ MeleeTeam_setShip (*list, i++, MELEE_UTWIG);
+ list++;
+ }
+
+ {
+ /* "The Peeled Eyes" */
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, GAME_STRING (MELEE_STRING_BASE + 8));
+ MeleeTeam_setShip (*list, i++, MELEE_URQUAN);
+ MeleeTeam_setShip (*list, i++, MELEE_CHENJESU);
+ MeleeTeam_setShip (*list, i++, MELEE_MYCON);
+ MeleeTeam_setShip (*list, i++, MELEE_SYREEN);
+ MeleeTeam_setShip (*list, i++, MELEE_ZOQFOTPIK);
+ MeleeTeam_setShip (*list, i++, MELEE_SHOFIXTI);
+ MeleeTeam_setShip (*list, i++, MELEE_EARTHLING);
+ MeleeTeam_setShip (*list, i++, MELEE_KOHR_AH);
+ MeleeTeam_setShip (*list, i++, MELEE_MELNORME);
+ MeleeTeam_setShip (*list, i++, MELEE_DRUUGE);
+ MeleeTeam_setShip (*list, i++, MELEE_PKUNK);
+ MeleeTeam_setShip (*list, i++, MELEE_ORZ);
+ list++;
+ }
+
+ {
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, "Ford's Fighters");
+ MeleeTeam_setShip (*list, i++, MELEE_CHMMR);
+ MeleeTeam_setShip (*list, i++, MELEE_ZOQFOTPIK);
+ MeleeTeam_setShip (*list, i++, MELEE_MELNORME);
+ MeleeTeam_setShip (*list, i++, MELEE_SUPOX);
+ MeleeTeam_setShip (*list, i++, MELEE_UTWIG);
+ MeleeTeam_setShip (*list, i++, MELEE_UMGAH);
+ list++;
+ }
+
+ {
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, "Leyland's Lashers");
+ MeleeTeam_setShip (*list, i++, MELEE_ANDROSYNTH);
+ MeleeTeam_setShip (*list, i++, MELEE_EARTHLING);
+ MeleeTeam_setShip (*list, i++, MELEE_MYCON);
+ MeleeTeam_setShip (*list, i++, MELEE_ORZ);
+ MeleeTeam_setShip (*list, i++, MELEE_URQUAN);
+ list++;
+ }
+
+ {
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, "The Gregorizers 200");
+ MeleeTeam_setShip (*list, i++, MELEE_ANDROSYNTH);
+ MeleeTeam_setShip (*list, i++, MELEE_CHMMR);
+ MeleeTeam_setShip (*list, i++, MELEE_DRUUGE);
+ MeleeTeam_setShip (*list, i++, MELEE_MELNORME);
+ MeleeTeam_setShip (*list, i++, MELEE_EARTHLING);
+ MeleeTeam_setShip (*list, i++, MELEE_KOHR_AH);
+ MeleeTeam_setShip (*list, i++, MELEE_SUPOX);
+ MeleeTeam_setShip (*list, i++, MELEE_ORZ);
+ MeleeTeam_setShip (*list, i++, MELEE_PKUNK);
+ MeleeTeam_setShip (*list, i++, MELEE_SPATHI);
+ list++;
+ }
+
+ {
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, "300 point Armada!");
+ MeleeTeam_setShip (*list, i++, MELEE_ANDROSYNTH);
+ MeleeTeam_setShip (*list, i++, MELEE_CHMMR);
+ MeleeTeam_setShip (*list, i++, MELEE_CHENJESU);
+ MeleeTeam_setShip (*list, i++, MELEE_DRUUGE);
+ MeleeTeam_setShip (*list, i++, MELEE_EARTHLING);
+ MeleeTeam_setShip (*list, i++, MELEE_KOHR_AH);
+ MeleeTeam_setShip (*list, i++, MELEE_MELNORME);
+ MeleeTeam_setShip (*list, i++, MELEE_MYCON);
+ MeleeTeam_setShip (*list, i++, MELEE_ORZ);
+ MeleeTeam_setShip (*list, i++, MELEE_PKUNK);
+ MeleeTeam_setShip (*list, i++, MELEE_SPATHI);
+ MeleeTeam_setShip (*list, i++, MELEE_SUPOX);
+ MeleeTeam_setShip (*list, i++, MELEE_URQUAN);
+ MeleeTeam_setShip (*list, i++, MELEE_YEHAT);
+ list++;
+ }
+
+ {
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, "Little Dudes with Attitudes");
+ MeleeTeam_setShip (*list, i++, MELEE_UMGAH);
+ MeleeTeam_setShip (*list, i++, MELEE_THRADDASH);
+ MeleeTeam_setShip (*list, i++, MELEE_SHOFIXTI);
+ MeleeTeam_setShip (*list, i++, MELEE_EARTHLING);
+ MeleeTeam_setShip (*list, i++, MELEE_VUX);
+ MeleeTeam_setShip (*list, i++, MELEE_ZOQFOTPIK);
+ list++;
+ }
+
+ {
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, "New Alliance Ships");
+ MeleeTeam_setShip (*list, i++, MELEE_ARILOU);
+ MeleeTeam_setShip (*list, i++, MELEE_CHMMR);
+ MeleeTeam_setShip (*list, i++, MELEE_EARTHLING);
+ MeleeTeam_setShip (*list, i++, MELEE_ORZ);
+ MeleeTeam_setShip (*list, i++, MELEE_PKUNK);
+ MeleeTeam_setShip (*list, i++, MELEE_SHOFIXTI);
+ MeleeTeam_setShip (*list, i++, MELEE_SUPOX);
+ MeleeTeam_setShip (*list, i++, MELEE_SYREEN);
+ MeleeTeam_setShip (*list, i++, MELEE_UTWIG);
+ MeleeTeam_setShip (*list, i++, MELEE_ZOQFOTPIK);
+ MeleeTeam_setShip (*list, i++, MELEE_YEHAT);
+ MeleeTeam_setShip (*list, i++, MELEE_DRUUGE);
+ MeleeTeam_setShip (*list, i++, MELEE_THRADDASH);
+ MeleeTeam_setShip (*list, i++, MELEE_SPATHI);
+ list++;
+ }
+
+ {
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, "Old Alliance Ships");
+ MeleeTeam_setShip (*list, i++, MELEE_ARILOU);
+ MeleeTeam_setShip (*list, i++, MELEE_CHENJESU);
+ MeleeTeam_setShip (*list, i++, MELEE_EARTHLING);
+ MeleeTeam_setShip (*list, i++, MELEE_MMRNMHRM);
+ MeleeTeam_setShip (*list, i++, MELEE_SHOFIXTI);
+ MeleeTeam_setShip (*list, i++, MELEE_SYREEN);
+ MeleeTeam_setShip (*list, i++, MELEE_YEHAT);
+ list++;
+ }
+
+ {
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, "Old Hierarchy Ships");
+ MeleeTeam_setShip (*list, i++, MELEE_ANDROSYNTH);
+ MeleeTeam_setShip (*list, i++, MELEE_ILWRATH);
+ MeleeTeam_setShip (*list, i++, MELEE_MYCON);
+ MeleeTeam_setShip (*list, i++, MELEE_SPATHI);
+ MeleeTeam_setShip (*list, i++, MELEE_UMGAH);
+ MeleeTeam_setShip (*list, i++, MELEE_URQUAN);
+ MeleeTeam_setShip (*list, i++, MELEE_VUX);
+ list++;
+ }
+
+ {
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, "Star Control 1");
+ MeleeTeam_setShip (*list, i++, MELEE_ANDROSYNTH);
+ MeleeTeam_setShip (*list, i++, MELEE_ARILOU);
+ MeleeTeam_setShip (*list, i++, MELEE_CHENJESU);
+ MeleeTeam_setShip (*list, i++, MELEE_EARTHLING);
+ MeleeTeam_setShip (*list, i++, MELEE_ILWRATH);
+ MeleeTeam_setShip (*list, i++, MELEE_MMRNMHRM);
+ MeleeTeam_setShip (*list, i++, MELEE_MYCON);
+ MeleeTeam_setShip (*list, i++, MELEE_SHOFIXTI);
+ MeleeTeam_setShip (*list, i++, MELEE_SPATHI);
+ MeleeTeam_setShip (*list, i++, MELEE_SYREEN);
+ MeleeTeam_setShip (*list, i++, MELEE_UMGAH);
+ MeleeTeam_setShip (*list, i++, MELEE_URQUAN);
+ MeleeTeam_setShip (*list, i++, MELEE_VUX);
+ MeleeTeam_setShip (*list, i++, MELEE_YEHAT);
+ list++;
+ }
+
+ {
+ FleetShipIndex i = 0;
+ MeleeTeam_setName (*list, "Star Control 2");
+ MeleeTeam_setShip (*list, i++, MELEE_CHMMR);
+ MeleeTeam_setShip (*list, i++, MELEE_DRUUGE);
+ MeleeTeam_setShip (*list, i++, MELEE_KOHR_AH);
+ MeleeTeam_setShip (*list, i++, MELEE_MELNORME);
+ MeleeTeam_setShip (*list, i++, MELEE_ORZ);
+ MeleeTeam_setShip (*list, i++, MELEE_PKUNK);
+ MeleeTeam_setShip (*list, i++, MELEE_SLYLANDRO);
+ MeleeTeam_setShip (*list, i++, MELEE_SUPOX);
+ MeleeTeam_setShip (*list, i++, MELEE_THRADDASH);
+ MeleeTeam_setShip (*list, i++, MELEE_UTWIG);
+ MeleeTeam_setShip (*list, i++, MELEE_ZOQFOTPIK);
+ MeleeTeam_setShip (*list, i++, MELEE_ZOQFOTPIK);
+ MeleeTeam_setShip (*list, i++, MELEE_ZOQFOTPIK);
+ MeleeTeam_setShip (*list, i++, MELEE_ZOQFOTPIK);
+ list++;
+ }
+
+ assert (list == pMS->load.preBuiltList + pMS->load.preBuiltCount);
+}
+
+static void
+UninitPreBuilt (MELEE_STATE *pMS)
+{
+ size_t fleetI;
+ for (fleetI = 0; fleetI < pMS->load.preBuiltCount; fleetI++)
+ MeleeTeam_delete (pMS->load.preBuiltList[fleetI]);
+ HFree (pMS->load.preBuiltList);
+ pMS->load.preBuiltCount = 0;
+}
+
+static void
+InitLoadView (MELEE_STATE *pMS)
+{
+ size_t viewI;
+ MeleeTeam **view = pMS->load.view;
+
+ for (viewI = 0; viewI < LOAD_TEAM_VIEW_SIZE; viewI++)
+ view[viewI] = MeleeTeam_new ();
+}
+
+static void
+UninitLoadView (MELEE_STATE *pMS)
+{
+ size_t viewI;
+ MeleeTeam **view = pMS->load.view;
+
+ for (viewI = 0; viewI < LOAD_TEAM_VIEW_SIZE; viewI++)
+ MeleeTeam_delete(view[viewI]);
+}
+
+void
+InitMeleeLoadState (MELEE_STATE *pMS)
+{
+ pMS->load.entryIndices = NULL;
+ InitPreBuilt (pMS);
+ InitLoadView (pMS);
+}
+
+void
+UninitMeleeLoadState (MELEE_STATE *pMS)
+{
+ UninitLoadView (pMS);
+ UninitPreBuilt (pMS);
+ if (pMS->load.entryIndices != NULL)
+ HFree (pMS->load.entryIndices);
+}
+
+
diff --git a/src/uqm/supermelee/loadmele.h b/src/uqm/supermelee/loadmele.h
new file mode 100644
index 0000000..529ef66
--- /dev/null
+++ b/src/uqm/supermelee/loadmele.h
@@ -0,0 +1,67 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_SUPERMELEE_LOADMELE_H_
+#define UQM_SUPERMELEE_LOADMELE_H_
+
+#define LOAD_TEAM_VIEW_SIZE 5
+
+struct melee_load_state;
+
+#include "melee.h"
+#include "meleesetup.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct melee_load_state
+{
+ MeleeTeam **preBuiltList;
+ COUNT preBuiltCount;
+
+ DIRENTRY dirEntries;
+ COUNT *entryIndices;
+ COUNT numIndices;
+
+ MeleeTeam *view[LOAD_TEAM_VIEW_SIZE];
+ COUNT top;
+ // Index of the first entry for the view.
+ COUNT bot;
+ // Index of the first entry past the end of the view.
+
+ COUNT cur;
+ // Index of the current position in the view.
+ COUNT viewSize;
+ // Number of entries in the view.
+};
+
+void InitMeleeLoadState (MELEE_STATE *pMS);
+void UninitMeleeLoadState (MELEE_STATE *pMS);
+
+BOOLEAN DoLoadTeam (MELEE_STATE *pMS);
+BOOLEAN DoSaveTeam (MELEE_STATE *pMS);
+bool ReadTeamImage (MeleeTeam *pTI, uio_Stream *load_fp);
+int WriteTeamImage (const MeleeTeam *pTI, uio_Stream *save_fp);
+void LoadTeamList (MELEE_STATE *pMS);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_LOADMELE_H_ */
diff --git a/src/uqm/supermelee/melee.c b/src/uqm/supermelee/melee.c
new file mode 100644
index 0000000..70f3acb
--- /dev/null
+++ b/src/uqm/supermelee/melee.c
@@ -0,0 +1,2640 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "melee.h"
+
+#include "options.h"
+#include "buildpick.h"
+#include "meleeship.h"
+#include "../battle.h"
+#include "../build.h"
+#include "../status.h"
+#include "../colors.h"
+#include "../comm.h"
+ // for getLineWithinWidth()
+#include "../cons_res.h"
+ // for load_gravity_well() and free_gravity_well()
+#include "../controls.h"
+#include "../gamestr.h"
+#include "../globdata.h"
+#include "../intel.h"
+#include "../master.h"
+#include "../nameref.h"
+#ifdef NETPLAY
+# include "netplay/netconnection.h"
+# include "netplay/netmelee.h"
+# include "netplay/notify.h"
+# include "netplay/notifyall.h"
+# include "libs/graphics/widgets.h"
+ // for DrawShadowedBox()
+# include "../cnctdlg.h"
+ // for MeleeConnectDialog()
+#endif /* defined (NETPLAY) */
+#include "../resinst.h"
+#include "../settings.h"
+#include "../setup.h"
+#include "../sounds.h"
+#include "../util.h"
+ // for DrawStarConBox()
+#include "../planets/planets.h"
+ // for NUMBER_OF_PLANET_TYPES
+#include "libs/gfxlib.h"
+#include "libs/mathlib.h"
+ // for TFB_Random()
+#include "libs/reslib.h"
+#include "libs/log.h"
+#include "libs/uio.h"
+
+
+#include <assert.h>
+#include <string.h>
+
+
+static void StartMelee (MELEE_STATE *pMS);
+#ifdef NETPLAY
+static ssize_t numPlayersReady (void);
+#endif /* NETPLAY */
+
+enum
+{
+#ifdef NETPLAY
+ NET_TOP,
+#endif
+ CONTROLS_TOP,
+ SAVE_TOP,
+ LOAD_TOP,
+ START_MELEE,
+ LOAD_BOT,
+ SAVE_BOT,
+ CONTROLS_BOT,
+#ifdef NETPLAY
+ NET_BOT,
+#endif
+ QUIT_BOT,
+ EDIT_MELEE, // Editing a fleet or the team name
+ BUILD_PICK // Selecting a ship to add to a fleet
+};
+
+#ifdef NETPLAY
+#define TOP_ENTRY NET_TOP
+#else
+#define TOP_ENTRY CONTROLS_TOP
+#endif
+
+#define MELEE_X_OFFS 2
+#define MELEE_Y_OFFS 21
+#define MELEE_BOX_WIDTH 34
+#define MELEE_BOX_HEIGHT 34
+#define MELEE_BOX_SPACE 1
+
+#define MENU_X_OFFS 29
+
+#define INFO_ORIGIN_X 4
+#define INFO_WIDTH 58
+#define TEAM_INFO_ORIGIN_Y 3
+#define TEAM_INFO_HEIGHT (SHIP_INFO_HEIGHT + 75)
+#define MODE_INFO_ORIGIN_Y (TEAM_INFO_HEIGHT + 6)
+#define MODE_INFO_HEIGHT ((STATUS_HEIGHT - 3) - MODE_INFO_ORIGIN_Y)
+#define RACE_INFO_ORIGIN_Y (SHIP_INFO_HEIGHT + 6)
+#define RACE_INFO_HEIGHT ((STATUS_HEIGHT - 3) - RACE_INFO_ORIGIN_Y)
+
+#define MELEE_STATUS_X_OFFS 1
+#define MELEE_STATUS_Y_OFFS 201
+#define MELEE_STATUS_WIDTH (NUM_MELEE_COLUMNS * \
+ (MELEE_BOX_WIDTH + MELEE_BOX_SPACE))
+#define MELEE_STATUS_HEIGHT 38
+
+#define MELEE_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x00), 0x04)
+#define MELEE_TITLE_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x0A), 0x0C)
+#define MELEE_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x0A), 0x0C)
+#define MELEE_TEAM_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x0A), 0x0E)
+
+#define STATE_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01)
+#define STATE_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x14), 0x03)
+#define ACTIVE_STATE_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1F, 0x1F), 0x0B)
+#define UNAVAILABLE_STATE_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09)
+#define HI_STATE_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1F, 0x1F), 0x0B)
+#define HI_STATE_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09)
+
+// XXX: The following entries are unused:
+#define LIST_INFO_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x14), 0x05)
+#define LIST_INFO_TITLE_COLOR \
+ WHITE_COLOR
+#define LIST_INFO_TEXT_COLOR \
+ LT_GRAY_COLOR
+#define LIST_INFO_CURENTRY_TEXT_COLOR \
+ WHITE_COLOR
+#define HI_LIST_INFO_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x00), 0x04)
+#define HI_LIST_INFO_BACKGROUND_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x1F), 0x0D)
+
+#define TEAM_NAME_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0F, 0x10, 0x1B), 0x00)
+#define TEAM_NAME_EDIT_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x17, 0x18, 0x1D), 0x00)
+#define TEAM_NAME_EDIT_RECT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x14), 0x05)
+#define TEAM_NAME_EDIT_CURS_COLOR \
+ WHITE_COLOR
+
+#define SHIPBOX_TOPLEFT_COLOR_NORMAL \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x09), 0x56)
+#define SHIPBOX_BOTTOMRIGHT_COLOR_NORMAL \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x0E), 0x54)
+#define SHIPBOX_INTERIOR_COLOR_NORMAL \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x0C), 0x55)
+
+#define SHIPBOX_TOPLEFT_COLOR_HILITE \
+ BUILD_COLOR (MAKE_RGB15 (0x07, 0x00, 0x0C), 0x3E)
+#define SHIPBOX_BOTTOMRIGHT_COLOR_HILITE \
+ BUILD_COLOR (MAKE_RGB15 (0x0C, 0x00, 0x14), 0x3C)
+#define SHIPBOX_INTERIOR_COLOR_HILITE \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x00, 0x11), 0x3D)
+
+#define MELEE_STATUS_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x00, 0x14, 0x00), 0x02)
+
+
+FRAME MeleeFrame;
+ // Loaded from melee/melebkgd.ani
+MELEE_STATE *pMeleeState;
+
+BOOLEAN DoMelee (MELEE_STATE *pMS);
+static BOOLEAN DoEdit (MELEE_STATE *pMS);
+static BOOLEAN DoConfirmSettings (MELEE_STATE *pMS);
+
+#define DTSHS_NORMAL 0
+#define DTSHS_EDIT 1
+#define DTSHS_SELECTED 2
+#define DTSHS_REPAIR 4
+#define DTSHS_BLOCKCUR 8
+static BOOLEAN DrawTeamString (MELEE_STATE *pMS, COUNT side,
+ COUNT HiLiteState, const char *str);
+static void DrawFleetValue (MELEE_STATE *pMS, COUNT side, COUNT HiLiteState);
+
+static void Melee_UpdateView_fleetValue (MELEE_STATE *pMS, COUNT side);
+static void Melee_UpdateView_ship (MELEE_STATE *pMS, COUNT side,
+ FleetShipIndex index);
+static void Melee_UpdateView_teamName (MELEE_STATE *pMS, COUNT side);
+
+
+// These icons come from melee/melebkgd.ani
+void
+DrawMeleeIcon (COUNT which_icon)
+{
+ STAMP s;
+
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = SetAbsFrameIndex (MeleeFrame, which_icon);
+ DrawStamp (&s);
+}
+
+static FleetShipIndex
+GetShipIndex (BYTE row, BYTE col)
+{
+ return row * NUM_MELEE_COLUMNS + col;
+}
+
+static BYTE
+GetShipRow (FleetShipIndex index)
+{
+ return index / NUM_MELEE_COLUMNS;
+}
+
+static BYTE
+GetShipColumn (int index)
+{
+ return index % NUM_MELEE_COLUMNS;
+}
+
+// Get the rectangle containing the ship slot for the specified side, row,
+// and column.
+void
+GetShipBox (RECT *pRect, COUNT side, COUNT row, COUNT col)
+{
+ pRect->corner.x = MELEE_X_OFFS
+ + (col * (MELEE_BOX_WIDTH + MELEE_BOX_SPACE));
+ pRect->corner.y = MELEE_Y_OFFS
+ + (side * (MELEE_Y_OFFS + MELEE_BOX_SPACE
+ + (NUM_MELEE_ROWS * (MELEE_BOX_HEIGHT + MELEE_BOX_SPACE))))
+ + (row * (MELEE_BOX_HEIGHT + MELEE_BOX_SPACE));
+ pRect->extent.width = MELEE_BOX_WIDTH;
+ pRect->extent.height = MELEE_BOX_HEIGHT;
+}
+
+static void
+DrawShipBox (COUNT side, FleetShipIndex index, MeleeShip ship, BOOLEAN HiLite)
+{
+ RECT r;
+ BYTE row = GetShipRow (index);
+ BYTE col = GetShipColumn (index);
+
+ GetShipBox (&r, side, row, col);
+
+ BatchGraphics ();
+ if (HiLite)
+ DrawStarConBox (&r, 1,
+ SHIPBOX_TOPLEFT_COLOR_HILITE,
+ SHIPBOX_BOTTOMRIGHT_COLOR_HILITE,
+ (BOOLEAN)(ship != MELEE_NONE),
+ SHIPBOX_INTERIOR_COLOR_HILITE);
+ else
+ DrawStarConBox (&r, 1,
+ SHIPBOX_TOPLEFT_COLOR_NORMAL,
+ SHIPBOX_BOTTOMRIGHT_COLOR_NORMAL,
+ (BOOLEAN)(ship != MELEE_NONE),
+ SHIPBOX_INTERIOR_COLOR_NORMAL);
+
+ if (ship != MELEE_NONE)
+ {
+ STAMP s;
+ s.origin.x = r.corner.x + (r.extent.width >> 1);
+ s.origin.y = r.corner.y + (r.extent.height >> 1);
+ s.frame = GetShipMeleeIconsFromIndex (ship);
+
+ DrawStamp (&s);
+ }
+ UnbatchGraphics ();
+}
+
+static void
+ClearShipBox (COUNT side, FleetShipIndex index)
+{
+ RECT rect;
+ BYTE row = GetShipRow (index);
+ BYTE col = GetShipColumn (index);
+
+ GetShipBox (&rect, side, row, col);
+ RepairMeleeFrame (&rect);
+}
+
+static void
+DrawShipBoxCurrent (MELEE_STATE *pMS, BOOLEAN HiLite)
+{
+ FleetShipIndex slotI = GetShipIndex (pMS->row, pMS->col);
+ MeleeShip ship = MeleeSetup_getShip (pMS->meleeSetup, pMS->side, slotI);
+ DrawShipBox (pMS->side, slotI, ship, HiLite);
+}
+
+// Draw an image for one of the control method selection buttons.
+static void
+DrawControls (COUNT which_side, BOOLEAN HiLite)
+{
+ COUNT which_icon;
+
+ if (PlayerControl[which_side] & NETWORK_CONTROL)
+ {
+ DrawMeleeIcon (31 + (HiLite ? 1 : 0) + 2 * (1 - which_side));
+ /* "Network Control" */
+ return;
+ }
+
+ if (PlayerControl[which_side] & HUMAN_CONTROL)
+ which_icon = 0;
+ else
+ {
+ switch (PlayerControl[which_side]
+ & (STANDARD_RATING | GOOD_RATING | AWESOME_RATING))
+ {
+ case STANDARD_RATING:
+ which_icon = 1;
+ break;
+ case GOOD_RATING:
+ which_icon = 2;
+ break;
+ case AWESOME_RATING:
+ which_icon = 3;
+ break;
+ default:
+ // Should not happen. Satisfying compiler.
+ which_icon = 0;
+ break;
+ }
+ }
+
+ DrawMeleeIcon (1 + (8 * (1 - which_side)) + (HiLite ? 4 : 0) + which_icon);
+}
+
+static void
+DrawTeams (void)
+{
+ COUNT side;
+
+ for (side = 0; side < NUM_SIDES; side++)
+ {
+ FleetShipIndex index;
+
+ DrawControls (side, FALSE);
+
+ for (index = 0; index < MELEE_FLEET_SIZE; index++)
+ {
+ MeleeShip ship = MeleeSetup_getShip(pMeleeState->meleeSetup,
+ side, index);
+ DrawShipBox (side, index, ship, FALSE);
+ }
+
+ DrawTeamString (pMeleeState, side, DTSHS_NORMAL, NULL);
+ DrawFleetValue (pMeleeState, side, DTSHS_NORMAL);
+ }
+}
+
+void
+RepairMeleeFrame (const RECT *pRect)
+{
+ RECT r;
+ CONTEXT OldContext;
+ RECT OldRect;
+ POINT oldOrigin;
+
+ r.corner.x = pRect->corner.x + SAFE_X;
+ r.corner.y = pRect->corner.y + SAFE_Y;
+ r.extent = pRect->extent;
+ if (r.corner.y & 1)
+ {
+ --r.corner.y;
+ ++r.extent.height;
+ }
+
+ OldContext = SetContext (SpaceContext);
+ GetContextClipRect (&OldRect);
+ SetContextClipRect (&r);
+ // Offset the origin so that we draw the correct gfx in the cliprect
+ oldOrigin = SetContextOrigin (MAKE_POINT (-r.corner.x + SAFE_X,
+ -r.corner.y + SAFE_Y));
+ BatchGraphics ();
+
+ DrawMeleeIcon (0); /* Entire melee screen */
+#ifdef NETPLAY
+ DrawMeleeIcon (35); /* "Net..." (top, not highlighted) */
+ DrawMeleeIcon (37); /* "Net..." (bottom, not highlighted) */
+#endif
+ DrawMeleeIcon (26); /* "Battle!" (highlighted) */
+
+ DrawTeams ();
+
+ if (pMeleeState->MeleeOption == BUILD_PICK)
+ DrawPickFrame (pMeleeState);
+
+ UnbatchGraphics ();
+ SetContextOrigin (oldOrigin);
+ SetContextClipRect (&OldRect);
+ SetContext (OldContext);
+}
+
+static void
+RedrawMeleeFrame (void)
+{
+ RECT r;
+
+ r.corner.x = 0;
+ r.corner.y = 0;
+ r.extent.width = SCREEN_WIDTH;
+ r.extent.height = SCREEN_HEIGHT;
+
+ RepairMeleeFrame (&r);
+}
+
+static void
+GetTeamStringRect (COUNT side, RECT *r)
+{
+ r->corner.x = MELEE_X_OFFS - 1;
+ r->corner.y = (side + 1) * (MELEE_Y_OFFS
+ + ((MELEE_BOX_HEIGHT + MELEE_BOX_SPACE) * NUM_MELEE_ROWS + 2));
+ r->extent.width = NUM_MELEE_COLUMNS * (MELEE_BOX_WIDTH + MELEE_BOX_SPACE)
+ - 29;
+ r->extent.height = 13;
+}
+
+static void
+GetFleetValueRect (COUNT side, RECT *r)
+{
+ r->corner.x = MELEE_X_OFFS
+ + NUM_MELEE_COLUMNS * (MELEE_BOX_WIDTH + MELEE_BOX_SPACE) - 30;
+ r->corner.y = (side + 1) * (MELEE_Y_OFFS
+ + ((MELEE_BOX_HEIGHT + MELEE_BOX_SPACE) * NUM_MELEE_ROWS + 2));
+ r->extent.width = 29;
+ r->extent.height = 13;
+}
+
+static void
+DrawFleetValue (MELEE_STATE *pMS, COUNT side, COUNT HiLiteState)
+{
+ RECT r;
+ TEXT rtText;
+ UNICODE buf[30];
+ COUNT fleetValue;
+
+ GetFleetValueRect (side ,&r);
+
+ if (HiLiteState == DTSHS_REPAIR)
+ {
+ RepairMeleeFrame (&r);
+ return;
+ }
+
+ SetContextFont (MicroFont);
+
+ fleetValue = MeleeSetup_getFleetValue (pMS->meleeSetup, side);
+ sprintf (buf, "%u", fleetValue);
+ rtText.pStr = buf;
+ rtText.align = ALIGN_RIGHT;
+ rtText.CharCount = (COUNT)~0;
+ rtText.baseline.y = r.corner.y + r.extent.height - 3;
+ rtText.baseline.x = r.corner.x + r.extent.width;
+
+ SetContextForeGroundColor (!(HiLiteState & DTSHS_SELECTED)
+ ? TEAM_NAME_TEXT_COLOR : TEAM_NAME_EDIT_TEXT_COLOR);
+ font_DrawText (&rtText);
+}
+
+// If teamName == NULL, the team name is taken from pMS->meleeSetup
+static BOOLEAN
+DrawTeamString (MELEE_STATE *pMS, COUNT side, COUNT HiLiteState,
+ const char *teamName)
+{
+ RECT r;
+ TEXT lfText;
+
+ GetTeamStringRect (side, &r);
+ if (HiLiteState == DTSHS_REPAIR)
+ {
+ RepairMeleeFrame (&r);
+ return TRUE;
+ }
+
+ SetContextFont (MicroFont);
+
+ lfText.pStr = (teamName != NULL) ? teamName :
+ MeleeSetup_getTeamName (pMS->meleeSetup, side);
+ lfText.baseline.y = r.corner.y + r.extent.height - 3;
+ lfText.baseline.x = r.corner.x + 1;
+ lfText.align = ALIGN_LEFT;
+ lfText.CharCount = strlen (lfText.pStr);
+
+ BatchGraphics ();
+ if (!(HiLiteState & DTSHS_EDIT))
+ { // normal or selected state
+ SetContextForeGroundColor (!(HiLiteState & DTSHS_SELECTED)
+ ? TEAM_NAME_TEXT_COLOR : TEAM_NAME_EDIT_TEXT_COLOR);
+ font_DrawText (&lfText);
+ }
+ else
+ { // editing state
+ COUNT i;
+ RECT text_r;
+ BYTE char_deltas[MAX_TEAM_CHARS];
+ BYTE *pchar_deltas;
+
+ TextRect (&lfText, &text_r, char_deltas);
+ if ((text_r.extent.width + 2) >= r.extent.width)
+ { // the text does not fit the input box size and so
+ // will not fit when displayed later
+ UnbatchGraphics ();
+ // disallow the change
+ return FALSE;
+ }
+
+ text_r = r;
+ SetContextForeGroundColor (TEAM_NAME_EDIT_RECT_COLOR);
+ DrawFilledRectangle (&text_r);
+
+ // calculate the cursor position and draw it
+ pchar_deltas = char_deltas;
+ for (i = pMS->CurIndex; i > 0; --i)
+ text_r.corner.x += (SIZE)*pchar_deltas++;
+ if (pMS->CurIndex < lfText.CharCount) /* cursor mid-line */
+ --text_r.corner.x;
+ if (HiLiteState & DTSHS_BLOCKCUR)
+ { // Use block cursor for keyboardless systems
+ if (pMS->CurIndex == lfText.CharCount)
+ { // cursor at end-line -- use insertion point
+ text_r.extent.width = 1;
+ }
+ else if (pMS->CurIndex + 1 == lfText.CharCount)
+ { // extra pixel for last char margin
+ text_r.extent.width = (SIZE)*pchar_deltas + 2;
+ }
+ else
+ { // normal mid-line char
+ text_r.extent.width = (SIZE)*pchar_deltas + 1;
+ }
+ }
+ else
+ { // Insertion point cursor
+ text_r.extent.width = 1;
+ }
+ // position cursor within input field rect
+ ++text_r.corner.x;
+ ++text_r.corner.y;
+ text_r.extent.height -= 2;
+ SetContextForeGroundColor (TEAM_NAME_EDIT_CURS_COLOR);
+ DrawFilledRectangle (&text_r);
+
+ SetContextForeGroundColor (BLACK_COLOR); // TEAM_NAME_EDIT_TEXT_COLOR);
+ font_DrawText (&lfText);
+ }
+ UnbatchGraphics ();
+
+ return TRUE;
+}
+
+#ifdef NETPLAY
+// This function is generic. It should probably be moved to elsewhere.
+static void
+multiLineDrawText (TEXT *textIn, RECT *clipRect) {
+ RECT oldRect;
+
+ SIZE leading;
+ TEXT text;
+ SIZE lineWidth;
+
+ GetContextClipRect (&oldRect);
+
+ SetContextClipRect (clipRect);
+ GetContextFontLeading (&leading);
+
+ text = *textIn;
+ text.baseline.x = 1;
+ text.baseline.y = 0;
+
+ if (clipRect->extent.width <= text.baseline.x)
+ goto out;
+
+ lineWidth = clipRect->extent.width - text.baseline.x;
+
+ while (*text.pStr != '\0') {
+ const char *nextLine;
+
+ text.baseline.y += leading;
+ text.CharCount = (COUNT) ~0;
+ getLineWithinWidth (&text, &nextLine, lineWidth, text.CharCount);
+ // This will also fill in text->CharCount.
+
+ font_DrawText (&text);
+
+ text.pStr = nextLine;
+ }
+
+out:
+ SetContextClipRect (&oldRect);
+}
+
+// Use an empty string to clear the status area.
+static void
+DrawMeleeStatusMessage (const char *message)
+{
+ CONTEXT oldContext;
+ RECT r;
+
+ oldContext = SetContext (SpaceContext);
+
+ r.corner.x = MELEE_STATUS_X_OFFS;
+ r.corner.y = MELEE_STATUS_Y_OFFS;
+ r.extent.width = MELEE_STATUS_WIDTH;
+ r.extent.height = MELEE_STATUS_HEIGHT;
+
+ RepairMeleeFrame (&r);
+
+ if (message[0] != '\0')
+ {
+ TEXT lfText;
+ lfText.pStr = message;
+ lfText.align = ALIGN_LEFT;
+ lfText.CharCount = (COUNT)~0;
+
+ SetContextFont (MicroFont);
+ SetContextForeGroundColor (MELEE_STATUS_COLOR);
+
+ BatchGraphics ();
+ multiLineDrawText (&lfText, &r);
+ UnbatchGraphics ();
+ }
+
+ SetContext (oldContext);
+}
+
+static void
+UpdateMeleeStatusMessage (ssize_t player)
+{
+ NetConnection *conn;
+
+ assert (player == -1 || (player >= 0 && player < NUM_PLAYERS));
+
+ if (player == -1)
+ {
+ DrawMeleeStatusMessage ("");
+ return;
+ }
+
+ conn = netConnections[player];
+ if (conn == NULL)
+ {
+ DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 0));
+ // "Unconnected. Press LEFT to connect."
+ return;
+ }
+
+ switch (NetConnection_getState (conn)) {
+ case NetState_unconnected:
+ DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 0));
+ // "Unconnected. Press LEFT to connect."
+ break;
+ case NetState_connecting:
+ if (NetConnection_getPeerOptions (conn)->isServer)
+ DrawMeleeStatusMessage (
+ GAME_STRING (NETMELEE_STRING_BASE + 1));
+ // "Awaiting incoming connection...\n"
+ // "Press RIGHT to cancel."
+ else
+ DrawMeleeStatusMessage (
+ GAME_STRING (NETMELEE_STRING_BASE + 2));
+ // "Attempting outgoing connection...\n"
+ // "Press RIGHT to cancel."
+ break;
+ case NetState_init:
+ case NetState_inSetup:
+ DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 3));
+ // "Connected. Press RIGHT to disconnect."
+ break;
+ default:
+ DrawMeleeStatusMessage ("");
+ break;
+ }
+}
+#endif /* NETPLAY */
+
+// XXX: this function is called when the current selection is blinking off.
+static void
+Deselect (BYTE opt)
+{
+ switch (opt)
+ {
+ case START_MELEE:
+ DrawMeleeIcon (25); /* "Battle!" (not highlighted) */
+ break;
+ case LOAD_TOP:
+ DrawMeleeIcon (17); /* "Load" (top, not highlighted) */
+ break;
+ case LOAD_BOT:
+ DrawMeleeIcon (22); /* "Load" (bottom, not highlighted) */
+ break;
+ case SAVE_TOP:
+ DrawMeleeIcon (18); /* "Save" (top, not highlighted) */
+ break;
+ case SAVE_BOT:
+ DrawMeleeIcon (21); /* "Save" (bottom, not highlighted) */
+ break;
+#ifdef NETPLAY
+ case NET_TOP:
+ DrawMeleeIcon (35); /* "Net..." (top, not highlighted) */
+ break;
+ case NET_BOT:
+ DrawMeleeIcon (37); /* "Net..." (bottom, not highlighted) */
+ break;
+#endif
+ case QUIT_BOT:
+ DrawMeleeIcon (29); /* "Quit" (not highlighted) */
+ break;
+ case CONTROLS_TOP:
+ case CONTROLS_BOT:
+ {
+ COUNT which_side;
+
+ which_side = opt == CONTROLS_TOP ? 1 : 0;
+ DrawControls (which_side, FALSE);
+ break;
+ }
+ case EDIT_MELEE:
+ if (pMeleeState->InputFunc == DoEdit)
+ {
+ if (pMeleeState->row < NUM_MELEE_ROWS)
+ DrawShipBoxCurrent (pMeleeState, FALSE);
+ else if (pMeleeState->CurIndex == MELEE_STATE_INDEX_DONE)
+ {
+ // Not currently editing the team name.
+ DrawTeamString (pMeleeState, pMeleeState->side,
+ DTSHS_NORMAL, NULL);
+ DrawFleetValue (pMeleeState, pMeleeState->side,
+ DTSHS_NORMAL);
+ }
+ }
+ break;
+ case BUILD_PICK:
+ DrawPickIcon (pMeleeState->currentShip, true);
+ break;
+ }
+}
+
+// XXX: this function is called when the current selection is blinking off.
+static void
+Select (BYTE opt)
+{
+ switch (opt)
+ {
+ case START_MELEE:
+ DrawMeleeIcon (26); /* "Battle!" (highlighted) */
+ break;
+ case LOAD_TOP:
+ DrawMeleeIcon (19); /* "Load" (top, highlighted) */
+ break;
+ case LOAD_BOT:
+ DrawMeleeIcon (24); /* "Load" (bottom, highlighted) */
+ break;
+ case SAVE_TOP:
+ DrawMeleeIcon (20); /* "Save" (top; highlighted) */
+ break;
+ case SAVE_BOT:
+ DrawMeleeIcon (23); /* "Save" (bottom; highlighted) */
+ break;
+#ifdef NETPLAY
+ case NET_TOP:
+ DrawMeleeIcon (36); /* "Net..." (top; highlighted) */
+ break;
+ case NET_BOT:
+ DrawMeleeIcon (38); /* "Net..." (bottom; highlighted) */
+ break;
+#endif
+ case QUIT_BOT:
+ DrawMeleeIcon (30); /* "Quit" (highlighted) */
+ break;
+ case CONTROLS_TOP:
+ case CONTROLS_BOT:
+ {
+ COUNT which_side;
+
+ which_side = (opt == CONTROLS_TOP) ? 1 : 0;
+ DrawControls (which_side, TRUE);
+ break;
+ }
+ case EDIT_MELEE:
+ if (pMeleeState->InputFunc == DoEdit)
+ {
+ if (pMeleeState->row < NUM_MELEE_ROWS)
+ DrawShipBoxCurrent (pMeleeState, TRUE);
+ else if (pMeleeState->CurIndex == MELEE_STATE_INDEX_DONE)
+ {
+ // Not currently editing the team name.
+ DrawTeamString (pMeleeState, pMeleeState->side,
+ DTSHS_SELECTED, NULL);
+ DrawFleetValue (pMeleeState, pMeleeState->side,
+ DTSHS_SELECTED);
+ }
+ }
+ break;
+ case BUILD_PICK:
+ DrawPickIcon (pMeleeState->currentShip, false);
+ break;
+ }
+}
+
+void
+Melee_flashSelection (MELEE_STATE *pMS)
+{
+#define FLASH_RATE (ONE_SECOND / 9)
+ static TimeCount NextTime = 0;
+ static bool select = false;
+ TimeCount Now = GetTimeCounter ();
+
+ if (Now >= NextTime)
+ {
+ CONTEXT OldContext;
+
+ NextTime = Now + FLASH_RATE;
+ select = !select;
+
+ OldContext = SetContext (SpaceContext);
+ if (select)
+ Select (pMS->MeleeOption);
+ else
+ Deselect (pMS->MeleeOption);
+ SetContext (OldContext);
+ }
+}
+
+static void
+InitMelee (MELEE_STATE *pMS)
+{
+ RECT r;
+
+ SetContext (SpaceContext);
+ SetContextFGFrame (Screen);
+ SetContextClipRect (NULL);
+ SetContextBackGroundColor (BLACK_COLOR);
+ ClearDrawable ();
+ r.corner.x = SAFE_X;
+ r.corner.y = SAFE_Y;
+ r.extent.width = SCREEN_WIDTH - (SAFE_X * 2);
+ r.extent.height = SCREEN_HEIGHT - (SAFE_Y * 2);
+ SetContextClipRect (&r);
+
+ r.corner.x = r.corner.y = 0;
+ RedrawMeleeFrame ();
+
+ (void) pMS;
+}
+
+void
+DrawMeleeShipStrings (MELEE_STATE *pMS, MeleeShip NewStarShip)
+{
+ RECT r, OldRect;
+ CONTEXT OldContext;
+
+ OldContext = SetContext (StatusContext);
+ GetContextClipRect (&OldRect);
+ r = OldRect;
+ r.corner.x += ((SAFE_X << 1) - 32) + MENU_X_OFFS;
+ r.corner.y += 76;
+ r.extent.height = SHIP_INFO_HEIGHT;
+ SetContextClipRect (&r);
+ BatchGraphics ();
+
+ if (NewStarShip == MELEE_NONE)
+ {
+ RECT r;
+ TEXT t;
+
+ ClearShipStatus (0);
+ SetContextFont (StarConFont);
+ r.corner.x = 3;
+ r.corner.y = 4;
+ r.extent.width = 57;
+ r.extent.height = 60;
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawRectangle (&r);
+ t.baseline.x = STATUS_WIDTH >> 1;
+ t.baseline.y = 32;
+ t.align = ALIGN_CENTER;
+ if (pMS->row < NUM_MELEE_ROWS)
+ {
+ // A ship is selected (or an empty fleet position).
+ t.pStr = GAME_STRING (MELEE_STRING_BASE + 0); // "Empty"
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.pStr = GAME_STRING (MELEE_STRING_BASE + 1); // "Slot"
+ }
+ else
+ {
+ // The team name is selected.
+ t.pStr = GAME_STRING (MELEE_STRING_BASE + 2); // "Team"
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.pStr = GAME_STRING (MELEE_STRING_BASE + 3); // "Name"
+ }
+ t.baseline.y += TINY_TEXT_HEIGHT;
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ }
+ else
+ {
+ HMASTERSHIP hMasterShip;
+ MASTER_SHIP_INFO *MasterPtr;
+
+ hMasterShip = GetStarShipFromIndex (&master_q, NewStarShip);
+ MasterPtr = LockMasterShip (&master_q, hMasterShip);
+
+ InitShipStatus (&MasterPtr->ShipInfo, NULL, NULL);
+
+ UnlockMasterShip (&master_q, hMasterShip);
+ }
+
+ UnbatchGraphics ();
+ SetContextClipRect (&OldRect);
+ SetContext (OldContext);
+}
+
+// Set the currently displayed ship to the ship for the slot indicated by
+// pMS->row and pMS->col.
+static void
+UpdateCurrentShip (MELEE_STATE *pMS)
+{
+ if (pMS->row == NUM_MELEE_ROWS)
+ {
+ // The team name is selected.
+ pMS->currentShip = MELEE_NONE;
+ }
+ else
+ {
+ FleetShipIndex slotNr = GetShipIndex (pMS->row, pMS->col);
+ pMS->currentShip =
+ MeleeSetup_getShip (pMS->meleeSetup, pMS->side, slotNr);
+ }
+
+ DrawMeleeShipStrings (pMS, pMS->currentShip);
+}
+
+// returns (COUNT) ~0 for an invalid ship.
+COUNT
+GetShipValue (MeleeShip StarShip)
+{
+ COUNT val;
+
+ if (StarShip == MELEE_NONE)
+ return 0;
+
+ val = GetShipCostFromIndex (StarShip);
+ if (val == 0)
+ val = (COUNT)~0;
+
+ return val;
+}
+
+static void
+DeleteCurrentShip (MELEE_STATE *pMS)
+{
+ FleetShipIndex slotI = GetShipIndex (pMS->row, pMS->col);
+ Melee_LocalChange_ship (pMS, pMS->side, slotI, MELEE_NONE);
+}
+
+static bool
+isShipSlotSelected (MELEE_STATE *pMS, COUNT side, FleetShipIndex index)
+{
+ if (pMS->MeleeOption != EDIT_MELEE)
+ return false;
+
+ if (pMS->side != side)
+ return false;
+
+ return (index == GetShipIndex (pMS->row, pMS->col));
+}
+
+static void
+AdvanceCursor (MELEE_STATE *pMS)
+{
+ ++pMS->col;
+ if (pMS->col == NUM_MELEE_COLUMNS)
+ {
+ ++pMS->row;
+ if (pMS->row < NUM_MELEE_ROWS)
+ pMS->col = 0;
+ else
+ {
+ pMS->col = NUM_MELEE_COLUMNS - 1;
+ pMS->row = NUM_MELEE_ROWS - 1;
+ }
+ }
+}
+
+static BOOLEAN
+OnTeamNameChange (TEXTENTRY_STATE *pTES)
+{
+ MELEE_STATE *pMS = (MELEE_STATE*) pTES->CbParam;
+ BOOLEAN ret;
+ COUNT hl = DTSHS_EDIT;
+
+ pMS->CurIndex = pTES->CursorPos;
+ if (pTES->JoystickMode)
+ hl |= DTSHS_BLOCKCUR;
+
+ ret = DrawTeamString (pMS, pMS->side, hl, pTES->BaseStr);
+
+ return ret;
+}
+
+static BOOLEAN
+TeamNameFrameCallback (TEXTENTRY_STATE *pTES)
+{
+#ifdef NETPLAY
+ // Process incoming packets, so that remote changes are displayed
+ // while we are editing the team name.
+ // The team name itself isn't modified visually due to remote changes
+ // while it is being edited.
+ netInput ();
+#endif
+
+ (void) pTES;
+
+ return TRUE;
+ // Keep editing
+}
+
+static void
+BuildPickShipPopup (MELEE_STATE *pMS)
+{
+ bool buildOk;
+
+ pMS->MeleeOption = BUILD_PICK;
+
+ buildOk = BuildPickShip (pMS);
+ if (buildOk)
+ {
+ // A ship has been selected.
+ // Add the currently selected ship to the fleet.
+ FleetShipIndex index = GetShipIndex (pMS->row, pMS->col);
+ Melee_LocalChange_ship (pMS, pMS->side, index, pMS->currentShip);
+ AdvanceCursor (pMS);
+ }
+
+ pMS->MeleeOption = EDIT_MELEE;
+ // Must set this before the call to RepairMeleeFrame(), so that
+ // it will not redraw the BuildPickFrame.
+
+ {
+ RECT r;
+
+ GetBuildPickFrameRect (&r);
+ RepairMeleeFrame (&r);
+ }
+
+ UpdateCurrentShip (pMS);
+ pMS->InputFunc = DoEdit;
+}
+
+static BOOLEAN
+DoEdit (MELEE_STATE *pMS)
+{
+ DWORD TimeIn = GetTimeCounter ();
+
+ /* Cancel any presses of the Pause key. */
+ GamePaused = FALSE;
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT | MENU_SOUND_DELETE);
+ if (!pMS->Initialized)
+ {
+ UpdateCurrentShip (pMS);
+ pMS->Initialized = TRUE;
+ pMS->InputFunc = DoEdit;
+ return TRUE;
+ }
+
+#ifdef NETPLAY
+ netInput ();
+#endif
+ if ((pMS->row < NUM_MELEE_ROWS || pMS->currentShip == MELEE_NONE)
+ && (PulsedInputState.menu[KEY_MENU_CANCEL]
+ || (PulsedInputState.menu[KEY_MENU_RIGHT]
+ && (pMS->col == NUM_MELEE_COLUMNS - 1
+ || pMS->row == NUM_MELEE_ROWS))))
+ {
+ // Done editing the teams.
+ Deselect (EDIT_MELEE);
+ pMS->currentShip = MELEE_NONE;
+ pMS->MeleeOption = START_MELEE;
+ pMS->InputFunc = DoMelee;
+ pMS->LastInputTime = GetTimeCounter ();
+ }
+ else if (pMS->row < NUM_MELEE_ROWS
+ && PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ // Show a popup to add a new ship to the current team.
+ Deselect (EDIT_MELEE);
+ BuildPickShipPopup (pMS);
+ }
+ else if (pMS->row < NUM_MELEE_ROWS
+ && PulsedInputState.menu[KEY_MENU_SPECIAL])
+ {
+ // TODO: this is a stub; Should we display a ship spin?
+ Deselect (EDIT_MELEE);
+ if (pMS->currentShip != MELEE_NONE)
+ {
+ // Do something with pMS->currentShip here
+ }
+ }
+ else if (pMS->row < NUM_MELEE_ROWS &&
+ PulsedInputState.menu[KEY_MENU_DELETE])
+ {
+ // Remove the currently selected ship from the current team.
+ Deselect (EDIT_MELEE);
+ DeleteCurrentShip (pMS);
+ AdvanceCursor (pMS);
+ UpdateCurrentShip (pMS);
+ }
+ else
+ {
+ COUNT side = pMS->side;
+ COUNT row = pMS->row;
+ COUNT col = pMS->col;
+
+ if (row == NUM_MELEE_ROWS)
+ {
+ // Edit the name of the current team.
+ if (PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ TEXTENTRY_STATE tes;
+ char buf[MAX_TEAM_CHARS + 1];
+
+ // going to enter text
+ pMS->CurIndex = 0;
+ DrawTeamString (pMS, pMS->side, DTSHS_EDIT, NULL);
+
+ strncpy (buf, MeleeSetup_getTeamName (
+ pMS->meleeSetup, pMS->side), MAX_TEAM_CHARS);
+ buf[MAX_TEAM_CHARS] = '\0';
+
+ tes.Initialized = FALSE;
+ tes.BaseStr = buf;
+ tes.CursorPos = 0;
+ tes.MaxSize = MAX_TEAM_CHARS + 1;
+ tes.CbParam = pMS;
+ tes.ChangeCallback = OnTeamNameChange;
+ tes.FrameCallback = TeamNameFrameCallback;
+ DoTextEntry (&tes);
+
+ // done entering
+ pMS->CurIndex = MELEE_STATE_INDEX_DONE;
+ if (!tes.Success ||
+ !Melee_LocalChange_teamName (pMS, pMS->side, buf)) {
+ // The team name was not changed, so it was not redrawn.
+ // However, because we now leave edit mode, we still
+ // need to redraw.
+ Melee_UpdateView_teamName (pMS, pMS->side);
+ }
+
+ return TRUE;
+ }
+ }
+
+ {
+ if (PulsedInputState.menu[KEY_MENU_LEFT])
+ {
+ if (col > 0)
+ --col;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_RIGHT])
+ {
+ if (col < NUM_MELEE_COLUMNS - 1)
+ ++col;
+ }
+
+ if (PulsedInputState.menu[KEY_MENU_UP])
+ {
+ if (row-- == 0)
+ {
+ if (side == 0)
+ row = 0;
+ else
+ {
+ row = NUM_MELEE_ROWS;
+ side = !side;
+ }
+ }
+ }
+ else if (PulsedInputState.menu[KEY_MENU_DOWN])
+ {
+ if (row++ == NUM_MELEE_ROWS)
+ {
+ if (side == 1)
+ row = NUM_MELEE_ROWS;
+ else
+ {
+ row = 0;
+ side = !side;
+ }
+ }
+ }
+ }
+
+ if (col != pMS->col || row != pMS->row || side != pMS->side)
+ {
+ Deselect (EDIT_MELEE);
+ pMS->side = side;
+ pMS->row = row;
+ pMS->col = col;
+
+ UpdateCurrentShip (pMS);
+ }
+ }
+
+#ifdef NETPLAY
+ flushPacketQueues ();
+#endif
+
+ Melee_flashSelection (pMS);
+
+ SleepThreadUntil (TimeIn + ONE_SECOND / 30);
+
+ return TRUE;
+}
+
+#ifdef NETPLAY
+// Returns -1 if a connection has been aborted.
+static ssize_t
+numPlayersReady (void)
+{
+ size_t player;
+ size_t numDone;
+
+ numDone = 0;
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ if (!(PlayerControl[player] & NETWORK_CONTROL))
+ {
+ numDone++;
+ continue;
+ }
+
+ {
+ NetConnection *conn;
+
+ conn = netConnections[player];
+
+ if (conn == NULL || !NetConnection_isConnected (conn))
+ return -1;
+
+ if (NetConnection_getState (conn) > NetState_inSetup)
+ numDone++;
+ }
+ }
+
+ return numDone;
+}
+#endif /* NETPLAY */
+
+// The player has pressed "Start Game", and all Network players are
+// connected. We're now just waiting for the confirmation of the other
+// party.
+// When the other party changes something in the settings, the confirmation
+// is cancelled.
+static BOOLEAN
+DoConfirmSettings (MELEE_STATE *pMS)
+{
+#ifdef NETPLAY
+ ssize_t numDone;
+#endif
+
+ /* Cancel any presses of the Pause key. */
+ GamePaused = FALSE;
+
+ if (PulsedInputState.menu[KEY_MENU_CANCEL])
+ {
+ // The connection is explicitely cancelled, locally.
+ pMS->InputFunc = DoMelee;
+#ifdef NETPLAY
+ cancelConfirmations ();
+ DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 4));
+ // "Confirmation cancelled. Press FIRE to reconfirm."
+#endif
+ return TRUE;
+ }
+
+ if (PulsedInputState.menu[KEY_MENU_LEFT] ||
+ PulsedInputState.menu[KEY_MENU_UP] ||
+ PulsedInputState.menu[KEY_MENU_DOWN])
+ {
+ // The player moves the cursor; cancel the confirmation.
+ pMS->InputFunc = DoMelee;
+#ifdef NETPLAY
+ cancelConfirmations ();
+ DrawMeleeStatusMessage ("");
+#endif
+ return DoMelee (pMS);
+ // Let the pressed keys take effect immediately.
+ }
+
+#ifndef NETPLAY
+ pMS->InputFunc = DoMelee;
+ SeedRandomNumbers ();
+ pMS->meleeStarted = TRUE;
+ StartMelee (pMS);
+ pMS->meleeStarted = FALSE;
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+ return TRUE;
+#else
+ closeDisconnectedConnections ();
+ netInput ();
+ SleepThread (ONE_SECOND / 120);
+
+ numDone = numPlayersReady ();
+ if (numDone == -1)
+ {
+ // Connection aborted
+ cancelConfirmations ();
+ flushPacketQueues ();
+ pMS->InputFunc = DoMelee;
+ return TRUE;
+ }
+ else if (numDone != NUM_SIDES)
+ {
+ // Still waiting for some confirmation.
+ return TRUE;
+ }
+
+ // All sides have confirmed.
+
+ // Send our own prefered frame delay.
+ Netplay_NotifyAll_inputDelay (netplayOptions.inputDelay);
+
+ // Synchronise the RNGs:
+ {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn;
+
+ if (!(PlayerControl[player] & NETWORK_CONTROL))
+ continue;
+
+ conn = netConnections[player];
+ assert (conn != NULL);
+
+ if (!NetConnection_isConnected (conn))
+ continue;
+
+ if (NetConnection_getDiscriminant (conn))
+ Netplay_Notify_seedRandom (conn, SeedRandomNumbers ());
+ }
+ flushPacketQueues ();
+ }
+
+ {
+ // One side will send the seed followed by 'Done' and wait
+ // for the other side to report 'Done'.
+ // The other side will report 'Done' and will wait for the other
+ // side to report 'Done', but before the reception of 'Done'
+ // it will have received the seed.
+ bool allOk = negotiateReadyConnections (true, NetState_interBattle);
+ if (!allOk)
+ return FALSE;
+ }
+
+ // The maximum value for all connections is used.
+ {
+ bool ok = setupInputDelay (netplayOptions.inputDelay);
+ if (!ok)
+ return FALSE;
+ }
+
+ pMS->InputFunc = DoMelee;
+
+ StartMelee (pMS);
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ return TRUE;
+#endif /* defined (NETPLAY) */
+}
+
+static void
+LoadMeleeInfo (MELEE_STATE *pMS)
+{
+ BuildPickMeleeFrame ();
+ MeleeFrame = CaptureDrawable (LoadGraphic (MELEE_SCREEN_PMAP_ANIM));
+ BuildBuildPickFrame ();
+
+ InitSpace ();
+
+ LoadTeamList (pMS);
+}
+
+static void
+FreeMeleeInfo (MELEE_STATE *pMS)
+{
+ DestroyDirEntryTable (ReleaseDirEntryTable (pMS->load.dirEntries));
+ pMS->load.dirEntries = 0;
+
+ if (pMS->hMusic)
+ {
+ DestroyMusic (pMS->hMusic);
+ pMS->hMusic = 0;
+ }
+
+ UninitSpace ();
+
+ DestroyPickMeleeFrame ();
+ DestroyDrawable (ReleaseDrawable (MeleeFrame));
+ MeleeFrame = 0;
+ DestroyBuildPickFrame ();
+
+#ifdef NETPLAY
+ closeAllConnections ();
+ // Clear the input delay in case we will go into the full game later.
+ // Must be done after the net connections are closed.
+ setupInputDelay (0);
+#endif
+}
+
+static void
+BuildAndDrawShipList (MELEE_STATE *pMS)
+{
+ FillPickMeleeFrame (pMS->meleeSetup);
+ // XXX TODO: This also builds the race_q for each player.
+ // This should be split off.
+}
+
+static void
+StartMelee (MELEE_STATE *pMS)
+{
+ {
+ FadeMusic (0, ONE_SECOND / 2);
+ SleepThreadUntil (FadeScreen (FadeAllToBlack, ONE_SECOND / 2)
+ + ONE_SECOND / 60);
+ FlushColorXForms ();
+ StopMusic ();
+ }
+ FadeMusic (NORMAL_VOLUME, 0);
+ if (pMS->hMusic)
+ {
+ DestroyMusic (pMS->hMusic);
+ pMS->hMusic = 0;
+ }
+
+ do
+ {
+ if (!SetPlayerInputAll ())
+ break;
+ BuildAndDrawShipList (pMS);
+
+ WaitForSoundEnd (TFBSOUND_WAIT_ALL);
+
+ load_gravity_well ((BYTE)((COUNT)TFB_Random () %
+ NUMBER_OF_PLANET_TYPES));
+ Battle (NULL);
+ free_gravity_well ();
+ ClearPlayerInputAll ();
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return;
+
+ SleepThreadUntil (FadeScreen (FadeAllToBlack, ONE_SECOND / 2)
+ + ONE_SECOND / 60);
+ FlushColorXForms ();
+
+ } while (0 /* !(GLOBAL (CurrentActivity) & CHECK_ABORT) */);
+ GLOBAL (CurrentActivity) = SUPER_MELEE;
+
+ pMS->Initialized = FALSE;
+}
+
+static void
+StartMeleeButtonPressed (MELEE_STATE *pMS)
+{
+ // Either fleet must at least have one ship.
+ if (MeleeSetup_getFleetValue (pMS->meleeSetup, 0) == 0 ||
+ MeleeSetup_getFleetValue (pMS->meleeSetup, 1) == 0)
+ {
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ return;
+ }
+
+#ifdef NETPLAY
+ if ((PlayerControl[0] & NETWORK_CONTROL) &&
+ (PlayerControl[1] & NETWORK_CONTROL))
+ {
+ DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 32));
+ // "Only one side at a time can be network controlled."
+ return;
+ }
+
+ if (((PlayerControl[0] & NETWORK_CONTROL) &&
+ (PlayerControl[1] & COMPUTER_CONTROL)) ||
+ ((PlayerControl[0] & COMPUTER_CONTROL) &&
+ (PlayerControl[1] & NETWORK_CONTROL)))
+ {
+ DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 33));
+ // "Netplay with a computer-controlled side is currently
+ // not possible."
+ return;
+ }
+
+ // Check whether all network parties are ready;
+ {
+ COUNT player;
+ bool netReady = true;
+
+ // We collect all error conditions, instead of only reporting
+ // the first one.
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn;
+
+ if (!(PlayerControl[player] & NETWORK_CONTROL))
+ continue;
+
+ conn = netConnections[player];
+ if (conn == NULL || !NetConnection_isConnected (conn))
+ {
+ // Connection for player not established.
+ netReady = false;
+ if (player == 0)
+ DrawMeleeStatusMessage (
+ GAME_STRING (NETMELEE_STRING_BASE + 5));
+ // "Connection for bottom player not "
+ // "established."
+ else
+ DrawMeleeStatusMessage (
+ GAME_STRING (NETMELEE_STRING_BASE + 6));
+ // "Connection for top player not "
+ // "established."
+ }
+ else if (NetConnection_getState (conn) != NetState_inSetup)
+ {
+ // This side may be in the setup, but the network connection
+ // is not in a state that setup information can be sent.
+ netReady = false;
+ if (player == 0)
+ DrawMeleeStatusMessage (
+ GAME_STRING (NETMELEE_STRING_BASE + 14));
+ // "Connection for bottom player not ready."
+ else
+ DrawMeleeStatusMessage (
+ GAME_STRING (NETMELEE_STRING_BASE + 15));
+ // "Connection for top player not ready."
+
+ }
+ }
+ if (!netReady)
+ {
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ return;
+ }
+
+ if (numPlayersReady () != NUM_PLAYERS)
+ DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 7));
+ // "Waiting for remote confirmation."
+ confirmConnections ();
+ }
+#endif
+
+ pMS->InputFunc = DoConfirmSettings;
+}
+
+#ifdef NETPLAY
+
+static BOOLEAN
+DoConnectingDialog (MELEE_STATE *pMS)
+{
+ DWORD TimeIn = GetTimeCounter ();
+ COUNT which_side = (pMS->MeleeOption == NET_TOP) ? 1 : 0;
+ NetConnection *conn;
+
+ /* Cancel any presses of the Pause key. */
+ GamePaused = FALSE;
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
+ if (!pMS->Initialized)
+ {
+ RECT r;
+ FONT oldfont;
+ Color oldcolor;
+ TEXT t;
+
+ // Build a network connection.
+ if (netConnections[which_side] != NULL)
+ closePlayerNetworkConnection (which_side);
+
+ pMS->Initialized = TRUE;
+ conn = openPlayerNetworkConnection (which_side, pMS);
+ pMS->InputFunc = DoConnectingDialog;
+
+ /* Draw the dialog box here */
+ oldfont = SetContextFont (StarConFont);
+ oldcolor = SetContextForeGroundColor (BLACK_COLOR);
+ BatchGraphics ();
+ r.extent.width = 200;
+ r.extent.height = 30;
+ r.corner.x = (SCREEN_WIDTH - r.extent.width) >> 1;
+ r.corner.y = (SCREEN_HEIGHT - r.extent.height) >> 1;
+ DrawShadowedBox (&r, SHADOWBOX_BACKGROUND_COLOR,
+ SHADOWBOX_DARK_COLOR, SHADOWBOX_MEDIUM_COLOR);
+
+ if (NetConnection_getPeerOptions (conn)->isServer)
+ {
+ t.pStr = GAME_STRING (NETMELEE_STRING_BASE + 1);
+ /* "Awaiting incoming connection */
+ }
+ else
+ {
+ t.pStr = GAME_STRING (NETMELEE_STRING_BASE + 2);
+ /* "Awaiting outgoing connection */
+ }
+ t.baseline.y = r.corner.y + 10;
+ t.baseline.x = SCREEN_WIDTH >> 1;
+ t.align = ALIGN_CENTER;
+ t.CharCount = ~0;
+ font_DrawText (&t);
+
+ t.pStr = GAME_STRING (NETMELEE_STRING_BASE + 18);
+ /* "Press SPACE to cancel" */
+ t.baseline.y += 16;
+ font_DrawText (&t);
+
+ // Restore original graphics
+ SetContextFont (oldfont);
+ SetContextForeGroundColor (oldcolor);
+ UnbatchGraphics ();
+ }
+
+ netInput ();
+
+ if (PulsedInputState.menu[KEY_MENU_CANCEL])
+ {
+ // Terminate a network connection.
+ if (netConnections[which_side] != NULL) {
+ closePlayerNetworkConnection (which_side);
+ UpdateMeleeStatusMessage (which_side);
+ }
+ RedrawMeleeFrame ();
+ pMS->InputFunc = DoMelee;
+ pMS->LastInputTime = GetTimeCounter ();
+
+ flushPacketQueues ();
+
+ return TRUE;
+ }
+
+ conn = netConnections[which_side];
+ if (conn != NULL)
+ {
+ NetState status = NetConnection_getState (conn);
+ if ((status == NetState_init) ||
+ (status == NetState_inSetup))
+ {
+ /* Connection complete! */
+ PlayerControl[which_side] = NETWORK_CONTROL | STANDARD_RATING;
+ DrawControls (which_side, TRUE);
+
+ RedrawMeleeFrame ();
+
+ UpdateMeleeStatusMessage (which_side);
+ pMS->InputFunc = DoMelee;
+ pMS->LastInputTime = GetTimeCounter ();
+ Deselect (pMS->MeleeOption);
+ pMS->MeleeOption = START_MELEE;
+ }
+ }
+
+ flushPacketQueues ();
+
+ SleepThreadUntil (TimeIn + ONE_SECOND / 30);
+
+ return TRUE;
+}
+
+/* Check for disconnects, and revert to human control if there is one */
+static void
+check_for_disconnects (MELEE_STATE *pMS)
+{
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn;
+
+ if (!(PlayerControl[player] & NETWORK_CONTROL))
+ continue;
+
+ conn = netConnections[player];
+ if (conn == NULL || !NetConnection_isConnected (conn))
+ {
+ PlayerControl[player] = HUMAN_CONTROL | STANDARD_RATING;
+ DrawControls (player, FALSE);
+ log_add (log_User, "Player %d has disconnected; shifting "
+ "controls\n", player);
+ }
+ }
+
+ (void) pMS;
+}
+
+#endif
+
+static void
+nextControlType (COUNT which_side)
+{
+ switch (PlayerControl[which_side])
+ {
+ case HUMAN_CONTROL | STANDARD_RATING:
+ PlayerControl[which_side] = COMPUTER_CONTROL | STANDARD_RATING;
+ break;
+ case COMPUTER_CONTROL | STANDARD_RATING:
+ PlayerControl[which_side] = COMPUTER_CONTROL | GOOD_RATING;
+ break;
+ case COMPUTER_CONTROL | GOOD_RATING:
+ PlayerControl[which_side] = COMPUTER_CONTROL | AWESOME_RATING;
+ break;
+ case COMPUTER_CONTROL | AWESOME_RATING:
+ PlayerControl[which_side] = HUMAN_CONTROL | STANDARD_RATING;
+ break;
+
+#ifdef NETPLAY
+ case NETWORK_CONTROL | STANDARD_RATING:
+ if (netConnections[which_side] != NULL)
+ closePlayerNetworkConnection (which_side);
+ UpdateMeleeStatusMessage (-1);
+ PlayerControl[which_side] = HUMAN_CONTROL | STANDARD_RATING;
+ break;
+#endif /* NETPLAY */
+ default:
+ log_add (log_Error, "Error: Bad control type (%d) in "
+ "nextControlType().\n", PlayerControl[which_side]);
+ PlayerControl[which_side] = HUMAN_CONTROL | STANDARD_RATING;
+ break;
+ }
+
+ DrawControls (which_side, TRUE);
+}
+
+static MELEE_OPTIONS
+MeleeOptionDown (MELEE_OPTIONS current) {
+ if (current == QUIT_BOT)
+ return QUIT_BOT;
+ return current + 1;
+}
+
+static MELEE_OPTIONS
+MeleeOptionUp (MELEE_OPTIONS current)
+{
+ if (current == TOP_ENTRY)
+ return TOP_ENTRY;
+ return current - 1;
+}
+
+static void
+MeleeOptionSelect (MELEE_STATE *pMS)
+{
+ switch (pMS->MeleeOption)
+ {
+ case START_MELEE:
+ StartMeleeButtonPressed (pMS);
+ break;
+ case LOAD_TOP:
+ case LOAD_BOT:
+ pMS->Initialized = FALSE;
+ pMS->side = pMS->MeleeOption == LOAD_TOP ? 0 : 1;
+ DoLoadTeam (pMS);
+ break;
+ case SAVE_TOP:
+ case SAVE_BOT:
+ pMS->side = pMS->MeleeOption == SAVE_TOP ? 0 : 1;
+ if (MeleeSetup_getFleetValue (pMS->meleeSetup, pMS->side) > 0)
+ DoSaveTeam (pMS);
+ else
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ break;
+ case QUIT_BOT:
+ GLOBAL (CurrentActivity) |= CHECK_ABORT;
+ break;
+#ifdef NETPLAY
+ case NET_TOP:
+ case NET_BOT:
+ {
+ COUNT which_side;
+ BOOLEAN confirmed;
+
+ which_side = pMS->MeleeOption == NET_TOP ? 1 : 0;
+ confirmed = MeleeConnectDialog (which_side);
+ RedrawMeleeFrame ();
+ pMS->LastInputTime = GetTimeCounter ();
+ if (confirmed)
+ {
+ pMS->Initialized = FALSE;
+ pMS->InputFunc = DoConnectingDialog;
+ }
+ break;
+ }
+#endif /* NETPLAY */
+ case CONTROLS_TOP:
+ case CONTROLS_BOT:
+ {
+ COUNT which_side = (pMS->MeleeOption == CONTROLS_TOP) ? 1 : 0;
+ nextControlType (which_side);
+ break;
+ }
+ }
+}
+
+BOOLEAN
+DoMelee (MELEE_STATE *pMS)
+{
+ DWORD TimeIn = GetTimeCounter ();
+ BOOLEAN force_select = FALSE;
+
+ /* Cancel any presses of the Pause key. */
+ GamePaused = FALSE;
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ if (!pMS->Initialized)
+ {
+ if (pMS->hMusic)
+ {
+ StopMusic ();
+ DestroyMusic (pMS->hMusic);
+ pMS->hMusic = 0;
+ }
+ pMS->hMusic = LoadMusic (MELEE_MUSIC);
+ pMS->Initialized = TRUE;
+
+ pMS->MeleeOption = START_MELEE;
+ PlayMusic (pMS->hMusic, TRUE, 1);
+ InitMelee (pMS);
+
+ FadeScreen (FadeAllToColor, ONE_SECOND / 2);
+ pMS->LastInputTime = GetTimeCounter ();
+ return TRUE;
+ }
+
+#ifdef NETPLAY
+ netInput ();
+#endif
+
+ if (PulsedInputState.menu[KEY_MENU_CANCEL] ||
+ PulsedInputState.menu[KEY_MENU_LEFT])
+ {
+ // Start editing the teams.
+ pMS->LastInputTime = GetTimeCounter ();
+ Deselect (pMS->MeleeOption);
+ pMS->MeleeOption = EDIT_MELEE;
+ pMS->Initialized = FALSE;
+ if (PulsedInputState.menu[KEY_MENU_CANCEL])
+ {
+ pMS->side = 0;
+ pMS->row = 0;
+ pMS->col = 0;
+ }
+ else
+ {
+ pMS->side = 0;
+ pMS->row = NUM_MELEE_ROWS - 1;
+ pMS->col = NUM_MELEE_COLUMNS - 1;
+ }
+ DoEdit (pMS);
+ }
+ else
+ {
+ MELEE_OPTIONS NewMeleeOption;
+
+ NewMeleeOption = pMS->MeleeOption;
+ if (PulsedInputState.menu[KEY_MENU_UP])
+ {
+ pMS->LastInputTime = GetTimeCounter ();
+ NewMeleeOption = MeleeOptionUp (pMS->MeleeOption);
+ }
+ else if (PulsedInputState.menu[KEY_MENU_DOWN])
+ {
+ pMS->LastInputTime = GetTimeCounter ();
+ NewMeleeOption = MeleeOptionDown (pMS->MeleeOption);
+ }
+
+ if ((PlayerControl[0] & PlayerControl[1] & PSYTRON_CONTROL)
+ && GetTimeCounter () - pMS->LastInputTime > ONE_SECOND * 10)
+ {
+ force_select = TRUE;
+ NewMeleeOption = START_MELEE;
+ }
+
+ if (NewMeleeOption != pMS->MeleeOption)
+ {
+#ifdef NETPLAY
+ if (pMS->MeleeOption == CONTROLS_TOP ||
+ pMS->MeleeOption == CONTROLS_BOT)
+ UpdateMeleeStatusMessage (-1);
+#endif
+ Deselect (pMS->MeleeOption);
+ pMS->MeleeOption = NewMeleeOption;
+ Select (pMS->MeleeOption);
+#ifdef NETPLAY
+ if (NewMeleeOption == CONTROLS_TOP ||
+ NewMeleeOption == CONTROLS_BOT)
+ {
+ COUNT side = (NewMeleeOption == CONTROLS_TOP) ? 1 : 0;
+ if (PlayerControl[side] & NETWORK_CONTROL)
+ UpdateMeleeStatusMessage (side);
+ else
+ UpdateMeleeStatusMessage (-1);
+ }
+#endif
+ }
+
+ if (PulsedInputState.menu[KEY_MENU_SELECT] || force_select)
+ {
+ MeleeOptionSelect (pMS);
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+ }
+ }
+
+#ifdef NETPLAY
+ flushPacketQueues ();
+
+ check_for_disconnects (pMS);
+#endif
+
+ Melee_flashSelection (pMS);
+
+ SleepThreadUntil (TimeIn + ONE_SECOND / 30);
+
+ return TRUE;
+}
+
+static int
+LoadMeleeConfig (MELEE_STATE *pMS)
+{
+ uio_Stream *stream;
+ int status;
+ COUNT side;
+
+ stream = uio_fopen (configDir, "melee.cfg", "rb");
+ if (stream == NULL)
+ goto err;
+
+ {
+ struct stat sb;
+
+ if (uio_fstat(uio_streamHandle(stream), &sb) == -1)
+ goto err;
+ if ((size_t) sb.st_size != (1 + MeleeTeam_serialSize) * NUM_SIDES)
+ goto err;
+ }
+
+ for (side = 0; side < NUM_SIDES; side++)
+ {
+ status = uio_getc (stream);
+ if (status == EOF)
+ goto err;
+ PlayerControl[side] = (BYTE) status;
+ // XXX: insert sanity check on PlanetControl here.
+
+ if (MeleeSetup_deserializeTeam (pMS->meleeSetup, side, stream) == -1)
+ goto err;
+
+ /* Do not allow netplay mode at the start. */
+ if (PlayerControl[side] & NETWORK_CONTROL)
+ PlayerControl[side] = HUMAN_CONTROL | STANDARD_RATING;
+ }
+
+ uio_fclose (stream);
+ return 0;
+
+err:
+ if (stream)
+ uio_fclose (stream);
+ return -1;
+}
+
+static int
+WriteMeleeConfig (MELEE_STATE *pMS)
+{
+ uio_Stream *stream;
+ COUNT side;
+
+ stream = res_OpenResFile (configDir, "melee.cfg", "wb");
+ if (stream == NULL)
+ goto err;
+
+ for (side = 0; side < NUM_SIDES; side++)
+ {
+ if (uio_putc (PlayerControl[side], stream) == EOF)
+ goto err;
+
+ if (MeleeSetup_serializeTeam (pMS->meleeSetup, side, stream) == -1)
+ goto err;
+ }
+
+ if (!res_CloseResFile (stream))
+ goto err;
+
+ return 0;
+
+err:
+ if (stream)
+ {
+ res_CloseResFile (stream);
+ DeleteResFile (configDir, "melee.cfg");
+ }
+ return -1;
+}
+
+void
+Melee (void)
+{
+ InitGlobData ();
+ {
+ MELEE_STATE MenuState;
+
+ pMeleeState = &MenuState;
+ memset (pMeleeState, 0, sizeof (*pMeleeState));
+
+ MenuState.InputFunc = DoMelee;
+ MenuState.Initialized = FALSE;
+
+ MenuState.meleeSetup = MeleeSetup_new ();
+
+ MenuState.randomContext = RandomContext_New ();
+ RandomContext_SeedRandom (MenuState.randomContext,
+ GetTimeCounter ());
+ // Using the current time still leaves the random state a bit
+ // predictable, but it is good enough.
+
+#ifdef NETPLAY
+ {
+ COUNT player;
+ for (player = 0; player < NUM_PLAYERS; player++)
+ netConnections[player] = NULL;
+ }
+#endif
+
+ MenuState.currentShip = MELEE_NONE;
+ MenuState.CurIndex = MELEE_STATE_INDEX_DONE;
+ InitMeleeLoadState (&MenuState);
+
+ GLOBAL (CurrentActivity) = SUPER_MELEE;
+
+ GameSounds = CaptureSound (LoadSound (GAME_SOUNDS));
+ LoadMeleeInfo (&MenuState);
+ if (LoadMeleeConfig (&MenuState) == -1)
+ {
+ PlayerControl[0] = HUMAN_CONTROL | STANDARD_RATING;
+ Melee_LocalChange_team (&MenuState, 0,
+ MenuState.load.preBuiltList[0]);
+ PlayerControl[1] = COMPUTER_CONTROL | STANDARD_RATING;
+ Melee_LocalChange_team (&MenuState, 1,
+ MenuState.load.preBuiltList[1]);
+ }
+
+ MenuState.side = 0;
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ DoInput (&MenuState, TRUE);
+
+ StopMusic ();
+ WaitForSoundEnd (TFBSOUND_WAIT_ALL);
+
+ WriteMeleeConfig (&MenuState);
+ FreeMeleeInfo (&MenuState);
+ DestroySound (ReleaseSound (GameSounds));
+ GameSounds = 0;
+
+ UninitMeleeLoadState (&MenuState);
+
+ RandomContext_Delete (MenuState.randomContext);
+
+ MeleeSetup_delete (MenuState.meleeSetup);
+
+ FlushInput ();
+ }
+}
+
+#ifdef NETPLAY
+void
+updateRandomSeed (MELEE_STATE *pMS, COUNT side, DWORD seed)
+{
+ TFB_SeedRandom (seed);
+ (void) pMS;
+ (void) side;
+}
+
+// The remote player has done something which invalidates our confirmation.
+void
+confirmationCancelled (MELEE_STATE *pMS, COUNT side)
+{
+ if (side == 0)
+ DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 16));
+ // "Bottom player changed something -- need to reconfirm."
+ else
+ DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 17));
+ // "Top player changed something -- need to reconfirm."
+
+ if (pMS->InputFunc == DoConfirmSettings)
+ pMS->InputFunc = DoMelee;
+}
+
+static void
+connectionFeedback (NetConnection *conn, const char *str, bool forcePopup) {
+ struct battlestate_struct *bs = NetMelee_getBattleState (conn);
+
+ if (bs == NULL && !forcePopup)
+ {
+ // bs == NULL means the game has not started yet.
+ DrawMeleeStatusMessage (str);
+ }
+ else
+ {
+ DoPopupWindow (str);
+ }
+}
+
+void
+connectedFeedback (NetConnection *conn) {
+ if (NetConnection_getPlayerNr (conn) == 0)
+ connectionFeedback (conn, GAME_STRING (NETMELEE_STRING_BASE + 8),
+ false);
+ // "Bottom player is connected."
+ else
+ connectionFeedback (conn, GAME_STRING (NETMELEE_STRING_BASE + 9),
+ false);
+ // "Top player is connected."
+
+ PlayMenuSound (MENU_SOUND_INVOKED);
+}
+
+static const char *
+abortReasonString (NetplayAbortReason reason)
+{
+ switch (reason)
+ {
+ case AbortReason_unspecified:
+ return GAME_STRING (NETMELEE_STRING_BASE + 25);
+ // "Disconnect for an unspecified reason.'
+ case AbortReason_versionMismatch:
+ return GAME_STRING (NETMELEE_STRING_BASE + 26);
+ // "Connection aborted due to version mismatch."
+ case AbortReason_invalidHash:
+ return GAME_STRING (NETMELEE_STRING_BASE + 27);
+ // "Connection aborted because the remote side sent a "
+ // "fake signature."
+ case AbortReason_protocolError:
+ return GAME_STRING (NETMELEE_STRING_BASE + 28);
+ // "Connection aborted due to an internal protocol "
+ // "error."
+ }
+
+ return NULL;
+ // Should not happen.
+}
+
+void
+abortFeedback (NetConnection *conn, NetplayAbortReason reason)
+{
+ const char *msg;
+
+ msg = abortReasonString (reason);
+ if (msg != NULL)
+ connectionFeedback (conn, msg, true);
+}
+
+static const char *
+resetReasonString (NetplayResetReason reason)
+{
+ switch (reason)
+ {
+ case ResetReason_unspecified:
+ return GAME_STRING (NETMELEE_STRING_BASE + 29);
+ // "Game aborted for an unspecified reason."
+ case ResetReason_syncLoss:
+ return GAME_STRING (NETMELEE_STRING_BASE + 30);
+ // "Game aborted due to loss of synchronisation."
+ case ResetReason_manualReset:
+ return GAME_STRING (NETMELEE_STRING_BASE + 31);
+ // "Game aborted by the remote player."
+ }
+
+ return NULL;
+ // Should not happen.
+}
+
+void
+resetFeedback (NetConnection *conn, NetplayResetReason reason,
+ bool byRemote)
+{
+ const char *msg;
+
+ flushPacketQueues ();
+ // If the local side queued a reset packet as a result of a
+ // remote reset, that packet will not have been sent yet.
+ // We flush the queue now, so that the remote side won't be
+ // waiting for the reset packet while this side is waiting
+ // for an acknowledgement of the feedback message.
+
+ if (reason == ResetReason_manualReset && !byRemote) {
+ // No message needed, the player initiated the reset.
+ return;
+ }
+
+ msg = resetReasonString (reason);
+ if (msg != NULL)
+ connectionFeedback (conn, msg, false);
+
+ // End supermelee. This must not be done before connectionFeedback(),
+ // otherwise the message will immediately disappear.
+ GLOBAL (CurrentActivity) |= CHECK_ABORT;
+}
+
+void
+errorFeedback (NetConnection *conn)
+{
+ if (NetConnection_getPlayerNr (conn) == 0)
+ connectionFeedback (conn, GAME_STRING (NETMELEE_STRING_BASE + 10),
+ false);
+ // "Bottom player: connection failed."
+ else
+ connectionFeedback (conn, GAME_STRING (NETMELEE_STRING_BASE + 11),
+ false);
+ // "Top player: connection failed."
+}
+
+void
+closeFeedback (NetConnection *conn)
+{
+ if (NetConnection_getPlayerNr (conn) == 0)
+ connectionFeedback (conn, GAME_STRING (NETMELEE_STRING_BASE + 12),
+ false);
+ // "Bottom player: connection closed."
+ else
+ connectionFeedback (conn, GAME_STRING (NETMELEE_STRING_BASE + 13),
+ false);
+ // "Top player: connection closed."
+}
+
+#endif /* NETPLAY */
+
+
+///////////////////////////////////////////////////////////////////////////
+
+// Melee_UpdateView_xxx() functions are called when some value in the
+// supermelee fleet setup screen needs to be updated visually.
+
+static void
+Melee_UpdateView_fleetValue (MELEE_STATE *pMS, COUNT side)
+{
+ if (pMS->meleeStarted)
+ return;
+
+ DrawFleetValue (pMS, side, DTSHS_REPAIR);
+ // BUG: The fleet value is always drawn as deselected.
+}
+
+static void
+Melee_UpdateView_ship (MELEE_STATE *pMS, COUNT side, FleetShipIndex index)
+{
+ MeleeShip ship;
+
+ if (pMS->meleeStarted)
+ return;
+
+ ship = MeleeSetup_getShip (pMS->meleeSetup, side, index);
+
+ if (ship == MELEE_NONE)
+ {
+ ClearShipBox (side, index);
+ }
+ else
+ {
+ DrawShipBox (side, index, ship, FALSE);
+ }
+}
+
+static void
+Melee_UpdateView_teamName (MELEE_STATE *pMS, COUNT side)
+{
+ if (pMS->meleeStarted)
+ return;
+
+ DrawTeamString (pMS, side, DTSHS_REPAIR, NULL);
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+// Melee_Change_xxx() functions are helper functions, called when some value
+// in the supermelee fleet setup screen has changed, eithed because of a
+// local change, or a remote change.
+
+static bool
+Melee_Change_ship (MELEE_STATE *pMS, COUNT side, FleetShipIndex index,
+ MeleeShip ship)
+{
+ if (!MeleeSetup_setShip (pMS->meleeSetup, side, index, ship))
+ {
+ // No change.
+ return false;
+ }
+
+ // Update the view
+ Melee_UpdateView_ship (pMS, side, index);
+ Melee_UpdateView_fleetValue (pMS, side);
+
+ // If the modified slot is currently selected, display the new ship icon
+ // on the right of the screen.
+ if (isShipSlotSelected (pMS, side, index))
+ {
+ pMS->currentShip = ship;
+ DrawMeleeShipStrings (pMS, ship);
+ }
+
+ return true;
+}
+
+// Pre: 'name' is '\0'-terminated
+static bool
+Melee_Change_teamName (MELEE_STATE *pMS, COUNT side, const char *name)
+{
+ MeleeSetup *setup = pMS->meleeSetup;
+
+ if (!MeleeSetup_setTeamName (setup, side, name))
+ {
+ // No change.
+ return false;
+ }
+
+ if (pMS->row != NUM_MELEE_ROWS || pMS->side != side ||
+ pMeleeState->CurIndex == MELEE_STATE_INDEX_DONE)
+ {
+ // The team name is not currently being edited, so we can
+ // update it on screen. If it was edited, then this function
+ // will be called again after it is done.
+ Melee_UpdateView_teamName (pMS, side);
+ }
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+// Melee_LocalChange_xxx() functions are called when some value in the
+// supermelee fleet setup screen has changed because of a local action.
+// The behavior of these functions (and the comments therein) follow the
+// description in doc/devel/netplay/protocol.
+
+bool
+Melee_LocalChange_ship (MELEE_STATE *pMS, COUNT side, FleetShipIndex index,
+ MeleeShip ship)
+{
+ if (!Melee_Change_ship (pMS, side, index, ship))
+ return false;
+
+#ifdef NETPLAY
+ {
+ MeleeSetup *setup = pMS->meleeSetup;
+
+ MeleeShip sentShip = MeleeSetup_getSentShip (setup, side, index);
+ if (sentShip == MELEE_UNSET)
+ {
+ // State 1.
+ // Notify network connections of the change.
+ Netplay_NotifyAll_setShip (pMS, side, index);
+ MeleeSetup_setSentShip (setup, side, index, ship);
+ }
+ }
+#endif /* NETPLAY */
+
+ return true;
+}
+
+
+// Pre: 'name' is '\0'-terminated
+bool
+Melee_LocalChange_teamName (MELEE_STATE *pMS, COUNT side, const char *name)
+{
+ if (!Melee_Change_teamName (pMS, side, name))
+ return false;
+
+#ifdef NETPLAY
+ {
+ MeleeSetup *setup = pMS->meleeSetup;
+
+ const char *sentName = MeleeSetup_getSentTeamName (setup, side);
+ if (sentName == NULL)
+ {
+ // State 1.
+ // Notify network connections of the change.
+ Netplay_NotifyAll_setTeamName (pMS, side);
+ MeleeSetup_setSentTeamName (setup, side, name);
+ }
+ }
+#endif /* NETPLAY */
+
+ return true;
+}
+
+bool
+Melee_LocalChange_fleet (MELEE_STATE *pMS, size_t teamNr,
+ const MeleeShip *fleet)
+{
+ FleetShipIndex slotI;
+ bool changed = false;
+
+ for (slotI = 0; slotI < MELEE_FLEET_SIZE; slotI++)
+ {
+ if (Melee_LocalChange_ship (pMS, teamNr, slotI, fleet[slotI]))
+ changed = true;
+ }
+ return changed;
+}
+
+bool
+Melee_LocalChange_team (MELEE_STATE *pMS, size_t teamNr,
+ const MeleeTeam *team)
+{
+ const MeleeShip *fleet = MeleeTeam_getFleet (team);
+ const char *name = MeleeTeam_getTeamName (team);
+ bool changed = false;
+
+ if (Melee_LocalChange_fleet (pMS, teamNr, fleet))
+ changed = true;
+ if (Melee_LocalChange_teamName (pMS, teamNr, name))
+ changed = true;
+
+ return changed;
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+#ifdef NETPLAY
+
+// Send the entire team to the remote side. Used when the connection has
+// just been established, or after the setup menu is reentered after battle.
+void
+Melee_bootstrapSyncTeam (MELEE_STATE *meleeState, size_t teamNr)
+{
+ MeleeSetup *setup = meleeState->meleeSetup;
+ FleetShipIndex slotI;
+ const char *teamName;
+
+ // Send the current fleet.
+ Netplay_NotifyAll_setFleet(meleeState, teamNr);
+
+ // Update the last sent fleet.
+ for (slotI = 0; slotI < MELEE_FLEET_SIZE; slotI++)
+ {
+ MeleeShip ship = MeleeSetup_getShip (setup, teamNr, slotI);
+ assert (MeleeSetup_getSentShip (setup, teamNr, slotI) == MELEE_UNSET);
+ MeleeSetup_setSentShip (setup, teamNr, slotI, ship);
+ }
+
+ // Send the current team name.
+ Netplay_NotifyAll_setTeamName (meleeState, teamNr);
+
+ // Update the last sent team name.
+ teamName = MeleeSetup_getTeamName (setup, teamNr);
+ MeleeSetup_setSentTeamName (setup, teamNr, teamName);
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+// Melee_RemoteChange_xxx() functions are called when some value in the
+// supermelee fleet setup screen has changed remotely.
+// The behavior of these functions (and the comments therein) follow the
+// description in doc/devel/netplay/protocol.
+
+void
+Melee_RemoteChange_ship (MELEE_STATE *pMS, NetConnection *conn, COUNT side,
+ FleetShipIndex index, MeleeShip ship)
+{
+ MeleeSetup *setup = pMS->meleeSetup;
+
+ MeleeShip sentShip = MeleeSetup_getSentShip (setup, side, index);
+ MeleeShip currentShip;
+
+ if (sentShip == MELEE_UNSET)
+ {
+ // State 1
+
+ // Change the ship locally.
+ Melee_Change_ship (pMS, side, index, ship);
+
+ // Notify the remote side.
+ Netplay_NotifyAll_setShip (pMS, side, index);
+
+ // A packet has now been received and sent. End of turn.
+ return;
+ }
+
+ // A packet has been sent and received. End of turn.
+ MeleeSetup_setSentShip (setup, side, index, MELEE_UNSET);
+
+ if (ship != sentShip)
+ {
+ // Rule 2c or 3d. The value which we sent is different from the value
+ // which the opponent sent. We need a tie-breaker to determine which
+ // value prevails.
+ if (NetConnection_getPlayerNr (conn) != side)
+ {
+ // Rule 2c+ or 3d+
+ // We win the tie-breaker. The value which we sent prevails.
+ }
+ else
+ {
+ // Rule 2c- or 3d-.
+ // We lose the tie-breaker. We adopt the remote value.
+ Melee_Change_ship (pMS, side, index, ship);
+ return;
+ }
+ }
+ /*
+ else
+ {
+ // Rule 2b or 3c. The value which we sent is the value which
+ // the opponent sent. This confirms the value.
+ }
+ */
+
+ // Rule 2b, 2c+, 3c, or 3d+. The value which we sent is confirmed.
+
+ currentShip = MeleeSetup_getShip (setup, side, index);
+ if (currentShip != sentShip)
+ {
+ // Rule 3c or 3d+. We had a local change which was yet
+ // unreported.
+
+ // Notify the remote side and keep track of what we sent.
+ Netplay_NotifyAll_setShip (pMS, side, index);
+ MeleeSetup_setSentShip (setup, side, index, ship);
+ }
+}
+
+void
+Melee_RemoteChange_teamName (MELEE_STATE *pMS, NetConnection *conn,
+ COUNT side, const char *newName)
+{
+ MeleeSetup *setup = pMS->meleeSetup;
+
+ const char *sentName = MeleeSetup_getSentTeamName (setup, side);
+ const char *currentName;
+
+ if (sentName == NULL)
+ {
+ // State 1
+
+ // Change the team name locally.
+ Melee_Change_teamName (pMS, side, newName);
+
+ // Notify the remote side.
+ Netplay_NotifyAll_setTeamName (pMS, side);
+
+ // A packet has now been received and sent. End of turn.
+ // The sent team name is still unset, so we don't have to reset it.
+ return;
+ }
+
+ if (strcmp (newName, sentName) == 0)
+ {
+ // Rule 2c or 3d. The value which we sent is different from the value
+ // which the opponent sent. We need a tie-breaker to determine which
+ // value prevails.
+ if (NetConnection_getPlayerNr (conn) != side)
+ {
+ // Rule 2c+ or 3d+
+ // We win the tie-breaker. The value which we sent prevails.
+ }
+ else
+ {
+ // Rule 2c- or 3d-.
+ // We lose the tie-breaker. We adopt the remote value.
+ Melee_Change_teamName (pMS, side, newName);
+ MeleeSetup_setSentTeamName (setup, side, NULL);
+ return;
+ }
+ }
+ /*
+ else
+ {
+ // Rule 2b or 3c. The value which we sent is the value which
+ // the opponent sent. This confirms the value.
+ }
+ */
+
+ // Rule 2b, 2c+, 3c, or 3d+. The value which we sent is confirmed.
+
+ currentName = MeleeSetup_getTeamName (setup, side);
+ if (strcmp (currentName, sentName) != 0)
+ {
+ // Rule 3c or 3d+. We had a local change which was yet
+ // unreported.
+
+ // A packet has been sent and received, which ends the turn.
+ // We don't bother clearing the sent team name, as we're going
+ // to send a new packet immediately.
+
+ // Notify the remote side and keep track of what we sent.
+ Netplay_NotifyAll_setTeamName (pMS, side);
+
+ // Update the last sent message.
+ MeleeSetup_setSentTeamName (setup, side, newName);
+ }
+ else
+ {
+ // A packet has been sent and received. End of turn.
+ MeleeSetup_setSentTeamName (setup, side, NULL);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+#endif /* NETPLAY */
+
diff --git a/src/uqm/supermelee/melee.h b/src/uqm/supermelee/melee.h
new file mode 100644
index 0000000..e6026a3
--- /dev/null
+++ b/src/uqm/supermelee/melee.h
@@ -0,0 +1,144 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef UQM_SUPERMELEE_MELEE_H_
+#define UQM_SUPERMELEE_MELEE_H_
+
+#include "../init.h"
+#include "libs/gfxlib.h"
+#include "libs/mathlib.h"
+#include "libs/sndlib.h"
+#include "libs/timelib.h"
+#include "libs/reslib.h"
+#include "netplay/packet.h"
+ // for NetplayAbortReason and NetplayResetReason.
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct melee_state MELEE_STATE;
+
+#define NUM_MELEE_ROWS 2
+#define NUM_MELEE_COLUMNS 7
+//#define NUM_MELEE_COLUMNS 6
+#define MELEE_FLEET_SIZE (NUM_MELEE_ROWS * NUM_MELEE_COLUMNS)
+#define ICON_WIDTH 16
+#define ICON_HEIGHT 16
+
+extern FRAME PickMeleeFrame;
+
+#define PICK_BG_COLOR BUILD_COLOR (MAKE_RGB15 (0x00, 0x01, 0x0F), 0x01)
+#define PICK_VALUE_COLOR BUILD_COLOR (MAKE_RGB15 (0x13, 0x00, 0x00), 0x2C)
+ // Used for the current fleet value in the next ship selection
+ // in SuperMelee.
+
+#define MAX_TEAM_CHARS 30
+#define NUM_PICK_COLS 5
+#define NUM_PICK_ROWS 5
+
+typedef BYTE MELEE_OPTIONS;
+
+#if defined(__cplusplus)
+}
+#endif
+
+#include "loadmele.h"
+#include "meleesetup.h"
+#include "meleeship.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct melee_state
+{
+ BOOLEAN (*InputFunc) (struct melee_state *pInputState);
+
+ BOOLEAN Initialized;
+ BOOLEAN meleeStarted;
+ MELEE_OPTIONS MeleeOption;
+ COUNT side;
+ COUNT row;
+ COUNT col;
+ MeleeSetup *meleeSetup;
+ struct melee_load_state load;
+ MeleeShip currentShip;
+ // The ship currently displayed. Not really needed.
+ // Also the current ship position when selecting a ship.
+ COUNT CurIndex;
+#define MELEE_STATE_INDEX_DONE ((COUNT) -1)
+ // Current position in the team string when editing it.
+ // Set to MELEE_STATE_INDEX_DONE when done.
+ BOOLEAN buildPickConfirmed;
+ // Used by DoPickShip () to communicate to the calling
+ // function BuildPickShip() whether a ship has been selected
+ // to add to the fleet, or whether the operation has been
+ // cancelled. If a ship was selected, it is set in
+ // currentShip.
+ RandomContext *randomContext;
+ /* RNG state for all local random decisions, i.e. those
+ * decisions that are not shared among network parties. */
+ TimeCount LastInputTime;
+
+ MUSIC_REF hMusic;
+};
+
+extern void Melee (void);
+
+// Some prototypes for use by loadmele.c:
+BOOLEAN DoMelee (MELEE_STATE *pMS);
+void DrawMeleeIcon (COUNT which_icon);
+void GetShipBox (RECT *pRect, COUNT side, COUNT row, COUNT col);
+void RepairMeleeFrame (const RECT *pRect);
+void DrawMeleeShipStrings (MELEE_STATE *pMS, MeleeShip NewStarShip);
+extern FRAME MeleeFrame;
+void Melee_flashSelection (MELEE_STATE *pMS);
+
+COUNT GetShipValue (MeleeShip StarShip);
+
+void updateRandomSeed (MELEE_STATE *pMS, COUNT side, DWORD seed);
+void confirmationCancelled(MELEE_STATE *pMS, COUNT side);
+void connectedFeedback (NetConnection *conn);
+void abortFeedback (NetConnection *conn, NetplayAbortReason reason);
+void resetFeedback (NetConnection *conn, NetplayResetReason reason,
+ bool byRemote);
+void errorFeedback (NetConnection *conn);
+void closeFeedback (NetConnection *conn);
+
+bool Melee_LocalChange_ship (MELEE_STATE *pMS, COUNT side,
+ FleetShipIndex index, MeleeShip ship);
+bool Melee_LocalChange_teamName (MELEE_STATE *pMS, COUNT side,
+ const char *name);
+bool Melee_LocalChange_fleet (MELEE_STATE *pMS, size_t teamNr,
+ const MeleeShip *fleet);
+bool Melee_LocalChange_team (MELEE_STATE *pMS, size_t teamNr,
+ const MeleeTeam *team);
+
+void Melee_bootstrapSyncTeam (MELEE_STATE *pMS, size_t teamNr);
+
+void Melee_RemoteChange_ship (MELEE_STATE *pMS, NetConnection *conn,
+ COUNT side, FleetShipIndex index, MeleeShip ship);
+void Melee_RemoteChange_teamName (MELEE_STATE *pMS, NetConnection *conn,
+ COUNT side, const char *name);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_MELEE_H_ */
diff --git a/src/uqm/supermelee/meleesetup.c b/src/uqm/supermelee/meleesetup.c
new file mode 100644
index 0000000..a45f172
--- /dev/null
+++ b/src/uqm/supermelee/meleesetup.c
@@ -0,0 +1,440 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define MELEESETUP_INTERNAL
+#include "port.h"
+#include "meleesetup.h"
+
+#include "../master.h"
+#include "libs/log.h"
+
+
+///////////////////////////////////////////////////////////////////////////
+
+// Temporary
+const size_t MeleeTeam_serialSize = MELEE_FLEET_SIZE +
+ sizeof (((MeleeTeam*)0)->name);
+
+void
+MeleeTeam_init (MeleeTeam *team)
+{
+ FleetShipIndex slotI;
+
+ for (slotI = 0; slotI < MELEE_FLEET_SIZE; slotI++)
+ team->ships[slotI] = MELEE_NONE;
+
+ team->name[0] = '\0';
+}
+
+void
+MeleeTeam_uninit (MeleeTeam *team)
+{
+ (void) team;
+}
+
+MeleeTeam *
+MeleeTeam_new (void)
+{
+ MeleeTeam *result = HMalloc (sizeof (MeleeTeam));
+ MeleeTeam_init (result);
+ return result;
+}
+
+void
+MeleeTeam_delete (MeleeTeam *team)
+{
+ MeleeTeam_uninit (team);
+ HFree (team);
+}
+
+int
+MeleeTeam_serialize (const MeleeTeam *team, uio_Stream *stream)
+{
+ FleetShipIndex slotI;
+
+ for (slotI = 0; slotI < MELEE_FLEET_SIZE; slotI++) {
+ if (uio_putc ((int) team->ships[slotI], stream) == EOF)
+ return -1;
+ }
+ if (uio_fwrite ((const char *) team->name, sizeof team->name, 1,
+ stream) != 1)
+ return -1;
+
+ return 0;
+}
+
+int
+MeleeTeam_deserialize (MeleeTeam *team, uio_Stream *stream)
+{
+ FleetShipIndex slotI;
+
+ // Sanity check on the ships.
+ for (slotI = 0; slotI < MELEE_FLEET_SIZE; slotI++)
+ {
+ int ship = uio_getc (stream);
+ if (ship == EOF)
+ goto err;
+ team->ships[slotI] = (MeleeShip) ship;
+
+ if (team->ships[slotI] == MELEE_NONE)
+ continue;
+
+ if (team->ships[slotI] >= NUM_MELEE_SHIPS)
+ {
+ log_add (log_Warning, "Invalid ship type in loaded team (index "
+ "%d, ship type is %d, max valid is %d).",
+ slotI, team->ships[slotI], NUM_MELEE_SHIPS - 1);
+ team->ships[slotI] = MELEE_NONE;
+ }
+ }
+
+ if (uio_fread (team->name, sizeof team->name, 1, stream) != 1)
+ goto err;
+
+ team->name[MAX_TEAM_CHARS] = '\0';
+
+ return 0;
+
+err:
+ MeleeTeam_delete(team);
+ return -1;
+}
+
+// XXX: move this to elsewhere?
+COUNT
+MeleeTeam_getValue (const MeleeTeam *team)
+{
+ COUNT total = 0;
+ FleetShipIndex slotI;
+
+ for (slotI = 0; slotI < MELEE_FLEET_SIZE; slotI++)
+ {
+ MeleeShip ship = team->ships[slotI];
+ COUNT shipValue = GetShipValue (ship);
+ if (shipValue == (COUNT)~0)
+ {
+ // Invalid ship.
+ continue;
+ }
+ total += shipValue;
+ }
+
+ return total;
+}
+
+MeleeShip
+MeleeTeam_getShip (const MeleeTeam *team, FleetShipIndex slotNr)
+{
+ return team->ships[slotNr];
+}
+
+void
+MeleeTeam_setShip (MeleeTeam *team, FleetShipIndex slotNr, MeleeShip ship)
+{
+ team->ships[slotNr] = ship;
+}
+
+const MeleeShip *
+MeleeTeam_getFleet (const MeleeTeam *team)
+{
+ return team->ships;
+}
+
+const char *
+MeleeTeam_getTeamName (const MeleeTeam *team)
+{
+ return team->name;
+}
+
+// Returns true iff the state has actually changed.
+void
+MeleeTeam_setName (MeleeTeam *team, const char *name)
+{
+ strncpy (team->name, name, sizeof team->name - 1);
+ team->name[sizeof team->name - 1] = '\0';
+}
+
+void
+MeleeTeam_copy (MeleeTeam *copy, const MeleeTeam *original)
+{
+ *copy = *original;
+}
+
+#if 0
+bool
+MeleeTeam_isEqual (const MeleeTeam *team1, const MeleeTeam *team2)
+{
+ const MeleeShip *fleet1;
+ const MeleeShip *fleet2;
+ FleetShipIndex slotI;
+
+ if (strcmp (team1->name, team2->name) != 0)
+ return false;
+
+ fleet1 = team1->ships;
+ fleet2 = team2->ships;
+
+ for (slotI = 0; slotI < MELEE_FLEET_SIZE; slotI++)
+ {
+ if (fleet1[slotI] != fleet2[slotI])
+ return false;
+ }
+
+ return true;
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////
+
+#ifdef NETPLAY
+static void
+MeleeSetup_initSentTeam (MeleeSetup *setup, size_t teamNr)
+{
+ MeleeTeam *team = &setup->sentTeams[teamNr];
+ FleetShipIndex slotI;
+
+ for (slotI = 0; slotI < MELEE_FLEET_SIZE; slotI++)
+ MeleeTeam_setShip (team, slotI, MELEE_UNSET);
+
+ setup->haveSentTeamName[teamNr] = false;
+#ifdef DEBUG
+ // The actual team name should be irrelevant if haveSentTeamName is
+ // set to false. In a debug build, we set it to invalid, so that
+ // it is more likely that it will be noticed if it is ever used.
+ MeleeTeam_setName (team, "<INVALID>");
+#endif /* DEBUG */
+}
+#endif /* NETPLAY */
+
+MeleeSetup *
+MeleeSetup_new (void)
+{
+ size_t teamI;
+ MeleeSetup *result = HMalloc (sizeof (MeleeSetup));
+ if (result == NULL)
+ return NULL;
+
+ for (teamI = 0; teamI < NUM_SIDES; teamI++)
+ {
+ MeleeTeam_init (&result->teams[teamI]);
+ result->fleetValue[teamI] = 0;
+#ifdef NETPLAY
+ MeleeSetup_initSentTeam (result, teamI);
+#endif /* NETPLAY */
+ }
+ return result;
+}
+
+void
+MeleeSetup_delete (MeleeSetup *setup)
+{
+ HFree (setup);
+}
+
+#ifdef NETPLAY
+void
+MeleeSetup_resetSentTeams (MeleeSetup *setup)
+{
+ size_t teamI;
+
+ for (teamI = 0; teamI < NUM_SIDES; teamI++)
+ MeleeSetup_initSentTeam (setup, teamI);
+}
+#endif /* NETPLAY */
+
+// Returns true iff the state has actually changed.
+bool
+MeleeSetup_setShip (MeleeSetup *setup, size_t teamNr, FleetShipIndex slotNr,
+ MeleeShip ship)
+{
+ MeleeTeam *team = &setup->teams[teamNr];
+ MeleeShip oldShip = MeleeTeam_getShip (team, slotNr);
+
+ if (ship == oldShip)
+ return false;
+
+ if (oldShip != MELEE_NONE)
+ setup->fleetValue[teamNr] -= GetShipCostFromIndex (oldShip);
+
+ MeleeTeam_setShip (team, slotNr, ship);
+
+ if (ship != MELEE_NONE)
+ setup->fleetValue[teamNr] += GetShipCostFromIndex (ship);
+
+ return true;
+}
+
+MeleeShip
+MeleeSetup_getShip (const MeleeSetup *setup, size_t teamNr,
+ FleetShipIndex slotNr)
+{
+ return MeleeTeam_getShip (&setup->teams[teamNr], slotNr);
+}
+
+const MeleeShip *
+MeleeSetup_getFleet (const MeleeSetup *setup, size_t teamNr)
+{
+ return MeleeTeam_getFleet (&setup->teams[teamNr]);
+}
+
+// Returns true iff the state has actually changed.
+bool
+MeleeSetup_setTeamName (MeleeSetup *setup, size_t teamNr,
+ const char *name)
+{
+ MeleeTeam *team = &setup->teams[teamNr];
+ const char *oldName = MeleeTeam_getTeamName (team);
+
+ if (strcmp (oldName, name) == 0)
+ return false;
+
+ MeleeTeam_setName (team, name);
+ return true;
+}
+
+// NB. This function returns a pointer to a static buffer, which is
+// overwritten by calls to MeleeSetup_setTeamName().
+const char *
+MeleeSetup_getTeamName (const MeleeSetup *setup, size_t teamNr)
+{
+ return MeleeTeam_getTeamName (&setup->teams[teamNr]);
+}
+
+COUNT
+MeleeSetup_getFleetValue (const MeleeSetup *setup, size_t teamNr)
+{
+ return setup->fleetValue[teamNr];
+}
+
+int
+MeleeSetup_deserializeTeam (MeleeSetup *setup, size_t teamNr,
+ uio_Stream *stream)
+{
+ MeleeTeam *team = &setup->teams[teamNr];
+ int ret = MeleeTeam_deserialize (team, stream);
+ if (ret == 0)
+ setup->fleetValue[teamNr] = MeleeTeam_getValue (team);
+ return ret;
+}
+
+int
+MeleeSetup_serializeTeam (const MeleeSetup *setup, size_t teamNr,
+ uio_Stream *stream)
+{
+ const MeleeTeam *team = &setup->teams[teamNr];
+ return MeleeTeam_serialize (team, stream);
+}
+
+#ifdef NETPLAY
+MeleeShip
+MeleeSetup_getSentShip (const MeleeSetup *setup, size_t teamNr,
+ FleetShipIndex slotNr)
+{
+ return MeleeTeam_getShip (&setup->sentTeams[teamNr], slotNr);
+}
+
+// Returns NULL if there is no team name set. This is not the same
+// as when an empty (zero-length) team name is set.
+// NB. This function returns a pointer to a static buffer, which is
+// overwritten by calls to MeleeSetup_setSentTeamName().
+const char *
+MeleeSetup_getSentTeamName (const MeleeSetup *setup, size_t teamNr)
+{
+ if (!setup->haveSentTeamName[teamNr])
+ return NULL;
+
+ return MeleeTeam_getTeamName (&setup->sentTeams[teamNr]);
+}
+
+// Returns true iff the state has actually changed.
+bool
+MeleeSetup_setSentShip (MeleeSetup *setup, size_t teamNr,
+ FleetShipIndex slotNr, MeleeShip ship)
+{
+ MeleeTeam *team = &setup->sentTeams[teamNr];
+ MeleeShip oldShip = MeleeTeam_getShip (team, slotNr);
+
+ if (ship == oldShip)
+ return false;
+
+ MeleeTeam_setShip (team, slotNr, ship);
+ return true;
+}
+
+// Returns true iff the state has actually changed.
+// 'name' can be NULL to indicate that no team name set. This is not the same
+// as when an empty (zero-length) team name is set.
+bool
+MeleeSetup_setSentTeamName (MeleeSetup *setup, size_t teamNr,
+ const char *name)
+{
+ bool haveSentName = setup->haveSentTeamName[teamNr];
+
+ if (name == NULL)
+ {
+ if (!haveSentName)
+ {
+ // Had not sent a team name, and still haven't.
+ return false;
+ }
+
+#ifdef DEBUG
+ {
+ // The actual team name should be irrelevant if haveSentTeamName
+ // is set to false. In a debug build, we set it to invalid, so
+ // that it is more likely that it will be noticed if it is ever
+ // used.
+ MeleeTeam *team = &setup->sentTeams[teamNr];
+ MeleeTeam_setName (team, "<INVALID>");
+ }
+#endif
+ }
+ else
+ {
+ MeleeTeam *team = &setup->sentTeams[teamNr];
+
+ if (haveSentName)
+ {
+ // Have sent a team name. Check whether it has actually changed.
+ const char *oldName = MeleeTeam_getTeamName (team);
+ if (strcmp (oldName, name) == 0)
+ return false; // Team name has not changed.
+ }
+
+ MeleeTeam_setName (team, name);
+ }
+
+ setup->haveSentTeamName[teamNr] = (name != NULL);
+
+ return true;
+}
+
+#if 0
+bool
+MeleeSetup_isTeamSent (MeleeSetup *setup, size_t teamNr)
+{
+ MeleeTeam *localTeam = &setup->teams[teamNr];
+ MeleeTeam *sentTeam = &setup->sentTeams[teamNr];
+
+ return MeleeTeam_isEqual (localTeam, sentTeam);
+}
+#endif
+
+#endif /* NETPLAY */
+
+///////////////////////////////////////////////////////////////////////////
+
+
diff --git a/src/uqm/supermelee/meleesetup.h b/src/uqm/supermelee/meleesetup.h
new file mode 100644
index 0000000..0097d92
--- /dev/null
+++ b/src/uqm/supermelee/meleesetup.h
@@ -0,0 +1,143 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef MELEESETUP_H
+#define MELEESETUP_H
+
+typedef struct MeleeTeam MeleeTeam;
+typedef struct MeleeSetup MeleeSetup;
+
+#ifdef MELEESETUP_INTERNAL
+# define MELEETEAM_INTERNAL
+#endif /* MELEESETUP_INTERNAL */
+
+#include "libs/compiler.h"
+
+typedef COUNT FleetShipIndex;
+
+#include "libs/uio.h"
+#include "melee.h"
+#include "meleeship.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#ifdef MELEETEAM_INTERNAL
+struct MeleeTeam
+{
+ MeleeShip ships[MELEE_FLEET_SIZE];
+ char name[MAX_TEAM_CHARS + 1 + 24];
+ /* The +1 is for the terminating \0; the +24 is in case some
+ * default name in starcon.txt is unknowingly mangled. */
+ // XXX: SvdB: Why would it be mangled? Why don't we just reject
+ // it if it is? Is this so that we have some space
+ // for multibyte UTF-8 chars?
+};
+#endif /* MELEETEAM_INTERNAL */
+
+#ifdef MELEESETUP_INTERNAL
+struct MeleeSetup
+{
+ MeleeTeam teams[NUM_SIDES];
+ COUNT fleetValue[NUM_SIDES];
+#ifdef NETPLAY
+ MeleeTeam sentTeams[NUM_SIDES];
+ // The last sent (parts of) teams.
+ // Used in the Update protocol. See doc/devel/netplay/protocol
+ // XXX: this may actually be deallocated when the battle starts.
+ bool haveSentTeamName[NUM_SIDES];
+ // Whether we have sent a team name this 'turn'.
+ // Used in the Update protocol. See doc/devel/netplay/protocol
+ // (also for the term 'turn').
+#endif
+};
+
+#endif /* MELEESETUP_INTERNAL */
+
+extern const size_t MeleeTeam_serialSize;
+
+void MeleeTeam_init (MeleeTeam *team);
+void MeleeTeam_uninit (MeleeTeam *team);
+MeleeTeam *MeleeTeam_new (void);
+void MeleeTeam_delete (MeleeTeam *team);
+#ifdef NETPLAY
+void MeleeSetup_resetSentTeams (MeleeSetup *setup);
+#endif /* NETPLAY */
+int MeleeTeam_serialize (const MeleeTeam *team, uio_Stream *stream);
+int MeleeTeam_deserialize (MeleeTeam *team, uio_Stream *stream);
+COUNT MeleeTeam_getValue (const MeleeTeam *team);
+MeleeShip MeleeTeam_getShip (const MeleeTeam *team, FleetShipIndex slotNr);
+void MeleeTeam_setShip (MeleeTeam *team, FleetShipIndex slotNr,
+ MeleeShip ship);
+const MeleeShip *MeleeTeam_getFleet (const MeleeTeam *team);
+const char *MeleeTeam_getTeamName (const MeleeTeam *team);
+void MeleeTeam_setName (MeleeTeam *team, const char *name);
+void MeleeTeam_copy (MeleeTeam *copy, const MeleeTeam *original);
+#if 0
+bool MeleeTeam_isEqual (const MeleeTeam *team1, const MeleeTeam *team2);
+#endif
+
+#ifdef NETPLAY
+MeleeShip MeleeSetup_getSentShip (const MeleeSetup *setup, size_t teamNr,
+ FleetShipIndex slotNr);
+const char *MeleeSetup_getSentTeamName (const MeleeSetup *setup,
+ size_t teamNr);
+bool MeleeSetup_setSentShip (MeleeSetup *setup, size_t teamNr,
+ FleetShipIndex slotNr, MeleeShip ship);
+bool MeleeSetup_setSentTeamName (MeleeSetup *setup, size_t teamNr,
+ const char *name);
+#if 0
+bool MeleeSetup_isTeamSent (MeleeSetup *setup, size_t teamNr);
+#endif
+#endif /* NETPLAY */
+
+MeleeSetup *MeleeSetup_new (void);
+void MeleeSetup_delete (MeleeSetup *setup);
+
+bool MeleeSetup_setShip (MeleeSetup *setup, size_t teamNr,
+ FleetShipIndex slotNr, MeleeShip ship);
+MeleeShip MeleeSetup_getShip (const MeleeSetup *setup, size_t teamNr,
+ FleetShipIndex slotNr);
+bool MeleeSetup_setFleet (MeleeSetup *setup, size_t teamNr,
+ const MeleeShip *fleet);
+const MeleeShip *MeleeSetup_getFleet (const MeleeSetup *setup, size_t teamNr);
+bool MeleeSetup_setTeamName (MeleeSetup *setup, size_t teamNr,
+ const char *name);
+const char *MeleeSetup_getTeamName (const MeleeSetup *setup,
+ size_t teamNr);
+COUNT MeleeSetup_getFleetValue (const MeleeSetup *setup, size_t teamNr);
+int MeleeSetup_deserializeTeam (MeleeSetup *setup, size_t teamNr,
+ uio_Stream *stream);
+int MeleeSetup_serializeTeam (const MeleeSetup *setup, size_t teamNr,
+ uio_Stream *stream);
+
+
+void MeleeState_setShip (MELEE_STATE *pMS, size_t teamNr,
+ FleetShipIndex slotNr, MeleeShip ship);
+void MeleeState_setFleet (MELEE_STATE *pMS, size_t teamNr,
+ const MeleeShip *fleet);
+void MeleeState_setTeamName (MELEE_STATE *pMS, size_t teamNr,
+ const char *name);
+void MeleeState_setTeam (MELEE_STATE *pMS, size_t teamNr,
+ const MeleeTeam *team);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* MELEESETUP_H */
+
diff --git a/src/uqm/supermelee/meleeship.h b/src/uqm/supermelee/meleeship.h
new file mode 100644
index 0000000..e917b75
--- /dev/null
+++ b/src/uqm/supermelee/meleeship.h
@@ -0,0 +1,55 @@
+#ifndef MELEESHIP_H
+#define MELEESHIP_H
+
+#include "types.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef enum MeleeShip {
+ MELEE_ANDROSYNTH,
+ MELEE_ARILOU,
+ MELEE_CHENJESU,
+ MELEE_CHMMR,
+ MELEE_DRUUGE,
+ MELEE_EARTHLING,
+ MELEE_ILWRATH,
+ MELEE_KOHR_AH,
+ MELEE_MELNORME,
+ MELEE_MMRNMHRM,
+ MELEE_MYCON,
+ MELEE_ORZ,
+ MELEE_PKUNK,
+ MELEE_SHOFIXTI,
+ MELEE_SLYLANDRO,
+ MELEE_SPATHI,
+ MELEE_SUPOX,
+ MELEE_SYREEN,
+ MELEE_THRADDASH,
+ MELEE_UMGAH,
+ MELEE_URQUAN,
+ MELEE_UTWIG,
+ MELEE_VUX,
+ MELEE_YEHAT,
+ MELEE_ZOQFOTPIK,
+
+ MELEE_UNSET = ((BYTE) ~0) - 1,
+ // Used with the Update protocol, to register in the sentTeam
+ MELEE_NONE = (BYTE) ~0
+ // Empty fleet position.
+} MeleeShip;
+#define NUM_MELEE_SHIPS (MELEE_ZOQFOTPIK + 1)
+
+static inline bool
+MeleeShip_valid (MeleeShip ship)
+{
+ return (ship < NUM_MELEE_SHIPS) || (ship == MELEE_NONE);
+}
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* MELEESHIP_H */
+
diff --git a/src/uqm/supermelee/netplay/FILES b/src/uqm/supermelee/netplay/FILES
new file mode 100644
index 0000000..7b93fd1
--- /dev/null
+++ b/src/uqm/supermelee/netplay/FILES
@@ -0,0 +1,50 @@
+In netplay/:
+crc.{c,h} Generic CRC routines.
+checkbuf.{c,h} Buffer of checksums.
+checksum.{c,h} Routines for checksumming the game state.
+netconnection.{c,h} Definition of NetConnection, being the state of a
+ network connection, and operations on it.
+nc_connect.ci Part of netconnection.c that handles establishing
+ connections.
+netinput.{c,h} Definitions and operations for melee input commands
+ over a network connection.
+netmelee.{c,h} Keeps track of network connections used in the game,
+ with methods to control them all at once.
+ Functions that directly access the game data are kept
+ in the relevant files (melee.c, pickmele.c, battle.c).
+netmisc.{c,h} Miscelaneous functions that didn't fit in elsewhere.
+netoptions.{c,h} Description of a network connection to be established.
+netplay.h Some global netplay definitions.
+netrcv.{c,h} Processes incoming packets. Does know about the protocol
+ and will do consistency checking.
+ Does not directly manipulate the game state.
+netsend.{c,h} Enqueues all sorts of packets for sending.
+ Does not know about the protocol and hence will do
+ no checks for protocol sanity.
+netstate.{c,h} Definitions of the states of a network connection.
+notify.{c,h} Routines for notifying a remote side of local changes.
+ Knows about the protocal and has assert()s to
+ check for local consistency.
+packet.{c,h} Definition and creation of packets. Create functions
+ should only be called from netsend.c.
+packethandlers.{c,h} Routines for processing each type of incoming packet.
+packetq.{c,h} Manages the packet queue.
+packetsenders.{c,h} Creates and sends/queues packets.
+
+In netplay/proto/:
+npconfirm.{c,h} Functions for handing the 'confirmation' protocol.
+ready.{c,h} Functions for handling the 'ready' protocol.
+reset.{c,h} Functions for handling the 'reset' protocol.
+
+
+
+
+
+TODO:
+Division:
+- files that interface with sockets and knows nothing of the game
+ (these are in libs/network)
+- files that know of sockets and the game but don't directly interface
+ with either
+- files that interface with the game and know nothing of sockets
+
diff --git a/src/uqm/supermelee/netplay/Makeinfo b/src/uqm/supermelee/netplay/Makeinfo
new file mode 100644
index 0000000..ff31011
--- /dev/null
+++ b/src/uqm/supermelee/netplay/Makeinfo
@@ -0,0 +1,4 @@
+uqm_SUBDIRS="proto"
+uqm_CFILES="checkbuf.c checksum.c crc.c netconnection.c netinput.c netmelee.c netmisc.c netoptions.c netrcv.c netsend.c netstate.c notify.c notifyall.c packet.c packethandlers.c packetsenders.c packetq.c"
+uqm_HFILES="checkbuf.h checksum.h crc.h netconnection.h netinput.h netmelee.h netmisc.h netoptions.h netplay.h netrcv.h netsend.h netstate.h notifyall.h notify.h packet.h packethandlers.h packetq.h packetsenders.h"
+
diff --git a/src/uqm/supermelee/netplay/checkbuf.c b/src/uqm/supermelee/netplay/checkbuf.c
new file mode 100644
index 0000000..e9c5a32
--- /dev/null
+++ b/src/uqm/supermelee/netplay/checkbuf.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define PORT_WANT_ERRNO
+#include "port.h"
+
+#include "netplay.h"
+#include "checkbuf.h"
+#include "libs/log.h"
+
+#include "../../battle.h"
+ // for battleFrameCount
+
+
+#include <errno.h>
+#include <stdlib.h>
+
+
+
+static inline BattleFrameCounter
+ChecksumBuffer_getCurrentFrameNr(void) {
+ return battleFrameCount;
+}
+
+void
+ChecksumBuffer_init(ChecksumBuffer *cb, size_t delay, size_t interval) {
+ // The input buffer lags BattleInput_inputDelay frames behind,
+ // but only every interval frames will there be a checksum to be
+ // checked.
+
+ // Checksums will be checked when 'frameNr % interval == 0'.
+ // (and frameNr is zero-based).
+ // The checksum of frame n will be processed in frame 'n + delay'.
+
+ // In the worst case, side 1 processes frames 'n' through 'n + delay - 1',
+ // then blocks in frame 'n + delay' (after sending a checksum, if that's
+ // pertinent for that frame).
+ // Then side 2 receives all this input and these checksums, and
+ // progresses to 'delay' frames after the last frame the received input
+ // originated from, and blocks in the frame after it (after sending a
+ // checksum, if that's pertinent for the frame).
+ // So it sent input and checksums for frames 'n' through
+ // 'n + delay + delay + 1'.
+ // The input and checksums for these '2*delay + 2' frames are still
+ // unhandled by side 1, so it needs buffer space for this.
+ // With checksums only sent every interval frames, the buffer space
+ // needed will be 'roundUp(2*delay + 2)' spaces.
+
+ size_t bufSize = ((2 * delay + 2) + (interval - 1)) / interval;
+
+ {
+#ifdef NETPLAY_DEBUG
+ size_t i;
+#endif
+
+ cb->checksums = malloc(bufSize * sizeof (ChecksumEntry));
+ cb->maxSize = bufSize;
+ cb->interval = interval;
+
+#ifdef NETPLAY_DEBUG
+ for (i = 0; i < bufSize; i++) {
+ cb->checksums[i].checksum = 0;
+ cb->checksums[i].frameNr = (BattleFrameCounter) -1;
+ }
+#endif
+ }
+}
+
+void
+ChecksumBuffer_uninit(ChecksumBuffer *cb) {
+ if (cb->checksums != NULL) {
+ free(cb->checksums);
+ cb->checksums = NULL;
+ }
+}
+
+// Returns the entry that would be used for the checksum for the specified
+// frame. Whether the entry is actually valid is not checked.
+static ChecksumEntry *
+ChecksumBuffer_getChecksumEntry(ChecksumBuffer *cb,
+ BattleFrameCounter frameNr) {
+ size_t index;
+ ChecksumEntry *entry;
+
+ assert(frameNr % cb->interval == 0);
+ // We only record checksums exactly every 'interval' frames.
+
+ index = (frameNr / cb->interval) % cb->maxSize;
+ entry = &cb->checksums[index];
+
+ return entry;
+}
+
+bool
+ChecksumBuffer_addChecksum(ChecksumBuffer *cb, BattleFrameCounter frameNr,
+ Checksum checksum) {
+ ChecksumEntry *entry;
+
+ assert(frameNr % cb->interval == 0);
+
+ entry = ChecksumBuffer_getChecksumEntry(cb, frameNr);
+
+#ifdef NETPLAY_DEBUG
+ entry->frameNr = frameNr;
+#endif
+ entry->checksum = checksum;
+ return true;
+}
+
+// Pre: frameNr is within the range of the checksums stored in cb.
+bool
+ChecksumBuffer_getChecksum(ChecksumBuffer *cb, BattleFrameCounter frameNr,
+ Checksum *result) {
+ ChecksumEntry *entry;
+
+ entry = ChecksumBuffer_getChecksumEntry(cb, frameNr);
+
+#ifdef NETPLAY_DEBUG
+ if (frameNr != entry->frameNr) {
+ log_add(log_Error, "Checksum buffer entry for requested frame %u "
+ "(still?) contains a checksum for frame %u.\n",
+ frameNr, entry->frameNr);
+ return false;
+ }
+#endif
+
+ *result = entry->checksum;
+ return true;
+}
+
diff --git a/src/uqm/supermelee/netplay/checkbuf.h b/src/uqm/supermelee/netplay/checkbuf.h
new file mode 100644
index 0000000..f609448
--- /dev/null
+++ b/src/uqm/supermelee/netplay/checkbuf.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_CHECKBUF_H_
+#define UQM_SUPERMELEE_NETPLAY_CHECKBUF_H_
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct ChecksumEntry ChecksumEntry;
+typedef struct ChecksumBuffer ChecksumBuffer;
+
+#if defined(__cplusplus)
+}
+#endif
+
+#include "../../battle.h"
+ // for BattleFrameCounter
+#include "checksum.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+
+struct ChecksumEntry {
+#ifdef NETPLAY_DEBUG
+ BattleFrameCounter frameNr;
+ // The number of the frame this checksum originated from.
+ // If the checksumming code is working correctly, the checksum
+ // can only come from one frame, so this value is not needed
+ // for normal operation.
+ // Its only use is to detect some cases where checksumming code
+ // is *not* working correctly.
+#endif
+ Checksum checksum;
+};
+
+struct ChecksumBuffer {
+ ChecksumEntry *checksums;
+ // Cyclic buffer. if 'size' > 0, then 'first' is an index to
+ // the first used entry, 'size' is the number of used
+ // entries in the buffer, and (first + size) % maxSize is the
+ // index to just past the end of the buffer.
+ size_t maxSize;
+
+ size_t interval;
+};
+
+void ChecksumBuffer_init(ChecksumBuffer *cb, size_t delay, size_t interval);
+void ChecksumBuffer_uninit(ChecksumBuffer *cb);
+bool ChecksumBuffer_addChecksum(ChecksumBuffer *cb,
+ BattleFrameCounter frameNr, Checksum checksum);
+bool ChecksumBuffer_getChecksum(ChecksumBuffer *cb,
+ BattleFrameCounter frameNr, Checksum *result);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_CHECKBUF_H_ */
diff --git a/src/uqm/supermelee/netplay/checksum.c b/src/uqm/supermelee/netplay/checksum.c
new file mode 100644
index 0000000..5d687f0
--- /dev/null
+++ b/src/uqm/supermelee/netplay/checksum.c
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifdef NETPLAY
+
+#include "checksum.h"
+#include "netoptions.h"
+
+#ifdef NETPLAY_CHECKSUM
+
+#include "checkbuf.h"
+#include "crc.h"
+ // for DUMP_CRC_OPS
+#include "netconnection.h"
+#include "netmelee.h"
+#include "libs/log.h"
+#include "libs/mathlib.h"
+
+ChecksumBuffer localChecksumBuffer;
+
+void
+crc_processEXTENT(crc_State *state, const EXTENT *val) {
+#ifdef DUMP_CRC_OPS
+ crc_log("START crc_processEXTENT().");
+#endif
+ crc_processCOORD(state, val->width);
+ crc_processCOORD(state, val->height);
+#ifdef DUMP_CRC_OPS
+ crc_log("END crc_processEXTENT().");
+#endif
+}
+
+void
+crc_processVELOCITY_DESC(crc_State *state, const VELOCITY_DESC *val) {
+#ifdef DUMP_CRC_OPS
+ crc_log("START crc_processVELOCITY_DESC().");
+#endif
+ crc_processCOUNT(state, val->TravelAngle);
+ crc_processEXTENT(state, &val->vector);
+ crc_processEXTENT(state, &val->fract);
+ crc_processEXTENT(state, &val->error);
+ crc_processEXTENT(state, &val->incr);
+#ifdef DUMP_CRC_OPS
+ crc_log("END crc_processVELOCITY_DESC().");
+#endif
+}
+
+void
+crc_processPOINT(crc_State *state, const POINT *val) {
+#ifdef DUMP_CRC_OPS
+ crc_log("START crc_processPOINT().");
+#endif
+ crc_processCOORD(state, val->x);
+ crc_processCOORD(state, val->y);
+#ifdef DUMP_CRC_OPS
+ crc_log("END crc_processPOINT().");
+#endif
+}
+
+#if 0
+void
+crc_processSTAMP(crc_State *state, const STAMP *val) {
+#ifdef DUMP_CRC_OPS
+ crc_log("START crc_processSTAMP().");
+#endif
+ crc_processPOINT(state, val->origin);
+ crc_processFRAME(state, val->frame);
+#ifdef DUMP_CRC_OPS
+ crc_log("END crc_processSTAMP().");
+#endif
+}
+
+void
+crc_processINTERSECT_CONTROL(crc_State *state, const INTERSECT_CONTROL *val) {
+#ifdef DUMP_CRC_OPS
+ crc_log("START crc_processINTERSECT_CONTROL().");
+#endif
+ crc_processTIME_VALUE(state, val->last_time_val);
+ crc_processPOINT(state, &val->EndPoint);
+#ifdef DUMP_CRC_OPS
+ crc_log("END crc_processINTERSECT_CONTROL().");
+#endif
+}
+#endif
+
+void
+crc_processSTATE(crc_State *state, const STATE *val) {
+ crc_processPOINT(state, &val->location);
+}
+
+void
+crc_processELEMENT(crc_State *state, const ELEMENT *val) {
+#ifdef DUMP_CRC_OPS
+ crc_log("START crc_processELEMENT().");
+#endif
+ if (val->state_flags & BACKGROUND_OBJECT) {
+ // The element never influences the state of other elements,
+ // and is to be excluded from checksums.
+#ifdef DUMP_CRC_OPS
+ crc_log(" BACKGROUND_OBJECT element omited");
+#endif
+ } else {
+ crc_processELEMENT_FLAGS(state, val->state_flags);
+ crc_processCOUNT(state, val->life_span);
+ crc_processCOUNT(state, val->crew_level);
+ crc_processBYTE(state, val->mass_points);
+ crc_processBYTE(state, val->turn_wait);
+ crc_processBYTE(state, val->thrust_wait);
+ crc_processVELOCITY_DESC(state, &val->velocity);
+ crc_processSTATE(state, &val->current);
+ crc_processSTATE(state, &val->next);
+ }
+#ifdef DUMP_CRC_OPS
+ crc_log("END crc_processELEMENT().");
+#endif
+}
+
+void
+crc_processDispQueue(crc_State *state) {
+ HELEMENT element;
+ HELEMENT nextElement;
+
+#ifdef DUMP_CRC_OPS
+ size_t i = 0;
+ crc_log("START crc_processDispQueue().");
+#endif
+ for (element = GetHeadElement(); element != 0; element = nextElement) {
+ ELEMENT *elementPtr;
+
+#ifdef DUMP_CRC_OPS
+ crc_log("===== disp_q[%d]:", i);
+#endif
+ LockElement(element, &elementPtr);
+
+ crc_processELEMENT(state, elementPtr);
+
+ nextElement = GetSuccElement(elementPtr);
+ UnlockElement(element);
+#ifdef DUMP_CRC_OPS
+ i++;
+#endif
+ }
+#ifdef DUMP_CRC_OPS
+ crc_log("END crc_processDispQueue().");
+#endif
+}
+
+void
+crc_processRNG(crc_State *state) {
+ DWORD seed;
+
+#ifdef DUMP_CRC_OPS
+ crc_log("START crc_processRNG().");
+#endif
+
+ seed = TFB_SeedRandom(0);
+ // This modifies the seed too.
+ crc_processDWORD(state, seed);
+ TFB_SeedRandom(seed);
+ // Restore the old seed.
+
+#ifdef DUMP_CRC_OPS
+ crc_log("END crc_processRNG().");
+#endif
+}
+
+void
+crc_processState(crc_State *state) {
+#ifdef DUMP_CRC_OPS
+ crc_log("--------------------\n"
+ "START crc_processState() (frame %u).", battleFrameCount);
+#endif
+
+ crc_processRNG(state);
+ crc_processDispQueue(state);
+
+#ifdef DUMP_CRC_OPS
+ crc_log("END crc_processState() (frame %u).",
+ battleFrameCount);
+#endif
+}
+
+void
+initChecksumBuffers(void) {
+ size_t player;
+
+ for (player = 0; player < NETPLAY_NUM_PLAYERS; player++)
+ {
+ NetConnection *conn;
+ ChecksumBuffer *cb;
+
+ conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ cb = NetConnection_getChecksumBuffer(conn);
+ ChecksumBuffer_init(cb, getBattleInputDelay(),
+ NETPLAY_CHECKSUM_INTERVAL);
+ }
+
+ ChecksumBuffer_init(&localChecksumBuffer, getBattleInputDelay(),
+ NETPLAY_CHECKSUM_INTERVAL);
+}
+
+void
+uninitChecksumBuffers(void)
+{
+ size_t player;
+
+ for (player = 0; player < NETPLAY_NUM_PLAYERS; player++)
+ {
+ NetConnection *conn;
+ ChecksumBuffer *cb;
+
+ conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ cb = NetConnection_getChecksumBuffer(conn);
+
+ ChecksumBuffer_uninit(cb);
+ }
+
+ ChecksumBuffer_uninit(&localChecksumBuffer);
+}
+
+void
+addLocalChecksum(BattleFrameCounter frameNr, Checksum checksum) {
+ assert(frameNr == battleFrameCount);
+
+ ChecksumBuffer_addChecksum(&localChecksumBuffer, frameNr, checksum);
+}
+
+void
+addRemoteChecksum(NetConnection *conn, BattleFrameCounter frameNr,
+ Checksum checksum) {
+ ChecksumBuffer *cb;
+
+ assert(frameNr <= battleFrameCount + getBattleInputDelay() + 1);
+ assert(frameNr + getBattleInputDelay() >= battleFrameCount);
+
+ cb = NetConnection_getChecksumBuffer(conn);
+ ChecksumBuffer_addChecksum(cb, frameNr, checksum);
+}
+
+bool
+verifyChecksums(BattleFrameCounter frameNr) {
+ Checksum localChecksum;
+ size_t player;
+
+ if (!ChecksumBuffer_getChecksum(&localChecksumBuffer, frameNr,
+ &localChecksum)) {
+ // Right now, we require that a checksum is present.
+ // If/when we move to UDP, and packets may get lost, we may prefer
+ // not to do any checks in this case.
+ return false;
+ }
+
+ for (player = 0; player < NETPLAY_NUM_PLAYERS; player++)
+ {
+ NetConnection *conn;
+ ChecksumBuffer *cb;
+ Checksum remoteChecksum;
+
+ conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ cb = NetConnection_getChecksumBuffer(conn);
+
+ if (!ChecksumBuffer_getChecksum(cb, frameNr, &remoteChecksum))
+ return false;
+
+ if (localChecksum != remoteChecksum) {
+ log_add(log_Error, "Network connections have gone out of "
+ "sync.\n");
+ return false;
+ }
+ }
+ return true;
+}
+
+
+#endif /* NETPLAY_CHECKSUM */
+
+#endif /* NETPLAY */
+
diff --git a/src/uqm/supermelee/netplay/checksum.h b/src/uqm/supermelee/netplay/checksum.h
new file mode 100644
index 0000000..cfb48d6
--- /dev/null
+++ b/src/uqm/supermelee/netplay/checksum.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_CHECKSUM_H_
+#define UQM_SUPERMELEE_NETPLAY_CHECKSUM_H_
+
+
+#include "types.h"
+
+typedef uint32 Checksum;
+
+
+#include "netplay.h"
+#include "crc.h"
+
+#include "../../element.h"
+#include "libs/gfxlib.h"
+
+#include "netconnection.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+
+static inline void
+crc_processELEMENT_FLAGS(crc_State *state, ELEMENT_FLAGS val) {
+ crc_processUint16(state, (uint16) val);
+}
+
+static inline void
+crc_processCOUNT(crc_State *state, COUNT val) {
+ crc_processUint16(state, (uint16) val);
+}
+
+static inline void
+crc_processBYTE(crc_State *state, BYTE val) {
+ crc_processUint8(state, (uint8) val);
+}
+
+static inline void
+crc_processDWORD(crc_State *state, DWORD val) {
+ crc_processUint32(state, (uint32) val);
+}
+
+static inline void
+crc_processCOORD(crc_State *state, COORD val) {
+ crc_processUint16(state, (uint16) val);
+}
+
+#if 0
+static inline void
+crc_processTIME_VALUE(crc_State *state, const TIME_VALUE val) {
+ crc_processUint16(state, (uint16) val);
+}
+#endif
+
+void crc_processEXTENT(crc_State *state, const EXTENT *val);
+void crc_processVELOCITY_DESC(crc_State *state, const VELOCITY_DESC *val);
+void crc_processPOINT(crc_State *state, const POINT *val);
+#if 0
+void crc_processSTAMP(crc_State *state, const STAMP *val);
+void crc_processINTERSECT_CONTROL(crc_State *state,
+ const INTERSECT_CONTROL *val);
+#endif
+void crc_processSTATE(crc_State *state, const STATE *val);
+void crc_processELEMENT(crc_State *state, const ELEMENT *val);
+void crc_processDispQueue(crc_State *state);
+void crc_processRNG(crc_State *state);
+void crc_processState(crc_State *state);
+
+
+void initChecksumBuffers(void);
+void uninitChecksumBuffers(void);
+void addLocalChecksum(BattleFrameCounter frameNr, Checksum checksum);
+void addRemoteChecksum(NetConnection *conn, BattleFrameCounter frameNr,
+ Checksum checksum);
+bool verifyChecksums(BattleFrameCounter frameNr);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_CHECKSUM_H_ */
diff --git a/src/uqm/supermelee/netplay/crc.c b/src/uqm/supermelee/netplay/crc.c
new file mode 100644
index 0000000..677b36f
--- /dev/null
+++ b/src/uqm/supermelee/netplay/crc.c
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "netplay.h"
+ // For DUMP_CRC_OPS
+
+#include "crc.h"
+
+#ifdef DUMP_CRC_OPS
+# include "libs/log.h"
+#endif
+
+
+// CRC table for Polynomial 0x04c11db7 (0xedb88320 reversed)
+uint32 crcTable[256] = {
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
+ 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+ 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
+ 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
+ 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+ 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
+ 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
+ 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+ 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
+ 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
+ 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+ 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
+ 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
+ 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+ 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
+ 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
+ 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+ 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
+ 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
+ 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+ 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
+ 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
+ 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+ 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
+ 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
+ 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+ 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
+ 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
+ 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+ 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
+ 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
+ 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+ 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+};
+
+void
+crc_init(crc_State *state) {
+ state->crc = 0xffffffff;
+}
+
+void
+crc_processBytes(crc_State *state, uint8 *buf, size_t bufLen) {
+ uint8 *end = buf + bufLen;
+ uint32 newCrc = state->crc;
+
+ while (buf < end)
+ newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ *buf) & 0xff];
+
+#ifdef DUMP_CRC_OPS
+ crc_log("crc_processBytes(%08x, [%zu bytes]) --> %08x.",
+ state->crc, bufLen, newCrc);
+#endif
+ state->crc = newCrc;
+}
+
+void
+crc_processUint8(crc_State *state, uint8 val) {
+ uint32 newCrc = state->crc;
+
+ newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ val) & 0xff];
+#ifdef DUMP_CRC_OPS
+ crc_log("crc_processUint8(%08x, %02x) --> %08x.",
+ state->crc, (int) val, newCrc);
+#endif
+ state->crc = newCrc;
+}
+
+void
+crc_processUint16(crc_State *state, uint16 val) {
+ uint32 newCrc = state->crc;
+
+ newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ (val & 0xff)) & 0xff];
+ newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ (val >> 8) ) & 0xff];
+#ifdef DUMP_CRC_OPS
+ crc_log("crc_processUint16(%08x, %04x) --> %08x.",
+ state->crc, (int) val, newCrc);
+#endif
+ state->crc = newCrc;
+}
+
+void
+crc_processUint32(crc_State *state, uint32 val) {
+ uint32 newCrc = state->crc;
+
+ newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ (val & 0xff)) & 0xff];
+ newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ ((val >> 8) & 0xff)) & 0xff];
+ newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ ((val >> 16) & 0xff)) & 0xff];
+ newCrc = (newCrc >> 8) ^ crcTable[(newCrc ^ ((val >> 24) )) & 0xff];
+
+#ifdef DUMP_CRC_OPS
+ crc_log("crc_processUint32(%08x, %08x) --> %08x.",
+ state->crc, (int) val, newCrc);
+#endif
+ state->crc = newCrc;
+}
+
+uint32
+crc_finish(const crc_State *state) {
+ return ~state->crc;
+}
+
+
diff --git a/src/uqm/supermelee/netplay/crc.h b/src/uqm/supermelee/netplay/crc.h
new file mode 100644
index 0000000..1744d11
--- /dev/null
+++ b/src/uqm/supermelee/netplay/crc.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_CRC_H_
+#define UQM_SUPERMELEE_NETPLAY_CRC_H_
+
+typedef struct crc_State crc_State;
+
+#include "types.h"
+
+#include <stddef.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct crc_State {
+ uint32 crc;
+};
+
+void crc_init(crc_State *state);
+void crc_processBytes(crc_State *state, uint8 *buf, size_t bufLen);
+void crc_processUint8(crc_State *state, uint8 val);
+void crc_processUint16(crc_State *state, uint16 val);
+void crc_processUint32(crc_State *state, uint32 val);
+uint32 crc_finish(const crc_State *state);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#ifdef DUMP_CRC_OPS
+#include "netconnection.h"
+ // for netplayDebugFile
+//#define crc_log(...) log_add (logDebug, __VA_ARGS__)
+#define crc_log(...) if (netplayDebugFile != NULL) \
+ { \
+ uio_fprintf (netplayDebugFile, __VA_ARGS__); \
+ uio_putc ('\n', netplayDebugFile); \
+ } else \
+ (void) 0
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_CRC_H_ */
+
diff --git a/src/uqm/supermelee/netplay/nc_connect.ci b/src/uqm/supermelee/netplay/nc_connect.ci
new file mode 100644
index 0000000..6c67fed
--- /dev/null
+++ b/src/uqm/supermelee/netplay/nc_connect.ci
@@ -0,0 +1,300 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+// This file is part of netconnection.c, from where it is #included.
+
+static inline ConnectStateData *ConnectStateData_alloc(void);
+static inline ConnectStateData *ConnectStateData_new(void);
+static inline void ConnectStateData_free(ConnectStateData *connectStateData);
+static void ConnectStateData_delete(ConnectStateData *connectStateData);
+
+static int NetConnection_go(NetConnection *conn);
+static ListenState *NetConnection_serverGo(NetConnection *conn);
+static ConnectState *NetConnection_clientGo(NetConnection *conn);
+static void NetConnection_connected(NetConnection *conn);
+static void NetConnection_connectedServerCallback(ListenState *listenState,
+ NetDescriptor *listenNd, NetDescriptor *newNd,
+ const struct sockaddr *addr, SOCKLEN_T addrLen);
+static void NetConnection_connectedClientCallback(ConnectState *connectState,
+ NetDescriptor *newNd, const struct sockaddr *addr, SOCKLEN_T addrLen);
+static void NetConnection_connectedServerErrorCallback(
+ ListenState *listenState, const ListenError *listenError);
+static void NetConnection_connectedClientErrorCallback(
+ ConnectState *connectState, const ConnectError *connectError);
+
+
+////////////////////////////////////////////////////////////////////////////
+
+
+static inline ConnectStateData *
+ConnectStateData_alloc(void) {
+ return (ConnectStateData *) malloc(sizeof (ConnectStateData));
+}
+
+static inline ConnectStateData *
+ConnectStateData_new(void) {
+ ConnectStateData *connectStateData = ConnectStateData_alloc();
+ connectStateData->releaseFunction =
+ (NetConnectionStateData_ReleaseFunction) ConnectStateData_delete;
+ return connectStateData;
+}
+
+static inline void
+ConnectStateData_free(ConnectStateData *connectStateData) {
+ free(connectStateData);
+}
+
+static void
+ConnectStateData_delete(ConnectStateData *connectStateData) {
+ if (connectStateData->isServer) {
+ ListenState *listenState = connectStateData->state.listenState;
+ ListenState_close(listenState);
+ } else {
+ ConnectState *connectState = connectStateData->state.connectState;
+ ConnectState_close(connectState);
+ }
+ ConnectStateData_free(connectStateData);
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+
+
+static int
+NetConnection_go(NetConnection *conn) {
+ ConnectStateData *connectStateData;
+
+ if (NetConnection_isConnected(conn))
+ return 0;
+
+ if (conn->options->isServer) {
+ ListenState *listenState;
+ listenState = NetConnection_serverGo(conn);
+ if (listenState == NULL) {
+ // errno is set
+ return -1;
+ }
+ connectStateData = ConnectStateData_new();
+ connectStateData->state.listenState = listenState;
+ } else {
+ ConnectState *connectState;
+ connectState = NetConnection_clientGo(conn);
+ if (connectState == NULL) {
+ // errno is set
+ return -1;
+ }
+ connectStateData = ConnectStateData_new();
+ connectStateData->state.connectState = connectState;
+ }
+ connectStateData->isServer = conn->options->isServer;
+
+ NetConnection_setStateData(conn, (void *) connectStateData);
+ return 0;
+}
+
+static ListenState *
+NetConnection_serverGo(NetConnection *conn) {
+ ListenFlags listenFlags;
+ ListenState *result;
+
+ assert(conn->state == NetState_unconnected);
+ assert(conn->options->isServer);
+
+ NetConnection_setState(conn, NetState_connecting);
+
+ memset (&listenFlags, 0, sizeof listenFlags);
+ listenFlags.familyDemand =
+#if NETPLAY == NETPLAY_IPV4
+ PF_inet;
+#else
+ PF_unspec;
+#endif
+ listenFlags.familyPrefer = PF_unspec;
+ listenFlags.backlog = NETPLAY_LISTEN_BACKLOG;
+
+ result = listenPort(conn->options->port, IPProto_tcp, &listenFlags,
+ NetConnection_connectedServerCallback,
+ NetConnection_connectedServerErrorCallback, (void *) conn);
+
+ return result;
+}
+
+static ConnectState *
+NetConnection_clientGo(NetConnection *conn) {
+ ConnectFlags connectFlags;
+ ConnectState *result;
+
+ assert(conn->state == NetState_unconnected);
+ assert(!conn->options->isServer);
+
+ NetConnection_setState(conn, NetState_connecting);
+
+ memset (&connectFlags, 0, sizeof connectFlags);
+ connectFlags.familyDemand =
+#if NETPLAY == NETPLAY_IPV4
+ PF_inet;
+#else
+ PF_unspec;
+#endif
+ connectFlags.familyPrefer = PF_unspec;
+ connectFlags.timeout = NETPLAY_CONNECTTIMEOUT;
+ connectFlags.retryDelayMs = NETPLAY_RETRYDELAY;
+
+ result = connectHostByName(conn->options->host, conn->options->port,
+ IPProto_tcp, &connectFlags, NetConnection_connectedClientCallback,
+ NetConnection_connectedClientErrorCallback, (void *) conn);
+
+ return result;
+}
+
+// Called when an incoming connection has been established.
+// The caller gives up ownership of newNd
+static void
+NetConnection_connectedServerCallback(ListenState *listenState,
+ NetDescriptor *listenNd, NetDescriptor *newNd,
+ const struct sockaddr *addr, SOCKLEN_T addrLen) {
+ NetConnection *conn =
+ (NetConnection *) ListenState_getExtra(listenState);
+
+ assert(conn->nd == NULL);
+
+ conn->nd = newNd;
+ // No incRef(); the caller gives up ownership.
+ NetDescriptor_setExtra(conn->nd, (void *) conn);
+
+ (void) Socket_setInteractive(NetDescriptor_getSocket(conn->nd));
+ // Ignore errors; it's not a big deal. In debug mode, a message
+ // will already have been printed from the function itself.
+
+ conn->stateFlags.discriminant = true;
+
+ NetConnection_connected(conn);
+ (void) listenNd;
+ (void) addr;
+ (void) addrLen;
+}
+
+// Called when an outgoing connection has been established.
+// The caller gives up ownership of newNd
+static void
+NetConnection_connectedClientCallback(ConnectState *connectState,
+ NetDescriptor *newNd,
+ const struct sockaddr *addr, SOCKLEN_T addrLen) {
+ NetConnection *conn =
+ (NetConnection *) ConnectState_getExtra(connectState);
+
+ assert(conn->nd == NULL);
+
+ conn->nd = newNd;
+ // No incRef(); the caller gives up ownership.
+ NetDescriptor_setExtra(conn->nd, (void *) conn);
+
+ (void) Socket_setInteractive(NetDescriptor_getSocket(conn->nd));
+ // Ignore errors; it's not a big deal. In debug mode, a message
+ // will already have been printed from the function itself.
+
+ conn->stateFlags.discriminant = false;
+
+ NetConnection_connected(conn);
+ (void) addr;
+ (void) addrLen;
+}
+
+// Called when a connection has been established.
+static void
+NetConnection_connected(NetConnection *conn) {
+ ConnectStateData *connectStateData;
+
+ conn->stateFlags.connected = true;
+ NetConnection_setState(conn, NetState_init);
+
+ connectStateData = (ConnectStateData *) NetConnection_getStateData(conn);
+ ConnectStateData_delete(connectStateData);
+ NetConnection_setStateData(conn, NULL);
+
+ NetDescriptor_setReadCallback(conn->nd, dataReadyCallback);
+ NetDescriptor_setCloseCallback(conn->nd, closeCallback);
+
+ (*conn->connectCallback)(conn);
+}
+
+static void
+NetConnection_connectedServerErrorCallback(ListenState *listenState,
+ const ListenError *listenError) {
+ NetConnection *conn =
+ (NetConnection *) ListenState_getExtra(listenState);
+ NetConnectionError error;
+
+ conn->stateFlags.connected = false;
+ conn->stateFlags.disconnected = true;
+ ListenState_close(listenState);
+ NetConnection_setState(conn, NetState_unconnected);
+
+ {
+ ConnectStateData *connectStateData;
+ connectStateData =
+ (ConnectStateData *) NetConnection_getStateData(conn);
+ ConnectStateData_free(connectStateData);
+ NetConnection_setStateData(conn, NULL);
+ }
+
+ if (conn->errorCallback != NULL) {
+ error.state = NetState_connecting;
+ error.err = listenError->err;
+ error.extra.listenError = listenError;
+ //NetConnection_incRef(conn);
+ (*conn->errorCallback)(conn, &error);
+ //NetConnection_decRef(conn);
+ }
+
+ NetConnection_close(conn);
+}
+
+static void
+NetConnection_connectedClientErrorCallback(ConnectState *connectState,
+ const ConnectError *connectError) {
+ NetConnection *conn =
+ (NetConnection *) ConnectState_getExtra(connectState);
+ NetConnectionError error;
+
+ conn->stateFlags.connected = false;
+ conn->stateFlags.disconnected = true;
+ ConnectState_close(connectState);
+ NetConnection_setState(conn, NetState_unconnected);
+
+ {
+ ConnectStateData *connectStateData;
+ connectStateData =
+ (ConnectStateData *) NetConnection_getStateData(conn);
+ ConnectStateData_free(connectStateData);
+ NetConnection_setStateData(conn, NULL);
+ }
+
+ if (conn->errorCallback != NULL) {
+ error.state = NetState_connecting;
+ error.err = connectError->err;
+ error.extra.connectError = connectError;
+ //NetConnection_incRef(conn);
+ (*conn->errorCallback)(conn, &error);
+ //NetConnection_decRef(conn);
+ }
+
+ NetConnection_close(conn);
+}
+
+
diff --git a/src/uqm/supermelee/netplay/netconnection.c b/src/uqm/supermelee/netplay/netconnection.c
new file mode 100644
index 0000000..48ab46b
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netconnection.c
@@ -0,0 +1,378 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define NETCONNECTION_INTERNAL
+#include "netplay.h"
+#include "netconnection.h"
+
+#include "netrcv.h"
+
+#if defined(DEBUG) || defined(NETPLAY_DEBUG)
+# include "libs/log.h"
+#endif
+#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE)
+# include "options.h"
+ // for configDir
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE)
+# include <errno.h>
+# include <time.h>
+#endif
+
+
+static void closeCallback(NetDescriptor *nd);
+static void NetConnection_doClose(NetConnection *conn);
+
+
+#include "nc_connect.ci"
+
+#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE)
+uio_Stream *netplayDebugFile;
+#endif
+
+// Used as initial value for Agreement structures, by structure assignment.
+const Agreement Agreement_nothingAgreed;
+
+
+// The NetConnection keeps a pointer to the passed NetplayPeerOptions;
+// do not free it as long as the NetConnection exists.
+NetConnection *
+NetConnection_open(int player, const NetplayPeerOptions *options,
+ NetConnection_ConnectCallback connectCallback,
+ NetConnection_CloseCallback closeCallback,
+ NetConnection_ErrorCallback errorCallback,
+ NetConnection_DeleteCallback deleteCallback, void *extra) {
+ NetConnection *conn;
+
+ conn = malloc(sizeof (NetConnection));
+
+#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE)
+ {
+ char dumpFileName[PATH_MAX];
+ time_t now;
+ struct tm *nowTm;
+ size_t strftimeResult;
+
+ now = time (NULL);
+ if (now == (time_t) -1) {
+ log_add (log_Fatal, "time() failed: %s.", strerror (errno));
+ abort ();
+ }
+
+ nowTm = localtime(&now);
+ // XXX: I would like to use localtime_r(), but it isn't very
+ // portable (yet), and adding a check for it to the build.sh script
+ // is not worth the effort for a debugging function right now.
+
+ strftimeResult = strftime (dumpFileName, sizeof dumpFileName,
+ "debug/netlog-%Y%m%d%H%M%S", nowTm);
+ if (strftimeResult == 0) {
+ log_add (log_Fatal, "strftime() failed: %s.", strerror (errno));
+ abort ();
+ }
+
+ // The user needs to create the debug/ dir manually. If there
+ // is no debug/ dir, no log will be created.
+ conn->debugFile = uio_fopen (configDir, dumpFileName, "wt");
+ if (conn->debugFile == NULL) {
+ log_add (log_Debug, "Not creating a netplay debug log for "
+ "player %d.", player);
+ } else {
+ log_add (log_Debug, "Creating netplay debug log '%s' for "
+ "player %d.", dumpFileName, player);
+ if (netplayDebugFile == NULL) {
+ // Debug info relating to no specific network connection
+ // is sent to the first opened one.
+ netplayDebugFile = conn->debugFile;
+ }
+ }
+ }
+#endif
+
+ conn->nd = NULL;
+ conn->player = player;
+ conn->state = NetState_unconnected;
+ conn->options = options;
+ conn->extra = extra;
+ PacketQueue_init(&conn->queue);
+
+ conn->connectCallback = connectCallback;
+ conn->closeCallback = closeCallback;
+ conn->errorCallback = errorCallback;
+ conn->deleteCallback = deleteCallback;
+ conn->readyCallback = NULL;
+ conn->readyCallbackArg = NULL;
+ conn->resetCallback = NULL;
+ conn->resetCallbackArg = NULL;
+
+ conn->readBuf = malloc(NETPLAY_READBUFSIZE);
+ conn->readEnd = conn->readBuf;
+
+ conn->stateData = NULL;
+ conn->stateFlags.connected = false;
+ conn->stateFlags.disconnected = false;
+ conn->stateFlags.discriminant = false;
+ conn->stateFlags.handshake.localOk = false;
+ conn->stateFlags.handshake.remoteOk = false;
+ conn->stateFlags.handshake.canceling = false;
+ conn->stateFlags.ready.localReady = false;
+ conn->stateFlags.ready.remoteReady = false;
+ conn->stateFlags.reset.localReset = false;
+ conn->stateFlags.reset.remoteReset = false;
+ conn->stateFlags.agreement = Agreement_nothingAgreed;
+ conn->stateFlags.inputDelay = 0;
+#ifdef NETPLAY_CHECKSUM
+ conn->stateFlags.checksumInterval = NETPLAY_CHECKSUM_INTERVAL;
+#endif
+
+#ifdef NETPLAY_STATISTICS
+ {
+ size_t i;
+
+ conn->statistics.packetsReceived = 0;
+ conn->statistics.packetsSent = 0;
+ for (i = 0; i < PACKET_NUM; i++)
+ {
+ conn->statistics.packetTypeReceived[i] = 0;
+ conn->statistics.packetTypeSent[i] = 0;
+ }
+ }
+#endif
+
+ NetConnection_go(conn);
+
+ return conn;
+}
+
+static void
+NetConnection_doDeleteCallback(NetConnection *conn) {
+ if (conn->deleteCallback != NULL) {
+ //NetConnection_incRef(conn);
+ conn->deleteCallback(conn);
+ //NetConnection_decRef(conn);
+ }
+}
+
+static void
+NetConnection_delete(NetConnection *conn) {
+ NetConnection_doDeleteCallback(conn);
+ if (conn->stateData != NULL) {
+ NetConnectionStateData_release(conn->stateData);
+ conn->stateData = NULL;
+ }
+ free(conn->readBuf);
+ PacketQueue_uninit(&conn->queue);
+
+#ifdef NETPLAY_DEBUG_FILE
+ if (conn->debugFile != NULL) {
+ if (netplayDebugFile == conn->debugFile) {
+ // There may be other network connections, with an open
+ // debug file, but we don't know about that.
+ // The debugging person just has to work around that.
+ netplayDebugFile = NULL;
+ }
+ uio_fclose(conn->debugFile);
+ }
+#endif
+
+ free(conn);
+}
+
+static void
+Netplay_doCloseCallback(NetConnection *conn) {
+ if (conn->closeCallback != NULL) {
+ //NetConnection_incRef(conn);
+ conn->closeCallback(conn);
+ //NetConnection_decRef(conn);
+ }
+}
+
+// Auxiliary function for closing, used by both closeCallback() and
+// NetConnection_close()
+static void
+NetConnection_doClose(NetConnection *conn) {
+ conn->stateFlags.connected = false;
+ conn->stateFlags.disconnected = true;
+
+ // First the callback, so that it can still use the information
+ // of what is the current state, and the stateData:
+ Netplay_doCloseCallback(conn);
+
+ NetConnection_setState(conn, NetState_unconnected);
+}
+
+// Called when the NetDescriptor is shut down.
+static void
+closeCallback(NetDescriptor *nd) {
+ NetConnection *conn = (NetConnection *) NetDescriptor_getExtra(nd);
+ if (conn == NULL)
+ return;
+ conn->nd = NULL;
+ NetConnection_doClose(conn);
+}
+
+// Close and release a NetConnection.
+void
+NetConnection_close(NetConnection *conn) {
+ if (conn->nd != NULL) {
+ NetDescriptor_setCloseCallback(conn->nd, NULL);
+ // We're not interested in the close callback of the
+ // NetDescriptor anymore.
+ NetDescriptor_close(conn->nd);
+ // This would queue the close callback.
+ conn->nd = NULL;
+ }
+ if (!conn->stateFlags.disconnected)
+ NetConnection_doClose(conn);
+ NetConnection_delete(conn);
+}
+
+void
+NetConnection_doErrorCallback(NetConnection *nd, int err) {
+ NetConnectionError error;
+
+ if (nd->errorCallback != NULL) {
+ error.state = nd->state;
+ error.err = err;
+ }
+ (*nd->errorCallback)(nd, &error);
+}
+
+void
+NetConnection_setStateData(NetConnection *conn,
+ NetConnectionStateData *stateData) {
+ conn->stateData = stateData;
+}
+
+NetConnectionStateData *
+NetConnection_getStateData(const NetConnection *conn) {
+ return conn->stateData;
+}
+
+void
+NetConnection_setExtra(NetConnection *conn, void *extra) {
+ conn->extra = extra;
+}
+
+void *
+NetConnection_getExtra(const NetConnection *conn) {
+ return conn->extra;
+}
+
+void
+NetConnection_setReadyCallback(NetConnection *conn,
+ NetConnection_ReadyCallback callback, void *arg) {
+ conn->readyCallback = callback;
+ conn->readyCallbackArg = arg;
+}
+
+NetConnection_ReadyCallback
+NetConnection_getReadyCallback(const NetConnection *conn) {
+ return conn->readyCallback;
+}
+
+void *
+NetConnection_getReadyCallbackArg(const NetConnection *conn) {
+ return conn->readyCallbackArg;
+}
+
+void
+NetConnection_setResetCallback(NetConnection *conn,
+ NetConnection_ResetCallback callback, void *arg) {
+ conn->resetCallback = callback;
+ conn->resetCallbackArg = arg;
+}
+
+NetConnection_ResetCallback
+NetConnection_getResetCallback(const NetConnection *conn) {
+ return conn->resetCallback;
+}
+
+void *
+NetConnection_getResetCallbackArg(const NetConnection *conn) {
+ return conn->resetCallbackArg;
+}
+
+void
+NetConnection_setState(NetConnection *conn, NetState state) {
+#ifdef NETPLAY_DEBUG
+ log_add(log_Debug, "NETPLAY: [%d] +/- Connection state changed to: "
+ "%s.\n", conn->player, netStateData[state].name);
+#endif
+#ifdef DEBUG
+ if (state == conn->state) {
+ log_add(log_Warning, "NETPLAY: [%d] Connection state set to %s "
+ "while already in that state.\n",
+ conn->player, netStateData[state].name);
+ }
+#endif
+ conn->state = state;
+}
+
+NetState
+NetConnection_getState(const NetConnection *conn) {
+ return conn->state;
+}
+
+bool
+NetConnection_getDiscriminant(const NetConnection *conn) {
+ return conn->stateFlags.discriminant;
+}
+
+const NetplayPeerOptions *
+NetConnection_getPeerOptions(const NetConnection *conn) {
+ return conn->options;
+}
+
+bool
+NetConnection_isConnected(const NetConnection *conn) {
+ return conn->stateFlags.connected;
+}
+
+int
+NetConnection_getPlayerNr(const NetConnection *conn) {
+ return conn->player;
+}
+
+size_t
+NetConnection_getInputDelay(const NetConnection *conn) {
+ return conn->stateFlags.inputDelay;
+}
+
+#ifdef NETPLAY_CHECKSUM
+ChecksumBuffer *
+NetConnection_getChecksumBuffer(NetConnection *conn) {
+ return &conn->checksumBuffer;
+}
+
+size_t
+NetConnection_getChecksumInterval(const NetConnection *conn) {
+ return conn->stateFlags.checksumInterval;
+}
+#endif /* NETPLAY_CHECKSUM */
+
+#ifdef NETPLAY_STATISTICS
+NetStatistics *
+NetConnection_getStatistics(NetConnection *conn) {
+ return &conn->statistics;
+}
+#endif
+
diff --git a/src/uqm/supermelee/netplay/netconnection.h b/src/uqm/supermelee/netplay/netconnection.h
new file mode 100644
index 0000000..485d3c4
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netconnection.h
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_NETCONNECTION_H_
+#define UQM_SUPERMELEE_NETPLAY_NETCONNECTION_H_
+
+#include "netplay.h"
+ // for NETPLAY_STATISTICS
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct NetConnection NetConnection;
+typedef struct NetConnectionError NetConnectionError;
+typedef struct ConnectStateData ConnectStateData;
+#ifdef NETPLAY_STATISTICS
+typedef struct NetStatistics NetStatistics;
+#endif
+
+typedef void (*NetConnection_ConnectCallback)(NetConnection *nd);
+typedef void (*NetConnection_CloseCallback)(NetConnection *nd);
+typedef void (*NetConnection_ErrorCallback)(NetConnection *nd,
+ const NetConnectionError *error);
+typedef void (*NetConnection_DeleteCallback)(NetConnection *nd);
+
+typedef void (*NetConnection_ReadyCallback)(NetConnection *conn, void *arg);
+typedef void (*NetConnection_ResetCallback)(NetConnection *conn, void *arg);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#include "netstate.h"
+#include "netoptions.h"
+#ifdef NETPLAY_CHECKSUM
+# include "checkbuf.h"
+#endif
+#if defined(NETPLAY_STATISTICS) || defined(NETCONNECTION_INTERNAL)
+# include "packet.h"
+#endif
+#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE)
+# include "libs/uio.h"
+#endif
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct NetConnectionError {
+ NetState state;
+ int err;
+ union {
+ const struct ListenError *listenError;
+ const struct ConnectError *connectError;
+ } extra;
+};
+
+#ifdef NETPLAY_STATISTICS
+struct NetStatistics {
+ size_t packetsReceived;
+ size_t packetTypeReceived[PACKET_NUM];
+ size_t packetsSent;
+ size_t packetTypeSent[PACKET_NUM];
+};
+#endif
+
+#if defined(__cplusplus)
+}
+#endif
+
+#ifdef NETCONNECTION_INTERNAL
+#include "libs/net.h"
+#include "packetq.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct {
+ // For actions that require agreement by both parties.
+ bool localOk : 1; /* Action confirmed by us */
+ bool remoteOk : 1; /* Action confirmed by the remote party */
+ bool canceling : 1; /* Awaiting cancel confirmation */
+} HandShakeFlags;
+
+typedef struct {
+ // For actions that do not require agreement, but for which it
+ // only is relevant that both sides are ready.
+ bool localReady : 1;
+ bool remoteReady : 1;
+} ReadyFlags;
+
+typedef struct {
+ bool localReset : 1;
+ bool remoteReset : 1;
+} ResetFlags;
+
+// Which parameters have we both sides of a connection reached agreement on?
+typedef struct {
+ bool randomSeed : 1;
+} Agreement;
+
+typedef struct {
+ bool connected;
+ /* This NetConnection is connected. */
+ bool disconnected;
+ /* This NetConnection has been disconnected. This implies
+ * !connected. It is only set if the NetConnection was once
+ * connected, but is no longer. */
+ bool discriminant;
+ /* If it is true here, it is false on the remote side
+ * of the same connection. It may be used to break ties.
+ * It is guaranteed not to change during a connection. Undefined
+ * while not connected. */
+ HandShakeFlags handshake;
+ ReadyFlags ready;
+ ResetFlags reset;
+ Agreement agreement;
+ size_t inputDelay;
+ /* Used during negotiation of the actual inputDelay. This
+ * field does NOT necessarilly contain the actual input delay,
+ * which is a property of the game, not of any specific
+ * connection. Use getBattleInputDelay() to get at it. */
+#ifdef NETPLAY_CHECKSUM
+ size_t checksumInterval;
+#endif
+} NetStateFlags;
+
+struct NetConnection {
+ NetDescriptor *nd;
+ int player;
+ // Number of the player for this connection, as it is
+ // known locally. For the other player, it may be
+ // differently.
+ NetState state;
+ NetStateFlags stateFlags;
+
+ NetConnection_ReadyCallback readyCallback;
+ // Called when both sides have indicated that they are ready.
+ // Set by Netplay_localReady().
+ void *readyCallbackArg;
+ // Extra argument for readyCallback().
+ // XXX: when is this cleaned up if a connection is broken?
+
+ NetConnection_ResetCallback resetCallback;
+ // Called when a reset has been signalled and confirmed.
+ // Set by Netplay_localReset().
+ void *resetCallbackArg;
+ // Extra argument for resetCallback().
+ // XXX: when is this cleaned up if a connection is broken?
+
+ const NetplayPeerOptions *options;
+ PacketQueue queue;
+#ifdef NETPLAY_STATISTICS
+ NetStatistics statistics;
+#endif
+#ifdef NETPLAY_CHECKSUM
+ ChecksumBuffer checksumBuffer;
+#endif
+#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE)
+ uio_Stream *debugFile;
+#endif
+
+ NetConnection_ConnectCallback connectCallback;
+ NetConnection_CloseCallback closeCallback;
+ // Called when the NetConnection becomes disconnected.
+ NetConnection_ErrorCallback errorCallback;
+ NetConnection_DeleteCallback deleteCallback;
+ // Called when the NetConnection is destroyed.
+ uint8 *readBuf;
+ uint8 *readEnd;
+ NetConnectionStateData *stateData;
+ // State dependant information.
+ void *extra;
+};
+
+struct ConnectStateData {
+ NETCONNECTION_STATE_DATA_COMMON
+
+ bool isServer;
+ union {
+ struct ConnectState *connectState;
+ struct ListenState *listenState;
+ } state;
+};
+
+#endif /* NETCONNECTION_INTERNAL */
+
+
+NetConnection *NetConnection_open(int player,
+ const NetplayPeerOptions *options,
+ NetConnection_ConnectCallback connectCallback,
+ NetConnection_CloseCallback closeCallback,
+ NetConnection_ErrorCallback errorCallback,
+ NetConnection_DeleteCallback deleteCallback, void *extra);
+void NetConnection_close(NetConnection *conn);
+bool NetConnection_isConnected(const NetConnection *conn);
+
+void NetConnection_doErrorCallback(NetConnection *nd, int err);
+
+void NetConnection_setStateData(NetConnection *conn,
+ NetConnectionStateData *stateData);
+NetConnectionStateData *NetConnection_getStateData(const NetConnection *conn);
+void NetConnection_setExtra(NetConnection *conn, void *extra);
+void *NetConnection_getExtra(const NetConnection *conn);
+void NetConnection_setState(NetConnection *conn, NetState state);
+NetState NetConnection_getState(const NetConnection *conn);
+bool NetConnection_getDiscriminant(const NetConnection *conn);
+const NetplayPeerOptions *NetConnection_getPeerOptions(
+ const NetConnection *conn);
+int NetConnection_getPlayerNr(const NetConnection *conn);
+size_t NetConnection_getInputDelay(const NetConnection *conn);
+#ifdef NETPLAY_CHECKSUM
+ChecksumBuffer *NetConnection_getChecksumBuffer(NetConnection *conn);
+size_t NetConnection_getChecksumInterval(const NetConnection *conn);
+#endif
+#ifdef NETPLAY_STATISTICS
+NetStatistics *NetConnection_getStatistics(NetConnection *conn);
+#endif
+
+void NetConnection_setReadyCallback(NetConnection *conn,
+ NetConnection_ReadyCallback callback, void *arg);
+NetConnection_ReadyCallback NetConnection_getReadyCallback(
+ const NetConnection *conn);
+void *NetConnection_getReadyCallbackArg(const NetConnection *conn);
+
+void NetConnection_setResetCallback(NetConnection *conn,
+ NetConnection_ResetCallback callback, void *arg);
+NetConnection_ResetCallback NetConnection_getResetCallback(
+ const NetConnection *conn);
+void *NetConnection_getResetCallbackArg(const NetConnection *conn);
+
+
+#if defined(NETPLAY_DEBUG) && defined(NETPLAY_DEBUG_FILE)
+extern uio_Stream *netplayDebugFile;
+#endif
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_NETCONNECTION_H_ */
+
+
diff --git a/src/uqm/supermelee/netplay/netinput.c b/src/uqm/supermelee/netplay/netinput.c
new file mode 100644
index 0000000..9823deb
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netinput.c
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define PORT_WANT_ERRNO
+#include "port.h"
+
+#include "netplay.h"
+#include "netinput.h"
+
+#include "../../intel.h"
+ // for NETWORK_CONTROL
+#include "../../setup.h"
+ // For PlayerControl
+#include "libs/log.h"
+
+#include <errno.h>
+#include <stdlib.h>
+
+
+static BattleInputBuffer battleInputBuffers[NUM_PLAYERS];
+static size_t BattleInput_inputDelay;
+
+// Call before initBattleInputBuffers()
+void
+setBattleInputDelay(size_t delay) {
+ BattleInput_inputDelay = delay;
+}
+
+size_t
+getBattleInputDelay(void) {
+ return BattleInput_inputDelay;
+}
+
+static void
+BattleInputBuffer_init(BattleInputBuffer *bib, size_t bufSize) {
+ bib->buf = malloc(bufSize * sizeof (BATTLE_INPUT_STATE));
+ bib->maxSize = bufSize;
+ bib->first = 0;
+ bib->size = 0;
+}
+
+static void
+BattleInputBuffer_uninit(BattleInputBuffer *bib) {
+ if (bib->buf != NULL) {
+ free(bib->buf);
+ bib->buf = NULL;
+ }
+ bib->maxSize = 0;
+ bib->first = 0;
+ bib->size = 0;
+}
+
+void
+initBattleInputBuffers(void) {
+ size_t player;
+ int bufSize = BattleInput_inputDelay * 2 + 2;
+
+ // The input of frame n will be processed in frame 'n + delay'.
+ //
+ // In the worst case, side 1 processes frames 'n' through 'n + delay - 1',
+ // then blocks in frame 'n + delay'.
+ // Then side 2 receives all this input, and progresses to 'delay' frames
+ // after the last frame the received input originated from, and blocks
+ // in the frame after it.
+ // So it sent input for frames 'n' through 'n + delay + delay + 1'.
+ // The input for these '2*delay + 2' frames are still
+ // unhandled by side 1, so it needs buffer space for this.
+ //
+ // Initially the buffer is filled with inputDelay zeroes,
+ // so that a party can process at least that much frames.
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ BattleInputBuffer *bib = &battleInputBuffers[player];
+ BattleInputBuffer_init(bib, bufSize);
+
+ {
+ // Initially a party must be able to process at least inputDelay
+ // frames, so we fill the buffer with inputDelay zeros.
+ size_t i;
+ for (i = 0; i < BattleInput_inputDelay; i++)
+ BattleInputBuffer_push(bib, (BATTLE_INPUT_STATE) 0);
+ }
+ }
+}
+
+void
+uninitBattleInputBuffers(void)
+{
+ size_t player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ BattleInputBuffer *bib;
+
+ bib = &battleInputBuffers[player];
+ BattleInputBuffer_uninit(bib);
+ }
+}
+
+// On error, returns false and sets errno.
+bool
+BattleInputBuffer_push(BattleInputBuffer *bib, BATTLE_INPUT_STATE input)
+{
+ size_t next;
+
+ if (bib->size == bib->maxSize) {
+ // No more space.
+ log_add(log_Error, "NETPLAY: battleInputBuffer full.\n");
+ errno = ENOBUFS;
+ return false;
+ }
+
+ next = (bib->first + bib->size) % bib->maxSize;
+ bib->buf[next] = input;
+ bib->size++;
+ return true;
+}
+
+// On error, returns false and sets errno, and *input remains unchanged.
+bool
+BattleInputBuffer_pop(BattleInputBuffer *bib, BATTLE_INPUT_STATE *input)
+{
+ if (bib->size == 0)
+ {
+ // Buffer is empty.
+ errno = EAGAIN;
+ return false;
+ }
+
+ *input = bib->buf[bib->first];
+ bib->first = (bib->first + 1) % bib->maxSize;
+ bib->size--;
+ return true;
+}
+
+BattleInputBuffer *
+getBattleInputBuffer(size_t player) {
+ return &battleInputBuffers[player];
+}
+
+
diff --git a/src/uqm/supermelee/netplay/netinput.h b/src/uqm/supermelee/netplay/netinput.h
new file mode 100644
index 0000000..2afdf02
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netinput.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_NETINPUT_H_
+#define UQM_SUPERMELEE_NETPLAY_NETINPUT_H_
+
+#include "../../controls.h"
+ // for BATTLE_INPUT_STATE
+#include "../../init.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+ // for NUM_PLAYERS
+
+typedef struct BattleInputBuffer {
+ BATTLE_INPUT_STATE *buf;
+ // Cyclic buffer. if 'size' > 0, then 'first' is an index to
+ // the first used entry, 'size' is the number of used
+ // entries in the buffer, and (first + size) % maxSize is the
+ // index to just past the end of the buffer.
+ size_t maxSize;
+ size_t first;
+ size_t size;
+} BattleInputBuffer;
+
+void setBattleInputDelay(size_t delay);
+size_t getBattleInputDelay(void);
+void initBattleInputBuffers(void);
+void uninitBattleInputBuffers(void);
+bool BattleInputBuffer_push(BattleInputBuffer *bib,
+ BATTLE_INPUT_STATE input);
+bool BattleInputBuffer_pop(BattleInputBuffer *bib,
+ BATTLE_INPUT_STATE *input);
+
+BattleInputBuffer *getBattleInputBuffer(size_t player);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_NETINPUT_H_ */
diff --git a/src/uqm/supermelee/netplay/netmelee.c b/src/uqm/supermelee/netplay/netmelee.c
new file mode 100644
index 0000000..e9368fd
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netmelee.c
@@ -0,0 +1,740 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define PORT_WANT_ERRNO
+#include "port.h"
+#include "netmelee.h"
+#include "libs/async.h"
+#include "libs/callback.h"
+#include "libs/log.h"
+#include "libs/net.h"
+#include "netinput.h"
+#include "netmisc.h"
+#include "netsend.h"
+#include "notify.h"
+#include "packetq.h"
+#include "proto/npconfirm.h"
+#include "proto/ready.h"
+#include "proto/reset.h"
+
+#include "../../battlecontrols.h"
+ // for NetworkInputContext
+#include "../../controls.h"
+ // for BATTLE_INPUT_STATE
+#include "../../init.h"
+ // for NUM_PLAYERS
+#include "../../globdata.h"
+ // for GLOBAL
+
+#include <errno.h>
+#include <stdlib.h>
+
+
+////////////////////////////////////////////////////////////////////////////
+
+
+NetConnection *netConnections[NUM_PLAYERS];
+size_t numNetConnections;
+
+void
+addNetConnection(NetConnection *conn, int playerNr) {
+ netConnections[playerNr] = conn;
+ numNetConnections++;
+}
+
+void
+removeNetConnection(int playerNr) {
+ netConnections[playerNr] = NULL;
+ numNetConnections--;
+}
+
+size_t
+getNumNetConnections(void) {
+ return numNetConnections;
+}
+
+// If the callback function returns 'false', the function will immediately
+// return with 'false'. Otherwise it will return 'true' after calling
+// the callback function for each connected player.
+bool
+forEachConnectedPlayer(ForEachConnectionCallback callback, void *arg) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn))
+ continue;
+
+ if (!(*callback)(conn, arg))
+ return false;
+ }
+ return true;
+}
+
+void
+closeAllConnections(void) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+
+ if (conn != NULL)
+ closePlayerNetworkConnection(player);
+ }
+}
+
+void
+closeDisconnectedConnections(void) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+
+ if (conn != NULL && !NetConnection_isConnected(conn))
+ closePlayerNetworkConnection(player);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+
+struct melee_state *
+NetMelee_getMeleeState(NetConnection *conn) {
+ if (NetConnection_getState(conn) > NetState_connecting) {
+ BattleStateData *battleStateData =
+ (BattleStateData *) NetConnection_getStateData(conn);
+ return battleStateData->meleeState;
+ } else {
+ return (struct melee_state *) NetConnection_getExtra(conn);
+ }
+}
+
+struct battlestate_struct *
+NetMelee_getBattleState(NetConnection *conn) {
+ if (NetConnection_getState(conn) > NetState_connecting) {
+ BattleStateData *battleStateData =
+ (BattleStateData *) NetConnection_getStateData(conn);
+ return battleStateData->battleState;
+ } else {
+ return NULL;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+static inline void
+netInputAux(uint32 timeoutMs) {
+ NetManager_process(&timeoutMs);
+ // This may cause more packets to be queued, hence the
+ // flushPacketQueues().
+ Async_process();
+ flushPacketQueues();
+ // During the flush, a disconnect may be noticed, which triggers
+ // another callback. It must be handled immediately, before
+ // another flushPacketQueue() can occur, which would not know
+ // that the socket is no longer valid.
+ // TODO: modify the close handling so this order isn't
+ // necessary.
+ Callback_process();
+}
+
+// Check the network connections for input.
+void
+netInput(void) {
+ netInputAux(0);
+}
+
+void
+netInputBlocking(uint32 timeoutMs) {
+ uint32 nextAsyncMs;
+
+ nextAsyncMs = Async_timeBeforeNextMs();
+ if (nextAsyncMs < timeoutMs)
+ timeoutMs = nextAsyncMs;
+
+ netInputAux(timeoutMs);
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+
+
+// Send along all pending network packets.
+void
+flushPacketQueues(void) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn;
+ int flushStatus;
+
+ conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn))
+ continue;
+
+ flushStatus = flushPacketQueue(conn);
+ if (flushStatus == -1 && errno != EAGAIN && errno != EWOULDBLOCK)
+ closePlayerNetworkConnection(player);
+ }
+}
+
+void
+confirmConnections(void) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn))
+ continue;
+
+ Netplay_confirm(conn);
+ }
+}
+
+void
+cancelConfirmations(void) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn))
+ continue;
+
+ Netplay_cancelConfirmation(conn);
+ }
+}
+
+void
+connectionsLocalReady(NetConnection_ReadyCallback callback, void *arg) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn))
+ continue;
+
+ Netplay_localReady(conn, callback, arg, true);
+ }
+}
+
+bool
+allConnected(void) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn))
+ return false;
+ }
+ return true;
+}
+
+void
+initBattleStateDataConnections(void) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ BattleStateData *battleStateData;
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ battleStateData =
+ (BattleStateData *) NetConnection_getStateData(conn);
+ battleStateData->endFrameCount = 0;
+ }
+}
+
+void
+setBattleStateConnections(struct battlestate_struct *bs) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ BattleStateData *battleStateData;
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ battleStateData =
+ (BattleStateData *) NetConnection_getStateData(conn);
+ battleStateData->battleState = bs;
+ }
+}
+
+BATTLE_INPUT_STATE
+networkBattleInput(NetworkInputContext *context, STARSHIP *StarShipPtr) {
+ BattleInputBuffer *bib = getBattleInputBuffer(context->playerNr);
+ BATTLE_INPUT_STATE result;
+
+ for (;;) {
+ bool ok;
+
+#if 0
+ // This is a useful debugging trick. By enabling this #if
+ // block, this side will always lag the maximum number of frames
+ // behind the other side. When the remote side stops on some event
+ // (a breakpoint or so), this side will stop too, waiting for input
+ // in the loop below, but it won't have processed the frame that
+ // triggered the event yet. If you then jump over this 'if'
+ // statement here, you can walk through the decisive frames
+ // manually. Works best with no input delay.
+ if (bib->size <= getBattleInputDelay() + 1) {
+ ok = false;
+ } else
+#endif
+ ok = BattleInputBuffer_pop(bib, &result);
+ // Get the input from the front of the
+ // buffer.
+ if (ok)
+ break;
+
+ {
+ NetConnection *conn = netConnections[context->playerNr];
+
+ // First try whether there is incoming data, without blocking.
+ // If there isn't any, only then give a warning, and then
+ // block after all.
+ netInput();
+ if (!NetConnection_isConnected(conn))
+ {
+ // Connection aborted.
+ GLOBAL(CurrentActivity) |= CHECK_ABORT;
+ return (BATTLE_INPUT_STATE) 0;
+ }
+
+ if (GLOBAL(CurrentActivity) & CHECK_ABORT)
+ return (BATTLE_INPUT_STATE) 0;
+
+#if 0
+ log_add(log_Warning, "NETPLAY: [%d] stalling for "
+ "network input. Increase the input delay if this "
+ "happens a lot.\n", context->playerNr);
+#endif
+#define MAX_BLOCK_TIME 500
+ netInputBlocking(MAX_BLOCK_TIME);
+ if (!NetConnection_isConnected(conn))
+ {
+ // Connection aborted.
+ GLOBAL(CurrentActivity) |= CHECK_ABORT;
+ return (BATTLE_INPUT_STATE) 0;
+ }
+ }
+ }
+
+ (void) StarShipPtr;
+ return result;
+}
+
+static void
+deleteConnectionCallback(NetConnection *conn) {
+ removeNetConnection(NetConnection_getPlayerNr(conn));
+}
+
+NetConnection *
+openPlayerNetworkConnection(COUNT player, void *extra) {
+ NetConnection *conn;
+
+ assert(netConnections[player] == NULL);
+
+ conn = NetConnection_open(player,
+ &netplayOptions.peer[player], NetMelee_connectCallback,
+ NetMelee_closeCallback, NetMelee_errorCallback,
+ deleteConnectionCallback, extra);
+
+ addNetConnection(conn, player);
+ return conn;
+}
+
+void
+closePlayerNetworkConnection(COUNT player) {
+ assert(netConnections[player] != NULL);
+
+ NetConnection_close(netConnections[player]);
+}
+
+bool
+setupInputDelay(size_t localInputDelay) {
+ COUNT player;
+ bool haveNetworkPlayer = false;
+ // We have at least one network controlled player.
+ size_t inputDelay = 0;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn))
+ continue;
+
+ haveNetworkPlayer = true;
+ if (NetConnection_getInputDelay(conn) > inputDelay)
+ inputDelay = NetConnection_getInputDelay(conn);
+ }
+
+ if (haveNetworkPlayer && inputDelay < localInputDelay)
+ inputDelay = localInputDelay;
+
+ setBattleInputDelay(inputDelay);
+ return true;
+}
+
+static bool
+setStateConnection(NetConnection *conn, void *arg) {
+ const NetState *state = (NetState *) arg;
+ NetConnection_setState(conn, *state);
+ return true;
+}
+
+bool
+setStateConnections(NetState state) {
+ return forEachConnectedPlayer(setStateConnection, &state);
+}
+
+static bool
+sendAbortConnection(NetConnection *conn, void *arg) {
+ const NetplayAbortReason *reason = (NetplayAbortReason *) arg;
+ sendAbort(conn, *reason);
+ return true;
+}
+
+bool
+sendAbortConnections(NetplayAbortReason reason) {
+ return forEachConnectedPlayer(sendAbortConnection, &reason);
+}
+
+static bool
+resetConnection(NetConnection *conn, void *arg) {
+ const NetplayResetReason *reason = (NetplayResetReason *) arg;
+ Netplay_localReset(conn, *reason);
+ return true;
+}
+
+bool
+resetConnections(NetplayResetReason reason) {
+ return forEachConnectedPlayer(resetConnection, &reason);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+
+typedef struct {
+ NetConnection_ReadyCallback readyCallback;
+ void *readyCallbackArg;
+ bool notifyRemote;
+} LocalReadyConnectionArg;
+
+static bool
+localReadyConnection(NetConnection *conn, void *arg) {
+ LocalReadyConnectionArg *readyArg = (LocalReadyConnectionArg *) arg;
+ Netplay_localReady(conn, readyArg->readyCallback,
+ readyArg->readyCallbackArg, readyArg->notifyRemote);
+ return true;
+}
+
+bool
+localReadyConnections(NetConnection_ReadyCallback readyCallback,
+ void *readyArg, bool notifyRemote) {
+ LocalReadyConnectionArg arg;
+ arg.readyCallback = readyCallback;
+ arg.readyCallbackArg = readyArg;
+ arg.notifyRemote = notifyRemote;
+
+ return forEachConnectedPlayer(localReadyConnection, &arg);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+
+#define NETWORK_POLL_DELAY (ONE_SECOND / 24)
+
+typedef struct NegotiateReadyState NegotiateReadyState;
+struct NegotiateReadyState {
+ // Common fields of INPUT_STATE_DESC, from which this structure
+ // "inherits".
+ BOOLEAN(*InputFunc)(void *pInputState);
+
+ NetConnection *conn;
+ NetState nextState;
+ bool done;
+};
+
+static BOOLEAN
+negotiateReadyInputFunc(NegotiateReadyState *state) {
+ netInputBlocking(NETWORK_POLL_DELAY);
+ // The timing out is necessary so that immediate key presses get
+ // handled while we wait. If we could do without the timeout,
+ // we wouldn't even need negotiateReadyInputFunc() and the
+ // DoInput() call.
+
+ // No need to call flushPacketQueues(); nothing needs to be sent
+ // right now.
+
+ if (!NetConnection_isConnected(state->conn))
+ return FALSE;
+
+ return !state->done;
+}
+
+// Called when both sides are ready
+static void
+negotiateReadyBothReadyCallback(NetConnection *conn, void *arg) {
+ NegotiateReadyState *state =(NegotiateReadyState *) arg;
+
+ NetConnection_setState(conn, state->nextState);
+ // This has to be done immediately, as more packets in the
+ // receive queue may be handled by the netInput() call that
+ // triggered this callback.
+ // This is the reason for the nextState argument to
+ // negotiateReady(); setting the state after the call to
+ // negotiateReady() would be too late.
+ state->done = true;
+}
+
+bool
+negotiateReady(NetConnection *conn, bool notifyRemote, NetState nextState) {
+ NegotiateReadyState state;
+ state.InputFunc = (BOOLEAN(*)(void *)) negotiateReadyInputFunc;
+ state.conn = conn;
+ state.nextState = nextState;
+ state.done = false;
+
+ Netplay_localReady(conn, negotiateReadyBothReadyCallback,
+ (void *) &state, notifyRemote);
+ flushPacketQueue(conn);
+ if (!state.done)
+ DoInput(&state, FALSE);
+
+ return NetConnection_isConnected(conn);
+}
+
+// Wait for all connections to get ready.
+// XXX: Right now all connections are handled one by one. Handling them all
+// at once would be faster but would require more work, which is
+// not worth it as the time is minimal and this function is not
+// time critical.
+bool
+negotiateReadyConnections(bool notifyRemote, NetState nextState) {
+ COUNT player;
+ size_t numDisconnected = 0;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn)) {
+ numDisconnected++;
+ continue;
+ }
+
+ negotiateReady(conn, notifyRemote, nextState);
+ }
+
+ return numDisconnected == 0;
+}
+
+typedef struct WaitReadyState WaitReadyState;
+struct WaitReadyState {
+ // Common fields of INPUT_STATE_DESC, from which this structure
+ // "inherits".
+ BOOLEAN (*InputFunc)(void *pInputState);
+
+ NetConnection *conn;
+ NetConnection_ReadyCallback readyCallback;
+ void *readyCallbackArg;
+ bool done;
+};
+
+static void
+waitReadyCallback(NetConnection *conn, void *arg) {
+ WaitReadyState *state =(WaitReadyState *) arg;
+ state->done = true;
+
+ // Call the original callback.
+ state->readyCallback(conn, state->readyCallbackArg);
+}
+
+static BOOLEAN
+waitReadyInputFunc(WaitReadyState *state) {
+ netInputBlocking(NETWORK_POLL_DELAY);
+ // The timing out is necessary so that immediate key presses get
+ // handled while we wait. If we could do without the timeout,
+ // we wouldn't even need negotiateReadyInputFunc() and the
+ // DoInput() call.
+
+ // No need to call flushPacketQueues(); nothing needs to be sent
+ // right now.
+
+ if (!NetConnection_isConnected(state->conn))
+ return FALSE;
+
+ return !state->done;
+}
+
+bool
+waitReady(NetConnection *conn) {
+ WaitReadyState state;
+ state.InputFunc =(BOOLEAN(*)(void *)) waitReadyInputFunc;
+ state.conn = conn;
+ state.readyCallback = NetConnection_getReadyCallback(conn);
+ state.readyCallbackArg = NetConnection_getReadyCallbackArg(conn);
+ state.done = false;
+
+ NetConnection_setReadyCallback(conn, waitReadyCallback, (void *) &state);
+
+ DoInput(&state, FALSE);
+
+ return NetConnection_isConnected(conn);
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+
+typedef struct WaitResetState WaitResetState;
+struct WaitResetState {
+ // Common fields of INPUT_STATE_DESC, from which this structure
+ // "inherits".
+ BOOLEAN(*InputFunc)(void *pInputState);
+
+ NetConnection *conn;
+ NetState nextState;
+ bool done;
+};
+
+static BOOLEAN
+waitResetInputFunc(WaitResetState *state) {
+ netInputBlocking(NETWORK_POLL_DELAY);
+ // The timing out is necessary so that immediate key presses get
+ // handled while we wait. If we could do without the timeout,
+ // we wouldn't even need waitResetInputFunc() and the
+ // DoInput() call.
+
+ // No need to call flushPacketQueues(); nothing needs to be sent
+ // right now.
+
+ if (!NetConnection_isConnected(state->conn))
+ return FALSE;
+
+ return !state->done;
+}
+
+// Called when both sides are reset.
+static void
+waitResetBothResetCallback(NetConnection *conn, void *arg) {
+ WaitResetState *state = (WaitResetState *) arg;
+
+ if (state->nextState != (NetState) -1) {
+ NetConnection_setState(conn, state->nextState);
+ // This has to be done immediately, as more packets in the
+ // receive queue may be handled by the netInput() call that
+ // triggered this callback.
+ // This is the reason for the nextState argument to
+ // waitReset(); setting the state after the call to
+ // waitReset() would be too late.
+ }
+ state->done = true;
+}
+
+bool
+waitReset(NetConnection *conn, NetState nextState) {
+ WaitResetState state;
+ state.InputFunc = (BOOLEAN(*)(void *)) waitResetInputFunc;
+ state.conn = conn;
+ state.nextState = nextState;
+ state.done = false;
+
+ Netplay_setResetCallback(conn, waitResetBothResetCallback,
+ (void *) &state);
+ if (state.done)
+ goto out;
+
+
+ if (!Netplay_isLocalReset(conn)) {
+ Netplay_localReset(conn, ResetReason_manualReset);
+ flushPacketQueue(conn);
+ }
+
+ if (!state.done)
+ DoInput(&state, FALSE);
+
+out:
+ return NetConnection_isConnected(conn);
+}
+
+// Wait until we have received a reset packet from all connections. If we
+// ourselves have not sent a reset packet, one is sent, with reason
+// 'manualReset'.
+// XXX: Right now all connections are handled one by one. Handling them all
+// at once would be faster but would require more work, which is
+// not worth it as the time is minimal and this function is not
+// time critical.
+// Use '(NetState) -1' for nextState to keep the current state.
+bool
+waitResetConnections(NetState nextState) {
+ COUNT player;
+ size_t numDisconnected = 0;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn)) {
+ numDisconnected++;
+ continue;
+ }
+
+ waitReset(conn, nextState);
+ }
+
+ return numDisconnected == 0;
+}
+
+////////////////////////////////////////////////////////////////////////////
+
diff --git a/src/uqm/supermelee/netplay/netmelee.h b/src/uqm/supermelee/netplay/netmelee.h
new file mode 100644
index 0000000..83f3562
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netmelee.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#if !defined(UQM_SUPERMELEE_NETPLAY_NETMELEE_H_) && defined(NETPLAY)
+#define UQM_SUPERMELEE_NETPLAY_NETMELEE_H_
+
+#include "netplay.h"
+#include "netinput.h"
+#include "netconnection.h"
+#include "packetsenders.h"
+
+#include "../../battlecontrols.h"
+ // for NetworkInputContext
+#include "../../controls.h"
+ // for BATTLE_INPUT_STATE
+#include "../../races.h"
+ // for STARSHIP
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+extern struct NetConnection *netConnections[];
+
+
+void addNetConnection(NetConnection *conn, int playerNr);
+void removeNetConnection(int playerNr);
+void closeAllConnections(void);
+void closeDisconnectedConnections(void);
+size_t getNumNetConnections(void);
+typedef bool(*ForEachConnectionCallback)(NetConnection *conn, void *arg);
+bool forEachConnectedPlayer(ForEachConnectionCallback callback, void *arg);
+
+struct melee_state *NetMelee_getMeleeState(NetConnection *conn);
+struct battlestate_struct *NetMelee_getBattleState(NetConnection *conn);
+
+void netInput(void);
+void netInputBlocking(uint32 timeoutMs);
+void flushPacketQueues(void);
+
+void confirmConnections(void);
+void cancelConfirmations(void);
+void connectionsLocalReady(NetConnection_ReadyCallback callback, void *arg);
+
+bool allConnected(void);
+
+void initBattleStateDataConnections(void);
+void setBattleStateConnections(struct battlestate_struct *bs);
+
+BATTLE_INPUT_STATE networkBattleInput(NetworkInputContext *context,
+ STARSHIP *StarShipPtr);
+
+NetConnection *openPlayerNetworkConnection(COUNT player, void *extra);
+void closePlayerNetworkConnection(COUNT player);
+
+bool setupInputDelay(size_t localInputDelay);
+bool setStateConnections(NetState state);
+bool sendAbortConnections(NetplayAbortReason reason);
+bool resetConnections(NetplayResetReason reason);
+bool localReadyConnections(NetConnection_ReadyCallback readyCallback,
+ void *arg, bool notifyRemote);
+
+bool negotiateReady(NetConnection *conn, bool notifyRemote,
+ NetState nextState);
+bool negotiateReadyConnections(bool notifyRemote, NetState nextState);
+bool waitReady(NetConnection *conn);
+
+bool waitReset(NetConnection *conn, NetState nextState);
+bool waitResetConnections(NetState nextState);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_NETMELEE_H_ */
diff --git a/src/uqm/supermelee/netplay/netmisc.c b/src/uqm/supermelee/netplay/netmisc.c
new file mode 100644
index 0000000..3ab2f72
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netmisc.c
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "netplay.h"
+#include "netmisc.h"
+
+#include "netmelee.h"
+#include "notifyall.h"
+#include "packetsenders.h"
+#include "proto/ready.h"
+
+#include "../melee.h"
+ // For feedback functions.
+
+#include <stdlib.h>
+
+
+static BattleStateData *BattleStateData_alloc(void);
+static void BattleStateData_free(BattleStateData *battleStateData);
+static inline BattleStateData *BattleStateData_new(
+ struct melee_state *meleeState,
+ struct battlestate_struct *battleState,
+ struct getmelee_struct *getMeleeState);
+static void BattleStateData_delete(BattleStateData *battleStateData);
+
+
+static BattleStateData *
+BattleStateData_alloc(void) {
+ return (BattleStateData *) malloc(sizeof (BattleStateData));
+}
+
+static void
+BattleStateData_free(BattleStateData *battleStateData) {
+ free(battleStateData);
+}
+
+static inline BattleStateData *
+BattleStateData_new(struct melee_state *meleeState,
+ struct battlestate_struct *battleState,
+ struct getmelee_struct *getMeleeState) {
+ BattleStateData *battleStateData = BattleStateData_alloc();
+ battleStateData->releaseFunction =
+ (NetConnectionStateData_ReleaseFunction) BattleStateData_delete;
+ battleStateData->meleeState = meleeState;
+ battleStateData->battleState = battleState;
+ battleStateData->getMeleeState = getMeleeState;
+ return battleStateData;
+}
+
+static void
+BattleStateData_delete(BattleStateData *battleStateData) {
+ BattleStateData_free(battleStateData);
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+
+
+static void NetMelee_enterState_inSetup(NetConnection *conn, void *arg);
+
+// Called when a connection has been established.
+void
+NetMelee_connectCallback(NetConnection *conn) {
+ BattleStateData *battleStateData;
+ struct melee_state *meleeState;
+
+ meleeState = (struct melee_state *) NetConnection_getExtra(conn);
+ battleStateData = BattleStateData_new(meleeState, NULL, NULL);
+ NetConnection_setStateData(conn, (void *) battleStateData);
+ NetConnection_setExtra(conn, NULL);
+
+ // We have sent no teams yet. Initialize the state accordingly.
+ MeleeSetup_resetSentTeams (meleeState->meleeSetup);
+
+ sendInit(conn);
+ Netplay_localReady (conn, NetMelee_enterState_inSetup, NULL, false);
+}
+
+// Called when a connection is closed.
+void
+NetMelee_closeCallback(NetConnection *conn) {
+ closeFeedback(conn);
+}
+
+// Called when a network error occurs during connect.
+void
+NetMelee_errorCallback(NetConnection *conn,
+ const NetConnectionError *error) {
+ errorFeedback(conn);
+ (void) error;
+}
+
+// Callback function for when both sides have finished initialisation after
+// initial connect.
+static void
+NetMelee_enterState_inSetup(NetConnection *conn, void *arg) {
+ BattleStateData *battleStateData;
+ struct melee_state *meleeState;
+ int player;
+
+ NetConnection_setState(conn, NetState_inSetup);
+
+ battleStateData = (BattleStateData *) NetConnection_getStateData(conn);
+ meleeState = battleStateData->meleeState;
+
+ player = NetConnection_getPlayerNr(conn);
+
+ connectedFeedback(conn);
+
+ // Send our team to the remote side.
+ // XXX This only works with 2 players atm.
+ assert (NUM_PLAYERS == 2);
+ Melee_bootstrapSyncTeam (meleeState, player);
+
+ flushPacketQueues();
+
+ (void) arg;
+}
+
diff --git a/src/uqm/supermelee/netplay/netmisc.h b/src/uqm/supermelee/netplay/netmisc.h
new file mode 100644
index 0000000..ea14921
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netmisc.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_NETMISC_H_
+#define UQM_SUPERMELEE_NETPLAY_NETMISC_H_
+
+typedef struct BattleStateData BattleStateData;
+
+#include "netconnection.h"
+#include "netstate.h"
+#include "types.h"
+
+#include "../../battle.h"
+ // for BattleFrameCounter, BATTLE_FRAME_RATE
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct BattleStateData {
+ NETCONNECTION_STATE_DATA_COMMON
+
+ struct melee_state *meleeState;
+ struct battlestate_struct *battleState;
+ struct getmelee_struct *getMeleeState;
+ BattleFrameCounter endFrameCount;
+};
+
+
+void NetMelee_connectCallback(NetConnection *conn);
+void NetMelee_closeCallback(NetConnection *conn);
+void NetMelee_errorCallback(NetConnection *conn,
+ const NetConnectionError *error);
+
+void NetMelee_reenterState_inSetup(NetConnection *conn);
+
+
+// Returns true iff the connection is in a state where the confirmation
+// handshake is meaningful. Right now this is only when we're in the
+// pre-game setup menu.
+static inline bool
+handshakeMeaningful(NetState state) {
+ return state == NetState_inSetup;
+}
+
+static inline bool
+readyFlagsMeaningful(NetState state) {
+ return state == NetState_init ||
+ state == NetState_preBattle ||
+ state == NetState_selectShip ||
+ state == NetState_interBattle ||
+ state == NetState_inBattle ||
+ state == NetState_endingBattle ||
+ state == NetState_endingBattle2;
+}
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_NETMISC_H_ */
diff --git a/src/uqm/supermelee/netplay/netoptions.c b/src/uqm/supermelee/netplay/netoptions.c
new file mode 100644
index 0000000..a74e1a3
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netoptions.c
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "netoptions.h"
+
+NetplayOptions netplayOptions = {
+ /* .metaServer = */ "uqm.stack.nl",
+ /* .metaPort = */ "21836",
+ /* .peer = */ {
+ /* [0] Player 1 (bottom) */ {
+ /* .isServer = */ true,
+ /* .host = */ "localhost",
+ /* .port = */ "21837" /* 0x554d - "UM" */,
+ },
+ /* [1] Player 2 (top) */ {
+ /* .isServer = */ true,
+ /* .host = */ "localhost",
+ /* .port = */ "21837" /* 0x554d - "UM" */,
+ },
+ },
+ /* .inputDelay = */ 2,
+};
+
+
diff --git a/src/uqm/supermelee/netplay/netoptions.h b/src/uqm/supermelee/netplay/netoptions.h
new file mode 100644
index 0000000..77b1637
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netoptions.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_NETOPTIONS_H_
+#define UQM_SUPERMELEE_NETPLAY_NETOPTIONS_H_
+
+#include "types.h"
+
+#include <stddef.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define NETPLAY_NUM_PLAYERS 2
+ // Not using NUM_PLAYERS because that would mean we'd have
+ // to include init.h, and all that comes with it.
+ // XXX: Don't use a hardcoded limit.
+
+typedef struct {
+ bool isServer;
+ const char *host;
+ const char *port;
+ // May be given as a service name.
+} NetplayPeerOptions;
+
+typedef struct {
+ const char *metaServer;
+ const char *metaPort;
+ // May be given as a service name.
+ NetplayPeerOptions peer[NETPLAY_NUM_PLAYERS];
+ size_t inputDelay;
+} NetplayOptions;
+extern NetplayOptions netplayOptions;
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_NETOPTIONS_H_ */
diff --git a/src/uqm/supermelee/netplay/netplay.h b/src/uqm/supermelee/netplay/netplay.h
new file mode 100644
index 0000000..b78c69a
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netplay.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#if !defined(UQM_SUPERMELEE_NETPLAY_NETPLAY_H_) && defined(NETPLAY)
+#define UQM_SUPERMELEE_NETPLAY_NETPLAY_H_
+
+// NETPLAY can either be unset (in which case we will never get here)
+// NETPLAY_FULL, or NETPLAY_IPV4 (disables IPv6)
+#define NETPLAY_IPV4 1
+#define NETPLAY_FULL 2
+
+#define NETPLAY_PROTOCOL_VERSION_MAJOR 0
+#define NETPLAY_PROTOCOL_VERSION_MINOR 4
+
+#define NETPLAY_MIN_UQM_VERSION_MAJOR 0
+#define NETPLAY_MIN_UQM_VERSION_MINOR 6
+#define NETPLAY_MIN_UQM_VERSION_PATCH 9
+
+#undef NETPLAY_DEBUG
+ /* Extra debugging for netplay */
+#undef NETPLAY_DEBUG_FILE
+ /* Dump extra debugging information to file.
+ * Implies NETPLAY_DEBUG.*/
+#define NETPLAY_STATISTICS
+ /* Keep some statistics */
+#define NETPLAY_CHECKSUM
+ /* Send/process checksums to verify that both sides of a network
+ * connection are still in sync.
+ * If not enabled, incoming checksum packets will be ignored.
+ * TODO: make compilation of crc.c and checksum.c conditional. */
+#define NETPLAY_CHECKSUM_INTERVAL 1
+ /* If NETPLAY_CHECKSUM is defined, this define determines
+ * every how many frames a checksum packet is sent. */
+
+#define NETPLAY_READBUFSIZE 2048
+#define NETPLAY_CONNECTTIMEOUT 2000
+ /* Time to wait for a connect() to succeed. In ms. */
+//#define NETPLAY_LISTENTIMEOUT 30000
+// /* Time to wait for a listen() to succeed. In ms. */
+#define NETPLAY_RETRYDELAY 2000
+ /* Time to wait after all addresses of a host have been tried
+ * before starting retrying them all. In ms. */
+#define NETPLAY_LISTEN_BACKLOG 2
+ /* Second argument to listen(). */
+
+
+#ifdef _MSC_VER
+# if _MSC_VER < 1300
+ /* NETPLAY_DEBUG_FILE requires the __VA_ARGS__ macro, which is
+ * not available on MSVC 6.0. */
+# undef NETPLAY_DEBUG_FILE
+# endif
+#endif
+
+#ifdef NETPLAY_DEBUG_FILE
+# define NETPLAY_DEBUG
+# define DUMP_CRC_OPS
+#endif
+
+
+#endif /* UQM_SUPERMELEE_NETPLAY_NETPLAY_H_ */
+
diff --git a/src/uqm/supermelee/netplay/netrcv.c b/src/uqm/supermelee/netplay/netrcv.c
new file mode 100644
index 0000000..b9ea5f7
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netrcv.c
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define PORT_WANT_ERRNO
+#define NETCONNECTION_INTERNAL
+#include "netplay.h"
+#include "port.h"
+
+#include "netconnection.h"
+#include "netrcv.h"
+#include "packet.h"
+
+#include "types.h"
+#include "libs/log.h"
+
+#include <errno.h>
+#include <string.h>
+
+
+// Try to get a single packet from a stream of data.
+// Returns 0 if the packet was successfully processed, or
+// -1 on an error, in which case the state is unchanged.
+static ssize_t
+dataReceivedSingle(NetConnection *conn, const uint8 *data,
+ size_t dataLen) {
+ uint32 packetLen;
+ PacketType type;
+ int result;
+
+ if (dataLen < sizeof (PacketHeader)) {
+ // Incomplete packet. We'll have to wait for the rest.
+ return 0;
+ }
+
+ packetLen = packetLength((const Packet *) data);
+ type = packetType((const Packet *) data);
+
+ if (!validPacketType(type)) {
+ log_add(log_Warning, "Packet with invalid type %d received.\n", type);
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (packetLen < packetTypeData[type].len) {
+ // Bad len field of packet.
+ log_add(log_Warning, "Packet with bad length field received (type="
+ "%s, lenfield=%d.\n", packetTypeData[type].name,
+ packetLen);
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (dataLen < packetLen) {
+ // Incomplete packet. We'll have to wait for the rest.
+ return 0;
+ }
+
+#ifdef NETPLAY_STATISTICS
+ NetConnection_getStatistics(conn)->packetsReceived++;
+ NetConnection_getStatistics(conn)->packetTypeReceived[type]++;
+#endif
+
+#ifdef NETPLAY_DEBUG
+ if (type != PACKET_BATTLEINPUT && type != PACKET_CHECKSUM) {
+ // Reporting BattleInput and Checksum would get so spammy that it
+ // would slow down the battle.
+ log_add(log_Debug, "NETPLAY: [%d] <== Received packet of type %s.\n",
+ NetConnection_getPlayerNr(conn), packetTypeData[type].name);
+ }
+#ifdef NETPLAY_DEBUG_FILE
+ if (conn->debugFile != NULL) {
+ uio_fprintf(conn->debugFile,
+ "NETPLAY: [%d] <== Received packet of type %s.\n",
+ NetConnection_getPlayerNr(conn), packetTypeData[type].name);
+ }
+#endif /* NETPLAY_DEBUG_FILE */
+#endif /* NETPLAY_DEBUG */
+
+ result = packetTypeData[type].handler(conn, data);
+ if (result == -1) {
+ // An error occured. errno is set by the handler.
+ return -1;
+ }
+
+ return packetLen;
+}
+
+// Try to get all the packets from a stream of data.
+// Returns the number of bytes processed.
+static ssize_t
+dataReceivedMulti(NetConnection *conn, const uint8 *data, size_t len) {
+ size_t processed;
+
+ processed = 0;
+ while (len > 0) {
+ ssize_t packetLen = dataReceivedSingle(conn, data, len);
+ if (packetLen == -1) {
+ // Bad packet. Errno is set.
+ return -1;
+ }
+
+ if (packetLen == 0) {
+ // No packet was processed. This means that no complete
+ // packet arrived.
+ break;
+ }
+
+ processed += packetLen;
+ data += packetLen;
+ len -= packetLen;
+ }
+
+ return processed;
+}
+
+void
+dataReadyCallback(NetDescriptor *nd) {
+ NetConnection *conn = (NetConnection *) NetDescriptor_getExtra(nd);
+ Socket *socket = NetDescriptor_getSocket(nd);
+
+ for (;;) {
+ ssize_t numRead;
+ ssize_t numProcessed;
+
+ numRead = Socket_recv(socket, conn->readEnd,
+ NETPLAY_READBUFSIZE - (conn->readEnd - conn->readBuf), 0);
+ if (numRead == 0) {
+ // Other side closed the connection.
+ NetDescriptor_close(nd);
+ return;
+ }
+
+ if (numRead == -1) {
+ if (errno == EWOULDBLOCK || errno == EAGAIN)
+ return; // No more data for now.
+ else if (errno == EINTR)
+ continue; // System call was interrupted. Retry.
+ else
+ {
+ int savedErrno = errno;
+ log_add(log_Error, "recv() failed: %s.\n",
+ strerror(errno));
+ NetConnection_doErrorCallback(conn, savedErrno);
+ NetDescriptor_close(nd);
+ return;
+ }
+ }
+
+ conn->readEnd += numRead;
+
+ numProcessed = dataReceivedMulti(conn, conn->readBuf,
+ conn->readEnd - conn->readBuf);
+ if (numProcessed == -1) {
+ // An error occured during processing.
+ // errno is set.
+ NetConnection_doErrorCallback(conn, errno);
+ NetDescriptor_close(nd);
+ return;
+ }
+ if (numProcessed == 0) {
+ // No packets could be processed. This means we need to receive
+ // more data first.
+ return;
+ }
+
+ // Some packets have been processed.
+ // We more any rest to the front of the buffer, to make room
+ // for more data.
+ // A cyclic buffer would obviate the need for this move,
+ // but it would complicate things a lot.
+ memmove(conn->readBuf, conn->readBuf + numProcessed,
+ (conn->readEnd - conn->readBuf) - numProcessed);
+ conn->readEnd -= numProcessed;
+ }
+}
+
+
+
diff --git a/src/uqm/supermelee/netplay/netrcv.h b/src/uqm/supermelee/netplay/netrcv.h
new file mode 100644
index 0000000..a7577d9
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netrcv.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_NETRCV_H_
+#define UQM_SUPERMELEE_NETPLAY_NETRCV_H_
+
+#include "libs/net.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+void dataReadyCallback(NetDescriptor *nd);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_NETRCV_H_ */
diff --git a/src/uqm/supermelee/netplay/netsend.c b/src/uqm/supermelee/netplay/netsend.c
new file mode 100644
index 0000000..b9f371f
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netsend.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define PORT_WANT_ERRNO
+#define NETCONNECTION_INTERNAL
+#include "netplay.h"
+#include "port.h"
+
+#include "netsend.h"
+#include "netconnection.h"
+#include "packet.h"
+#include "libs/log.h"
+#include "libs/net.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+
+
+int
+sendPacket(NetConnection *conn, Packet *packet) {
+ ssize_t sendResult;
+ size_t len;
+ Socket *socket;
+
+ assert(NetConnection_isConnected(conn));
+
+#ifdef NETPLAY_DEBUG
+ //if (packetType(packet) != PACKET_BATTLEINPUT &&
+ // packetType(packet) != PACKET_CHECKSUM) {
+ // // Reporting BattleInput or Checksum would get so spammy that it
+ // // would slow down the battle.
+ // log_add(log_Debug, "NETPLAY: [%d] ==> Sending packet of type %s.\n",
+ // conn->player, packetTypeData[packetType(packet)].name);
+ //}
+#ifdef NETPLAY_DEBUG_FILE
+ if (conn->debugFile != NULL) {
+ uio_fprintf(conn->debugFile,
+ "NETPLAY: [%d] ==> Sending packet of type %s.\n",
+ conn->player, packetTypeData[packetType(packet)].name);
+ }
+#endif /* NETPLAY_DEBUG_FILE */
+#endif /* NETPLAY_DEBUG */
+
+ socket = NetDescriptor_getSocket(conn->nd);
+
+ len = packetLength(packet);
+ while (len > 0) {
+ sendResult = Socket_send(socket, (void *) packet, len, 0);
+ if (sendResult >= 0) {
+ len -= sendResult;
+ continue;
+ }
+
+ switch (errno) {
+ case EINTR: // System call interrupted, retry;
+ continue;
+ case ECONNRESET: { // Connection reset by peer.
+ // keep errno
+ return -1;
+ }
+ default: {
+ // Should not happen.
+ int savedErrno = errno;
+ log_add(log_Error, "send() failed: %s.\n", strerror(errno));
+ errno = savedErrno;
+ return -1;
+ }
+ }
+ }
+
+#ifdef NETPLAY_STATISTICS
+ NetConnection_getStatistics(conn)->packetsSent++;
+ NetConnection_getStatistics(conn)->packetTypeSent[packetType(packet)]++;
+#endif
+
+ return 0;
+}
+
+
diff --git a/src/uqm/supermelee/netplay/netsend.h b/src/uqm/supermelee/netplay/netsend.h
new file mode 100644
index 0000000..e005d03
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netsend.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_NETSEND_H_
+#define UQM_SUPERMELEE_NETPLAY_NETSEND_H_
+
+#include "packet.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+int sendPacket(NetConnection *conn, Packet *packet);
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_NETSEND_H_ */
diff --git a/src/uqm/supermelee/netplay/netstate.c b/src/uqm/supermelee/netplay/netstate.c
new file mode 100644
index 0000000..4382b94
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netstate.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "netplay.h"
+#include "netstate.h"
+
+#include <assert.h>
+#include <stdlib.h>
+
+
+#define DEFINE_NETSTATEDATA(stateName) \
+ { \
+ /* .name = */ #stateName, \
+ }
+NetStateData netStateData[] = {
+ DEFINE_NETSTATEDATA(unconnected),
+ DEFINE_NETSTATEDATA(connecting),
+ DEFINE_NETSTATEDATA(init),
+ DEFINE_NETSTATEDATA(inSetup),
+ DEFINE_NETSTATEDATA(preBattle),
+ DEFINE_NETSTATEDATA(interBattle),
+ DEFINE_NETSTATEDATA(selectShip),
+ DEFINE_NETSTATEDATA(inBattle),
+ DEFINE_NETSTATEDATA(endingBattle),
+ DEFINE_NETSTATEDATA(endingBattle2),
+};
+
+void
+NetConnectionStateData_release(NetConnectionStateData *stateData) {
+ assert(stateData->releaseFunction != NULL);
+ stateData->releaseFunction(stateData);
+}
+
diff --git a/src/uqm/supermelee/netplay/netstate.h b/src/uqm/supermelee/netplay/netstate.h
new file mode 100644
index 0000000..1d46d49
--- /dev/null
+++ b/src/uqm/supermelee/netplay/netstate.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_NETSTATE_H_
+#define UQM_SUPERMELEE_NETPLAY_NETSTATE_H_
+
+#include "port.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct NetConnectionStateData NetConnectionStateData;
+
+// State of a NetConnection.
+typedef enum {
+ NetState_unconnected, /* No connection initiated */
+ NetState_connecting, /* Connection being setup */
+ NetState_init, /* Initialising the connection */
+ NetState_inSetup, /* In the network game setup */
+ NetState_preBattle, /* Pre-battle initialisations */
+ NetState_interBattle, /* Negotiations between battles. */
+ NetState_selectShip, /* Selecting a ship in battle */
+ NetState_inBattle, /* Battle has started */
+ NetState_endingBattle, /* Both sides are prepared to end */
+ NetState_endingBattle2, /* Waiting for the final synchronisation */
+} NetState;
+
+#if defined(__cplusplus)
+}
+#endif
+
+#include "types.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct {
+ const char *name;
+} NetStateData;
+extern NetStateData netStateData[];
+
+typedef void (*NetConnectionStateData_ReleaseFunction)(
+ NetConnectionStateData *stateData);
+
+#define NETCONNECTION_STATE_DATA_COMMON \
+ NetConnectionStateData_ReleaseFunction releaseFunction;
+
+struct
+NetConnectionStateData {
+ NETCONNECTION_STATE_DATA_COMMON
+};
+
+void NetConnectionStateData_release(NetConnectionStateData *stateData);
+
+static inline bool
+NetState_battleActive(NetState state) {
+ return state == NetState_inBattle || state == NetState_endingBattle ||
+ state == NetState_endingBattle2;
+}
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_NETSTATE_H_ */
diff --git a/src/uqm/supermelee/netplay/notify.c b/src/uqm/supermelee/netplay/notify.c
new file mode 100644
index 0000000..8b35ead
--- /dev/null
+++ b/src/uqm/supermelee/netplay/notify.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+// This files contains functions that notify the other side of local
+// changes.
+
+#define NETCONNECTION_INTERNAL
+#include "netplay.h"
+#include "notify.h"
+
+#include "packetsenders.h"
+
+
+// Convert a local player number to a side indication relative to this
+// party.
+static inline NetplaySide
+netSide(NetConnection *conn, int side) {
+ if (side == conn->player)
+ return NetplaySide_remote;
+
+ return NetplaySide_local;
+}
+
+void
+Netplay_Notify_shipSelected(NetConnection *conn, FleetShipIndex index) {
+ assert(NetConnection_getState(conn) == NetState_selectShip);
+
+ sendSelectShip(conn, index);
+}
+
+void
+Netplay_Notify_battleInput(NetConnection *conn, BATTLE_INPUT_STATE input) {
+ assert(NetConnection_getState(conn) == NetState_inBattle ||
+ NetConnection_getState(conn) == NetState_endingBattle ||
+ NetConnection_getState(conn) == NetState_endingBattle2);
+
+ sendBattleInput(conn, input);
+}
+
+void
+Netplay_Notify_setTeamName(NetConnection *conn, int player,
+ const char *name, size_t len) {
+ assert(NetConnection_getState(conn) == NetState_inSetup);
+ assert(!conn->stateFlags.handshake.localOk);
+
+ sendTeamName(conn, netSide(conn, player), name, len);
+}
+
+// On initialisation, or load.
+void
+Netplay_Notify_setFleet(NetConnection *conn, int player,
+ const MeleeShip *fleet, size_t fleetSize) {
+ assert(NetConnection_getState(conn) == NetState_inSetup);
+ assert(!conn->stateFlags.handshake.localOk);
+
+ sendFleet(conn, netSide(conn, player), fleet, fleetSize);
+}
+
+void
+Netplay_Notify_setShip(NetConnection *conn, int player,
+ FleetShipIndex index, MeleeShip ship) {
+ assert(NetConnection_getState(conn) == NetState_inSetup);
+ assert(!conn->stateFlags.handshake.localOk);
+
+ sendFleetShip(conn, netSide(conn, player), index, ship);
+}
+
+void
+Netplay_Notify_seedRandom(NetConnection *conn, uint32 seed) {
+ assert(NetConnection_getState(conn) == NetState_preBattle);
+
+ sendSeedRandom(conn, seed);
+ conn->stateFlags.agreement.randomSeed = true;
+}
+
+void
+Netplay_Notify_inputDelay(NetConnection *conn, uint32 delay) {
+ assert(NetConnection_getState(conn) == NetState_preBattle);
+
+ sendInputDelay(conn, delay);
+}
+
+void
+Netplay_Notify_frameCount(NetConnection *conn,
+ BattleFrameCounter frameCount) {
+ assert(NetConnection_getState(conn) == NetState_endingBattle);
+
+ sendFrameCount(conn, frameCount);
+}
+
+#ifdef NETPLAY_CHECKSUM
+void
+Netplay_Notify_checksum(NetConnection *conn, BattleFrameCounter frameNr,
+ Checksum checksum) {
+ assert(NetState_battleActive(NetConnection_getState(conn)));
+
+ sendChecksum(conn, frameNr, checksum);
+}
+#endif /* NETPLAY_CHECKSUM */
+
+
+
+
diff --git a/src/uqm/supermelee/netplay/notify.h b/src/uqm/supermelee/netplay/notify.h
new file mode 100644
index 0000000..60a127d
--- /dev/null
+++ b/src/uqm/supermelee/netplay/notify.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_NOTIFY_H_
+#define UQM_SUPERMELEE_NETPLAY_NOTIFY_H_
+
+#include "netplay.h"
+ // for NETPLAY_CHECKSUM
+#include "netconnection.h"
+#include "../../controls.h"
+ // for BATTLE_INPUT_STATE
+#ifdef NETPLAY_CHECKSUM
+# include "checksum.h"
+#endif
+#include "../meleeship.h"
+ // for MeleeShip
+#include "../meleesetup.h"
+ // for FleetShipIndex
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+void Netplay_Notify_shipSelected(NetConnection *conn, FleetShipIndex index);
+void Netplay_Notify_battleInput(NetConnection *conn,
+ BATTLE_INPUT_STATE input);
+void Netplay_Notify_setTeamName(NetConnection *conn, int player,
+ const char *name, size_t len);
+void Netplay_Notify_setFleet(NetConnection *conn, int player,
+ const MeleeShip *fleet, size_t fleetSize);
+void Netplay_Notify_setShip(NetConnection *conn, int player,
+ FleetShipIndex index, MeleeShip ship);
+void Netplay_Notify_seedRandom(NetConnection *conn, uint32 seed);
+void Netplay_Notify_inputDelay(NetConnection *conn, uint32 delay);
+void Netplay_Notify_frameCount(NetConnection *conn,
+ BattleFrameCounter frameCount);
+#ifdef NETPLAY_CHECKSUM
+void Netplay_Notify_checksum(NetConnection *conn,
+ BattleFrameCounter frameCount, Checksum checksum);
+#endif
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_NOTIFY_H_ */
diff --git a/src/uqm/supermelee/netplay/notifyall.c b/src/uqm/supermelee/netplay/notifyall.c
new file mode 100644
index 0000000..2d0cc8a
--- /dev/null
+++ b/src/uqm/supermelee/netplay/notifyall.c
@@ -0,0 +1,146 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "notifyall.h"
+
+#include "netmelee.h"
+#include "notify.h"
+
+// Notify the network connections of a team name change.
+void
+Netplay_NotifyAll_setTeamName (MELEE_STATE *pMS, size_t playerNr)
+{
+ const char *name;
+ size_t len;
+ size_t playerI;
+
+ name = MeleeSetup_getTeamName (pMS->meleeSetup, playerNr);
+ len = strlen (name);
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ {
+ NetConnection *conn = netConnections[playerI];
+
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected (conn))
+ continue;
+
+ if (NetConnection_getState (conn) != NetState_inSetup)
+ continue;
+
+ Netplay_Notify_setTeamName (conn, playerNr, name, len);
+ }
+}
+
+// Notify the network connections of the configuration of a fleet.
+void
+Netplay_NotifyAll_setFleet (MELEE_STATE *pMS, size_t playerNr)
+{
+ MeleeSetup *setup = pMS->meleeSetup;
+ const MeleeShip *ships = MeleeSetup_getFleet (setup, playerNr);
+ size_t playerI;
+
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++) {
+ NetConnection *conn = netConnections[playerI];
+
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected (conn))
+ continue;
+
+ if (NetConnection_getState (conn) != NetState_inSetup)
+ continue;
+
+ Netplay_Notify_setFleet (conn, playerNr, ships, MELEE_FLEET_SIZE);
+ }
+}
+
+// Notify the network of a change in the configuration of a fleet.
+void
+Netplay_NotifyAll_setShip (MELEE_STATE *pMS, size_t playerNr, size_t index)
+{
+ MeleeSetup *setup = pMS->meleeSetup;
+ MeleeShip ship = MeleeSetup_getShip (setup, playerNr, index);
+
+ size_t playerI;
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ {
+ NetConnection *conn = netConnections[playerI];
+
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected (conn))
+ continue;
+
+ if (NetConnection_getState (conn) != NetState_inSetup)
+ continue;
+
+ Netplay_Notify_setShip (conn, playerNr, index, ship);
+ }
+}
+
+static bool
+Netplay_NotifyAll_inputDelayCallback(NetConnection *conn, void *arg) {
+ const size_t *delay = (size_t *) arg;
+ Netplay_Notify_inputDelay(conn, *delay);
+ return true;
+}
+
+bool
+Netplay_NotifyAll_inputDelay(size_t delay) {
+ return forEachConnectedPlayer(Netplay_NotifyAll_inputDelayCallback,
+ &delay);
+}
+
+#ifdef NETPLAY_CHECKSUM
+void
+Netplay_NotifyAll_checksum(BattleFrameCounter frameNr, Checksum checksum) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn))
+ continue;
+
+ Netplay_Notify_checksum(conn, frameNr, checksum);
+ }
+}
+#endif /* NETPLAY_CHECKSUM */
+
+void
+Netplay_NotifyAll_battleInput(BATTLE_INPUT_STATE input) {
+ COUNT player;
+
+ for (player = 0; player < NUM_PLAYERS; player++)
+ {
+ NetConnection *conn = netConnections[player];
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected(conn))
+ continue;
+
+ Netplay_Notify_battleInput(conn, input);
+ }
+}
+
diff --git a/src/uqm/supermelee/netplay/notifyall.h b/src/uqm/supermelee/netplay/notifyall.h
new file mode 100644
index 0000000..d20ca81
--- /dev/null
+++ b/src/uqm/supermelee/netplay/notifyall.h
@@ -0,0 +1,49 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef NOTIFYALL_H
+#define NOTIFYALL_H
+
+#include "../../battle.h"
+#include "../../battlecontrols.h"
+#include "../melee.h"
+#ifdef NETPLAY_CHECKSUM
+# include "checksum.h"
+#endif /* NETPLAY_CHECKSUM */
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+void Netplay_NotifyAll_setTeamName (MELEE_STATE *pMS, size_t playerNr);
+void Netplay_NotifyAll_setFleet (MELEE_STATE *pMS, size_t playerNr);
+void Netplay_NotifyAll_setShip (MELEE_STATE *pMS, size_t playerNr,
+ size_t index);
+
+bool Netplay_NotifyAll_inputDelay(size_t delay);
+#ifdef NETPLAY_CHECKSUM
+void Netplay_NotifyAll_checksum(BattleFrameCounter frameNr,
+ Checksum checksum);
+#endif /* NETPLAY_CHECKSUM */
+void Netplay_NotifyAll_battleInput(BATTLE_INPUT_STATE input);
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* NOTIFYALL_H */
+
diff --git a/src/uqm/supermelee/netplay/packet.c b/src/uqm/supermelee/netplay/packet.c
new file mode 100644
index 0000000..442be17
--- /dev/null
+++ b/src/uqm/supermelee/netplay/packet.c
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "netplay.h"
+#include "packet.h"
+
+#include "uqmversion.h"
+
+#include "netrcv.h"
+#include "packethandlers.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+#define DEFINE_PACKETDATA(name) \
+ { \
+ /* .len = */ sizeof (Packet_##name), \
+ /* .handler = */ (PacketHandler) PacketHandler_##name, \
+ /* .name = */ #name, \
+ }
+PacketTypeData packetTypeData[PACKET_NUM] = {
+ DEFINE_PACKETDATA(Init),
+ DEFINE_PACKETDATA(Ping),
+ DEFINE_PACKETDATA(Ack),
+ DEFINE_PACKETDATA(Ready),
+ DEFINE_PACKETDATA(Fleet),
+ DEFINE_PACKETDATA(TeamName),
+ DEFINE_PACKETDATA(Handshake0),
+ DEFINE_PACKETDATA(Handshake1),
+ DEFINE_PACKETDATA(HandshakeCancel),
+ DEFINE_PACKETDATA(HandshakeCancelAck),
+ DEFINE_PACKETDATA(SeedRandom),
+ DEFINE_PACKETDATA(InputDelay),
+ DEFINE_PACKETDATA(SelectShip),
+ DEFINE_PACKETDATA(BattleInput),
+ DEFINE_PACKETDATA(FrameCount),
+ DEFINE_PACKETDATA(Checksum),
+ DEFINE_PACKETDATA(Abort),
+ DEFINE_PACKETDATA(Reset),
+};
+
+static inline void *
+Packet_alloc(size_t size) {
+ return malloc(size);
+}
+
+static Packet *
+Packet_create(PacketType type, size_t extraSize) {
+ Packet *result;
+ size_t len;
+
+ // Alignment requirement.
+ assert(extraSize % 4 == 0);
+
+ len = packetTypeData[type].len + extraSize;
+ result = Packet_alloc(len);
+ result->header.len = hton16((uint16) len);
+ result->header.type = hton16((uint16) type);
+ return result;
+}
+
+void
+Packet_delete(Packet *packet) {
+ free(packet);
+}
+
+Packet_Init *
+Packet_Init_create(void) {
+ Packet_Init *packet = (Packet_Init *) Packet_create(PACKET_INIT, 0);
+
+ packet->protoVersion.major = NETPLAY_PROTOCOL_VERSION_MAJOR;
+ packet->protoVersion.minor = NETPLAY_PROTOCOL_VERSION_MINOR;
+ packet->padding0 = 0;
+ packet->uqmVersion.major = UQM_MAJOR_VERSION;
+ packet->uqmVersion.minor = UQM_MINOR_VERSION;
+ packet->uqmVersion.patch = UQM_PATCH_VERSION;
+ packet->padding1 = 0;
+ return packet;
+}
+
+Packet_Ping *
+Packet_Ping_create(uint32 id) {
+ Packet_Ping *packet = (Packet_Ping *) Packet_create(PACKET_PING, 0);
+
+ packet->id = hton32(id);
+ return packet;
+}
+
+Packet_Ack *
+Packet_Ack_create(uint32 id) {
+ Packet_Ack *packet = (Packet_Ack *) Packet_create(PACKET_ACK, 0);
+
+ packet->id = hton32(id);
+ return packet;
+}
+
+Packet_Ready *
+Packet_Ready_create(void) {
+ Packet_Ready *packet = (Packet_Ready *) Packet_create(PACKET_READY, 0);
+
+ return packet;
+}
+
+// The fleet itself still needs to be filled by the caller.
+// This function takes care of the necessary padding; it is allocated,
+// and filled with zero chars.
+Packet_Fleet *
+Packet_Fleet_create(NetplaySide side, size_t numShips) {
+ Packet_Fleet *packet;
+ size_t fleetSize;
+ size_t extraSize;
+
+ fleetSize = numShips * sizeof (FleetEntry);
+ extraSize = (fleetSize + 3) & ~0x03;
+ packet = (Packet_Fleet *) Packet_create(PACKET_FLEET, extraSize);
+ packet->side = (uint8) side;
+ packet->padding = 0;
+ packet->numShips = hton16((uint16) numShips);
+ memset((char *) packet + sizeof (Packet_Fleet) + fleetSize,
+ '\0', extraSize - fleetSize);
+
+ return packet;
+}
+
+// 'size' is the number of bytes (not characters) in 'name', excluding
+// a possible terminating '\0'. A '\0' will be included in the packet though.
+// This function takes care of the required padding.
+Packet_TeamName *
+Packet_TeamName_create(NetplaySide side, const char *name, size_t size) {
+ Packet_TeamName *packet;
+ size_t extraSize;
+
+ extraSize = ((size + 1) + 3) & ~0x03;
+ // The +1 is for the '\0'.
+ packet = (Packet_TeamName *) Packet_create(PACKET_TEAMNAME, extraSize);
+ packet->side = (uint8) side;
+ packet->padding = 0;
+ memcpy(packet->name, name, size);
+ memset((char *) packet + sizeof (Packet_TeamName) + size, '\0',
+ extraSize - size);
+ // This takes care of the terminating '\0', as well as the
+ // padding.
+
+ return packet;
+}
+
+Packet_Handshake0 *
+Packet_Handshake0_create(void) {
+ Packet_Handshake0 *packet =
+ (Packet_Handshake0 *) Packet_create(PACKET_HANDSHAKE0, 0);
+ return packet;
+}
+
+Packet_Handshake1 *
+Packet_Handshake1_create(void) {
+ Packet_Handshake1 *packet =
+ (Packet_Handshake1 *) Packet_create(PACKET_HANDSHAKE1, 0);
+ return packet;
+}
+
+Packet_HandshakeCancel *
+Packet_HandshakeCancel_create(void) {
+ Packet_HandshakeCancel *packet =
+ (Packet_HandshakeCancel *) Packet_create(
+ PACKET_HANDSHAKECANCEL, 0);
+ return packet;
+}
+
+Packet_HandshakeCancelAck *
+Packet_HandshakeCancelAck_create(void) {
+ Packet_HandshakeCancelAck *packet =
+ (Packet_HandshakeCancelAck *) Packet_create(
+ PACKET_HANDSHAKECANCELACK, 0);
+ return packet;
+}
+
+Packet_SeedRandom *
+Packet_SeedRandom_create(uint32 seed) {
+ Packet_SeedRandom *packet =
+ (Packet_SeedRandom *) Packet_create(PACKET_SEEDRANDOM, 0);
+
+ packet->seed = hton32(seed);
+ return packet;
+}
+
+Packet_InputDelay *
+Packet_InputDelay_create(uint32 delay) {
+ Packet_InputDelay *packet =
+ (Packet_InputDelay *) Packet_create(PACKET_INPUTDELAY, 0);
+
+ packet->delay = hton32(delay);
+ return packet;
+}
+
+Packet_SelectShip *
+Packet_SelectShip_create(uint16 ship) {
+ Packet_SelectShip *packet =
+ (Packet_SelectShip *) Packet_create(PACKET_SELECTSHIP, 0);
+ packet->ship = hton16(ship);
+ packet->padding = 0;
+ return packet;
+}
+
+Packet_BattleInput *
+Packet_BattleInput_create(uint8 state) {
+ Packet_BattleInput *packet =
+ (Packet_BattleInput *) Packet_create(PACKET_BATTLEINPUT, 0);
+ packet->state = (uint8) state;
+ packet->padding0 = 0;
+ packet->padding1 = 0;
+ return packet;
+}
+
+Packet_FrameCount *
+Packet_FrameCount_create(uint32 frameCount) {
+ Packet_FrameCount *packet =
+ (Packet_FrameCount *) Packet_create(PACKET_FRAMECOUNT, 0);
+ packet->frameCount = hton32(frameCount);
+ return packet;
+}
+
+Packet_Checksum *
+Packet_Checksum_create(uint32 frameNr, uint32 checksum) {
+ Packet_Checksum *packet =
+ (Packet_Checksum *) Packet_create(PACKET_CHECKSUM, 0);
+ packet->frameNr = hton32(frameNr);
+ packet->checksum = hton32(checksum);
+ return packet;
+}
+
+Packet_Abort *
+Packet_Abort_create(uint16 reason) {
+ Packet_Abort *packet = (Packet_Abort *) Packet_create(PACKET_ABORT, 0);
+ packet->reason = hton16(reason);
+ return packet;
+}
+
+Packet_Reset *
+Packet_Reset_create(uint16 reason) {
+ Packet_Reset *packet = (Packet_Reset *) Packet_create(PACKET_RESET, 0);
+ packet->reason = hton16(reason);
+ return packet;
+}
+
+
+
diff --git a/src/uqm/supermelee/netplay/packet.h b/src/uqm/supermelee/netplay/packet.h
new file mode 100644
index 0000000..f8c9682
--- /dev/null
+++ b/src/uqm/supermelee/netplay/packet.h
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_PACKET_H_
+#define UQM_SUPERMELEE_NETPLAY_PACKET_H_
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct Packet Packet;
+
+typedef enum PacketType {
+ PACKET_INIT,
+ PACKET_PING,
+ PACKET_ACK,
+ PACKET_READY,
+ PACKET_FLEET,
+ PACKET_TEAMNAME,
+ PACKET_HANDSHAKE0,
+ PACKET_HANDSHAKE1,
+ PACKET_HANDSHAKECANCEL,
+ PACKET_HANDSHAKECANCELACK,
+ PACKET_SEEDRANDOM,
+ PACKET_INPUTDELAY,
+ PACKET_SELECTSHIP,
+ PACKET_BATTLEINPUT,
+ PACKET_FRAMECOUNT,
+ PACKET_CHECKSUM,
+ PACKET_ABORT,
+ PACKET_RESET,
+
+ PACKET_NUM, /* Number of packet types */
+} PacketType;
+
+// Sent before aborting the connection.
+typedef enum NetplayAbortReason {
+ AbortReason_unspecified,
+ AbortReason_versionMismatch,
+ AbortReason_invalidHash,
+ AbortReason_protocolError,
+ // Network is in an inconsistent state.
+} NetplayAbortReason;
+
+// Sent before resetting the connection. A game in progress is terminated.
+typedef enum NetplayResetReason {
+ ResetReason_unspecified,
+ ResetReason_syncLoss,
+ ResetReason_manualReset,
+} NetplayResetReason;
+
+#if defined(__cplusplus)
+}
+#endif
+
+#ifndef PACKET_H_STANDALONE
+#include "netconnection.h"
+
+#include "types.h"
+#include "libs/network/bytesex.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/* NB: These handlers are expected not to modify the state if an
+ * error occurs.
+ * When a handler is called, it has already been validated that the
+ * a complete packet has arrived.
+ */
+typedef int (*PacketHandler)(NetConnection *conn, const void *packet);
+
+typedef struct {
+ size_t len; /* Minimal length of a packet of this type */
+ PacketHandler handler;
+ const char *name;
+} PacketTypeData;
+
+extern PacketTypeData packetTypeData[];
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+// When adding new packets, be sure to have all the fields properly aligned,
+// and that the size of a packet is a multiple of 4 bytes in length.
+// Fields should be no longer than 4 bytes in themselves, as larger
+// fields may require a larger alignment.
+
+typedef struct {
+ uint16 len;
+ uint16 type; /* Actually of type PacketType */
+} PacketHeader;
+
+// "Base class" for all packets.
+struct Packet {
+ PacketHeader header;
+};
+
+static inline size_t
+packetLength(const Packet *packet) {
+ return (size_t) ntoh16(packet->header.len);
+}
+
+static inline PacketType
+packetType(const Packet *packet) {
+ return (PacketType) (int) ntoh16(packet->header.type);
+}
+
+static inline bool
+validPacketType(PacketType type) {
+ return type < PACKET_NUM;
+}
+
+typedef struct {
+ PacketHeader header;
+ struct {
+ uint8 major;
+ uint8 minor;
+ } protoVersion; /* Protocol version */
+ uint16 padding0; /* Set to 0 */
+ struct {
+ uint8 major;
+ uint8 minor;
+ uint8 patch;
+ } uqmVersion; /* Protocol version */
+ uint8 padding1; /* Set to 0 */
+} Packet_Init;
+
+typedef struct {
+ PacketHeader header;
+ uint32 id;
+} Packet_Ping;
+
+// Acknowledgement of a Ping.
+typedef struct {
+ PacketHeader header;
+ uint32 id;
+} Packet_Ack;
+
+// Used to signal that a party is ready to continue.
+typedef struct {
+ PacketHeader header;
+ // No contents.
+} Packet_Ready;
+
+typedef struct {
+ PacketHeader header;
+ uint32 seed;
+} Packet_SeedRandom;
+
+typedef struct {
+ PacketHeader header;
+ uint32 delay;
+} Packet_InputDelay;
+
+// This enum is used to indicate that a packet containing it relates to
+// either the local or the remote player, from the perspective of the
+// sender of the message;
+typedef enum {
+ NetplaySide_local,
+ NetplaySide_remote
+} NetplaySide;
+
+typedef struct {
+ uint8 index; /* Position in the fleet */
+ uint8 ship; /* Ship type index; actually MeleeShip */
+} FleetEntry;
+// Structure describing an update to a player's fleet.
+// TODO: use strings as ship identifiers, instead of numbers,
+// so that adding of new ships doesn't break this.
+typedef struct {
+ PacketHeader header;
+ uint8 side;
+ uint8 padding;
+ uint16 numShips;
+ FleetEntry ships[];
+ // Be sure to add padding to this structure to make it a multiple of
+ // 4 bytes in length.
+} Packet_Fleet;
+
+typedef struct {
+ PacketHeader header;
+ uint8 side;
+ uint8 padding;
+ uint8 name[];
+ // '\0' terminated.
+ // Be sure to add padding to this structure to make it a multiple of
+ // 4 bytes in length.
+} Packet_TeamName;
+
+typedef struct {
+ PacketHeader header;
+ // No contents.
+} Packet_Handshake0;
+
+typedef struct {
+ PacketHeader header;
+ // No contents.
+} Packet_Handshake1;
+
+typedef struct {
+ PacketHeader header;
+ // No contents.
+} Packet_HandshakeCancel;
+
+typedef struct {
+ PacketHeader header;
+ // No contents.
+} Packet_HandshakeCancelAck;
+
+typedef struct {
+ PacketHeader header;
+ uint16 ship;
+ // The value '(uint16) ~0' indicates random selection.
+ uint16 padding;
+} Packet_SelectShip;
+
+typedef struct {
+ PacketHeader header;
+ uint8 state; /* Actually BATTLE_INPUT_STATE */
+ uint8 padding0;
+ uint16 padding1;
+} Packet_BattleInput;
+
+typedef struct {
+ PacketHeader header;
+ uint32 frameCount; /* Actually BattleFrameCounter */
+} Packet_FrameCount;
+
+typedef struct {
+ PacketHeader header;
+ uint32 frameNr; /* Actually BattleFrameCounter */
+ uint32 checksum; /* Actually Checksum */
+} Packet_Checksum;
+
+typedef struct {
+ PacketHeader header;
+ uint16 reason; /* Actually NetplayAbortReason */
+ uint16 padding0;
+} Packet_Abort;
+
+typedef struct {
+ PacketHeader header;
+ uint16 reason; /* Actually NetplayResetReason */
+ uint16 padding0;
+} Packet_Reset;
+
+
+#ifndef PACKET_H_STANDALONE
+void Packet_delete(Packet *packet);
+Packet_Init *Packet_Init_create(void);
+Packet_Ping *Packet_Ping_create(uint32 id);
+Packet_Ack *Packet_Ack_create(uint32 id);
+Packet_Ready *Packet_Ready_create(void);
+Packet_Handshake0 *Packet_Handshake0_create(void);
+Packet_Handshake1 *Packet_Handshake1_create(void);
+Packet_HandshakeCancel *Packet_HandshakeCancel_create(void);
+Packet_HandshakeCancelAck *Packet_HandshakeCancelAck_create(void);
+Packet_SeedRandom *Packet_SeedRandom_create(uint32 seed);
+Packet_InputDelay *Packet_InputDelay_create(uint32 delay);
+Packet_Fleet *Packet_Fleet_create(NetplaySide side, size_t numShips);
+Packet_TeamName *Packet_TeamName_create(NetplaySide side, const char *name,
+ size_t size);
+Packet_SelectShip *Packet_SelectShip_create(uint16 ship);
+Packet_BattleInput *Packet_BattleInput_create(uint8 state);
+Packet_FrameCount *Packet_FrameCount_create(uint32 frameCount);
+Packet_Checksum *Packet_Checksum_create(uint32 frameNr, uint32 checksum);
+Packet_Abort *Packet_Abort_create(uint16 reason);
+Packet_Reset *Packet_Reset_create(uint16 reason);
+#endif
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_PACKET_H_ */
+
diff --git a/src/uqm/supermelee/netplay/packethandlers.c b/src/uqm/supermelee/netplay/packethandlers.c
new file mode 100644
index 0000000..5d2d8f4
--- /dev/null
+++ b/src/uqm/supermelee/netplay/packethandlers.c
@@ -0,0 +1,649 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define PORT_WANT_ERRNO
+#include "port.h"
+
+#define NETCONNECTION_INTERNAL
+#include "netplay.h"
+#include "packethandlers.h"
+
+#include "netinput.h"
+#include "netmisc.h"
+#include "packetsenders.h"
+#include "proto/npconfirm.h"
+#include "proto/ready.h"
+#include "proto/reset.h"
+#include "libs/log.h"
+
+#include "../../controls.h"
+ // for BATTLE_INPUT_STATE
+#include "../../init.h"
+ // for NUM_PLAYERS
+#include "../../globdata.h"
+ // for GLOBAL
+#include "../melee.h"
+ // for various update functions.
+#include "../meleeship.h"
+ // for MeleeShip
+#include "../pickmele.h"
+ // for various update functions.
+#include "libs/mathlib.h"
+ // for TFB_SeedRandom
+
+#include <errno.h>
+
+
+static bool
+testNetState(bool condition, PacketType type) {
+ if (!condition) {
+ log_add(log_Error, "Packet of type '%s' received from wrong "
+ "state.", packetTypeData[type].name);
+ errno = EBADMSG;
+ }
+ return condition;
+}
+
+static int
+versionCompare(int major1, int minor1, int patch1,
+ int major2, int minor2, int patch2) {
+ if (major1 < major2)
+ return -1;
+ if (major1 > major2)
+ return 1;
+
+ if (minor1 < minor2)
+ return -1;
+ if (minor1 > minor2)
+ return 1;
+
+ if (patch1 < patch2)
+ return -1;
+ if (patch1 > patch2)
+ return 1;
+
+ return 0;
+}
+
+int
+PacketHandler_Init(NetConnection *conn, const Packet_Init *packet) {
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(conn->state == NetState_init &&
+ !conn->stateFlags.ready.remoteReady, PACKET_INIT))
+ return -1; // errno is set
+
+ if (packet->protoVersion.major != NETPLAY_PROTOCOL_VERSION_MAJOR ||
+ packet->protoVersion.minor != NETPLAY_PROTOCOL_VERSION_MINOR) {
+ sendAbort (conn, AbortReason_versionMismatch);
+ abortFeedback(conn, AbortReason_versionMismatch);
+ log_add(log_Error, "Protocol version %d.%d not supported.",
+ packet->protoVersion.major, packet->protoVersion.minor);
+ errno = ENOSYS;
+ return -1;
+ }
+
+ if (versionCompare(packet->uqmVersion.major, packet->uqmVersion.minor,
+ packet->uqmVersion.patch, NETPLAY_MIN_UQM_VERSION_MAJOR,
+ NETPLAY_MIN_UQM_VERSION_MINOR, NETPLAY_MIN_UQM_VERSION_PATCH)
+ < 0) {
+ sendAbort (conn, AbortReason_versionMismatch);
+ abortFeedback(conn, AbortReason_versionMismatch);
+ log_add(log_Error, "Remote side is running a version of UQM that "
+ "is too old (%d.%d.%d; %d.%d.%d is required).",
+ packet->uqmVersion.major, packet->uqmVersion.minor,
+ packet->uqmVersion.patch, NETPLAY_MIN_UQM_VERSION_MAJOR,
+ NETPLAY_MIN_UQM_VERSION_MINOR, NETPLAY_MIN_UQM_VERSION_PATCH);
+ errno = ENOSYS;
+ return -1;
+ }
+
+ Netplay_remoteReady(conn);
+
+ return 0;
+}
+
+int
+PacketHandler_Ping(NetConnection *conn, const Packet_Ping *packet) {
+ if (!testNetState(conn->state > NetState_init, PACKET_PING))
+ return -1; // errno is set
+
+ sendAck(conn, packet->id);
+ return 0;
+}
+
+int
+PacketHandler_Ack(NetConnection *conn, const Packet_Ack *packet) {
+ if (!testNetState(conn->state > NetState_init, PACKET_ACK))
+ return -1; // errno is set
+
+ (void) conn;
+ (void) packet;
+ return 0;
+}
+
+// Convert the side indication relative to a remote party to
+// a local player number.
+static inline int
+localSide(NetConnection *conn, NetplaySide side) {
+ if (side == NetplaySide_local) {
+ // "local" relative to the remote party.
+ return conn->player;
+ }
+
+ return 1 - conn->player;
+}
+
+int
+PacketHandler_Ready(NetConnection *conn, const Packet_Ready *packet) {
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(readyFlagsMeaningful(conn->state) &&
+ !conn->stateFlags.ready.remoteReady, PACKET_READY))
+ return -1; // errno is set
+
+ Netplay_remoteReady(conn);
+
+ (void) packet;
+ // Its contents is not interesting.
+
+ return 0;
+}
+
+int
+PacketHandler_Fleet(NetConnection *conn, const Packet_Fleet *packet) {
+ uint16 numShips = ntoh16(packet->numShips);
+ size_t i;
+ size_t len;
+ int player;
+ BattleStateData *battleStateData;
+
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(conn->state == NetState_inSetup, PACKET_FLEET))
+ return -1; // errno is set
+
+ player = localSide(conn, (NetplaySide) packet->side);
+
+ len = packetLength((const Packet *) packet);
+ if (sizeof packet + numShips * sizeof(packet->ships[0]) > len) {
+ // There is not enough room in the packet to contain all
+ // the ships it says it contains.
+ log_add(log_Warning, "Invalid fleet size. Specified size is %d, "
+ "actual size = %d",
+ numShips, (int) ((len - sizeof packet) / sizeof(packet->ships[0])));
+ errno = EBADMSG;
+ return -1;
+ }
+
+ battleStateData = (BattleStateData *) NetConnection_getStateData(conn);
+
+ if (conn->stateFlags.handshake.localOk) {
+ Netplay_cancelConfirmation(conn);
+ confirmationCancelled(battleStateData->meleeState, conn->player);
+ }
+
+ for (i = 0; i < numShips; i++) {
+ MeleeShip ship = (MeleeShip) packet->ships[i].ship;
+ FleetShipIndex index = (FleetShipIndex) packet->ships[i].index;
+
+ if (!MeleeShip_valid(ship)) {
+ log_add (log_Warning, "Invalid ship type number %d (max = %d).\n",
+ ship, NUM_MELEE_SHIPS - 1);
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (index >= MELEE_FLEET_SIZE)
+ {
+ log_add (log_Warning, "Invalid ship position number %d "
+ "(max = %d).\n", index, MELEE_FLEET_SIZE - 1);
+ errno = EBADMSG;
+ return -1;
+ }
+
+ Melee_RemoteChange_ship (battleStateData->meleeState, conn,
+ player, index, ship);
+ }
+
+ // Padding data may follow; it is ignored.
+ return 0;
+}
+
+int
+PacketHandler_TeamName(NetConnection *conn, const Packet_TeamName *packet) {
+ size_t nameLen;
+ int side;
+ BattleStateData *battleStateData;
+
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(conn->state == NetState_inSetup, PACKET_FLEET))
+ return -1; // errno is set
+
+ battleStateData = (BattleStateData *) NetConnection_getStateData(conn);
+
+ if (conn->stateFlags.handshake.localOk) {
+ Netplay_cancelConfirmation(conn);
+ confirmationCancelled(battleStateData->meleeState, conn->player);
+ }
+
+ side = localSide(conn, (NetplaySide) packet->side);
+ nameLen = packetLength((const Packet *) packet)
+ - sizeof (Packet_TeamName) - 1;
+ // The -1 is for not counting the terminating '\0'.
+
+ {
+ char buf[MAX_TEAM_CHARS + 1];
+
+ if (nameLen > MAX_TEAM_CHARS)
+ nameLen = MAX_TEAM_CHARS;
+ memcpy (buf, (const char *) packet->name, nameLen);
+ buf[nameLen] = '\0';
+
+ Melee_RemoteChange_teamName(battleStateData->meleeState, conn,
+ side, buf);
+ }
+
+ // Padding data may follow; it is ignored.
+ return 0;
+}
+
+static void
+handshakeComplete(NetConnection *conn) {
+ assert(!conn->stateFlags.handshake.localOk);
+ assert(!conn->stateFlags.handshake.remoteOk);
+ assert(!conn->stateFlags.handshake.canceling);
+
+ assert(conn->state == NetState_inSetup);
+ NetConnection_setState(conn, NetState_preBattle);
+}
+
+int
+PacketHandler_Handshake0(NetConnection *conn,
+ const Packet_Handshake0 *packet) {
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(handshakeMeaningful(conn->state)
+ && !conn->stateFlags.handshake.remoteOk, PACKET_HANDSHAKE0))
+ return -1; // errno is set
+
+ conn->stateFlags.handshake.remoteOk = true;
+ if (conn->stateFlags.handshake.localOk &&
+ !conn->stateFlags.handshake.canceling)
+ sendHandshake1(conn);
+
+ (void) packet;
+ // Its contents is not interesting.
+
+ return 0;
+}
+
+int
+PacketHandler_Handshake1(NetConnection *conn,
+ const Packet_Handshake1 *packet) {
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(handshakeMeaningful(conn->state) &&
+ (conn->stateFlags.handshake.localOk ||
+ conn->stateFlags.handshake.canceling), PACKET_HANDSHAKE1))
+ return -1; // errno is set
+
+ if (conn->stateFlags.handshake.canceling) {
+ conn->stateFlags.handshake.remoteOk = true;
+ } else {
+ bool remoteWasOk = conn->stateFlags.handshake.remoteOk;
+
+ conn->stateFlags.handshake.localOk = false;
+ conn->stateFlags.handshake.remoteOk = false;
+
+ if (!remoteWasOk) {
+ // Received Handshake1 without prior Handshake0.
+ // A Handshake0 is implied, but we still need to confirm
+ // it with a Handshake1.
+ sendHandshake1(conn);
+ }
+
+ handshakeComplete(conn);
+ }
+
+ (void) packet;
+ // Its contents is not interesting.
+
+ return 0;
+}
+
+int
+PacketHandler_HandshakeCancel(NetConnection *conn,
+ const Packet_HandshakeCancel *packet) {
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(handshakeMeaningful(conn->state)
+ && conn->stateFlags.handshake.remoteOk, PACKET_HANDSHAKECANCEL))
+ return -1; // errno is set
+
+ conn->stateFlags.handshake.remoteOk = false;
+ sendHandshakeCancelAck(conn);
+
+ (void) packet;
+ // Its contents is not interesting.
+
+ return 0;
+}
+
+int
+PacketHandler_HandshakeCancelAck(NetConnection *conn,
+ const Packet_HandshakeCancelAck *packet) {
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(handshakeMeaningful(conn->state)
+ && conn->stateFlags.handshake.canceling,
+ PACKET_HANDSHAKECANCELACK))
+ return -1; // errno is set
+
+ conn->stateFlags.handshake.canceling = false;
+ if (conn->stateFlags.handshake.localOk) {
+ if (conn->stateFlags.handshake.remoteOk) {
+ sendHandshake1(conn);
+ } else
+ sendHandshake0(conn);
+ }
+
+ (void) packet;
+ // Its contents is not interesting.
+
+ return 0;
+}
+
+int
+PacketHandler_SeedRandom(NetConnection *conn,
+ const Packet_SeedRandom *packet) {
+ BattleStateData *battleStateData;
+
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(conn->state == NetState_preBattle &&
+ !conn->stateFlags.discriminant, PACKET_SEEDRANDOM))
+ return -1; // errno is set
+
+ battleStateData = (BattleStateData *) NetConnection_getStateData(conn);
+ updateRandomSeed (battleStateData->meleeState, conn->player,
+ ntoh32(packet->seed));
+
+ conn->stateFlags.agreement.randomSeed = true;
+ return 0;
+}
+
+int
+PacketHandler_InputDelay(NetConnection *conn,
+ const Packet_InputDelay *packet) {
+ BattleStateData *battleStateData;
+ uint32 delay;
+
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(conn->state == NetState_preBattle, PACKET_INPUTDELAY))
+ return -1; // errno is set
+
+ battleStateData = (BattleStateData *) NetConnection_getStateData(conn);
+ delay = ntoh32(packet->delay);
+ if (delay > BATTLE_FRAME_RATE) {
+ log_add(log_Error, "NETPLAY: [%d] Received absurdly large "
+ "input delay value (%d).", conn->player, delay);
+ return -1;
+ }
+ conn->stateFlags.inputDelay = delay;
+
+ return 0;
+}
+
+int
+PacketHandler_SelectShip(NetConnection *conn,
+ const Packet_SelectShip *packet) {
+ bool updateResult;
+ BattleStateData *battleStateData;
+
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(conn->state == NetState_selectShip, PACKET_SELECTSHIP))
+ return -1; // errno is set
+
+ battleStateData = (BattleStateData *) NetConnection_getStateData(conn);
+ updateResult = updateMeleeSelection(battleStateData->getMeleeState,
+ conn->player, ntoh16(packet->ship));
+ if (!updateResult)
+ {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+PacketHandler_BattleInput(NetConnection *conn,
+ const Packet_BattleInput *packet) {
+ BATTLE_INPUT_STATE input;
+ BattleInputBuffer *bib;
+
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(conn->state == NetState_inBattle ||
+ conn->state == NetState_endingBattle ||
+ conn->state == NetState_endingBattle2, PACKET_BATTLEINPUT))
+ return -1; // errno is set
+
+ input = (BATTLE_INPUT_STATE) packet->state;
+ bib = getBattleInputBuffer(conn->player);
+ if (!BattleInputBuffer_push(bib, input)) {
+ // errno is set
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+PacketHandler_FrameCount(NetConnection *conn,
+ const Packet_FrameCount *packet) {
+ BattleStateData *battleStateData;
+ BattleFrameCounter frameCount;
+
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(conn->state == NetState_endingBattle,
+ PACKET_FRAMECOUNT))
+ return -1; // errno is set
+
+ frameCount = (BattleFrameCounter) ntoh32(packet->frameCount);
+#ifdef NETPLAY_DEBUG
+ log_add(log_Debug, "NETPLAY: [%d] <== Received battleFrameCount %u.",
+ conn->player, (unsigned int) frameCount);
+#endif
+
+ battleStateData = (BattleStateData *) NetConnection_getStateData(conn);
+ if (frameCount > battleStateData->endFrameCount)
+ battleStateData->endFrameCount = frameCount;
+ Netplay_remoteReady(conn);
+
+ return 0;
+}
+
+int
+PacketHandler_Checksum(NetConnection *conn, const Packet_Checksum *packet) {
+#ifdef NETPLAY_CHECKSUM
+ uint32 frameNr;
+ uint32 checksum;
+ size_t delay;
+ size_t interval;
+#endif
+
+ if (conn->stateFlags.reset.localReset)
+ return 0;
+ if (conn->stateFlags.reset.remoteReset) {
+ errno = EBADMSG;
+ return -1;
+ }
+
+ if (!testNetState(NetState_battleActive(conn->state), PACKET_CHECKSUM))
+ return -1; // errno is set
+
+#ifdef NETPLAY_CHECKSUM
+ frameNr = ntoh32(packet->frameNr);
+ checksum = ntoh32(packet->checksum);
+ interval = NetConnection_getChecksumInterval(conn);
+ delay = getBattleInputDelay();
+
+ if (frameNr % interval != 0) {
+ log_add(log_Warning, "NETPLAY: [%d] <== Received checksum "
+ "for frame %u, while we only expect checksums on frames "
+ "divisable by %u -- discarding.", conn->player,
+ (unsigned int) frameNr, (unsigned int) interval);
+ return 0;
+ // No need to close the connection; checksums are not
+ // essential.
+ }
+
+ // The checksum is sent at the beginning of a frame.
+ // If we're in frame n and have sent our input already,
+ // the remote side has got enough input to progress delay + 1 frames from
+ // frame n. The next frame is then n + delay + 1, for which we can
+ // receive a checksum.
+ if (frameNr > battleFrameCount + delay + 1) {
+ log_add(log_Warning, "NETPLAY: [%d] <== Received checksum "
+ "for a frame too far in the future (frame %u, current "
+ "is %u, input delay is %u) -- discarding.", conn->player,
+ (unsigned int) frameNr, (unsigned int) battleFrameCount, (unsigned int) delay);
+ return 0;
+ // No need to close the connection; checksums are not
+ // essential.
+ }
+
+ // We can progress delay more frames after the last frame for which we
+ // received input. If we call that frame n, we can complete frames
+ // n through n + delay - 1. While we are waiting for the next input,
+ // in frame n + delay, we will first receive the checksum that the
+ // remote side sent at the start of frame n + 1.
+ // In this situation frameNr is n + 1, and battleFrameCount is
+ // n + delay.
+ if (frameNr + delay < battleFrameCount) {
+ log_add(log_Warning, "NETPLAY: [%d] <== Received checksum "
+ "for a frame too far in the past (frame %u, current "
+ "is %u, input delay is %u) -- discarding.", conn->player,
+ (unsigned int) frameNr, (unsigned int) battleFrameCount, (unsigned int) delay);
+ return 0;
+ // No need to close the connection; checksums are not
+ // essential.
+ }
+
+ addRemoteChecksum(conn, frameNr, checksum);
+#endif
+
+#ifndef NETPLAY_CHECKSUM
+ (void) packet;
+#endif
+ return 0;
+}
+
+int
+PacketHandler_Abort(NetConnection *conn, const Packet_Abort *packet) {
+ abortFeedback(conn, packet->reason);
+
+ return -1;
+ // Close connection.
+}
+
+int
+PacketHandler_Reset(NetConnection *conn, const Packet_Reset *packet) {
+ NetplayResetReason reason;
+
+ if (!testNetState(!conn->stateFlags.reset.remoteReset, PACKET_RESET))
+ return -1; // errno is set
+
+ reason = ntoh16(packet->reason);
+
+ Netplay_remoteReset(conn, reason);
+ return 0;
+}
+
+
diff --git a/src/uqm/supermelee/netplay/packethandlers.h b/src/uqm/supermelee/netplay/packethandlers.h
new file mode 100644
index 0000000..7bd686e
--- /dev/null
+++ b/src/uqm/supermelee/netplay/packethandlers.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_PACKETHANDLERS_H_
+#define UQM_SUPERMELEE_NETPLAY_PACKETHANDLERS_H_
+
+#include "packet.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define DECLARE_PACKETHANDLER(type) \
+ int PacketHandler_##type(NetConnection *conn, \
+ const Packet_##type *packet)
+
+DECLARE_PACKETHANDLER(Init);
+DECLARE_PACKETHANDLER(Ping);
+DECLARE_PACKETHANDLER(Ack);
+DECLARE_PACKETHANDLER(Ready);
+DECLARE_PACKETHANDLER(Fleet);
+DECLARE_PACKETHANDLER(TeamName);
+DECLARE_PACKETHANDLER(Handshake0);
+DECLARE_PACKETHANDLER(Handshake1);
+DECLARE_PACKETHANDLER(HandshakeCancel);
+DECLARE_PACKETHANDLER(HandshakeCancelAck);
+DECLARE_PACKETHANDLER(SeedRandom);
+DECLARE_PACKETHANDLER(InputDelay);
+DECLARE_PACKETHANDLER(SelectShip);
+DECLARE_PACKETHANDLER(BattleInput);
+DECLARE_PACKETHANDLER(FrameCount);
+DECLARE_PACKETHANDLER(Checksum);
+DECLARE_PACKETHANDLER(Abort);
+DECLARE_PACKETHANDLER(Reset);
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_PACKETHANDLERS_H_ */
diff --git a/src/uqm/supermelee/netplay/packetq.c b/src/uqm/supermelee/netplay/packetq.c
new file mode 100644
index 0000000..ee8ec01
--- /dev/null
+++ b/src/uqm/supermelee/netplay/packetq.c
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define NETCONNECTION_INTERNAL
+#include "netplay.h"
+#include "netconnection.h"
+#include "packetq.h"
+#include "netsend.h"
+#include "packetsenders.h"
+#ifdef NETPLAY_DEBUG
+# include "libs/log.h"
+#endif
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+static inline PacketQueueLink *
+PacketQueueLink_alloc(void) {
+ // XXX: perhaps keep a pool of links?
+ return malloc(sizeof (PacketQueueLink));
+}
+
+static inline void
+PacketQueueLink_delete(PacketQueueLink *link) {
+ free(link);
+}
+
+// 'maxSize' should at least be 1
+void
+PacketQueue_init(PacketQueue *queue) {
+ queue->size = 0;
+ queue->first = NULL;
+ queue->end = &queue->first;
+}
+
+static void
+PacketQueue_deleteLinks(PacketQueueLink *link) {
+ while (link != NULL) {
+ PacketQueueLink *next = link->next;
+ Packet_delete(link->packet);
+ PacketQueueLink_delete(link);
+ link = next;
+ }
+}
+
+void
+PacketQueue_uninit(PacketQueue *queue) {
+ PacketQueue_deleteLinks(queue->first);
+}
+
+void
+queuePacket(NetConnection *conn, Packet *packet) {
+ PacketQueue *queue;
+ PacketQueueLink *link;
+
+ assert(NetConnection_isConnected(conn));
+
+ queue = &conn->queue;
+
+ link = PacketQueueLink_alloc();
+ link->packet = packet;
+ link->next = NULL;
+ *queue->end = link;
+ queue->end = &link->next;
+
+ queue->size++;
+ // XXX: perhaps check that this queue isn't getting too large?
+
+#ifdef NETPLAY_DEBUG
+ if (packetType(packet) != PACKET_BATTLEINPUT &&
+ packetType(packet) != PACKET_CHECKSUM) {
+ // Reporting BattleInput or Checksum would get so spammy that it
+ // would slow down the battle.
+ log_add(log_Debug, "NETPLAY: [%d] ==> Queueing packet of type %s.\n",
+ NetConnection_getPlayerNr(conn),
+ packetTypeData[packetType(packet)].name);
+ }
+#ifdef NETPLAY_DEBUG_FILE
+ if (conn->debugFile != NULL) {
+ uio_fprintf(conn->debugFile,
+ "NETPLAY: [%d] ==> Queueing packet of type %s.\n",
+ NetConnection_getPlayerNr(conn),
+ packetTypeData[packetType(packet)].name);
+ }
+#endif /* NETPLAY_DEBUG_FILE */
+#endif /* NETPLAY_DEBUG */
+}
+
+// If an error occurs during sending, we leave the unsent packets in
+// the queue, and let the caller decide what to do with them.
+// This function may return -1 with errno EAGAIN or EWOULDBLOCK
+// if we're waiting for the other party to act first.
+static int
+flushPacketQueueLinks(NetConnection *conn, PacketQueueLink **first) {
+ PacketQueueLink *link;
+ PacketQueueLink *next;
+ PacketQueue *queue = &conn->queue;
+
+ for (link = *first; link != NULL; link = next) {
+ if (sendPacket(conn, link->packet) == -1) {
+ // Errno is set.
+ *first = link;
+ return -1;
+ }
+
+ next = link->next;
+ Packet_delete(link->packet);
+ PacketQueueLink_delete(link);
+ queue->size--;
+ }
+
+ *first = link;
+ return 0;
+}
+
+int
+flushPacketQueue(NetConnection *conn) {
+ int flushResult;
+ PacketQueue *queue = &conn->queue;
+
+ assert(NetConnection_isConnected(conn));
+
+ flushResult = flushPacketQueueLinks(conn, &queue->first);
+ if (queue->first == NULL)
+ queue->end = &queue->first;
+ if (flushResult == -1) {
+ // errno is set
+ return -1;
+ }
+
+ return 0;
+}
+
diff --git a/src/uqm/supermelee/netplay/packetq.h b/src/uqm/supermelee/netplay/packetq.h
new file mode 100644
index 0000000..71f2347
--- /dev/null
+++ b/src/uqm/supermelee/netplay/packetq.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_PACKETQ_H_
+#define UQM_SUPERMELEE_NETPLAY_PACKETQ_H_
+
+typedef struct PacketQueue PacketQueue;
+
+#include "packet.h"
+#include "types.h"
+
+#include <sys/types.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct PacketQueueLink PacketQueueLink;
+struct PacketQueueLink {
+ PacketQueueLink *next;
+ Packet *packet;
+};
+
+struct PacketQueue {
+ size_t size;
+ PacketQueueLink *first;
+ PacketQueueLink **end;
+
+ // first points to the first entry in the queue
+ // end points to the location where the next message should be inserted.
+};
+
+void PacketQueue_init(PacketQueue *queue);
+void PacketQueue_uninit(PacketQueue *queue);
+void queuePacket(NetConnection *conn, Packet *packet);
+int flushPacketQueue(NetConnection *conn);
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
+
diff --git a/src/uqm/supermelee/netplay/packetsenders.c b/src/uqm/supermelee/netplay/packetsenders.c
new file mode 100644
index 0000000..fb9f232
--- /dev/null
+++ b/src/uqm/supermelee/netplay/packetsenders.c
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "netplay.h"
+#include "packetsenders.h"
+
+#include "packet.h"
+#include "packetq.h"
+#include "netsend.h"
+
+
+void
+sendInit(NetConnection *conn) {
+ Packet_Init *packet;
+
+ packet = Packet_Init_create();
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendPing(NetConnection *conn, uint32 id) {
+ Packet_Ping *packet;
+
+ packet = Packet_Ping_create(id);
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendAck(NetConnection *conn, uint32 id) {
+ Packet_Ack *packet;
+
+ packet = Packet_Ack_create(id);
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendReady(NetConnection *conn) {
+ Packet_Ready *packet;
+
+ packet = Packet_Ready_create();
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendHandshake0(NetConnection *conn) {
+ Packet_Handshake0 *packet;
+
+ packet = Packet_Handshake0_create();
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendHandshake1(NetConnection *conn) {
+ Packet_Handshake1 *packet;
+
+ packet = Packet_Handshake1_create();
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendHandshakeCancel(NetConnection *conn) {
+ Packet_HandshakeCancel *packet;
+
+ packet = Packet_HandshakeCancel_create();
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendHandshakeCancelAck(NetConnection *conn) {
+ Packet_HandshakeCancelAck *packet;
+
+ packet = Packet_HandshakeCancelAck_create();
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendTeamName(NetConnection *conn, NetplaySide side, const char *name,
+ size_t len) {
+ Packet_TeamName *packet;
+
+ packet = Packet_TeamName_create(side, name, len);
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendFleet(NetConnection *conn, NetplaySide side, const MeleeShip *ships,
+ size_t shipCount) {
+ size_t i;
+ Packet_Fleet *packet;
+
+ packet = Packet_Fleet_create(side, shipCount);
+
+ for (i = 0; i < shipCount; i++) {
+ packet->ships[i].index = (uint8) i;
+ packet->ships[i].ship = (uint8) ships[i];
+ }
+
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendFleetShip(NetConnection *conn, NetplaySide side,
+ FleetShipIndex shipIndex, MeleeShip ship) {
+ Packet_Fleet *packet;
+
+ packet = Packet_Fleet_create(side, 1);
+
+ packet->ships[0].index = (uint8) shipIndex;
+ packet->ships[0].ship = (uint8) ship;
+
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendSeedRandom(NetConnection *conn, uint32 seed) {
+ Packet_SeedRandom *packet;
+
+ packet = Packet_SeedRandom_create(seed);
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendInputDelay(NetConnection *conn, uint32 delay) {
+ Packet_InputDelay *packet;
+
+ packet = Packet_InputDelay_create(delay);
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendSelectShip(NetConnection *conn, FleetShipIndex index) {
+ Packet_SelectShip *packet;
+
+ packet = Packet_SelectShip_create((uint16) index);
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendBattleInput(NetConnection *conn, BATTLE_INPUT_STATE input) {
+ Packet_BattleInput *packet;
+
+ packet = Packet_BattleInput_create((uint8) input);
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendFrameCount(NetConnection *conn, BattleFrameCounter frameCount) {
+ Packet_FrameCount *packet;
+
+ packet = Packet_FrameCount_create((uint32) frameCount);
+ queuePacket(conn, (Packet *) packet);
+}
+
+#ifdef NETPLAY_CHECKSUM
+void
+sendChecksum(NetConnection *conn, BattleFrameCounter frameNr,
+ Checksum checksum) {
+ Packet_Checksum *packet;
+
+ packet = Packet_Checksum_create((uint32) frameNr, (uint32) checksum);
+ queuePacket(conn, (Packet *) packet);
+}
+#endif
+
+void
+sendAbort(NetConnection *conn, NetplayAbortReason reason) {
+ Packet_Abort *packet;
+
+ packet = Packet_Abort_create((uint16) reason);
+ queuePacket(conn, (Packet *) packet);
+}
+
+void
+sendReset(NetConnection *conn, NetplayResetReason reason) {
+ Packet_Reset *packet;
+
+ packet = Packet_Reset_create((uint16) reason);
+ queuePacket(conn, (Packet *) packet);
+}
+
+
+
diff --git a/src/uqm/supermelee/netplay/packetsenders.h b/src/uqm/supermelee/netplay/packetsenders.h
new file mode 100644
index 0000000..de0bc6d
--- /dev/null
+++ b/src/uqm/supermelee/netplay/packetsenders.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_PACKETSENDERS_H_
+#define UQM_SUPERMELEE_NETPLAY_PACKETSENDERS_H_
+
+#include "types.h"
+
+#include "netconnection.h"
+#include "packet.h"
+
+#include "../../controls.h"
+ // for BATTLE_INPUT_STATE
+#include "../meleeship.h"
+ // for MeleeShip
+#include "../meleesetup.h"
+ // for FleetShipIndex
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+void sendInit(NetConnection *conn);
+void sendPing(NetConnection *conn, uint32 id);
+void sendAck(NetConnection *conn, uint32 id);
+void sendReady(NetConnection *conn);
+void sendHandshake0(NetConnection *conn);
+void sendHandshake1(NetConnection *conn);
+void sendHandshakeCancel(NetConnection *conn);
+void sendHandshakeCancelAck(NetConnection *conn);
+void sendTeamName(NetConnection *conn, NetplaySide side,
+ const char *name, size_t len);
+void sendFleet(NetConnection *conn, NetplaySide side,
+ const MeleeShip *ships, size_t numShips);
+void sendFleetShip(NetConnection *conn, NetplaySide player,
+ FleetShipIndex shipIndex, MeleeShip ship);
+void sendSeedRandom(NetConnection *conn, uint32 seed);
+void sendInputDelay(NetConnection *conn, uint32 delay);
+void sendSelectShip(NetConnection *conn, FleetShipIndex index);
+void sendBattleInput(NetConnection *conn, BATTLE_INPUT_STATE input);
+void sendFrameCount(NetConnection *conn, BattleFrameCounter frameCount);
+void sendChecksum(NetConnection *conn, BattleFrameCounter frameNr,
+ Checksum checksum);
+void sendAbort(NetConnection *conn, NetplayAbortReason reason);
+void sendReset(NetConnection *conn, NetplayResetReason reason);
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_PACKETSENDERS_H_ */
diff --git a/src/uqm/supermelee/netplay/proto/Makeinfo b/src/uqm/supermelee/netplay/proto/Makeinfo
new file mode 100644
index 0000000..1d9739c
--- /dev/null
+++ b/src/uqm/supermelee/netplay/proto/Makeinfo
@@ -0,0 +1,2 @@
+uqm_CFILES="npconfirm.c ready.c reset.c"
+uqm_HFILES="npconfirm.h ready.h reset.h"
diff --git a/src/uqm/supermelee/netplay/proto/npconfirm.c b/src/uqm/supermelee/netplay/proto/npconfirm.c
new file mode 100644
index 0000000..6929219
--- /dev/null
+++ b/src/uqm/supermelee/netplay/proto/npconfirm.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define NETCONNECTION_INTERNAL
+#include "../netplay.h"
+#include "npconfirm.h"
+
+#include "types.h"
+#include "../netmisc.h"
+#include "../packetsenders.h"
+
+#include <assert.h>
+#include <errno.h>
+
+int
+Netplay_confirm(NetConnection *conn) {
+ assert(handshakeMeaningful(NetConnection_getState(conn)));
+
+ if (conn->stateFlags.handshake.localOk) {
+ // Already confirmed
+ errno = EINVAL;
+ return -1;
+ }
+
+ conn->stateFlags.handshake.localOk = true;
+
+ if (conn->stateFlags.handshake.canceling) {
+ // If a previous confirmation was cancelled, but the cancel
+ // is not acknowledged yet, we don't have to send anything yet.
+ // The handshake0 packet will be sent when the acknowledgement
+ // arrives.
+ } else if (conn->stateFlags.handshake.remoteOk) {
+ // A Handshake0 is implied by the following Handshake1.
+ sendHandshake1(conn);
+ } else {
+ sendHandshake0(conn);
+ }
+
+ return 0;
+}
+
+int
+Netplay_cancelConfirmation(NetConnection *conn) {
+ assert(handshakeMeaningful(NetConnection_getState(conn)));
+
+ if (!conn->stateFlags.handshake.localOk) {
+ // Not confirmed, or already canceling.
+ errno = EINVAL;
+ return -1;
+ }
+
+ conn->stateFlags.handshake.localOk = false;
+ if (conn->stateFlags.handshake.canceling) {
+ // If previous cancellation is still waiting to be acknowledged,
+ // the confirmation we are cancelling here, has not actually been
+ // sent yet. By setting the localOk flag to false, it is
+ // cancelled, without the need for any packets to be sent.
+ } else {
+ conn->stateFlags.handshake.canceling = true;
+ sendHandshakeCancel(conn);
+ }
+
+ return 0;
+}
+
+
diff --git a/src/uqm/supermelee/netplay/proto/npconfirm.h b/src/uqm/supermelee/netplay/proto/npconfirm.h
new file mode 100644
index 0000000..1ae58f5
--- /dev/null
+++ b/src/uqm/supermelee/netplay/proto/npconfirm.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_PROTO_NPCONFIRM_H_
+#define UQM_SUPERMELEE_NETPLAY_PROTO_NPCONFIRM_H_
+
+#include "../netplay.h"
+#include "../netconnection.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+int Netplay_confirm(NetConnection *conn);
+int Netplay_cancelConfirmation(NetConnection *conn);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_PROTO_NPCONFIRM_H_ */
diff --git a/src/uqm/supermelee/netplay/proto/ready.c b/src/uqm/supermelee/netplay/proto/ready.c
new file mode 100644
index 0000000..e9f8c58
--- /dev/null
+++ b/src/uqm/supermelee/netplay/proto/ready.c
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define NETCONNECTION_INTERNAL
+#include "../netplay.h"
+#include "ready.h"
+
+#include "types.h"
+#include "../netmisc.h"
+#include "../packetsenders.h"
+
+#include <assert.h>
+
+static void
+Netplay_bothReady(NetConnection *conn) {
+ NetConnection_ReadyCallback callback;
+ void *readyArg;
+
+ assert(conn->readyCallback != NULL);
+
+ callback = conn->readyCallback;
+ readyArg = conn->readyCallbackArg;
+
+ NetConnection_setReadyCallback(conn, NULL, NULL);
+ // Clear the readyCallback field before performing the callback,
+ // so that it can be set again from inside the callback
+ // function.
+
+ callback(conn, readyArg);
+}
+
+// If notifyRemote is set, a 'Ready' message will be sent to the other side.
+// returns true iff both sides are ready.
+// Inside the callback function, ready flags may be set for a possible
+// next Ready communication.
+bool
+Netplay_localReady(NetConnection *conn, NetConnection_ReadyCallback callback,
+ void *readyArg, bool notifyRemote) {
+ assert(readyFlagsMeaningful(NetConnection_getState(conn)));
+ assert(!conn->stateFlags.ready.localReady);
+ assert(callback != NULL);
+
+ NetConnection_setReadyCallback(conn, callback, readyArg);
+
+ if (notifyRemote)
+ sendReady(conn);
+ if (!conn->stateFlags.ready.remoteReady) {
+ conn->stateFlags.ready.localReady = true;
+ return false;
+ }
+
+ // Reset ready flags:
+ conn->stateFlags.ready.remoteReady = false;
+
+ // Trigger the callback.
+ Netplay_bothReady(conn);
+ return true;
+}
+
+// returns true iff both sides are ready.
+bool
+Netplay_remoteReady(NetConnection *conn) {
+ assert(readyFlagsMeaningful(NetConnection_getState(conn)));
+ // This is supposed to be already verified by the calling
+ // function.
+ assert(!conn->stateFlags.ready.remoteReady);
+
+ if (!conn->stateFlags.ready.localReady) {
+ conn->stateFlags.ready.remoteReady = true;
+ return false;
+ }
+
+ // Reset ready flags:
+ conn->stateFlags.ready.localReady = false;
+
+ // Trigger the callback.
+ Netplay_bothReady(conn);
+ return true;
+}
+
+bool
+Netplay_isLocalReady(const NetConnection *conn) {
+ return conn->stateFlags.ready.localReady;
+}
+
+bool
+Netplay_isRemoteReady(const NetConnection *conn) {
+ return conn->stateFlags.ready.remoteReady;
+}
+
+
diff --git a/src/uqm/supermelee/netplay/proto/ready.h b/src/uqm/supermelee/netplay/proto/ready.h
new file mode 100644
index 0000000..3521557
--- /dev/null
+++ b/src/uqm/supermelee/netplay/proto/ready.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_PROTO_READY_H_
+#define UQM_SUPERMELEE_NETPLAY_PROTO_READY_H_
+
+#include "../netconnection.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+bool Netplay_localReady(NetConnection *conn,
+ NetConnection_ReadyCallback callback, void *arg, bool notifyRemote);
+bool Netplay_remoteReady(NetConnection *conn);
+bool Netplay_isLocalReady(const NetConnection *conn);
+bool Netplay_isRemoteReady(const NetConnection *conn);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_PROTO_READY_H_ */
diff --git a/src/uqm/supermelee/netplay/proto/reset.c b/src/uqm/supermelee/netplay/proto/reset.c
new file mode 100644
index 0000000..82483b1
--- /dev/null
+++ b/src/uqm/supermelee/netplay/proto/reset.c
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+// See doc/devel/netplay/protocol
+
+#define NETCONNECTION_INTERNAL
+#include "../netplay.h"
+#include "reset.h"
+
+#include "types.h"
+#include "../packetsenders.h"
+#include "../../melee.h"
+ // For resetFeedback.
+
+#include <assert.h>
+
+// Reset packets are sent to indicate that a game is to be reset.
+// i.e. the game is to return to the SuperMelee fleet setup menu.
+// The reset will occur when a reset packet has both been sent and
+// received. When a reset packet is received and the local side had not
+// sent a reset packet itself, the local side will confirm the reset.
+// When both sides initiate a reset simultaneously, the reset packets
+// of each side will act as a confirmation for the other side.
+//
+// When a reset packet has been sent, no further gameplay packets should be
+// sent until the game has been reset. Non-gameplay packets such as 'ping'
+// are allowed.
+// When a reset packet has been received, all further incoming gameplay
+// packets are ignored until the game has been reset.
+//
+// conn->stateFlags.reset.localReset is set when a reset packet is sent.
+// conn->stateFlags.reset.remoteReset is set when a reset packet is
+// received.
+//
+// When either localReset or remoteReset gets set and the other flag isn't
+// set, Netplay_connectionReset() gets called.
+//
+// As soon as the following three conditions are met, the reset callback is
+// called and the localReset and remoteReset flags are cleared.
+// - conn->stateFlags.reset.localReset is set
+// - conn->stateFlags.reset.remoteReset is set
+// - the reset callback is non-NULL.
+//
+// Elsewhere in the UQM source:
+// When the local side causes a reset, it calls Netplay_localReset().
+// When a remote reset packet is received, Netplay_remoteReset() is called
+// (which will sent a reset packet back as confirmation, as required).
+// At the end of melee, the reset callback is set (for each connection),
+// and the game will wait until the reset callback for each connection has
+// been called (when the forementioned conditions have become true)
+// (or until the connection is terminated).
+
+
+// This function is called when one side initiates a reset.
+static void
+Netplay_connectionReset(NetConnection *conn, NetplayResetReason reason,
+ bool byRemote) {
+ switch (NetConnection_getState(conn)) {
+ case NetState_unconnected:
+ case NetState_connecting:
+ case NetState_init:
+ case NetState_inSetup:
+ break;
+ case NetState_preBattle:
+ case NetState_interBattle:
+ case NetState_selectShip:
+ case NetState_inBattle:
+ case NetState_endingBattle:
+ case NetState_endingBattle2:
+ resetFeedback(conn, reason, byRemote);
+ break;
+ }
+}
+
+static void
+Netplay_doConnectionResetCallback(NetConnection *conn) {
+ NetConnection_ResetCallback callback;
+ void *resetArg;
+
+ callback = conn->resetCallback;
+ resetArg = conn->resetCallbackArg;
+
+ NetConnection_setResetCallback(conn, NULL, NULL);
+ // Clear the resetCallback field before performing the callback,
+ // so that it can be set again from inside the callback
+ // function.
+ callback(conn, resetArg);
+}
+
+static void
+Netplay_resetConditionTriggered(NetConnection *conn) {
+ if (conn->resetCallback == NULL)
+ return;
+
+ if (!conn->stateFlags.reset.localReset ||
+ !conn->stateFlags.reset.remoteReset)
+ return;
+
+ conn->stateFlags.reset.localReset = false;
+ conn->stateFlags.reset.remoteReset = false;
+
+ Netplay_doConnectionResetCallback(conn);
+}
+
+void
+Netplay_setResetCallback(NetConnection *conn,
+ NetConnection_ResetCallback callback, void *resetArg) {
+ NetConnection_setResetCallback(conn, callback, resetArg);
+
+ Netplay_resetConditionTriggered(conn);
+}
+
+void
+Netplay_localReset(NetConnection *conn, NetplayResetReason reason) {
+ assert(!conn->stateFlags.reset.localReset);
+
+ conn->stateFlags.reset.localReset = true;
+ if (conn->stateFlags.reset.remoteReset) {
+ // Both sides have initiated/confirmed the reset.
+ Netplay_resetConditionTriggered(conn);
+ } else {
+ sendReset(conn, reason);
+ Netplay_connectionReset(conn, reason, false);
+ }
+}
+
+void
+Netplay_remoteReset(NetConnection *conn, NetplayResetReason reason) {
+ assert(!conn->stateFlags.reset.remoteReset);
+ // Should already be checked when the packet arrives.
+
+ conn->stateFlags.reset.remoteReset = true;
+ if (!conn->stateFlags.reset.localReset) {
+ sendReset(conn, reason);
+ conn->stateFlags.reset.localReset = true;
+ Netplay_connectionReset(conn, reason, true);
+ }
+
+ Netplay_resetConditionTriggered(conn);
+}
+
+bool
+Netplay_isLocalReset(const NetConnection *conn) {
+ return conn->stateFlags.reset.localReset;
+}
+
+bool
+Netplay_isRemoteReset(const NetConnection *conn) {
+ return conn->stateFlags.reset.remoteReset;
+}
+
diff --git a/src/uqm/supermelee/netplay/proto/reset.h b/src/uqm/supermelee/netplay/proto/reset.h
new file mode 100644
index 0000000..e16b1d1
--- /dev/null
+++ b/src/uqm/supermelee/netplay/proto/reset.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2006 Serge van den Boom <svdb@stack.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_NETPLAY_PROTO_RESET_H_
+#define UQM_SUPERMELEE_NETPLAY_PROTO_RESET_H_
+
+#include "../netconnection.h"
+#include "../packet.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+void Netplay_setResetCallback(NetConnection *conn,
+ NetConnection_ResetCallback callback, void *resetArg);
+void Netplay_localReset(NetConnection *conn, NetplayResetReason reason);
+void Netplay_remoteReset(NetConnection *conn, NetplayResetReason reason);
+bool Netplay_isLocalReset(const NetConnection *conn);
+bool Netplay_isRemoteReset(const NetConnection *conn);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* UQM_SUPERMELEE_NETPLAY_PROTO_RESET_H_ */
+
diff --git a/src/uqm/supermelee/pickmele.c b/src/uqm/supermelee/pickmele.c
new file mode 100644
index 0000000..0ce6489
--- /dev/null
+++ b/src/uqm/supermelee/pickmele.c
@@ -0,0 +1,948 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#define PICKMELE_INTERNAL
+#include "pickmele.h"
+
+#include "../battlecontrols.h"
+#include "../battle.h"
+#include "../build.h"
+#include "../controls.h"
+#include "../flash.h"
+#include "../igfxres.h"
+#include "../intel.h"
+#include "../master.h"
+#include "../nameref.h"
+#include "melee.h"
+#ifdef NETPLAY
+# include "netplay/netmelee.h"
+# include "netplay/netmisc.h"
+# include "netplay/notify.h"
+#endif
+#include "../races.h"
+#include "../setup.h"
+#include "../sounds.h"
+#include "libs/async.h"
+#include "libs/log.h"
+#include "libs/mathlib.h"
+
+
+#define NUM_PICKMELEE_ROWS 2
+#define NUM_PICKMELEE_COLUMNS 7
+
+#define PICK_X_OFFS 57
+#define PICK_Y_OFFS 24
+#define PICK_SIDE_OFFS 100
+
+#define NAME_AREA_HEIGHT 7
+#define MELEE_WIDTH 149
+#define MELEE_HEIGHT (48 + NAME_AREA_HEIGHT)
+
+#define PICKSHIP_TEAM_NAME_TEXT_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09)
+#define PICKSHIP_TEAM_START_VALUE_COLOR \
+ BUILD_COLOR (MAKE_RGB15 (0x04, 0x05, 0x1F), 0x4B)
+
+
+#ifdef NETPLAY
+static void reportShipSelected (GETMELEE_STATE *gms, COUNT index);
+#endif
+
+
+FRAME PickMeleeFrame;
+
+
+static FleetShipIndex
+PickMelee_GetShipIndex (BYTE row, BYTE col)
+{
+ return row * NUM_PICKMELEE_COLUMNS + col;
+}
+
+static BYTE
+PickMelee_GetShipRow (FleetShipIndex index)
+{
+ return index / NUM_PICKMELEE_COLUMNS;
+}
+
+static BYTE
+PickMelee_GetShipColumn (int index)
+{
+ return index % NUM_PICKMELEE_COLUMNS;
+}
+
+// Returns the <index>th ship in the queue, or 0 if it is not available.
+static HSTARSHIP
+MeleeShipByQueueIndex (const QUEUE *queue, COUNT index)
+{
+ HSTARSHIP hShip;
+ HSTARSHIP hNextShip;
+
+ for (hShip = GetHeadLink (queue); hShip != 0; hShip = hNextShip)
+ {
+ STARSHIP *StarShipPtr = LockStarShip (queue, hShip);
+ if (StarShipPtr->index == index)
+ {
+ hNextShip = hShip;
+ if (StarShipPtr->SpeciesID == NO_ID)
+ hShip = 0;
+ UnlockStarShip (queue, hNextShip);
+ break;
+ }
+ hNextShip = _GetSuccLink (StarShipPtr);
+ UnlockStarShip (queue, hShip);
+ }
+
+ return hShip;
+}
+
+// Returns the <index>th available ship in the queue.
+static HSTARSHIP
+MeleeShipByUsedIndex (const QUEUE *queue, COUNT index)
+{
+ HSTARSHIP hShip;
+ HSTARSHIP hNextShip;
+
+ for (hShip = GetHeadLink (queue); hShip != 0; hShip = hNextShip)
+ {
+ STARSHIP *StarShipPtr = LockStarShip (queue, hShip);
+ if ((StarShipPtr->SpeciesID != NO_ID) && index-- == 0)
+ {
+ UnlockStarShip (queue, hShip);
+ break;
+ }
+ hNextShip = _GetSuccLink (StarShipPtr);
+ UnlockStarShip (queue, hShip);
+ }
+
+ return hShip;
+}
+
+#if 0
+static COUNT
+queueIndexFromShip (HSTARSHIP hShip)
+{
+ COUNT result;
+ STARSHIP *StarShipPtr = LockStarShip (queue, hShip);
+ result = StarShipPtr->index;
+ UnlockStarShip (queue, hShip);
+}
+#endif
+
+// Pre: called does not hold the graphics lock
+static void
+PickMelee_ChangedSelection (GETMELEE_STATE *gms, COUNT playerI)
+{
+ RECT r;
+ r.corner.x = PICK_X_OFFS + ((ICON_WIDTH + 2) * gms->player[playerI].col);
+ r.corner.y = PICK_Y_OFFS + ((ICON_HEIGHT + 2) * gms->player[playerI].row)
+ + ((1 - playerI) * PICK_SIDE_OFFS);
+ r.extent.width = (ICON_WIDTH + 2);
+ r.extent.height = (ICON_HEIGHT + 2);
+ Flash_setRect (gms->player[playerI].flashContext, &r);
+}
+
+// Only returns false when there is no ship for the choice.
+bool
+setShipSelected(GETMELEE_STATE *gms, COUNT playerI, COUNT choice,
+ bool reportNetwork)
+{
+ HSTARSHIP ship;
+
+ assert (!gms->player[playerI].done);
+
+ if (choice == (COUNT) ~0)
+ {
+ // Random ship selection.
+ ship = MeleeShipByUsedIndex (&race_q[playerI],
+ gms->player[playerI].randomIndex);
+ }
+ else
+ {
+ // Explicit ship selection.
+ ship = MeleeShipByQueueIndex (&race_q[playerI], choice);
+ }
+
+ if (ship == 0)
+ return false;
+
+ gms->player[playerI].choice = choice;
+ gms->player[playerI].hBattleShip = ship;
+ PlayMenuSound (MENU_SOUND_SUCCESS);
+#ifdef NETPLAY
+ if (reportNetwork)
+ reportShipSelected (gms, choice);
+#else
+ (void) reportNetwork;
+#endif
+ gms->player[playerI].done = true;
+ return true;
+}
+
+// Returns FALSE if aborted.
+static BOOLEAN
+SelectShip_processInput (GETMELEE_STATE *gms, COUNT playerI,
+ BATTLE_INPUT_STATE inputState)
+{
+ if (inputState & BATTLE_WEAPON)
+ {
+ if (gms->player[playerI].col == NUM_PICKMELEE_COLUMNS &&
+ gms->player[playerI].row == 0)
+ {
+ // Random ship
+ (void) setShipSelected (gms, playerI, (COUNT) ~0, TRUE);
+ }
+ else if (gms->player[playerI].col == NUM_PICKMELEE_COLUMNS &&
+ gms->player[playerI].row == 1)
+ {
+ // Selected exit
+ if (ConfirmExit ())
+ return FALSE;
+ }
+ else
+ {
+ // Selection is on a ship slot.
+ COUNT slotNr = PickMelee_GetShipIndex (gms->player[playerI].row,
+ gms->player[playerI].col);
+ (void) setShipSelected (gms, playerI, slotNr, TRUE);
+ // If the choice is not valid, setShipSelected()
+ // will not set .done.
+ }
+ }
+ else
+ {
+ // Process motion commands.
+ COUNT new_row, new_col;
+
+ new_row = gms->player[playerI].row;
+ new_col = gms->player[playerI].col;
+ if (inputState & BATTLE_LEFT)
+ {
+ if (new_col-- == 0)
+ new_col = NUM_PICKMELEE_COLUMNS;
+ }
+ else if (inputState & BATTLE_RIGHT)
+ {
+ if (new_col++ == NUM_PICKMELEE_COLUMNS)
+ new_col = 0;
+ }
+ if (inputState & BATTLE_THRUST)
+ {
+ if (new_row-- == 0)
+ new_row = NUM_PICKMELEE_ROWS - 1;
+ }
+ else if (inputState & BATTLE_DOWN)
+ {
+ if (++new_row == NUM_PICKMELEE_ROWS)
+ new_row = 0;
+ }
+
+ if (new_row != gms->player[playerI].row ||
+ new_col != gms->player[playerI].col)
+ {
+ gms->player[playerI].row = new_row;
+ gms->player[playerI].col = new_col;
+
+ PlayMenuSound (MENU_SOUND_MOVE);
+ PickMelee_ChangedSelection (gms, playerI);
+ }
+ }
+
+ return TRUE;
+}
+
+BOOLEAN
+selectShipHuman (HumanInputContext *context, GETMELEE_STATE *gms)
+{
+ BATTLE_INPUT_STATE inputState =
+ PulsedInputToBattleInput (context->playerNr);
+
+ return SelectShip_processInput (gms, context->playerNr, inputState);
+}
+
+BOOLEAN
+selectShipComputer (ComputerInputContext *context, GETMELEE_STATE *gms)
+{
+#define COMPUTER_SELECTION_DELAY (ONE_SECOND >> 1)
+ TimeCount now = GetTimeCounter ();
+ if (now < gms->player[context->playerNr].timeIn +
+ COMPUTER_SELECTION_DELAY)
+ return TRUE;
+
+ return SelectShip_processInput (gms, context->playerNr, BATTLE_WEAPON);
+ // Simulate selection of the random choice button.
+}
+
+#ifdef NETPLAY
+BOOLEAN
+selectShipNetwork (NetworkInputContext *context, GETMELEE_STATE *gms)
+{
+ flushPacketQueues ();
+ // Sets gms->player[context->playerNr].remoteSelected if input
+ // is received.
+ if (gms->player[context->playerNr].remoteSelected)
+ gms->player[context->playerNr].done = TRUE;
+
+ return TRUE;
+}
+#endif
+
+// Select a new ship from the fleet for battle.
+// Returns 'TRUE' if no choice has been made yet; this function is to be
+// called again later.
+// Returns 'FALSE' if a choice has been made. gms->hStarShip is set
+// to the chosen (or randomly selected) ship, or to 0 if 'exit' has
+// been chosen.
+/* TODO: Include player timeouts */
+static BOOLEAN
+DoGetMelee (GETMELEE_STATE *gms)
+{
+ BOOLEAN done;
+ COUNT playerI;
+
+ SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
+
+ if (!gms->Initialized)
+ {
+ gms->Initialized = TRUE;
+ return TRUE;
+ }
+
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ {
+ if (!gms->player[playerI].selecting)
+ continue;
+
+ if (!gms->player[playerI].done)
+ Flash_process (gms->player[playerI].flashContext);
+ }
+
+ SleepThread (ONE_SECOND / 120);
+
+#ifdef NETPLAY
+ netInput ();
+
+ if (!allConnected ())
+ goto aborted;
+#endif
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ goto aborted;
+
+ done = TRUE;
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ {
+ if (!gms->player[playerI].selecting)
+ continue;
+
+ if (!gms->player[playerI].done) {
+ if (!PlayerInput[playerI]->handlers->selectShip (
+ PlayerInput[playerI], gms))
+ goto aborted;
+
+ if (gms->player[playerI].done)
+ {
+ Flash_terminate (gms->player[playerI].flashContext);
+ gms->player[playerI].flashContext = NULL;
+ }
+ else
+ done = FALSE;
+ }
+ }
+
+#ifdef NETPLAY
+ flushPacketQueues ();
+#endif
+ return !done;
+
+aborted:
+#ifdef NETPLAY
+ flushPacketQueues ();
+#endif
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ {
+ if (!gms->player[playerI].selecting)
+ continue;
+
+ gms->player[playerI].choice = 0;
+ gms->player[playerI].hBattleShip = 0;
+ }
+ GLOBAL (CurrentActivity) &= ~CHECK_ABORT;
+ return FALSE;
+}
+
+static COUNT
+GetRaceQueueValue (const QUEUE *queue) {
+ COUNT result;
+ HSTARSHIP hBattleShip, hNextShip;
+
+ result = 0;
+ for (hBattleShip = GetHeadLink (queue);
+ hBattleShip != 0; hBattleShip = hNextShip)
+ {
+ STARSHIP *StarShipPtr = LockStarShip (queue, hBattleShip);
+ hNextShip = _GetSuccLink (StarShipPtr);
+
+ if (StarShipPtr->SpeciesID == NO_ID)
+ continue; // Not active any more.
+
+ result += StarShipPtr->ship_cost;
+
+ UnlockStarShip (queue, hBattleShip);
+ }
+
+ return result;
+}
+
+// Cross out the icon for the dead ship.
+// 'frame' is the PickMeleeFrame for the player.
+// 'shipI' is the index in the ship list.
+// Pre: caller holds the graphics lock.
+static void
+CrossOutShip (FRAME frame, COUNT shipNr)
+{
+ CONTEXT OldContext;
+ STAMP s;
+ BYTE row = PickMelee_GetShipRow (shipNr);
+ BYTE col = PickMelee_GetShipColumn (shipNr);
+
+ OldContext = SetContext (OffScreenContext);
+
+ SetContextFGFrame (frame);
+
+ s.origin.x = 3 + ((ICON_WIDTH + 2) * col);
+ s.origin.y = 9 + ((ICON_HEIGHT + 2) * row);
+ s.frame = SetAbsFrameIndex (StatusFrame, 3);
+ // Cross for through the ship image.
+ DrawStamp (&s);
+
+ SetContext (OldContext);
+}
+
+// Draw the value of the fleet in the top right of the PickMeleeFrame.
+// Pre: caller holds the graphics lock.
+static void
+UpdatePickMeleeFleetValue (FRAME frame, COUNT which_player)
+{
+ CONTEXT OldContext;
+ COUNT value;
+ RECT r;
+ TEXT t;
+ UNICODE buf[40];
+
+ value = GetRaceQueueValue (&race_q[which_player]);
+
+ OldContext = SetContext (OffScreenContext);
+ SetContextFGFrame (frame);
+
+ // Erase the old value text.
+ GetFrameRect (frame, &r);
+ r.extent.width -= 4;
+ t.baseline.x = r.extent.width;
+ r.corner.x = r.extent.width - (6 * 3);
+ r.corner.y = 2;
+ r.extent.width = (6 * 3);
+ r.extent.height = 7 - 2;
+ SetContextForeGroundColor (PICK_BG_COLOR);
+ DrawFilledRectangle (&r);
+
+ // Draw the new value text.
+ sprintf (buf, "%d", value);
+ t.baseline.y = 7;
+ t.align = ALIGN_RIGHT;
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+ SetContextFont (TinyFont);
+ SetContextForeGroundColor (PICK_VALUE_COLOR);
+ font_DrawText (&t);
+
+ SetContext (OldContext);
+}
+
+// Create a frame for each player to display their current fleet in,
+// to be used when selecting the next ship to fight with.
+void
+BuildPickMeleeFrame (void)
+{
+ STAMP s;
+ CONTEXT OldContext = SetContext (OffScreenContext);
+
+ if (PickMeleeFrame)
+ DestroyDrawable (ReleaseDrawable (PickMeleeFrame));
+
+ PickMeleeFrame = CaptureDrawable (CreateDrawable (
+ WANT_PIXMAP, MELEE_WIDTH, MELEE_HEIGHT, 2));
+ s.origin.x = 0;
+ s.origin.y = 0;
+
+ s.frame = CaptureDrawable (LoadGraphic (MELEE_PICK_MASK_PMAP_ANIM));
+ SetContextFGFrame (PickMeleeFrame);
+ DrawStamp (&s);
+
+ s.frame = IncFrameIndex (s.frame);
+ SetContextFGFrame (IncFrameIndex (PickMeleeFrame));
+ DrawStamp (&s);
+
+ DestroyDrawable (ReleaseDrawable (s.frame));
+
+ SetContext (OldContext);
+}
+
+// Put the ship icons in the PickMeleeFrame, and create a queue
+// for each player.
+// XXX TODO: split off creating the queue into a separate function.
+void
+FillPickMeleeFrame (MeleeSetup *setup)
+{
+ COUNT i;
+ CONTEXT OldContext;
+
+ OldContext = SetContext (OffScreenContext);
+
+ for (i = 0; i < NUM_SIDES; ++i)
+ {
+ COUNT side;
+ COUNT sideI;
+ RECT r;
+ TEXT t;
+ STAMP s;
+ UNICODE buf[30];
+ FleetShipIndex index;
+
+ sideI = GetPlayerOrder (i);
+ side = !sideI;
+
+ s.frame = SetAbsFrameIndex (PickMeleeFrame, side);
+ SetContextFGFrame (s.frame);
+
+ GetFrameRect (s.frame, &r);
+ t.baseline.x = r.extent.width >> 1;
+ t.baseline.y = r.extent.height - NAME_AREA_HEIGHT + 4;
+
+ r.corner.x += 2;
+ r.corner.y += 2;
+ r.extent.width -= (2 * 2) + (ICON_WIDTH + 2) + 1;
+ r.extent.height -= (2 * 2) + NAME_AREA_HEIGHT;
+ SetContextForeGroundColor (PICK_BG_COLOR);
+ DrawFilledRectangle (&r);
+
+ r.corner.x += 2;
+ r.extent.width += (ICON_WIDTH + 2) - (2 * 2);
+ r.corner.y += r.extent.height;
+ r.extent.height = NAME_AREA_HEIGHT;
+ DrawFilledRectangle (&r);
+
+ // Team name at the bottom of the frame:
+ t.align = ALIGN_CENTER;
+ t.pStr = MeleeSetup_getTeamName (setup, sideI);
+ t.CharCount = (COUNT) ~0;
+ SetContextFont (TinyFont);
+ SetContextForeGroundColor (PICKSHIP_TEAM_NAME_TEXT_COLOR);
+ font_DrawText (&t);
+
+ // Total team value of the starting team:
+ sprintf (buf, "%u", MeleeSetup_getFleetValue (setup, sideI));
+ t.baseline.x = 4;
+ t.baseline.y = 7;
+ t.align = ALIGN_LEFT;
+ t.pStr = buf;
+ t.CharCount = (COUNT)~0;
+ SetContextForeGroundColor (PICKSHIP_TEAM_START_VALUE_COLOR);
+ font_DrawText (&t);
+
+ assert (CountLinks (&race_q[side]) == 0);
+
+ for (index = 0; index < MELEE_FLEET_SIZE; index++)
+ {
+ MeleeShip StarShip;
+
+ StarShip = MeleeSetup_getShip (setup, sideI, index);
+ if (StarShip == MELEE_NONE)
+ continue;
+
+ {
+ BYTE row, col;
+ BYTE ship_cost;
+ HMASTERSHIP hMasterShip;
+ HSTARSHIP hBuiltShip;
+ MASTER_SHIP_INFO *MasterPtr;
+ STARSHIP *BuiltShipPtr;
+ BYTE captains_name_index;
+
+ hMasterShip = GetStarShipFromIndex (&master_q, StarShip);
+ MasterPtr = LockMasterShip (&master_q, hMasterShip);
+
+ captains_name_index = NameCaptain (&race_q[side],
+ MasterPtr->SpeciesID);
+ hBuiltShip = Build (&race_q[side], MasterPtr->SpeciesID);
+
+ // Draw the icon.
+ row = PickMelee_GetShipRow (index);
+ col = PickMelee_GetShipColumn (index);
+ s.origin.x = 4 + ((ICON_WIDTH + 2) * col);
+ s.origin.y = 10 + ((ICON_HEIGHT + 2) * row);
+ s.frame = MasterPtr->ShipInfo.icons;
+ DrawStamp (&s);
+
+ ship_cost = MasterPtr->ShipInfo.ship_cost;
+ UnlockMasterShip (&master_q, hMasterShip);
+
+ BuiltShipPtr = LockStarShip (&race_q[side], hBuiltShip);
+ BuiltShipPtr->index = index;
+ BuiltShipPtr->ship_cost = ship_cost;
+ BuiltShipPtr->playerNr = side;
+ BuiltShipPtr->captains_name_index = captains_name_index;
+ // The next ones are not used in Melee
+ BuiltShipPtr->crew_level = 0;
+ BuiltShipPtr->max_crew = 0;
+ BuiltShipPtr->race_strings = 0;
+ BuiltShipPtr->icons = 0;
+ BuiltShipPtr->RaceDescPtr = 0;
+ UnlockStarShip (&race_q[side], hBuiltShip);
+ }
+ }
+ }
+
+ SetContext (OldContext);
+}
+
+void
+DestroyPickMeleeFrame (void)
+{
+ DestroyDrawable (ReleaseDrawable (PickMeleeFrame));
+ PickMeleeFrame = 0;
+}
+
+// Pre: caller holds the graphics lock.
+static void
+DrawPickMeleeFrame (COUNT which_player)
+{
+ CONTEXT oldContext;
+ STAMP s;
+
+ oldContext = SetContext (SpaceContext);
+ s.frame = SetAbsFrameIndex (PickMeleeFrame, which_player);
+ s.origin.x = PICK_X_OFFS - 3;
+ s.origin.y = PICK_Y_OFFS - 9 + ((1 - which_player) * PICK_SIDE_OFFS);
+ DrawStamp (&s);
+ // Draw the selection box to screen.
+
+ SetContext (oldContext);
+}
+
+// Pre: caller holds the graphics lock.
+void
+MeleeGameOver (void)
+{
+ COUNT playerI;
+ DWORD TimeOut;
+ BOOLEAN PressState, ButtonState;
+
+ // Show the battle result.
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ DrawPickMeleeFrame (playerI);
+
+
+#ifdef NETPLAY
+ negotiateReadyConnections(true, NetState_inSetup);
+#endif
+
+ TimeOut = GetTimeCounter () + (ONE_SECOND * 4);
+
+ PressState = PulsedInputState.menu[KEY_MENU_SELECT] ||
+ PulsedInputState.menu[KEY_MENU_CANCEL];
+ do
+ {
+ UpdateInputState ();
+ ButtonState = PulsedInputState.menu[KEY_MENU_SELECT] ||
+ PulsedInputState.menu[KEY_MENU_CANCEL];
+ if (PressState)
+ {
+ PressState = ButtonState;
+ ButtonState = FALSE;
+ }
+
+ Async_process ();
+ TaskSwitch ();
+ } while (!(GLOBAL (CurrentActivity) & CHECK_ABORT) && (!ButtonState
+ && (!(PlayerControl[0] & PlayerControl[1] & PSYTRON_CONTROL)
+ || GetTimeCounter () < TimeOut)));
+
+}
+
+void
+MeleeShipDeath (STARSHIP *ship)
+{
+ FRAME frame;
+
+ // Deactivate fleet position.
+ ship->SpeciesID = NO_ID;
+
+ frame = SetAbsFrameIndex (PickMeleeFrame, ship->playerNr);
+ CrossOutShip (frame, ship->index);
+ UpdatePickMeleeFleetValue (frame, ship->playerNr);
+}
+
+// Post: the NetState for all players is NetState_interBattle
+static BOOLEAN
+GetMeleeStarShips (COUNT playerMask, HSTARSHIP *ships)
+{
+ COUNT playerI;
+ BOOLEAN ok;
+ GETMELEE_STATE gmstate;
+ TimeCount now;
+ COUNT i;
+
+#ifdef NETPLAY
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ {
+ NetConnection *conn;
+
+ if ((playerMask & (1 << playerI)) == 0)
+ continue;
+
+ // XXX: This does not have to be done per connection.
+ conn = netConnections[playerI];
+ if (conn != NULL) {
+ BattleStateData *battleStateData;
+ battleStateData =
+ (BattleStateData *) NetConnection_getStateData (conn);
+ battleStateData->getMeleeState = &gmstate;
+ }
+ }
+#endif
+
+ ok = true;
+
+ now = GetTimeCounter ();
+ gmstate.InputFunc = DoGetMelee;
+ gmstate.Initialized = FALSE;
+ for (i = 0; i < NUM_PLAYERS; ++i)
+ {
+ // We have to use TFB_Random() results in specific order
+ playerI = GetPlayerOrder (i);
+ gmstate.player[playerI].selecting =
+ (playerMask & (1 << playerI)) != 0;
+ gmstate.player[playerI].ships_left = battle_counter[playerI];
+
+ // We determine in advance which ship would be chosen if the player
+ // wants a random ship, to keep it simple to keep network parties
+ // synchronised.
+ gmstate.player[playerI].randomIndex =
+ (COUNT)TFB_Random () % gmstate.player[playerI].ships_left;
+ gmstate.player[playerI].done = FALSE;
+
+ if (!gmstate.player[playerI].selecting)
+ continue;
+
+ gmstate.player[playerI].timeIn = now;
+ gmstate.player[playerI].row = 0;
+ gmstate.player[playerI].col = NUM_PICKMELEE_COLUMNS;
+#ifdef NETPLAY
+ gmstate.player[playerI].remoteSelected = FALSE;
+#endif
+
+ gmstate.player[playerI].flashContext =
+ Flash_createHighlight (ScreenContext, NULL);
+ Flash_setMergeFactors (gmstate.player[playerI].flashContext,
+ 2, 3, 2);
+ Flash_setFrameTime (gmstate.player[playerI].flashContext,
+ ONE_SECOND / 16);
+#ifdef NETPLAY
+ if (PlayerControl[playerI] & NETWORK_CONTROL)
+ Flash_setSpeed (gmstate.player[playerI].flashContext,
+ ONE_SECOND / 2, 0, ONE_SECOND / 2, 0);
+ else
+#endif
+ {
+ Flash_setSpeed (gmstate.player[playerI].flashContext,
+ 0, ONE_SECOND / 16, 0, ONE_SECOND / 16);
+ }
+ PickMelee_ChangedSelection (&gmstate, playerI);
+ Flash_start (gmstate.player[playerI].flashContext);
+ }
+
+#ifdef NETPLAY
+ {
+ // NB. gmstate.player[].randomIndex and gmstate.player[].done must
+ // be initialised before negotiateReadyConnections is completed, to
+ // ensure that they are initialised when the SelectShip packet
+ // arrives.
+ bool allOk = negotiateReadyConnections (true, NetState_selectShip);
+ if (!allOk)
+ {
+ // Some network connection has been reset.
+ ok = false;
+ }
+ }
+#endif
+ SetDefaultMenuRepeatDelay ();
+
+ SetContext (OffScreenContext);
+
+
+ DoInput (&gmstate, FALSE);
+ WaitForSoundEnd (0);
+
+
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ {
+ if (!gmstate.player[playerI].selecting)
+ continue;
+
+ if (gmstate.player[playerI].done)
+ {
+ // Flash rectangle is already terminated.
+ ships[playerI] = gmstate.player[playerI].hBattleShip;
+ }
+ else
+ {
+ Flash_terminate (gmstate.player[playerI].flashContext);
+ gmstate.player[playerI].flashContext = NULL;
+ ok = false;
+ }
+ }
+
+#ifdef NETPLAY
+ if (ok)
+ {
+ if (!negotiateReadyConnections (true, NetState_interBattle))
+ ok = false;
+ }
+ else
+ setStateConnections (NetState_interBattle);
+#endif
+
+ if (!ok)
+ {
+ // Aborting.
+ GLOBAL (CurrentActivity) &= ~IN_BATTLE;
+ }
+
+#ifdef NETPLAY
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ {
+ NetConnection *conn;
+
+ if ((playerMask & (1 << playerI)) == 0)
+ continue;
+
+ // XXX: This does not have to be done per connection.
+ conn = netConnections[playerI];
+ if (conn != NULL && NetConnection_isConnected (conn))
+ {
+ BattleStateData *battleStateData;
+ battleStateData =
+ (BattleStateData *) NetConnection_getStateData (conn);
+ battleStateData->getMeleeState = NULL;
+ }
+ }
+#endif
+
+ return ok;
+}
+
+BOOLEAN
+GetInitialMeleeStarShips (HSTARSHIP *result)
+{
+ COUNT playerI;
+ COUNT playerMask;
+
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ {
+ FRAME frame;
+ frame = SetAbsFrameIndex (PickMeleeFrame, playerI);
+ UpdatePickMeleeFleetValue (frame, playerI);
+ DrawPickMeleeFrame (playerI);
+ }
+
+ // Fade in
+ SleepThreadUntil (FadeScreen (FadeAllToColor, ONE_SECOND / 2)
+ + ONE_SECOND / 60);
+ FlushColorXForms ();
+
+ playerMask = 0;
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ playerMask |= (1 << playerI);
+
+ return GetMeleeStarShips (playerMask, result);
+}
+
+// Get the next ship to use in SuperMelee.
+BOOLEAN
+GetNextMeleeStarShip (COUNT which_player, HSTARSHIP *result)
+{
+ COUNT playerMask;
+ HSTARSHIP ships[NUM_PLAYERS];
+ BOOLEAN ok;
+
+ DrawPickMeleeFrame (which_player);
+
+ playerMask = 1 << which_player;
+ ok = GetMeleeStarShips (playerMask, ships);
+ if (ok)
+ *result = ships[which_player];
+
+ return ok;
+}
+
+#ifdef NETPLAY
+// Called when a ship selection has arrived from a remote player.
+bool
+updateMeleeSelection (GETMELEE_STATE *gms, COUNT playerI, COUNT ship)
+{
+ if (gms == NULL || !gms->player[playerI].selecting ||
+ gms->player[playerI].done)
+ {
+ // This happens when we get an update message from a connection
+ // for who we are not selecting a ship.
+ log_add (log_Warning, "Unexpected ship selection packet "
+ "received.\n");
+ return false;
+ }
+
+ if (!setShipSelected (gms, playerI, ship, false))
+ {
+ log_add (log_Warning, "Invalid ship selection received from remote "
+ "party.\n");
+ return false;
+ }
+
+ gms->player[playerI].remoteSelected = TRUE;
+ return true;
+}
+
+static void
+reportShipSelected (GETMELEE_STATE *gms, COUNT index)
+{
+ size_t playerI;
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ {
+ NetConnection *conn = netConnections[playerI];
+
+ if (conn == NULL)
+ continue;
+
+ if (!NetConnection_isConnected (conn))
+ continue;
+
+ Netplay_Notify_shipSelected (conn, index);
+ }
+ (void) gms;
+}
+#endif
+
diff --git a/src/uqm/supermelee/pickmele.h b/src/uqm/supermelee/pickmele.h
new file mode 100644
index 0000000..3588063
--- /dev/null
+++ b/src/uqm/supermelee/pickmele.h
@@ -0,0 +1,102 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef UQM_SUPERMELEE_PICKMELE_H_
+#define UQM_SUPERMELEE_PICKMELE_H_
+
+typedef struct getmelee_struct GETMELEE_STATE;
+
+#include "../races.h"
+#include "../battlecontrols.h"
+#include "meleesetup.h"
+#include "libs/compiler.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+void MeleeShipDeath (STARSHIP *);
+void BuildPickMeleeFrame (void);
+void DestroyPickMeleeFrame (void);
+void FillPickMeleeFrame (MeleeSetup *setup);
+void MeleeGameOver (void);
+BOOLEAN GetInitialMeleeStarShips (HSTARSHIP *result);
+BOOLEAN GetNextMeleeStarShip (COUNT which_player, HSTARSHIP *result);
+
+bool updateMeleeSelection (GETMELEE_STATE *gms, COUNT player, COUNT ship);
+
+BOOLEAN selectShipHuman (HumanInputContext *context, GETMELEE_STATE *gms);
+BOOLEAN selectShipComputer (ComputerInputContext *context,
+ GETMELEE_STATE *gms);
+#ifdef NETPLAY
+BOOLEAN selectShipNetwork (NetworkInputContext *context, GETMELEE_STATE *gms);
+#endif /* NETPLAY */
+
+#if defined(__cplusplus)
+}
+#endif
+
+#ifdef PICKMELE_INTERNAL
+
+#include "../flash.h"
+#include "libs/timelib.h"
+#include "../init.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct getmelee_struct {
+ BOOLEAN (*InputFunc) (struct getmelee_struct *pInputState);
+
+ BOOLEAN Initialized;
+
+ struct {
+ TimeCount timeIn;
+ HSTARSHIP hBattleShip;
+ // Chosen ship.
+ COUNT choice;
+ // Index of chosen ship, or (COUNT) ~0 for random choice.
+
+ COUNT row;
+ COUNT col;
+ COUNT ships_left;
+ // Number of ships still available.
+ COUNT randomIndex;
+ // Pre-generated random number.
+ BOOLEAN selecting;
+ // Is this player selecting a ship?
+ BOOLEAN done;
+ // Has a selection been made for this player?
+ FlashContext *flashContext;
+ // Context for controlling the flash rectangle.
+#ifdef NETPLAY
+ BOOLEAN remoteSelected;
+#endif
+ } player[NUM_PLAYERS];
+};
+
+bool setShipSelected(GETMELEE_STATE *gms, COUNT playerI, COUNT choice,
+ bool reportNetwork);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* PICKMELE_INTERNAL */
+
+#endif /* UQM_SUPERMELEE_PICKMELE_H_ */
+