static void readyToEndCallback (NetConnection *conn, void *arg) { // This callback function gets called from inside the function that // updates the frame counter, but this is not a problem as the // ending frame count will at least be 1 greater than the current // frame count. BattleStateData *battleStateData; battleStateData = (BattleStateData *) NetConnection_getStateData(conn); #ifdef NETPLAY_DEBUG fprintf (stderr, "Both sides are ready to end the battle; starting " "end-of-battle synchronisation.\n"); #endif NetConnection_setState (conn, NetState_endingBattle); if (battleFrameCount + 1 > battleStateData->endFrameCount) battleStateData->endFrameCount = battleFrameCount + 1; Netplay_Notify_frameCount (conn, battleFrameCount + 1); // The +1 is to ensure that after the remote side receives the // frame count it will still receive one more frame data packet, // so it will know in advance when the last frame data packet // will come so it won't block. It also ensures that the // local frame counter won't go past the sent number, which // could happen when the function triggering the call to this // function is the frame update function which might update // the frame counter one more time. flushPacketQueue (conn); #ifdef NETPLAY_DEBUG fprintf (stderr, "NETPLAY: [%d] ==> Sent battleFrameCount %d.\n", NetConnection_getPlayerNr(conn), battleFrameCount + 1); #endif Netplay_localReady(conn, readyToEnd2Callback, NULL, false); (void) arg; }
/* * When one player's ship dies, there's a delay before the next ship * can be chosen. This time depends on the time the ditty is playing * and may differ for each side. * To synchronise the time, the following protocol is followed: * 1. (NetState_inBattle) The Ready protocol is used to let either * party know when they're ready to stop the battle. * 2. (NetState_endingBattle) Each party sends the frame number of when * it wants to end the battle, and continues until that point, where * it waits until it has received the frame number of the other party. * 3. After a player has both sent and received a frame count, the * simulation continues for each party, until the maximum of both * frame counts has been achieved. * 4. The Ready protocol is used to let each side signal that it has * reached the target frame count. * 5. The battle ends. */ static bool readyForBattleEndPlayer (NetConnection *conn) { BattleStateData *battleStateData; battleStateData = (BattleStateData *) NetConnection_getStateData(conn); if (NetConnection_getState (conn) == NetState_interBattle || NetConnection_getState (conn) == NetState_inSetup) { // This connection is already ready. The entire synchronisation // protocol has already been done for this connection. return true; } if (NetConnection_getState (conn) == NetState_inBattle) { if (Netplay_isLocalReady(conn)) { // We've already sent notice that we are ready, but we're // still waiting for the other side to say it's ready too. return false; } // We haven't yet told the other side we're ready. We do so now. Netplay_localReady (conn, readyToEndCallback, NULL, true); // This may set the state to endingBattle. if (NetConnection_getState (conn) == NetState_inBattle) return false; } assert (NetConnection_getState (conn) == NetState_endingBattle || NetConnection_getState (conn) == NetState_endingBattle2); // Keep the simulation going as long as the target frame count // hasn't been reached yet. Note that if the connection state is // NetState_endingBattle, then we haven't yet received the // remote frame count, so the target frame count may still rise. if (battleFrameCount < battleStateData->endFrameCount) return false; if (NetConnection_getState (conn) == NetState_endingBattle) { // We have reached the target frame count, but we don't know // the remote target frame count yet. So we wait until it has // come in. waitReady (conn); // TODO: check whether all connections are still connected. assert (NetConnection_getState (conn) == NetState_endingBattle2); // Continue the simulation if the battleFrameCount has gone up. if (battleFrameCount < battleStateData->endFrameCount) return false; } // We are ready and wait for the other party to become ready too. negotiateReady (conn, true, NetState_interBattle); return true; }
// 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; }