15 Oct 2010

This commit is contained in:
g-cont 2010-10-15 00:00:00 +04:00 committed by Alibek Omarov
parent d8a292491d
commit e03ae1c6e6
27 changed files with 1776 additions and 683 deletions

View File

@ -54,7 +54,6 @@ struct cl_entity_s
int index; // Index into cl_entities ( always match actual slot )
int player; // True if this entity is a "player"
int serverframe; // TEMPORARY PLACED HERE
entity_state_t baseline; // The original state from which to delta during an uncompressed message
entity_state_t prevstate; // The state information from the penultimate message received from the server
entity_state_t curstate; // The state information from the last message received from server

View File

@ -535,7 +535,7 @@ void CL_Record_f( void )
return;
}
}
else com.strncpy( demoname, name, sizeof( name ));
else com.strncpy( demoname, name, sizeof( demoname ));
// open the demo file
com.sprintf( demopath, "demos/%s.dem", demoname );

View File

@ -102,34 +102,20 @@ void CL_UpdateStudioVars( cl_entity_t *ent, entity_state_t *newstate )
VectorCopy( ent->curstate.angles, ent->latched.prevangles );
}
/*
==================
CL_DeltaEntity
Parses deltas from the given base and adds the resulting entity
to the current frame
==================
*/
void CL_DeltaEntity( sizebuf_t *msg, frame_t *frame, int newnum, entity_state_t *old, bool unchanged )
bool CL_ParseDelta( sizebuf_t *msg, entity_state_t *from, entity_state_t *to, int newnum )
{
cl_entity_t *ent;
entity_state_t *state;
bool newent = (old) ? false : true;
int result = 1;
int alive;
int noInterp = false;
ent = EDICT_NUM( newnum );
state = &cl.entity_curstates[cl.parse_entities & (MAX_PARSE_ENTITIES-1)];
alive = MSG_ReadDeltaEntity( msg, from, to, newnum, sv_time( ));
if( newent ) old = &ent->baseline;
if( unchanged ) *state = *old;
else result = MSG_ReadDeltaEntity( msg, old, state, newnum, sv_time( ));
if( !result )
if( !alive )
{
if( newent ) Host_Error( "Cl_DeltaEntity: tried to release new entity\n" );
if( state->number == -1 )
// entity was delta removed
if( to->number == -1 )
{
// Msg( "Entity %s was removed from server\n", ent->curstate.classname );
CL_FreeEntity( ent );
@ -140,45 +126,128 @@ void CL_DeltaEntity( sizebuf_t *msg, frame_t *frame, int newnum, entity_state_t
ent->curstate.effects |= EF_NODRAW; // don't rendering
clgame.dllFuncs.pfnUpdateOnRemove( ent );
}
// entity was delta removed
return;
return false;
}
cl.parse_entities++;
frame->num_entities++;
if( ent->index <= 0 )
{
// spawn entity
CL_InitEntity( ent );
state->effects |= EF_NOINTERP;
noInterp = true;
}
// some data changes will force no lerping
if( state->effects & EF_NOINTERP )
ent->serverframe = -99;
if( ent->serverframe != cl.frame.serverframe - 1 )
{
// duplicate the current state so lerping doesn't hurt anything
ent->prevstate = *state;
}
else
{
// shuffle the last state to previous
ent->prevstate = ent->curstate;
}
ent->serverframe = cl.frame.serverframe;
if( to->effects & EF_NOINTERP || noInterp )
ent->prevstate = *to; // duplicate the current state so lerping doesn't hurt anything
else ent->prevstate = ent->curstate; // shuffle the last state to previous
// NOTE: always check modelindex for new state not current
if( CM_GetModelType( state->modelindex ) == mod_studio )
CL_UpdateStudioVars( ent, state );
if( CM_GetModelType( to->modelindex ) == mod_studio )
CL_UpdateStudioVars( ent, to );
// set right current state
ent->curstate = *state;
ent->curstate = *to;
CL_LinkEdict( ent ); // relink entity
return true;
}
/*
=======================
CL_ClearPacketEntities
=======================
*/
void CL_ClearPacketEntities( frame_t *frame )
{
packet_entities_t *packet;
ASSERT( frame != NULL );
packet = &frame->entities;
if( packet )
{
if( packet->entities != NULL )
Mem_Free( packet->entities );
packet->num_entities = 0;
packet->max_entities = 0;
packet->entities = NULL;
}
}
/*
=======================
CL_AllocPacketEntities
=======================
*/
void CL_AllocPacketEntities( frame_t *frame, int count )
{
packet_entities_t *packet;
ASSERT( frame != NULL );
packet = &frame->entities;
if( count < 1 ) count = 1;
// check if new frame has more entities than previous
if( packet->max_entities < count )
{
CL_ClearPacketEntities( frame );
packet->entities = Mem_Alloc( clgame.mempool, sizeof( entity_state_t ) * count );
packet->max_entities = count;
}
packet->num_entities = count;
}
/*
================
CL_ClearFrames
free client frames memory
================
*/
void CL_ClearFrames( void )
{
frame_t *frame;
int i;
for( i = 0, frame = cl.frames; i < CL_UPDATE_BACKUP; i++, frame++ )
{
CL_ClearPacketEntities( frame );
}
}
/*
=================
CL_FlushEntityPacket
=================
*/
void CL_FlushEntityPacket( sizebuf_t *msg )
{
int newnum;
entity_state_t from, to;
MsgDev( D_INFO, "FlushEntityPacket()\n" );
Mem_Set( &from, 0, sizeof( from ));
cl.frames[cl.parsecountmod].valid = false;
cl.validsequence = 0; // can't render a frame
// read it all, but ignore it
while( 1 )
{
newnum = BF_ReadWord( msg );
if( !newnum ) break; // done
if( BF_CheckOverflow( msg ))
Host_Error( "CL_FlushEntityPacket: read overflow\n" );
while( newnum >= clgame.numEntities )
clgame.numEntities++;
MSG_ReadDeltaEntity( msg, &from, &to, newnum, sv_time( ));
}
}
/*
@ -189,40 +258,91 @@ An svc_packetentities has just been parsed, deal with the
rest of the data stream.
==================
*/
void CL_ParsePacketEntities( sizebuf_t *msg, frame_t *oldframe, frame_t *newframe )
void CL_ParsePacketEntities( sizebuf_t *msg, bool delta )
{
int newnum;
entity_state_t *oldstate;
int oldindex, oldnum;
frame_t *frame;
int oldpacket, newpacket;
packet_entities_t *from, *to, dummy;
int oldindex, newindex;
int count, delta_sequence = -1;
int newnum, oldnum;
bool full = false;
newframe->parse_entities = cl.parse_entities;
newframe->num_entities = 0;
// first, allocate packet for new frame
count = BF_ReadWord( msg );
// delta from the entities present in oldframe
oldindex = 0;
oldstate = NULL;
if( !oldframe )
newpacket = cl.parsecountmod;
frame = &cl.frames[newpacket];
frame->valid = true;
if( delta )
{
oldnum = MAX_ENTNUMBER;
oldpacket = BF_ReadByte( msg );
if(( cl.delta_sequence & CL_UPDATE_MASK ) != ( oldpacket & CL_UPDATE_MASK ))
MsgDev( D_WARN, "CL_ParsePacketEntities: mismatch delta_sequence %i != %i\n", cl.delta_sequence, oldpacket );
}
else oldpacket = -1;
if( oldpacket != -1 )
{
int subtracted = ((( cls.netchan.incoming_sequence & 0xFF ) - oldpacket ) & 0xFF );
if( subtracted == 0 )
{
Host_Error( "CL_DeltaPacketEntities: update too old, connection dropped.\n" );
return;
}
if( subtracted >= CL_UPDATE_MASK )
{
// we can't use this, it is too old
CL_FlushEntityPacket( msg );
return;
}
from = &cl.frames[oldpacket & CL_UPDATE_MASK].entities;
// alloc room for save entities from prevframe too
if( from->num_entities > count )
count = from->num_entities;
}
else
{
if( oldindex >= oldframe->num_entities )
{
oldnum = MAX_ENTNUMBER;
}
else
{
oldstate = &cl.entity_curstates[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)];
oldnum = oldstate->number;
}
{
// this is a full update that we can start delta compressing from now
dummy.entities = NULL;
dummy.num_entities = 0;
cls.demowaiting = false; // we can start recording now
cl.force_send_usercmd = true;
from = &dummy;
full = true;
}
CL_AllocPacketEntities( frame, count );
to = &frame->entities;
// mark current delta state
cl.validsequence = cls.netchan.incoming_sequence;
oldindex = 0;
newindex = 0;
to->num_entities = 0;
while( 1 )
{
// read the entity index number
newnum = BF_ReadShort( msg );
if( !newnum ) break; // end of packet entities
newnum = BF_ReadWord( msg );
if( !newnum )
{
while( oldindex < from->num_entities )
{
// copy all the rest of the entities from the old packet
to->entities[newindex] = from->entities[oldindex];
newindex++;
oldindex++;
}
break;
}
if( BF_CheckOverflow( msg ))
Host_Error( "CL_ParsePacketEntities: read overflow\n" );
@ -230,91 +350,96 @@ void CL_ParsePacketEntities( sizebuf_t *msg, frame_t *oldframe, frame_t *newfram
while( newnum >= clgame.numEntities )
clgame.numEntities++;
while( oldnum < newnum )
{
// one or more entities from the old packet are unchanged
CL_DeltaEntity( msg, newframe, oldnum, oldstate, true );
oldindex++;
oldnum = oldindex >= from->num_entities ? MAX_ENTNUMBER : from->entities[oldindex].number;
if( oldindex >= oldframe->num_entities )
while( newnum > oldnum )
{
if( full )
{
oldnum = MAX_ENTNUMBER;
}
else
{
oldstate = &cl.entity_curstates[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)];
oldnum = oldstate->number;
MsgDev( D_WARN, "CL_ParsePacketEntities: oldcopy on full update\n" );
CL_FlushEntityPacket( msg );
return;
}
to->entities[newindex] = from->entities[oldindex];
newindex++;
oldindex++;
oldnum = oldindex >= from->num_entities ? MAX_ENTNUMBER : from->entities[oldindex].number;
}
if( oldnum == newnum )
{
// delta from previous state
CL_DeltaEntity( msg, newframe, newnum, oldstate, false );
oldindex++;
if( newnum < oldnum )
{
cl_entity_t *ent;
if( oldindex >= oldframe->num_entities )
// new from baseline
ent = EDICT_NUM( newnum );
if( !CL_ParseDelta( msg, &ent->baseline, &to->entities[newindex], newnum ))
{
oldnum = MAX_ENTNUMBER;
}
else
{
oldstate = &cl.entity_curstates[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)];
oldnum = oldstate->number;
if( full )
{
MsgDev( D_WARN, "CL_ParsePacketEntities: remove on full update\n" );
CL_FlushEntityPacket( msg );
return;
}
continue;
}
newindex++;
continue;
}
if( oldnum > newnum )
if( newnum == oldnum )
{
// delta from baseline ?
CL_DeltaEntity( msg, newframe, newnum, NULL, false );
continue;
// delta from previous
if( full )
{
cl.validsequence = 0;
MsgDev( D_WARN, "CL_ParsePacketEntities: delta on full update\n" );
}
if( !CL_ParseDelta( msg, &from->entities[oldindex], &to->entities[newindex], newnum ))
{
// entity was delta-removed
oldindex++;
continue;
}
newindex++;
oldindex++;
}
}
// any remaining entities in the old frame are copied over
while( oldnum != MAX_ENTNUMBER )
{
// one or more entities from the old packet are unchanged
CL_DeltaEntity( msg, newframe, oldnum, oldstate, true );
oldindex++;
to->num_entities = newindex; // done
if( oldindex >= oldframe->num_entities )
{
oldnum = MAX_ENTNUMBER;
}
else
{
oldstate = &cl.entity_curstates[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)];
oldnum = oldstate->number;
}
}
}
/*
===================
CL_ParseClientData
===================
*/
void CL_ParseClientData( frame_t *from, frame_t *to, sizebuf_t *msg )
{
clientdata_t *cd, *ocd;
clientdata_t dummy;
cd = &to->cd;
// clear to old value before delta parsing
if( !from )
cl.frame = *frame;
if( !cl.frame.valid ) return;
if( cls.state != ca_active )
{
ocd = &dummy;
Mem_Set( &dummy, 0, sizeof( dummy ));
}
else ocd = &from->cd;
cl_entity_t *player;
MSG_ReadClientData( msg, ocd, cd, sv_time( ));
// client entered the game
cls.state = ca_active;
cl.force_refdef = true;
cls.changelevel = false; // changelevel is done
player = CL_GetLocalPlayer();
SCR_MakeLevelShot(); // make levelshot if needs
Cvar_SetValue( "scr_loading", 0.0f ); // reset progress bar
// getting a valid frame message ends the connection process
VectorCopy( player->origin, cl.predicted_origin );
VectorCopy( player->angles, cl.predicted_angles );
// request new HUD values
BF_WriteByte( &cls.netchan.message, clc_stringcmd );
BF_WriteString( &cls.netchan.message, "fullupdate" );
}
CL_CheckPredictionError();
}
/*
@ -324,63 +449,12 @@ CL_ParseFrame
*/
void CL_ParseFrame( sizebuf_t *msg )
{
int cmd;
cl_entity_t *clent;
Mem_Set( &cl.frame, 0, sizeof( cl.frame ));
cl.mtime[1] = cl.mtime[0];
cl.mtime[0] = BF_ReadFloat( msg );
cl.frame.serverframe = BF_ReadLong( msg );
cl.frame.deltaframe = BF_ReadLong( msg );
cl.surpressCount = BF_ReadByte( msg );
// If the frame is delta compressed from data that we
// no longer have available, we must suck up the rest of
// the frame, but not use it, then ask for a non-compressed
// message
if( cl.frame.deltaframe <= 0 )
{
cl.frame.valid = true; // uncompressed frame
cls.demowaiting = false; // we can start recording now
cl.oldframe = NULL;
}
else
{
cl.oldframe = &cl.frames[cl.frame.deltaframe & CL_UPDATE_MASK];
if( !cl.oldframe->valid )
{
// should never happen
MsgDev( D_INFO, "delta from invalid frame (not supposed to happen!)\n" );
}
if( cl.oldframe->serverframe != cl.frame.deltaframe )
{
// The frame that the server did the delta from
// is too old, so we can't reconstruct it properly.
MsgDev( D_INFO, "delta frame too old\n" );
}
else if( cl.parse_entities - cl.oldframe->parse_entities > MAX_PARSE_ENTITIES - 128 )
{
MsgDev( D_INFO, "delta parse_entities too old\n" );
}
else cl.frame.valid = true; // valid delta parse
}
// read clientdata
cmd = BF_ReadByte( msg );
if( cmd != svc_clientdata ) Host_Error( "CL_ParseFrame: not cliendata[%d]\n", cmd );
CL_ParseClientData( cl.oldframe, &cl.frame, msg );
clent = CL_GetLocalPlayer(); // get client
// read packet entities
cmd = BF_ReadByte( msg );
if( cmd != svc_packetentities ) Host_Error( "CL_ParseFrame: not packetentities[%d]\n", cmd );
CL_ParsePacketEntities( msg, cl.oldframe, &cl.frame );
// save the frame off in the backup array for later delta comparisons
cl.frames[cl.frame.serverframe & CL_UPDATE_MASK] = cl.frame;
if( !cl.frame.valid ) return;
if( cls.state != ca_active )
@ -433,17 +507,17 @@ void CL_AddPacketEntities( frame_t *frame )
if( !clent ) return;
// update client vars
clgame.dllFuncs.pfnTxferLocalOverrides( &clent->curstate, &cl.frame.cd );
clgame.dllFuncs.pfnTxferLocalOverrides( &clent->curstate, &cl.frame.clientdata );
// if viewmodel has changed update sequence here
if( clgame.viewent.curstate.modelindex != cl.frame.cd.viewmodel )
if( clgame.viewent.curstate.modelindex != cl.frame.clientdata.viewmodel )
{
cl_entity_t *view = &clgame.viewent;
CL_WeaponAnim( view->curstate.sequence, view->curstate.body );
}
// setup player viewmodel (only for local player!)
clgame.viewent.curstate.modelindex = cl.frame.cd.viewmodel;
clgame.viewent.curstate.modelindex = cl.frame.clientdata.viewmodel;
for( e = 1; e < clgame.numEntities; e++ )
{

View File

@ -487,7 +487,7 @@ void CL_DrawCrosshair( void )
pPlayer = CL_GetLocalPlayer();
if( cl.frame.cd.deadflag != DEAD_NO || cl.frame.cd.flags & FL_FROZEN )
if( cl.frame.clientdata.deadflag != DEAD_NO || cl.frame.clientdata.flags & FL_FROZEN )
return;
// any camera on
@ -743,8 +743,6 @@ void CL_InitEdicts( void )
ASSERT( clgame.entities == NULL );
CL_UPDATE_BACKUP = ( cl.maxclients == 1 ) ? SINGLEPLAYER_BACKUP : MULTIPLAYER_BACKUP;
cl.frames = Mem_Alloc( clgame.mempool, sizeof( frame_t ) * CL_UPDATE_BACKUP );
clgame.entities = Mem_Alloc( clgame.mempool, sizeof( cl_entity_t ) * clgame.maxEntities );
for( i = 0, e = clgame.entities; i < clgame.maxEntities; i++, e++ )
@ -767,10 +765,8 @@ void CL_FreeEdicts( void )
Mem_Free( clgame.entities );
}
if( cl.frames ) Mem_Free( cl.frames );
clgame.numEntities = 0;
clgame.entities = NULL;
cl.frames = NULL;
}
/*
@ -1176,8 +1172,8 @@ static void pfnGetPlayerInfo( int ent_num, hud_player_info_t *pinfo )
pinfo->model = player->model;
pinfo->spectator = spec;
pinfo->ping = com.atoi( Info_ValueForKey( player->userinfo, "ping" ));
pinfo->packetloss = com.atoi( Info_ValueForKey( player->userinfo, "loss" ));
pinfo->ping = player->ping;
pinfo->packetloss = player->packet_loss;
pinfo->topcolor = com.atoi( Info_ValueForKey( player->userinfo, "topcolor" ));
pinfo->bottomcolor = com.atoi( Info_ValueForKey( player->userinfo, "bottomcolor" ));
}
@ -1375,7 +1371,7 @@ pfnPhysInfo_ValueForKey
*/
static const char* pfnPhysInfo_ValueForKey( const char *key )
{
return Info_ValueForKey( cl.frame.cd.physinfo, key );
return Info_ValueForKey( cl.frame.clientdata.physinfo, key );
}
/*
@ -1398,7 +1394,7 @@ value that come from server
*/
static float pfnGetClientMaxspeed( void )
{
return cl.frame.cd.maxspeed;
return cl.frame.clientdata.maxspeed;
}
/*
@ -1704,7 +1700,7 @@ pfnLocalPlayerDucking
*/
int pfnLocalPlayerDucking( void )
{
return cl.frame.cd.bInDuck;
return cl.frame.clientdata.bInDuck;
}
/*

View File

@ -9,6 +9,9 @@
#include "net_encode.h"
#include "input.h"
#define MAX_TOTAL_CMDS 16
#define MIN_CMD_RATE 10.0
#define MAX_CMD_BUFFER 4000
#define CONNECTION_PROBLEM_TIME 15.0 // 15 seconds
cvar_t *rcon_client_password;
@ -20,9 +23,11 @@ cvar_t *cl_predict;
cvar_t *cl_showfps;
cvar_t *cl_nodelta;
cvar_t *cl_crosshair;
cvar_t *cl_cmdbackup;
cvar_t *cl_idealpitchscale;
cvar_t *cl_solid_players;
cvar_t *cl_showmiss;
cvar_t *cl_cmdrate;
cvar_t *userinfo;
//
@ -119,6 +124,40 @@ static float CL_LerpPoint( void )
return frac;
}
/*
=================
CL_ComputePacketLoss
=================
*/
void CL_ComputePacketLoss( void )
{
int i, frm;
frame_t *frame;
int count = 0;
int lost = 0;
if( host.realtime < cls.packet_loss_recalc_time )
return;
// recalc every second
cls.packet_loss_recalc_time = host.realtime + 1.0;
// compuate packet loss
for( i = cls.netchan.incoming_sequence - CL_UPDATE_BACKUP+1; i <= cls.netchan.incoming_sequence; i++ )
{
frm = i;
frame = &cl.frames[frm & CL_UPDATE_MASK];
if( frame->receivedtime == -1 )
lost++;
count++;
}
if( count <= 0 ) cls.packet_loss = 0.0f;
else cls.packet_loss = ( 100.0f * (float)lost ) / (float)count;
}
/*
=======================================================================
@ -231,10 +270,10 @@ usercmd_t CL_CreateCmd( void )
Mem_Set( &cmd, 0, sizeof( cmd ));
VectorCopy( cl.frame.cd.origin, cdata.origin );
VectorCopy( cl.frame.clientdata.origin, cdata.origin );
VectorCopy( cl.refdef.cl_viewangles, cdata.viewangles );
cdata.iWeaponBits = cl.frame.cd.weapons;
cdata.fov = cl.frame.cd.fov;
cdata.iWeaponBits = cl.frame.clientdata.weapons;
cdata.fov = cl.frame.clientdata.fov;
clgame.dllFuncs.pfnUpdateClientData( &cdata, cl_time( ));
@ -256,6 +295,30 @@ usercmd_t CL_CreateCmd( void )
return cmd;
}
void CL_WriteUsercmd( sizebuf_t *msg, int from, int to )
{
usercmd_t nullcmd;
usercmd_t *f, *t;
ASSERT( from == -1 || ( from >= 0 && from < MULTIPLAYER_BACKUP ) );
ASSERT( to >= 0 && to < MULTIPLAYER_BACKUP );
if( from == -1 )
{
Mem_Set( &nullcmd, 0, sizeof( nullcmd ));
f = &nullcmd;
}
else
{
f = &cl.cmds[from];
}
t = &cl.cmds[to];
// write it into the buffer
MSG_WriteDeltaUsercmd( msg, f, t );
}
/*
===================
CL_WritePacket
@ -273,13 +336,15 @@ During normal gameplay, a client packet will contain something like:
*/
void CL_WritePacket( void )
{
sizebuf_t buf;
bool noDelta = false;
byte data[MAX_MSGLEN];
usercmd_t *cmd, *oldcmd;
usercmd_t nullcmd;
int key, size;
sizebuf_t buf;
bool 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;
@ -287,6 +352,7 @@ void CL_WritePacket( void )
if( cls.state == ca_disconnected || cls.state == ca_connecting )
return;
/*
if( cls.state == ca_connected )
{
// just update reliable
@ -294,19 +360,49 @@ void CL_WritePacket( void )
Netchan_Transmit( &cls.netchan, 0, NULL );
return;
}
*/
CL_ComputePacketLoss ();
if( cl_nodelta->integer || !cl.frame.valid || cls.demowaiting )
noDelta = true;
if(( cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged ) >= CMD_BACKUP )
if( cl_cmdrate->value < MIN_CMD_RATE )
{
if(( host.realtime - cls.netchan.last_received ) > CONNECTION_PROBLEM_TIME )
Cvar_SetValue( "cl_cmdrate", MIN_CMD_RATE );
}
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 && !cls.demoplayback )
{
MsgDev( D_WARN, "^1 Connection Problem^7\n" );
noDelta = true; // request a fullupdate
cl.validsequence = 0;
}
}
if( cl_nodelta->integer )
{
cl.validsequence = 0;
}
// send a userinfo update if needed
if( userinfo->modified )
{
@ -315,44 +411,97 @@ void CL_WritePacket( void )
BF_WriteString( &cls.netchan.message, Cvar_Userinfo( ));
}
BF_Init( &buf, "ClientData", data, sizeof( data ));
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
// write new random_seed
BF_WriteByte( &buf, clc_random_seed );
BF_WriteUBitLong( &buf, cl.random_seed, 32 ); // full range
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 );
// begin a client move command
BF_WriteByte( &buf, clc_move );
// save the position for a checksum byte
key = BF_GetNumBytesWritten( &buf );
BF_WriteByte( &buf, 0 );
// save the position for a checksum byte
key = BF_GetRealBytesWritten( &buf );
BF_WriteByte( &buf, 0 );
// let the server know what the last frame we
// got was, so the next message can be delta compressed
if( noDelta ) BF_WriteLong( &buf, -1 ); // no compression
else BF_WriteLong( &buf, cl.frame.serverframe );
// write packet lossage percentation
BF_WriteByte( &buf, cls.packet_loss );
// send this and the previous cmds in the message, so
// if the last packet was dropped, it can be recovered
cmd = &cl.cmds[(cls.netchan.outgoing_sequence - 2) & CMD_MASK];
Mem_Set( &nullcmd, 0, sizeof( nullcmd ));
MSG_WriteDeltaUsercmd( &buf, &nullcmd, cmd );
oldcmd = cmd;
// say how many backups we'll be sending
BF_WriteByte( &buf, numbackup );
cmd = &cl.cmds[(cls.netchan.outgoing_sequence - 1) & CMD_MASK];
MSG_WriteDeltaUsercmd( &buf, oldcmd, cmd );
oldcmd = cmd;
// how many real commands have queued up
newcmds = ( cls.netchan.outgoing_sequence - cls.lastoutgoingcommand );
cmd = &cl.cmds[(cls.netchan.outgoing_sequence - 0) & CMD_MASK];
MSG_WriteDeltaUsercmd( &buf, oldcmd, cmd );
// 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 );
// calculate a checksum over the move commands
size = BF_GetNumBytesWritten( &buf ) - key - 1;
buf.pData[key] = CRC_Sequence( buf.pData + key + 1, size, cls.netchan.outgoing_sequence );
numcmds = newcmds + numbackup;
from = -1;
// deliver the message
Netchan_Transmit( &cls.netchan, BF_GetNumBytesWritten( &buf ), BF_GetData( &buf ));
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] = CRC_Sequence( 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.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;
// 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++;
}
// update download/upload slider.
Netchan_UpdateProgress( &cls.netchan );
@ -368,7 +517,7 @@ Called every frame to builds and sends a command packet to the server.
void CL_SendCmd( void )
{
// we create commands even if a demo is playing,
cl.refdef.cmd = &cl.cmds[cls.netchan.outgoing_sequence & CMD_MASK];
cl.refdef.cmd = &cl.cmds[cls.netchan.outgoing_sequence & CL_UPDATE_MASK];
*cl.refdef.cmd = CL_CreateCmd();
// clc_move, userinfo etc
@ -574,6 +723,7 @@ void CL_ClearState( void )
S_StopAllSounds ();
CL_ClearEffects ();
CL_FreeEdicts ();
CL_ClearFrames ();
if( clgame.hInstance ) clgame.dllFuncs.pfnReset();
@ -956,6 +1106,12 @@ void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg )
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
UI_SetActiveMenu( false );
}
else if( !com.strcmp( c, "info" ))
@ -1143,7 +1299,7 @@ CL_Physinfo_f
void CL_Physinfo_f( void )
{
Msg( "Phys info settings:\n" );
Info_Print( cl.frame.cd.physinfo );
Info_Print( cl.frame.clientdata.physinfo );
}
int precache_check; // for autodownload of precache items
@ -1348,6 +1504,8 @@ void CL_InitLocal( void )
cl_showfps = Cvar_Get( "cl_showfps", "1", CVAR_ARCHIVE, "show client fps" );
cl_lw = Cvar_Get( "cl_lw", "1", CVAR_ARCHIVE|CVAR_USERINFO, "enable client weapon predicting" );
cl_smooth = Cvar_Get ("cl_smooth", "1", 0, "smooth up stair climbing and interpolate position in multiplayer" );
cl_cmdbackup = Cvar_Get( "cl_cmdbackup", "2", CVAR_ARCHIVE, "how many additional history commands are sent" );
cl_cmdrate = Cvar_Get( "cl_cmdrate", "30", CVAR_ARCHIVE, "Max number of command packets sent to server per second" );
Cvar_Get( "hud_scale", "0", CVAR_ARCHIVE|CVAR_LATCH, "scale hud at current resolution" );
// register our commands

