engine: client: allow extended amount of commands to be sent for newer GoldSrc server. Also refactoring

This commit is contained in:
Alibek Omarov 2024-11-05 21:57:31 +03:00
parent ad457f717b
commit fa6f564c12
3 changed files with 115 additions and 165 deletions

View file

@ -25,11 +25,10 @@ GNU General Public License for more details.
#include "pm_local.h"
#include "multi_emulator.h"
#define MAX_TOTAL_CMDS 32
#define MAX_CMD_BUFFER 8000
#define CONNECTION_PROBLEM_TIME 15.0 // 15 seconds
#define CL_CONNECTION_RETRIES 10
#define CL_TEST_RETRIES 5
#define MAX_CMD_BUFFER 8000
#define CL_CONNECTION_TIMEOUT 15.0f
#define CL_CONNECTION_RETRIES 10
#define CL_TEST_RETRIES 5
CVAR_DEFINE_AUTO( showpause, "1", 0, "show pause logo when paused" );
CVAR_DEFINE_AUTO( mp_decals, "300", FCVAR_ARCHIVE, "decals limit in multiplayer" );
@ -379,30 +378,20 @@ CL_ComputePacketLoss
*/
static void CL_ComputePacketLoss( void )
{
int i, frm;
frame_t *frame;
int count = 0;
int lost = 0;
int i, 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.0 )
if( cl.frames[i & CL_UPDATE_MASK].receivedtime == -1.0 )
lost++;
count++;
}
if( count <= 0 ) cls.packet_loss = 0.0f;
else cls.packet_loss = ( 100.0f * (float)lost ) / (float)count;
cls.packet_loss = lost * 100.0f / (float)CL_UPDATE_BACKUP;
}
/*
@ -504,13 +493,12 @@ static qboolean CL_ProcessShowTexturesCmds( usercmd_t *cmd )
{
static int oldbuttons;
int changed;
int pressed, released;
int released;
if( !r_showtextures.value || CL_IsDevOverviewMode( ))
return false;
changed = (oldbuttons ^ cmd->buttons);
pressed = changed & cmd->buttons;
released = changed & (~cmd->buttons);
if( released & ( IN_RIGHT|IN_MOVERIGHT ))
@ -729,14 +717,10 @@ Including both the reliable commands and the usercmds
*/
static void CL_WritePacket( void )
{
sizebuf_t buf;
qboolean send_command = false;
byte data[MAX_CMD_BUFFER];
int i, from, to, key, size;
int numbackup = 2, maxbackup;
int numcmds, maxcmds;
int newcmds;
int cmdnumber;
sizebuf_t buf;
byte data[MAX_CMD_BUFFER] = { 0 };
runcmd_t *pcmd;
int numbackup, maxbackup, maxcmds;
const connprotocol_t proto = cls.legacymode;
// don't send anything if playing back a demo
@ -748,13 +732,12 @@ static void CL_WritePacket( void )
Netchan_TransmitBits( &cls.netchan, 0, "" );
return;
}
// cls.state can only be ca_validate or ca_active from here
CL_ComputePacketLoss ();
CL_ComputePacketLoss( );
memset( data, 0, sizeof( data ));
MSG_Init( &buf, "ClientData", data, sizeof( data ));
// Determine number of backup commands to send along
switch( proto )
{
case PROTO_GOLDSRC:
@ -773,168 +756,118 @@ static void CL_WritePacket( void )
numbackup = bound( 0, cl_cmdbackup.value, maxbackup );
// allow extended usercmd limit
if( proto == PROTO_GOLDSRC && cls.build_num >= 5971 )
maxcmds = MAX_GOLDSRC_EXTENDED_TOTAL_CMDS - numbackup;
// clamp cmdrate
if( cl_cmdrate.value < 10.0f )
Cvar_DirectSet( &cl_cmdrate, "10" );
else if( cl_cmdrate.value > 100.0f )
Cvar_DirectSet( &cl_cmdrate, "100" );
// Check to see if we can actually send this command
// are we hltv spectator?
if( cls.spectator && cl.delta_sequence == cl.validsequence && ( !cls.demorecording || !cls.demowaiting ) && cls.nextcmdtime + 1.0f > host.realtime )
return;
// 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.value ))
send_command = true;
// can send this command?
pcmd = &cl.commands[cls.netchan.outgoing_sequence & CL_UPDATE_MASK];
if(( host.realtime >= cls.nextcmdtime ) && Netchan_CanPacket( &cls.netchan, true ))
send_command = true;
if( cl.maxclients == 1 || ( NET_IsLocalAddress( cls.netchan.remote_address ) && !host_limitlocal.value ) || ( host.realtime >= cls.nextcmdtime && Netchan_CanPacket( &cls.netchan, true )))
pcmd->heldback = false;
else pcmd->heldback = true;
// spectator is not sending cmds to server
if( cls.spectator && cls.state == ca_active && cl.delta_sequence == cl.validsequence )
// immediately add it to the demo, regardless if we send the message or not
if( cls.demorecording )
CL_WriteDemoUserCmd( cls.netchan.outgoing_sequence & CL_UPDATE_MASK );
if( !pcmd->heldback )
{
if( !( cls.demorecording && cls.demowaiting ) && cls.nextcmdtime + 1.0f > host.realtime )
return;
}
int newcmds, numcmds;
int from, i, key;
if(( cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged ) >= CL_UPDATE_MASK )
{
if(( host.realtime - cls.netchan.last_received ) > CONNECTION_PROBLEM_TIME )
cls.nextcmdtime = host.realtime + ( 1.0f / cl_cmdrate.value );
if( cls.lastoutgoingcommand < 0 )
cls.lastoutgoingcommand = cls.netchan.outgoing_sequence;
newcmds = cls.netchan.outgoing_sequence - cls.lastoutgoingcommand;
newcmds = bound( 0, newcmds, maxcmds );
numcmds = newcmds + numbackup;
// goldsrc starts writing clc_move earlier but it doesn't make sense if it's not going to be sent
MSG_BeginClientCmd( &buf, clc_move );
if( proto == PROTO_GOLDSRC )
MSG_WriteByte( &buf, 0 ); // command length
key = MSG_GetRealBytesWritten( &buf );
MSG_WriteByte( &buf, 0 );
MSG_WriteByte( &buf, bound( 0, (int)cls.packet_loss, 100 ));
MSG_WriteByte( &buf, numbackup );
MSG_WriteByte( &buf, newcmds );
for( from = -1, i = numcmds - 1; i >= 0; i-- )
{
int to = ( cls.netchan.outgoing_sequence - i ) & CL_UPDATE_MASK;
CL_WriteUsercmd( proto, &buf, from, to );
from = to;
}
// finalize message
if( proto == PROTO_GOLDSRC )
{
int size = MSG_GetRealBytesWritten( &buf ) - key - 1;
buf.pData[key - 1] = Q_min( size, 255 );
buf.pData[key] = CRC32_BlockSequence( &buf.pData[key + 1], size, cls.netchan.outgoing_sequence );
COM_Munge( &buf.pData[key + 1], Q_min( size, 255 ), cls.netchan.outgoing_sequence );
}
else
{
int size = MSG_GetRealBytesWritten( &buf ) - key - 1;
buf.pData[key] = CRC32_BlockSequence( &buf.pData[key + 1], size, cls.netchan.outgoing_sequence );
}
// check if we're timing out
if( cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged >= CL_UPDATE_MASK && host.realtime - cls.netchan.last_received >= CL_CONNECTION_TIMEOUT )
{
Con_NPrintf( 1, "^3Warning:^1 Connection Problem^7\n" );
Con_NPrintf( 2, "^1Auto-disconnect in %.1f seconds^7", cl_timeout.value - ( host.realtime - cls.netchan.last_received ));
cl.validsequence = 0;
}
}
if( cl_nodelta.value )
cl.validsequence = 0;
if( cl_nodelta.value )
cl.validsequence = 0;
if( send_command )
{
int outgoing_sequence;
cls.nextcmdtime = host.realtime + ( 1.0f / cl_cmdrate.value );
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
MSG_BeginClientCmd( &buf, clc_move );
if( proto == PROTO_GOLDSRC )
MSG_WriteByte( &buf, 0 ); // length
// save the position for a checksum byte
key = MSG_GetRealBytesWritten( &buf );
MSG_WriteByte( &buf, 0 );
// write packet lossage percentation
MSG_WriteByte( &buf, bound( 0, (int)cls.packet_loss, 100 ) );
// say how many backups we'll be sending
MSG_WriteByte( &buf, numbackup );
// how many real commands have queued up
newcmds = ( cls.netchan.outgoing_sequence - cls.lastoutgoingcommand );
// put an upper/lower bound on this
newcmds = bound( 0, newcmds, maxcmds );
if( cls.state == ca_connected )
newcmds = 0;
MSG_WriteByte( &buf, newcmds );
numcmds = newcmds + numbackup;
from = -1;
for( i = numcmds - 1; i >= 0; i-- )
{
cmdnumber = ( cls.netchan.outgoing_sequence - i ) & CL_UPDATE_MASK;
to = cmdnumber;
CL_WriteUsercmd( proto, &buf, from, to );
from = to;
if( MSG_CheckOverflow( &buf ))
Host_Error( "%s: overflowed command buffer (%i bytes)\n", __func__, MAX_CMD_BUFFER );
}
// calculate a checksum over the move commands
if( proto == PROTO_GOLDSRC )
{
size = MSG_GetRealBytesWritten( &buf ) - key - 1;
buf.pData[key - 1] = Q_min( size, 255 );
buf.pData[key] = CRC32_BlockSequence( buf.pData + key + 1, size, cls.netchan.outgoing_sequence );
COM_Munge( buf.pData + key + 1, Q_min( size, 255 ), cls.netchan.outgoing_sequence );
}
else
{
size = MSG_GetRealBytesWritten( &buf ) - key - 1;
buf.pData[key] = CRC32_BlockSequence( buf.pData + key + 1, size, cls.netchan.outgoing_sequence );
}
// message we are constructing.
i = cls.netchan.outgoing_sequence & CL_UPDATE_MASK;
// determine if we need to ask for a new set of delta's.
if( cl.validsequence && (cls.state == ca_active) && !( cls.demorecording && cls.demowaiting ))
if( cl.validsequence && ( !cls.demorecording || !cls.demowaiting ))
{
cl.delta_sequence = cl.validsequence;
MSG_BeginClientCmd( &buf, clc_delta );
MSG_WriteByte( &buf, cl.validsequence & 0xFF );
}
else
{
// request delta compression of entities
cl.delta_sequence = -1;
MSG_WriteByte( &buf, cl.validsequence & 0xff );
}
else cl.delta_sequence = -1;
if( MSG_CheckOverflow( &buf ))
Host_Error( "%s: overflowed command buffer (%i bytes)\n", __func__, MAX_CMD_BUFFER );
// remember outgoing command that we are sending
// command finished, remember last sent sequence id
cls.lastoutgoingcommand = cls.netchan.outgoing_sequence;
pcmd->sendsize = MSG_GetNumBytesWritten( &buf );
// update size counter for netgraph
cl.commands[cls.netchan.outgoing_sequence & CL_UPDATE_MASK].sendsize = MSG_GetNumBytesWritten( &buf );
cl.commands[cls.netchan.outgoing_sequence & CL_UPDATE_MASK].heldback = false;
// send voice data to the server
CL_AddVoiceToDatagram();
// composite the rest of the datagram..
// now add unreliable, if there is enough space
if( MSG_GetNumBitsWritten( &cls.datagram ) <= MSG_GetNumBitsLeft( &buf ))
MSG_WriteBits( &buf, MSG_GetData( &cls.datagram ), MSG_GetNumBitsWritten( &cls.datagram ));
MSG_Clear( &cls.datagram );
// deliver the message (or update reliable)
Netchan_TransmitBits( &cls.netchan, MSG_GetNumBitsWritten( &buf ), MSG_GetData( &buf ));
}
else
{
// mark command as held back so we'll send it next time
cl.commands[cls.netchan.outgoing_sequence & CL_UPDATE_MASK].heldback = true;
// increment sequence number so we can detect that we've held back packets.
cls.netchan.outgoing_sequence++;
}
if( cls.demorecording && numbackup > 0 )
{
// Back up one because we've incremented outgoing_sequence each frame by 1 unit
cmdnumber = ( cls.netchan.outgoing_sequence - 1 ) & CL_UPDATE_MASK;
CL_WriteDemoUserCmd( cmdnumber );
}
// update download/upload slider.
Netchan_UpdateProgress( &cls.netchan );
}
@ -1294,9 +1227,9 @@ static void CL_CheckForResend( void )
return;
if( cl_resend.value < CL_MIN_RESEND_TIME )
Cvar_SetValue( "cl_resend", CL_MIN_RESEND_TIME );
Cvar_DirectSetValue( &cl_resend, CL_MIN_RESEND_TIME );
else if( cl_resend.value > CL_MAX_RESEND_TIME )
Cvar_SetValue( "cl_resend", CL_MAX_RESEND_TIME );
Cvar_DirectSetValue( &cl_resend, CL_MAX_RESEND_TIME );
bandwidthTest = cls.legacymode == PROTO_CURRENT && cl_test_bandwidth.value && cls.connect_retry <= CL_TEST_RETRIES;
resendTime = bandwidthTest ? 1.0f : cl_resend.value;
@ -1334,7 +1267,7 @@ static void CL_CheckForResend( void )
Con_Printf( "Bandwidth test failed, fallback to default connecting method\n" );
Con_Printf( "Connecting to %s... (retry #%i)\n", cls.servername, cls.connect_retry + 1 );
CL_SendGetChallenge( adr );
Cvar_SetValue( "cl_dlmax", FRAGMENT_MIN_SIZE );
Cvar_DirectSetValue( &cl_dlmax, FRAGMENT_MIN_SIZE );
cls.connect_time = host.realtime;
cls.connect_retry++;
return;
@ -2391,7 +2324,7 @@ static void CL_HandleTestPacket( netadr_t from, sizebuf_t *msg )
}
}
static void CL_ClientConnect( const char *c, netadr_t from, sizebuf_t *msg )
static void CL_ClientConnect( connprotocol_t proto, const char *c, netadr_t from )
{
if( !CL_IsFromConnectingServer( from ))
return;
@ -2402,7 +2335,18 @@ static void CL_ClientConnect( const char *c, netadr_t from, sizebuf_t *msg )
return;
}
if( cls.legacymode != PROTO_GOLDSRC && !Q_strcmp( c, S2C_GOLDSRC_CONNECTION ))
if( proto == PROTO_GOLDSRC )
{
if( Q_strcmp( c, S2C_GOLDSRC_CONNECTION ))
{
Con_DPrintf( S_ERROR "GoldSrc client connect expected but wasn't received, ignored\n");
return;
}
if( Cmd_Argc() > 4 )
cls.build_num = Q_atoi( Cmd_Argv( 4 ));
}
else if( !Q_strcmp( c, S2C_GOLDSRC_CONNECTION ))
{
Con_DPrintf( S_ERROR "GoldSrc client connect received but wasn't expected, ignored\n");
return;
@ -2576,7 +2520,7 @@ static void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg )
// server connection
if( !Q_strcmp( c, S2C_GOLDSRC_CONNECTION ) || !Q_strcmp( c, S2C_CONNECTION ))
{
CL_ClientConnect( c, from, msg );
CL_ClientConnect( cls.legacymode, c, from );
}
else if( !Q_strcmp( c, A2A_INFO ))
{
@ -3692,9 +3636,10 @@ void CL_Init( void )
COM_GetCommonLibraryPath( LIBRARY_CLIENT, libpath, sizeof( libpath ));
if( !CL_LoadProgs( libpath ) )
Host_Error( "can't initialize %s: %s\n", libpath, COM_GetLibraryError() );
if( !CL_LoadProgs( libpath ))
Host_Error( "can't initialize %s: %s\n", libpath, COM_GetLibraryError( ));
cls.build_num = 0;
cls.initialized = true;
cl.maxclients = 1; // allow to drawing player in menu
cls.olddemonum = -1;

View file

@ -633,6 +633,9 @@ typedef struct
// do we accept utf8 as input
qboolean accept_utf8;
// server's build number (might be zero)
int build_num;
} client_static_t;
#ifdef __cplusplus

