summaryrefslogtreecommitdiff
path: root/src/uqm/supermelee/melee.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/uqm/supermelee/melee.c')
-rw-r--r--src/uqm/supermelee/melee.c2640
1 files changed, 2640 insertions, 0 deletions
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 */
+