From c068f22329d5cc722622a2183bbb22eef2093df7 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Mon, 24 Aug 2026 11:16:07 +0200 Subject: initial import --- src/parsec/net_game.cpp | 1304 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1304 insertions(+) create mode 100644 src/parsec/net_game.cpp (limited to 'src/parsec/net_game.cpp') diff --git a/src/parsec/net_game.cpp b/src/parsec/net_game.cpp new file mode 100644 index 0000000..07f67df --- /dev/null +++ b/src/parsec/net_game.cpp @@ -0,0 +1,1304 @@ +/* + * PARSEC - Network Game Functions + * + * $Author: uberlinuxguy $ - $Date: 2004/09/26 03:43:39 $ + * + * Orginally written by: + * Copyright (c) Clemens Beer 2002 + * Copyright (c) Markus Hadwiger 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 +#include +#include +#include +#include + +// compilation flags/debug support +#include "config.h" +#include "debug.h" + +// general definitions +#include "general.h" +#include "objstruc.h" + +// global externals +#include "globals.h" + +// subsystem headers +#include "aud_defs.h" +#include "net_defs.h" +#include "sys_defs.h" + +// mathematics header +#include "utl_math.h" + +// network code config +#include "net_conf.h" + +// subsystem linkage info +#include "linkinfo.h" + +// local module header +#include "net_game.h" + +// proprietary module headers +#include "aud_game.h" +#include "con_aux.h" +#include "e_record.h" +#include "g_camera.h" +#include "g_stars.h" +#include "g_supp.h" +#include "h_supp.h" +#ifdef LINKED_PROTOCOL_GAMESERVER +# include "net_csdf.h" +#endif // LINKED_PROTOCOL_GAMESERVER +#include "obj_creg.h" +#include "obj_ctrl.h" +#include "obj_xtra.h" +#include "g_sfx.h" + + +// flags +//#define INCREMENTAL_KILLSTATS +#define _SLERP_DEBUGGING + +#ifdef _SLERP_DEBUGGING + +#ifdef SYSTEM_TARGET_WINDOWS + #include "windows.h" + #include "mmsystem.h" + #pragma comment( lib, "winmm.lib" ) +#endif // SYSTEM_TARGET_WINDOWS + +#endif // _SLERP_DEBUGGINGs + + +// generic string paste area -------------------------------------------------- +// +#define PASTE_STR_LEN 255 +static char paste_str[ PASTE_STR_LEN + 1 ]; + + +// local node address (has to be set on networking subsystem startup) --------- +// +node_t LocalNode; + + +// local broadcast address (has to be set on networking subsystem startup) ---- +// +node_t LocalBroadcast; + + +// server node address (only valid for client/server protocols) --------------- +// +node_t Server_Node; + + +// remote player node address table ------------------------------------------- +// +node_t Player_Node[ MAX_NET_ALLOC_SLOTS ]; + + +// list where object control code inserts remote events (global rmev list) ---- +// +char REListMem[ RE_LIST_ALLOC_SIZE ]; + +// ---------------------------------------------------------------------------- +// +#include "g_main_cl.h" +G_Extracted TheGameExtraction; + +// force clean disconnect ----------------------------------------------------- +// +void NET_ForceDisconnect() +{ + //NOTE: + // this function is used by NETs_KillAPI() to disconnect + // cleanly before killing the networking code. + + // if joined on server unjoin first + if ( NetJoined ) { + ObjCamOff(); + NETs_Unjoin( USER_EXIT ); + } + + // if connected perform disconnect + if ( NetConnected == NETWORK_GAME_ON ) { + NETs_Disconnect(); + } +} + + +// add local player to remote player table ------------------------------------ +// +void NET_InitLocalPlayer() +{ + //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. + + Player_Status[ LocalPlayerId ] = PLAYER_CONNECTED; + + Player_Ship[ LocalPlayerId ] = MyShip; + Player_ShipId[ LocalPlayerId ] = SHIPID_LOCALPLAYER; + + NETs_ResolveNode( &Player_Node[ LocalPlayerId ], &LocalNode ); + + CopyRemoteName( Player_Name[ LocalPlayerId ], LocalPlayerName ); +} + +// init tables used to store info about remote players ------------------------ +// +void NET_InitRemotePlayerTables() +{ + // start with assumption that no other players present + LocalPlayerId = PLAYERID_ANONYMOUS; + NumRemPlayers = 0; + + //NOTE: + // just to be on the safe side, initialize all + // MAX_NET_ALLOC_SLOTS slots, not just MAX_NET_PROTO_PLAYERS. + + // do initializations + for ( int id = 0; id < MAX_NET_ALLOC_SLOTS; id++ ) { + + Player_Status[ id ] = PLAYER_INACTIVE; + Player_Ship[ id ] = NULL; + Player_ShipId[ id ] = SHIPID_NOSHIP; + Player_AliveCounter[ id ] = 0; + Player_UpToDate[ id ] = FALSE; + Player_KillStat[ id ] = 0; + Player_LastMsgId[ id ] = 0; + Player_LastUpdateGameStateMsgId[ id ] = 0; + Player_Name[ id ][ 0 ] = '\0'; + memset( &Player_Node[ id ], 0, sizeof( node_t ) ); + +#ifdef LINKED_PROTOCOL_GAMESERVER + if ( NET_ProtocolGMSV() ) { + extern client_s client_list[ MAX_NET_ALLOC_SLOTS ]; + client_list[ id ].Reset(); + } +#endif // LINKED_PROTOCOL_GAMESERVER + + } +} + + +// remove all remote players -------------------------------------------------- +// +void NET_RemoveRemotePlayers() +{ + ASSERT( NetConnected ); + + // process table + for ( int id = 0; id < MAX_NET_PROTO_PLAYERS; id++ ) { + + // player must be active in order to be removed + if ( Player_Status[ id ] == PLAYER_INACTIVE ) + continue; + + // don't remove local player + if ( id == LocalPlayerId ) + continue; + + DBGTXT( MSGOUT( "NET_RemoveRemotePlayers(): removing player: %s (id %d).", Player_Name[ id ], id ); ); + DBGTXT( NETs_PrintNode( &Player_Node[ id ] ); ); + + // if player is joined ship must be destroyed + if ( Player_Status[ id ] == PLAYER_JOINED ) { + + DBGTXT( MSGOUT( "NET_RemoveRemotePlayers(): removing remote ship." ); ); + + ShipObject *shippo = (ShipObject *) Player_Ship[ id ]; + ASSERT( shippo != NULL ); + ASSERT( Player_ShipId[ id ] != SHIPID_LOCALPLAYER ); + ASSERT( Player_ShipId[ id ] != SHIPID_NOSHIP ); + KillSpecificObject( Player_ShipId[ id ], PShipObjects ); + + } else { + + ASSERT( Player_Ship[ id ] == NULL ); + ASSERT( Player_ShipId[ id ] == SHIPID_NOSHIP ); + } + + // remove player from table + Player_Status[ id ] = PLAYER_INACTIVE; + Player_Ship[ id ] = NULL; + Player_ShipId[ id ] = SHIPID_NOSHIP; + +//FIXME: DEMO_TEMP +// this assertion fired in +// one of the recorded demos. + + // decrease global number of remote players +// ASSERT( NumRemPlayers > 1 ); + NumRemPlayers--; + } +} + + +// materialize remote player with specified state ----------------------------- +// +void NET_SetRemotePlayerState( int id, int status, int objclass, int killstat ) +{ + ASSERT( NetConnected ); + + //NOTE: + // this function is used by CON_ACT::AcRemotePlayer() to + // create a remote player in exactly the state he was in + // when the "ac.remoteplayer" command had been saved. + + ASSERT( ( id >= 0 ) && ( id < MAX_NET_PROTO_PLAYERS ) ); + ASSERT( ( status == PLAYER_INACTIVE ) || + ( status == PLAYER_CONNECTED ) || + ( status == PLAYER_JOINED ) ); + ASSERT( objclass < NumObjClasses ); + // ( objclass < 0 ) explicitly allowed! + ASSERT( killstat >= 0 ); + + // check if active player is being overwritten + if ( Player_Status[ id ] != PLAYER_INACTIVE ) { + + DBGTXT( MSGOUT( "NET_SetRemotePlayerState(): player %d already active.", id ); ); + + if ( Player_ShipId[ id ] == SHIPID_LOCALPLAYER ) { + + DBGTXT( MSGOUT( "NET_SetRemotePlayerState(): active player %d was local.", id ); ); + + } else if ( Player_Status[ id ] == PLAYER_JOINED ) { + + DBGTXT( MSGOUT( "NET_SetRemotePlayerState(): removing ship of player %d.", id ); ); + + // kill ship object + ShipObject *shippo = (ShipObject *) Player_Ship[ id ]; + ASSERT( shippo != NULL ); + if ( shippo != NULL ) + KillSpecificObject( Player_ShipId[ id ], PShipObjects ); + + } else { + + ASSERT( Player_Ship[ id ] == NULL ); + ASSERT( Player_ShipId[ id ] == SHIPID_NOSHIP ); + } + } + + // negative objclass denotes local player + if ( objclass < 0 ) { + + DBGTXT( MSGOUT( "NET_SetRemotePlayerState(): changing local player id from %d to %d.", LocalPlayerId, id ); ); + DBGTXT( MSGOUT( "NET_SetRemotePlayerState(): local player status is %d.", status ); ); + + LocalPlayerId = id; + LocalPlayerName[ 0 ] = '\0'; + NumRemPlayers = -objclass; + ASSERT( NumRemPlayers > 0 ); + + // set player state + Player_Status[ id ] = status; + Player_Ship[ id ] = MyShip; + Player_ShipId[ id ] = SHIPID_LOCALPLAYER; + Player_KillStat[ id ] = killstat; + Player_Name[ id ][ 0 ] = '\0'; + NETs_ResolveNode( &Player_Node[ id ], &LocalNode ); + + // we also must reset the msg id's for the local player if LocalPlayerId == 0, because + // PKTP_CONNECT and PKTP_CONNECT_REPLY packets contain SendPlayerId 0. + + Player_LastMsgId[ id ] = 0; + Player_LastUpdateGameStateMsgId[ id ] = 0; + + // restore join state + NetJoined = ( status == PLAYER_JOINED ); + + // restore entry-mode state + int emstate = ( status == PLAYER_JOINED ) ? FALSE : TRUE; + DBGTXT( + if ( emstate != EntryMode ) + MSGOUT( "NET_SetRemotePlayerState(): changing entry-mode state to %d.", emstate ); + ); + EntryMode = emstate; + InFloatingMenu = EntryMode ? FloatingMenu : FALSE; + + } else { + + // set player state + Player_Status[ id ] = status; + Player_Ship[ id ] = NULL; + Player_ShipId[ id ] = SHIPID_NOSHIP; + Player_AliveCounter[ id ] = MAX_ALIVE_COUNTER; + Player_UpToDate[ id ] = FALSE; + Player_KillStat[ id ] = killstat; + Player_LastMsgId[ id ] = 0; + Player_LastUpdateGameStateMsgId[ id ] = 0; + Player_Name[ id ][ 0 ] = '\0'; + NETs_SetVirtualAddress( &Player_Node[ id ] ); + + if ( status == PLAYER_JOINED ) { + + // default position (no other info available) + Xmatrx objposition; + MakeIdMatrx( objposition ); + + // create ship object for player + GenObject *objectpo = CreateObject( objclass, objposition ); + ASSERT( objectpo != NULL ); + objectpo->HostObjNumber = ShipHostObjId( id ); + Player_Ship[ id ] = objectpo; + Player_ShipId[ id ] = objectpo->ObjectNumber; + + DBGTXT( MSGOUT( "NET_SetRemotePlayerState(): materializing joined player %d.", id ); ); + + } else if ( status == PLAYER_CONNECTED ) { + + DBGTXT( MSGOUT( "NET_SetRemotePlayerState(): materializing connected player %d.", id ); ); + } + } +} + + +// set a remote player's position matrix -------------------------------------- +// +void NET_SetRemotePlayerMatrix( int id, const Xmatrx matrix ) +{ + ASSERT( NetConnected ); + + //NOTE: + // this function is used by CON_ACT::AcRemoteMatrix() to set + // the matrix of a remote player already created by + // CON_ACT::AcRemotePlayer() (via NET_SetRemotePlayerState()). + + ASSERT( Player_Ship[ id ] != NULL ); + + //NOTE: + // this also works for the local player and is actually used thusly. + + // copy matrix into object + if ( Player_Ship[ id ] != NULL ) { + + // copy over matrix without any further checks + memcpy( Player_Ship[ id ]->ObjPosition, matrix, sizeof( Xmatrx ) ); + } +} + + +// set a remote player's name ------------------------------------------------- +// +void NET_SetRemotePlayerName( int id, const char *name ) +{ + ASSERT( NetConnected ); + + //NOTE: + // this function is used by CON_ACT::AcRemoteName() to set + // the name of a remote player already created by + // CON_ACT::AcRemotePlayer() (via NET_SetRemotePlayerState()). + + // store name in table + CopyRemoteName( Player_Name[ id ], name ); + + // additionally store name of local player in LocalPlayerName + if ( Player_ShipId[ id ] == SHIPID_LOCALPLAYER ) + CopyRemoteName( LocalPlayerName, name ); +} + + + + +// register prospective remote player (may fail if no slot available) --------- +// +int NET_RegisterRemotePlayer( int slotid, node_t *node, char *name ) +{ + ASSERT( ( node != NULL ) || NET_ProtocolGMSV() ); + ASSERT( name != NULL ); + + //NOTE: + // NumRemPlayers must already include the player that + // is being registered. it will not be updated here. + + ASSERT( NumRemPlayers > 1 ); + + // register in specified slot + if ( slotid != SLOTID_CONNECT_REFUSED ) { + + ASSERT( Player_Status[ slotid ] == PLAYER_INACTIVE ); + ASSERT( Player_Ship[ slotid ] == NULL ); + + // accept connection + DBGTXT( MSGOUT( "NET_RegisterRemotePlayer(): registering player %s with id %d.", name, slotid ); ); + if ( node != NULL ) + ADXTXT( NETs_PrintNode( node ); ); + + // init remote player table entries + Player_Status[ slotid ] = PLAYER_CONNECTED; + if ( node != NULL ) { + Player_Node[ slotid ] = *node; + } else { + memset( &Player_Node[ slotid ], 0, sizeof(node_t) ); + } + + Player_Ship[ slotid ] = NULL; + Player_ShipId[ slotid ] = SHIPID_NOSHIP; + Player_AliveCounter[ slotid ] = MAX_ALIVE_COUNTER; + CopyRemoteName( Player_Name[ slotid ], name ); + + // init up-to-date flag and killstats + Player_UpToDate[ slotid ] = FALSE; + Player_KillStat[ slotid ] = 0; + + // init msg ids + Player_LastMsgId[ slotid ] = 0; + Player_LastUpdateGameStateMsgId[ slotid ] = 0; + + // give UI feedback that a player has connected + TheGameExtraction.UI_PlayerConnectedFeedback( slotid ) ; + + } else { + + // refuse connection + DBGTXT( MSGOUT( "NET_RegisterRemotePlayer(): rejecting player %s (no slot available).", name ); ); + ADXTXT( NETs_PrintNode( node ); ); + } + + return slotid; +} + + + + +// reset global remote event list --------------------------------------------- +// +void NET_RmEvListReset() +{ + ASSERT( RE_LIST_ALLOC_SIZE >= RE_LIST_MAXAVAIL ); + + RE_List_Avail = RE_LIST_MAXAVAIL; + RE_List_CurPos = REListMem; + ( (RE_Header *) RE_List_CurPos )->RE_Type = RE_EMPTY; +} + + +// update global remote event list entries for lasers and missiles ------------ +// +void NET_RmEvListUpdateLocations() +{ + ASSERT( NetConnected ); + + // scan remote event list and update positions of lasers and missiles + RE_Header *relist = (RE_Header *) REListMem; + while ( relist->RE_Type != RE_EMPTY ) { + + if ( ( relist->RE_Type == RE_CREATELASER ) || + ( relist->RE_Type == RE_CREATEMISSILE ) ) { + + UPDTXT( + if ( relist->RE_Type == RE_CREATELASER ) { + MSGOUT( "NET_RmEvListUpdateLocations(): updating laser position." ); + } else { + MSGOUT( "NET_RmEvListUpdateLocations(): updating missile position." ); + } + ); + + GenObject *objectpo = ( relist->RE_Type == RE_CREATELASER ) ? (GenObject*)FetchFirstLaser() : (GenObject*)FetchFirstMissile(); + + RE_CreateLaser *re_entry = (RE_CreateLaser *) relist; + dword objnum = re_entry->HostObjId; + while ( ( objectpo != NULL ) && ( objectpo->HostObjNumber != objnum ) ) { + objectpo = objectpo->NextObj; + } + + if ( objectpo == NULL ) { + //NOTE: + // in GMSV mode, laser-creations are always sent to the server. + // a predicted collision, thus removal of the laser, is only visible to the local client + if ( NET_ConnectedPEER() ) { + re_entry->RE_Type = RE_DELETED; + } + } else { + memcpy( &re_entry->ObjPosition, &objectpo->ObjPosition, sizeof( Xmatrx ) ); + } + + } + + // advance to next event + ASSERT( ( relist->RE_BlockSize == RE_BLOCKSIZE_INVALID ) || + ( relist->RE_BlockSize == NET_RmEvGetSize( relist ) ) ); + relist = (RE_Header *) ( (char *) relist + NET_RmEvGetSize( relist ) ); + } +} + + +// clear remote event list in packet ------------------------------------------ +// +void NET_RmEvSingleClear( RE_Header* relist ) +{ + ASSERT( relist != NULL ); + relist->RE_Type = RE_EMPTY; +} + + + + +// store local playername as RE_List head ------------------------------------- +// +void NET_RmEvSinglePlayerName( RE_Header* relist ) +{ + ASSERT( relist != NULL ); + + RE_PlayerName *re_playername = (RE_PlayerName *) relist; + re_playername->RE_Type = RE_PLAYERNAME; + re_playername->RE_BlockSize = sizeof( RE_PlayerName ); + + ASSERT( sizeof( RE_PlayerName ) <= RE_LIST_MAXAVAIL ); + + // fill in local player's name + CopyRemoteName( re_playername->PlayerName, LocalPlayerName ); + + re_playername++; + re_playername->RE_Type = RE_EMPTY; +} + + +// store local player and ship state as RE_List head -------------------------- +// +void NET_RmEvSinglePlayerAndShipStatus( RE_Header* relist, word desired_player_state, int unjoin_flag, int killer ) +{ + ASSERT( relist != NULL ); + + RE_PlayerAndShipStatus* re_pas_status = (RE_PlayerAndShipStatus*) relist; + re_pas_status->RE_Type = RE_PLAYERANDSHIPSTATUS; + re_pas_status->RE_BlockSize = sizeof( RE_PlayerAndShipStatus ); + + re_pas_status->senderid = LocalPlayerId; + re_pas_status->objectindex = ObjClassShipIndex[ MyShip->ObjectClass ]; + + // set remote player's damage and speed from ship-object structure + re_pas_status->CurDamage = MyShip->CurDamage; + re_pas_status->CurShield = MyShip->CurShield; + re_pas_status->CurSpeed = MyShip->CurSpeed; + + // store current position of player + memcpy( &re_pas_status->ObjPosition, &MyShip->ObjPosition, sizeof( Xmatrx ) ); + + // store speeds for interpolation + re_pas_status->CurYaw = CurYaw; + re_pas_status->CurPitch = CurPitch; + re_pas_status->CurRoll = CurRoll; + re_pas_status->CurSlideHorz = CurSlideHorz; + re_pas_status->CurSlideVert = CurSlideVert; + + // set desired player status + re_pas_status->player_status = desired_player_state; + + // insert unjoin flag and killer + re_pas_status->params[ 0 ] = unjoin_flag; + //FIXME: do we really need the killer information in GMSV mode ( server should know this anyway ) + re_pas_status->params[ 2 ] = killer; + + // store the refframe for serverside movement checks + re_pas_status->RefFrame = SYSs_GetRefFrameCount(); + + re_pas_status++; + re_pas_status->RE_Type = RE_EMPTY; +} + +// store local player and ship state as RE_List head -------------------------- +// +void NET_RmEvSinglePlayerStatus( RE_Header* relist, word desired_player_state, int unjoin_flag, int killer ) +{ + ASSERT( relist != NULL ); + + RE_PlayerStatus* re_playerstatus = (RE_PlayerStatus*) relist; + re_playerstatus->RE_Type = RE_PLAYERSTATUS; + re_playerstatus->RE_BlockSize = sizeof( RE_PlayerStatus ); + + re_playerstatus->senderid = LocalPlayerId; + re_playerstatus->objectindex = ObjClassShipIndex[ MyShip->ObjectClass ]; + + // set desired player status + re_playerstatus->player_status = desired_player_state; + + // insert unjoin flag and killer + re_playerstatus->params[ 0 ] = unjoin_flag; + //FIXME: do we really need the killer information in GMSV mode ( server should know this anyway ) + re_playerstatus->params[ 2 ] = killer; + + // store the refframe for serverside movement checks + re_playerstatus->RefFrame = SYSs_GetRefFrameCount(); + + re_playerstatus++; + re_playerstatus->RE_Type = RE_EMPTY; +} + + + +// remote player action tracking for interpolation ---------------------------- +// +playerlerp_s Player_Interpolation[ MAX_NET_ALLOC_SLOTS ]; + + +// bit mask for determining whether orientation should be interpolated -------- +// +#define SLERP_ORIENTATION ( AUX_ADVANCED_PLAYERINTERPOLATION & 0x02 ) + + +// calc rotation matrix as result of slerp between two quaternions ------------ +// +INLINE +void CalcSlerpedOrientation( GenObject *playership, playerlerp_s *playerlerp ) +{ + ASSERT( playership != NULL ); + ASSERT( playerlerp != NULL ); + + // determine whether slerp info is valid + if ( playerlerp->curalpha == 0.0f ) + return; + + // advance slerp alpha + playerlerp->curalpha += playerlerp->incalpha * CurScreenRefFrames; + if ( playerlerp->curalpha > 1.0f ) { + playerlerp->curalpha = 1.0f; + } + + Xmatrx curbasematrix; + CalcSlerpedMatrix( curbasematrix, playerlerp ); + + // concat base with accumulated (yaw,pitch,roll) + MtxMtxMULt( curbasematrix, playerlerp->dstposition, DestXmatrx ); + + // set current orientation + playership->ObjPosition[ 0 ][ 0 ] = DestXmatrx[ 0 ][ 0 ]; + playership->ObjPosition[ 0 ][ 1 ] = DestXmatrx[ 0 ][ 1 ]; + playership->ObjPosition[ 0 ][ 2 ] = DestXmatrx[ 0 ][ 2 ]; + playership->ObjPosition[ 1 ][ 0 ] = DestXmatrx[ 1 ][ 0 ]; + playership->ObjPosition[ 1 ][ 1 ] = DestXmatrx[ 1 ][ 1 ]; + playership->ObjPosition[ 1 ][ 2 ] = DestXmatrx[ 1 ][ 2 ]; + playership->ObjPosition[ 2 ][ 0 ] = DestXmatrx[ 2 ][ 0 ]; + playership->ObjPosition[ 2 ][ 1 ] = DestXmatrx[ 2 ][ 1 ]; + playership->ObjPosition[ 2 ][ 2 ] = DestXmatrx[ 2 ][ 2 ]; +} + +// interpolate remote player orientation -------------------------------------- +// +PRIVATE +void InterpolateOrientation( GenObject *playership, playerlerp_s *playerlerp ) +{ + ASSERT( playership != NULL ); + ASSERT( playerlerp != NULL ); + + // check whether slerp in progress + int slerpactive = SLERP_ORIENTATION && ( playerlerp->curalpha < 1.0f ); + + // determine matrix for (yaw,pitch,roll) accumulation + pXmatrx activematrix = slerpactive ? playerlerp->dstposition : playership->ObjPosition; + + // interpolate yaw + bams_t yaw = playerlerp->curyaw * CurScreenRefFrames; + ObjRotY( activematrix, yaw ); + + // interpolate pitch + bams_t pitch = playerlerp->curpitch * CurScreenRefFrames; + ObjRotX( activematrix, pitch ); + + // interpolate roll + bams_t roll = playerlerp->curroll * CurScreenRefFrames; + ObjRotZ( activematrix, roll ); + + // slerp base orientation + if ( slerpactive ) { + CalcSlerpedOrientation( playership, playerlerp ); + } + +#ifdef _SLERP_DEBUGGING + //if ( playerlerp->curroll != BAMS_DEG0 ) { + //DBGTXT( MSGOUT( "InterpolateOrientation(): %d", slerpactive ); ); + //DBGTXT( DumpMatrix( activematrix ); ); + //DBGTXT( MSGOUT( "%d: PREDICTING: phi: %f", timeGetTime(), RAD_TO_DEG( acos( activematrix[ 0 ][ 0 ] ) ) ); ); + //} +#endif // _SLERP_DEBUGGING +} + + +// interpolate actions of remote player (motion) ------------------------------ +// +void NET_InterpolatePlayer( int playerid ) +{ + ASSERT( NetConnected ); + ASSERT( ( playerid >= 0 ) && ( playerid < MAX_NET_PROTO_PLAYERS ) ); + ASSERT( playerid != LocalPlayerId ); + + ASSERT( Player_Status[ playerid ] == PLAYER_JOINED ); + ASSERT( Player_ShipId[ playerid ] != SHIPID_NOSHIP ); + ASSERT( Player_ShipId[ playerid ] != SHIPID_LOCALPLAYER ); + + // disable interpolation +// if ( !AUX_ADVANCED_PLAYERINTERPOLATION ) +// return; + + GenObject *playership = Player_Ship[ playerid ]; + ASSERT( playership != NULL ); + playerlerp_s *playerlerp = &Player_Interpolation[ playerid ]; + + // interpolate orientation + InterpolateOrientation( playership, playerlerp ); + + // check whether transition in progress + if ( playerlerp->transition == 0 ) { + + // calculate the movement + Vector3 movement_offset; + CalcMovement( &movement_offset, + playership->ObjPosition, + playerlerp->curspeed, + playerlerp->curslidehorz, + playerlerp->curslidevert, + CurScreenRefFrames); + + // apply movement + playership->ObjPosition[ 0 ][ 3 ] += movement_offset.X; + playership->ObjPosition[ 1 ][ 3 ] += movement_offset.Y; + playership->ObjPosition[ 2 ][ 3 ] += movement_offset.Z; + + } else { + + ASSERT( playerlerp->transition > 0 ); + + playerlerp->transition -= CurScreenRefFrames; + if ( playerlerp->transition <= 0 ) { + + // after transition assume destination position + playership->ObjPosition[ 0 ][ 3 ] = playerlerp->dstposition[ 0 ][ 3 ]; + playership->ObjPosition[ 1 ][ 3 ] = playerlerp->dstposition[ 1 ][ 3 ]; + playership->ObjPosition[ 2 ][ 3 ] = playerlerp->dstposition[ 2 ][ 3 ]; + + // remaining frametime: advance in direction of flight + int remainder = -playerlerp->transition; + + // calculate the movement + Vector3 movement_offset; + CalcMovement( &movement_offset, + playership->ObjPosition, + playerlerp->curspeed, + playerlerp->curslidehorz, + playerlerp->curslidevert, + remainder ); + + playership->ObjPosition[ 0 ][ 3 ] += movement_offset.X; + playership->ObjPosition[ 1 ][ 3 ] += movement_offset.Y; + playership->ObjPosition[ 2 ][ 3 ] += movement_offset.Z; + + // turn transition off + playerlerp->transition = 0; + + } else { + + // interpolate position transition (along transition vector) + playership->ObjPosition[ 0 ][ 3 ] += playerlerp->transvec_x * CurScreenRefFrames; + playership->ObjPosition[ 1 ][ 3 ] += playerlerp->transvec_y * CurScreenRefFrames; + playership->ObjPosition[ 2 ][ 3 ] += playerlerp->transvec_z * CurScreenRefFrames; + } + } +} + + +// execute remote info about ship (as contained in GAME_STATE packet) --------- +// +void NET_ExecShipRemInfo( ShipRemInfo *info, ShipObject *shippo, int senderid ) +{ + ASSERT( NetConnected ); + ASSERT( info != NULL ); + ASSERT( shippo != NULL ); + ASSERT( ( senderid >= 0 ) && ( senderid < MAX_NET_PROTO_PLAYERS ) ); + ASSERT( senderid != LocalPlayerId ); + + // update remote player's damage and speed in ship-object structure + shippo->CurDamage = info->CurDamage; + shippo->CurShield = info->CurShield; + shippo->CurSpeed = info->CurSpeed; + shippo->NumMissls = info->NumMissls; + shippo->NumHomMissls = info->NumHomMissls; + shippo->NumMines = info->NumMines; + shippo->NumPartMissls = info->NumPartMissls; + // remember speeds (linear/angular) for interpolation + playerlerp_s *playerlerp = &Player_Interpolation[ senderid ]; + playerlerp->curspeed = info->CurSpeed; + playerlerp->curyaw = info->CurYaw; + playerlerp->curpitch = info->CurPitch; + playerlerp->curroll = info->CurRoll; + playerlerp->curslidehorz = info->CurSlideHorz; + playerlerp->curslidevert = info->CurSlideVert; + +#ifdef _SLERP_DEBUGGING + //if ( playerlerp->curroll != BAMS_DEG0 ) { + //DBGTXT( MSGOUT( "%d: NEW_REMOTE_STATE: phi: %f", timeGetTime(), RAD_TO_DEG( acos( info->ObjPosition[ 0 ][ 0 ] ) ) ); ); + //} +#endif // _SLERP_DEBUGGING + + // set new position of remote player + if ( AUX_ADVANCED_PLAYERINTERPOLATION ) { + + #define DEFAULT_PLAYERLERP_TRANSITION_TIME 200 + + if ( AUXDATA_PLAYERLERP_TRANSITION_TIME == 0 ) { + playerlerp->transition = DEFAULT_PLAYERLERP_TRANSITION_TIME; + } else { + playerlerp->transition = AUXDATA_PLAYERLERP_TRANSITION_TIME; + } + + // remember destination position + memcpy( playerlerp->dstposition, info->ObjPosition, sizeof( Xmatrx ) ); + + // position in last (prior to this) frame + Vector3 oldpos; + FetchTVector( shippo->ObjPosition, &oldpos ); + + // destination (current) position as contained in packet + Vector3 newpos; + FetchTVector( info->ObjPosition, &newpos ); + + // calculate the movement + Vector3 movement_offset; + CalcMovement( &movement_offset, + info->ObjPosition, + info->CurSpeed, + info->CurSlideHorz, + info->CurSlideVert, + (refframe_t)playerlerp->transition); + + // predict where destination position will be after transition time + newpos.X += movement_offset.X; + newpos.Y += movement_offset.Y; + newpos.Z += movement_offset.Z; + + // store corrected (predicted) destination + playerlerp->dstposition[ 0 ][ 3 ] = newpos.X; + playerlerp->dstposition[ 1 ][ 3 ] = newpos.Y; + playerlerp->dstposition[ 2 ][ 3 ] = newpos.Z; + + // account for this frame's step + playerlerp->transition += CurScreenRefFrames; + + // determine one step on path from current to destination position + float normfac = 1.0f / playerlerp->transition; + playerlerp->transvec_x = FLOAT_TO_GEOMV( GEOMV_TO_FLOAT( newpos.X - oldpos.X ) * normfac ); + playerlerp->transvec_y = FLOAT_TO_GEOMV( GEOMV_TO_FLOAT( newpos.Y - oldpos.Y ) * normfac ); + playerlerp->transvec_z = FLOAT_TO_GEOMV( GEOMV_TO_FLOAT( newpos.Z - oldpos.Z ) * normfac ); + + playerlerp->transition -= CurScreenRefFrames; + + if ( playerlerp->transition <= 0 ) { + + playerlerp->transition = 0; + memcpy( shippo->ObjPosition, info->ObjPosition, sizeof( Xmatrx ) ); + + } else { + + if ( SLERP_ORIENTATION ) { + + // orientation in previous frame + Quaternion srcquat; + QuaternionFromMatrx( &srcquat, shippo->ObjPosition ); + QuaternionMakeUnit( &srcquat ); + + // orientation state from packet + Quaternion dstquat; + QuaternionFromMatrx( &dstquat, info->ObjPosition ); + QuaternionMakeUnit( &dstquat ); + + // stepsize currently bound to poslerp speed + //FIXME: where does this constant come from + float stepsize = normfac; //* 1.2f; + + playerlerp->srcquat = srcquat; + playerlerp->dstquat = dstquat; + playerlerp->incalpha = stepsize; + playerlerp->curalpha = stepsize * CurScreenRefFrames; + + if ( playerlerp->curalpha > 1.0f ) { + playerlerp->curalpha = 1.0f; + } + + // fill in first slerped orientation + CalcSlerpedMatrix( shippo->ObjPosition, playerlerp ); + + // start with identity for (yaw,pitch,roll) accumulation + playerlerp->dstposition[ 0 ][ 0 ] = GEOMV_1; + playerlerp->dstposition[ 0 ][ 1 ] = GEOMV_0; + playerlerp->dstposition[ 0 ][ 2 ] = GEOMV_0; + playerlerp->dstposition[ 1 ][ 0 ] = GEOMV_0; + playerlerp->dstposition[ 1 ][ 1 ] = GEOMV_1; + playerlerp->dstposition[ 1 ][ 2 ] = GEOMV_0; + playerlerp->dstposition[ 2 ][ 0 ] = GEOMV_0; + playerlerp->dstposition[ 2 ][ 1 ] = GEOMV_0; + playerlerp->dstposition[ 2 ][ 2 ] = GEOMV_1; + + } else { + + // simply assume new orientation + shippo->ObjPosition[ 0 ][ 0 ] = info->ObjPosition[ 0 ][ 0 ]; + shippo->ObjPosition[ 0 ][ 1 ] = info->ObjPosition[ 0 ][ 1 ]; + shippo->ObjPosition[ 0 ][ 2 ] = info->ObjPosition[ 0 ][ 2 ]; + shippo->ObjPosition[ 1 ][ 0 ] = info->ObjPosition[ 1 ][ 0 ]; + shippo->ObjPosition[ 1 ][ 1 ] = info->ObjPosition[ 1 ][ 1 ]; + shippo->ObjPosition[ 1 ][ 2 ] = info->ObjPosition[ 1 ][ 2 ]; + shippo->ObjPosition[ 2 ][ 0 ] = info->ObjPosition[ 2 ][ 0 ]; + shippo->ObjPosition[ 2 ][ 1 ] = info->ObjPosition[ 2 ][ 1 ]; + shippo->ObjPosition[ 2 ][ 2 ] = info->ObjPosition[ 2 ][ 2 ]; + } + + // advance first step in position + shippo->ObjPosition[ 0 ][ 3 ] += playerlerp->transvec_x * CurScreenRefFrames; + shippo->ObjPosition[ 1 ][ 3 ] += playerlerp->transvec_y * CurScreenRefFrames; + shippo->ObjPosition[ 2 ][ 3 ] += playerlerp->transvec_z * CurScreenRefFrames; + } + + } else { + + // simply set ship to new position (no smooth transition) + playerlerp->transition = 0; + memcpy( shippo->ObjPosition, info->ObjPosition, sizeof( Xmatrx ) ); + } +} + + +// fill ShipRemInfo structure for specified ship ------------------------------ +// +void NET_FillShipRemInfo( ShipRemInfo *info, ShipObject *shippo, int playerid ) +{ + ASSERT( NetConnected ); + ASSERT( info != NULL ); + ASSERT( shippo != NULL ); + ASSERT( ( playerid >= 0 ) && ( playerid < MAX_NET_PROTO_PLAYERS ) ); + + // set remote player's damage and speed from ship-object structure + info->CurDamage = shippo->CurDamage; + info->CurShield = shippo->CurShield; + info->CurSpeed = shippo->CurSpeed; + + // store current position of player + memcpy( &info->ObjPosition, &shippo->ObjPosition, sizeof( Xmatrx ) ); + + // store speeds for interpolation + if ( shippo == MyShip ) { + info->CurYaw = CurYaw; + info->CurPitch = CurPitch; + info->CurRoll = CurRoll; + info->CurSlideHorz = CurSlideHorz; + info->CurSlideVert = CurSlideVert; + } else { + info->CurYaw = Player_Interpolation[ playerid ].curyaw; + info->CurPitch = Player_Interpolation[ playerid ].curpitch; + info->CurRoll = Player_Interpolation[ playerid ].curroll; + info->CurSlideHorz = Player_Interpolation[ playerid ].curslidehorz; + info->CurSlideVert = Player_Interpolation[ playerid ].curslidevert; + } +} + + +// if this is enabled the kill stats will not be updated from packets --------- +// +int kill_stats_frozen = FALSE; + + +// freeze kill stats in order to allow clean reset everywhere ----------------- +// +void NET_FreezeKillStats( int freeze ) +{ + kill_stats_frozen = freeze; +} + + + + + +// update tables and create ship for newly joined player ---------------------- +// +PRIVATE +void PerformJoin( RE_PlayerAndShipStatus* pas_status ) +{ + ASSERT( NetConnected ); + ASSERT( pas_status != NULL ); + ASSERT( pas_status->player_status == PLAYER_JOINED ); + + int nClientID = pas_status->senderid; + ASSERT( ( nClientID >= 0 ) && ( nClientID < MAX_NET_PROTO_PLAYERS ) ); + ASSERT( nClientID != LocalPlayerId ); + + // there must not be a ship for this player + ASSERT( Player_Ship [ nClientID ] == NULL ); + ASSERT( Player_ShipId[ nClientID ] == SHIPID_NOSHIP ); + + // update player status + Player_Status[ nClientID ] = PLAYER_JOINED; + + // create ship object for new player + dword objclass = ShipClasses[ pas_status->objectindex ]; + ASSERT( objclass != CLASS_ID_INVALID ); + + GenObject *objectpo = CreateObject( objclass, pas_status->ObjPosition ); + ASSERT( objectpo != NULL ); + + objectpo->HostObjNumber = ShipHostObjId( nClientID ); + Player_Ship [ nClientID ] = objectpo; + Player_ShipId[ nClientID ] = objectpo->ObjectNumber; + + // reset target if locked onto newly joined player + if ( TargetObjNumber == objectpo->HostObjNumber ) { + + TargetObjNumber = TARGETID_NO_TARGET; + TargetVisible = FALSE; + + //NOTE: + // this caused a very subtle bug when a player rejoined + // before the explosion animation of his old ship reached + // the stage where the target would be removed. the target + // would then jump from the old ship to the new ship, possibly + // also causing a problem with target caret drawing when the + // new ship joined behind the local player. + } + + // display object id of remote player's ship + if ( AUX_DISPLAY_REMOTE_JOIN_OBJECT_ID ) { + MSGOUT( "player %s joined and got object id %d.", Player_Name[ nClientID ], Player_ShipId[ nClientID ] ); + } + + // give UI feedback when player has joined + TheGameExtraction.UI_PlayerJoinedFeedback( (RE_PlayerStatus*)pas_status ) ; + +} + +// update tables and delete ship of player who unjoined ----------------------- +// +PRIVATE +void PerformUnjoin( RE_PlayerAndShipStatus* pas_status ) +{ + ASSERT( NetConnected ); + ASSERT( pas_status != NULL ); + + int nClientID = pas_status->senderid; + ASSERT( ( nClientID >= 0 ) && ( nClientID < MAX_NET_PROTO_PLAYERS ) ); + ASSERT( nClientID != LocalPlayerId ); + + // check desired player status + ASSERT( pas_status->player_status == PLAYER_CONNECTED ); + + // there has to be a ship for this player + ShipObject *shippo = (ShipObject *) Player_Ship[ nClientID ]; + ASSERT( shippo != NULL ); + + ASSERT( Player_ShipId[ nClientID ] != SHIPID_NOSHIP ); + ASSERT( Player_ShipId[ nClientID ] != SHIPID_LOCALPLAYER ); + + if ( shippo != NULL ) { + + // downing of ship starts explosion + if ( pas_status->params[ 0 ] == SHIP_DOWNED ) { + + // exec gamecode when player ship is downed + TheGameExtraction.GC_UnjoinPlayer_ShipDowned( pas_status->senderid, + pas_status->params[ 2 ] - KILLERID_BIAS, + pas_status->params[ 3 ] ) ; + + // user exit opens stargate and deletes ship + } else if ( pas_status->params[ 0 ] == USER_EXIT ) { + + // exec gamecode when player exits to menu + TheGameExtraction.GC_UnjoinPlayer_UserExit( (RE_PlayerStatus*)pas_status ) ; + } + } + + // signify that player is only connected, but not joined. + Player_Status[ nClientID ] = PLAYER_CONNECTED; + Player_Ship [ nClientID ] = NULL; + Player_ShipId[ nClientID ] = SHIPID_NOSHIP; + + // give UI feedback when player has unjoined + TheGameExtraction.UI_PlayerUnjoinedFeedback( (RE_PlayerStatus*)pas_status ) ; +} + +// set the desired player status ( inactive/joined/unjoined ) ----------------- +// +void NET_SetDesiredPlayerStatus( RE_PlayerAndShipStatus* pas_status ) +{ + ASSERT( NetConnected ); + ASSERT( pas_status != NULL ); + + // identify sender + int nClientID = pas_status->senderid; + ASSERT( ( nClientID >= 0 ) && ( nClientID < MAX_NET_PROTO_PLAYERS ) ); + ASSERT( nClientID != LocalPlayerId ); + + switch( pas_status->player_status ) { + // only join/unjoin allowed + case PLAYER_INACTIVE: + { + ASSERT( FALSE ); + } + break; + // desired player_status is CONNECTED ( unjoin/ensure unjoined ) + case PLAYER_CONNECTED: + { + // player must be joined in order to unjoin + if ( Player_Status[ nClientID ] == PLAYER_CONNECTED ) { + return; + } + + // player must be connected in order to unjoin + if ( Player_Status[ nClientID ] == PLAYER_INACTIVE ) { + DBGTXT( MSGOUT( "NET_SetDesiredPlayerStatus(): filtering unjoin for inactive player: %d.", nClientID ); ); + return; + } + + // player status must be JOINED + ASSERT( Player_Status[ nClientID ] == PLAYER_JOINED ); + + // update tables and delete ship + PerformUnjoin( pas_status ); + + // player status must now be CONNECTED + ASSERT( Player_Status[ nClientID ] == PLAYER_CONNECTED ); + + DBGTXT( MSGOUT( "NET_SetDesiredPlayerStatus(): unjoined player %d.", nClientID ); ); + + } + break; + // desired player_status is JOINED ( join/ensure joined ) + case PLAYER_JOINED: + { + // player must not be joined already in order to join + if ( Player_Status[ nClientID ] == PLAYER_JOINED ) { + return; + } + + // player must be connected in order to join + if ( Player_Status[ nClientID ] == PLAYER_INACTIVE ) { + DBGTXT( MSGOUT( "NET_SetDesiredPlayerStatus (): filtering join for inactive player: %d.", nClientID ); ); + return; + } + + // player status must be CONNECTED + ASSERT( Player_Status[ nClientID ] == PLAYER_CONNECTED ); + + // update tables and create ship + PerformJoin( pas_status ); + + // player status must now be JOINED + ASSERT( Player_Status[ nClientID ] == PLAYER_JOINED ); + + DBGTXT( MSGOUT( "NET_SetDesiredPlayerStatus(): joined player %d.", nClientID ); ); + } + break; + default: + ASSERT( FALSE ); + break; + } +} + + +// remote player has disconnected (left universe) ----------------------------- +// +void NET_PlayerDisconnected( int playerid ) +{ + ASSERT( NetConnected ); + ASSERT( ( playerid >= 0 ) && ( playerid < MAX_NET_PROTO_PLAYERS ) ); + + // player must be connected in order to disconnect + if ( Player_Status[ playerid ] == PLAYER_INACTIVE ) { + DBGTXT( MSGOUT( "NET_PlayerDisconnected(): filtering disconnect for inactive player: %d.", playerid ); ); + return; + } + + // player must not be joined in order to disconnect + if ( Player_Status[ playerid ] != PLAYER_CONNECTED ) { + DBGTXT( MSGOUT( "NET_PlayerDisconnected(): filtering disconnect for joined player: %d.", playerid ); ); + return; + } + + // set player inactive + Player_Status[ playerid ] = PLAYER_INACTIVE; + ASSERT( Player_Ship[ playerid ] == NULL ); + ASSERT( Player_ShipId[ playerid ] == SHIPID_NOSHIP ); + + // decrease global number of remote players + NumRemPlayers--; + + // give UI feedback when player disconnects + TheGameExtraction.UI_PlayerDisconnectedFeedback( playerid ) ; + + DBGTXT( MSGOUT( "NET_PlayerDisconnected(): player with id %d has disconnected.", playerid ); ); +} + + +// remove player if he didn't send a packet in too long a time ---------------- +// +void NET_RemovePlayer( int playerid ) +{ + ASSERT( NetConnected ); + ASSERT( ( playerid >= 0 ) && ( playerid < MAX_NET_PROTO_PLAYERS ) ); + + // player must be active in order to be removed + if ( Player_Status[ playerid ] == PLAYER_INACTIVE ) { + DBGTXT( MSGOUT( "NET_RemovePlayer(): filtering redundant removement of inactive player: %d.", playerid ); ); + return; + } + + ASSERT( REMOTE_PLAYER_ACTIVE( playerid ) ); + if ( playerid == LocalPlayerId ) { + DBGTXT( MSGOUT( "NET_RemovePlayer(): filtering removement of local player: %d.", playerid ); ); + return; + } + + DBGTXT( MSGOUT( "NET_RemovePlayer(): removing player: %s (id %d).", Player_Name[ playerid ], playerid ); ); + DBGTXT( NETs_PrintNode( &Player_Node[ playerid ] ); ); + + // if player is joined ship must be destroyed + if ( Player_Status[ playerid ] == PLAYER_JOINED ) { + + DBGTXT( MSGOUT( "NET_RemovePlayer(): removing remote ship." ); ); + + ShipObject *shippo = (ShipObject *) Player_Ship[ playerid ]; + ASSERT( shippo != NULL ); + ASSERT( Player_ShipId[ playerid ] != SHIPID_LOCALPLAYER ); + ASSERT( Player_ShipId[ playerid ] != SHIPID_NOSHIP ); + KillSpecificObject( Player_ShipId[ playerid ], PShipObjects ); + + } else { + + ASSERT( Player_Ship[ playerid ] == NULL ); + ASSERT( Player_ShipId[ playerid ] == SHIPID_NOSHIP ); + } + + // remove player from table + Player_Status[ playerid ] = PLAYER_INACTIVE; + Player_Ship[ playerid ] = NULL; + Player_ShipId[ playerid ] = SHIPID_NOSHIP; + + // decrease global number of remote players + ASSERT( NumRemPlayers > 1 ); + NumRemPlayers--; + + // give UI feedback when player is removed + TheGameExtraction.UI_PlayerWasRemovedFeedback( playerid ) ; +} + + + -- cgit v1.2.3