diff --git a/sc2/ChangeLog b/sc2/ChangeLog index 28ecab819..be6cd969d 100644 --- a/sc2/ChangeLog +++ b/sc2/ChangeLog @@ -1,4 +1,5 @@ Changes towards version 0.6: +- Better abort and disconnect handling for Netplay - SvdB - Menu sounds in Setup track rest of game (#922), from Nic - Michael - Shifted the Mouse Error to a Popup Window, moved the message to starcon.txt for translators - Michael diff --git a/sc2/content/lbm/starcon.txt b/sc2/content/lbm/starcon.txt index 61481ef9e..9147cb9ae 100755 --- a/sc2/content/lbm/starcon.txt +++ b/sc2/content/lbm/starcon.txt @@ -2261,8 +2261,8 @@ Awaiting incoming connection #(Attempting outgoing connection...) Attempting outgoing connection -#(Connected. Press RIGHT to disconnect.) -Connected. Change setting to disconnect. +#(Connected. Change the control method to disconnect.) +Connected. Change the control method to disconnect. #(Confirmation cancelled. Press FIRE to reconfirm.) @@ -2324,20 +2324,52 @@ Top player changed something -- need to reconfirm. #(Press SPACE to cancel) Press SPACE to cancel + #(Connect to remote host) Connect to remote host + #(Wait for incoming connection) Wait for incoming connection + #(Cancel) Cancel + #(Host) Host + #(Port) Port + #(Net Delay) -Net Delay \ No newline at end of file +Net Delay + + +#(Disconnect for an unspecified reason.) +Disconnect for an unspecified reason. + + +#(Connection aborted due to version mismatch.) +Connection aborted due to version mismatch. + + +#(Connection aborted due to an internal protocol error.) +Connection aborted due to an internal protocol error. + + +#(Game aborted for an unspecified reason.) +Game aborted for an unspecified reason. + + +#(Game aborted due to loss of synchronisation.) +Game aborted due to loss of synchronisation. + + +#(Game aborted by the remote player.) +Game aborted by the remote player. + + diff --git a/sc2/doc/devel/netplay/protocol b/sc2/doc/devel/netplay/protocol index e7b651502..37a284c6e 100644 --- a/sc2/doc/devel/netplay/protocol +++ b/sc2/doc/devel/netplay/protocol @@ -1,4 +1,4 @@ -There are three types of negotiations used to synchronised the parties +There are several types of negotiations used to synchronised the parties of a network connection. - Continue when we know the other is ready ("Ready") @@ -11,7 +11,8 @@ of a network connection. This is used to end a state where both parties are modifying common data. Both parties have to agree with the data for either party to continue. - +- Reset a connection. This is used to abort a game in progress and return + to the fleet setup menu. ============================================================================ @@ -199,6 +200,13 @@ I also check whether it is possible for packets to arrive that aren't expected. +============================================================================ + +"Reset" negotiation. + +See src/sc2code/netplay/proto.c + + ============================================================================ Battle ending negotiation. diff --git a/sc2/doc/devel/netplay/todo b/sc2/doc/devel/netplay/todo index 79e709a32..e452e1390 100644 --- a/sc2/doc/devel/netplay/todo +++ b/sc2/doc/devel/netplay/todo @@ -117,9 +117,6 @@ To put in the announcement of Netplay: Bugs and todos unrelated to netplay. -- When you insert a new ship, the cursor moves to the next square, - but the picture at the right doesn't change. -- The "Battle!" icon is not positioned correctly. - other player being able to choose the next ship after 3 seconds of inactivity - DoRunAway() shouldn't be handled in ProcessInput() diff --git a/sc2/src/msvc++/UrQuanMasters.dsp b/sc2/src/msvc++/UrQuanMasters.dsp index 5510eca05..8b0ddadc6 100644 --- a/sc2/src/msvc++/UrQuanMasters.dsp +++ b/sc2/src/msvc++/UrQuanMasters.dsp @@ -2604,6 +2604,14 @@ SOURCE=..\sc2code\netplay\proto\ready.c SOURCE=..\sc2code\netplay\proto\ready.h # End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\proto\reset.c +# End Source File +# Begin Source File + +SOURCE=..\sc2code\netplay\proto\reset.h +# End Source File # End Group # End Group diff --git a/sc2/src/sc2code/battle.c b/sc2/src/sc2code/battle.c index 656d90d93..1a324585b 100644 --- a/sc2/src/sc2code/battle.c +++ b/sc2/src/sc2code/battle.c @@ -47,6 +47,12 @@ size_t battleInputOrder[NUM_SIDES]; #ifdef NETPLAY BattleFrameCounter battleFrameCount; // Used for synchronisation purposes during netplay. +COUNT currentDeadSide; + // When a ship has been destroyed, each side of a network + // connection waits until the other side is ready. + // When two ships die at the same time, this is handled for one + // ship after the other. This variable indicate for which player + // we're currently doing this. #endif static BOOLEAN @@ -158,7 +164,7 @@ ProcessInput (void) JournalInput (InputState); #endif /* CREATE_JOURNAL */ #ifdef NETPLAY - if (PlayerControl[cur_player] & HUMAN_CONTROL) + if (!(PlayerControl[cur_player] & NETWORK_CONTROL)) { BattleInputBuffer *bib = getBattleInputBuffer(cur_player); sendBattleInputConnections (InputState); @@ -278,8 +284,11 @@ DoBattle (BATTLE_STATE *bs) if (battleFrameCount >= delay && (battleFrameCount - delay) % NETPLAY_CHECKSUM_INTERVAL == 0) { - if (!verifyChecksums (battleFrameCount - delay)) - GLOBAL(CurrentActivity) |= CHECK_ABORT; + if (!(GLOBAL (CurrentActivity) & CHECK_ABORT)) + if (!verifyChecksums (battleFrameCount - delay)) { + GLOBAL(CurrentActivity) |= CHECK_ABORT; + resetConnections(ResetReason_syncLoss); + } } } #endif @@ -440,23 +449,31 @@ Battle (void) CountLinks (&race_q[1]) ); - if (!selectAllShips (num_ships)) - goto AbortBattle; - - BattleSong (TRUE); - bs.NextTime = 0; setupBattleInputOrder (); #ifdef NETPLAY initBattleInputBuffers (); #ifdef NETPLAY_CHECKSUM initChecksumBuffers (); -#endif +#endif /* NETPLAY_CHECKSUM */ battleFrameCount = 0; + currentDeadSide = (COUNT)~0; +#endif /* NETPLAY */ + + if (!selectAllShips (num_ships)) { + GLOBAL (CurrentActivity) |= CHECK_ABORT; + goto AbortBattle; + } + + BattleSong (TRUE); + bs.NextTime = 0; +#ifdef NETPLAY initBattleStateDataConnections (); { bool allOk = negotiateReadyConnections (true, NetState_inBattle); - if (!allOk) + if (!allOk) { + GLOBAL (CurrentActivity) |= CHECK_ABORT; goto AbortBattle; + } } #endif /* NETPLAY */ bs.InputFunc = DoBattle; @@ -468,11 +485,26 @@ Battle (void) LockMutex (GraphicsLock); AbortBattle: + if (LOBYTE (GLOBAL (CurrentActivity)) == SUPER_MELEE && + (GLOBAL (CurrentActivity) & CHECK_ABORT)) + { + // Do not return to the main menu when a game is aborted, + // (just to the supermelee menu). + UnlockMutex (GraphicsLock); + waitResetConnections(NetState_inSetup); + // A connection may already be in inSetup (set from + // GetMeleeStarship). This is not a problem, although + // it will generate a warning in debug mode. + LockMutex (GraphicsLock); + + GLOBAL (CurrentActivity) &= ~CHECK_ABORT; + } + #ifdef NETPLAY uninitBattleInputBuffers(); #ifdef NETPLAY_CHECKSUM uninitChecksumBuffers (); -#endif +#endif /* NETPLAY_CHECKSUM */ #endif /* NETPLAY */ StopMusic (); diff --git a/sc2/src/sc2code/battle.h b/sc2/src/sc2code/battle.h index 94404cfee..236b273ed 100644 --- a/sc2/src/sc2code/battle.h +++ b/sc2/src/sc2code/battle.h @@ -25,6 +25,7 @@ extern BOOLEAN instantVictory; #ifdef NETPLAY typedef DWORD BattleFrameCounter; extern BattleFrameCounter battleFrameCount; +extern COUNT currentDeadSide; #endif extern BOOLEAN Battle (void); diff --git a/sc2/src/sc2code/element.h b/sc2/src/sc2code/element.h index e1b568e4a..0d92a7fcd 100644 --- a/sc2/src/sc2code/element.h +++ b/sc2/src/sc2code/element.h @@ -95,6 +95,7 @@ typedef struct state } STATE; typedef STATE *PSTATE; +// Any physical object in the simulation. typedef struct element { HELEMENT pred, succ; @@ -116,6 +117,7 @@ typedef struct element STATE current, next; PVOID pParent; + // The ship this element belongs to. HELEMENT hTarget; } ELEMENT; typedef ELEMENT *PELEMENT; diff --git a/sc2/src/sc2code/globdata.h b/sc2/src/sc2code/globdata.h index 92e594de1..8a41563f0 100644 --- a/sc2/src/sc2code/globdata.h +++ b/sc2/src/sc2code/globdata.h @@ -923,7 +923,7 @@ enum START_INTERPLANETARY = MAKE_WORD (0, (1 << 3)), CHECK_LOAD = MAKE_WORD (0, (1 << 4)), CHECK_RESTART = MAKE_WORD (0, (1 << 5)), - CHECK_ABORT = MAKE_WORD (0, (1 << 6)) + CHECK_ABORT = MAKE_WORD (0, (1 << 6)), }; typedef UWORD ACTIVITY; diff --git a/sc2/src/sc2code/melee.c b/sc2/src/sc2code/melee.c index bc40f6b08..64d86147e 100644 --- a/sc2/src/sc2code/melee.c +++ b/sc2/src/sc2code/melee.c @@ -575,6 +575,7 @@ DrawPickIcon (COUNT iship, BYTE DrawErase) #ifdef NETPLAY // This function is generic. It should probably be moved to elsewhere. +// The caller should hold the GraphicsLock. static void multiLineDrawText(TEXT *textIn, RECT *clipRect) { RECT oldRect; @@ -701,6 +702,7 @@ UpdateMeleeStatusMessage (ssize_t player) #endif /* NETPLAY */ // XXX: this function is called when the current selection is blinking off. +// The caller should hold the GraphicsLock. static void Deselect (BYTE opt) { @@ -760,6 +762,7 @@ Deselect (BYTE opt) } // XXX: this function is called when the current selection is blinking off. +// The caller should hold the GraphicsLock. static void Select (BYTE opt) { @@ -2303,7 +2306,6 @@ DoConnectingDialog (PMELEE_STATE pMS) if ((status == NetState_init) || (status == NetState_inSetup)) { - RECT r; /* Connection complete! */ PlayerControl[which_side] = NETWORK_CONTROL | STANDARD_RATING; SetPlayerInput (); @@ -2349,7 +2351,8 @@ check_for_disconnections (PMELEE_STATE pMS) { PlayerControl[player] = HUMAN_CONTROL & STANDARD_RATING; DrawControls (player, FALSE); - log_add (log_User, "Player %d has disconnected; shifting controls\n", player); + log_add (log_User, "Player %d has disconnected; shifting " + "controls\n", player); changed = TRUE; } } @@ -2358,6 +2361,8 @@ check_for_disconnections (PMELEE_STATE pMS) { SetPlayerInput (); } + + (void) pMS; } #endif @@ -2852,6 +2857,88 @@ InitPreBuilt (PMELEE_STATE pMS) } } +int +LoadMeleeConfig (PMELEE_STATE pMS) +{ + uio_Stream *load_fp; + int status; + + load_fp = res_OpenResFile (configDir, "melee.cfg", "rb"); + if (!load_fp) + goto err; + + if (LengthResFile (load_fp) != (1 + sizeof (TEAM_IMAGE)) * 2) + goto err; + + status = GetResFileChar (load_fp); + if (status == -1) + goto err; + PlayerControl[0] = (BYTE)status; + + status = ReadTeamImage (&pMS->TeamImage[0], load_fp); + if (status == -1) + goto err; + + status = GetResFileChar (load_fp); + if (status == -1) + goto err; + PlayerControl[1] = (BYTE)status; + + status = ReadTeamImage (&pMS->TeamImage[1], load_fp); + if (status == -1) + goto err; + + res_CloseResFile (load_fp); + + /* Do not allow netplay mode at the start. */ + if (PlayerControl[0] & NETWORK_CONTROL) + PlayerControl[0] = HUMAN_CONTROL | STANDARD_RATING; + if (PlayerControl[1] & NETWORK_CONTROL) + PlayerControl[1] = HUMAN_CONTROL | STANDARD_RATING; + + return 0; + +err: + if (load_fp) + res_CloseResFile (load_fp); + return -1; +} + +int +WriteMeleeConfig (PMELEE_STATE pMS) +{ + uio_Stream *save_fp; + + save_fp = res_OpenResFile (configDir, "melee.cfg", "wb"); + if (!save_fp) + goto err; + + if (PutResFileChar (PlayerControl[0], save_fp) == -1) + goto err; + + if (WriteTeamImage (&pMS->TeamImage[0], save_fp) == 0) + goto err; + + if (PutResFileChar (PlayerControl[1], save_fp) == -1) + goto err; + + if (WriteTeamImage (&pMS->TeamImage[1], save_fp) == 0) + goto err; + + if (!res_CloseResFile (save_fp)) + goto err; + + return 0; + +err: + if (save_fp) + { + res_CloseResFile (save_fp); + DeleteResFile (configDir, "melee.cfg"); + } + return -1; +} + void Melee (void) { @@ -2880,49 +2967,12 @@ Melee (void) GameSounds = CaptureSound (LoadSound (GAME_SOUNDS)); LoadMeleeInfo (&MenuState); + if (LoadMeleeConfig (&MenuState) == -1) { - uio_Stream *load_fp; - - load_fp = res_OpenResFile (configDir, "melee.cfg", "rb"); - if (load_fp) - { - int status; - - if (LengthResFile (load_fp) != (1 + sizeof (TEAM_IMAGE)) * 2) - status = -1; - else if ((status = GetResFileChar (load_fp)) != -1) - { - PlayerControl[0] = (BYTE)status; - status = ReadTeamImage (&MenuState.TeamImage[0], load_fp); - if (status != -1) - { - status = GetResFileChar (load_fp); - if (status != -1) - { - PlayerControl[1] = (BYTE)status; - status = ReadTeamImage ( - &MenuState.TeamImage[1], load_fp); - } - } - } - res_CloseResFile (load_fp); - if (status == -1) - load_fp = 0; - } - - if (load_fp == 0) - { - PlayerControl[0] = HUMAN_CONTROL | STANDARD_RATING; - MenuState.TeamImage[0] = MenuState.PreBuiltList[0]; - PlayerControl[1] = COMPUTER_CONTROL | STANDARD_RATING; - MenuState.TeamImage[1] = MenuState.PreBuiltList[1]; - } - - /* Do not allow netplay mode at the start. */ - if (PlayerControl[0] & NETWORK_CONTROL) - PlayerControl[0] = HUMAN_CONTROL | STANDARD_RATING; - if (PlayerControl[1] & NETWORK_CONTROL) - PlayerControl[1] = HUMAN_CONTROL | STANDARD_RATING; + PlayerControl[0] = HUMAN_CONTROL | STANDARD_RATING; + MenuState.TeamImage[0] = MenuState.PreBuiltList[0]; + PlayerControl[1] = COMPUTER_CONTROL | STANDARD_RATING; + MenuState.TeamImage[1] = MenuState.PreBuiltList[1]; } SetPlayerInput (); teamStringChanged (&MenuState, 0); @@ -2939,35 +2989,7 @@ Melee (void) StopMusic (); WaitForSoundEnd (TFBSOUND_WAIT_ALL); - { - uio_Stream *save_fp; - BOOLEAN err; - - err = FALSE; - save_fp = res_OpenResFile (configDir, "melee.cfg", "wb"); - if (save_fp) - { - if (PutResFileChar (PlayerControl[0], save_fp) == -1) - err = TRUE; - if (!err && WriteTeamImage (&MenuState.TeamImage[0], - save_fp) == 0) - err = TRUE; - if (!err && PutResFileChar (PlayerControl[1], save_fp) == -1) - err = TRUE; - if (!err && WriteTeamImage (&MenuState.TeamImage[1], - save_fp) == 0) - err = TRUE; - if (res_CloseResFile (save_fp) == 0) - err = TRUE; - } - else - err = TRUE; - - if (err) - { - DeleteResFile (configDir, "melee.cfg"); - } - } + WriteMeleeConfig (&MenuState); FreeMeleeInfo (&MenuState); DestroySound (ReleaseSound (GameSounds)); GameSounds = 0; @@ -3083,6 +3105,7 @@ updateTeamName (PMELEE_STATE pMS, COUNT side, const char *name, strncpy (pMS->TeamImage[side].TeamName, name, len); pMS->TeamImage[side].TeamName[len] = '\0'; + LockMutex (GraphicsLock); #if 0 /* DTSHS_REPAIR does not combine with other options */ if (pMS->MeleeOption == EDIT_MELEE && pMS->side == side && pMS->row == NUM_MELEE_ROWS) @@ -3090,6 +3113,7 @@ updateTeamName (PMELEE_STATE pMS, COUNT side, const char *name, else #endif DrawTeamString (pMS, side, DTSHS_REPAIR); + UnlockMutex (GraphicsLock); } // Update a ship in a fleet as specified by a remote party. @@ -3131,6 +3155,7 @@ updateFleetShip (PMELEE_STATE pMS, COUNT side, COUNT index, BYTE ship) (pMS->side == side) && (index == selectedShipIndex); // Ship to be updated is the currently selected one. + LockMutex (GraphicsLock); if (ship == MELEE_NONE) { RECT r; @@ -3148,12 +3173,14 @@ updateFleetShip (PMELEE_STATE pMS, COUNT side, COUNT index, BYTE ship) // Reprint the team value: //DrawTeamString (pMeleeState, side, DTSHS_NORMAL); DrawTeamString (pMS, side, DTSHS_REPAIR); + UnlockMutex (GraphicsLock); return true; } void -updateRandomSeed (PMELEE_STATE pMS, COUNT side, DWORD seed) { +updateRandomSeed (PMELEE_STATE pMS, COUNT side, DWORD seed) +{ TFB_SeedRandom (seed); (void) pMS; (void) side; @@ -3161,51 +3188,144 @@ updateRandomSeed (PMELEE_STATE pMS, COUNT side, DWORD seed) { // The remote player has done something which invalidates our confirmation. void -confirmationCancelled(PMELEE_STATE pMS, COUNT side) { +confirmationCancelled(PMELEE_STATE pMS, COUNT side) +{ + LockMutex (GraphicsLock); 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." + UnlockMutex (GraphicsLock); + if (pMS->InputFunc == DoConfirmSettings) pMS->InputFunc = DoMelee; } void connectedFeedback (PMELEE_STATE pMS, COUNT side) { + LockMutex (GraphicsLock); if (side == 0) DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 8)); // "Bottom player is connected." else DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 9)); // "Top player is connected." + UnlockMutex (GraphicsLock); PlayMenuSound (MENU_SOUND_INVOKED); (void) pMS; } +const char * +abortReasonString (NetplayResetReason 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_protocolError: + return GAME_STRING (NETMELEE_STRING_BASE + 27); + // "Connection aborted due to an internal protocol " + // "error." + } + + return NULL; + // Should not happen. +} + void -errorFeedback (PMELEE_STATE pMS, COUNT side) { +abortFeedback (COUNT side, NetplayAbortReason reason) +{ + const char *msg; + + msg = abortReasonString (reason); + if (msg != NULL) + { + LockMutex (GraphicsLock); + DrawMeleeStatusMessage (msg); + UnlockMutex (GraphicsLock); + } + + (void) side; +} + +const char * +resetReasonString (NetplayResetReason reason) +{ + switch (reason) + { + case ResetReason_unspecified: + return GAME_STRING (NETMELEE_STRING_BASE + 28); + // "Game aborted for an unspecified reason." + case ResetReason_syncLoss: + return GAME_STRING (NETMELEE_STRING_BASE + 29); + // "Game aborted due to loss of synchronisation." + case ResetReason_manualReset: + return GAME_STRING (NETMELEE_STRING_BASE + 30); + // "Game aborted by the remote player." + } + + return NULL; + // Should not happen. +} + +void +resetFeedback (COUNT side, NetplayResetReason reason, bool byRemote) +{ + const char *msg; + + GLOBAL (CurrentActivity) |= CHECK_ABORT; + + if (reason == ResetReason_manualReset && !byRemote) { + // No message needed, the player initiated the reset. + return; + } + + msg = resetReasonString (reason); + if (msg != NULL) + { + LockMutex (GraphicsLock); + DrawMeleeStatusMessage (msg); + UnlockMutex (GraphicsLock); + } + + (void) side; +} + +void +errorFeedback (PMELEE_STATE pMS, COUNT side) +{ + LockMutex (GraphicsLock); if (side == 0) DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 10)); // "Bottom player: connection failed." else DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 11)); // "Top player: connection failed." + UnlockMutex (GraphicsLock); (void) pMS; } void -closeFeedback (PMELEE_STATE pMS, COUNT side) { +closeFeedback (PMELEE_STATE pMS, COUNT side) +{ + LockMutex (GraphicsLock); if (side == 0) DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 12)); // "Bottom player: connection closed." else DrawMeleeStatusMessage (GAME_STRING (NETMELEE_STRING_BASE + 13)); // "Top player: connection closed." + UnlockMutex (GraphicsLock); (void) pMS; } + #endif /* NETPLAY */ diff --git a/sc2/src/sc2code/melee.h b/sc2/src/sc2code/melee.h index da9cf630a..fb4c04062 100644 --- a/sc2/src/sc2code/melee.h +++ b/sc2/src/sc2code/melee.h @@ -24,6 +24,8 @@ #include "libs/gfxlib.h" #include "libs/sndlib.h" #include "libs/reslib.h" +#include "netplay/packet.h" + // for NetplayAbortREason and NetplayResetReason. typedef struct melee_state MELEE_STATE; @@ -123,6 +125,8 @@ bool updateFleetShip (PMELEE_STATE pMS, COUNT side, COUNT index, BYTE ship); void updateRandomSeed (PMELEE_STATE pMS, COUNT side, DWORD seed); void confirmationCancelled(PMELEE_STATE pMS, COUNT side); void connectedFeedback (PMELEE_STATE pMS, COUNT side); +void abortFeedback (COUNT side, NetplayAbortReason reason); +void resetFeedback (COUNT side, NetplayResetReason reason, bool byRemote); void errorFeedback (PMELEE_STATE pMS, COUNT side); void closeFeedback (PMELEE_STATE pMS, COUNT side); diff --git a/sc2/src/sc2code/netplay/netconnection.c b/sc2/src/sc2code/netplay/netconnection.c index f5c65e3e6..28f4a4ea8 100644 --- a/sc2/src/sc2code/netplay/netconnection.c +++ b/sc2/src/sc2code/netplay/netconnection.c @@ -47,7 +47,8 @@ NetConnection * NetConnection_open(int player, const NetplayPeerOptions *options, NetConnection_ConnectCallback connectCallback, NetConnection_CloseCallback closeCallback, - NetConnection_ErrorCallback errorCallback, void *extra) { + NetConnection_ErrorCallback errorCallback, + NetConnection_DeleteCallback deleteCallback, void *extra) { NetConnection *conn; conn = malloc(sizeof (NetConnection)); @@ -62,8 +63,11 @@ NetConnection_open(int player, const NetplayPeerOptions *options, 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; @@ -80,6 +84,8 @@ NetConnection_open(int player, const NetplayPeerOptions *options, 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 @@ -105,8 +111,22 @@ NetConnection_open(int player, const NetplayPeerOptions *options, 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); free(conn); @@ -133,10 +153,6 @@ NetConnection_doClose(NetConnection *conn) { Netplay_doCloseCallback(conn); NetConnection_setState(conn, NetState_unconnected); - if (conn->stateData != NULL) { - NetConnectionStateData_release(conn->stateData); - conn->stateData = NULL; - } } // Called when the NetDescriptor is shut down. @@ -213,6 +229,23 @@ 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 diff --git a/sc2/src/sc2code/netplay/netconnection.h b/sc2/src/sc2code/netplay/netconnection.h index 4393012a7..8bae636ba 100644 --- a/sc2/src/sc2code/netplay/netconnection.h +++ b/sc2/src/sc2code/netplay/netconnection.h @@ -33,8 +33,10 @@ 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); #include "netstate.h" #include "netoptions.h" @@ -80,6 +82,11 @@ typedef struct { 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; @@ -108,6 +115,7 @@ typedef struct { * 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 @@ -135,6 +143,13 @@ struct NetConnection { // 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 @@ -145,7 +160,10 @@ struct NetConnection { #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; @@ -170,7 +188,8 @@ NetConnection *NetConnection_open(int player, const NetplayPeerOptions *options, NetConnection_ConnectCallback connectCallback, NetConnection_CloseCallback closeCallback, - NetConnection_ErrorCallback errorCallback, void *extra); + NetConnection_ErrorCallback errorCallback, + NetConnection_DeleteCallback deleteCallback, void *extra); void NetConnection_close(NetConnection *conn); bool NetConnection_isConnected(const NetConnection *conn); @@ -203,6 +222,12 @@ 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); + #endif /* _NETCONNECTION_H */ diff --git a/sc2/src/sc2code/netplay/netmelee.c b/sc2/src/sc2code/netplay/netmelee.c index 6335113b4..50405d707 100644 --- a/sc2/src/sc2code/netplay/netmelee.c +++ b/sc2/src/sc2code/netplay/netmelee.c @@ -28,6 +28,7 @@ #include "netplay/packetq.h" #include "netplay/proto/npconfirm.h" #include "netplay/proto/ready.h" +#include "netplay/proto/reset.h" #include "build.h" // for StarShipPlayer() @@ -65,8 +66,12 @@ closeAllConnections(void) { COUNT player; for (player = 0; player < NUM_PLAYERS; player++) - if (netConnections[player] != NULL) + { + NetConnection *conn = netConnections[player]; + + if (conn != NULL && NetConnection_isConnected(conn)) closePlayerNetworkConnection(player); + } } size_t @@ -100,6 +105,9 @@ netInputBlocking(uint32 timeoutMs) { timeoutMs = nextAlarmMs; NetManager_process(&timeoutMs); + // This may cause more packets to be queued, hence the + // flushPacketQueues(). + flushPacketQueues(); Alarm_process(); Callback_process(); @@ -183,6 +191,22 @@ connectionsLocalReady(NetConnection_ReadyCallback callback, void *arg) { } } +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 sendBattleInputConnections(BATTLE_INPUT_STATE input) { COUNT player; @@ -274,7 +298,7 @@ networkBattleInput(COUNT player, STARSHIPPTR StarShipPtr) { { // Connection aborted. GLOBAL(CurrentActivity) |= CHECK_ABORT; - return(BATTLE_INPUT_STATE) 0; + return (BATTLE_INPUT_STATE) 0; } #if 0 @@ -288,7 +312,7 @@ networkBattleInput(COUNT player, STARSHIPPTR StarShipPtr) { { // Connection aborted. GLOBAL(CurrentActivity) |= CHECK_ABORT; - return(BATTLE_INPUT_STATE) 0; + return (BATTLE_INPUT_STATE) 0; } } } @@ -297,6 +321,11 @@ networkBattleInput(COUNT player, STARSHIPPTR StarShipPtr) { return result; } +static void +deleteConnectionCallback(NetConnection *conn) { + removeNetConnection(NetConnection_getPlayerNr(conn)); +} + NetConnection * openPlayerNetworkConnection(COUNT player, void *extra) { NetConnection *conn; @@ -305,7 +334,8 @@ openPlayerNetworkConnection(COUNT player, void *extra) { conn = NetConnection_open(player, &netplayOptions.peer[player], NetMelee_connectCallback, - NetMelee_closeCallback, NetMelee_errorCallback, extra); + NetMelee_closeCallback, NetMelee_errorCallback, + deleteConnectionCallback, extra); addNetConnection(conn, player); return conn; @@ -321,7 +351,6 @@ closePlayerNetworkConnection(COUNT player) { assert(netConnections[player] != NULL); NetConnection_close(netConnections[player]); - removeNetConnection(player); } // If the callback function returns 'false', the function will immediately @@ -392,6 +421,30 @@ setStateConnections(NetState state) { (bool(*)(NetConnection *, void *)) setStateConnection, &state); } +static bool +sendAbortConnection(NetConnection *conn, const NetplayAbortReason *reason) { + sendAbort(conn, *reason); + return true; +} + +bool +sendAbortConnections(NetplayAbortReason reason) { + return forAllConnectedPlayers( + (bool(*)(NetConnection *, void *)) sendAbortConnection, &reason); +} + +static bool +resetConnection(NetConnection *conn, const NetplayResetReason *reason) { + Netplay_localReset(conn, *reason); + return true; +} + +bool +resetConnections(NetplayResetReason reason) { + return forAllConnectedPlayers( + (bool(*)(NetConnection *, void *)) resetConnection, &reason); +} + ///////////////////////////////////////////////////////////////////////////// typedef struct { @@ -422,6 +475,7 @@ localReadyConnections(NetConnection_ReadyCallback readyCallback, ///////////////////////////////////////////////////////////////////////////// +#define NETWORK_POLL_DELAY (ONE_SECOND / 24) typedef struct NegotiateReadyState NegotiateReadyState; struct NegotiateReadyState { @@ -437,7 +491,6 @@ struct NegotiateReadyState { static BOOLEAN negotiateReadyInputFunc(NegotiateReadyState *state) { -#define NETWORK_POLL_DELAY (ONE_SECOND / 24) 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, @@ -572,4 +625,108 @@ waitReady(NetConnection *conn) { //////////////////////////////////////////////////////////////////////////// +typedef struct WaitResetState WaitResetState; +struct WaitResetState { + // Common fields of INPUT_STATE_DESC, from which this structure + // "inherits". + BOOLEAN(*InputFunc)(PVOID pInputState); + COUNT MenuRepeatDelay; + + 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.MenuRepeatDelay = 0; + 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 to 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/sc2/src/sc2code/netplay/netmelee.h b/sc2/src/sc2code/netplay/netmelee.h index d6f3202d7..f75d5bb20 100644 --- a/sc2/src/sc2code/netplay/netmelee.h +++ b/sc2/src/sc2code/netplay/netmelee.h @@ -22,6 +22,7 @@ #include "netplay.h" #include "netinput.h" #include "netconnection.h" +#include "packetsenders.h" #include "../controls.h" // for BATTLE_INPUT_STATE @@ -44,6 +45,8 @@ void confirmConnections(void); void cancelConfirmations(void); void connectionsLocalReady(NetConnection_ReadyCallback callback, void *arg); +bool allConnected(void); + void sendBattleInputConnections(BATTLE_INPUT_STATE input); void sendChecksumConnections(uint32 frameNr, uint32 checksum); void initBattleStateDataConnections(void); @@ -59,6 +62,8 @@ bool forAllConnectedPlayers(ForAllCallback callback, void *arg); bool setupInputDelay(size_t localInputDelay); bool sendInputDelayConnections(size_t delay); bool setStateConnections(NetState state); +bool sendAbortConnections(NetplayAbortReason reason); +bool resetConnections(NetplayResetReason reason); bool localReadyConnections(NetConnection_ReadyCallback readyCallback, void *arg, bool notifyRemote); @@ -67,6 +72,9 @@ bool negotiateReady(NetConnection *conn, bool notifyRemote, bool negotiateReadyConnections(bool notifyRemote, NetState nextState); bool waitReady(NetConnection *conn); +bool waitReset(NetConnection *conn, NetState nextState); +bool waitResetConnections(NetState nextState); + #endif /* _NETMELEE_H */ diff --git a/sc2/src/sc2code/netplay/netrcv.c b/sc2/src/sc2code/netplay/netrcv.c index 64f19dfbd..c05331d99 100644 --- a/sc2/src/sc2code/netplay/netrcv.c +++ b/sc2/src/sc2code/netplay/netrcv.c @@ -121,8 +121,6 @@ dataReceivedMulti(NetConnection *conn, const uint8 *data, size_t len) { return processed; } -// Returns -1 on error (setting errno), or 0 if everything went -// ok, regardless of whether any packets were actually processed. void dataReadyCallback(NetDescriptor *nd) { NetConnection *conn = (NetConnection *) NetDescriptor_getExtra(nd); diff --git a/sc2/src/sc2code/netplay/netstate.c b/sc2/src/sc2code/netplay/netstate.c index 8dbc561e3..771fa7de4 100644 --- a/sc2/src/sc2code/netplay/netstate.c +++ b/sc2/src/sc2code/netplay/netstate.c @@ -25,7 +25,7 @@ #define DEFINE_NETSTATEDATA(stateName) \ { \ - /* .name = */ #stateName, \ + /* .name = */ #stateName, \ } NetStateData netStateData[] = { DEFINE_NETSTATEDATA(unconnected), diff --git a/sc2/src/sc2code/netplay/netstate.h b/sc2/src/sc2code/netplay/netstate.h index 056d6f96b..c74273b5b 100644 --- a/sc2/src/sc2code/netplay/netstate.h +++ b/sc2/src/sc2code/netplay/netstate.h @@ -24,6 +24,7 @@ typedef struct NetConnectionStateData NetConnectionStateData; +// State of a NetConnection. typedef enum { NetState_unconnected, /* No connection initiated */ NetState_connecting, /* Connection being setup */ diff --git a/sc2/src/sc2code/netplay/packet.c b/sc2/src/sc2code/netplay/packet.c index 81bb3408d..74025a95c 100644 --- a/sc2/src/sc2code/netplay/packet.c +++ b/sc2/src/sc2code/netplay/packet.c @@ -54,6 +54,8 @@ PacketTypeData packetTypeData[PACKET_NUM] = { DEFINE_PACKETDATA(BattleInput, false), DEFINE_PACKETDATA(FrameCount, false), DEFINE_PACKETDATA(Checksum, false), + DEFINE_PACKETDATA(Abort, false), + DEFINE_PACKETDATA(Reset, false), }; static inline void * @@ -253,5 +255,19 @@ Packet_Checksum_create(uint32 frameNr, uint32 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/sc2/src/sc2code/netplay/packet.h b/sc2/src/sc2code/netplay/packet.h index 36a12ad7f..bf025f23f 100644 --- a/sc2/src/sc2code/netplay/packet.h +++ b/sc2/src/sc2code/netplay/packet.h @@ -39,10 +39,26 @@ typedef enum PacketType { 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_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; #ifndef PACKET_H_STANDALONE #include "netconnection.h" @@ -227,6 +243,18 @@ typedef struct { 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); @@ -248,6 +276,8 @@ 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 diff --git a/sc2/src/sc2code/netplay/packethandlers.c b/sc2/src/sc2code/netplay/packethandlers.c index 3a21901b8..5dbe19c0c 100644 --- a/sc2/src/sc2code/netplay/packethandlers.c +++ b/sc2/src/sc2code/netplay/packethandlers.c @@ -28,12 +28,15 @@ #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 "pickmele.h" @@ -56,6 +59,13 @@ testNetState(bool condition, PacketType type) { 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 @@ -152,6 +162,13 @@ checkYourTurn(NetConnection *conn, PacketType type) { 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 @@ -172,6 +189,13 @@ PacketHandler_Fleet(NetConnection *conn, const Packet_Fleet *packet) { 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 @@ -223,6 +247,13 @@ PacketHandler_TeamName(NetConnection *conn, const Packet_TeamName *packet) { 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_TEAMNAME)) return -1; // errno is set @@ -262,6 +293,13 @@ handshakeComplete(NetConnection *conn) { 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 @@ -280,6 +318,13 @@ PacketHandler_Handshake0(NetConnection *conn, 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)) @@ -312,6 +357,13 @@ PacketHandler_Handshake1(NetConnection *conn, 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 @@ -328,6 +380,13 @@ PacketHandler_HandshakeCancel(NetConnection *conn, 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)) @@ -352,6 +411,13 @@ 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 @@ -370,6 +436,13 @@ PacketHandler_InputDelay(NetConnection *conn, 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 @@ -392,6 +465,13 @@ PacketHandler_SelectShip(NetConnection *conn, 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 @@ -413,6 +493,13 @@ PacketHandler_BattleInput(NetConnection *conn, 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)) @@ -434,6 +521,13 @@ PacketHandler_FrameCount(NetConnection *conn, 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 @@ -461,6 +555,13 @@ PacketHandler_Checksum(NetConnection *conn, const Packet_Checksum *packet) { 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 @@ -521,5 +622,25 @@ PacketHandler_Checksum(NetConnection *conn, const Packet_Checksum *packet) { return 0; } +int +PacketHandler_Abort(NetConnection *conn, const Packet_Abort *packet) { + abortFeedback(conn->player, 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/sc2/src/sc2code/netplay/packethandlers.h b/sc2/src/sc2code/netplay/packethandlers.h index b02b15967..c40f061c0 100644 --- a/sc2/src/sc2code/netplay/packethandlers.h +++ b/sc2/src/sc2code/netplay/packethandlers.h @@ -42,6 +42,8 @@ DECLARE_PACKETHANDLER(SelectShip); DECLARE_PACKETHANDLER(BattleInput); DECLARE_PACKETHANDLER(FrameCount); DECLARE_PACKETHANDLER(Checksum); +DECLARE_PACKETHANDLER(Abort); +DECLARE_PACKETHANDLER(Reset); #endif /* _PACKETHANDLERS_H */ diff --git a/sc2/src/sc2code/netplay/packetsenders.c b/sc2/src/sc2code/netplay/packetsenders.c index 151e4a143..a8001e46a 100644 --- a/sc2/src/sc2code/netplay/packetsenders.c +++ b/sc2/src/sc2code/netplay/packetsenders.c @@ -195,5 +195,21 @@ sendChecksum(NetConnection *conn, BattleFrameCounter frameNr, } #endif +void +sendAbort(NetConnection *conn, NetplayAbortReason reason) { + Packet_Abort *packet; + + packet = Packet_Abort_create((uint16) reason); + queuePacket(conn, (Packet *) packet, false); +} + +void +sendReset(NetConnection *conn, NetplayResetReason reason) { + Packet_Reset *packet; + + packet = Packet_Reset_create((uint16) reason); + queuePacket(conn, (Packet *) packet, false); +} + diff --git a/sc2/src/sc2code/netplay/packetsenders.h b/sc2/src/sc2code/netplay/packetsenders.h index 2fee1f49d..4ee298cb0 100644 --- a/sc2/src/sc2code/netplay/packetsenders.h +++ b/sc2/src/sc2code/netplay/packetsenders.h @@ -49,6 +49,8 @@ void sendSelectShip(NetConnection *conn, COUNT ship); void sendBattleInput(NetConnection *conn, BATTLE_INPUT_STATE input); void sendFrameCount(NetConnection *conn, uint32 frameCount); void sendChecksum(NetConnection *conn, uint32 frameNr, uint32 checksum); +void sendAbort(NetConnection *conn, NetplayAbortReason reason); +void sendReset(NetConnection *conn, NetplayResetReason reason); #endif /* _PACKETSENDERS_H */ diff --git a/sc2/src/sc2code/netplay/proto/Makeinfo b/sc2/src/sc2code/netplay/proto/Makeinfo index 3e0225bae..624ae1ccc 100644 --- a/sc2/src/sc2code/netplay/proto/Makeinfo +++ b/sc2/src/sc2code/netplay/proto/Makeinfo @@ -1,2 +1,2 @@ -uqm_CFILES="npconfirm.c ready.c" +uqm_CFILES="npconfirm.c ready.c reset.c" diff --git a/sc2/src/sc2code/netplay/proto/ready.c b/sc2/src/sc2code/netplay/proto/ready.c index 06bb1359c..e9f8c58d2 100644 --- a/sc2/src/sc2code/netplay/proto/ready.c +++ b/sc2/src/sc2code/netplay/proto/ready.c @@ -36,11 +36,11 @@ Netplay_bothReady(NetConnection *conn) { callback = conn->readyCallback; readyArg = conn->readyCallbackArg; - conn->readyCallback = NULL; + 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. - conn->readyCallbackArg = NULL; + callback(conn, readyArg); } @@ -94,12 +94,12 @@ Netplay_remoteReady(NetConnection *conn) { } bool -Netplay_isLocalReady(NetConnection *conn) { +Netplay_isLocalReady(const NetConnection *conn) { return conn->stateFlags.ready.localReady; } bool -Netplay_isRemoteReady(NetConnection *conn) { +Netplay_isRemoteReady(const NetConnection *conn) { return conn->stateFlags.ready.remoteReady; } diff --git a/sc2/src/sc2code/netplay/proto/ready.h b/sc2/src/sc2code/netplay/proto/ready.h index 384237656..296bbf7a6 100644 --- a/sc2/src/sc2code/netplay/proto/ready.h +++ b/sc2/src/sc2code/netplay/proto/ready.h @@ -24,8 +24,8 @@ bool Netplay_localReady(NetConnection *conn, NetConnection_ReadyCallback callback, void *arg, bool notifyRemote); bool Netplay_remoteReady(NetConnection *conn); -bool Netplay_isLocalReady(NetConnection *conn); -bool Netplay_isRemoteReady(NetConnection *conn); +bool Netplay_isLocalReady(const NetConnection *conn); +bool Netplay_isRemoteReady(const NetConnection *conn); #endif /* _READY_H */ diff --git a/sc2/src/sc2code/pickmele.c b/sc2/src/sc2code/pickmele.c index 0aa776ed9..797e1a87d 100644 --- a/sc2/src/sc2code/pickmele.c +++ b/sc2/src/sc2code/pickmele.c @@ -153,7 +153,7 @@ DoGetMelee (GETMELEE_STATE *gms) gms->remoteSelected = FALSE; #endif - // We determine upfront which ship would be chosen if the player + // 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. gms->randomIndex = (COUNT)TFB_Random () % gms->ships_left; @@ -165,8 +165,14 @@ DoGetMelee (GETMELEE_STATE *gms) SleepThread (ONE_SECOND / 120); #ifdef NETPLAY netInput (); + + if (!allConnected()) + goto aborted; #endif + if (GLOBAL (CurrentActivity) & CHECK_ABORT) + goto aborted; + if (PlayerInput[which_player] == ComputerInput) { /* TODO: Make this a frame-by-frame thing. This code is currently @@ -200,16 +206,7 @@ DoGetMelee (GETMELEE_STATE *gms) select = PulsedInputState.key[template][KEY_WEAPON]; } - if (GLOBAL (CurrentActivity) & CHECK_ABORT) - { - gms->hBattleShip = 0; - GLOBAL (CurrentActivity) &= ~CHECK_ABORT; -#ifdef NETPLAY - // TODO: send abort packet -#endif - done = true; - } - else if (select) + if (select) { if (gms->col == NUM_MELEE_COLS_ORIG) { @@ -225,13 +222,9 @@ DoGetMelee (GETMELEE_STATE *gms) } else { - // Exit + // Selected exit if (ConfirmExit ()) - { - gms->hBattleShip = 0; - GLOBAL (CurrentActivity) &= ~CHECK_ABORT; - done = TRUE; - } + goto aborted; } } else @@ -294,6 +287,14 @@ DoGetMelee (GETMELEE_STATE *gms) #endif return !done; + +aborted: +#ifdef NETPLAY + flushPacketQueues (); +#endif + gms->hBattleShip = 0; + GLOBAL (CurrentActivity) &= ~CHECK_ABORT; + return FALSE; } #ifdef NETPLAY @@ -495,7 +496,10 @@ GetMeleeStarShip (STARSHIPPTR LastStarShipPtr, COUNT which_player) SetFlashRect (NULL_PTR, (FRAME)0); if (gmstate.hBattleShip == 0) + { + // Aborting. GLOBAL (CurrentActivity) &= ~IN_BATTLE; + } else { StarShipPtr = @@ -513,14 +517,12 @@ GetMeleeStarShip (STARSHIPPTR LastStarShipPtr, COUNT which_player) #ifdef NETPLAY { NetConnection *conn = netConnections[which_player]; - if (conn != NULL) + if (conn != NULL && NetConnection_isConnected(conn)) { BattleStateData *battleStateData; battleStateData = (BattleStateData *) NetConnection_getStateData(conn); battleStateData->getMeleeState = NULL; - if (gmstate.hBattleShip == 0) - NetConnection_setState(conn, NetState_inSetup); } } #endif diff --git a/sc2/src/sc2code/pickship.c b/sc2/src/sc2code/pickship.c index f855b65d4..6ea265b3b 100644 --- a/sc2/src/sc2code/pickship.c +++ b/sc2/src/sc2code/pickship.c @@ -393,8 +393,7 @@ GetEncounterStarShip (STARSHIPPTR LastStarShipPtr, COUNT which_player) if (LastStarShipPtr->special_counter == 0) /* died in the line of duty */ GLOBAL_SIS (CrewEnlisted) = (COUNT)~0; - else if (GLOBAL_SIS (FuelOnBoard) > - RUN_AWAY_FUEL_COST) + else if (GLOBAL_SIS (FuelOnBoard) > RUN_AWAY_FUEL_COST) GLOBAL_SIS (FuelOnBoard) -= RUN_AWAY_FUEL_COST; else GLOBAL_SIS (FuelOnBoard) = 0; diff --git a/sc2/src/sc2code/ship.c b/sc2/src/sc2code/ship.c index 5d7216f42..bc73f9b25 100644 --- a/sc2/src/sc2code/ship.c +++ b/sc2/src/sc2code/ship.c @@ -387,8 +387,8 @@ spawn_ship (STARSHIPPTR StarShipPtr) RDPtr->ship_info.var2 = (BYTE)StarShipPtr->ShipFacing; StarShipPtr->ship_input_state = 0; - StarShipPtr->cur_status_flags = - StarShipPtr->old_status_flags = 0; + StarShipPtr->cur_status_flags = 0; + StarShipPtr->old_status_flags = 0; if (LOBYTE (GLOBAL (CurrentActivity)) == IN_ENCOUNTER || LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE) @@ -420,6 +420,7 @@ spawn_ship (STARSHIPPTR StarShipPtr) StarShipPtr->hShip = hShip; if (StarShipPtr->hShip != 0) { + // Construct an ELEMENT for the STARSHIP ELEMENTPTR ShipElementPtr; LockElement (hShip, &ShipElementPtr); @@ -437,6 +438,7 @@ spawn_ship (STARSHIPPTR StarShipPtr) if ((ShipElementPtr->state_flags & BAD_GUY) && LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE) { + // This is the Sa-Matra StarShipPtr->ShipFacing = 0; ShipElementPtr->current.image.frame = SetAbsFrameIndex (RDPtr->ship_data.ship[0], @@ -452,7 +454,8 @@ spawn_ship (STARSHIPPTR StarShipPtr) { COUNT facing; - if ((facing = LOWORD (GLOBAL (ShipStamp.frame))) > 0) + facing = LOWORD (GLOBAL (ShipStamp.frame)); + if (facing > 0) --facing; GLOBAL (ShipStamp.frame) = (FRAME)MAKE_DWORD ( diff --git a/sc2/src/sc2code/ships/syreen/syreen.c b/sc2/src/sc2code/ships/syreen/syreen.c index ff874ceb8..2f8320e40 100644 --- a/sc2/src/sc2code/ships/syreen/syreen.c +++ b/sc2/src/sc2code/ships/syreen/syreen.c @@ -142,7 +142,8 @@ spawn_crew (PELEMENT ElementPtr) { HELEMENT hCrew; - if ((hCrew = AllocElement ()) != 0) + hCrew = AllocElement (); + if (hCrew != 0) { ELEMENTPTR CrewPtr; @@ -151,9 +152,7 @@ spawn_crew (PELEMENT ElementPtr) CrewPtr->state_flags = APPEARING | NONSOLID | FINITE_LIFE | (ElementPtr->state_flags & (GOOD_GUY | BAD_GUY)); CrewPtr->life_span = 0; - { - CrewPtr->death_func = spawn_crew; - } + CrewPtr->death_func = spawn_crew; CrewPtr->pParent = ElementPtr->pParent; CrewPtr->hTarget = 0; UnlockElement (hCrew); @@ -181,12 +180,13 @@ spawn_crew (PELEMENT ElementPtr) SIZE dx, dy; DWORD d_squared; - if ((dx = ObjPtr->next.location.x - - ElementPtr->next.location.x) < 0) + dx = ObjPtr->next.location.x - ElementPtr->next.location.x; + if (dx < 0) dx = -dx; - if ((dy = ObjPtr->next.location.y - - ElementPtr->next.location.y) < 0) + dy = ObjPtr->next.location.y - ElementPtr->next.location.y; + if (dy < 0) dy = -dy; + dx = WORLD_TO_DISPLAY (dx); dy = WORLD_TO_DISPLAY (dy); #define ABANDONER_RANGE 208 /* originally SPACE_HEIGHT */ diff --git a/sc2/src/sc2code/tactrans.c b/sc2/src/sc2code/tactrans.c index b1610dfcf..66abd2def 100644 --- a/sc2/src/sc2code/tactrans.c +++ b/sc2/src/sc2code/tactrans.c @@ -25,6 +25,7 @@ # include "netplay/netmisc.h" # include "netplay/notify.h" # include "netplay/proto/ready.h" +# include "netplay/packet.h" # include "netplay/packetq.h" #endif #include "races.h" @@ -180,7 +181,7 @@ readyForBattleEndPlayer (NetConnection *conn, void *arg) #endif static inline bool -readyForBattleEnd (void) +readyForBattleEnd (COUNT side) { #ifndef NETPLAY #if DEMO_MODE @@ -194,9 +195,26 @@ readyForBattleEnd (void) if (PLRPlaying ((MUSIC_REF)~0)) return false; + // We can only handle one dead ship at a time. So 'deadSide' is set + // to the side we're handling now. (COUNT)~0 means we're not handling + // any side yet. + if (currentDeadSide == (COUNT)~0) + { + // Not handling any side yet. + currentDeadSide = side; + } + else if (side != currentDeadSide) + { + // We're handing another side at the moment. + return false; + } + if (!forAllConnectedPlayers (readyForBattleEndPlayer, NULL)) return false; + currentDeadSide = (COUNT)~0; + // Another side may be handled. + return true; #endif /* defined (NETPLAY) */ } @@ -239,9 +257,9 @@ new_ship (PELEMENT DeadShipPtr) DeadShipPtr->turn_wait = (BYTE)( DeadShipPtr->state_flags & (GOOD_GUY | BAD_GUY)); // DeadShipPtr->turn_wait is abused to store which - // side this element is for, probably because this - // information will be lost from state_flags (why is this - // necessary?). + // side this element is for, because this information + // will be lost from state_flags when the element is + // set up for deletion below. for (hElement = GetHeadElement (); hElement; hElement = hSuccElement) { ELEMENTPTR ElementPtr; @@ -250,14 +268,20 @@ new_ship (PELEMENT DeadShipPtr) LockElement (hElement, &ElementPtr); hSuccElement = GetSuccElement (ElementPtr); GetElementStarShip (ElementPtr, &StarShipPtr); + // Get the STARSHIP that this ELEMENT belongs to. + if (StarShipPtr == DeadStarShipPtr) { + // This element belongs to the dead ship; it may be the + // ship's own element. SetElementStarShip (ElementPtr, 0); if (!(ElementPtr->state_flags & CREW_OBJECT) || ElementPtr->preprocess_func != crew_preprocess) { - SetPrimType (&DisplayArray[ElementPtr->PrimIndex], NO_PRIM); + // Set the element up for deletion. + SetPrimType (&DisplayArray[ElementPtr->PrimIndex], + NO_PRIM); ElementPtr->life_span = 0; ElementPtr->state_flags = NONSOLID | DISAPPEARING | FINITE_LIFE; @@ -271,6 +295,7 @@ new_ship (PELEMENT DeadShipPtr) if (StarShipPtr && (StarShipPtr->cur_status_flags & PLAY_VICTORY_DITTY)) { + // StarShipPtr points to the surviving ship. MusicStarted = TRUE; PlayMusic ((MUSIC_REF)StarShipPtr->RaceDescPtr-> ship_data.victory_ditty, FALSE, 3); @@ -287,7 +312,8 @@ new_ship (PELEMENT DeadShipPtr) SetElementStarShip (DeadShipPtr, DeadStarShipPtr); } - if (DeadShipPtr->life_span || !readyForBattleEnd ()) + if (DeadShipPtr->life_span || !readyForBattleEnd ( + WHICH_SIDE (DeadShipPtr->turn_wait))) { DeadShipPtr->state_flags &= ~DISAPPEARING; ++DeadShipPtr->life_span; @@ -328,7 +354,7 @@ UnbatchGraphics (); #endif /* NETPLAY */ if (GetNextStarShip (DeadStarShipPtr, - WHICH_SIDE (DeadShipPtr->turn_wait)) && RestartMusic) + WHICH_SIDE (DeadShipPtr->turn_wait))) { #ifdef NETPLAY { @@ -343,11 +369,22 @@ UnbatchGraphics (); } } #endif - BattleSong (TRUE); + if (RestartMusic) + BattleSong (TRUE); } else if (LOBYTE (battle_counter) == 0 || HIBYTE (battle_counter) == 0) + { + // One player is out of ships. The battle is over. GLOBAL (CurrentActivity) &= ~IN_BATTLE; + } +#ifdef NETPLAY + else + { + // Battle has been aborted. + GLOBAL (CurrentActivity) |= CHECK_ABORT; + } +#endif #ifdef NETPLAY // Turn_wait was abused to store the side this element was on.