View File

@ -27,12 +27,12 @@ const char *svc_strings[256] =
"svc_stufftext",
"svc_setangle",
"svc_serverdata",
"svc_addangle",
"svc_restore",
"svc_frame",
"svc_clientdata",
"svc_packetentities",
"svc_download",
"svc_usermessage",
"svc_clientdata",
"svc_download",
"svc_updatepings",
"svc_particle",
"svc_ambientsound",
"svc_spawnstatic",
@ -53,11 +53,11 @@ const char *svc_strings[256] =
"svc_weaponanim",
"svc_bspdecal",
"svc_roomtype",
"svc_restore",
"svc_addangle",
"svc_unused39",
"svc_unused40",
"svc_unused41",
"svc_unused42",
"svc_packetentities",
"svc_deltapacketentities",
"svc_chokecount",
"svc_unused43",
"svc_unused44",
"svc_unused45",
@ -182,7 +182,7 @@ void CL_WriteMessageHistory( void )
for( i = 0; i < MSG_COUNT - 1; i++ )
{
thecmd &= CMD_MASK;
thecmd &= MSG_MASK;
old = &cls_message_debug.oldcmd[thecmd];
MsgDev( D_INFO,"%i %04i %s\n", old->frame_number, old->starting_offset, CL_MsgInfo( old->command ));
@ -583,6 +583,85 @@ void CL_ParseServerData( sizebuf_t *msg )
Mem_Set( &clgame.oldmovevars, 0, sizeof( clgame.oldmovevars ));
}
/*
===================
CL_ParseClientData
===================
*/
void CL_ParseClientData( sizebuf_t *msg )
{
int i, j;
clientdata_t *from_cd, *to_cd;
weapon_data_t *from_wd, *to_wd;
weapon_data_t nullwd[32];
clientdata_t nullcd;
frame_t *frame;
int idx;
// this is the frame update that this message corresponds to
i = cls.netchan.incoming_sequence;
// did we drop some frames?
if( i > cl.last_incoming_sequence + 1 )
{
// mark as dropped
for( j = cl.last_incoming_sequence + 1; j < i; j++ )
{
if( cl.frames[j & CL_UPDATE_MASK].receivedtime >= 0.0 )
{
cl.frames[j & CL_UPDATE_MASK ].receivedtime = -1;
cl.frames[j & CL_UPDATE_MASK ].latency = 0;
}
}
}
cl.parsecount = i; // ack'd incoming messages.
cl.parsecountmod = cl.parsecount & CL_UPDATE_MASK; // index into window.
frame = &cl.frames[cl.parsecountmod]; // frame at index.
frame->time = cl.mtime[0]; // mark network received time
frame->receivedtime = host.realtime; // time now that we are parsing.
// Fixme, do this after all packets read for this frame?
cl.last_incoming_sequence = cls.netchan.incoming_sequence;
to_cd = &frame->clientdata;
to_wd = frame->weapondata;
// clear to old value before delta parsing
if( !BF_ReadOneBit( msg ))
{
Mem_Set( &nullcd, 0, sizeof( nullcd ));
Mem_Set( nullwd, 0, sizeof( nullwd ));
from_cd = &nullcd;
from_wd = nullwd;
}
else
{
int delta_sequence = BF_ReadByte( msg );
from_cd = &cl.frames[delta_sequence & CL_UPDATE_MASK].clientdata;
from_wd = cl.frames[delta_sequence & CL_UPDATE_MASK].weapondata;
if(( delta_sequence & CL_UPDATE_MASK ) != ( cl.delta_sequence & CL_UPDATE_MASK ))
MsgDev( D_WARN, "CL_ParseClientData: mismatch delta_sequence\n" );
}
MSG_ReadClientData( msg, from_cd, to_cd, sv_time( ));
for( i = 0; i < MAX_WEAPONS; i++ )
{
// check for end of weapondata (and clientdata_t message)
if( !BF_ReadOneBit( msg )) break;
// read the weapon idx
idx = BF_ReadUBitLong( msg, MAX_WEAPON_BITS );
MSG_ReadWeaponData( msg, &from_wd[idx], &to_wd[idx], sv_time( ));
}
}
/*
==================
CL_ParseBaseline
@ -755,6 +834,33 @@ void CL_UpdateUserinfo( sizebuf_t *msg )
else Mem_Set( player, 0, sizeof( *player ));
}
/*
================
CL_UpdateUserPings
collect pings and packet lossage from clients
================
*/
void CL_UpdateUserPings( sizebuf_t *msg )
{
int i, slot;
player_info_t *player;
for( i = 0; i < MAX_CLIENTS; i++ )
{
if( !BF_ReadOneBit( msg )) break; // end of message
slot = BF_ReadUBitLong( msg, MAX_CLIENT_BITS );
if( slot >= MAX_CLIENTS )
Host_Error( "CL_ParseServerMessage: svc_updatepings > MAX_CLIENTS\n" );
player = &cl.players[slot];
player->ping = BF_ReadUBitLong( msg, 12 );
player->packet_loss = BF_ReadUBitLong( msg, 7 );
}
}
/*
==============
CL_ServerInfo
@ -863,7 +969,7 @@ CL_ParseServerMessage
void CL_ParseServerMessage( sizebuf_t *msg )
{
char *s;
int i, cmd;
int i, j, cmd;
int param1, param2;
int bufStart;
@ -898,7 +1004,6 @@ void CL_ParseServerMessage( sizebuf_t *msg )
Host_Error( "svc_bad\n" );
break;
case svc_nop:
MsgDev( D_ERROR, "CL_ParseServerMessage: user message out of bounds\n" );
break;
case svc_disconnect:
CL_Drop ();
@ -931,7 +1036,9 @@ void CL_ParseServerMessage( sizebuf_t *msg )
CL_ParseSoundPacket( msg, false );
break;
case svc_time:
BF_ReadFloat( msg ); // time
cl.mtime[1] = cl.mtime[0];
cl.mtime[0] = BF_ReadFloat( msg );
break;
break;
case svc_print:
i = BF_ReadByte( msg );
@ -953,18 +1060,21 @@ void CL_ParseServerMessage( sizebuf_t *msg )
case svc_addangle:
CL_ParseAddAngle( msg );
break;
case svc_frame:
CL_ParseFrame( msg );
break;
case svc_clientdata:
Host_Error( "svc_clientdata: out of place frame data\n" );
CL_ParseClientData( msg );
break;
case svc_packetentities:
Host_Error( "svc_packetentities: out of place frame data\n" );
CL_ParsePacketEntities( msg, false );
break;
case svc_deltapacketentities:
CL_ParsePacketEntities( msg, true );
break;
case svc_download:
CL_ParseDownload( msg );
break;
case svc_updatepings:
CL_UpdateUserPings( msg );
break;
case svc_usermessage:
CL_RegisterUserMessage( msg );
break;
@ -1032,6 +1142,18 @@ void CL_ParseServerMessage( sizebuf_t *msg )
param1 = BF_ReadShort( msg );
Cvar_SetValue( "room_type", param1 );
break;
case svc_chokecount:
i = BF_ReadByte( msg );
j = cls.netchan.incoming_acknowledged - 1;
for( ; i > 0 && j > cls.netchan.outgoing_sequence - CL_UPDATE_BACKUP; j-- )
{
if( cl.frames[j & CL_UPDATE_MASK].receivedtime != -3.0 )
{
cl.frames[j & CL_UPDATE_MASK].receivedtime = -2.0;
i--;
}
}
break;
case svc_director:
CL_ParseDirector( msg );
break;

View File

@ -436,8 +436,8 @@ void CL_SetSolidEntities( void )
for( i = 0; i < 3; i++ )
{
absmin[i] = cl.frame.cd.origin[i] - 1024;
absmax[i] = cl.frame.cd.origin[i] + 1024;
absmin[i] = cl.frame.clientdata.origin[i] - 1024;
absmax[i] = cl.frame.clientdata.origin[i] + 1024;
}
CL_CopyEntityToPhysEnt( &clgame.pmove->physents[0], &clgame.entities[0] );

View File

@ -69,7 +69,7 @@ void CL_CheckPredictionError( void )
// calculate the last usercmd_t we sent that the server has processed
frame = cls.netchan.incoming_acknowledged;
frame &= CMD_MASK;
frame &= CL_UPDATE_MASK;
// compare what the server returned with what we had predicted it to be
VectorSubtract( player->curstate.origin, cl.predicted_origins[frame], delta );
@ -106,7 +106,7 @@ void CL_SetIdealPitch( cl_entity_t *ent )
int i, j;
int step, dir, steps;
if( !( cl.frame.cd.flags & FL_ONGROUND ))
if( !( cl.frame.clientdata.flags & FL_ONGROUND ))
return;
angleval = ent->angles[YAW] * M_PI * 2 / 360;
@ -116,7 +116,7 @@ void CL_SetIdealPitch( cl_entity_t *ent )
{
top[0] = ent->origin[0] + cosval * (i + 3) * 12;
top[1] = ent->origin[1] + sinval * (i + 3) * 12;
top[2] = ent->origin[2] + cl.frame.cd.view_ofs[2];
top[2] = ent->origin[2] + cl.frame.clientdata.view_ofs[2];
bottom[0] = top[0];
bottom[1] = top[1];
@ -177,7 +177,7 @@ void CL_PredictMovement( void )
player = CL_GetLocalPlayer ();
viewent = CL_GetEntityByIndex( cl.refdef.viewentity );
cd = &cl.frame.cd;
cd = &cl.frame.clientdata;
CL_SetIdealPitch( player );
@ -230,7 +230,7 @@ void CL_PredictMovement( void )
// run frames
while( ++ack < current )
{
frame = ack & CMD_MASK;
frame = ack & CL_UPDATE_MASK;
cmd = &cl.cmds[frame];
CL_PreRunCmd( player, cmd );

View File

@ -33,11 +33,11 @@ void V_SetupRefDef( void )
clent = CL_GetLocalPlayer ();
VectorCopy( cl.frame.cd.punchangle, cl.refdef.punchangle );
VectorCopy( cl.frame.clientdata.punchangle, cl.refdef.punchangle );
cl.refdef.movevars = &clgame.movevars;
cl.refdef.onground = ( cl.frame.cd.flags & FL_ONGROUND ) ? 1 : 0;
cl.refdef.health = cl.frame.cd.health;
cl.refdef.onground = ( cl.frame.clientdata.flags & FL_ONGROUND ) ? 1 : 0;
cl.refdef.health = cl.frame.clientdata.health;
cl.refdef.lerpfrac = cl.lerpFrac;
cl.refdef.num_entities = clgame.numEntities;
cl.refdef.max_entities = clgame.maxEntities;
@ -46,7 +46,7 @@ void V_SetupRefDef( void )
cl.refdef.frametime = cl.time - cl.oldtime;
cl.refdef.demoplayback = cls.demoplayback;
cl.refdef.smoothing = cl_smooth->integer;
cl.refdef.waterlevel = cl.frame.cd.waterlevel;
cl.refdef.waterlevel = cl.frame.clientdata.waterlevel;
cl.refdef.flags = cl.render_flags;
cl.refdef.viewsize = 120; // FIXME if you can
cl.refdef.nextView = 0;
@ -73,8 +73,8 @@ void V_SetupRefDef( void )
else
{
VectorCopy( clent->origin, cl.refdef.simorg );
VectorCopy( cl.frame.cd.view_ofs, cl.refdef.viewheight );
VectorCopy( cl.frame.cd.velocity, cl.refdef.simvel );
VectorCopy( cl.frame.clientdata.view_ofs, cl.refdef.viewheight );
VectorCopy( cl.frame.clientdata.velocity, cl.refdef.simvel );
}
}

View File

@ -29,49 +29,61 @@
//=============================================================================
typedef struct frame_s
{
// received from server
double receivedtime; // time message was received, or -1
double latency;
double time; // server timestamp
clientdata_t clientdata; // message received that reflects performing
weapon_data_t weapondata[32];
packet_entities_t entities;
bool valid; // cleared if delta parsing was invalid
int serverframe;
int deltaframe;
int num_entities;
int parse_entities; // non-masked index into cl_parse_entities array
clientdata_t cd; // current client data
} frame_t;
#define CMD_BACKUP 64 // allow a lot of command backups for very fast systems
#define CMD_BACKUP MULTIPLAYER_BACKUP // allow a lot of command backups for very fast systems
#define CMD_MASK (CMD_BACKUP - 1)
#define CL_UPDATE_MASK (CL_UPDATE_BACKUP - 1)
extern int CL_UPDATE_BACKUP;
// the cl_parse_entities must be large enough to hold CL_UPDATE_BACKUP frames of
// entities, so that when a delta compressed message arives from the server
// it can be un-deltad from the original
#define MAX_PARSE_ENTITIES 2048
// the client_t structure is wiped completely at every
// server map change
typedef struct
{
int timeoutcount;
int servercount; // server identification for prespawns
int validsequence; // this is the sequence number of the last good
// world snapshot/update we got. If this is 0, we can't
// render a frame yet
int parsecount; // server message counter
int parsecountmod; // modulo with network window
double parsecounttime; // timestamp of parse
bool video_prepped; // false if on new level or new ref dll
bool audio_prepped; // false if on new level or new snd dll
bool force_refdef; // vid has changed, so we can't use a paused refdef
int parse_entities; // index (not anded off) into cl_parse_entities[]
usercmd_t cmds[CMD_BACKUP]; // each mesage will send several old cmds
int delta_sequence; // acknowledged sequence number
double mtime[2]; // the timestamp of the last two messages
int last_incoming_sequence;
bool force_send_usercmd;
frame_t frame; // received from server
frame_t *oldframe; // previous frame to lerping from
int surpressCount; // number of messages rate supressed
frame_t *frames; // alloced on svc_serverdata
frame_t frames[MULTIPLAYER_BACKUP]; // alloced on svc_serverdata
usercmd_t cmds[MULTIPLAYER_BACKUP]; // each mesage will send several old cmds
double time; // this is the time value that the client
// is rendering at. always <= cls.realtime
// a lerp point for other data
double oldtime; // previous cl.time, time-oldtime is used
// to decay light values and smooth step ups
double mtime[2]; // the timestamp of the last two messages
int render_flags; // clearing at end of frame
float lerpFrac; // interpolation value
@ -93,10 +105,8 @@ typedef struct
// server state information
int playernum;
int maxclients;
int servercount; // server identification for prespawns
int movemessages;
char configstrings[MAX_CONFIGSTRINGS][CS_SIZE];
entity_state_t entity_curstates[MAX_PARSE_ENTITIES]; // FIXME: this is must match with max_edicts ?
model_t *worldmodel; // pointer to world
@ -291,6 +301,12 @@ typedef struct
int serverProtocol; // in case we are doing some kind of version hack
int challenge; // from the server to use for connecting
float packet_loss;
double packet_loss_recalc_time;
float nextcmdtime; // when can we send the next command packet?
int lastoutgoingcommand; // sequence number of last outgoing command
// internal shaders
shader_t fillShader; // used for emulate FillRGBA to avoid wrong draw-sort
shader_t pauseIcon; // draw 'paused' when game in-pause
@ -477,9 +493,10 @@ bool CL_IsPredicted( void );
//
// cl_frame.c
//
void CL_ParseFrame( sizebuf_t *msg );
void CL_ParsePacketEntities( sizebuf_t *msg, bool delta );
void CL_UpdateStudioVars( cl_entity_t *ent, entity_state_t *newstate );
bool CL_GetEntitySpatialization( int ent, vec3_t origin, vec3_t velocity );
void CL_ClearFrames( void );
//
// cl_tent.c

View File

@ -64,6 +64,7 @@ extern cvar_t *scr_width;
extern cvar_t *scr_height;
extern cvar_t *scr_download;
extern cvar_t *allow_download;
extern cvar_t *host_limitlocal;
extern cvar_t *host_maxfps;
/*

View File

@ -112,7 +112,7 @@ void BF_WriteOneBit( sizebuf_t *bf, int nValue )
void BF_WriteUBitLongExt( sizebuf_t *bf, uint curData, int numbits, bool bCheckRange )
{
#ifdef _DEBUG
#ifdef _NETDEBUG
// make sure it doesn't overflow.
if( bCheckRange && numbits < 32 )
{
@ -175,7 +175,7 @@ void BF_WriteSBitLong( sizebuf_t *bf, int data, int numbits )
// (Some old code writes direct integers right into the buffer).
if( data < 0 )
{
#ifdef _DEBUG
#ifdef _NETDEBUG
if( numbits < 32 )
{
// Make sure it doesn't overflow.

View File

@ -35,6 +35,7 @@ typedef struct
#define BF_WriteUBitLong( bf, data, bits ) BF_WriteUBitLongExt( bf, data, bits, true );
#define BF_StartReading BF_StartWriting
#define BF_GetNumBytesRead BF_GetNumBytesWritten
#define BF_GetRealBytesRead BF_GetRealBytesWritten
#define BF_GetNumBitsRead BF_GetNumBitsWritten
#define BF_ReadBitAngles BF_ReadBitVec3Coord
#define BF_ReadString( bf ) BF_ReadStringExt( bf, false )
@ -81,7 +82,8 @@ bool BF_WriteString( sizebuf_t *bf, const char *pStr ); // returns false if it
bool BF_WriteDeltaMovevars( sizebuf_t *sb, struct movevars_s *from, struct movevars_s *to );
// helper functions
_inline int BF_GetNumBytesWritten( sizebuf_t *bf ) { return BitByte( bf->iCurBit ); }
_inline int BF_GetNumBytesWritten( sizebuf_t *bf ) { return BitByte( bf->iCurBit ); }
_inline int BF_GetRealBytesWritten( sizebuf_t *bf ) { return bf->iCurBit >> 3; } // unpadded
_inline int BF_GetNumBitsWritten( sizebuf_t *bf ) { return bf->iCurBit; }
_inline int BF_GetMaxBits( sizebuf_t *bf ) { return bf->nDataBits; }
_inline int BF_GetMaxBytes( sizebuf_t *bf ) { return bf->nDataBits >> 3; }

View File

@ -83,6 +83,7 @@ cvar_t *net_showdrop;
cvar_t *net_speeds;
cvar_t *net_qport;
int net_drop;
netadr_t net_from;
sizebuf_t net_message;
byte *net_mempool;
@ -1556,9 +1557,11 @@ bool Netchan_Process( netchan_t *chan, sizebuf_t *msg )
}
// dropped packets don't keep the message from being used
chan->dropped = sequence - ( chan->incoming_sequence + 1 );
if( chan->dropped > 0 )
net_drop = sequence - ( chan->incoming_sequence + 1 );
if( net_drop > 0 )
{
chan->drop_count += 1;
if( net_showdrop->integer )
{
Msg( "%s:Dropped %i packets at %i\n"

View File

@ -1359,6 +1359,81 @@ void MSG_ReadClientData( sizebuf_t *msg, clientdata_t *from, clientdata_t *to, f
/*
=============================================================================
weapon_data_t communication
=============================================================================
*/
/*
==================
MSG_WriteWeaponData
Writes current client data only for local client
Other clients can grab the client state from entity_state_t
==================
*/
void MSG_WriteWeaponData( sizebuf_t *msg, weapon_data_t *from, weapon_data_t *to, float timebase, int index )
{
delta_t *pField;
delta_info_t *dt;
int i, startBit;
int numChanges = 0;
dt = Delta_FindStruct( "weapon_data_t" );
ASSERT( dt && dt->bInitialized );
pField = dt->pFields;
ASSERT( pField );
// activate fields and call custom encode func
Delta_CustomEncode( dt, from, to );
startBit = msg->iCurBit;
BF_WriteOneBit( msg, 1 );
BF_WriteUBitLong( msg, index, MAX_WEAPON_BITS );
// process fields
for( i = 0; i < dt->numFields; i++, pField++ )
{
if( Delta_WriteField( msg, pField, from, to, timebase ))
numChanges++;
}
// if we have no changes - kill the message
if( !numChanges ) BF_SeekToBit( msg, startBit );
}
/*
==================
MSG_ReadWeaponData
Read the clientdata
==================
*/
void MSG_ReadWeaponData( sizebuf_t *msg, weapon_data_t *from, weapon_data_t *to, float timebase )
{
delta_t *pField;
delta_info_t *dt;
int i;
dt = Delta_FindStruct( "weapon_data_t" );
ASSERT( dt && dt->bInitialized );
pField = dt->pFields;
ASSERT( pField );
*to = *from;
// process fields
for( i = 0; i < dt->numFields; i++, pField++ )
{
Delta_ReadField( msg, pField, from, to, timebase );
}
}
/*
=============================================================================
entity_state_t communication
=============================================================================

View File

@ -100,6 +100,8 @@ bool MSG_WriteDeltaMovevars( sizebuf_t *msg, struct movevars_s *from, struct mov
void MSG_ReadDeltaMovevars( sizebuf_t *msg, struct movevars_s *from, struct movevars_s *to );
void MSG_WriteClientData( sizebuf_t *msg, struct clientdata_s *from, struct clientdata_s *to, float timebase );
void MSG_ReadClientData( sizebuf_t *msg, struct clientdata_s *from, struct clientdata_s *to, float timebase );
void MSG_WriteWeaponData( sizebuf_t *msg, struct weapon_data_s *from, struct weapon_data_s *to, float timebase, int index );
void MSG_ReadWeaponData( sizebuf_t *msg, struct weapon_data_s *from, struct weapon_data_s *to, float timebase );
void MSG_WriteDeltaEntity( struct entity_state_s *from, struct entity_state_s *to, sizebuf_t *msg, bool force, float timebase );
bool MSG_ReadDeltaEntity( sizebuf_t *msg, struct entity_state_s *from, struct entity_state_s *to, int num, float timebase );

View File

@ -119,7 +119,6 @@ typedef struct netchan_s
netadr_t remote_address; // address this channel is talking to.
int qport; // qport value to write when transmitting
int dropped; // between last packet and previous
bool compress; // enable huffman compression
double last_received; // for timeouts
@ -183,6 +182,7 @@ extern netadr_t net_from;
extern sizebuf_t net_message;
extern byte net_message_buffer[MAX_MSGLEN];
extern cvar_t *net_speeds;
extern int net_drop;
void Netchan_Init( void );
void Netchan_Shutdown( void );
@ -197,6 +197,7 @@ void Netchan_OutOfBandPrint( int net_socket, netadr_t adr, char *format, ... );
bool Netchan_Process( netchan_t *chan, sizebuf_t *msg );
void Netchan_UpdateProgress( netchan_t *chan );
bool Netchan_IncomingReady( netchan_t *chan );
bool Netchan_CanPacket( netchan_t *chan );
void Netchan_Clear( netchan_t *chan );
// huffman compression

View File

@ -8,61 +8,75 @@
#define PROTOCOL_VERSION 39
// server to client
#define svc_bad 0 // immediately crash client when received
#define svc_nop 1 // does nothing
#define svc_disconnect 2 // kick client from server
#define svc_changing 3 // changelevel by server request
#define svc_configstring 4 // [short] [string]
#define svc_setview 5 // [short] entity number
#define svc_sound 6 // <see code>
#define svc_time 7 // [float] server time
#define svc_print 8 // [byte] id [string] null terminated string
#define svc_stufftext 9 // [string] stuffed into client's console buffer, should be \n terminated
#define svc_setangle 10 // [angle angle] set the view angle to this absolute value
#define svc_serverdata 11 // [long] protocol ...
#define svc_addangle 12 // [angle] add angles when client turn on mover
#define svc_frame 13 // begin a new server frame
#define svc_clientdata 14 // [...]
#define svc_packetentities 15 // [...]
#define svc_download 16 // [short] size [size bytes]
#define svc_usermessage 17 // [string][byte] REG_USER_MSG stuff
#define svc_particle 18 // [float*3][char*3][byte][byte]
#define svc_ambientsound 19 // <see code>
#define svc_spawnstatic 20 // NOT IMPLEMENTED
#define svc_crosshairangle 21 // [short][short][short]
#define svc_spawnbaseline 22 // <see code>
#define svc_temp_entity 23 // <variable sized>
#define svc_setpause 24 // [byte] 0 = unpaused, 1 = paused
#define svc_deltamovevars 25 // [movevars_t]
#define svc_centerprint 26 // [string] to put in center of the screen
#define svc_event 27 // playback event queue
#define svc_event_reliable 28 // playback event directly from message, not queue
#define svc_updateuserinfo 29 // [byte] playernum, [string] userinfo
#define svc_intermission 30 // empty message (event)
#define svc_soundfade 31 // [float*4] sound fade parms
#define svc_cdtrack 32 // [byte] track [byte] looptrack
#define svc_serverinfo 33 // [string] key [string] value
#define svc_deltatable 34 // [table header][...]
#define svc_weaponanim 35 // [byte]iAnim [byte]body
#define svc_bspdecal 36 // [float*3][short][short][short]
#define svc_roomtype 37 // [short] room type
#define svc_restore 38 // restore saved game on the client
#define svc_bad 0 // immediately crash client when received
#define svc_nop 1 // does nothing
#define svc_disconnect 2 // kick client from server
#define svc_changing 3 // changelevel by server request
#define svc_configstring 4 // [short] [string]
#define svc_setview 5 // [short] entity number
#define svc_sound 6 // <see code>
#define svc_time 7 // [float] server time
#define svc_print 8 // [byte] id [string] null terminated string
#define svc_stufftext 9 // [string] stuffed into client's console buffer
#define svc_setangle 10 // [angle angle] set the view angle to this absolute value
#define svc_serverdata 11 // [long] protocol ...
#define svc_restore 12 // restore saved game on the client
#define svc_frame 13 // begin a new server frame
#define svc_usermessage 14 // [string][byte] REG_USER_MSG stuff
#define svc_clientdata 15 // [...]
#define svc_download 16 // [short] size [size bytes]
#define svc_updatepings 17 // [bit][idx][ping][packet_loss]
#define svc_particle 18 // [float*3][char*3][byte][byte]
#define svc_ambientsound 19 // <see code>
#define svc_spawnstatic 20 // NOT IMPLEMENTED
#define svc_crosshairangle 21 // [short][short][short]
#define svc_spawnbaseline 22 // <see code>
#define svc_temp_entity 23 // <variable sized>
#define svc_setpause 24 // [byte] 0 = unpaused, 1 = paused
#define svc_deltamovevars 25 // [movevars_t]
#define svc_centerprint 26 // [string] to put in center of the screen
#define svc_event 27 // playback event queue
#define svc_event_reliable 28 // playback event directly from message, not queue
#define svc_updateuserinfo 29 // [byte] playernum, [string] userinfo
#define svc_intermission 30 // empty message (event)
#define svc_soundfade 31 // [float*4] sound fade parms
#define svc_cdtrack 32 // [byte] track [byte] looptrack
#define svc_serverinfo 33 // [string] key [string] value
#define svc_deltatable 34 // [table header][...]
#define svc_weaponanim 35 // [byte]iAnim [byte]body
#define svc_bspdecal 36 // [float*3][short][short][short]
#define svc_roomtype 37 // [short] room type
#define svc_addangle 38 // [angle] add angles when client turn on mover
#define svc_director 51 // <variable sized>
#define svc_lastmsg 64 // start user messages at this point
#define svc_packetentities 40 // [short][...]
#define svc_deltapacketentities 41 // [short][byte][...]
#define svc_chokecount 42 // [byte]
#define svc_director 51 // <variable sized>
#define svc_lastmsg 64 // start user messages at this point
// client to server
#define clc_bad 0 // immediately drop client when received
#define clc_nop 1
#define clc_move 2 // [[usercmd_t]
#define clc_delta 3 // [byte] sequence number, requests delta compression of message
#define clc_userinfo 4 // [[userinfo string]
#define clc_stringcmd 5 // [string] message
#define clc_random_seed 6 // [long] random seed
#define clc_bad 0 // immediately drop client when received
#define clc_nop 1
#define clc_move 2 // [[usercmd_t]
#define clc_stringcmd 3 // [string] message
#define clc_delta 4 // [byte] sequence number, requests delta compression of message
#define clc_resourcelist 5
#define clc_userinfo 6 // [[userinfo string] // FIXME: remove
#define clc_fileconsistency 7
#define clc_voicedata 8
#define clc_random_seed 9 // [long] random seed // FIXME: remove
// additional protocol data
#define MAX_CLIENT_BITS 5
#define MAX_CLIENTS (1<<MAX_CLIENT_BITS)
#define MAX_CLIENT_BITS 5
#define MAX_CLIENTS (1<<MAX_CLIENT_BITS)
#define MAX_WEAPON_BITS 5
#define MAX_WEAPONS (1<<MAX_WEAPON_BITS)
// Max number of history commands to send ( 2 by default ) in case of dropped packets
#define NUM_BACKUP_COMMAND_BITS 3
#define MAX_BACKUP_COMMANDS (1 << NUM_BACKUP_COMMAND_BITS)
/*
==============================================================
@ -74,8 +88,9 @@ DELTA-PACKET ENTITIES
typedef struct
{
int num_entities;
entity_state_t *entities;
int max_entities; // this is a real allocated space
int num_entities;
} packet_entities_t;
#endif//PROTOCOL_H

View File

@ -19,6 +19,7 @@ dll_info_t render_dll = { "", NULL, "CreateAPI", NULL, NULL, 0, sizeof(render_ex
dll_info_t vsound_dll = { "", NULL, "CreateAPI", NULL, NULL, 0, sizeof(vsound_exp_t), sizeof(stdlib_api_t) };
cvar_t *host_serverstate;
cvar_t *host_limitlocal;
cvar_t *host_cheats;
cvar_t *host_maxfps;
cvar_t *host_framerate;
@ -742,6 +743,7 @@ void Host_Init( const int argc, const char **argv )
host_maxfps = Cvar_Get( "fps_max", "72", CVAR_ARCHIVE, "host fps upper limit" );
host_framerate = Cvar_Get( "host_framerate", "0", 0, "locks frame timing to this value in seconds" );
host_serverstate = Cvar_Get( "host_serverstate", "0", 0, "displays current server state" );
host_limitlocal = Cvar_Get( "host_limitlocal", "0", 0, "apply cl_cmdrate and rate to loopback connection" );
// content control
Cvar_Get( "violence_hgibs", "1", CVAR_INIT|CVAR_ARCHIVE, "content control disables human gibs" );

View File

@ -71,7 +71,6 @@ typedef struct server_s
double time; // sv.time += sv.frametime
float frametime;
int framenum;
int net_framenum; // to avoid send edicts twice through portals
int hostflags; // misc server flags: predicting etc
@ -105,13 +104,12 @@ typedef struct server_s
typedef struct
{
double senttime;
float ping_time;
float raw_ping;
float latency;
clientdata_t clientdata;
weapon_data_t weapondata[32];
packet_entities_t entities;
// legacy (needs to be removed)
float latency;
} client_frame_t;
typedef struct sv_client_s
@ -120,15 +118,22 @@ typedef struct sv_client_s
char userinfo[MAX_INFO_STRING]; // name, etc (received from client)
char physinfo[MAX_INFO_STRING]; // set on server (transmit to client)
bool send_message;
bool skip_message;
bool local_weapons; // enable weapon predicting
bool lag_compensation; // enable lag compensation
bool hltv_proxy; // this is spectator proxy (hltv)
netchan_t netchan;
int chokecount; // number of messages rate supressed
int delta_sequence; // -1 = no compression.
double next_messagetime; // time when we should send next world state update
double next_messageinterval; // default time to wait for next message
double cl_updaterate; // default time to wait for next message
double next_checkpingtime; // time to send all players pings to client
double timebase; // client timebase
bool sendmovevars;
bool sendinfo;
@ -136,17 +141,16 @@ typedef struct sv_client_s
bool fakeclient; // This client is a fake player controlled by the game DLL
int random_seed; // fpr predictable random values
int lastframe; // for delta compression
usercmd_t lastcmd; // for filling in big drops
double last_cmdtime;
double last_movetime;
double next_movetime;
int modelindex; // custom playermodel index
int packet_loss;
int ping;
int message_size[RATE_MESSAGES]; // used to rate drop packets
int rate;
int surpressCount; // number of messages rate supressed
float latency;
float ping;
float addangle; // add angles to client position
@ -211,6 +215,22 @@ typedef struct
vec3_t angles;
} sv_pushed_t;
typedef struct
{
bool active;
bool moving;
bool firstframe;
bool nointerp;
vec3_t mins;
vec3_t maxs;
vec3_t curpos;
vec3_t oldpos;
vec3_t newpos;
vec3_t finalpos;
} sv_interp_t;
typedef struct
{
// user messages stuff
@ -240,6 +260,7 @@ typedef struct
movevars_t movevars; // curstate
movevars_t oldmovevars; // oldstate
playermove_t *pmove; // pmove state
sv_interp_t interp[32]; // interpolate clients
sv_pushed_t pushed[256]; // no reason to keep array for all edicts
// 256 it should be enough for any game situation
@ -306,6 +327,11 @@ extern cvar_t *sv_maxspeed;
extern cvar_t *sv_maxclients;
extern cvar_t *sv_skyname;
extern cvar_t *serverinfo;
extern cvar_t *sv_failuretime;
extern cvar_t *sv_unlag;
extern cvar_t *sv_maxunlag;
extern cvar_t *sv_unlagpush;
extern cvar_t *sv_unlagsamples;
extern cvar_t *physinfo;
extern sv_client_t *sv_client;
@ -376,15 +402,17 @@ void SV_GetChallenge( netadr_t from );
void SV_DirectConnect( netadr_t from );
void SV_TogglePause( const char *msg );
void SV_PutClientInServer( edict_t *ent );
bool SV_ShouldUpdatePing( sv_client_t *cl );
void SV_FullClientUpdate( sv_client_t *cl, sizebuf_t *msg );
void SV_FullUpdateMovevars( sv_client_t *cl, sizebuf_t *msg );
void SV_GetPlayerStats( sv_client_t *cl, int *ping, int *packet_loss );
bool SV_ClientConnect( edict_t *ent, char *userinfo );
void SV_ClientThink( sv_client_t *cl, usercmd_t *cmd );
void SV_ExecuteClientMessage( sv_client_t *cl, sizebuf_t *msg );
void SV_ConnectionlessPacket( netadr_t from, sizebuf_t *msg );
edict_t *SV_FakeConnect( const char *netname );
void SV_PreRunCmd( sv_client_t *cl, usercmd_t *ucmd );
void SV_RunCmd( sv_client_t *cl, usercmd_t *ucmd );
void SV_PreRunCmd( sv_client_t *cl, usercmd_t *ucmd, int random_seed );
void SV_RunCmd( sv_client_t *cl, usercmd_t *ucmd, int random_seed );
void SV_PostRunCmd( sv_client_t *cl );
void SV_InitClientMove( void );
void SV_UpdateServerInfo( void );

View File

@ -9,6 +9,20 @@
#include "net_encode.h"
#include "entity_types.h"
const char *clc_strings[10] =
{
"clc_bad",
"clc_nop",
"clc_move",
"clc_stringcmd",
"clc_delta",
"clc_resourcelist",
"clc_userinfo",
"clc_fileconsistency",
"clc_voicedata",
"clc_random_seed",
};
typedef struct ucmd_s
{
const char *name;
@ -179,8 +193,6 @@ gotnewcl:
sv_client = newcl;
edictnum = (newcl - svs.clients) + 1;
SV_ClearFrames( &newcl->frames );
ent = EDICT_NUM( edictnum );
newcl->edict = ent;
newcl->challenge = challenge; // save challenge for checksumming
@ -207,8 +219,11 @@ gotnewcl:
BF_Init( &newcl->datagram, "Datagram", newcl->datagram_buf, sizeof( newcl->datagram_buf )); // datagram buf
newcl->state = cs_connected;
newcl->cl_updaterate = 0.05;
newcl->lastmessage = host.realtime;
newcl->lastconnect = host.realtime;
newcl->next_messagetime = host.realtime + newcl->cl_updaterate;
newcl->delta_sequence = -1;
// if this was the first client on the server, or the last client
// the server can hold, send a heartbeat to the master.
@ -271,6 +286,7 @@ edict_t *SV_FakeConnect( const char *netname )
newcl->edict = ent;
newcl->challenge = -1; // fake challenge
newcl->fakeclient = true;
newcl->delta_sequence = -1;
ent->v.flags |= FL_FAKECLIENT; // mark it as fakeclient
// get the game a chance to reject this connection or modify the userinfo
@ -597,6 +613,81 @@ void SV_RemoteCommand( netadr_t from, sizebuf_t *msg )
SV_EndRedirect();
}
/*
===================
SV_CalcPing
recalc ping on current client
===================
*/
int SV_CalcPing( sv_client_t *cl )
{
float ping = 0;
int i, count;
client_frame_t *frame;
// bots don't have a real ping
if( cl->fakeclient )
return 5;
count = 0;
for( i = 0; i < SV_UPDATE_BACKUP; i++ )
{
frame = &cl->frames[(cl->netchan.incoming_acknowledged - (i + 1)) & SV_UPDATE_MASK];
if( frame->raw_ping > 0 )
{
ping += frame->raw_ping;
count++;
}
}
if( !count )
return 0;
return (( ping / count ) * 1000 );
}
/*
===================
SV_EstablishTimeBase
Finangles latency and the like.
===================
*/
void SV_EstablishTimeBase( sv_client_t *cl, usercmd_t *ucmd, int numdrops, int numbackup, int newcmds )
{
double start;
double end;
int i;
start = 0;
end = sv.time + sv.frametime;
if( numdrops < 24 )
{
while( numdrops > numbackup )
{
start += cl->lastcmd.msec / 1000.0;
numdrops--;
}
while( numdrops > 0 )
{
start += ucmd[numdrops + newcmds - 1].msec / 1000.0;
numdrops--;
}
}
for( i = newcmds - 1; i >= 0; i-- )
{
start += ucmd[i].msec / 1000.0;
}
cl->timebase = end - start;
}
/*
===================
SV_FullClientUpdate
@ -652,6 +743,58 @@ void SV_FullUpdateMovevars( sv_client_t *cl, sizebuf_t *msg )
MSG_WriteDeltaMovevars( msg, &nullmovevars, &svgame.movevars );
}
/*
===================
SV_ShouldUpdatePing
this calls SV_CalcPing, and returns true
if this person needs ping data.
===================
*/
bool SV_ShouldUpdatePing( sv_client_t *cl )
{
if( !cl->hltv_proxy )
{
SV_CalcPing( cl );
return cl->lastcmd.buttons & IN_SCORE; // they are viewing the scoreboard. Send them pings.
}
if( host.realtime > cl->next_checkpingtime )
{
cl->next_checkpingtime = host.realtime + 2.0;
return true;
}
return false;
}
/*
===================
SV_GetPlayerStats
This function and its static vars track some of the networking
conditions. I haven't bothered to trace it beyond that, because
this fucntion sucks pretty badly.
===================
*/
void SV_GetPlayerStats( sv_client_t *cl, int *ping, int *packet_loss )
{
static int last_ping[MAX_CLIENTS];
static int last_loss[MAX_CLIENTS];
int i;
i = cl - svs.clients;
if( cl->next_checkpingtime < host.realtime )
{
cl->next_checkpingtime = host.realtime + 2.0;
last_ping[i] = SV_CalcPing( cl );
last_loss[i] = cl->packet_loss;
}
if( ping ) *ping = last_ping[i];
if( packet_loss ) *packet_loss = last_loss[i];
}
/*
===========
PutClientInServer
@ -698,6 +841,11 @@ void SV_PutClientInServer( edict_t *ent )
client->pViewEntity = NULL; // reset pViewEntity
// reset client times
client->last_cmdtime = 0.0;
client->last_movetime = 0.0;
client->next_movetime = 0.0;
if( !client->fakeclient )
{
// copy signon buffer
@ -1153,14 +1301,17 @@ void SV_UserinfoChanged( sv_client_t *cl, const char *userinfo )
// rate command
val = Info_ValueForKey( cl->userinfo, "rate" );
if( com.strlen( val ))
cl->rate = bound ( 100, com.atoi( val ), 15000 );
else cl->rate = 5000;
cl->netchan.rate = bound( MIN_RATE, com.atoi( val ), MAX_RATE );
else cl->netchan.rate = DEFAULT_RATE;
// msg command
val = Info_ValueForKey( cl->userinfo, "msg" );
if( com.strlen( val ))
cl->messagelevel = com.atoi( val );
cl->local_weapons = com.atoi( Info_ValueForKey( cl->userinfo, "cl_lw" )) ? true : false;
cl->lag_compensation = com.atoi( Info_ValueForKey( cl->userinfo, "cl_lc" )) ? true : false;
if( SV_IsValidEdict( ent ))
{
if( sv_maxclients->integer > 1 )
@ -1300,23 +1451,12 @@ each of the backup packets.
*/
static void SV_ReadClientMove( sv_client_t *cl, sizebuf_t *msg )
{
int key, size;
int checksum1, checksum2;
int key, lastframe, net_drop, size;
usercmd_t oldest, oldcmd, newcmd, nulcmd;
key = BF_GetNumBytesRead( msg );
checksum1 = BF_ReadByte( msg );
lastframe = BF_ReadLong( msg );
if( lastframe != cl->lastframe )
{
cl->lastframe = lastframe;
if( cl->lastframe > 0 )
{
client_frame_t *frame = &cl->frames[cl->lastframe & SV_UPDATE_MASK];
frame->latency = host.realtime - frame->senttime;
}
}
cl->packet_loss = SV_CalcPacketLoss( cl );
@ -1327,7 +1467,7 @@ static void SV_ReadClientMove( sv_client_t *cl, sizebuf_t *msg )
if( cl->state != cs_spawned )
{
cl->lastframe = -1;
cl->delta_sequence = -1;
return;
}
@ -1342,24 +1482,21 @@ static void SV_ReadClientMove( sv_client_t *cl, sizebuf_t *msg )
if( !sv.paused )
{
SV_PreRunCmd( cl, &newcmd ); // get random_seed from newcmd
net_drop = cl->netchan.dropped;
cl->netchan.dropped = 0; // reset counter
SV_PreRunCmd( cl, &newcmd, cl->random_seed ); // get random_seed from newcmd
if( net_drop < 20 )
{
while( net_drop > 2 )
{
SV_RunCmd( cl, &cl->lastcmd );
SV_RunCmd( cl, &cl->lastcmd, cl->random_seed );
net_drop--;
}
if( net_drop > 1 ) SV_RunCmd( cl, &oldest );
if( net_drop > 0 ) SV_RunCmd( cl, &oldcmd );
if( net_drop > 1 ) SV_RunCmd( cl, &oldest, cl->random_seed );
if( net_drop > 0 ) SV_RunCmd( cl, &oldcmd, cl->random_seed );
}
SV_RunCmd( cl, &newcmd );
SV_RunCmd( cl, &newcmd, cl->random_seed );
SV_PostRunCmd( cl );
}
@ -1367,6 +1504,131 @@ static void SV_ReadClientMove( sv_client_t *cl, sizebuf_t *msg )
cl->lastcmd.buttons = 0; // avoid multiple fires on lag
}
static void SV_ParseClientMove( sv_client_t *cl, sizebuf_t *msg )
{
client_frame_t *frame;
int key, size, checksum1, checksum2;
int i, numbackup, newcmds, numcmds;
usercmd_t nullcmd, *from;
usercmd_t cmds[32], *to;
edict_t *player;
numbackup = 2;
player = cl->edict;
frame = &cl->frames[cl->netchan.incoming_acknowledged & SV_UPDATE_MASK];
Mem_Set( &nullcmd, 0, sizeof( usercmd_t ));
key = BF_GetRealBytesRead( msg );
checksum1 = BF_ReadByte( msg );
cl->packet_loss = BF_ReadByte( msg );
numbackup = BF_ReadByte( msg );
newcmds = BF_ReadByte( msg );
numcmds = numbackup + newcmds;
net_drop = net_drop + 1 - newcmds;
if( numcmds < 0 || numcmds > 28 )
{
MsgDev( D_ERROR, "%s sending too many commands %i\n", cl->name, numcmds );
SV_DropClient( cl );
return;
}
from = &nullcmd; // first cmd are starting from null-comressed usercmd_t
for( i = numcmds - 1; i >= 0; i-- )
{
to = &cmds[i];
MSG_ReadDeltaUsercmd( msg, from, to );
from = to; // get new baseline
}
if( cl->state != cs_spawned )
{
cl->delta_sequence = -1;
return;
}
// if the checksum fails, ignore the rest of the packet
size = BF_GetRealBytesRead( msg ) - key - 1;
checksum2 = CRC_Sequence( msg->pData + key + 1, size, cl->netchan.incoming_sequence );
if( checksum2 != checksum1 )
{
MsgDev( D_ERROR, "SV_UserMove: failed command checksum for %s (%d != %d)\n", cl->name, checksum2, checksum1 );
return;
}
// check for pause or frozen
if( sv.paused || sv.loadgame || !CL_IsInGame() || ( player->v.flags & FL_FROZEN ))
{
for( i = 0; i < newcmds; i++ )
{
cmds[i].msec = 0;
cmds[i].forwardmove = 0;
cmds[i].sidemove = 0;
cmds[i].upmove = 0;
cmds[i].buttons = 0;
if( player->v.flags & FL_FROZEN )
cmds[i].impulse = 0;
VectorCopy( cmds[i].viewangles, player->v.v_angle );
}
net_drop = 0;
}
else
{
VectorCopy( cmds[0].viewangles, player->v.v_angle );
}
player->v.button = cmds[0].buttons;
player->v.light_level = cmds[0].lightlevel;
SV_EstablishTimeBase( cl, cmds, net_drop, numbackup, newcmds );
if( net_drop < 24 )
{
while( net_drop > numbackup )
{
SV_PreRunCmd( cl, &cl->lastcmd, 0 );
SV_RunCmd( cl, &cl->lastcmd, 0 );
SV_PostRunCmd( cl );
net_drop--;
}
while( net_drop > 0 )
{
i = net_drop + newcmds - 1;
SV_PreRunCmd( cl, &cmds[i], cl->netchan.incoming_sequence - i );
SV_RunCmd( cl, &cmds[i], cl->netchan.incoming_sequence - i );
SV_PostRunCmd( cl );
net_drop--;
}
}
for( i = newcmds - 1; i >= 0; i-- )
{
SV_PreRunCmd( cl, &cmds[i], cl->netchan.incoming_sequence - i );
SV_RunCmd( cl, &cmds[i], cl->netchan.incoming_sequence - i );
SV_PostRunCmd( cl );
}
cl->lastcmd = cmds[0];
cl->lastcmd.buttons = 0; // avoid multiple fires on lag
// adjust latency time by 1/2 last client frame since
// the message probably arrived 1/2 through client's frame loop
frame->latency -= cl->lastcmd.msec * 0.5f / 1000.0f;
frame->latency = max( 0.0f, frame->latency );
if( player->v.animtime > sv.time + sv.frametime )
player->v.animtime = sv.time + sv.frametime;
}
/*
===================
SV_ExecuteClientMessage
@ -1376,19 +1638,33 @@ Parse a client packet
*/
void SV_ExecuteClientMessage( sv_client_t *cl, sizebuf_t *msg )
{
int c, stringCmdCount = 0;
bool move_issued = false;
char *s;
int c, stringCmdCount = 0;
bool move_issued = false;
client_frame_t *frame;
char *s;
// make sure the reply sequence number matches the incoming sequence number
if( cl->netchan.incoming_sequence >= cl->netchan.outgoing_sequence )
cl->netchan.outgoing_sequence = cl->netchan.incoming_sequence;
else cl->send_message = false; // don't reply, sequences have slipped
// calc ping time
frame = &cl->frames[cl->netchan.incoming_acknowledged & SV_UPDATE_MASK];
// save time for ping calculations
cl->frames[cl->netchan.outgoing_sequence & SV_UPDATE_MASK].senttime = host.realtime;
cl->frames[cl->netchan.outgoing_sequence & SV_UPDATE_MASK].latency = -1.0f;
// raw ping doesn't factor in message interval, either
frame->raw_ping = host.realtime - frame->senttime;
// on first frame ( no senttime ) don't skew ping
if( frame->senttime == 0.0f )
{
frame->latency = 0.0f;
frame->raw_ping = 0.0f;
}
// don't skew ping based on signon stuff either
if(( host.realtime - cl->lastconnect ) < 2.0f && ( frame->latency > 0.0 ))
{
frame->latency = 0.0f;
frame->raw_ping = 0.0f;
}
cl->delta_sequence = -1; // no delta unless requested
// read optional clientCommand strings
while( cl->state != cs_zombie )
{
@ -1412,10 +1688,14 @@ void SV_ExecuteClientMessage( sv_client_t *cl, sizebuf_t *msg )
case clc_userinfo:
SV_UserinfoChanged( cl, BF_ReadString( msg ));
break;
case clc_delta:
cl->delta_sequence = BF_ReadByte( msg );
break;
case clc_move:
if( move_issued ) return; // someone is trying to cheat...
move_issued = true;
SV_ReadClientMove( cl, msg );
// SV_ReadClientMove( cl, msg );
SV_ParseClientMove( cl, msg );
break;
case clc_stringcmd:
s = BF_ReadString( msg );

View File

@ -20,7 +20,7 @@ typedef struct
static byte *clientpvs; // FatPVS
static byte *clientphs; // FatPHS
int c_fullsend;
int c_fullsend; // just a debug counter
/*
=======================
@ -61,6 +61,7 @@ void SV_ClearPacketEntities( client_frame_t *frame )
Mem_Free( packet->entities );
packet->num_entities = 0;
packet->max_entities = 0;
packet->entities = NULL;
}
}
@ -77,15 +78,15 @@ void SV_AllocPacketEntities( client_frame_t *frame, int count )
ASSERT( frame != NULL );
packet = &frame->entities;
if( packet->entities != NULL )
{
SV_ClearPacketEntities( frame );
}
if( count < 1 ) count = 1;
packet->entities = Mem_Alloc( svgame.mempool, sizeof( entity_state_t ) * count );
// check if new frame has more entities than previous
if( packet->max_entities < count )
{
SV_ClearPacketEntities( frame );
packet->entities = Mem_Alloc( svgame.mempool, sizeof( entity_state_t ) * count );
packet->max_entities = count;
}
packet->num_entities = count;
}
@ -104,86 +105,21 @@ void SV_ClearFrames( client_frame_t **frames )
if( *frames == NULL )
return;
for( i = 0, frame = *frames; i < SV_UPDATE_BACKUP; i++, frames++ )
for( i = 0, frame = *frames; i < SV_UPDATE_BACKUP; i++, frame++ )
{
SV_ClearPacketEntities( frame );
frame->senttime = 0.0f;
frame->ping_time = -1;
}
Mem_Free( *frames );
*frames = NULL;
}
/*
=============================================================================
Encode a client frame onto the network channel
=============================================================================
*/
/*
=============
SV_EmitPacketEntities
SV_AddEntitiesToPacket
Writes a delta update of an entity_state_t list to the message->
=============
*/
void SV_EmitPacketEntities( packet_entities_t *from, packet_entities_t *to, sizebuf_t *msg )
{
int oldindex, newindex;
int oldnum, newnum;
int oldmax;
BF_WriteByte( msg, svc_packetentities );
if( !from ) oldmax = 0;
else oldmax = from->num_entities;
newindex = 0;
oldindex = 0;
while( newindex < to->num_entities || oldindex < oldmax )
{
newnum = newindex >= to->num_entities ? MAX_ENTNUMBER : to->entities[newindex].number;
oldnum = oldindex >= oldmax ? MAX_ENTNUMBER : from->entities[oldindex].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( &from->entities[oldindex], &to->entities[newindex], msg, false, sv_time( ));
oldindex++;
newindex++;
continue;
}
if( newnum < oldnum )
{
// this is a new entity, send it from the baseline
MSG_WriteDeltaEntity( &svs.baselines[newnum], &to->entities[newindex], msg, true, sv_time( ));
newindex++;
continue;
}
if( newnum > oldnum )
{
bool force;
if( EDICT_NUM( from->entities[oldindex].number )->free )
force = true; // entity completely removed from server
else force = false; // just removed from delta-message
// remove from message
MSG_WriteDeltaEntity( &from->entities[oldindex], NULL, msg, force, sv_time( ));
oldindex++;
continue;
}
}
BF_WriteWord( msg, 0 ); // end of packetentities
}
static void SV_AddEntitiesToPacket( edict_t *pViewEnt, edict_t *pClient, client_frame_t *frame, sv_ents_t *ents )
{
edict_t *ent;
@ -207,8 +143,10 @@ static void SV_AddEntitiesToPacket( edict_t *pViewEnt, edict_t *pClient, client_
ASSERT( cl );
// setup hostflags
if( com.atoi( Info_ValueForKey( cl->userinfo, "cl_lw" )) == 1 )
if( cl->local_weapons )
{
sv.hostflags |= SVF_SKIPLOCALHOST;
}
}
svgame.dllFuncs.pfnSetupVisibility( pViewEnt, pClient, &clientpvs, &clientphs );
@ -254,7 +192,12 @@ static void SV_AddEntitiesToPacket( edict_t *pViewEnt, edict_t *pClient, client_
c_fullsend++; // debug counter
}
else MsgDev( D_ERROR, "too many entities in visible packet list\n" );
else
{
// visibility list is full
MsgDev( D_ERROR, "too many entities in visible packet list\n" );
break;
}
}
if( fullvis ) continue; // portal ents will be added anyway, ignore recursion
@ -269,7 +212,100 @@ static void SV_AddEntitiesToPacket( edict_t *pViewEnt, edict_t *pClient, client_
}
}
static void SV_EmitEvents( sv_client_t *cl, client_frame_t *frame, sizebuf_t *msg )
/*
=============================================================================
Encode a client frame onto the network channel
=============================================================================
*/
/*
=============
SV_EmitPacketEntities
Writes a delta update of an entity_state_t list to the message->
=============
*/
void SV_EmitPacketEntities( sv_client_t *cl, packet_entities_t *to, sizebuf_t *msg )
{
packet_entities_t *from;
client_frame_t *oldframe;
int oldindex, newindex;
int oldnum, newnum;
int oldmax;
// this is the frame that we are going to delta update from
if( cl->delta_sequence != -1 )
{
oldframe = &cl->frames[cl->delta_sequence & SV_UPDATE_MASK];
from = &oldframe->entities;
oldmax = from->num_entities;
BF_WriteByte( msg, svc_deltapacketentities );
BF_WriteWord( msg, to->num_entities );
BF_WriteByte( msg, cl->delta_sequence );
}
else
{
oldmax = 0; // no delta update
from = NULL;
BF_WriteByte( msg, svc_packetentities );
BF_WriteWord( msg, to->num_entities );
}
newindex = 0;
oldindex = 0;
while( newindex < to->num_entities || oldindex < oldmax )
{
newnum = newindex >= to->num_entities ? MAX_ENTNUMBER : to->entities[newindex].number;
oldnum = oldindex >= oldmax ? MAX_ENTNUMBER : from->entities[oldindex].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( &from->entities[oldindex], &to->entities[newindex], msg, false, sv_time( ));
oldindex++;
newindex++;
continue;
}
if( newnum < oldnum )
{
// this is a new entity, send it from the baseline
MSG_WriteDeltaEntity( &svs.baselines[newnum], &to->entities[newindex], msg, true, sv_time( ));
newindex++;
continue;
}
if( newnum > oldnum )
{
bool force;
if( EDICT_NUM( from->entities[oldindex].number )->free )
force = true; // entity completely removed from server
else force = false; // just removed from delta-message
// remove from message
MSG_WriteDeltaEntity( &from->entities[oldindex], NULL, msg, force, sv_time( ));
oldindex++;
continue;
}
}
BF_WriteWord( msg, 0 ); // end of packetentities
}
/*
=============
SV_EmitEvents
=============
*/
static void SV_EmitEvents( sv_client_t *cl, packet_entities_t *to, sizebuf_t *msg )
{
int i, ev;
event_state_t *es;
@ -315,146 +351,159 @@ static void SV_EmitEvents( sv_client_t *cl, client_frame_t *frame, sizebuf_t *ms
/*
=============
SV_WriteClientData
SV_EmitPings
=============
*/
void SV_WriteClientData( client_frame_t *from, client_frame_t *to, sizebuf_t *msg )
void SV_EmitPings( sizebuf_t *msg )
{
clientdata_t *cd, *ocd;
clientdata_t dummy;
sv_client_t *cl;
int packet_loss;
int i, ping;
cd = &to->clientdata;
BF_WriteByte( msg, svc_updatepings );
if( !from )
for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ )
{
Mem_Set( &dummy, 0, sizeof( dummy ));
ocd = &dummy;
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 );
}
else ocd = &from->clientdata;
// end marker
BF_WriteOneBit( msg, 0 );
}
/*
==================
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;
Mem_Set( &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 );
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 );
clent->v.effects |= EF_NOINTERP;
break;
case 2:
BF_WriteByte( msg, svc_addangle );
BF_WriteBitAngle( msg, cl->addangle, 16 );
cl->addangle = 0;
break;
}
clent->v.fixangle = 0; // reset fixangle
Mem_Set( &frame->clientdata, 0, sizeof( frame->clientdata ));
// update clientdata_t
svgame.dllFuncs.pfnUpdateClientData( clent, cl->local_weapons, &frame->clientdata );
BF_WriteByte( msg, svc_clientdata );
MSG_WriteClientData( msg, ocd, cd, sv.time );
}
if( cl->hltv_proxy ) return; // don't send more nothing
/*
==================
SV_WriteFrameToClient
==================
*/
void SV_WriteFrameToClient( sv_client_t *cl, sizebuf_t *msg )
{
client_frame_t *frame, *oldframe;
packet_entities_t *from, *to;
int lastframe;
if( cl->delta_sequence == -1 ) from_cd = &nullcd;
else from_cd = &cl->frames[cl->delta_sequence & SV_UPDATE_MASK].clientdata;
to_cd = &frame->clientdata;
// this is the frame we are creating
frame = &cl->frames[sv.framenum & SV_UPDATE_MASK];
to = &frame->entities;
if( cl->lastframe <= 0 )
{
// client is asking for a retransmit
oldframe = NULL;
lastframe = -1;
from = NULL;
}
else if( sv.framenum - cl->lastframe >= (SV_UPDATE_BACKUP - 3))
if( cl->delta_sequence == -1 )
{
// client hasn't gotten a good message through in a long time
oldframe = NULL;
lastframe = -1;
from = NULL;
BF_WriteOneBit( msg, 0 ); // no delta-compression
}
else
{ // we have a valid message to delta from
oldframe = &cl->frames[cl->lastframe & SV_UPDATE_MASK];
lastframe = cl->lastframe;
from = &oldframe->entities;
{
BF_WriteOneBit( msg, 1 ); // we are delta-ing from
BF_WriteByte( msg, cl->delta_sequence );
}
// delta encode the events
SV_EmitEvents( cl, frame, msg );
// write clientdata_t
MSG_WriteClientData( msg, from_cd, to_cd, sv.time );
BF_WriteByte( msg, svc_frame );
BF_WriteFloat( msg, (float)sv.time ); // send a servertime each frame
BF_WriteLong( msg, sv.framenum );
BF_WriteLong( msg, lastframe ); // what we are delta'ing from
BF_WriteByte( msg, cl->surpressCount ); // rate dropped packets
cl->surpressCount = 0;
if( cl->local_weapons && svgame.dllFuncs.pfnGetWeaponData( clent, frame->weapondata ))
{
Mem_Set( &nullwd, 0, sizeof( nullwd ));
// delta encode the clientdata
SV_WriteClientData( oldframe, frame, msg );
for( i = 0; i < 32; 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];
// delta encode the entities
SV_EmitPacketEntities( from, to, msg );
MSG_WriteWeaponData( msg, from_wd, to_wd, sv.time, i );
}
}
// end marker
BF_WriteOneBit( msg, 0 );
}
/*
=============================================================================
==================
SV_WriteEntitiesToClient
Build a client frame structure
=============================================================================
==================
*/
/*
=============
SV_BuildClientFrame
Decides which entities are going to be visible to the client,
and copies off the playerstate.
=============
*/
void SV_BuildClientFrame( sv_client_t *cl )
void SV_WriteEntitiesToClient( sv_client_t *cl, sizebuf_t *msg )
{
edict_t *clent;
edict_t *viewent; // may be NULL
client_frame_t *frame;
packet_entities_t *packet;
static sv_ents_t frame_ents;
bool send_pings;
clent = cl->edict;
viewent = cl->pViewEntity;
sv.net_framenum++;
viewent = cl->pViewEntity; // himself or trigger_camera
if( !sv.paused )
{
// update client fixangle
switch( clent->v.fixangle )
{
case 1:
BF_WriteByte( &cl->netchan.message, svc_setangle );
BF_WriteBitAngle( &cl->netchan.message, clent->v.angles[0], 16 );
BF_WriteBitAngle( &cl->netchan.message, clent->v.angles[1], 16 );
clent->v.effects |= EF_NOINTERP;
break;
case 2:
BF_WriteByte( &cl->netchan.message, svc_addangle );
BF_WriteBitAngle( &cl->netchan.message, cl->addangle, 16 );
cl->addangle = 0;
break;
}
clent->v.fixangle = 0; // reset fixangle
}
// this is the frame we are creating
frame = &cl->frames[sv.framenum & SV_UPDATE_MASK];
frame->senttime = host.realtime; // save it for ping calc later
frame = &cl->frames[cl->netchan.outgoing_sequence & SV_UPDATE_MASK];
packet = &frame->entities;
send_pings = SV_ShouldUpdatePing( cl );
sv.net_framenum++; // now all portal-through entities are invalidate
sv.hostflags &= ~SVF_PORTALPASS;
// clear everything in this snapshot
frame_ents.num_entities = c_fullsend = 0;
if( !SV_ClientFromEdict( clent, true )) return; // not in game yet
// update clientdata_t
svgame.dllFuncs.pfnUpdateClientData( clent, false, &frame->clientdata );
// add all the entities directly visible to the eye, which
// may include portal entities that merge other viewpoints
sv.hostflags &= ~SVF_PORTALPASS;
SV_AddEntitiesToPacket( viewent, clent, frame, &frame_ents );
// if there were portals visible, there may be out of order entities
// in the list which will need to be resorted for the delta compression
// to work correctly. This also catches the error condition
@ -464,6 +513,10 @@ void SV_BuildClientFrame( sv_client_t *cl )
// copy the entity states to client frame
SV_AllocPacketEntities( frame, frame_ents.num_entities );
Mem_Copy( packet->entities, frame_ents.entities, sizeof( entity_state_t ) * frame_ents.num_entities );
SV_EmitPacketEntities( cl, packet, msg );
SV_EmitEvents( cl, packet, msg );
if( send_pings ) SV_EmitPings( msg );
}
/*
@ -478,30 +531,24 @@ FRAME UPDATES
SV_SendClientDatagram
=======================
*/
bool SV_SendClientDatagram( sv_client_t *cl )
void SV_SendClientDatagram( sv_client_t *cl )
{
byte msg_buf[MAX_MSGLEN];
byte msg_buf[NET_MAX_PAYLOAD];
sizebuf_t msg;
SV_BuildClientFrame( cl );
svs.currentPlayer = cl;
BF_Init( &msg, "Datagram", msg_buf, sizeof( msg_buf ));
// send over all the relevant entity_state_t
// and the player state
SV_WriteFrameToClient( cl, &msg );
// always send servertime at new frame
BF_WriteByte( &msg, svc_time );
BF_WriteFloat( &msg, sv.time );
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 );
}
SV_WriteClientdataToMessage( cl, &msg );
SV_WriteEntitiesToClient( cl, &msg );
// copy the accumulated multicast datagram
// for this client out to the message
// it is necessary for this to be after the WriteEntities
// so that entity references will be current
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 );
@ -515,39 +562,6 @@ bool SV_SendClientDatagram( sv_client_t *cl )
// send the datagram
Netchan_TransmitBits( &cl->netchan, BF_GetNumBitsWritten( &msg ), BF_GetData( &msg ));
// record the size for rate estimation
cl->message_size[sv.framenum % RATE_MESSAGES] = BF_GetNumBytesWritten( &msg );
return true;
}
/*
=======================
SV_RateDrop
Returns true if the client is over its current
bandwidth estimation and should not be sent another packet
=======================
*/
bool SV_RateDrop( sv_client_t *cl )
{
int i, total = 0;
// never drop over the loopback
if( NET_IsLocalAddress( cl->netchan.remote_address ))
return false;
for( i = 0; i < RATE_MESSAGES; i++ )
total += cl->message_size[i];
if( total > cl->rate )
{
cl->surpressCount++;
cl->message_size[sv.framenum % RATE_MESSAGES] = 0;
return true;
}
return false;
}
/*
@ -573,6 +587,12 @@ void SV_UpdateToReliableMessages( void )
cl->sendinfo = false;
SV_FullClientUpdate( cl, &sv.reliable_datagram );
}
if( cl->sendmovevars )
{
cl->sendmovevars = false;
SV_FullUpdateMovevars( cl, &cl->netchan.message );
}
}
// clear the server datagram if it overflowed.
@ -614,27 +634,42 @@ void SV_SendClientMessages( void )
SV_UpdateToReliableMessages ();
// we always need to bump framenum, even if we
// don't run the world, otherwise the delta
// compression can get confused when a client
// has the "current" frame
sv.framenum++;
// send a message to each connected client
for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ )
{
if( !cl->state ) continue;
if( !cl->edict || (cl->edict->v.flags & ( FL_FAKECLIENT|FL_SPECTATOR )))
if( !cl->state || cl->fakeclient )
continue;
svs.currentPlayer = cl;
if( cl->sendmovevars )
if( cl->skip_message )
{
cl->sendmovevars = false;
SV_FullUpdateMovevars( cl, &cl->netchan.message );
}
cl->skip_message = false;
continue;
}
if( !host_limitlocal->integer && NET_IsLocalAddress( cl->netchan.remote_address ))
{
cl->send_message = true;
}
if( cl->state == cs_spawned )
{
// Try to send a message as soon as we can.
// If the target time for sending is within the next frame interval ( based on last frame ),
// trigger the send now. Note that in single player,
// send_message is also set to true any time a packet arrives from the client.
float time_unti_next_message = cl->next_messagetime - (host.realtime + host.frametime);
if( time_unti_next_message <= 0.0f )
{
cl->send_message = true;
}
// something got hosed
if( time_unti_next_message > 2.0f )
{
cl->send_message = true;
}
}
// if the reliable message overflowed, drop the client
if( BF_CheckOverflow( &cl->netchan.message ))
@ -642,26 +677,46 @@ void SV_SendClientMessages( void )
BF_Clear( &cl->netchan.message );
BF_Clear( &cl->datagram );
SV_BroadcastPrintf( PRINT_HIGH, "%s overflowed\n", cl->name );
MsgDev( D_WARN, "reliable overflow for %s\n", cl->name );
SV_DropClient( cl );
cl->send_message = true;
cl->netchan.cleartime = 0; // don't choke this message
}
else if( cl->send_message )
{
// If we haven't gotten a message in sv_failuretime seconds, then stop sending messages to this client
// until we get another packet in from the client. This prevents crash/drop and reconnect where they are
// being hosed with "sequenced packet without connection" packets.
if(( host.realtime - cl->netchan.last_received ) > sv_failuretime->value )
{
cl->send_message = false;
}
}
// only send messages if the client has sent one
// and the bandwidth is not choked
if( !cl->send_message ) continue;
// Bandwidth choke active?
if( !Netchan_CanPacket( &cl->netchan ))
{
cl->chokecount++;
continue;
}
cl->send_message = false;
// Now that we were able to send, reset timer to point to next possible send time.
cl->next_messagetime = host.realtime + host.frametime + cl->cl_updaterate;
if( cl->state == cs_spawned )
{
// don't overrun bandwidth
if( SV_RateDrop( cl )) continue;
SV_SendClientDatagram( cl );
}
else
{
if( cl->netchan.message.iCurBit || host.realtime - cl->netchan.last_sent > 1.0f )
Netchan_Transmit( &cl->netchan, 0, NULL );
Netchan_Transmit( &cl->netchan, 0, NULL );
}
// yes, message really sended
cl->send_message = false;
}
// reset current client

View File

@ -2970,8 +2970,8 @@ void pfnRunPlayerMove( edict_t *pClient, const float *v_angle, float fmove, floa
cl->random_seed = Com_RandomLong( 0, 0x7fffffff ); // full range
SV_PreRunCmd( cl, &cmd );
SV_RunCmd( cl, &cmd );
SV_PreRunCmd( cl, &cmd, cl->random_seed );
SV_RunCmd( cl, &cmd, cl->random_seed );
SV_PostRunCmd( cl );
cl->lastcmd = cmd;
@ -3418,9 +3418,7 @@ int pfnCanSkipPlayer( const edict_t *player )
return false;
}
if( com.atoi( Info_ValueForKey( cl->userinfo, "cl_lw" )) == 1 )
return true;
return false;
return cl->local_weapons;
}
/*
@ -3551,7 +3549,7 @@ void pfnGetPlayerStats( const edict_t *pClient, int *ping, int *packet_loss )
return;
}
if( ping ) *ping = cl->ping;
if( ping ) *ping = cl->ping * 1000;
if( packet_loss ) *packet_loss = cl->packet_loss;
}

View File

@ -122,7 +122,7 @@ void SV_ActivateServer( void )
if( svs.clients[i].state >= cs_connected )
{
Netchan_Clear( &svs.clients[i].netchan );
svs.clients[i].lastframe = -1;
svs.clients[i].delta_sequence = -1;
}
}
@ -183,13 +183,10 @@ void SV_DeactivateServer( void )
svgame.dllFuncs.pfnServerDeactivate();
// set client fields on player ents
for( i = 0; i < svgame.globals->maxClients; i++ )
{
// free client frames
if( svs.clients[i].frames )
Mem_Free( svs.clients[i].frames );
svs.clients[i].frames = NULL;
// release client frames
SV_ClearFrames( &svs.clients[i].frames );
}
svgame.globals->maxEntities = GI->max_edicts;
@ -303,10 +300,6 @@ bool SV_SpawnServer( const char *mapname, const char *startspot )
// needs to reconnect
if( svs.clients[i].state > cs_connected )
svs.clients[i].state = cs_connected;
svs.clients[i].lastframe = -1;
// release frames
SV_ClearFrames( &svs.clients[i].frames );
}
// make cvars consistant
@ -447,13 +440,7 @@ void SV_InitGame( void )
// setup all the clients
ent = EDICT_NUM( i + 1 );
SV_InitEdict( ent );
SV_ClearFrames( &svs.clients[i].frames );
// make crosslinks
svs.clients[i].edict = ent;
Mem_Set( &svs.clients[i].lastcmd, 0, sizeof( svs.clients[i].lastcmd ));
Mem_Set( &svs.clients[i].physinfo, 0, sizeof( svs.clients[i].physinfo ));
}
svgame.numEntities = svgame.globals->maxClients + 1; // clients + world

View File

@ -12,6 +12,10 @@
netadr_t master_adr[MAX_MASTERS]; // address of group servers
cvar_t *sv_zmax;
cvar_t *sv_unlag;
cvar_t *sv_maxunlag;
cvar_t *sv_unlagpush;
cvar_t *sv_unlagsamples;
cvar_t *sv_pausable;
cvar_t *sv_newunit;
cvar_t *sv_wateramp;
@ -41,8 +45,10 @@ cvar_t *sv_check_errors;
cvar_t *sv_footsteps;
cvar_t *public_server; // should heartbeats be sent
cvar_t *sv_reconnect_limit; // minimum seconds between connect messages
cvar_t *sv_failuretime;
cvar_t *serverinfo;
cvar_t *physinfo;
cvar_t *clockwindow;
// sky variables
cvar_t *sv_skycolor_r;
@ -218,6 +224,47 @@ void SV_UpdateServerInfo( void )
serverinfo->modified = false;
}
/*
=================
SV_CheckCmdTimes
=================
*/
void SV_CheckCmdTimes( void )
{
sv_client_t *cl;
static double lastreset = 0;
double timewindow;
int i;
if( 1.0 > host.realtime - lastreset )
return;
lastreset = host.realtime;
for( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ )
{
if( cl->state != cs_spawned )
continue;
if( cl->last_cmdtime == 0.0 )
{
cl->last_cmdtime = host.realtime;
}
timewindow = cl->last_movetime + cl->last_cmdtime - host.realtime;
if( timewindow > clockwindow->value )
{
cl->next_movetime = clockwindow->value + host.realtime;
cl->last_movetime = host.realtime - cl->last_cmdtime;
}
else if( timewindow < -clockwindow->value )
{
cl->last_movetime = host.realtime - cl->last_cmdtime;
}
}
}
/*
=================
SV_ReadPackets
@ -412,6 +459,9 @@ void Host_ServerFrame( void )
// check timeouts
SV_CheckTimeouts ();
// check clients timewindow
SV_CheckCmdTimes ();
// read packets from clients
SV_ReadPackets ();
@ -572,6 +622,12 @@ void SV_Init( void )
serverinfo = Cvar_Get( "@serverinfo", "0", CVAR_READ_ONLY, "" ); // use ->modified value only
public_server = Cvar_Get ("public", "0", 0, "change server type from private to public" );
sv_reconnect_limit = Cvar_Get ("sv_reconnect_limit", "3", CVAR_ARCHIVE, "max reconnect attempts" );
sv_failuretime = Cvar_Get( "sv_failuretime", "0.5", 0, "after this long without a packet from client, don't send any more until client starts sending again" );
sv_unlag = Cvar_Get( "sv_unlag", "1", 0, "allow lag compensation on server-side" );
sv_maxunlag = Cvar_Get( "sv_maxunlag", "0.5", 0, "max latency which can be interpolated" );
sv_unlagpush = Cvar_Get( "sv_unlagpush", "0.0", 0, "unlag push bias" );
sv_unlagsamples = Cvar_Get( "sv_unlagsamples", "1", 0, "max samples to interpolate" );
clockwindow = Cvar_Get( "clockwindow", "0.5", 0, "timewindow to execute client moves" );
SV_ClearSaveDir (); // delete all temporary *.hl files
BF_Init( &net_message, "NetMessage", net_message_buffer, sizeof( net_message_buffer ));

View File

@ -613,10 +613,207 @@ static void PM_FinishMove( playermove_t *pmove, edict_t *clent )
else clent->v.groundentity = NULL;
}
void SV_PreRunCmd( sv_client_t *cl, usercmd_t *ucmd )
int nofind = 0;
entity_state_t *SV_FindEntInPack( int index, packet_entities_t *PacksToSearch )
{
int i;
for( i = 0; i < PacksToSearch->max_entities; i++ )
{
if( PacksToSearch->entities[i].number == index )
return(&(PacksToSearch->entities[i]));
}
return NULL;
}
int SV_UnlagCheckTeleport( vec3_t old_pos, vec3_t new_pos )
{
int i;
for( i = 0; i < 3; i++ )
{
if( fabs( old_pos[i] - new_pos[i] ) > 50 )
return 1;
}
return 0;
}
void SV_SetupMoveInterpolant( sv_client_t *cl )
{
int i, j, var_C_entindex;
float finalpush, lerp_msec, latency, temp, lerpFrac;
client_frame_t *frame, *var_24;
packet_entities_t *entities;
entity_state_t *state, *var_38_FoundEntity;
sv_client_t *check;
sv_interp_t *lerp;
vec3_t tempvec, tempvec2;
Mem_Set( svgame.interp, 0, sizeof( svgame.interp ));
nofind = 1;
// don't allow unlag in singleplayer
if( sv_maxclients->integer <= 1 ) return;
// unlag disabled by game request
if( !svgame.dllFuncs.pfnAllowLagCompensation() || !sv_unlag->integer )
return;
// unlag disabled for current client
if( cl->state != cs_spawned || !cl->lag_compensation )
return;
nofind = 0;
for( i = 0, check = svs.clients; i < sv_maxclients->integer; i++, cl++ )
{
if( check->state != cs_spawned || check == cl )
continue;
lerp = &svgame.interp[i];
VectorCopy( check->edict->v.origin, lerp->oldpos );
VectorCopy( check->edict->v.absmin, lerp->mins );
VectorCopy( check->edict->v.absmax, lerp->maxs );
lerp->active = true;
}
if( cl->latency > 1.5f )
latency = 1.5f;
else latency = cl->latency;
temp = sv_maxunlag->value;
if( temp > 0 && latency > temp )
latency = temp;
lerp_msec = cl->lastcmd.lerp_msec * 0.001f;
if( lerp_msec > 0.1f ) lerp_msec = 0.1f;
if( lerp_msec < cl->cl_updaterate )
lerp_msec = cl->cl_updaterate;
finalpush = sv_unlagpush->value + (( host.realtime - latency ) - lerp_msec );
if( finalpush > host.realtime )
finalpush = host.realtime;
frame = NULL;
for( var_24 = NULL, i = 0; i < SV_UPDATE_BACKUP; i++, var_24 = frame )
{
frame = &cl->frames[(cl->netchan.outgoing_sequence - 1) & SV_UPDATE_MASK];
entities = &frame->entities;
for( j = 0; j < entities->num_entities; j++ )
{
state = &entities->entities[j];
if( state->number <= 0 || state->number >= sv_maxclients->integer )
continue;
// you know, jge doesn't sound right given how the indexing normally works.
lerp = &svgame.interp[state->number-1];
if( lerp->nointerp )
continue;
if( state->health <= 0 || ( state->effects & EF_NOINTERP ))
lerp->nointerp = true;
if( !lerp->firstframe )
lerp->firstframe = true;
else if( SV_UnlagCheckTeleport( state->origin, lerp->finalpos ))
lerp->nointerp = true;
VectorCopy( state->origin, lerp->finalpos );
}
if( finalpush > frame->senttime )
break;
}
if( i == SV_UPDATE_BACKUP || finalpush - frame->senttime > 1.0 )
{
Mem_Set( svgame.interp, 0, sizeof( svgame.interp ));
nofind = 1;
return;
}
entities = &frame->entities;
if( var_24 == NULL )
{
var_24 = frame;
lerpFrac = 0;
}
else
{
if( var_24->senttime - frame->senttime == 0.0 )
{
lerpFrac = 0;
}
else
{
lerpFrac = (finalpush - frame->senttime) / (var_24->senttime - frame->senttime);
lerpFrac = bound( 0.0f, lerpFrac, 1.0f );
}
}
for( i = 0; i < entities->num_entities; i++ )
{
state = &entities->entities[i];
if( state->number <= 0 || state->number >= sv_maxclients->integer )
continue;
var_C_entindex = state->number - 1;
check = &svs.clients[var_C_entindex];
if( check->state != cs_spawned || check == cl )
continue;
lerp = &svgame.interp[var_C_entindex];
if( !lerp->active || lerp->nointerp )
continue;
var_38_FoundEntity = SV_FindEntInPack( state->number, &var_24->entities );
if( var_38_FoundEntity == NULL )
{
tempvec[0] = state->origin[0];
tempvec[1] = state->origin[1];
tempvec[2] = state->origin[2];
}
else
{
tempvec2[0] = var_38_FoundEntity->origin[0] - state->origin[0];
tempvec2[1] = var_38_FoundEntity->origin[1] - state->origin[1];
tempvec2[2] = var_38_FoundEntity->origin[2] - state->origin[2];
VectorMA( state->origin, lerpFrac, tempvec2, tempvec );
}
VectorCopy( tempvec, lerp->curpos );
VectorCopy( tempvec, lerp->newpos );
if( !VectorCompare( tempvec, check->edict->v.origin ))
{
VectorCopy( tempvec, check->edict->v.origin );
SV_LinkEdict( check->edict, false );
lerp->moving = true;
}
}
}
void SV_PreRunCmd( sv_client_t *cl, usercmd_t *ucmd, int random_seed )
{
svgame.pmove->runfuncs = true; // FIXME: check cl_lc ?
svgame.dllFuncs.pfnCmdStart( cl->edict, ucmd, cl->random_seed );
svgame.dllFuncs.pfnCmdStart( cl->edict, ucmd, random_seed );
}
/*
@ -624,13 +821,38 @@ void SV_PreRunCmd( sv_client_t *cl, usercmd_t *ucmd )
SV_RunCmd
===========
*/
void SV_RunCmd( sv_client_t *cl, usercmd_t *ucmd )
void SV_RunCmd( sv_client_t *cl, usercmd_t *ucmd, int random_seed )
{
edict_t *clent;
vec3_t oldvel;
usercmd_t cmd;
int oldmsec;
clent = cl->edict;
if( !SV_IsValidEdict( clent )) return;
if( cl->next_movetime > host.realtime )
{
cl->last_movetime += ( ucmd->msec * 0.001f );
return;
}
cl->next_movetime = 0.0;
// chop up very long commands
if( ucmd->msec > 50 )
{
cmd = *ucmd;
oldmsec = ucmd->msec;
cmd.msec = oldmsec / 2;
SV_RunCmd( cl, &cmd, random_seed );
cmd.msec = oldmsec / 2 + (oldmsec & 1); // give them back thier msec.
cmd.impulse = 0;
SV_RunCmd( cl, &cmd, random_seed );
return;
}
PM_CheckMovingGround( clent, ucmd->msec * 0.001f );

View File

@ -1060,8 +1060,8 @@ void ResizeTexture( s_texture_t *ptexture )
// dword alignment for each scan
ptexture->skintop = ptexture->min_t;
ptexture->skinleft = ptexture->min_s;
ptexture->skinwidth = (int)(ptexture->max_s - ptexture->min_s) + 1;
ptexture->skinheight = (int)(ptexture->max_t - ptexture->min_t) + 1;
ptexture->skinwidth = ((int)(ptexture->max_s - ptexture->min_s + 1) + 3) & ~3;
ptexture->skinheight = (int)(ptexture->max_t - ptexture->min_t + 1);
ptexture->size = ptexture->skinwidth * ptexture->skinheight + 256 * 3;
percent = ((ptexture->skinwidth * ptexture->skinheight) / (float)(ptexture->srcwidth * ptexture->srcheight)) * 100.0f;