diff options
Diffstat (limited to 'src/uqm/supermelee/melee.c')
-rw-r--r-- | src/uqm/supermelee/melee.c | 2640 |
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 */ + |