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