/* ================== SV_FinalMessage Used by SV_Shutdown to send a final message to all connected clients before the server goes down. The messages are sent immediately, not just stuck on the outgoing message list, because the server is going to totally exit after returning from this function. ================== */ void SV_FinalMessage( char *message, qboolean reconnect ) { sv_client_t *cl; byte msg_buf[1024]; sizebuf_t msg; int i; BF_Init( &msg, "FinalMessage", msg_buf, sizeof( msg_buf )); BF_WriteByte( &msg, svc_print ); BF_WriteByte( &msg, PRINT_HIGH ); BF_WriteString( &msg, va( "%s\n", message )); if( reconnect ) { BF_WriteByte( &msg, svc_changing ); if( sv.loadgame || sv_maxclients->integer > 1 || sv.changelevel ) BF_WriteOneBit( &msg, 1 ); // changelevel else BF_WriteOneBit( &msg, 0 ); } else { BF_WriteByte( &msg, svc_disconnect ); } // send it twice // stagger the packets to crutch operating system limited buffers for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) if( cl->state >= cs_connected && !cl->fakeclient ) Netchan_Transmit( &cl->netchan, BF_GetNumBytesWritten( &msg ), BF_GetData( &msg )); for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) if( cl->state >= cs_connected && !cl->fakeclient ) Netchan_Transmit( &cl->netchan, BF_GetNumBytesWritten( &msg ), BF_GetData( &msg )); }
/* ================= SV_BroadcastPrintf Sends text to all active clients ================= */ void SV_BroadcastPrintf( int level, char *fmt, ... ) { char string[MAX_SYSPATH]; va_list argptr; sv_client_t *cl; int i; if( !sv.state ) return; va_start( argptr, fmt ); Q_vsprintf( string, fmt, argptr ); va_end( argptr ); // echo to console if( host.type == HOST_DEDICATED ) Msg( "%s", string ); for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( level < cl->messagelevel ) continue; if( cl->state != cs_spawned ) continue; if( cl->fakeclient ) continue; BF_WriteByte( &cl->netchan.message, svc_print ); BF_WriteByte( &cl->netchan.message, level ); BF_WriteString( &cl->netchan.message, string ); } }
/* ======================= SV_SendClientDatagram ======================= */ void SV_SendClientDatagram( sv_client_t *cl ) { byte msg_buf[NET_MAX_PAYLOAD]; sizebuf_t msg; svs.currentPlayer = cl; svs.currentPlayerNum = (cl - svs.clients); BF_Init( &msg, "Datagram", msg_buf, sizeof( msg_buf )); // always send servertime at new frame BF_WriteByte( &msg, svc_time ); BF_WriteFloat( &msg, sv.time ); SV_WriteClientdataToMessage( cl, &msg ); SV_WriteEntitiesToClient( cl, &msg ); // copy the accumulated multicast datagram // for this client out to the message if( BF_CheckOverflow( &cl->datagram )) MsgDev( D_WARN, "datagram overflowed for %s\n", cl->name ); else BF_WriteBits( &msg, BF_GetData( &cl->datagram ), BF_GetNumBitsWritten( &cl->datagram )); BF_Clear( &cl->datagram ); if( BF_CheckOverflow( &msg )) { // must have room left for the packet header MsgDev( D_WARN, "msg overflowed for %s\n", cl->name ); BF_Clear( &msg ); } // send the datagram Netchan_TransmitBits( &cl->netchan, BF_GetNumBitsWritten( &msg ), BF_GetData( &msg )); }
/* =================== Cmd_ForwardToServer adds the current command line as a clc_stringcmd to the client message. things like godmode, noclip, etc, are commands directed to the server, so when they are typed in at the console, they will need to be forwarded. =================== */ void Cmd_ForwardToServer( void ) { char str[MAX_CMD_BUFFER]; if( cls.demoplayback ) { if( !Q_stricmp( Cmd_Argv( 0 ), "pause" )) cl.refdef.paused ^= 1; return; } if( cls.state != ca_connected && cls.state != ca_active ) { MsgDev( D_INFO, "Can't \"%s\", not connected\n", Cmd_Argv( 0 )); return; // not connected } BF_WriteByte( &cls.netchan.message, clc_stringcmd ); str[0] = 0; if( Q_stricmp( Cmd_Argv( 0 ), "cmd" )) { Q_strcat( str, Cmd_Argv( 0 )); Q_strcat( str, " " ); } if( Cmd_Argc() > 1 ) Q_strcat( str, Cmd_Args( )); else Q_strcat( str, "\n" ); BF_WriteString( &cls.netchan.message, str ); }
/* ============== CL_ParseResourceList ============== */ void CL_ParseResourceList( sizebuf_t *msg ) { int i = 0; Q_memset( &reslist, 0, sizeof( resourcelist_t )); reslist.rescount = BF_ReadWord( msg ) - 1; for( i = 0; i < reslist.rescount; i++ ) { reslist.restype[i] = BF_ReadWord( msg ); Q_strncpy( reslist.resnames[i], BF_ReadString( msg ), CS_SIZE ); } cls.downloadcount = 0; for( i = 0; i < reslist.rescount; i++ ) { if( reslist.restype[i] == t_sound ) CL_CheckingSoundResFile( reslist.resnames[i] ); else CL_CheckingResFile( reslist.resnames[i] ); } if( !cls.downloadcount ) { BF_WriteByte( &cls.netchan.message, clc_stringcmd ); BF_WriteString( &cls.netchan.message, "continueloading" ); } }
/* ============================================================================= movevars_t communication ============================================================================= */ qboolean MSG_WriteDeltaMovevars( sizebuf_t *msg, movevars_t *from, movevars_t *to ) { delta_t *pField; delta_info_t *dt; int i, startBit; int numChanges = 0; dt = Delta_FindStruct( "movevars_t" ); ASSERT( dt && dt->bInitialized ); pField = dt->pFields; ASSERT( pField ); startBit = msg->iCurBit; // activate fields and call custom encode func Delta_CustomEncode( dt, from, to ); BF_WriteByte( msg, svc_deltamovevars ); // process fields for( i = 0; i < dt->numFields; i++, pField++ ) { if( Delta_WriteField( msg, pField, from, to, 0.0f )) numChanges++; } // if we have no changes - kill the message if( !numChanges ) { BF_SeekToBit( msg, startBit ); return false; } return true; }
/* ============== CL_CheckingResFile ============== */ void CL_CheckingResFile( char *pResFileName ) { sizebuf_t buf; byte data[32]; if( FS_FileExists( pResFileName, false )) return; // already exists cls.downloadcount++; if( cl_allow_fragment->integer ) { Msg( "Starting file download: %s\n", pResFileName ); if( cls.state == ca_disconnected ) return; BF_Init( &buf, "ClientPacket", data, sizeof( data )); BF_WriteByte( &buf, clc_resourcelist ); BF_WriteString( &buf, pResFileName ); if( !cls.netchan.remote_address.type ) // download in singleplayer ??? cls.netchan.remote_address.type = NA_LOOPBACK; // make sure message will be delivered Netchan_Transmit( &cls.netchan, BF_GetNumBytesWritten( &buf ), BF_GetData( &buf )); } else HTTP_AddDownload( pResFileName, -1, true ); }
/* ================= SV_ClientPrintf Sends text across to be displayed if the level passes ================= */ void SV_ClientPrintf( sv_client_t *cl, int level, char *fmt, ... ) { va_list argptr; char string[MAX_SYSPATH]; if( level < cl->messagelevel || cl->fakeclient ) return; va_start( argptr, fmt ); Q_vsprintf( string, fmt, argptr ); va_end( argptr ); BF_WriteByte( &cl->netchan.message, svc_print ); BF_WriteByte( &cl->netchan.message, level ); BF_WriteString( &cl->netchan.message, string ); }
/* ============= SV_EmitPings ============= */ void SV_EmitPings( sizebuf_t *msg ) { sv_client_t *cl; int packet_loss; int i, ping; BF_WriteByte( msg, svc_updatepings ); for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( cl->state != cs_spawned ) continue; SV_GetPlayerStats( cl, &ping, &packet_loss ); // there are 25 bits for each client BF_WriteOneBit( msg, 1 ); BF_WriteUBitLong( msg, i, MAX_CLIENT_BITS ); BF_WriteUBitLong( msg, ping, 12 ); BF_WriteUBitLong( msg, packet_loss, 7 ); } // end marker BF_WriteOneBit( msg, 0 ); }
/* ============== CL_ParseCvarValue Find the client cvar value and sent it back to the server ============== */ void CL_ParseCvarValue( sizebuf_t *msg ) { const char *cvarName = BF_ReadString( msg ); convar_t *cvar = Cvar_FindVar( cvarName ); // build the answer BF_WriteByte( &cls.netchan.message, clc_requestcvarvalue ); BF_WriteString( &cls.netchan.message, cvar ? cvar->string : "Not Found" ); }
static void pfnParticle( float *origin, int color, float life, int zpos, int zvel ) { int v; if( !origin ) { MsgDev( D_ERROR, "SV_StartParticle: NULL origin. Ignored\n" ); return; } BF_WriteByte( &sv.reliable_datagram, svc_particle ); BF_WriteVec3Coord( &sv.reliable_datagram, origin ); BF_WriteChar( &sv.reliable_datagram, 0 ); // no x-vel BF_WriteChar( &sv.reliable_datagram, 0 ); // no y-vel v = bound( -128, (zpos * zvel) * 16.0f, 127 ); BF_WriteChar( &sv.reliable_datagram, v ); // write z-vel BF_WriteByte( &sv.reliable_datagram, 1 ); BF_WriteByte( &sv.reliable_datagram, color ); BF_WriteByte( &sv.reliable_datagram, bound( 0, life * 8, 255 )); }
void pfnUpdateServerInfo( const char *szKey, const char *szValue, const char *unused, void *unused2 ) { convar_t *cv = Cvar_FindVar( szKey ); if( !cv || !cv->modified ) return; // this cvar not changed BF_WriteByte( &sv.reliable_datagram, svc_serverinfo ); BF_WriteString( &sv.reliable_datagram, szKey ); BF_WriteString( &sv.reliable_datagram, szValue ); cv->modified = false; // reset state }
/* ================= CL_Precache_f The server will send this command right before allowing the client into the server ================= */ void CL_Precache_f( void ) { int spawncount; spawncount = Q_atoi( Cmd_Argv( 1 )); CL_PrepSound(); CL_PrepVideo(); BF_WriteByte( &cls.netchan.message, clc_stringcmd ); BF_WriteString( &cls.netchan.message, va( "begin %i\n", spawncount )); }
/* ================= SV_BroadcastCommand Sends text to all active clients ================= */ void SV_BroadcastCommand( char *fmt, ... ) { va_list argptr; char string[MAX_SYSPATH]; if( !sv.state ) return; va_start( argptr, fmt ); Q_vsprintf( string, fmt, argptr ); va_end( argptr ); BF_WriteByte( &sv.reliable_datagram, svc_stufftext ); BF_WriteString( &sv.reliable_datagram, string ); }
/* ==================== CL_ProcessFile A file has been received via the fragmentation/reassembly layer, put it in the right spot and see if we have finished downloading files. ==================== */ void CL_ProcessFile( BOOL successfully_received, const char *filename ) { MsgDev( D_INFO, "Received %s, but file processing is not hooked up!!!\n", filename ); if( cls.downloadfileid == cls.downloadcount - 1 ) { MsgDev( D_INFO, "All Files downloaded\n" ); BF_WriteByte( &cls.netchan.message, clc_stringcmd ); BF_WriteString( &cls.netchan.message, "continueloading" ); } cls.downloadfileid++; }
/* ===================== CL_SendDisconnectMessage Sends a disconnect message to the server ===================== */ void CL_SendDisconnectMessage( void ) { sizebuf_t buf; byte data[32]; if( cls.state == ca_disconnected ) return; BF_Init( &buf, "LastMessage", data, sizeof( data )); BF_WriteByte( &buf, clc_stringcmd ); BF_WriteString( &buf, "disconnect" ); if( !cls.netchan.remote_address.type ) cls.netchan.remote_address.type = NA_LOOPBACK; // make sure message will be delivered Netchan_Transmit( &cls.netchan, BF_GetNumBytesWritten( &buf ), BF_GetData( &buf )); Netchan_Transmit( &cls.netchan, BF_GetNumBytesWritten( &buf ), BF_GetData( &buf )); Netchan_Transmit( &cls.netchan, BF_GetNumBytesWritten( &buf ), BF_GetData( &buf )); }
/* ================= CL_Reconnect_f The server is changing levels ================= */ void CL_Reconnect_f( void ) { if( cls.state == ca_disconnected ) return; S_StopAllSounds (); if( cls.state == ca_connected ) { cls.demonum = cls.movienum = -1; // not in the demo loop now cls.state = ca_connected; // clear channel and stuff Netchan_Clear( &cls.netchan ); BF_Clear( &cls.netchan.message ); BF_WriteByte( &cls.netchan.message, clc_stringcmd ); BF_WriteString( &cls.netchan.message, "new" ); cl.validsequence = 0; // haven't gotten a valid frame update yet cl.delta_sequence = -1; // we'll request a full delta from the baseline cls.lastoutgoingcommand = -1; // we don't have a backed up cmd history yet cls.nextcmdtime = host.realtime; // we can send a cmd right away CL_StartupDemoHeader (); return; } if( cls.servername[0] ) { if( cls.state >= ca_connected ) { CL_Disconnect(); cls.connect_time = host.realtime - 1.5; } else cls.connect_time = MAX_HEARTBEAT; // fire immediately cls.demonum = cls.movienum = -1; // not in the demo loop now cls.state = ca_connecting; Msg( "reconnecting...\n" ); } }
/* ============== CL_ParseResourceList ============== */ void CL_ParseResourceList( sizebuf_t *msg ) { int i = 0; Q_memset( &reslist, 0, sizeof( resourcelist_t )); reslist.rescount = BF_ReadWord( msg ) - 1; for( i = 0; i < reslist.rescount; i++ ) { reslist.restype[i] = BF_ReadWord( msg ); Q_strncpy( reslist.resnames[i], BF_ReadString( msg ), CS_SIZE ); } cls.downloadcount = 0; HTTP_ResetProcessState(); for( i = 0; i < reslist.rescount; i++ ) { // skip some types #if 0 if( reslist.restype[i] == t_model && !Q_strchr( download_types->latched_string, 'm' ) ) continue; if( reslist.restype[i] == t_sound && !Q_strchr( download_types->latched_string, 's' ) ) continue; if( reslist.restype[i] == t_eventscript && !Q_strchr( download_types->latched_string, 'e' ) ) continue; if( reslist.restype[i] == t_generic && !Q_strchr( download_types->latched_string, 'c' ) ) continue; #endif if( reslist.restype[i] == t_sound ) CL_CheckingSoundResFile( reslist.resnames[i] ); else CL_CheckingResFile( reslist.resnames[i] ); } if( !cls.downloadcount ) { BF_WriteByte( &cls.netchan.message, clc_stringcmd ); BF_WriteString( &cls.netchan.message, "continueloading" ); } }
/* ==================== CL_ProcessFile A file has been received via the fragmentation/reassembly layer, put it in the right spot and see if we have finished downloading files. ==================== */ void CL_ProcessFile( qboolean successfully_received, const char *filename ) { if( successfully_received) MsgDev( D_INFO, "Received %s\n", filename ); else MsgDev( D_WARN, "Failed to download %s\n", filename ); if( cls.downloadfileid == cls.downloadcount - 1 ) { MsgDev( D_INFO, "Download completed, resuming connection\n" ); FS_Rescan(); BF_WriteByte( &cls.netchan.message, clc_stringcmd ); BF_WriteString( &cls.netchan.message, "continueloading" ); cls.downloadfileid = 0; cls.downloadcount = 0; return; } cls.downloadfileid++; }
void Delta_WriteTableField( sizebuf_t *msg, int tableIndex, const delta_t *pField ) { int nameIndex; delta_info_t *dt; ASSERT( pField ); if( !pField->name || !*pField->name ) return; // not initialized ? dt = Delta_FindStructByIndex( tableIndex ); ASSERT( dt && dt->bInitialized ); nameIndex = Delta_IndexForFieldInfo( dt->pInfo, pField->name ); ASSERT( nameIndex >= 0 && nameIndex < dt->maxFields ); BF_WriteByte( msg, svc_deltatable ); BF_WriteUBitLong( msg, tableIndex, 4 ); // assume we support 16 network tables BF_WriteUBitLong( msg, nameIndex, 8 ); // 255 fields by struct should be enough BF_WriteUBitLong( msg, pField->flags, 10 ); // flags are indicated various input types BF_WriteUBitLong( msg, pField->bits - 1, 5 ); // max received value is 32 (32 bit) // multipliers is null-compressed if( pField->multiplier != 1.0f ) { BF_WriteOneBit( msg, 1 ); BF_WriteFloat( msg, pField->multiplier ); } else BF_WriteOneBit( msg, 0 ); if( pField->post_multiplier != 1.0f ) { BF_WriteOneBit( msg, 1 ); BF_WriteFloat( msg, pField->post_multiplier ); } else BF_WriteOneBit( msg, 0 ); }
int SV_ModelIndex( const char *filename ) { char name[64]; int i; if( !filename || !filename[0] ) return 0; if( *filename == '!' ) filename++; Q_strncpy( name, filename, sizeof( name )); COM_FixSlashes( name ); for( i = 1; i < MAX_MODELS && sv.model_precache[i][0]; i++ ) { if( !Q_stricmp( sv.model_precache[i], name )) return i; } if( i == MAX_MODELS ) { Host_Error( "SV_ModelIndex: MAX_MODELS limit exceeded\n" ); return 0; } // register new model Q_strncpy( sv.model_precache[i], name, sizeof( sv.model_precache[i] )); if( sv.state != ss_loading ) { // send the update to everyone BF_WriteByte( &sv.reliable_datagram, svc_modelindex ); BF_WriteUBitLong( &sv.reliable_datagram, i, MAX_MODEL_BITS ); BF_WriteString( &sv.reliable_datagram, name ); } return i; }
int SV_SoundIndex( const char *filename ) { char name[64]; int i; // don't precache sentence names! if( !filename || !filename[0] || filename[0] == '!' ) return 0; Q_strncpy( name, filename, sizeof( name )); COM_FixSlashes( name ); for( i = 1; i < MAX_SOUNDS && sv.sound_precache[i][0]; i++ ) { if( !Q_stricmp( sv.sound_precache[i], name )) return i; } if( i == MAX_SOUNDS ) { Host_Error( "SV_SoundIndex: MAX_SOUNDS limit exceeded\n" ); return 0; } // register new sound Q_strncpy( sv.sound_precache[i], name, sizeof( sv.sound_precache[i] )); if( sv.state != ss_loading ) { // send the update to everyone BF_WriteByte( &sv.reliable_datagram, svc_soundindex ); BF_WriteUBitLong( &sv.reliable_datagram, i, MAX_SOUND_BITS ); BF_WriteString( &sv.reliable_datagram, name ); } return i; }
/* ================= CL_ConnectionlessPacket Responses to broadcasts, etc ================= */ void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) { char *args; char *c, buf[MAX_SYSPATH]; int len = sizeof( buf ); int dataoffset = 0; netadr_t servadr; BF_Clear( msg ); BF_ReadLong( msg ); // skip the -1 args = BF_ReadStringLine( msg ); Cmd_TokenizeString( args ); c = Cmd_Argv( 0 ); MsgDev( D_NOTE, "CL_ConnectionlessPacket: %s : %s\n", NET_AdrToString( from ), c ); // server connection if( !Q_strcmp( c, "client_connect" )) { if( cls.state == ca_connected ) { MsgDev( D_INFO, "dup connect received. ignored\n"); return; } Netchan_Setup( NS_CLIENT, &cls.netchan, from, Cvar_VariableValue( "net_qport" )); BF_WriteByte( &cls.netchan.message, clc_stringcmd ); BF_WriteString( &cls.netchan.message, "new" ); cls.state = ca_connected; cl.validsequence = 0; // haven't gotten a valid frame update yet cl.delta_sequence = -1; // we'll request a full delta from the baseline cls.lastoutgoingcommand = -1; // we don't have a backed up cmd history yet cls.nextcmdtime = host.realtime; // we can send a cmd right away CL_StartupDemoHeader (); UI_SetActiveMenu( false ); } else if( !Q_strcmp( c, "info" )) { // server responding to a status broadcast CL_ParseStatusMessage( from, msg ); } else if( !Q_strcmp( c, "netinfo" )) { // server responding to a status broadcast CL_ParseNETInfoMessage( from, msg ); } else if( !Q_strcmp( c, "cmd" )) { // remote command from gui front end if( !NET_IsLocalAddress( from )) { Msg( "Command packet from remote host. Ignored.\n" ); return; } ShowWindow( host.hWnd, SW_RESTORE ); SetForegroundWindow ( host.hWnd ); args = BF_ReadString( msg ); Cbuf_AddText( args ); Cbuf_AddText( "\n" ); } else if( !Q_strcmp( c, "print" )) { // print command from somewhere args = BF_ReadString( msg ); Msg( args ); } else if( !Q_strcmp( c, "ping" )) { // ping from somewhere Netchan_OutOfBandPrint( NS_CLIENT, from, "ack" ); } else if( !Q_strcmp( c, "challenge" )) { // challenge from the server we are connecting to cls.challenge = Q_atoi( Cmd_Argv( 1 )); CL_SendConnectPacket(); return; } else if( !Q_strcmp( c, "echo" )) { // echo request from server Netchan_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv( 1 )); } else if( !Q_strcmp( c, "disconnect" )) { // a disconnect message from the server, which will happen if the server // dropped the connection but it is still getting packets from us CL_Disconnect(); } else if( clgame.dllFuncs.pfnConnectionlessPacket( &from, args, buf, &len )) { // user out of band message (must be handled in CL_ConnectionlessPacket) if( len > 0 ) Netchan_OutOfBand( NS_SERVER, from, len, buf ); } else if( msg->pData[0] == 0xFF && msg->pData[1] == 0xFF && msg->pData[2] == 0xFF && msg->pData[3] == 0xFF && msg->pData[4] == 0x66 && msg->pData[5] == 0x0A ) { dataoffset = 6; while( 1 ) { servadr.type = NA_IP; servadr.ip[0] = msg->pData[dataoffset + 0]; servadr.ip[1] = msg->pData[dataoffset + 1]; servadr.ip[2] = msg->pData[dataoffset + 2]; servadr.ip[3] = msg->pData[dataoffset + 3]; servadr.port = *(word *)&msg->pData[dataoffset + 4]; if( !servadr.port ) break; MsgDev( D_INFO, "Found server: %s\n", NET_AdrToString( servadr )); NET_Config( true ); // allow remote Netchan_OutOfBandPrint( NS_CLIENT, servadr, "info %i", PROTOCOL_VERSION ); dataoffset += 6; } } else MsgDev( D_ERROR, "bad connectionless packet from %s:\n%s\n", NET_AdrToString( from ), args ); }
/* ============= SV_EmitPacketEntities Writes a delta update of an entity_state_t list to the message-> ============= */ void SV_EmitPacketEntities( sv_client_t *cl, client_frame_t *to, sizebuf_t *msg ) { entity_state_t *oldent, *newent; int oldindex, newindex; int oldnum, newnum; int from_num_entities; client_frame_t *from; // this is the frame that we are going to delta update from if( cl->delta_sequence != -1 ) { from = &cl->frames[cl->delta_sequence & SV_UPDATE_MASK]; from_num_entities = from->num_entities; // the snapshot's entities may still have rolled off the buffer, though if( from->first_entity <= svs.next_client_entities - svs.num_client_entities ) { MsgDev( D_WARN, "%s: delta request from out of date entities.\n", cl->name ); from = NULL; from_num_entities = 0; BF_WriteByte( msg, svc_packetentities ); BF_WriteWord( msg, to->num_entities ); } else { BF_WriteByte( msg, svc_deltapacketentities ); BF_WriteWord( msg, to->num_entities ); BF_WriteByte( msg, cl->delta_sequence ); } } else { from = NULL; from_num_entities = 0; BF_WriteByte( msg, svc_packetentities ); BF_WriteWord( msg, to->num_entities ); } newent = NULL; oldent = NULL; newindex = 0; oldindex = 0; while( newindex < to->num_entities || oldindex < from_num_entities ) { if( newindex >= to->num_entities ) { newnum = MAX_ENTNUMBER; } else { newent = &svs.packet_entities[(to->first_entity+newindex)%svs.num_client_entities]; newnum = newent->number; } if( oldindex >= from_num_entities ) { oldnum = MAX_ENTNUMBER; } else { oldent = &svs.packet_entities[(from->first_entity+oldindex)%svs.num_client_entities]; oldnum = oldent->number; } if( newnum == oldnum ) { // delta update from old position // because the force parm is false, this will not result // in any bytes being emited if the entity has not changed at all MSG_WriteDeltaEntity( oldent, newent, msg, false, SV_IsPlayerIndex( newent->number ), sv.time ); oldindex++; newindex++; continue; } if( newnum < oldnum ) { // this is a new entity, send it from the baseline MSG_WriteDeltaEntity( &svs.baselines[newnum], newent, msg, true, SV_IsPlayerIndex( newent->number ), sv.time ); newindex++; continue; } if( newnum > oldnum ) { qboolean force; if( EDICT_NUM( oldent->number )->free ) force = true; // entity completely removed from server else force = false; // just removed from delta-message // remove from message MSG_WriteDeltaEntity( oldent, NULL, msg, force, false, sv.time ); oldindex++; continue; } } BF_WriteWord( msg, 0 ); // end of packetentities }
/* ============= SV_EmitEvents ============= */ static void SV_EmitEvents( sv_client_t *cl, client_frame_t *to, sizebuf_t *msg ) { event_state_t *es; event_info_t *info; entity_state_t *state; event_args_t nullargs; int ev_count = 0; int count, ent_index; int i, j, ev; Q_memset( &nullargs, 0, sizeof( nullargs )); es = &cl->events; // count events for( ev = 0; ev < MAX_EVENT_QUEUE; ev++ ) { if( es->ei[ev].index ) ev_count++; } // nothing to send if( !ev_count ) return; // nothing to send if( ev_count >= 31 ) ev_count = 31; for( i = 0; i < MAX_EVENT_QUEUE; i++ ) { info = &es->ei[i]; if( info->index == 0 ) continue; ent_index = info->entity_index; for( j = 0; j < to->num_entities; j++ ) { state = &svs.packet_entities[(to->first_entity+j)%svs.num_client_entities]; if( state->number == ent_index ) break; } if( j >= to->num_entities ) { // couldn't find info->packet_index = to->num_entities; info->args.entindex = ent_index; } else { info->packet_index = j; info->args.ducking = 0; if(!( info->args.flags & FEVENT_ORIGIN )) VectorClear( info->args.origin ); if(!( info->args.flags & FEVENT_ANGLES )) VectorClear( info->args.angles ); VectorClear( info->args.velocity ); } } BF_WriteByte( msg, svc_event ); // create message BF_WriteUBitLong( msg, ev_count, 5 ); // up to MAX_EVENT_QUEUE events for( count = i = 0; i < MAX_EVENT_QUEUE; i++ ) { info = &es->ei[i]; if( info->index == 0 ) { info->packet_index = -1; info->entity_index = -1; continue; } // only send if there's room if( count < ev_count ) { BF_WriteUBitLong( msg, info->index, MAX_EVENT_BITS ); // 1024 events if( info->packet_index == -1 ) { BF_WriteOneBit( msg, 0 ); } else { BF_WriteOneBit( msg, 1 ); BF_WriteUBitLong( msg, info->packet_index, MAX_ENTITY_BITS ); if( !Q_memcmp( &nullargs, &info->args, sizeof( event_args_t ))) { BF_WriteOneBit( msg, 0 ); } else { BF_WriteOneBit( msg, 1 ); MSG_WriteDeltaEvent( msg, &nullargs, &info->args ); } } if( info->fire_time ) { BF_WriteOneBit( msg, 1 ); BF_WriteWord( msg, Q_rint( info->fire_time * 100.0f )); } else BF_WriteOneBit( msg, 0 ); } info->index = 0; info->packet_index = -1; info->entity_index = -1; count++; } }
/* ================== SV_WriteClientdataToMessage ================== */ void SV_WriteClientdataToMessage( sv_client_t *cl, sizebuf_t *msg ) { clientdata_t nullcd; clientdata_t *from_cd, *to_cd; weapon_data_t nullwd; weapon_data_t *from_wd, *to_wd; client_frame_t *frame; edict_t *clent; int i; Q_memset( &nullcd, 0, sizeof( nullcd )); clent = cl->edict; frame = &cl->frames[cl->netchan.outgoing_sequence & SV_UPDATE_MASK]; frame->senttime = host.realtime; frame->raw_ping = -1.0f; frame->latency = -1.0f; if( cl->chokecount != 0 ) { BF_WriteByte( msg, svc_chokecount ); BF_WriteByte( msg, cl->chokecount ); cl->chokecount = 0; } // update client fixangle switch( clent->v.fixangle ) { case 1: BF_WriteByte( msg, svc_setangle ); BF_WriteBitAngle( msg, clent->v.angles[0], 16 ); BF_WriteBitAngle( msg, clent->v.angles[1], 16 ); BF_WriteBitAngle( msg, clent->v.angles[2], 16 ); clent->v.effects |= EF_NOINTERP; break; case 2: BF_WriteByte( msg, svc_addangle ); BF_WriteBitAngle( msg, clent->v.avelocity[1], 16 ); clent->v.avelocity[1] = 0.0f; break; } clent->v.fixangle = 0; // reset fixangle clent->v.pushmsec = 0; // reset net framenum Q_memset( &frame->clientdata, 0, sizeof( frame->clientdata )); // update clientdata_t svgame.dllFuncs.pfnUpdateClientData( clent, cl->local_weapons, &frame->clientdata ); BF_WriteByte( msg, svc_clientdata ); if( cl->hltv_proxy ) return; // don't send more nothing if( cl->delta_sequence == -1 ) from_cd = &nullcd; else from_cd = &cl->frames[cl->delta_sequence & SV_UPDATE_MASK].clientdata; to_cd = &frame->clientdata; if( cl->delta_sequence == -1 ) { BF_WriteOneBit( msg, 0 ); // no delta-compression } else { BF_WriteOneBit( msg, 1 ); // we are delta-ing from BF_WriteByte( msg, cl->delta_sequence ); } // write clientdata_t MSG_WriteClientData( msg, from_cd, to_cd, sv.time ); if( cl->local_weapons && svgame.dllFuncs.pfnGetWeaponData( clent, frame->weapondata )) { Q_memset( &nullwd, 0, sizeof( nullwd )); for( i = 0; i < 64; i++ ) { if( cl->delta_sequence == -1 ) from_wd = &nullwd; else from_wd = &cl->frames[cl->delta_sequence & SV_UPDATE_MASK].weapondata[i]; to_wd = &frame->weapondata[i]; MSG_WriteWeaponData( msg, from_wd, to_wd, sv.time, i ); } } // end marker BF_WriteOneBit( msg, 0 ); }
/* ======================= SV_UpdateToReliableMessages ======================= */ void SV_UpdateToReliableMessages( void ) { int i; sv_client_t *cl; // check for changes to be sent over the reliable streams to all clients for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( !cl->edict ) continue; // not in game yet if( cl->state != cs_spawned ) continue; if( cl->sendinfo ) { cl->sendinfo = false; SV_FullClientUpdate( cl, &sv.reliable_datagram ); } if( cl->sendmovevars ) { cl->sendmovevars = false; SV_FullUpdateMovevars( cl, &cl->netchan.message ); } } // 1% chanse for simulate random network bugs if( sv.write_bad_message && Com_RandomLong( 0, 512 ) == 404 ) { // just for network debugging (send only for local client) BF_WriteByte( &sv.datagram, svc_bad ); BF_WriteLong( &sv.datagram, rand( )); // send some random data BF_WriteString( &sv.datagram, host.finalmsg ); // send final message sv.write_bad_message = false; } // clear the server datagram if it overflowed. if( BF_CheckOverflow( &sv.datagram )) { MsgDev( D_ERROR, "sv.datagram overflowed!\n" ); BF_Clear( &sv.datagram ); } // clear the server datagram if it overflowed. if( BF_CheckOverflow( &sv.spectator_datagram )) { MsgDev( D_ERROR, "sv.spectator_datagram overflowed!\n" ); BF_Clear( &sv.spectator_datagram ); } // now send the reliable and server datagrams to all clients. for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if( cl->state < cs_connected || cl->fakeclient ) continue; // reliables go to all connected or spawned BF_WriteBits( &cl->netchan.message, BF_GetData( &sv.reliable_datagram ), BF_GetNumBitsWritten( &sv.reliable_datagram )); BF_WriteBits( &cl->datagram, BF_GetData( &sv.datagram ), BF_GetNumBitsWritten( &sv.datagram )); if( cl->hltv_proxy ) { BF_WriteBits( &cl->datagram, BF_GetData( &sv.spectator_datagram ), BF_GetNumBitsWritten( &sv.spectator_datagram )); } } // now clear the reliable and datagram buffers. BF_Clear( &sv.spectator_datagram ); BF_Clear( &sv.reliable_datagram ); BF_Clear( &sv.datagram ); }
/* =================== CL_WritePacket Create and send the command packet to the server Including both the reliable commands and the usercmds =================== */ void CL_WritePacket( void ) { sizebuf_t buf; qboolean send_command = false; byte data[MAX_CMD_BUFFER]; int i, from, to, key, size; int numbackup = 2; int numcmds; int newcmds; int cmdnumber; // don't send anything if playing back a demo if( cls.demoplayback || cls.state == ca_cinematic ) return; if( cls.state == ca_disconnected || cls.state == ca_connecting ) return; CL_ComputePacketLoss (); #ifndef _DEBUG if( cl_cmdrate->value < MIN_CMD_RATE ) { Cvar_SetFloat( "cl_cmdrate", MIN_CMD_RATE ); } #endif Q_memset( data, 0, MAX_CMD_BUFFER ); BF_Init( &buf, "ClientData", data, sizeof( data )); // Determine number of backup commands to send along numbackup = bound( 0, cl_cmdbackup->integer, MAX_BACKUP_COMMANDS ); if( cls.state == ca_connected ) numbackup = 0; // Check to see if we can actually send this command // In single player, send commands as fast as possible // Otherwise, only send when ready and when not choking bandwidth if(( cl.maxclients == 1 ) || ( NET_IsLocalAddress( cls.netchan.remote_address ) && !host_limitlocal->integer )) send_command = true; else if(( host.realtime >= cls.nextcmdtime ) && Netchan_CanPacket( &cls.netchan )) send_command = true; if( cl.force_send_usercmd ) { send_command = true; cl.force_send_usercmd = false; } if(( cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged ) >= CL_UPDATE_MASK ) { if(( host.realtime - cls.netchan.last_received ) > CONNECTION_PROBLEM_TIME ) { Con_NPrintf( 1, "^3Warning:^1 Connection Problem^7\n" ); cl.validsequence = 0; } } if( cl_nodelta->integer ) { cl.validsequence = 0; } // send a userinfo update if needed if( userinfo->modified ) { BF_WriteByte( &cls.netchan.message, clc_userinfo ); BF_WriteString( &cls.netchan.message, Cvar_Userinfo( )); } if( send_command ) { int outgoing_sequence; if( cl_cmdrate->integer > 0 ) cls.nextcmdtime = host.realtime + ( 1.0f / cl_cmdrate->value ); else cls.nextcmdtime = host.realtime; // always able to send right away if( cls.lastoutgoingcommand == -1 ) { outgoing_sequence = cls.netchan.outgoing_sequence; cls.lastoutgoingcommand = cls.netchan.outgoing_sequence; } else outgoing_sequence = cls.lastoutgoingcommand + 1; // begin a client move command BF_WriteByte( &buf, clc_move ); // save the position for a checksum byte key = BF_GetRealBytesWritten( &buf ); BF_WriteByte( &buf, 0 ); // write packet lossage percentation BF_WriteByte( &buf, cls.packet_loss ); // say how many backups we'll be sending BF_WriteByte( &buf, numbackup ); // how many real commands have queued up newcmds = ( cls.netchan.outgoing_sequence - cls.lastoutgoingcommand ); // put an upper/lower bound on this newcmds = bound( 0, newcmds, MAX_TOTAL_CMDS ); if( cls.state == ca_connected ) newcmds = 0; BF_WriteByte( &buf, newcmds ); numcmds = newcmds + numbackup; from = -1; for( i = numcmds - 1; i >= 0; i-- ) { cmdnumber = ( cls.netchan.outgoing_sequence - i ) & CL_UPDATE_MASK; to = cmdnumber; CL_WriteUsercmd( &buf, from, to ); from = to; if( BF_CheckOverflow( &buf )) Host_Error( "CL_Move, overflowed command buffer (%i bytes)\n", MAX_CMD_BUFFER ); } // calculate a checksum over the move commands size = BF_GetRealBytesWritten( &buf ) - key - 1; buf.pData[key] = CRC32_BlockSequence( buf.pData + key + 1, size, cls.netchan.outgoing_sequence ); // message we are constructing. i = cls.netchan.outgoing_sequence & CL_UPDATE_MASK; // determine if we need to ask for a new set of delta's. if( cl.validsequence && (cls.state == ca_active) && !( cls.demorecording && cls.demowaiting )) { cl.delta_sequence = cl.validsequence; BF_WriteByte( &buf, clc_delta ); BF_WriteByte( &buf, cl.validsequence & 0xFF ); } else { // request delta compression of entities cl.delta_sequence = -1; } if( BF_CheckOverflow( &buf )) { Host_Error( "CL_Move, overflowed command buffer (%i bytes)\n", MAX_CMD_BUFFER ); } // remember outgoing command that we are sending cls.lastoutgoingcommand = cls.netchan.outgoing_sequence; // composite the rest of the datagram.. if( BF_GetNumBitsWritten( &cls.datagram ) <= BF_GetNumBitsLeft( &buf )) BF_WriteBits( &buf, BF_GetData( &cls.datagram ), BF_GetNumBitsWritten( &cls.datagram )); BF_Clear( &cls.datagram ); // deliver the message (or update reliable) Netchan_Transmit( &cls.netchan, BF_GetNumBytesWritten( &buf ), BF_GetData( &buf )); } else { // increment sequence number so we can detect that we've held back packets. cls.netchan.outgoing_sequence++; } if( cls.demorecording ) { // Back up one because we've incremented outgoing_sequence each frame by 1 unit cmdnumber = ( cls.netchan.outgoing_sequence - 1 ) & CL_UPDATE_MASK; CL_WriteDemoUserCmd( cmdnumber ); } // update download/upload slider. Netchan_UpdateProgress( &cls.netchan ); }
/* ================= CL_ConnectionlessPacket Responses to broadcasts, etc ================= */ void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) { char *args; char *c, buf[MAX_SYSPATH]; int len = sizeof( buf ), i = 0; netadr_t servadr; BF_Clear( msg ); BF_ReadLong( msg ); // skip the -1 args = BF_ReadStringLine( msg ); Cmd_TokenizeString( args ); c = Cmd_Argv( 0 ); MsgDev( D_NOTE, "CL_ConnectionlessPacket: %s : %s\n", NET_AdrToString( from ), c ); // server connection if( !Q_strcmp( c, "client_connect" )) { if( cls.state == ca_connected ) { MsgDev( D_INFO, "Dup connect received. Ignored.\n"); return; } Netchan_Setup( NS_CLIENT, &cls.netchan, from, net_qport->integer); BF_WriteByte( &cls.netchan.message, clc_stringcmd ); BF_WriteString( &cls.netchan.message, "new" ); cls.state = ca_connected; cl.validsequence = 0; // haven't gotten a valid frame update yet cl.delta_sequence = -1; // we'll request a full delta from the baseline cls.lastoutgoingcommand = -1; // we don't have a backed up cmd history yet cls.nextcmdtime = host.realtime; // we can send a cmd right away CL_StartupDemoHeader (); } else if( !Q_strcmp( c, "info" )) { // server responding to a status broadcast CL_ParseStatusMessage( from, msg ); } else if( !Q_strcmp( c, "netinfo" )) { // server responding to a status broadcast CL_ParseNETInfoMessage( from, msg ); } else if( !Q_strcmp( c, "cmd" )) { // remote command from gui front end if( !NET_IsLocalAddress( from )) { Msg( "Command packet from remote host. Ignored.\n" ); return; } #ifdef XASH_SDL SDL_RestoreWindow( host.hWnd ); #endif args = BF_ReadString( msg ); Cbuf_AddText( args ); Cbuf_AddText( "\n" ); } else if( !Q_strcmp( c, "print" )) { // print command from somewhere Msg("remote: %s\n", BF_ReadString( msg ) ); } else if( !Q_strcmp( c, "ping" )) { // ping from somewhere Netchan_OutOfBandPrint( NS_CLIENT, from, "ack" ); } else if( !Q_strcmp( c, "challenge" )) { // challenge from the server we are connecting to cls.challenge = Q_atoi( Cmd_Argv( 1 )); CL_SendConnectPacket(); return; } else if( !Q_strcmp( c, "echo" )) { // echo request from server Netchan_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv( 1 )); } else if( !Q_strcmp( c, "disconnect" )) { // a disconnect message from the server, which will happen if the server // dropped the connection but it is still getting packets from us CL_Disconnect(); CL_ClearEdicts(); } else if( !Q_strcmp( c, "f") ) { // serverlist got from masterserver while( !msg->bOverflow ) { servadr.type = NA_IP; // 4 bytes for IP BF_ReadBytes( msg, servadr.ip, sizeof( servadr.ip )); // 2 bytes for Port servadr.port = BF_ReadShort( msg ); if( !servadr.port ) break; MsgDev( D_INFO, "Found server: %s\n", NET_AdrToString( servadr )); NET_Config( true ); // allow remote Netchan_OutOfBandPrint( NS_CLIENT, servadr, "info %i", PROTOCOL_VERSION ); } // execute at next frame preventing relation on fps Cbuf_AddText("menu_resetping\n"); } else if( clgame.dllFuncs.pfnConnectionlessPacket( &from, args, buf, &len )) { // user out of band message (must be handled in CL_ConnectionlessPacket) if( len > 0 ) Netchan_OutOfBand( NS_SERVER, from, len, (byte *)buf ); } else MsgDev( D_ERROR, "Bad connectionless packet from %s:\n%s\n", NET_AdrToString( from ), args ); }