aboutsummaryrefslogtreecommitdiff
path: root/src/parsec/net_peer.cpp
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@gmail.com>2026-08-24 11:16:07 +0200
committerFelix Morgner <felix.morgner@gmail.com>2026-08-24 11:16:07 +0200
commitc068f22329d5cc722622a2183bbb22eef2093df7 (patch)
tree12d56c1aede67988a55e241364606bfbb4dba933 /src/parsec/net_peer.cpp
downloadopenparsec-c068f22329d5cc722622a2183bbb22eef2093df7.tar.xz
openparsec-c068f22329d5cc722622a2183bbb22eef2093df7.zip
initial importHEADmain
Diffstat (limited to 'src/parsec/net_peer.cpp')
-rw-r--r--src/parsec/net_peer.cpp1457
1 files changed, 1457 insertions, 0 deletions
diff --git a/src/parsec/net_peer.cpp b/src/parsec/net_peer.cpp
new file mode 100644
index 0000000..6e3ec2b
--- /dev/null
+++ b/src/parsec/net_peer.cpp
@@ -0,0 +1,1457 @@
+/*
+ * PARSEC - Peer-to-Peer Protocol
+ *
+ * $Author: uberlinuxguy $ - $Date: 2004/09/26 03:43:39 $
+ *
+ * Orginally written by:
+ * Copyright (c) Clemens Beer <cbx@parsec.org> 2002
+ * Copyright (c) Markus Hadwiger <msh@parsec.org> 1996-1999
+ *
+ * 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
+ */
+
+// C library
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// compilation flags/debug support
+#include "config.h"
+#include "debug.h"
+
+// general definitions
+#include "general.h"
+#include "objstruc.h"
+#include "od_class.h"
+
+// global externals
+#include "globals.h"
+
+// subsystem headers
+#include "aud_defs.h"
+#include "net_defs.h"
+#include "sys_defs.h"
+
+// network code config
+#include "net_conf.h"
+
+// local module header
+#include "net_peer.h"
+
+// proprietary module headers
+#include "con_aux.h"
+#include "con_ext.h"
+#include "e_demo.h"
+#include "e_record.h"
+#include "g_supp.h"
+#include "net_conn.h"
+#include "net_game.h"
+#include "net_game_peer.h"
+#include "net_rmev.h"
+#include "net_rmev_peer.h"
+#include "obj_creg.h"
+#include "obj_ctrl.h"
+#include "g_sfx.h"
+#include "sys_bind.h"
+
+
+// connect timing
+
+#define TIMEOUT_CONNECT 10 // number of connect tries
+#define RETRYWAIT_CONNECT 200 // time to wait until next try
+
+
+// slot request timing
+
+#define TIMEOUT_SLOTREQUEST 20 // number of slot-request tries
+#define RETRYWAIT_SLOTREQUEST 400 // time to wait until next try
+
+
+// quantum for master timeoffset
+
+#define TIMEOFFSET_QUANTUM 100 // time until next node acts as master
+
+
+// connect-loop exit codes
+
+#define EXIT_TO_CONNECT_LOOP 0 // stay in connect-loop until timeout
+#define EXIT_TO_GAME_LOOP 1 // master found -> protocol done
+#define EXIT_TO_SLOTREQ_LOOP 2 // host is slave -> request slot
+#define EXIT_TO_CONNECT_REFUSED 3 // connection refused
+
+
+
+// address of temporary master
+node_t master_node;
+
+// exit code of connect-loop
+int connect_loop_exit;
+
+
+// buffer that can be used to prepare NetPacket for sending --------------------
+//
+static char GamePacketBuffer[ NET_MAX_NETPACKET_INTERNAL_LEN ];
+static NetPacket_PEER* GamePacket = (NetPacket_PEER*) GamePacketBuffer;
+
+
+// identification of slave for DetermineSlave() -------------------------------
+//
+#define SLAVE_LOCALNODE 0
+#define SLAVE_REMOTENODE 1
+
+
+// determine node's slave property depending on ipx address -------------------
+//
+PRIVATE
+int DetermineSlave( node_t *localplayer, node_t *remoteplayer )
+{
+ ASSERT( localplayer != NULL );
+ ASSERT( remoteplayer != NULL );
+
+ //NOTE:
+ // this function determines for two nodes who is
+ // a slave in any case. this does not necessarily
+ // mean the other node is master; this is indeed
+ // not the case if there is another node with still
+ // higher priority. then both nodes will be slaves.
+
+ // compare node addresses
+ int cmpcode = NETs_CompareNodes( localplayer, remoteplayer );
+
+ // enslave player with lower ethernet node address
+ if ( cmpcode == NODECMP_LESSTHAN )
+ return SLAVE_LOCALNODE;
+ if ( cmpcode == NODECMP_GREATERTHAN )
+ return SLAVE_REMOTENODE;
+
+ MSGOUT( "two identical node addresses encountered." );
+ ASSERT(0);
+
+ return SLAVE_REMOTENODE;
+}
+
+
+// create player in already allocated slot ------------------------------------
+//
+PRIVATE
+void CreateRemotePlayer( RE_PlayerList *re_playerlist, int id )
+{
+ ASSERT( re_playerlist != NULL );
+ ASSERT( re_playerlist->RE_Type == RE_PLAYERLIST );
+ ASSERT( ( id >= 0 ) && ( id < CurMaxPlayers ) );
+
+ int listnum = id >> 2;
+
+ // skip empty slots
+ if ( re_playerlist[ listnum ].Status[ id % 4 ] == PLAYER_INACTIVE ) {
+ return;
+ }
+
+ // prevent creation of local player
+ if ( id == LocalPlayerId ) {
+ return;
+ }
+
+ // make function idempotent by creating each player only once
+ if ( Player_Status[ id ] != PLAYER_INACTIVE ) {
+ DBGTXT( MSGOUT( "preempting redundant player creation: %d.", id ); );
+ return;
+ }
+
+ // store status and init alive counter
+ Player_Status[ id ] = re_playerlist[ listnum ].Status[ id % 4 ];
+ Player_AliveCounter[ id ] = MAX_ALIVE_COUNTER;
+
+ // reset up-to-date flag and killstats for newly assigned slot
+ Player_UpToDate[ id ] = FALSE;
+ Player_KillStat[ id ] = 0;
+
+ // reset msg ids for newly assigned slot
+ Player_LastMsgId[ id ] = 0;
+ Player_LastUpdateGameStateMsgId[ id ] = 0;
+
+ // store address and name
+ NETs_ResolveNode( &Player_Node[ id ], &re_playerlist[ listnum ].AddressTable[ id % 4 ] );
+ CopyRemoteName( Player_Name[ id ], re_playerlist[ listnum ].NameTable[ id % 4 ] );
+
+ // if player is joined store ship info and create ship
+ if ( Player_Status[ id ] == PLAYER_JOINED ) {
+
+ // try to resolve ship index
+ dword shipindex = re_playerlist[ listnum ].ShipInfoTable[ id % 4 ].ShipIndex;
+ dword shipclass = CLASS_ID_INVALID;
+ if ( shipindex < (dword)NumShipClasses ) {
+ shipclass = ShipClasses[ shipindex ];
+ }
+
+ // take default ship if the one remotely sent not available
+ if ( shipclass == CLASS_ID_INVALID ) {
+ shipclass = SHIP_CLASS_1;
+ }
+
+ pXmatrx objposition = re_playerlist[ listnum ].ShipInfoTable[ id % 4 ].ObjPosition;
+ GenObject *objectpo = CreateObject( shipclass, objposition );
+ ASSERT( objectpo != NULL );
+ objectpo->HostObjNumber = ShipHostObjId( id );
+ Player_Ship[ id ] = objectpo;
+ Player_ShipId[ id ] = objectpo->ObjectNumber;
+ }
+
+
+ DBGTXT( MSGOUT( "created player: id %d, status %d.", id, Player_Status[ id ] ); );
+ ADXTXT( NETs_PrintNode( &Player_Node[ id ] ); );
+}
+
+
+// broadcast connect request --------------------------------------------------
+//
+PRIVATE
+void ConnectBroadcast()
+{
+ node_t node;
+ NETs_SetBroadcastAddress( &node );
+
+ // fill game data header
+ NETs_StdGameHeader( PKTP_CONNECT, (NetPacket*)GamePacket );
+ ASSERT( LocalPlayerId == PLAYERID_ANONYMOUS );
+
+ // connect packets should always be sent anonymous
+ GamePacket->SendPlayerId = PLAYERID_ANONYMOUS;
+
+ // create single remote event: slot request queue
+ NET_RmEvSingleConnectQueue( GamePacket );
+
+ DBGTXT( MSGOUT( "broadcasting connect for universe %d (pid=%d).", MyUniverse, GamePacket->MessageId ); );
+ NETs_AuxSendPacket( (NetPacket*)GamePacket, &node );
+}
+
+
+// store info about real destination into snoop packet ------------------------
+//
+INLINE
+void StoreRealDestination( NetPacket_PEER* gamepacket, int qpos, int snooper )
+{
+ ASSERT( gamepacket != NULL );
+ ASSERT( ( qpos >= 0 ) && ( qpos < NumSlotRequests ) );
+ ASSERT( ( snooper >= 0 ) && ( snooper < CurMaxPlayers ) );
+
+ RE_PlayerList *re_playerlist = (RE_PlayerList *) &gamepacket->RE_List;
+
+ ASSERT( re_playerlist->RE_Type == RE_PLAYERLIST );
+
+ //NOTE:
+ // the address and name of the original player are
+ // stored into the entries for the snooping player.
+ // they are the only ones where it is certain that
+ // the snooper doesn't need them. if a player is
+ // refused a connection, there is no other place
+ // where his node address could be stored in order
+ // to be transmitted to the snooper.
+
+ CopyRemoteNode( re_playerlist[ snooper >> 2 ].AddressTable[ snooper % 4 ],
+ SlotReqQueue[ qpos ].node );
+ CopyRemoteName( re_playerlist[ snooper >> 2 ].NameTable[ snooper % 4 ],
+ SlotReqQueue[ qpos ].name );
+}
+
+
+// send connect reply to remote player (may be negative) ----------------------
+//
+PRIVATE
+void SendConnectReply( int qpos, int slotid )
+{
+ ASSERT( ( qpos >= 0 ) && ( qpos < NumSlotRequests ) );
+ ASSERT( slotid < CurMaxPlayers );
+
+ // filter out special slot requests
+ if ( slotid < SLOTID_CONNECT_REFUSED )
+ return;
+
+ DBGTXT( MSGOUT( "sending connect reply to: %s (id %d).", SlotReqQueue[ qpos ].name, slotid ); );
+ DBGTXT( NETs_PrintNode( &SlotReqQueue[ qpos ].node ); );
+
+ // store id remote player got assigned
+ GamePacket->DestPlayerId = slotid;
+
+ // send connect reply to new remote player
+ NETs_AuxSendPacket( (NetPacket*)GamePacket, &SlotReqQueue[ qpos ].node );
+
+ // send also to all other remote players
+ // so that they can snoop the reply.
+ for ( int id = 0; id < CurMaxPlayers; id++ ) {
+ if ( ( id != slotid ) && REMOTE_PLAYER_ACTIVE( id ) ) {
+
+ DBGTXT( MSGOUT( "sending snoop-cc to id %d.", id ); );
+
+ //NOTE:
+ // the DestPlayerId field in the packet is not
+ // changed here. that is, it contains always
+ // the value of the original destination.
+ // this denotes snooped packets and allows for
+ // easy determination of the actual receiver id.
+
+ StoreRealDestination( GamePacket, qpos, id );
+ NETs_AuxSendPacket( (NetPacket*)GamePacket, &Player_Node[ id ] );
+ }
+ }
+}
+
+
+// send connect reply to all slot request queue members -----------------------
+//
+PRIVATE
+void ConnectQueue()
+{
+ //NOTE:
+ // connect replies are sent to all queue members.
+ // to ensure that packet-drop has no significant
+ // impact on the behavior, queue entries are not
+ // deleted as long as it has not been verified
+ // that the connect reply has been received by
+ // the remote player. that is, connect replies
+ // may be sent more than once to the same player.
+
+ int dosend[ MAX_SLOT_REQUESTS ] = {FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE};
+ ASSERT( NumSlotRequests <= MAX_SLOT_REQUESTS );
+
+ // process all queued requests (try to allocate
+ // slots and schedule replies if necessary)
+ int qpos = 0;
+ for ( qpos = 0; qpos < NumSlotRequests; qpos++ ) {
+
+ // assume no reply necessary
+ dosend[ qpos ] = FALSE;
+
+ int slotid = SlotReqQueue[ qpos ].slotid;
+
+ // skip special slots
+ if ( ( slotid == SLOTID_DELETED ) || ( slotid == SLOTID_LOCALHOST ) )
+ continue;
+
+ //NOTE:
+ // the timetag is used to determine the time when
+ // a reply should be sent by the local host. this
+ // is done for temporary master election purposes.
+ // the timetag may also be used to contain additional
+ // flags when the actual time is not needed.
+
+ int timetag = SlotReqQueue[ qpos ].timetag;
+ ASSERT( timetag != TIMETAG_LOCALHOST );
+
+ // check timetag
+ refframe_t curtime = SYSs_GetRefFrameCount();
+ if ( ( timetag != TIMETAG_REPLYNOW ) && ( curtime < timetag ) ) {
+ DBGTXT( MSGOUT( "refraining from acting as master (id %d).", slotid ); );
+ continue;
+ }
+
+ // only register remote players for which
+ // this has not already been done
+ if ( slotid == SLOTID_NOT_ASSIGNED ) {
+
+ node_t node;
+ NETs_ResolveNode( &node, &SlotReqQueue[ qpos ].node );
+ char *name = SlotReqQueue[ qpos ].name;
+
+ // try to register remote player
+ slotid = NET_AcquireRemoteSlot( &node );
+ NET_RegisterRemotePlayer( slotid, &node, name );
+ SlotReqQueue[ qpos ].slotid = slotid;
+
+ DBGTXT( MSGOUT( "registered %s with id %d.", name, slotid ); );
+
+ } else {
+
+ DBGTXT( MSGOUT( "skipping registration (id %d).", slotid ); );
+ }
+
+ // schedule reply
+ dosend[ qpos ] = TRUE;
+ }
+
+ // avoid increasing messageid if nothing will be sent
+ int pending = FALSE;
+ for ( qpos = 0; qpos < NumSlotRequests; qpos++ ) {
+ if ( dosend[ qpos ] ) {
+ pending = TRUE;
+ }
+ }
+ if ( !pending ) {
+ return;
+ }
+
+ // fill game data header
+ NETs_StdGameHeader( PKTP_CONNECT_REPLY, (NetPacket*)GamePacket );
+
+ // create single remote event: player table
+ NET_RmEvSinglePlayerTable( GamePacket );
+
+ // send replies to scheduled nodes
+ for ( qpos = 0; qpos < NumSlotRequests; qpos++ ) {
+ if ( dosend[ qpos ] ) {
+ SendConnectReply( qpos, SlotReqQueue[ qpos ].slotid );
+ }
+ }
+}
+
+
+// sent slot request to temporary master --------------------------------------
+//
+PRIVATE
+void SendSlotRequest( node_t *node )
+{
+ ASSERT( node != NULL );
+
+ DBGTXT( MSGOUT( "sending slot_request to host:" ); );
+ DBGTXT( NETs_PrintNode( node ); );
+
+ // fill game data header
+ NETs_StdGameHeader( PKTP_SLOT_REQUEST, (NetPacket*)GamePacket );
+ ASSERT( LocalPlayerId == PLAYERID_ANONYMOUS );
+
+ // create single remote event: slot request queue
+ NET_RmEvSingleConnectQueue( GamePacket );
+
+ NETs_AuxSendPacket( (NetPacket*)GamePacket, node );
+}
+
+
+// subdue slave ---------------------------------------------------------------
+//
+PRIVATE
+void SendSubdueCommand( node_t *node )
+{
+ ASSERT( node != NULL );
+
+ DBGTXT( MSGOUT( "subduing slave:" ); );
+ DBGTXT( NETs_PrintNode( node ); );
+
+ // fill game data header
+ NETs_StdGameHeader( PKTP_SUBDUE_SLAVE, (NetPacket*)GamePacket );
+ ASSERT( LocalPlayerId == PLAYERID_ANONYMOUS );
+
+ NETs_AuxSendPacket( (NetPacket*)GamePacket, node );
+}
+
+
+// process connect request received in connect-loop ---------------------------
+//
+PRIVATE
+void ProcessConnect( NetPacket_PEER* gamepacket, int bufid )
+{
+ ASSERT( gamepacket != NULL );
+ ASSERT( gamepacket->Command == PKTP_CONNECT );
+
+ node_t *node = NETs_GetSender( bufid );
+
+ DBGTXT( MSGOUT( "processing connect request:" ); );
+ DBGTXT( NETs_PrintNode( node ); );
+
+ if ( DetermineSlave( &LocalNode, node ) == SLAVE_REMOTENODE ) {
+
+ // local host is now temporary master
+
+ NET_MergeSlotRequests( gamepacket, TIMETAG_REPLYNOW );
+ SendSubdueCommand( node );
+
+ } else {
+
+ // local host is now slave
+
+ // remember address of sender (who is temporary master for local host)
+ CopyRemoteNode( master_node, *node );
+
+ // exit connect-loop to slotreq-loop
+ connect_loop_exit = EXIT_TO_SLOTREQ_LOOP;
+ }
+}
+
+
+// process reply to connect request -------------------------------------------
+//
+PRIVATE
+int ProcessConnectReply( NetPacket_PEER* gamepacket, int bufid )
+{
+ //NOTE:
+ // this function is called to process a PKTP_CONNECT_REPLY
+ // received in either the connect-loop or the slotreq-loop.
+
+ ASSERT( gamepacket != NULL );
+ ASSERT( gamepacket->Command == PKTP_CONNECT_REPLY );
+
+ // retrieve sender's id
+ int senderid = gamepacket->SendPlayerId;
+ ASSERT( ( senderid >= 0 ) && ( senderid < CurMaxPlayers ) );
+
+ // filter duplicates
+ if ( Player_Status[ senderid ] != PLAYER_INACTIVE ) {
+ DBGTXT( MSGOUT( "got redundant connect reply from id %d (startup).", senderid ); );
+ return TRUE;
+ }
+
+ DBGTXT( MSGOUT( "processing connect reply from id %d (pid=%d).", senderid, gamepacket->MessageId ); );
+ ADXTXT( NETs_PrintNode( NETs_GetSender( bufid ) ); );
+
+ // fetch player id and number of remote players (local player included)
+ int playerid = gamepacket->DestPlayerId;
+ NumRemPlayers = gamepacket->NumPlayers;
+ ASSERT( NumRemPlayers > 1 );
+
+ // check whether connect accepted (slot available)
+ if ( playerid != SLOTID_CONNECT_REFUSED ) {
+
+ // set local id
+ LocalPlayerId = gamepacket->DestPlayerId;
+
+ DBGTXT( MSGOUT( "connection accepted: got id %d.", LocalPlayerId ); );
+
+ RE_PlayerList *re_playerlist = (RE_PlayerList *) &gamepacket->RE_List;
+
+ ASSERT( re_playerlist->RE_Type == RE_PLAYERLIST );
+
+ // sync critical values
+ dword killlimit = re_playerlist->SyncValKillLimit;
+ dword nebulaid = re_playerlist->SyncValNebulaId;
+
+ if ( ( nebulaid >= 2 ) && ( nebulaid <= 5 ) ) {
+ AUX_KILL_LIMIT_FOR_GAME_END = killlimit;
+ AUXDATA_BACKGROUND_NEBULA_ID = nebulaid;
+ }
+
+ // process player table
+ for ( int id = 0; id < CurMaxPlayers; id++ ) {
+ re_playerlist = (RE_PlayerList *) &gamepacket->RE_List;
+ CreateRemotePlayer( re_playerlist, id );
+ }
+
+ } else {
+
+ DBGTXT( MSGOUT( "connection refused." ); );
+
+ connect_loop_exit = EXIT_TO_CONNECT_REFUSED;
+ return FALSE;
+ }
+
+ // exit connect-loop to game-loop (this also
+ // causes slotreq-loop to exit correctly)
+ connect_loop_exit = EXIT_TO_GAME_LOOP;
+
+ return TRUE;
+}
+
+
+// process slot request received in either connect- or slotreq-loop -----------
+//
+PRIVATE
+void ProcessSlotRequest( NetPacket_PEER* gamepacket, int bufid )
+{
+ ASSERT( gamepacket != NULL );
+ ASSERT( gamepacket->Command == PKTP_SLOT_REQUEST );
+
+ DBGTXT( MSGOUT( "got slot_request:" ); );
+ DBGTXT( NETs_PrintNode( NETs_GetSender( bufid ) ); );
+
+ // merge slot request queue of remote host with local queue
+ NET_MergeSlotRequests( gamepacket, TIMETAG_REPLYNOW );
+}
+
+
+// process subdue slave received in connect-loop ------------------------------
+//
+PRIVATE
+void ProcessSubdueSlave( NetPacket_PEER* gamepacket, int bufid )
+{
+ ASSERT( gamepacket != NULL );
+ ASSERT( gamepacket->Command == PKTP_SUBDUE_SLAVE );
+
+ node_t *node = NETs_GetSender( bufid );
+
+ DBGTXT( MSGOUT( "got subdue_slave:" ); );
+ DBGTXT( NETs_PrintNode( node ); );
+
+ // remember address of sender (who is temporary master)
+ CopyRemoteNode( master_node, *node );
+
+ // exit connect-loop to slotreq-loop
+ connect_loop_exit = EXIT_TO_SLOTREQ_LOOP;
+}
+
+
+// process the content of a packet received in connect-loop -------------------
+//
+PRIVATE
+void ProcPacketConnectLoop( NetPacket* pIntPkt, int bufid )
+{
+ ASSERT( pIntPkt != NULL );
+
+ NetPacket_PEER* gamepacket = (NetPacket_PEER*)pIntPkt;
+
+ // perform action according to packet type
+ switch( gamepacket->Command ) {
+
+ case PKTP_CONNECT:
+ ProcessConnect( gamepacket, bufid );
+ break;
+
+ case PKTP_CONNECT_REPLY:
+ ProcessConnectReply( gamepacket, bufid );
+ break;
+
+// case PKTP_DISCONNECT:
+
+ case PKTP_SLOT_REQUEST:
+ ProcessSlotRequest( gamepacket, bufid );
+ break;
+
+ case PKTP_SUBDUE_SLAVE:
+ ProcessSubdueSlave( gamepacket, bufid );
+ break;
+
+// case PKTP_JOIN:
+// case PKTP_UNJOIN:
+
+ case PKTP_GAME_STATE:
+ case PKTP_NODE_ALIVE:
+ // these packets may be received here if a
+ // remote node has already accepted us but
+ // we have not exited the connect-loop yet.
+ break;
+
+// case PKTP_PING:
+
+ default:
+ MSGOUT( "connectloop: received packet of invalid type: %d.", gamepacket->Command );
+ //FIXME: check which packet type causes this
+ //ASSERT( 0 );
+ }
+}
+
+
+// process the content of a packet received in slotreq-loop -------------------
+//
+PRIVATE
+void ProcPacketSlotReqLoop( NetPacket* pIntPkt, int bufid )
+{
+ ASSERT( pIntPkt != NULL );
+
+ NetPacket_PEER* gamepacket = (NetPacket_PEER*)pIntPkt;
+
+ // perform action according to packet type
+ switch( gamepacket->Command ) {
+
+ case PKTP_CONNECT:
+ // filter this packet type
+ DBGTXT( MSGOUT( "slotreqloop: filtering connect_request." ); );
+ break;
+
+ case PKTP_CONNECT_REPLY:
+ ProcessConnectReply( gamepacket, bufid );
+ break;
+
+// case PKTP_DISCONNECT:
+
+ case PKTP_SLOT_REQUEST:
+ ProcessSlotRequest( gamepacket, bufid );
+ break;
+
+ case PKTP_SUBDUE_SLAVE:
+ // filter this packet type
+ DBGTXT( MSGOUT( "slotreqloop: filtering subdue_slave." ); );
+ break;
+
+// case PKTP_JOIN:
+// case PKTP_UNJOIN:
+
+ case PKTP_GAME_STATE:
+ case PKTP_NODE_ALIVE:
+ // these packets may be received here if a
+ // remote node has already accepted us but
+ // we have not exited the slotreq-loop yet.
+ break;
+
+// case PKTP_PING:
+
+ default:
+ MSGOUT( "slotreqloop: received packet of invalid type: %d.", gamepacket->Command );
+ ASSERT( 0 );
+ }
+}
+
+
+// avoid nameclashes in debug version if multiple protocols linked ------------
+//
+#define ProcPacketGameLoop PEER_ProcPacketGameLoop
+#define SendGameState PEER_SendGameState
+#define SendNodeAlive PEER_SendNodeAlive
+#define PlayersNotUptodate PEER_PlayersNotUptodate
+#define InterpolatePlayers PEER_InterpolatePlayers
+#define CheckAliveStatus PEER_CheckAliveStatus
+#define FireDurationWeapons PEER_FireDurationWeapons
+
+
+// try to connect to peers (establish temporary master and connection) --------
+//
+int NETs_Connect()
+{
+ ASSERT( !NetConnected && !NetJoined );
+
+ // turn off game time
+ CurGameTime = GAME_PEERTOPEER;
+
+ MSGOUT( "max players: %d", CurMaxPlayers );
+
+ // (re)init tables
+ NET_InitRemotePlayerTables();
+
+ MSGOUT( "trying to establish connection..." );
+ MSGPUT( "searching for other players in universe %d.", MyUniverse );
+
+ // discard old packets
+ NETs_FlushListenBuffers();
+
+ // add slot request for local host
+ NET_AddSlotRequest( &LocalNode, LocalPlayerName, TIMETAG_LOCALHOST );
+
+ // connect-loop
+ int timeout = TIMEOUT_CONNECT;
+ connect_loop_exit = EXIT_TO_CONNECT_LOOP;
+ while ( ( connect_loop_exit == EXIT_TO_CONNECT_LOOP ) && ( timeout > 0 ) ) {
+
+ if ( TextModeActive )
+ MSGPUT( "." );
+
+ // send request for connection and wait a bit
+ ConnectBroadcast();
+ SYSs_Wait( RETRYWAIT_CONNECT );
+
+ // process any received packets
+ NETs_ProcessPacketChain( ProcPacketConnectLoop );
+
+ timeout--;
+ }
+
+ if ( TextModeActive )
+ MSGOUT( "\n" );
+
+ // check if slot request should be sent (local host is slave)
+ if ( connect_loop_exit == EXIT_TO_SLOTREQ_LOOP ) {
+
+ DBGTXT( MSGOUT( "other players found: this host is slave." ); );
+
+ // slotreq-loop
+ timeout = TIMEOUT_SLOTREQUEST;
+ while ( ( connect_loop_exit == EXIT_TO_SLOTREQ_LOOP ) && ( timeout > 0 ) ) {
+
+ if ( TextModeActive )
+ MSGPUT( "." );
+
+ // send slot request and wait a bit
+ SendSlotRequest( &master_node );
+ SYSs_Wait( RETRYWAIT_SLOTREQUEST );
+
+ // process any received packets
+ NETs_ProcessPacketChain( ProcPacketSlotReqLoop );
+
+ timeout--;
+ }
+
+ if ( TextModeActive )
+ MSGOUT( "\n" );
+
+ if ( ( connect_loop_exit == EXIT_TO_SLOTREQ_LOOP ) && ( timeout == 0 ) ) {
+
+ MSGOUT( "lost connection to temporary master." );
+ NET_ResetSlotReqQueue();
+ return FALSE;
+ }
+ }
+
+ // check if connection has been refused
+ if ( connect_loop_exit == EXIT_TO_CONNECT_REFUSED ) {
+ NET_ResetSlotReqQueue();
+ return FALSE;
+ }
+
+ // determine whether local host is master
+ int IsMaster = ( ( connect_loop_exit == EXIT_TO_CONNECT_LOOP ) && ( timeout == 0 ) );
+
+ // if this client is the master, we must set the playerid to 0
+ if ( IsMaster ) {
+ ASSERT( LocalPlayerId == PLAYERID_ANONYMOUS );
+ LocalPlayerId = 0;
+ }
+
+ // add local player to remote players
+ NET_InitLocalPlayer();
+
+ if ( IsMaster ) {
+
+ MSGOUT( "connection established: local host is master." );
+ DBGTXT( MSGOUT( "sending connect reply to slaves." ); );
+
+ // remove local host from queue
+ NET_DelSlotRequest( &LocalNode );
+
+ // count local player
+ ASSERT( NumRemPlayers == 0 );
+ NumRemPlayers = 1;
+
+ // send replies to queued slaves
+ ConnectQueue();
+
+ } else {
+
+ MSGOUT( "connection established: master found." );
+
+ // delete slot requests
+ NET_ResetSlotReqQueue();
+ }
+
+ //NOTE:
+ // for implementation efficiency and simplicity the
+ // local player is added to the table of remote players.
+ // to determine if a remote player is actually local
+ // (e.g., to prevent sending a packet to oneself)
+ // the shipid can be used, which is set to SHIPID_LOCALPLAYER.
+ // the REMOTE_PLAYER_ACTIVE() macro determines if a player is
+ // both active and actually remote.
+
+ // list all remote players currently connected
+ int otherplayers = 0;
+ for ( int id = 0; id < CurMaxPlayers; id++ )
+ if ( REMOTE_PLAYER_ACTIVE( id ) )
+ otherplayers++;
+ if ( otherplayers > 0 ) {
+ MSGOUT( "the following remote players are in universe %d:", MyUniverse );
+ for ( int id = 0; id < CurMaxPlayers; id++ )
+ if ( REMOTE_PLAYER_ACTIVE( id ) )
+ MSGOUT( "id %d: %s", id, Player_Name[ id ] );
+ } else {
+ MSGOUT( "there are no other players in universe %d.", MyUniverse );
+ }
+
+ // set global flag
+ NetConnected = NETWORK_GAME_ON;
+
+ return TRUE;
+}
+
+
+// notify remote players of disconnect (exit entry-mode) ----------------------
+//
+int NETs_Disconnect()
+{
+ ASSERT( ( NetConnected == NETWORK_GAME_ON ) && !NetJoined );
+ ASSERT( Player_Status[ LocalPlayerId ] == PLAYER_CONNECTED );
+
+ // send disconnect message to all active remote players
+ for ( int id = 0; id < CurMaxPlayers; id++ ) {
+ if ( REMOTE_PLAYER_ACTIVE( id ) ) {
+
+ DBGTXT( MSGOUT( "sending disconnect message to id %d.", id ); );
+ ADXTXT( NETs_PrintNode( &Player_Node[ id ] ); );
+
+ // fill game data header
+ NETs_StdGameHeader( PKTP_DISCONNECT, (NetPacket*)GamePacket );
+ GamePacket->DestPlayerId = id;
+
+ NETs_AuxSendPacket( (NetPacket*)GamePacket, &Player_Node[ id ] );
+ }
+ }
+
+ // delete slot requests
+ NET_ResetSlotReqQueue();
+
+ // remove all remote players
+ NET_RemoveRemotePlayers();
+
+ // remove local player
+ ASSERT( NumRemPlayers == 1 );
+ NumRemPlayers = 0;
+
+ // update player status
+ Player_Status[ LocalPlayerId ] = PLAYER_INACTIVE;
+
+ // reset global flag
+ NetConnected = NETWORK_GAME_OFF;
+
+ return TRUE;
+}
+
+
+// join game (enter game from entry mode in already allocated slot) -----------
+//
+int NETs_Join()
+{
+ ASSERT( NetConnected && !NetJoined );
+ ASSERT( Player_Status[ LocalPlayerId ] == PLAYER_CONNECTED );
+
+ // update player status
+ Player_Status[ LocalPlayerId ] = PLAYER_JOINED;
+
+ // set global flag
+ NetJoined = TRUE;
+
+ // send join message to all active remote players
+ for ( int id = 0; id < CurMaxPlayers; id++ )
+ if ( REMOTE_PLAYER_ACTIVE( id ) ) {
+
+ DBGTXT( MSGOUT( "sending join message to id %d.", id ); );
+ ADXTXT( NETs_PrintNode( &Player_Node[ id ] ); );
+
+ // fill game data header
+ NETs_StdGameHeader( PKTP_JOIN, (NetPacket*)GamePacket );
+ GamePacket->DestPlayerId = id;
+ GamePacket->params[ 3 ] = MyShip->ObjectClass;
+
+ GamePacket->ShipInfo.CurSpeed = MyShip->CurSpeed;
+ memcpy( &GamePacket->ShipInfo.ObjPosition, MyShip->ObjPosition, sizeof( Xmatrx ) );
+
+ // create single remote event: player name
+ NET_RmEvSinglePlayerName( (RE_Header*)&GamePacket->RE_List );
+
+ NETs_AuxSendPacket( (NetPacket*)GamePacket, &Player_Node[ id ] );
+ }
+
+ //FIXME: [1/27/2002] ??? GAMECODE() ???
+
+ // open stargate
+ if ( !AUX_DISABLE_LOCAL_STARGATE ) {
+ SFX_CreateStargate( MyShip );
+ }
+
+ // record join
+ Record_Join();
+
+ return TRUE;
+}
+
+
+// last unjoin flag (allows delayed unjoins to compensate for packet drop) ----
+//
+static byte last_unjoin_flag = USER_EXIT;
+
+
+// notify remote players of unjoin (exit to entry-mode) -----------------------
+//
+int NETs_Unjoin( byte flag )
+{
+ ASSERT( NetConnected && NetJoined );
+ ASSERT( Player_Status[ LocalPlayerId ] == PLAYER_JOINED );
+
+ // update player status
+ Player_Status[ LocalPlayerId ] = PLAYER_CONNECTED;
+
+ // reset global flag
+ NetJoined = FALSE;
+
+ // remember unjoin flag
+ last_unjoin_flag = flag;
+
+ // send unjoin message to all active remote players
+ for ( int id = 0; id < CurMaxPlayers; id++ )
+ if ( REMOTE_PLAYER_ACTIVE( id ) ) {
+
+ DBGTXT( MSGOUT( "sending unjoin message to id %d.", id ); );
+ ADXTXT( NETs_PrintNode( &Player_Node[ id ] ); );
+
+ // fill game data header
+ NETs_StdGameHeader( PKTP_UNJOIN, (NetPacket*)GamePacket );
+ GamePacket->DestPlayerId = id;
+ GamePacket->params[ 0 ] = flag;
+ NET_SetPacketKillStats( GamePacket );
+
+ // insert id of player who downed us
+ if ( flag == SHIP_DOWNED ) {
+ ASSERT( ( CurKiller >= KILLERID_UNKNOWN ) && ( CurKiller < MAX_NET_PROTO_PLAYERS + KILLERID_BIAS ) );
+ ASSERT( ( CurKiller >= 0 ) && ( CurKiller < 128 ) );
+ GamePacket->params[ 2 ] = CurKiller;
+ }
+
+ NETs_AuxSendPacket( (NetPacket*)GamePacket, &Player_Node[ id ] );
+ }
+
+ //FIXME: [1/27/2002] ??? GAMECODE() ???
+
+ // open stargate
+ if ( !AUX_DISABLE_LOCAL_STARGATE && ( flag == USER_EXIT ) )
+ SFX_CreateStargate( MyShip );
+
+ // record unjoin
+ Record_Unjoin( flag );
+
+ return TRUE;
+}
+
+
+// update player name on server -----------------------------------------------
+//
+int NETs_UpdateName()
+{
+ //NOTE:
+ // not needed since there is no server.
+
+ return TRUE;
+}
+
+
+// calculate time offset to act as master and reply to connect request --------
+//
+PRIVATE
+int CalcReplyTimeOffset()
+{
+ // assume local host is first to reply
+ int localpos = 0;
+
+ node_t localnode;
+ NETs_MakeNodeRaw( &localnode, &Player_Node[ LocalPlayerId ] );
+
+ for ( int id = 0; id < CurMaxPlayers; id++ )
+ if ( REMOTE_PLAYER_ACTIVE( id ) ) {
+
+ node_t remotenode;
+ NETs_MakeNodeRaw( &remotenode, &Player_Node[ id ] );
+
+ // use node address to establish ordering
+ int cmpcode = NETs_CompareNodes( &localnode, &remotenode );
+
+ // nodes with greater address have higher priority
+ if ( cmpcode == NODECMP_GREATERTHAN )
+ localpos++;
+ }
+
+ return ( localpos * TIMEOFFSET_QUANTUM );
+}
+
+
+// schedule reply to connect request received in game-loop --------------------
+//
+PRIVATE
+void ScheduleConnect( NetPacket_PEER* gamepacket, int bufid )
+{
+ ASSERT( gamepacket != NULL );
+ ASSERT( ( gamepacket->Command == PKTP_CONNECT ) ||
+ ( gamepacket->Command == PKTP_SLOT_REQUEST ) );
+
+ DBGTXT( MSGOUT( "scheduling connect request:" ); );
+ DBGTXT( NETs_PrintNode( NETs_GetSender( bufid ) ); );
+
+ // calc timetag
+ int timetag = SYSs_GetRefFrameCount() + CalcReplyTimeOffset();
+
+ // merge in remote slot request(s)
+ NET_MergeSlotRequests( gamepacket, timetag );
+}
+
+
+// snoop connect reply sent from an acting master to a new remote player ------
+//
+PRIVATE
+void SnoopConnect( NetPacket_PEER* gamepacket, int bufid )
+{
+ ASSERT( gamepacket != NULL );
+ ASSERT( gamepacket->Command == PKTP_CONNECT_REPLY );
+
+ int srcid = gamepacket->SendPlayerId;
+ int destid = gamepacket->DestPlayerId;
+
+ ASSERT( ( srcid >= 0 ) && ( srcid < MAX_NET_PROTO_PLAYERS ) );
+ ASSERT( ( destid >= SLOTID_CONNECT_REFUSED ) && ( destid < MAX_NET_PROTO_PLAYERS ) );
+
+ // retrieve contained player list
+ RE_PlayerList *re_playerlist = (RE_PlayerList *) &gamepacket->RE_List;
+
+ ASSERT( re_playerlist->RE_Type == RE_PLAYERLIST );
+
+ // remove request from queue
+ NET_DelSlotRequest( &re_playerlist[ LocalPlayerId >> 2 ].AddressTable[ LocalPlayerId % 4 ] );
+
+ // filter packets actually addressed to local host
+ if ( destid == LocalPlayerId ) {
+ DBGTXT( MSGOUT( "got redundant CONNECT_REPLY from id %d.", srcid ); );
+ return;
+ }
+
+ DBGTXT( MSGOUT( "snooping connect reply from id %d to id %d.", srcid, destid ); );
+ DBGTXT( NETs_PrintNode( NETs_GetSender( bufid ) ); );
+ DBGTXT( NETs_PrintNode( &re_playerlist[ LocalPlayerId >> 2 ].AddressTable[ LocalPlayerId % 4 ] ); );
+
+ // check for duplicate replies
+ if ( Player_Status[ destid ] != PLAYER_INACTIVE ) {
+ DBGTXT( MSGOUT( "snooped CONNECT_REPLY was redundant." ); );
+ return;
+ }
+
+ // check whether connect accepted (slot available)
+ if ( destid != SLOTID_CONNECT_REFUSED ) {
+
+ // sync critical values
+ dword killlimit = re_playerlist->SyncValKillLimit;
+ dword nebulaid = re_playerlist->SyncValNebulaId;
+
+ if ( ( nebulaid >= 2 ) && ( nebulaid <= 5 ) ) {
+ AUX_KILL_LIMIT_FOR_GAME_END = killlimit;
+ AUXDATA_BACKGROUND_NEBULA_ID = nebulaid;
+ }
+
+ CreateRemotePlayer( re_playerlist, destid );
+
+ NumRemPlayers++;
+ ASSERT( NumRemPlayers == gamepacket->NumPlayers );
+
+ DBGTXT( MSGOUT( "creating new remote player: %s (id %d).", re_playerlist[ LocalPlayerId >> 2 ].NameTable[ LocalPlayerId % 4 ], destid ); );
+
+ } else {
+
+ DBGTXT( MSGOUT( "snooped connect refusal for player %s.", re_playerlist[ LocalPlayerId >> 2 ].NameTable[ LocalPlayerId % 4 ] ); );
+ }
+}
+
+
+// called to remove pending slot request when connection established ----------
+//
+PRIVATE
+void ConnectionEstablished( int bufid )
+{
+ //NOTE:
+ // this function is called when a node alive or
+ // game update message is received. at that time
+ // it is absolutely certain that the connect reply
+ // has been received, so the slot request can be
+ // removed from the queue.
+
+ // remove request from queue if contained
+ if ( NET_DelSlotRequest( NETs_GetSender( bufid ) ) ) {
+
+ DBGTXT( MSGOUT( "slot request deleted." ); );
+ DBGTXT( NETs_PrintNode( NETs_GetSender( bufid ) ); );
+ }
+}
+
+
+// process the content of a packet received in game-loop ----------------------
+//
+PRIVATE
+void ProcPacketGameLoop( NetPacket* pIntPkt, int bufid )
+{
+ ASSERT( pIntPkt != NULL );
+
+ NetPacket_PEER* gamepacket = (NetPacket_PEER*)pIntPkt;
+
+ // perform action according to packet type
+ switch( gamepacket->Command ) {
+
+ case PKTP_CONNECT:
+ ScheduleConnect( gamepacket, bufid );
+ break;
+
+ case PKTP_CONNECT_REPLY:
+ SnoopConnect( gamepacket, bufid );
+ break;
+
+ case PKTP_DISCONNECT:
+ NET_PlayerDisconnected( gamepacket->SendPlayerId );
+ break;
+
+ case PKTP_SLOT_REQUEST:
+ DBGTXT( MSGOUT( "warning: SLOT_REQUEST received in gameloop." ); );
+ ScheduleConnect( gamepacket, bufid );
+ break;
+
+ case PKTP_SUBDUE_SLAVE:
+ DBGTXT( MSGOUT( "warning: SUBDUE_SLAVE received in gameloop." ); );
+ break;
+
+ case PKTP_JOIN:
+ {
+ // translate from packet to remote event
+ RE_PlayerAndShipStatus pas_status;
+ NET_Translate_PKTP_JOIN( gamepacket, &pas_status );
+
+ // perform necessary steps to ensure desired player status ( was NET_PlayerJoined )
+ NET_SetDesiredPlayerStatus( &pas_status );
+ }
+ break;
+
+ case PKTP_UNJOIN:
+ {
+ // translate from packet to remote event
+ RE_PlayerAndShipStatus pas_status;
+ RE_KillStats killstats;
+ NET_Translate_PKTP_UNJOIN( gamepacket, &pas_status, &killstats );
+
+ // do last killstat update
+ NETs_UpdateKillStats( &killstats );
+
+ // perform necessary steps to ensure desired player status ( was NET_PlayerUnjoined )
+ NET_SetDesiredPlayerStatus( &pas_status );
+ }
+ break;
+
+ case PKTP_GAME_STATE:
+ {
+ ConnectionEstablished( bufid );
+
+ // translate from packet to remote event
+ RE_PlayerAndShipStatus pas_status;
+ RE_KillStats killstats;
+ NET_Translate_PKTP_GAME_STATE( gamepacket, &pas_status, &killstats );
+
+ // perform necessary steps to ensure desired player status ( was NET_EnsureJoined )
+ NET_SetDesiredPlayerStatus( &pas_status );
+
+ // update player kill stats
+ NETs_UpdateKillStats( &killstats );
+
+ // keep player in game, update player ship ( ignore older states ), and process remote event list
+ NET_UpdateGameState_PEER( gamepacket );
+ }
+ break;
+
+ case PKTP_NODE_ALIVE:
+ {
+ ConnectionEstablished( bufid );
+
+ // translate from packet to remote event
+ RE_PlayerAndShipStatus pas_status;
+ RE_KillStats killstats;
+ NET_Translate_PKTP_NODE_ALIVE( gamepacket, &pas_status, &killstats );
+
+ ASSERT( pas_status.player_status == PLAYER_CONNECTED );
+
+ // perform necessary steps to ensure desired player status ( was NET_EnsureUnjoined )
+ NET_SetDesiredPlayerStatus( &pas_status );
+
+ // update player kill stats
+ NETs_UpdateKillStats( &killstats );
+
+ // keep player in game and process remote event list ( can only be namechange as player is not joined )
+ NET_ProcessNodeAlive_PEER( gamepacket );
+ }
+ break;
+
+ default:
+ MSGOUT( "gameloop: received packet of unknown type: %d.", gamepacket->Command );
+ ASSERT( 0 );
+ }
+}
+
+
+// update game state of active remote players ---------------------------------
+//
+PRIVATE
+void SendGameState()
+{
+ // fill game data header
+ NETs_StdGameHeader( PKTP_GAME_STATE, (NetPacket*)GamePacket );
+ NET_SetPacketKillStats( GamePacket );
+ NET_FillShipRemInfo( &GamePacket->ShipInfo, MyShip, LocalPlayerId );
+ NET_RmEvListUpdateLocations();
+
+ // send state packet to all active remote players
+ for ( int id = 0; id < CurMaxPlayers; id++ )
+ if ( REMOTE_PLAYER_ACTIVE( id ) ) {
+
+ UPDTXT( MSGOUT( "updating id %d (pid=%d).", id, GamePacket->MessageId ); );
+ UPDTXT( ADXTXT( NETs_PrintNode( &Player_Node[ id ] ); ); );
+
+ // enable remote player to determine whether
+ // join status is consistent
+ GamePacket->params[ 1 ] = Player_Status[ id ];
+
+ // enable remote player to perform delayed join
+ // (to compensate for dropped join packets)
+ GamePacket->params[ 3 ] = MyShip->ObjectClass;
+
+ // store remote player's id and send packet
+ GamePacket->DestPlayerId = id;
+ //FIXME: we only need to copy the global REList into the packet once
+ NETs_SendPacket( (NetPacket*)GamePacket, &Player_Node[ id ] );
+ }
+}
+
+
+// send node alive packets to prevent being kicked out while in entry-mode ----
+//
+PRIVATE
+void SendNodeAlive()
+{
+ // fill game data header
+ NETs_StdGameHeader( PKTP_NODE_ALIVE, (NetPacket*)GamePacket );
+
+ //NOTE:
+ // node alive packets contain killstats in order
+ // for the server to receive all kills
+ NET_SetPacketKillStats( GamePacket );
+
+ NET_RmEvListUpdateLocations();
+
+ // send node alive message to all active remote players
+ for ( int id = 0; id < CurMaxPlayers; id++ )
+ if ( REMOTE_PLAYER_ACTIVE( id ) ) {
+
+ UPDTXT( MSGOUT( "sending node_alive to id %d (pid=%d).", id, GamePacket->MessageId ); );
+ UPDTXT( ADXTXT( NETs_PrintNode( &Player_Node[ id ] ); ); );
+
+ // enable remote player to perform delayed unjoin
+ // (to compensate for dropped unjoin packets)
+ GamePacket->params[ 0 ] = last_unjoin_flag;
+
+ // enable remote player to determine whether
+ // join status is consistent
+ GamePacket->params[ 1 ] = Player_Status[ id ];
+
+ // enable remote player to determine who
+ // killed us if ( param1 == SHIP_DOWNED )
+ if ( last_unjoin_flag == SHIP_DOWNED ) {
+ ASSERT( ( CurKiller >= KILLERID_UNKNOWN ) && ( CurKiller < MAX_NET_PROTO_PLAYERS + KILLERID_BIAS ) );
+ ASSERT( ( CurKiller >= 0 ) && ( CurKiller < 128 ) );
+ GamePacket->params[ 2 ] = CurKiller;
+ }
+
+ // store remote player's id and send packet
+ GamePacket->DestPlayerId = id;
+ NETs_SendPacket( (NetPacket*)GamePacket, &Player_Node[ id ] );
+ }
+}
+
+
+// assume all remote players are not up-to-date -------------------------------
+//
+PRIVATE
+void PlayersNotUptodate()
+{
+ // reset up-to-date flags for all remote players
+ for ( int id = 0; id < CurMaxPlayers; id++ )
+ Player_UpToDate[ id ] = FALSE;
+}
+
+
+// interpolate remote player actions ------------------------------------------
+//
+PRIVATE
+void InterpolatePlayers()
+{
+#ifdef INTERPOLATE_PLAYER_ACTIONS
+
+ // interpolate actions of active players that are not up-to-date
+ for ( int id = 0; id < CurMaxPlayers; id++ )
+ if ( REMOTE_PLAYER_ACTIVE( id ) &&
+ !Player_UpToDate[ id ] && Player_Ship[ id ] ) {
+
+ NET_InterpolatePlayer( id );
+ }
+#endif
+}
+
+
+// check if all active remote players are still alive -------------------------
+//
+PRIVATE
+void CheckAliveStatus()
+{
+ for ( int id = 0; id < CurMaxPlayers; id++ )
+ if ( REMOTE_PLAYER_ACTIVE( id ) ) {
+
+ // decrease counter
+ Player_AliveCounter[ id ] -= CurScreenRefFrames;
+
+ // check for counter underrun
+ if ( Player_AliveCounter[ id ] < 0 )
+ NET_RemovePlayer( id );
+ }
+}
+
+
+// fire duration weapons ------------------------------------------------------
+//
+PRIVATE
+void FireDurationWeapons()
+{
+ for ( int id = 0; id < MAX_NET_PROTO_PLAYERS; id++ )
+ if ( REMOTE_PLAYER_JOINED( id ) ) {
+ MaintainDurationWeapons( id );
+ }
+}
+
+
+// maintain network game play -------------------------------------------------
+//
+void NETs_MaintainNet()
+{
+ CHECKMEMINTEGRITY();
+
+ // make NumRemPlayers accessible from console
+ AUXDATA_NUMREMPLAYERS_COPY = NumRemPlayers;
+
+ // exit if not connected
+ if ( !NetConnected )
+ return;
+
+ // send packet if necessary
+ if ( CurPacketRefFrames >= Packet_Send_Frametime ) {
+
+ // advance base for frame measurement
+ PacketFrameBase += CurPacketRefFrames;
+
+ // reply to received connect requests
+ ConnectQueue();
+
+ // perform regular packet sending
+ if ( NetJoined ) {
+
+ // send remote event list
+ SendGameState();
+
+ // check downing of local ship (only if live, not replay)
+//FIXME: if ( NetConnected == NETWORK_GAME_ON ) {
+ if ( !DEMO_ReplayActive() ) {
+ if ( MyShip->CurDamage > MyShip->MaxDamage ) {
+ ShipDowned = TRUE;
+ AUD_PlayerKilled();
+ }
+ }
+
+ } else {
+
+ // keep node alive
+ SendNodeAlive();
+ }
+
+ // reset remote event list
+ NET_RmEvListReset();
+
+// UPDTXT( MSGOUT( "__current message id: %d__", CurSendMessageId_PEER ); );
+ }
+
+ // reset up-to-date flags
+ PlayersNotUptodate();
+
+ // process all received packets
+ NETs_ProcessPacketChain( ProcPacketGameLoop );
+
+ // check if all active remote players are still alive
+ CheckAliveStatus();
+
+ // interpolate remote player actions
+ InterpolatePlayers();
+
+ // fire active duration weapons of remote ships
+ FireDurationWeapons();
+}
+
+
+