aboutsummaryrefslogtreecommitdiff
path: root/src/parsec/net_rmev_gmsv.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_rmev_gmsv.cpp
downloadopenparsec-main.tar.xz
openparsec-main.zip
initial importHEADmain
Diffstat (limited to 'src/parsec/net_rmev_gmsv.cpp')
-rw-r--r--src/parsec/net_rmev_gmsv.cpp652
1 files changed, 652 insertions, 0 deletions
diff --git a/src/parsec/net_rmev_gmsv.cpp b/src/parsec/net_rmev_gmsv.cpp
new file mode 100644
index 0000000..f1dc607
--- /dev/null
+++ b/src/parsec/net_rmev_gmsv.cpp
@@ -0,0 +1,652 @@
+/*
+ * PARSEC - Remote Events
+ *
+ * $Author: uberlinuxguy $ - $Date: 2004/09/26 03:43:40 $
+ *
+ * Orginally written by:
+ * Copyright (c) Clemens Beer <cbx@parsec.org> 2002
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+// 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"
+
+// mathematics header
+#include "utl_math.h"
+
+// local module header
+#include "net_rmev_gmsv.h"
+
+// proprietary module headers
+#include "con_aux.h"
+//#include "con_say.h"
+//#include "h_supp.h"
+#include "net_prediction.h"
+#include "net_game.h"
+#include "net_game_gmsv.h"
+#include "net_rmev.h"
+#include "net_serv.h"
+#include "net_stream.h"
+#include "net_udpdf.h"
+#include "net_univ.h"
+#include "obj_clas.h"
+#include "obj_creg.h"
+#include "obj_ctrl.h"
+#include "obj_cust.h"
+#include "obj_xtra.h"
+//#include "g_sfx.h"
+//#include "g_wfx.h"
+//#include "g_emp.h"
+//#include "g_swarm.h"
+#include "g_stgate.h"
+
+
+// process entire remote event list (execute all contained events) coming from the server
+//
+void NET_ProcessRmEvList_GMSV( NetPacket_GMSV* gamepacket )
+{
+ ASSERT( gamepacket != NULL );
+
+ //NOTE:
+ // if new remote events are added this function
+ // should be updated.
+
+ RE_Header *pREList = (RE_Header *) &gamepacket->RE_List;
+
+ // default to invalid owner
+ int ownerid = PLAYERID_ANONYMOUS;
+
+ // process remote event list
+ while ( pREList->RE_Type != RE_EMPTY ) {
+
+ //MSGOUT( "processing RE type %d", pREList->RE_Type );
+
+ switch ( pREList->RE_Type ) {
+
+ case RE_OWNERSECTION:
+ // get the ownerid from RE_OWNERSECTION
+ ownerid = ( (RE_OwnerSection*)pREList )->owner;
+ break;
+
+ case RE_DELETED:
+ // ignore
+ break;
+
+ case RE_CREATEOBJECT:
+ if ( NetConnected ) {
+ NET_ExecRmEvCreateObject( pREList, ownerid );
+ }
+ break;
+
+ case RE_CREATELASER:
+
+ if ( NetConnected ) {
+ // ownerid is taken from RE
+ NET_ExecRmEvCreateLaser( pREList, PLAYERID_ANONYMOUS );
+ }
+ break;
+
+ case RE_CREATEMISSILE:
+
+ if ( NetConnected ) {
+ NET_ExecRmEvCreateMissile( pREList, ownerid );
+ }
+ break;
+
+ case RE_PARTICLEOBJECT:
+
+ if ( NetConnected ) {
+ //FIXME: POBJ_ENERGYFIELD ??? CBX: ask SID/MSH why ???
+ NET_ExecRmEvParticleObject( pREList, ownerid );
+ }
+ break;
+
+ case RE_CREATEEXTRA:
+ // not supported anymore
+ ASSERT( FALSE );
+ break;
+
+ case RE_CREATEEXTRA2:
+
+ if ( NetConnected ) {
+
+ int owner = GetOwnerFromHostOjbNumber( ((RE_CreateExtra2*)pREList)->HostObjId );
+ NET_ExecRmEvCreateExtra2( pREList, owner );
+ }
+ break;
+
+ case RE_KILLOBJECT:
+
+ if ( NetConnected ) {
+ NET_ExecRmEvKillObject( pREList );
+ }
+ break;
+
+ case RE_SENDTEXT:
+
+ if ( NetConnected ) {
+ NET_ExecRmEvSendText( pREList, ownerid );
+ }
+ break;
+
+ case RE_PLAYERNAME:
+
+ if ( NetConnected ) {
+ NET_ExecRmEvPlayerName( pREList, ownerid );
+ }
+ break;
+
+ case RE_WEAPONSTATE:
+
+ if ( NetConnected ) {
+ NET_ExecRmEvWeaponState( pREList, ownerid );
+ }
+ break;
+
+ case RE_STATESYNC:
+
+ if ( NetConnected ) {
+ NET_ExecRmEvStateSync( pREList, ownerid );
+ }
+ break;
+
+ case RE_CREATESWARM:
+
+ if ( NetConnected ) {
+ NET_ExecRmEvCreateSwarm( pREList, ownerid );
+ }
+ break;
+
+ case RE_CREATEEMP:
+
+ if ( NetConnected ) {
+ NET_ExecRmEvCreateEmp( pREList, ownerid );
+ }
+ break;
+
+ case RE_PLAYERSTATUS:
+ // should never be received by a client
+ DBGTXT( MSGOUT( "WARNING ! received RE_PLAYERSTATUS from server." ); );
+ ASSERT( FALSE );
+ break;
+
+ case RE_PLAYERANDSHIPSTATUS:
+
+ if ( NetConnected ) {
+ RE_PlayerAndShipStatus* pas_status = (RE_PlayerAndShipStatus*)pREList;
+
+ // set the desired playerstate ( PLAYER_INACTIVE, PLAYER_CONNECTED, PLAYER_JOINED ) for remote clients
+ if ( pas_status->senderid != LocalPlayerId ) {
+ NET_SetDesiredPlayerStatus( pas_status );
+ }
+
+ // set the shipstate according to the remote event ( also mainains playerstate for local player )
+ NET_ExecRmEvPlayerAndShipStatus( pas_status, gamepacket->MessageId );
+ }
+ break;
+ case RE_KILLSTATS:
+
+ if ( NetConnected ) {
+ // update the killstats according to the ones on the server
+ NET_ExecRmEvKillStats( (RE_KillStats*)pREList );
+ }
+
+ break;
+
+ case RE_GAMESTATE:
+
+ if ( NetConnected ) {
+ // execute RE_GameState
+ NET_ExecRmEvGameState( (RE_GameState*) pREList );
+ }
+ break;
+
+ case RE_COMMANDINFO:
+
+ // execute the single remote event containing the CommandInfo
+ NET_ExecRmEvCommandInfo( (RE_CommandInfo*) pREList );
+
+ // check whether a RE_COMMANDINFO in the stream disconnected us
+ // stop executing remote events
+ if ( NetConnected == NETWORK_GAME_OFF ) {
+ return;
+ }
+
+ break;
+
+ case RE_CLIENTINFO:
+
+ // RE_ClientInfo is C->S only
+ ASSERT( FALSE );
+ break;
+
+ case RE_IPV4SERVERINFO:
+
+ // can be received when connected and when not connected
+ NET_ExecRmEvIPv4ServerInfo( (RE_IPv4ServerInfo*) pREList );
+ break;
+
+ case RE_SERVERLINKINFO:
+
+ // can be received when connected and when not connected
+ NET_ExecRmEvServerLinkInfo( (RE_ServerLinkInfo*) pREList );
+ break;
+
+ case RE_MAPOBJECT:
+ // can be received when connected and when not connected
+ NET_ExecRmEvMapObject( (RE_MapObject*) pREList );
+ break;
+
+ case RE_STARGATE:
+ // can only be received when connected
+ NET_ExecRmEvStargate( (RE_Stargate*) pREList );
+ break;
+
+ default:
+ MSGOUT( "ProcessRmEvList_GMSV(): unknown remote event (%d).", pREList->RE_Type );
+ }
+
+ // advance to next event in list
+ ASSERT( ( pREList->RE_BlockSize == RE_BLOCKSIZE_INVALID ) ||
+ ( pREList->RE_BlockSize == NET_RmEvGetSize( pREList ) ) );
+ pREList = (RE_Header *) ( (char *) pREList + NET_RmEvGetSize( pREList ) );
+ }
+}
+
+
+// dump the ship remote info to the console -----------------------------------
+//
+static
+void DumpShipRemInfo( ShipRemInfo* pShipRemInfo )
+{
+ MSGOUT( "CurYaw %d CurPitch %d CurRoll %d CurSlideHorz %d CurSlideVert %d", pShipRemInfo->CurYaw,
+ pShipRemInfo->CurPitch,
+ pShipRemInfo->CurRoll,
+ pShipRemInfo->CurSlideHorz,
+ pShipRemInfo->CurSlideVert );
+}
+
+
+// execute received remote event: create extra (2)-----------------------------
+//
+void NET_ExecRmEvCreateExtra2( RE_Header *rmev, int ownerid )
+{
+ ASSERT( rmev != NULL );
+ ASSERT( rmev->RE_Type == RE_CREATEEXTRA2 );
+ ASSERT( ( ( ownerid >= 0 ) && ( ownerid < MAX_NET_PROTO_PLAYERS ) ) || ( ownerid == PLAYERID_SERVER ) );
+
+ RE_CreateExtra2* re_ce2 = (RE_CreateExtra2 *) rmev;
+
+ // resolve to class id
+ ASSERT( re_ce2->ExtraIndex < NumExtraClasses );
+ dword classid = ExtraClasses[ re_ce2->ExtraIndex ];
+
+ // create object if extra available on this client
+ if ( classid != CLASS_ID_INVALID ) {
+
+ ExtraObject *extrapo = (ExtraObject *) CreateObject( classid, re_ce2->ObjPosition );
+ ASSERT( extrapo != NULL );
+ extrapo->HostObjNumber = re_ce2->HostObjId;
+ if ( extrapo->ObjectType == MINE1TYPE ) {
+ ASSERT( ownerid != PLAYERID_SERVER );
+ ((MineObject*)extrapo)->Owner = ownerid;
+ AUD_Mine( extrapo );
+ } else {
+ OBJ_FillExtraMemberVars( extrapo );
+ }
+
+ // get drift vector
+ extrapo->DriftVec.X = re_ce2->DriftVec.X;
+ extrapo->DriftVec.Y = re_ce2->DriftVec.Y;
+ extrapo->DriftVec.Z = re_ce2->DriftVec.Z;
+
+ // get drift time
+ extrapo->DriftTimeout = re_ce2->DriftTimeout;
+
+ RMEVTXT( MSGOUT( "--executing RE_CREATEEXTRA2: class %d, obj %d.", classid, extrapo->HostObjNumber ); );
+ }
+}
+
+
+// execute RE containing a command info ---------------------------------------
+//
+void NET_ExecRmEvCommandInfo( RE_CommandInfo* commandinfo )
+{
+ ASSERT( commandinfo->RE_Type == RE_COMMANDINFO );
+
+ // copy to safe buffer
+ char recvline[ MAX_RE_COMMANDINFO_COMMAND_LEN + 1 ];
+ memset( recvline, 0, ( MAX_RE_COMMANDINFO_COMMAND_LEN + 1 ) * sizeof( char ) );
+ ASSERT( sizeof( recvline ) >= sizeof( commandinfo->command ) );
+ strncpy( recvline, commandinfo->command, MAX_RE_COMMANDINFO_COMMAND_LEN );
+ recvline[ MAX_RE_COMMANDINFO_COMMAND_LEN ] = 0;
+
+ // only process non-empty commands
+ if( *recvline != '\0' ) {
+ // process the command
+ NET_ServerParseMessage( recvline );
+ }
+
+ DBGTXT(MSGOUT( "NET_ExecRmEvCommandInfo: %s", recvline ););
+}
+
+
+// exectue RE containing a serverinfo from the masterserver -------------------
+//
+void NET_ExecRmEvIPv4ServerInfo( RE_IPv4ServerInfo* pIPv4ServerInfo )
+{
+ ASSERT( pIPv4ServerInfo != NULL );
+
+ MSGOUT( "got server %d from masterserver: %d.%d.%d.%d:%d at pos %d/%d",
+ pIPv4ServerInfo->serverid,
+ pIPv4ServerInfo->node[ 0 ],
+ pIPv4ServerInfo->node[ 1 ],
+ pIPv4ServerInfo->node[ 2 ],
+ pIPv4ServerInfo->node[ 3 ],
+ UDP_GetNodePort( (node_t*)pIPv4ServerInfo->node ),
+ pIPv4ServerInfo->xpos,
+ pIPv4ServerInfo->ypos
+ );
+
+ // try to update server in serverlist ( also appends to empty serverlist, if no full serverlist has yet been requested )
+ if ( !NET_ServerList_UpdateServer( pIPv4ServerInfo ) ) {
+
+ // normal add to the serverlist ( no update )
+ NET_ServerList_AddServer( pIPv4ServerInfo );
+ }
+}
+
+
+// exectue RE containing a server linkinfo from the masterserver --------------
+//
+void NET_ExecRmEvServerLinkInfo( RE_ServerLinkInfo* pServerLinkInfo )
+{
+ ASSERT( pServerLinkInfo != NULL );
+
+ /*MSGOUT( "got linkinfo from masterserver: %d to %d, flags: %d",
+ pServerLinkInfo->serverid1,
+ pServerLinkInfo->serverid2,
+ pServerLinkInfo->flags );
+ */
+
+ // link the 2 servers
+ NET_ServerList_AddLinkInfo( pServerLinkInfo );
+}
+
+
+// exectue RE containing a map object to be displayed in the starmap ----------
+//
+void NET_ExecRmEvMapObject( RE_MapObject* pMapObject )
+{
+ ASSERT( pMapObject != NULL );
+
+ // link the 2 servers
+ NET_ServerList_AddMapObject( pMapObject );
+}
+
+
+// exectue RE containing a stargate -------------------------------------------
+//
+void NET_ExecRmEvStargate( RE_Stargate* pRE_Stargate )
+{
+ ASSERT( pRE_Stargate != NULL );
+ ASSERT( NetConnected );
+
+ // found an existing stargate ?
+ Stargate* stargate = NET_FindStargate( pRE_Stargate->serverid );
+
+ // create new stargate ?
+ if ( stargate == NULL ) {
+
+ DBGTXT( MSGOUT( "adding stargate to %d at (%d %d %d)",
+ pRE_Stargate->serverid,
+ pRE_Stargate->pos[ 0 ],
+ pRE_Stargate->pos[ 1 ],
+ pRE_Stargate->pos[ 2 ] ); );
+
+ dword objclass = OBJ_FetchObjectClassId( "stargate" );
+
+ if ( objclass != CLASS_ID_INVALID ) {
+
+ Xmatrx startm;
+ MakeIdMatrx( startm );
+ startm[ 0 ][ 3 ] = pRE_Stargate->pos[ 0 ];
+ startm[ 1 ][ 3 ] = pRE_Stargate->pos[ 1 ];
+ startm[ 2 ][ 3 ] = pRE_Stargate->pos[ 2 ];
+
+ startm[ 0 ][ 2 ] = pRE_Stargate->dir[ 0 ];
+ startm[ 1 ][ 2 ] = pRE_Stargate->dir[ 1 ];
+ startm[ 2 ][ 2 ] = pRE_Stargate->dir[ 2 ];
+
+ // ensure orthogonal matrix
+ CrossProduct2( &startm[ 0 ][ 1 ], &startm[ 0 ][ 2 ], &startm[ 0 ][ 0 ] );
+ CrossProduct2( &startm[ 0 ][ 0 ], &startm[ 0 ][ 2 ], &startm[ 0 ][ 1 ] );
+
+ stargate = (Stargate *) SummonObject( objclass, startm );
+ } else {
+ MSGOUT( "object class stargate not found." );
+ }
+ }
+
+ // read properties with persistency callback
+ if ( stargate != NULL ) {
+ if ( stargate->callback_persist != NULL ) {
+ stargate->callback_persist( (CustomObject*)stargate, FALSE, (void*)pRE_Stargate );
+ }
+
+ // request server information for server
+ NET_ServerList_Get( Masters[ 0 ], stargate->serverid );
+ }
+}
+
+
+// execute RE holding newest player & ship state -----------------------------
+//
+void NET_ExecRmEvPlayerAndShipStatus( RE_PlayerAndShipStatus* pas_state, dword messageid )
+{
+ ASSERT( pas_state != NULL );
+ ASSERT( pas_state->RE_Type == RE_PLAYERANDSHIPSTATUS );
+
+ // get the sender
+ int nClientID = pas_state->senderid;
+
+ UPDTXT( MSGOUT( "updating game state for client %d (msg=%d).", nClientID, messageid ); );
+
+ // keep player from being kicked out ( only done in PEER )
+ Player_AliveCounter[ nClientID ] = MAX_ALIVE_COUNTER;
+
+ // set up-to-date flag (i.e., no interpolation need be performed since real data is available).
+ Player_UpToDate[ nClientID ] = TRUE;
+
+ // ship status will only be updated on newer messages
+ if ( messageid > (dword)Player_LastUpdateGameStateMsgId[ nClientID ] ) {
+ if ( nClientID == LocalPlayerId ) {
+
+ // set the properties/speeds/position of the local player
+ NET_ResyncLocalPlayer( pas_state );
+
+ // clear all predictions made obsolete with this update from the server
+ // and re-apply the ones not yet ACK
+ ThePredictionManager.DoCheckpoint( ServerStream.GetLastACKMessageId() );
+
+ } else {
+
+ // player must be joined in order to send GAME_STATE
+ if ( Player_Status[ nClientID ] != PLAYER_JOINED ) {
+ DBGTXT( MSGOUT( "filtering GAME_STATE for player who is not joined: %d.", nClientID ); );
+ return;
+ }
+
+ // ensure correct player state
+ ASSERT( Player_Status[ nClientID ] == pas_state->player_status );
+
+ ShipRemInfo _ShipRemInfo;
+ memcpy( &_ShipRemInfo.ObjPosition, pas_state->ObjPosition, sizeof( Xmatrx ) );
+ _ShipRemInfo.CurDamage = pas_state->CurDamage;
+ _ShipRemInfo.CurShield = pas_state->CurShield;
+ _ShipRemInfo.CurSpeed = pas_state->CurSpeed;
+ _ShipRemInfo.CurYaw = pas_state->CurYaw;
+ _ShipRemInfo.CurPitch = pas_state->CurPitch;
+ _ShipRemInfo.CurRoll = pas_state->CurRoll;
+ _ShipRemInfo.CurSlideHorz = pas_state->CurSlideHorz;
+ _ShipRemInfo.CurSlideVert = pas_state->CurSlideVert;
+ _ShipRemInfo.NumMissls = pas_state->NumMissls;
+ _ShipRemInfo.NumHomMissls = pas_state->NumHomMissls;
+ _ShipRemInfo.NumMines = pas_state->NumMines;
+ _ShipRemInfo.NumPartMissls = pas_state->NumPartMissls;
+
+ // fetch pointer to remote player's local ship-object
+ ShipObject *playerobj = (ShipObject *) Player_Ship[ nClientID ];
+ ASSERT( playerobj != NULL );
+
+ //FIXME: we must change this to work with RE_PlayerShipAndStatus
+ NET_ExecShipRemInfo( &_ShipRemInfo, playerobj, nClientID );
+ }
+ } else {
+ DBGTXT( MSGOUT( "NET_ExecRmEvShipStatus(): supressed update from packet %d ( already had %d )", messageid, Player_LastUpdateGameStateMsgId[ nClientID ] ); );
+ }
+
+ // ensure message ids are monotonically increasing
+ Player_LastUpdateGameStateMsgId[ nClientID ] = max( (dword)Player_LastUpdateGameStateMsgId[ nClientID ], messageid );
+}
+
+
+// store commandinfo as RE_List head ------------------------------------------
+//
+RE_CommandInfo* NET_RmEvSingleCommandInfo( NetPacket_GMSV* gamepacket, const char* commandstring )
+{
+ ASSERT( gamepacket != NULL );
+ ASSERT( gamepacket->Command == PKTP_COMMAND );
+ RE_CommandInfo* re_commandinfo = (RE_CommandInfo*)&gamepacket->RE_List;
+
+ int len = strlen( commandstring );
+ if ( len > MAX_RE_COMMANDINFO_COMMAND_LEN ) {
+ len = MAX_RE_COMMANDINFO_COMMAND_LEN;
+ }
+
+ re_commandinfo->RE_Type = RE_COMMANDINFO;
+ re_commandinfo->RE_BlockSize = len + 1/*string terminator '\0'*/ + 2/*header*/ + 1/*code*/;
+ strncpy(re_commandinfo->command, commandstring, MAX_RE_COMMANDINFO_COMMAND_LEN );
+ re_commandinfo->command[ MAX_RE_COMMANDINFO_COMMAND_LEN ] = 0;
+
+ char* pNext = (char*)re_commandinfo;
+ pNext += re_commandinfo->RE_BlockSize;
+ ((RE_Header*)pNext)->RE_Type = RE_EMPTY;
+
+ return re_commandinfo;
+}
+
+
+// scan a remote event list for a specific type -------------------------------
+//
+RE_Header* FindInREList( RE_Header *relist, byte type )
+{
+ ASSERT( relist != NULL );
+
+ // scan remote event list for already inserted type
+ while ( relist->RE_Type != RE_EMPTY ) {
+
+ if ( relist->RE_Type == type ) {
+ return relist;
+ }
+
+ // 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 ) );
+ }
+
+ return NULL;
+}
+
+
+// insert ClientInfo event into RE_List ---------------------------------------
+//
+RE_ClientInfo* NET_RmEvClientInfo( int nSendFreq, int nRecvRate )
+{
+ //FIXME: this remote event must be transfered RELIABLE
+
+ if ( NetConnected == NETWORK_GAME_ON ) {
+
+ // adjust to bounds
+ nSendFreq = max( CLIENT_SEND_FREQUENCY_MIN, nSendFreq );
+ nSendFreq = min( CLIENT_SEND_FREQUENCY_MAX, nSendFreq );
+ nRecvRate = max( CLIENT_RECV_RATE_MIN, nRecvRate );
+ nRecvRate = min( CLIENT_RECV_RATE_MAX, nRecvRate );
+
+ // scan remote event list for already inserted RE_ClientInfo
+ RE_ClientInfo* re_clientinfo = (RE_ClientInfo*)FindInREList( (RE_Header *) REListMem, RE_CLIENTINFO );
+
+ // reuse already inserted RE
+ if ( re_clientinfo != NULL ) {
+
+ // fill in info
+ re_clientinfo->client_sendfreq = (byte)nSendFreq;
+ re_clientinfo->server_sendrate = PACK_SERVERRATE( nRecvRate );
+
+ } else {
+
+ if ( (size_t)RE_List_Avail < sizeof( RE_ClientInfo ) ) {
+ ASSERT( 0 );
+ return 0;
+ }
+
+ // append RE
+ re_clientinfo = (RE_ClientInfo*) RE_List_CurPos;
+ re_clientinfo->RE_Type = RE_CLIENTINFO;
+ re_clientinfo->RE_BlockSize = sizeof( RE_ClientInfo );
+
+ // fill in info
+ re_clientinfo->client_sendfreq = (byte)nSendFreq;
+ re_clientinfo->server_sendrate = PACK_SERVERRATE( nRecvRate );
+
+ // maintain list
+ re_clientinfo++;
+ re_clientinfo->RE_Type = RE_EMPTY;
+
+ RE_List_CurPos = (char *) re_clientinfo;
+ RE_List_Avail -= sizeof( RE_ClientInfo );
+ }
+
+ return re_clientinfo;
+ } else {
+ return NULL;
+ }
+}
+