View file

@ -167,8 +167,9 @@ GNU General Public License for more details.
#define GAME_TEAMPLAY 4
// Max number of history commands to send ( 2 by default ) in case of dropped packets
#define NUM_BACKUP_COMMAND_BITS 4
#define MAX_BACKUP_COMMANDS (1 << NUM_BACKUP_COMMAND_BITS)
#define NUM_BACKUP_COMMAND_BITS 4
#define MAX_BACKUP_COMMANDS BIT( NUM_BACKUP_COMMAND_BITS )
#define MAX_TOTAL_CMDS 32
#define MAX_RESOURCES (MAX_MODELS+MAX_SOUNDS+MAX_CUSTOM+MAX_EVENTS)
#define MAX_RESOURCE_BITS 13 // 13 bits 8192 resource (4096 models + 2048 sounds + 1024 events + 1024 files)
@ -345,6 +346,7 @@ extern const char *const clc_strings[clc_lastmsg+1];
#define MAX_GOLDSRC_BACKUP_CMDS 8
#define MAX_GOLDSRC_TOTAL_CMDS 16
#define MAX_GOLDSRC_EXTENDED_TOTAL_CMDS 62
#define MAX_GOLDSRC_MODEL_BITS 10
#define MAX_GOLDSRC_RESOURCE_BITS 12
#define MAX_GOLDSRC_ENTITY_BITS 11