/*
cl_main . c - client main loop
Copyright ( C ) 2009 Uncle Mike
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
*/
# include "common.h"
# include "client.h"
# include "net_encode.h"
# include "cl_tent.h"
# include "input.h"
# include "kbutton.h"
# include "vgui_draw.h"
# include "library.h"
# include "vid_common.h"
# include "pm_local.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_NORESPONCE 3
# define CL_TEST_RETRIES 5
CVAR_DEFINE_AUTO ( mp_decals , " 300 " , FCVAR_ARCHIVE , " decals limit in multiplayer " ) ;
CVAR_DEFINE_AUTO ( dev_overview , " 0 " , 0 , " draw level in overview-mode " ) ;
CVAR_DEFINE_AUTO ( cl_resend , " 6.0 " , 0 , " time to resend connect " ) ;
CVAR_DEFINE ( cl_allow_download , " cl_allowdownload " , " 1 " , FCVAR_ARCHIVE , " allow to downloading resources from the server " ) ;
CVAR_DEFINE_AUTO ( cl_allow_upload , " 1 " , FCVAR_ARCHIVE , " allow to uploading resources to the server " ) ;
CVAR_DEFINE_AUTO ( cl_download_ingame , " 1 " , FCVAR_ARCHIVE , " allow to downloading resources while client is active " ) ;
CVAR_DEFINE_AUTO ( cl_logofile , " lambda " , FCVAR_ARCHIVE , " player logo name " ) ;
CVAR_DEFINE_AUTO ( cl_logocolor , " orange " , FCVAR_ARCHIVE , " player logo color " ) ;
CVAR_DEFINE_AUTO ( cl_logoext , " bmp " , FCVAR_ARCHIVE , " temporary cvar to tell engine which logo must be packed " ) ;
CVAR_DEFINE_AUTO ( cl_test_bandwidth , " 1 " , FCVAR_ARCHIVE , " test network bandwith before connection " ) ;
CVAR_DEFINE ( cl_draw_particles , " r_drawparticles " , " 1 " , FCVAR_CHEAT , " render particles " ) ;
CVAR_DEFINE ( cl_draw_tracers , " r_drawtracers " , " 1 " , FCVAR_CHEAT , " render tracers " ) ;
CVAR_DEFINE ( cl_draw_beams , " r_drawbeams " , " 1 " , FCVAR_CHEAT , " render beams " ) ;
static CVAR_DEFINE_AUTO ( rcon_address , " " , FCVAR_PRIVILEGED , " remote control address " ) ;
CVAR_DEFINE_AUTO ( cl_timeout , " 60 " , 0 , " connect timeout (in-seconds) " ) ;
CVAR_DEFINE_AUTO ( cl_nopred , " 0 " , FCVAR_ARCHIVE | FCVAR_USERINFO , " disable client movement prediction " ) ;
CVAR_DEFINE_AUTO ( cl_nodelta , " 0 " , 0 , " disable delta-compression for server messages " ) ;
CVAR_DEFINE ( cl_crosshair , " crosshair " , " 1 " , FCVAR_ARCHIVE , " show weapon chrosshair " ) ;
static CVAR_DEFINE_AUTO ( cl_cmdbackup , " 10 " , FCVAR_ARCHIVE , " how many additional history commands are sent " ) ;
CVAR_DEFINE_AUTO ( cl_showerror , " 0 " , FCVAR_ARCHIVE , " show prediction error " ) ;
CVAR_DEFINE_AUTO ( cl_bmodelinterp , " 1 " , FCVAR_ARCHIVE , " enable bmodel interpolation " ) ;
CVAR_DEFINE_AUTO ( cl_lightstyle_lerping , " 0 " , FCVAR_ARCHIVE , " enables animated light lerping (perfomance option) " ) ;
CVAR_DEFINE_AUTO ( cl_idealpitchscale , " 0.8 " , 0 , " how much to look up/down slopes and stairs when not using freelook " ) ;
CVAR_DEFINE_AUTO ( cl_nosmooth , " 0 " , FCVAR_ARCHIVE , " disable smooth up stair climbing " ) ;
CVAR_DEFINE_AUTO ( cl_smoothtime , " 0.1 " , FCVAR_ARCHIVE , " time to smooth up " ) ;
CVAR_DEFINE_AUTO ( cl_clockreset , " 0.1 " , FCVAR_ARCHIVE , " frametime delta maximum value before reset " ) ;
CVAR_DEFINE_AUTO ( cl_fixtimerate , " 7.5 " , FCVAR_ARCHIVE , " time in msec to client clock adjusting " ) ;
CVAR_DEFINE_AUTO ( hud_fontscale , " 1.0 " , FCVAR_ARCHIVE | FCVAR_LATCH , " scale hud font texture " ) ;
CVAR_DEFINE_AUTO ( hud_fontrender , " 0 " , FCVAR_ARCHIVE , " hud font render mode (0: additive, 1: holes, 2: trans) " ) ;
CVAR_DEFINE_AUTO ( hud_scale , " 0 " , FCVAR_ARCHIVE | FCVAR_LATCH , " scale hud at current resolution " ) ;
CVAR_DEFINE_AUTO ( hud_scale_minimal_width , " 640 " , FCVAR_ARCHIVE | FCVAR_LATCH , " if hud_scale results in a HUD virtual screen smaller than this value, it won't be applied " ) ;
CVAR_DEFINE_AUTO ( cl_solid_players , " 1 " , 0 , " Make all players not solid (can't traceline them) " ) ;
CVAR_DEFINE_AUTO ( cl_updaterate , " 20 " , FCVAR_USERINFO | FCVAR_ARCHIVE , " refresh rate of server messages " ) ;
CVAR_DEFINE_AUTO ( cl_showevents , " 0 " , FCVAR_ARCHIVE , " show events playback " ) ;
CVAR_DEFINE_AUTO ( cl_cmdrate , " 60 " , FCVAR_ARCHIVE , " Max number of command packets sent to server per second " ) ;
CVAR_DEFINE ( cl_interp , " ex_interp " , " 0.1 " , FCVAR_ARCHIVE | FCVAR_FILTERABLE , " Interpolate object positions starting this many seconds in past " ) ;
CVAR_DEFINE_AUTO ( cl_nointerp , " 0 " , FCVAR_CLIENTDLL , " disable interpolation of entities and players " ) ;
static CVAR_DEFINE_AUTO ( cl_dlmax , " 0 " , FCVAR_USERINFO | FCVAR_ARCHIVE , " max allowed outcoming fragment size " ) ;
static CVAR_DEFINE_AUTO ( cl_upmax , " 1200 " , FCVAR_ARCHIVE , " max allowed incoming fragment size " ) ;
CVAR_DEFINE_AUTO ( cl_lw , " 1 " , FCVAR_ARCHIVE | FCVAR_USERINFO , " enable client weapon predicting " ) ;
CVAR_DEFINE_AUTO ( cl_charset , " utf-8 " , FCVAR_ARCHIVE , " 1-byte charset to use (iconv style) " ) ;
CVAR_DEFINE_AUTO ( cl_trace_stufftext , " 0 " , FCVAR_ARCHIVE | FCVAR_CHEAT , " enable stufftext (server-to-client console commands) tracing (good for developers) " ) ;
CVAR_DEFINE_AUTO ( cl_trace_messages , " 0 " , FCVAR_ARCHIVE | FCVAR_CHEAT , " enable message names tracing (good for developers) " ) ;
CVAR_DEFINE_AUTO ( cl_trace_events , " 0 " , FCVAR_ARCHIVE | FCVAR_CHEAT , " enable events tracing (good for developers) " ) ;
static CVAR_DEFINE_AUTO ( cl_nat , " 0 " , 0 , " show servers running under NAT " ) ;
CVAR_DEFINE_AUTO ( hud_utf8 , " 0 " , FCVAR_ARCHIVE , " Use utf-8 encoding for hud text " ) ;
CVAR_DEFINE_AUTO ( ui_renderworld , " 0 " , FCVAR_ARCHIVE , " render world when UI is visible " ) ;
static CVAR_DEFINE_AUTO ( cl_maxframetime , " 0 " , 0 , " set deadline timer for client rendering to catch freezes " ) ;
CVAR_DEFINE_AUTO ( cl_fixmodelinterpolationartifacts , " 1 " , 0 , " try to fix up models interpolation on a moving platforms (monsters on trains for example) " ) ;
//
// userinfo
//
static CVAR_DEFINE_AUTO ( name , " player " , FCVAR_USERINFO | FCVAR_ARCHIVE | FCVAR_PRINTABLEONLY | FCVAR_FILTERABLE , " player name " ) ;
static CVAR_DEFINE_AUTO ( model , " " , FCVAR_USERINFO | FCVAR_ARCHIVE | FCVAR_FILTERABLE , " player model ('player' is a singleplayer model) " ) ;
static CVAR_DEFINE_AUTO ( topcolor , " 0 " , FCVAR_USERINFO | FCVAR_ARCHIVE | FCVAR_FILTERABLE , " player top color " ) ;
static CVAR_DEFINE_AUTO ( bottomcolor , " 0 " , FCVAR_USERINFO | FCVAR_ARCHIVE | FCVAR_FILTERABLE , " player bottom color " ) ;
CVAR_DEFINE_AUTO ( rate , " 3500 " , FCVAR_USERINFO | FCVAR_ARCHIVE | FCVAR_FILTERABLE , " player network rate " ) ;
client_t cl ;
client_static_t cls ;
clgame_static_t clgame ;
static void CL_SendMasterServerScanRequest ( void ) ;
//======================================================================
int GAME_EXPORT CL_Active ( void )
{
return ( cls . state = = ca_active ) ;
}
qboolean CL_Initialized ( void )
{
return cls . initialized ;
}
//======================================================================
qboolean CL_IsInGame ( void )
{
if ( host . type = = HOST_DEDICATED )
return true ; // always active for dedicated servers
if ( cl . background | | CL_GetMaxClients ( ) > 1 )
return true ; // always active for multiplayer or background map
return ( cls . key_dest = = key_game ) ; // active if not menu or console
}
qboolean CL_IsInMenu ( void )
{
return ( cls . key_dest = = key_menu ) ;
}
qboolean CL_IsInConsole ( void )
{
return ( cls . key_dest = = key_console ) ;
}
qboolean CL_IsIntermission ( void )
{
return cl . intermission ;
}
qboolean CL_IsPlaybackDemo ( void )
{
return cls . demoplayback ;
}
qboolean CL_IsRecordDemo ( void )
{
return cls . demorecording ;
}
qboolean CL_IsTimeDemo ( void )
{
return cls . timedemo ;
}
qboolean CL_DisableVisibility ( void )
{
return cls . envshot_disable_vis ;
}
char * CL_Userinfo ( void )
{
return cls . userinfo ;
}
int CL_IsDevOverviewMode ( void )
{
if ( dev_overview . value > 0.0f )
{
if ( host_developer . value | | cls . spectator )
return ( int ) dev_overview . value ;
}
return 0 ;
}
/*
= = = = = = = = = = = = = = =
CL_CheckClientState
finalize connection process and begin new frame
with new cls . state
= = = = = = = = = = = = = = =
*/
static void CL_CheckClientState ( void )
{
// first update is the pre-final signon stage
if ( ( cls . state = = ca_connected | | cls . state = = ca_validate ) & & ( cls . signon = = SIGNONS ) )
{
cls . state = ca_active ;
cls . changelevel = false ; // changelevel is done
cls . changedemo = false ; // changedemo is done
cl . first_frame = true ; // first rendering frame
SCR_MakeLevelShot ( ) ; // make levelshot if needs
Cvar_SetValue ( " scr_loading " , 0.0f ) ; // reset progress bar
Netchan_ReportFlow ( & cls . netchan ) ;
Con_DPrintf ( " client connected at %.2f sec \n " , Sys_DoubleTime ( ) - cls . timestart ) ;
}
}
int CL_GetFragmentSize ( void * unused , fragsize_t mode )
{
if ( mode = = FRAGSIZE_SPLIT )
return 0 ;
if ( mode = = FRAGSIZE_UNRELIABLE )
return NET_MAX_MESSAGE ;
if ( Netchan_IsLocal ( & cls . netchan ) )
return FRAGMENT_LOCAL_SIZE ;
return cl_upmax . value ;
}
/*
= = = = = = = = = = = = = = = = = = = = =
CL_SignonReply
An svc_signonnum has been received , perform a client side setup
= = = = = = = = = = = = = = = = = = = = =
*/
void CL_SignonReply ( void )
{
// g-cont. my favorite message :-)
Con_Reportf ( " CL_SignonReply: %i \n " , cls . signon ) ;
switch ( cls . signon )
{
case 1 :
CL_ServerCommand ( true , " begin " ) ;
if ( host_developer . value > = DEV_EXTENDED )
Mem_PrintStats ( ) ;
break ;
case 2 :
SCR_EndLoadingPlaque ( ) ;
if ( cl . proxy_redirect & & ! cls . spectator )
CL_Disconnect ( ) ;
cl . proxy_redirect = false ;
break ;
}
}
/*
= = = = = = = = = = = = = = =
CL_LerpPoint
Determines the fraction between the last two messages that the objects
should be put at .
= = = = = = = = = = = = = = =
*/
static float CL_LerpPoint ( void )
{
double f = cl_serverframetime ( ) ;
double frac ;
if ( f = = 0.0 | | cls . timedemo )
{
double fgap = cl_clientframetime ( ) ;
cl . time = cl . mtime [ 0 ] ;
// maybe don't need for Xash demos
if ( cls . demoplayback )
cl . oldtime = cl . mtime [ 0 ] - fgap ;
return 1.0f ;
}
if ( cl_interp . value < = 0.001 )
return 1.0f ;
frac = ( cl . time - cl . mtime [ 0 ] ) / cl_interp . value ;
return frac ;
}
/*
= = = = = = = = = = = = = = =
CL_DriftInterpolationAmount
Drift interpolation value ( this is used for server unlag system )
= = = = = = = = = = = = = = =
*/
static int CL_DriftInterpolationAmount ( int goal )
{
float fgoal , maxmove , diff ;
int msec ;
fgoal = ( float ) goal / 1000.0f ;
if ( fgoal ! = cl . local . interp_amount )
{
maxmove = host . frametime * 0.05 ;
diff = fgoal - cl . local . interp_amount ;
diff = bound ( - maxmove , diff , maxmove ) ;
cl . local . interp_amount + = diff ;
}
msec = cl . local . interp_amount * 1000.0f ;
msec = bound ( 0 , msec , 100 ) ;
return msec ;
}
/*
= = = = = = = = = = = = = = =
CL_ComputeClientInterpolationAmount
Validate interpolation cvars , calc interpolation window
= = = = = = = = = = = = = = =
*/
static void CL_ComputeClientInterpolationAmount ( usercmd_t * cmd )
{
const float epsilon = 0.001f ; // to avoid float invalid comparision
float min_interp ;
float max_interp = MAX_EX_INTERP ;
float interpolation_time ;
if ( cl_updaterate . value < MIN_UPDATERATE )
{
Con_Printf ( " cl_updaterate minimum is %f, resetting to default (20) \n " , MIN_UPDATERATE ) ;
Cvar_Reset ( " cl_updaterate " ) ;
}
if ( cl_updaterate . value > MAX_UPDATERATE )
{
Con_Printf ( " cl_updaterate clamped at maximum (%f) \n " , MAX_UPDATERATE ) ;
Cvar_SetValue ( " cl_updaterate " , MAX_UPDATERATE ) ;
}
if ( cls . spectator )
max_interp = 0.2f ;
min_interp = 1.0f / cl_updaterate . value ;
interpolation_time = cl_interp . value * 1000.0 ;
if ( ( cl_interp . value + epsilon ) < min_interp )
{
Con_Printf ( " ex_interp forced up to %.1f msec \n " , min_interp * 1000.f ) ;
Cvar_SetValue ( " ex_interp " , min_interp ) ;
}
else if ( ( cl_interp . value - epsilon ) > max_interp )
{
Con_Printf ( " ex_interp forced down to %.1f msec \n " , max_interp * 1000.f ) ;
Cvar_SetValue ( " ex_interp " , max_interp ) ;
}
interpolation_time = bound ( min_interp , interpolation_time , max_interp ) ;
cmd - > lerp_msec = CL_DriftInterpolationAmount ( interpolation_time * 1000 ) ;
}
/*
= = = = = = = = = = = = = = = = =
CL_ComputePacketLoss
= = = = = = = = = = = = = = = = =
*/
static 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.0 )
lost + + ;
count + + ;
}
if ( count < = 0 ) cls . packet_loss = 0.0f ;
else cls . packet_loss = ( 100.0f * ( float ) lost ) / ( float ) count ;
}
/*
= = = = = = = = = = = = = = = = =
CL_UpdateFrameLerp
= = = = = = = = = = = = = = = = =
*/
void CL_UpdateFrameLerp ( void )
{
if ( cls . state ! = ca_active | | ! cl . validsequence )
return ;
// compute last interpolation amount
cl . lerpFrac = CL_LerpPoint ( ) ;
cl . commands [ ( cls . netchan . outgoing_sequence - 1 ) & CL_UPDATE_MASK ] . frame_lerp = cl . lerpFrac ;
}
static void CL_FindInterpolatedAddAngle ( float t , float * frac , pred_viewangle_t * * prev , pred_viewangle_t * * next )
{
int i , i0 , i1 , imod ;
float at ;
imod = cl . angle_position - 1 ;
i0 = ( imod + 1 ) & ANGLE_MASK ;
i1 = ( imod + 0 ) & ANGLE_MASK ;
if ( cl . predicted_angle [ i0 ] . starttime > = t )
{
for ( i = 0 ; i < ANGLE_BACKUP - 2 ; i + + )
{
at = cl . predicted_angle [ imod & ANGLE_MASK ] . starttime ;
if ( at = = 0.0f ) break ;
if ( at < t )
{
i0 = ( imod + 1 ) & ANGLE_MASK ;
i1 = ( imod + 0 ) & ANGLE_MASK ;
break ;
}
imod - - ;
}
}
* next = & cl . predicted_angle [ i0 ] ;
* prev = & cl . predicted_angle [ i1 ] ;
// avoid division by zero (probably this should never happens)
if ( ( * prev ) - > starttime = = ( * next ) - > starttime )
{
* prev = * next ;
* frac = 0.0f ;
return ;
}
// time spans the two entries
* frac = ( t - ( * prev ) - > starttime ) / ( ( * next ) - > starttime - ( * prev ) - > starttime ) ;
* frac = bound ( 0.0f , * frac , 1.0f ) ;
}
static void CL_ApplyAddAngle ( void )
{
pred_viewangle_t * prev = NULL , * next = NULL ;
float addangletotal = 0.0f ;
float amove , frac = 0.0f ;
CL_FindInterpolatedAddAngle ( cl . time , & frac , & prev , & next ) ;
if ( prev & & next )
addangletotal = prev - > total + frac * ( next - > total - prev - > total ) ;
else addangletotal = cl . prevaddangletotal ;
amove = addangletotal - cl . prevaddangletotal ;
// update input angles
cl . viewangles [ YAW ] + = amove ;
// remember last total
cl . prevaddangletotal = addangletotal ;
}
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
CLIENT MOVEMENT COMMUNICATION
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
/*
= = = = = = = = = = = = = = =
CL_ProcessShowTexturesCmds
navigate around texture atlas
= = = = = = = = = = = = = = =
*/
static qboolean CL_ProcessShowTexturesCmds ( usercmd_t * cmd )
{
static int oldbuttons ;
int changed ;
int pressed , 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 ) )
Cvar_SetValue ( " r_showtextures " , r_showtextures . value + 1 ) ;
if ( released & ( IN_LEFT | IN_MOVELEFT ) )
Cvar_SetValue ( " r_showtextures " , Q_max ( 1 , r_showtextures . value - 1 ) ) ;
oldbuttons = cmd - > buttons ;
return true ;
}
/*
= = = = = = = = = = = = = = =
CL_ProcessOverviewCmds
Transform user movement into overview adjust
= = = = = = = = = = = = = = =
*/
static qboolean CL_ProcessOverviewCmds ( usercmd_t * cmd )
{
ref_overview_t * ov = & clgame . overView ;
int sign = 1 ;
float size = world . size [ ! ov - > rotated ] / world . size [ ov - > rotated ] ;
float step = ( 2.0f / size ) * host . realframetime ;
float step2 = step * 100.0f * ( 2.0f / ov - > flZoom ) ;
if ( ! CL_IsDevOverviewMode ( ) | | r_showtextures . value )
return false ;
if ( ov - > flZoom < 0.0f ) sign = - 1 ;
if ( cmd - > upmove > 0.0f ) ov - > zNear + = step ;
else if ( cmd - > upmove < 0.0f ) ov - > zNear - = step ;
if ( cmd - > buttons & IN_JUMP ) ov - > zFar + = step ;
else if ( cmd - > buttons & IN_DUCK ) ov - > zFar - = step ;
if ( cmd - > buttons & IN_FORWARD ) ov - > origin [ ov - > rotated ] - = sign * step2 ;
else if ( cmd - > buttons & IN_BACK ) ov - > origin [ ov - > rotated ] + = sign * step2 ;
if ( ov - > rotated )
{
if ( cmd - > buttons & ( IN_RIGHT | IN_MOVERIGHT ) )
ov - > origin [ 0 ] - = sign * step2 ;
else if ( cmd - > buttons & ( IN_LEFT | IN_MOVELEFT ) )
ov - > origin [ 0 ] + = sign * step2 ;
}
else
{
if ( cmd - > buttons & ( IN_RIGHT | IN_MOVERIGHT ) )
ov - > origin [ 1 ] + = sign * step2 ;
else if ( cmd - > buttons & ( IN_LEFT | IN_MOVELEFT ) )
ov - > origin [ 1 ] - = sign * step2 ;
}
if ( cmd - > buttons & IN_ATTACK ) ov - > flZoom + = step ;
else if ( cmd - > buttons & IN_ATTACK2 ) ov - > flZoom - = step ;
if ( ov - > flZoom = = 0.0f ) ov - > flZoom = 0.0001f ; // to prevent disivion by zero
return true ;
}
/*
= = = = = = = = = = = = = = = = =
CL_UpdateClientData
tell the client . dll about player origin , angles , fov , etc
= = = = = = = = = = = = = = = = =
*/
static void CL_UpdateClientData ( void )
{
client_data_t cdat ;
if ( cls . state ! = ca_active )
return ;
memset ( & cdat , 0 , sizeof ( cdat ) ) ;
VectorCopy ( cl . viewangles , cdat . viewangles ) ;
VectorCopy ( clgame . entities [ cl . viewentity ] . origin , cdat . origin ) ;
cdat . iWeaponBits = cl . local . weapons ;
cdat . fov = cl . local . scr_fov ;
if ( clgame . dllFuncs . pfnUpdateClientData ( & cdat , cl . time ) )
{
// grab changes if successful
VectorCopy ( cdat . viewangles , cl . viewangles ) ;
cl . local . scr_fov = cdat . fov ;
}
}
/*
= = = = = = = = = = = = = = = = =
CL_CreateCmd
= = = = = = = = = = = = = = = = =
*/
static void CL_CreateCmd ( void )
{
usercmd_t nullcmd , * cmd ;
runcmd_t * pcmd ;
vec3_t angles ;
qboolean active ;
int input_override ;
int i , ms ;
if ( cls . state < ca_connected | | cls . state = = ca_cinematic )
return ;
// store viewangles in case it's will be freeze
VectorCopy ( cl . viewangles , angles ) ;
ms = bound ( 1 , host . frametime * 1000 , 255 ) ;
input_override = 0 ;
CL_SetSolidEntities ( ) ;
CL_PushPMStates ( ) ;
CL_SetSolidPlayers ( cl . playernum ) ;
// message we are constructing.
i = cls . netchan . outgoing_sequence & CL_UPDATE_MASK ;
pcmd = & cl . commands [ i ] ;
if ( ! cls . demoplayback )
{
pcmd - > processedfuncs = false ;
pcmd - > senttime = host . realtime ;
memset ( & pcmd - > cmd , 0 , sizeof ( pcmd - > cmd ) ) ;
pcmd - > receivedtime = - 1.0 ;
pcmd - > heldback = false ;
pcmd - > sendsize = 0 ;
cmd = & pcmd - > cmd ;
}
else
{
memset ( & nullcmd , 0 , sizeof ( nullcmd ) ) ;
cmd = & nullcmd ;
}
active = ( ( cls . signon = = SIGNONS ) & & ! cl . paused & & ! cls . demoplayback ) ;
Platform_PreCreateMove ( ) ;
clgame . dllFuncs . CL_CreateMove ( host . frametime , cmd , active ) ;
IN_EngineAppendMove ( host . frametime , cmd , active ) ;
CL_PopPMStates ( ) ;
if ( ! cls . demoplayback )
{
CL_ComputeClientInterpolationAmount ( & pcmd - > cmd ) ;
pcmd - > cmd . lightlevel = cl . local . light_level ;
pcmd - > cmd . msec = ms ;
}
input_override | = CL_ProcessOverviewCmds ( & pcmd - > cmd ) ;
input_override | = CL_ProcessShowTexturesCmds ( & pcmd - > cmd ) ;
if ( ( cl . background & & ! cls . demoplayback ) | | input_override | | cls . changelevel )
{
VectorCopy ( angles , pcmd - > cmd . viewangles ) ;
VectorCopy ( angles , cl . viewangles ) ;
if ( ! cl . background ) pcmd - > cmd . msec = 0 ;
}
// demo always have commands so don't overwrite them
if ( ! cls . demoplayback ) cl . cmd = & pcmd - > cmd ;
// predict all unacknowledged movements
CL_PredictMovement ( false ) ;
}
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 )
{
memset ( & nullcmd , 0 , sizeof ( nullcmd ) ) ;
f = & nullcmd ;
}
else
{
f = & cl . commands [ from ] . cmd ;
}
t = & cl . commands [ to ] . cmd ;
// write it into the buffer
MSG_WriteDeltaUsercmd ( msg , f , t ) ;
}
/*
= = = = = = = = = = = = = = = = = = =
CL_WritePacket
Create and send the command packet to the server
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 ;
int numcmds ;
int newcmds ;
int cmdnumber ;
// don't send anything if playing back a demo
if ( cls . demoplayback | | cls . state < ca_connected | | cls . state = = ca_cinematic )
return ;
CL_ComputePacketLoss ( ) ;
memset ( data , 0 , sizeof ( data ) ) ;
MSG_Init ( & buf , " ClientData " , data , sizeof ( data ) ) ;
// Determine number of backup commands to send along
numbackup = bound ( 0 , cl_cmdbackup . value , cls . legacymode ? MAX_LEGACY_BACKUP_CMDS : MAX_BACKUP_COMMANDS ) ;
if ( cls . state = = ca_connected ) numbackup = 0 ;
// 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
// 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 ;
if ( ( host . realtime > = cls . nextcmdtime ) & & Netchan_CanPacket ( & cls . netchan , true ) )
send_command = true ;
if ( cl . send_reply )
{
cl . send_reply = false ;
send_command = true ;
}
// spectator is not sending cmds to server
if ( cls . spectator & & cls . state = = ca_active & & cl . delta_sequence = = cl . validsequence )
{
if ( ! ( cls . demorecording & & cls . demowaiting ) & & cls . nextcmdtime + 1.0f > host . realtime )
return ;
}
if ( ( cls . netchan . outgoing_sequence - cls . netchan . incoming_acknowledged ) > = CL_UPDATE_MASK )
{
if ( ( host . realtime - cls . netchan . last_received ) > CONNECTION_PROBLEM_TIME )
{
Con_NPrintf ( 1 , " ^3Warning:^1 Connection Problem^7 \n " ) ;
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 ( send_command )
{
int outgoing_sequence ;
if ( cl_cmdrate . value > 0 ) // clamped between 10 and 100 fps
cls . nextcmdtime = host . realtime + bound ( 0.1f , ( 1.0f / cl_cmdrate . value ) , 0.01f ) ;
else cls . nextcmdtime = host . realtime ; // always able to send right away
if ( cls . lastoutgoingcommand = = - 1 )
{
outgoing_sequence = cls . netchan . outgoing_sequence ;
cls . lastoutgoingcommand = cls . netchan . outgoing_sequence ;
}
else outgoing_sequence = cls . lastoutgoingcommand + 1 ;
// begin a client move command
MSG_BeginClientCmd ( & buf , clc_move ) ;
// save the position for a checksum byte
key = MSG_GetRealBytesWritten ( & buf ) ;
MSG_WriteByte ( & buf , 0 ) ;
// write packet lossage percentation
MSG_WriteByte ( & buf , cls . packet_loss ) ;
// 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 , cls . legacymode ? MAX_LEGACY_TOTAL_CMDS : MAX_TOTAL_CMDS ) ;
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 ( & buf , from , to ) ;
from = to ;
if ( MSG_CheckOverflow ( & buf ) )
Host_Error ( " CL_WritePacket: overflowed command buffer (%i bytes) \n " , MAX_CMD_BUFFER ) ;
}
// calculate a checksum over the move commands
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 ) )
{
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 ;
}
if ( MSG_CheckOverflow ( & buf ) )
Host_Error ( " CL_WritePacket: overflowed command buffer (%i bytes) \n " , MAX_CMD_BUFFER ) ;
// remember outgoing command that we are sending
cls . lastoutgoingcommand = cls . netchan . outgoing_sequence ;
// 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..
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 ) ;
}
/*
= = = = = = = = = = = = = = = = =
CL_SendCommand
Called every frame to builds and sends a command packet to the server .
= = = = = = = = = = = = = = = = =
*/
static void CL_SendCommand ( void )
{
// we create commands even if a demo is playing,
CL_CreateCmd ( ) ;
// clc_move, userinfo etc
CL_WritePacket ( ) ;
}
/*
= = = = = = = = = = = = = = = = = =
CL_BeginUpload_f
= = = = = = = = = = = = = = = = = =
*/
static void CL_BeginUpload_f ( void )
{
const char * name ;
resource_t custResource ;
byte * buf = NULL ;
int size = 0 ;
byte md5 [ 16 ] ;
name = Cmd_Argv ( 1 ) ;
if ( ! COM_CheckString ( name ) )
return ;
if ( ! cl_allow_upload . value )
return ;
if ( Q_strlen ( name ) ! = 36 | | Q_strnicmp ( name , " !MD5 " , 4 ) )
{
Con_Printf ( " Ingoring upload of non-customization \n " ) ;
return ;
}
memset ( & custResource , 0 , sizeof ( custResource ) ) ;
COM_HexConvert ( name + 4 , 32 , md5 ) ;
if ( HPAK_ResourceForHash ( CUSTOM_RES_PATH , md5 , & custResource ) )
{
if ( memcmp ( md5 , custResource . rgucMD5_hash , 16 ) )
{
Con_Reportf ( " Bogus data retrieved from %s, attempting to delete entry \n " , CUSTOM_RES_PATH ) ;
HPAK_RemoveLump ( CUSTOM_RES_PATH , & custResource ) ;
return ;
}
if ( HPAK_GetDataPointer ( CUSTOM_RES_PATH , & custResource , & buf , & size ) )
{
byte md5 [ 16 ] ;
MD5Context_t ctx ;
memset ( & ctx , 0 , sizeof ( ctx ) ) ;
MD5Init ( & ctx ) ;
MD5Update ( & ctx , buf , size ) ;
MD5Final ( md5 , & ctx ) ;
if ( memcmp ( custResource . rgucMD5_hash , md5 , 16 ) )
{
Con_Reportf ( " HPAK_AddLump called with bogus lump, md5 mismatch \n " ) ;
Con_Reportf ( " Purported: %s \n " , MD5_Print ( custResource . rgucMD5_hash ) ) ;
Con_Reportf ( " Actual : %s \n " , MD5_Print ( md5 ) ) ;
Con_Reportf ( " Removing conflicting lump \n " ) ;
HPAK_RemoveLump ( CUSTOM_RES_PATH , & custResource ) ;
return ;
}
}
}
if ( buf & & size > 0 )
{
Netchan_CreateFileFragmentsFromBuffer ( & cls . netchan , name , buf , size ) ;
Netchan_FragSend ( & cls . netchan ) ;
Mem_Free ( buf ) ;
}
}
/*
= = = = = = = = = = = = = = = = = =
CL_Quit_f
= = = = = = = = = = = = = = = = = =
*/
void CL_Quit_f ( void )
{
CL_Disconnect ( ) ;
Sys_Quit ( ) ;
}
/*
= = = = = = = = = = = = = = = =
CL_Drop
Called after an Host_Error was thrown
= = = = = = = = = = = = = = = =
*/
void CL_Drop ( void )
{
if ( ! cls . initialized )
return ;
CL_Disconnect ( ) ;
}
/*
= = = = = = = = = = = = = = = = = = = = = = =
CL_SendConnectPacket
We have gotten a challenge from the server , so try and
connect .
= = = = = = = = = = = = = = = = = = = = = =
*/
static void CL_SendConnectPacket ( void )
{
char protinfo [ MAX_INFO_STRING ] ;
const char * qport ;
const char * key ;
netadr_t adr ;
if ( ! NET_StringToAdr ( cls . servername , & adr ) )
{
Con_Printf ( " CL_SendConnectPacket: bad server address \n " ) ;
cls . connect_time = 0 ;
return ;
}
if ( adr . port = = 0 ) adr . port = MSG_BigShort ( PORT_SERVER ) ;
qport = Cvar_VariableString ( " net_qport " ) ;
key = ID_GetMD5 ( ) ;
memset ( protinfo , 0 , sizeof ( protinfo ) ) ;
if ( adr . type = = NA_LOOPBACK )
{
IN_LockInputDevices ( false ) ;
}
else
{
int input_devices ;
input_devices = IN_CollectInputDevices ( ) ;
IN_LockInputDevices ( true ) ;
Cvar_SetCheatState ( ) ;
Cvar_FullSet ( " sv_cheats " , " 0 " , FCVAR_READ_ONLY | FCVAR_SERVER ) ;
Info_SetValueForKeyf ( protinfo , " d " , sizeof ( protinfo ) , " %d " , input_devices ) ;
Info_SetValueForKey ( protinfo , " v " , XASH_VERSION , sizeof ( protinfo ) ) ;
Info_SetValueForKeyf ( protinfo , " b " , sizeof ( protinfo ) , " %d " , Q_buildnum ( ) ) ;
Info_SetValueForKey ( protinfo , " o " , Q_buildos ( ) , sizeof ( protinfo ) ) ;
Info_SetValueForKey ( protinfo , " a " , Q_buildarch ( ) , sizeof ( protinfo ) ) ;
}
if ( cls . legacymode )
{
// set related userinfo keys
if ( cl_dlmax . value > = 40000 | | cl_dlmax . value < 100 )
Info_SetValueForKey ( cls . userinfo , " cl_maxpacket " , " 1400 " , sizeof ( cls . userinfo ) ) ;
else
Info_SetValueForKey ( cls . userinfo , " cl_maxpacket " , cl_dlmax . string , sizeof ( cls . userinfo ) ) ;
if ( ! * Info_ValueForKey ( cls . userinfo , " cl_maxpayload " ) )
Info_SetValueForKey ( cls . userinfo , " cl_maxpayload " , " 1000 " , sizeof ( cls . userinfo ) ) ;
Info_SetValueForKey ( protinfo , " i " , key , sizeof ( protinfo ) ) ;
Netchan_OutOfBandPrint ( NS_CLIENT , adr , " connect %i %i %i \" %s \" %d \" %s \" \n " ,
PROTOCOL_LEGACY_VERSION , Q_atoi ( qport ) , cls . challenge , cls . userinfo , NET_LEGACY_EXT_SPLIT , protinfo ) ;
Con_Printf ( " Trying to connect by legacy protocol \n " ) ;
}
else
{
int extensions = NET_EXT_SPLITSIZE ;
if ( cl_dlmax . value > FRAGMENT_MAX_SIZE | | cl_dlmax . value < FRAGMENT_MIN_SIZE )
Cvar_SetValue ( " cl_dlmax " , FRAGMENT_DEFAULT_SIZE ) ;
Info_RemoveKey ( cls . userinfo , " cl_maxpacket " ) ;
Info_RemoveKey ( cls . userinfo , " cl_maxpayload " ) ;
Info_SetValueForKey ( protinfo , " uuid " , key , sizeof ( protinfo ) ) ;
Info_SetValueForKey ( protinfo , " qport " , qport , sizeof ( protinfo ) ) ;
Info_SetValueForKeyf ( protinfo , " ext " , sizeof ( protinfo ) , " %d " , extensions ) ;
Netchan_OutOfBandPrint ( NS_CLIENT , adr , " connect %i %i \" %s \" \" %s \" \n " , PROTOCOL_VERSION , cls . challenge , protinfo , cls . userinfo ) ;
Con_Printf ( " Trying to connect by modern protocol \n " ) ;
}
cls . timestart = Sys_DoubleTime ( ) ;
}
/*
= = = = = = = = = = = = = = = = =
CL_CheckForResend
Resend a connect message if the last one has timed out
= = = = = = = = = = = = = = = = =
*/
static void CL_CheckForResend ( void )
{
netadr_t adr ;
net_gai_state_t res ;
qboolean bandwidthTest ;
if ( cls . internetservers_wait )
CL_SendMasterServerScanRequest ( ) ;
// if the local server is running and we aren't then connect
if ( cls . state = = ca_disconnected & & SV_Active ( ) )
{
cls . signon = 0 ;
cls . state = ca_connecting ;
Q_strncpy ( cls . servername , " localhost " , sizeof ( cls . servername ) ) ;
cls . serveradr . type = NA_LOOPBACK ;
// we don't need a challenge on the localhost
CL_SendConnectPacket ( ) ;
return ;
}
// resend if we haven't gotten a reply yet
if ( cls . demoplayback | | cls . state ! = ca_connecting )
return ;
if ( cl_resend . value < CL_MIN_RESEND_TIME )
Cvar_SetValue ( " cl_resend " , CL_MIN_RESEND_TIME ) ;
else if ( cl_resend . value > CL_MAX_RESEND_TIME )
Cvar_SetValue ( " cl_resend " , CL_MAX_RESEND_TIME ) ;
if ( ( host . realtime - cls . connect_time ) < cl_resend . value )
return ;
res = NET_StringToAdrNB ( cls . servername , & adr ) ;
if ( res = = NET_EAI_NONAME )
{
CL_Disconnect ( ) ;
return ;
}
if ( res = = NET_EAI_AGAIN )
{
cls . connect_time = MAX_HEARTBEAT ;
return ;
}
// only retry so many times before failure.
if ( cls . connect_retry > = CL_CONNECTION_RETRIES )
{
Con_DPrintf ( S_ERROR " CL_CheckForResend: couldn't connected \n " ) ;
CL_Disconnect ( ) ;
return ;
}
if ( adr . port = = 0 ) adr . port = MSG_BigShort ( PORT_SERVER ) ;
if ( cls . connect_retry = = CL_TEST_RETRIES_NORESPONCE )
{
// too many fails use default connection method
Con_Printf ( " hi-speed connection is failed, use default method \n " ) ;
Netchan_OutOfBandPrint ( NS_CLIENT , adr , " getchallenge \n " ) ;
Cvar_SetValue ( " cl_dlmax " , FRAGMENT_MIN_SIZE ) ;
cls . connect_time = host . realtime ;
cls . connect_retry + + ;
return ;
}
bandwidthTest = ! cls . legacymode & & cl_test_bandwidth . value ;
cls . serveradr = adr ;
cls . max_fragment_size = Q_min ( FRAGMENT_MAX_SIZE , cls . max_fragment_size / ( cls . connect_retry + 1 ) ) ;
cls . connect_time = host . realtime ; // for retransmit requests
cls . connect_retry + + ;
if ( bandwidthTest )
Con_Printf ( " Connecting to %s... [retry #%i, max fragment size %i] \n " , cls . servername , cls . connect_retry , cls . max_fragment_size ) ;
else
Con_Printf ( " Connecting to %s... [retry #%i] \n " , cls . servername , cls . connect_retry ) ;
if ( bandwidthTest )
Netchan_OutOfBandPrint ( NS_CLIENT , adr , " bandwidth %i %i \n " , PROTOCOL_VERSION , cls . max_fragment_size ) ;
else
Netchan_OutOfBandPrint ( NS_CLIENT , adr , " getchallenge \n " ) ;
}
static resource_t * CL_AddResource ( resourcetype_t type , const char * name , int size , qboolean bFatalIfMissing , int index )
{
resource_t * r = & cl . resourcelist [ cl . num_resources ] ;
if ( cl . num_resources > = MAX_RESOURCES )
Host_Error ( " Too many resources on client \n " ) ;
cl . num_resources + + ;
Q_strncpy ( r - > szFileName , name , sizeof ( r - > szFileName ) ) ;
r - > ucFlags | = bFatalIfMissing ? RES_FATALIFMISSING : 0 ;
r - > nDownloadSize = size ;
r - > nIndex = index ;
r - > type = type ;
return r ;
}
static void CL_CreateResourceList ( void )
{
char szFileName [ MAX_OSPATH ] ;
byte rgucMD5_hash [ 16 ] ;
resource_t * pNewResource ;
int nSize ;
file_t * fp ;
HPAK_FlushHostQueue ( ) ;
cl . num_resources = 0 ;
memset ( rgucMD5_hash , 0 , sizeof ( rgucMD5_hash ) ) ;
// sanitize cvar value
if ( Q_strcmp ( cl_logoext . string , " bmp " ) & &
Q_strcmp ( cl_logoext . string , " png " ) )
Cvar_DirectSet ( & cl_logoext , " bmp " ) ;
Q_snprintf ( szFileName , sizeof ( szFileName ) ,
" logos/remapped.%s " , cl_logoext . string ) ;
fp = FS_Open ( szFileName , " rb " , true ) ;
if ( ! fp )
return ;
MD5_HashFile ( rgucMD5_hash , szFileName , NULL ) ;
nSize = FS_FileLength ( fp ) ;
if ( nSize ! = 0 )
{
pNewResource = CL_AddResource ( t_decal , szFileName , nSize , false , 0 ) ;
if ( pNewResource )
{
SetBits ( pNewResource - > ucFlags , RES_CUSTOM ) ;
memcpy ( pNewResource - > rgucMD5_hash , rgucMD5_hash , 16 ) ;
HPAK_AddLump ( false , CUSTOM_RES_PATH , pNewResource , NULL , fp ) ;
}
}
FS_Close ( fp ) ;
}
/*
= = = = = = = = = = = = = = = =
CL_Connect_f
= = = = = = = = = = = = = = = =
*/
static void CL_Connect_f ( void )
{
string server ;
qboolean legacyconnect = false ;
// hidden hint to connect by using legacy protocol
if ( Cmd_Argc ( ) = = 3 )
{
legacyconnect = ! Q_strcmp ( Cmd_Argv ( 2 ) , " legacy " ) ;
}
else if ( Cmd_Argc ( ) ! = 2 )
{
Con_Printf ( S_USAGE " connect <server> \n " ) ;
return ;
}
Q_strncpy ( server , Cmd_Argv ( 1 ) , sizeof ( server ) ) ;
// if running a local server, kill it and reissue
if ( SV_Active ( ) ) Host_ShutdownServer ( ) ;
NET_Config ( true , ! cl_nat . value ) ; // allow remote
Con_Printf ( " server %s \n " , server ) ;
CL_Disconnect ( ) ;
// TESTTEST: a see console during connection
UI_SetActiveMenu ( false ) ;
Key_SetKeyDest ( key_console ) ;
cls . state = ca_connecting ;
cls . legacymode = legacyconnect ;
Q_strncpy ( cls . servername , server , sizeof ( cls . servername ) ) ;
cls . connect_time = MAX_HEARTBEAT ; // CL_CheckForResend() will fire immediately
cls . max_fragment_size = FRAGMENT_MAX_SIZE ; // guess a we can establish connection with maximum fragment size
cls . connect_retry = 0 ;
cls . spectator = false ;
cls . signon = 0 ;
}
/*
= = = = = = = = = = = = = = = = = = = = =
CL_Rcon_f
Send the rest of the command line over as
an unconnected command .
= = = = = = = = = = = = = = = = = = = = =
*/
static void CL_Rcon_f ( void )
{
char message [ 1024 ] ;
netadr_t to ;
string command ;
int i ;
if ( ! COM_CheckString ( rcon_password . string ) )
{
Con_Printf ( " You must set 'rcon_password' before issuing an rcon command. \n " ) ;
return ;
}
message [ 0 ] = ( char ) 255 ;
message [ 1 ] = ( char ) 255 ;
message [ 2 ] = ( char ) 255 ;
message [ 3 ] = ( char ) 255 ;
message [ 4 ] = 0 ;
NET_Config ( true , false ) ; // allow remote
Q_strncat ( message , " rcon " , sizeof ( message ) ) ;
Q_strncat ( message , rcon_password . string , sizeof ( message ) ) ;
Q_strncat ( message , " " , sizeof ( message ) ) ;
for ( i = 1 ; i < Cmd_Argc ( ) ; i + + )
{
Cmd_Escape ( command , Cmd_Argv ( i ) , sizeof ( command ) ) ;
Q_strncat ( message , command , sizeof ( message ) ) ;
Q_strncat ( message , " " , sizeof ( message ) ) ;
}
if ( cls . state > = ca_connected )
{
to = cls . netchan . remote_address ;
}
else
{
if ( ! COM_CheckString ( rcon_address . string ) )
{
Con_Printf ( " You must either be connected or set the 'rcon_address' cvar to issue rcon commands \n " ) ;
return ;
}
NET_StringToAdr ( rcon_address . string , & to ) ;
if ( to . port = = 0 ) to . port = MSG_BigShort ( PORT_SERVER ) ;
}
NET_SendPacket ( NS_CLIENT , Q_strlen ( message ) + 1 , message , to ) ;
}
/*
= = = = = = = = = = = = = = = = = = = = =
CL_ClearState
= = = = = = = = = = = = = = = = = = = = =
*/
void CL_ClearState ( void )
{
int i ;
CL_ClearResourceLists ( ) ;
for ( i = 0 ; i < MAX_CLIENTS ; i + + )
COM_ClearCustomizationList ( & cl . players [ i ] . customdata , false ) ;
S_StopAllSounds ( true ) ;
CL_ClearEffects ( ) ;
CL_FreeEdicts ( ) ;
PM_ClearPhysEnts ( clgame . pmove ) ;
NetAPI_CancelAllRequests ( ) ;
// wipe the entire cl structure
memset ( & cl , 0 , sizeof ( cl ) ) ;
MSG_Clear ( & cls . netchan . message ) ;
memset ( & clgame . fade , 0 , sizeof ( clgame . fade ) ) ;
memset ( & clgame . shake , 0 , sizeof ( clgame . shake ) ) ;
clgame . mapname [ 0 ] = ' \0 ' ;
Cvar_FullSet ( " cl_background " , " 0 " , FCVAR_READ_ONLY ) ;
cl . maxclients = 1 ; // allow to drawing player in menu
cl . mtime [ 0 ] = cl . mtime [ 1 ] = 1.0f ; // because level starts from 1.0f second
cls . signon = 0 ;
cl . resourcesneeded . pNext = cl . resourcesneeded . pPrev = & cl . resourcesneeded ;
cl . resourcesonhand . pNext = cl . resourcesonhand . pPrev = & cl . resourcesonhand ;
CL_CreateResourceList ( ) ;
CL_ClearSpriteTextures ( ) ; // now all hud sprites are invalid
cl . local . interp_amount = 0.1f ;
cl . local . scr_fov = 90.0f ;
Cvar_SetValue ( " scr_download " , - 1.0f ) ;
Cvar_SetValue ( " scr_loading " , 0.0f ) ;
host . allow_console = host . allow_console_init ;
HTTP_ClearCustomServers ( ) ;
}
/*
= = = = = = = = = = = = = = = = = = = = =
CL_SendDisconnectMessage
Sends a disconnect message to the server
= = = = = = = = = = = = = = = = = = = = =
*/
static void CL_SendDisconnectMessage ( void )
{
sizebuf_t buf ;
byte data [ 32 ] ;
if ( cls . state = = ca_disconnected ) return ;
MSG_Init ( & buf , " LastMessage " , data , sizeof ( data ) ) ;
MSG_BeginClientCmd ( & buf , clc_stringcmd ) ;
MSG_WriteString ( & buf , " disconnect " ) ;
if ( ! cls . netchan . remote_address . type )
cls . netchan . remote_address . type = NA_LOOPBACK ;
// make sure message will be delivered
Netchan_TransmitBits ( & cls . netchan , MSG_GetNumBitsWritten ( & buf ) , MSG_GetData ( & buf ) ) ;
Netchan_TransmitBits ( & cls . netchan , MSG_GetNumBitsWritten ( & buf ) , MSG_GetData ( & buf ) ) ;
Netchan_TransmitBits ( & cls . netchan , MSG_GetNumBitsWritten ( & buf ) , MSG_GetData ( & buf ) ) ;
}
int CL_GetSplitSize ( void )
{
int splitsize ;
if ( Host_IsDedicated ( ) )
return 0 ;
if ( ! ( cls . extensions & NET_EXT_SPLITSIZE ) )
return 1400 ;
splitsize = cl_dlmax . value ;
if ( splitsize < FRAGMENT_MIN_SIZE | | splitsize > FRAGMENT_MAX_SIZE )
Cvar_SetValue ( " cl_dlmax " , FRAGMENT_DEFAULT_SIZE ) ;
return cl_dlmax . value ;
}
/*
= = = = = = = = = = = = = = = = = = = = =
CL_Reconnect
build a request to reconnect client
= = = = = = = = = = = = = = = = = = = = =
*/
static void CL_Reconnect ( qboolean setup_netchan )
{
if ( setup_netchan )
{
Netchan_Setup ( NS_CLIENT , & cls . netchan , net_from , Cvar_VariableInteger ( " net_qport " ) , NULL , CL_GetFragmentSize ) ;
if ( cls . legacymode )
{
unsigned int extensions = Q_atoi ( Cmd_Argv ( 1 ) ) ;
if ( extensions & NET_LEGACY_EXT_SPLIT )
{
// only enable incoming split for legacy mode
cls . netchan . split = true ;
Con_Reportf ( " ^2NET_EXT_SPLIT enabled^7 (packet sizes is %d/%d) \n " , ( int ) cl_dlmax . value , 65536 ) ;
}
}
else
{
cls . extensions = Q_atoi ( Info_ValueForKey ( Cmd_Argv ( 1 ) , " ext " ) ) ;
if ( cls . extensions & NET_EXT_SPLITSIZE )
{
Con_Reportf ( " ^2NET_EXT_SPLITSIZE enabled^7 (packet size is %d) \n " , ( int ) cl_dlmax . value ) ;
}
}
}
else
{
// clear channel and stuff
Netchan_Clear ( & cls . netchan ) ;
MSG_Clear ( & cls . netchan . message ) ;
}
cls . demonum = cls . movienum = - 1 ; // not in the demo loop now
cls . state = ca_connected ;
cls . signon = 0 ;
CL_ServerCommand ( true , " new " ) ;
cl . validsequence = 0 ; // haven't gotten a valid frame update yet
cl . delta_sequence = - 1 ; // we'll request a full delta from the baseline
cls . lastoutgoingcommand = - 1 ; // we don't have a backed up cmd history yet
cls . nextcmdtime = host . realtime ; // we can send a cmd right away
cl . last_command_ack = - 1 ;
CL_StartupDemoHeader ( ) ;
}
/*
= = = = = = = = = = = = = = = = = = = = =
CL_Disconnect
Goes from a connected state to full screen console state
Sends a disconnect message to the server
This is also called on Host_Error , so it shouldn ' t cause any errors
= = = = = = = = = = = = = = = = = = = = =
*/
void CL_Disconnect ( void )
{
cls . legacymode = false ;
if ( cls . state = = ca_disconnected )
return ;
cls . connect_time = 0 ;
cls . changedemo = false ;
cls . max_fragment_size = FRAGMENT_MAX_SIZE ; // reset fragment size
Voice_Disconnect ( ) ;
CL_Stop_f ( ) ;
// send a disconnect message to the server
CL_SendDisconnectMessage ( ) ;
CL_ClearState ( ) ;
S_StopBackgroundTrack ( ) ;
SCR_EndLoadingPlaque ( ) ; // get rid of loading plaque
// clear the network channel, too.
Netchan_Clear ( & cls . netchan ) ;
IN_LockInputDevices ( false ) ; // unlock input devices
cls . state = ca_disconnected ;
memset ( & cls . serveradr , 0 , sizeof ( cls . serveradr ) ) ;
cls . set_lastdemo = false ;
cls . connect_retry = 0 ;
cls . signon = 0 ;
// back to menu in non-developer mode
if ( host_developer . value | | CL_IsInMenu ( ) )
return ;
UI_SetActiveMenu ( true ) ;
}
void CL_Disconnect_f ( void )
{
if ( Host_IsLocalClient ( ) )
Host_EndGame ( true , " disconnected from server \n " ) ;
else CL_Disconnect ( ) ;
}
void CL_Crashed ( void )
{
// already freed
if ( host . status = = HOST_CRASHED ) return ;
if ( host . type ! = HOST_NORMAL ) return ;
if ( ! cls . initialized ) return ;
host . status = HOST_CRASHED ;
CL_Stop_f ( ) ; // stop any demos
// send a disconnect message to the server
CL_SendDisconnectMessage ( ) ;
Host_WriteOpenGLConfig ( ) ;
Host_WriteConfig ( ) ; // write config
}
/*
= = = = = = = = = = = = = = = = =
CL_LocalServers_f
= = = = = = = = = = = = = = = = =
*/
static void CL_LocalServers_f ( void )
{
netadr_t adr ;
memset ( & adr , 0 , sizeof ( adr ) ) ;
Con_Printf ( " Scanning for servers on the local network area... \n " ) ;
NET_Config ( true , true ) ; // allow remote
// send a broadcast packet
adr . type = NA_BROADCAST ;
adr . port = MSG_BigShort ( PORT_SERVER ) ;
Netchan_OutOfBandPrint ( NS_CLIENT , adr , " info %i " , PROTOCOL_VERSION ) ;
adr . type = NA_MULTICAST_IP6 ;
Netchan_OutOfBandPrint ( NS_CLIENT , adr , " info %i " , PROTOCOL_VERSION ) ;
}
/*
= = = = = = = = = = = = = = = = =
CL_BuildMasterServerScanRequest
= = = = = = = = = = = = = = = = =
*/
static size_t NONNULL CL_BuildMasterServerScanRequest ( char * buf , size_t size , uint32_t * key , qboolean nat , const char * filter )
{
size_t remaining ;
char * info , temp [ 32 ] ;
if ( unlikely ( size < sizeof ( MS_SCAN_REQUEST ) ) )
return 0 ;
Q_strncpy ( buf , MS_SCAN_REQUEST , size ) ;
info = buf + sizeof ( MS_SCAN_REQUEST ) - 1 ;
remaining = size - sizeof ( MS_SCAN_REQUEST ) ;
Q_strncpy ( info , filter , remaining ) ;
* key = COM_RandomLong ( 0 , 0x7FFFFFFF ) ;
# ifndef XASH_ALL_SERVERS
Info_SetValueForKey ( info , " gamedir " , GI - > gamefolder , remaining ) ;
# endif
Info_SetValueForKey ( info , " clver " , XASH_VERSION , remaining ) ; // let master know about client version
Info_SetValueForKey ( info , " nat " , nat ? " 1 " : " 0 " , remaining ) ;
Q_snprintf ( temp , sizeof ( temp ) , " %x " , * key ) ;
Info_SetValueForKey ( info , " key " , temp , remaining ) ;
return sizeof ( MS_SCAN_REQUEST ) + Q_strlen ( info ) ;
}
/*
= = = = = = = = = = = = = = = = =
CL_SendMasterServerScanRequest
= = = = = = = = = = = = = = = = =
*/
static void CL_SendMasterServerScanRequest ( void )
{
cls . internetservers_wait = NET_SendToMasters ( NS_CLIENT ,
cls . internetservers_query_len , cls . internetservers_query ) ;
cls . internetservers_pending = true ;
}
/*
= = = = = = = = = = = = = = = = =
CL_InternetServers_f
= = = = = = = = = = = = = = = = =
*/
static void CL_InternetServers_f ( void )
{
qboolean nat = cl_nat . value ! = 0.0f ;
uint32_t key ;
if ( Cmd_Argc ( ) > 2 | | ( Cmd_Argc ( ) = = 2 & & ! Info_IsValid ( Cmd_Argv ( 1 ) ) ) )
{
Con_Printf ( S_USAGE " internetservers [filter] \n " ) ;
return ;
}
cls . internetservers_query_len = CL_BuildMasterServerScanRequest (
cls . internetservers_query , sizeof ( cls . internetservers_query ) ,
& cls . internetservers_key , nat , Cmd_Argv ( 1 ) ) ;
Con_Printf ( " Scanning for servers on the internet area... \n " ) ;
NET_Config ( true , true ) ; // allow remote
CL_SendMasterServerScanRequest ( ) ;
}
/*
= = = = = = = = = = = = = = = = =
CL_Reconnect_f
The server is changing levels
= = = = = = = = = = = = = = = = =
*/
static void CL_Reconnect_f ( void )
{
if ( cls . state = = ca_disconnected )
return ;
S_StopAllSounds ( true ) ;
if ( cls . state = = ca_connected )
{
CL_Reconnect ( false ) ;
return ;
}
if ( COM_CheckString ( cls . servername ) )
{
qboolean legacy = cls . legacymode ;
if ( cls . state > = ca_connected )
CL_Disconnect ( ) ;
cls . connect_time = MAX_HEARTBEAT ; // fire immediately
cls . demonum = cls . movienum = - 1 ; // not in the demo loop now
cls . state = ca_connecting ;
cls . signon = 0 ;
cls . legacymode = legacy ; // don't change protocol
Con_Printf ( " reconnecting... \n " ) ;
}
}
/*
= = = = = = = = = = = = = = = = =
CL_FixupColorStringsForInfoString
all the keys and values must be ends with ^ 7
= = = = = = = = = = = = = = = = =
*/
static void CL_FixupColorStringsForInfoString ( const char * in , char * out )
{
qboolean hasPrefix = false ;
qboolean endOfKeyVal = false ;
int color = 7 ;
int count = 0 ;
if ( * in = = ' \\ ' )
{
* out + + = * in + + ;
count + + ;
}
while ( * in & & count < MAX_INFO_STRING )
{
if ( IsColorString ( in ) )
color = ColorIndex ( * ( in + 1 ) ) ;
// color the not reset while end of key (or value) was found!
if ( * in = = ' \\ ' & & color ! = 7 )
{
if ( IsColorString ( out - 2 ) )
{
* ( out - 1 ) = ' 7 ' ;
}
else
{
* out + + = ' ^ ' ;
* out + + = ' 7 ' ;
count + = 2 ;
}
color = 7 ;
}
* out + + = * in + + ;
count + + ;
}
// check the remaining value
if ( color ! = 7 )
{
// if the ends with another color rewrite it
if ( IsColorString ( out - 2 ) )
{
* ( out - 1 ) = ' 7 ' ;
}
else
{
* out + + = ' ^ ' ;
* out + + = ' 7 ' ;
count + = 2 ;
}
}
* out = ' \0 ' ;
}
/*
= = = = = = = = = = = = = = = = =
CL_ParseStatusMessage
Handle a reply from a info
= = = = = = = = = = = = = = = = =
*/
static void CL_ParseStatusMessage ( netadr_t from , sizebuf_t * msg )
{
static char infostring [ MAX_INFO_STRING + 8 ] ;
char * s = MSG_ReadString ( msg ) ;
int i ;
const char * magic = " : wrong version \n " ;
size_t len = Q_strlen ( s ) , magiclen = Q_strlen ( magic ) ;
if ( len > = magiclen & & ! Q_strcmp ( s + len - magiclen , magic ) )
{
Netchan_OutOfBandPrint ( NS_CLIENT , from , " info %i " , PROTOCOL_LEGACY_VERSION ) ;
return ;
}
if ( ! Info_IsValid ( s ) )
{
Con_Printf ( " ^1Server^7: %s, invalid infostring \n " , NET_AdrToString ( from ) ) ;
return ;
}
CL_FixupColorStringsForInfoString ( s , infostring ) ;
if ( ! COM_CheckString ( Info_ValueForKey ( infostring , " gamedir " ) ) )
{
Con_Printf ( " ^1Server^7: %s, Info: %s \n " , NET_AdrToString ( from ) , infostring ) ;
return ; // unsupported proto
}
if ( ! COM_CheckString ( Info_ValueForKey ( infostring , " p " ) ) )
{
Info_SetValueForKey ( infostring , " legacy " , " 1 " , sizeof ( infostring ) ) ;
Con_Printf ( " ^3Server^7: %s, Game: %s \n " , NET_AdrToString ( from ) , Info_ValueForKey ( infostring , " gamedir " ) ) ;
}
else
{
// more info about servers
Con_Printf ( " ^2Server^7: %s, Game: %s \n " , NET_AdrToString ( from ) , Info_ValueForKey ( infostring , " gamedir " ) ) ;
}
UI_AddServerToList ( from , infostring ) ;
}
/*
= = = = = = = = = = = = = = = = =
CL_ParseNETInfoMessage
Handle a reply from a netinfo
= = = = = = = = = = = = = = = = =
*/
static void CL_ParseNETInfoMessage ( netadr_t from , sizebuf_t * msg , const char * s )
{
net_request_t * nr ;
static char infostring [ MAX_INFO_STRING + 8 ] ;
int i , context , type ;
int errorBits = 0 ;
const char * val ;
context = Q_atoi ( Cmd_Argv ( 1 ) ) ;
type = Q_atoi ( Cmd_Argv ( 2 ) ) ;
while ( * s ! = ' \\ ' ) s + + ; // fetching infostring
// check for errors
val = Info_ValueForKey ( s , " neterror " ) ;
if ( ! Q_stricmp ( val , " protocol " ) )
SetBits ( errorBits , NET_ERROR_PROTO_UNSUPPORTED ) ;
else if ( ! Q_stricmp ( val , " undefined " ) )
SetBits ( errorBits , NET_ERROR_UNDEFINED ) ;
CL_FixupColorStringsForInfoString ( s , infostring ) ;
// find a request with specified context
for ( i = 0 ; i < MAX_REQUESTS ; i + + )
{
nr = & clgame . net_requests [ i ] ;
if ( nr - > resp . context = = context & & nr - > resp . type = = type )
{
// setup the answer
nr - > resp . response = infostring ;
nr - > resp . remote_address = from ;
nr - > resp . error = NET_SUCCESS ;
nr - > resp . ping = host . realtime - nr - > timesend ;
if ( nr - > timeout < = host . realtime )
SetBits ( nr - > resp . error , NET_ERROR_TIMEOUT ) ;
SetBits ( nr - > resp . error , errorBits ) ; // misc error bits
nr - > pfnFunc ( & nr - > resp ) ;
if ( ! FBitSet ( nr - > flags , FNETAPI_MULTIPLE_RESPONSE ) )
memset ( nr , 0 , sizeof ( * nr ) ) ; // done
return ;
}
}
}
/*
= = = = = = = = = = = = = = = = =
CL_ProcessNetRequests
check for timeouts
= = = = = = = = = = = = = = = = =
*/
static void CL_ProcessNetRequests ( void )
{
net_request_t * nr ;
int i ;
// find a request with specified context
for ( i = 0 ; i < MAX_REQUESTS ; i + + )
{
nr = & clgame . net_requests [ i ] ;
if ( ! nr - > pfnFunc ) continue ; // not used
if ( nr - > timeout < = host . realtime )
{
// setup the answer
SetBits ( nr - > resp . error , NET_ERROR_TIMEOUT ) ;
nr - > resp . ping = host . realtime - nr - > timesend ;
nr - > pfnFunc ( & nr - > resp ) ;
memset ( nr , 0 , sizeof ( * nr ) ) ; // done
}
}
}
//===================================================================
/*
= = = = = = = = = = = = = = =
CL_SetupOverviewParams
Get initial overview values
= = = = = = = = = = = = = = =
*/
void CL_SetupOverviewParams ( void )
{
ref_overview_t * ov = & clgame . overView ;
float mapAspect , screenAspect , aspect ;
ov - > rotated = ( world . size [ 1 ] < = world . size [ 0 ] ) ? true : false ;
// calculate nearest aspect
mapAspect = world . size [ ! ov - > rotated ] / world . size [ ov - > rotated ] ;
screenAspect = ( float ) refState . width / ( float ) refState . height ;
aspect = Q_max ( mapAspect , screenAspect ) ;
ov - > zNear = world . maxs [ 2 ] ;
ov - > zFar = world . mins [ 2 ] ;
ov - > flZoom = ( 8192.0f / world . size [ ov - > rotated ] ) / aspect ;
VectorAverage ( world . mins , world . maxs , ov - > origin ) ;
memset ( & cls . spectator_state , 0 , sizeof ( cls . spectator_state ) ) ;
if ( cls . spectator )
{
cls . spectator_state . playerstate . friction = 1 ;
cls . spectator_state . playerstate . gravity = 1 ;
cls . spectator_state . playerstate . number = cl . playernum + 1 ;
cls . spectator_state . playerstate . usehull = 1 ;
cls . spectator_state . playerstate . movetype = MOVETYPE_NOCLIP ;
cls . spectator_state . client . maxspeed = clgame . movevars . spectatormaxspeed ;
}
}
/*
= = = = = = = = = = = = = = = = =
CL_IsFromConnectingServer
Used for connectionless packets , when netchan may not be ready .
= = = = = = = = = = = = = = = = =
*/
static qboolean CL_IsFromConnectingServer ( netadr_t from )
{
return NET_IsLocalAddress ( from ) | |
NET_CompareAdr ( cls . serveradr , from ) ;
}
/*
= = = = = = = = = = = = = = = = =
CL_ConnectionlessPacket
Responses to broadcasts , etc
= = = = = = = = = = = = = = = = =
*/
static void CL_ConnectionlessPacket ( netadr_t from , sizebuf_t * msg )
{
char * args ;
const char * c ;
char buf [ MAX_SYSPATH ] ;
int len = sizeof ( buf ) ;
int dataoffset = 0 ;
netadr_t servadr ;
MSG_Clear ( msg ) ;
MSG_ReadLong ( msg ) ; // skip the -1
args = MSG_ReadStringLine ( msg ) ;
Cmd_TokenizeString ( args ) ;
c = Cmd_Argv ( 0 ) ;
Con_Reportf ( " CL_ConnectionlessPacket: %s : %s \n " , NET_AdrToString ( from ) , c ) ;
// server connection
if ( ! Q_strcmp ( c , " client_connect " ) )
{
if ( ! CL_IsFromConnectingServer ( from ) )
return ;
if ( cls . state = = ca_connected )
{
Con_DPrintf ( S_ERROR " dup connect received. ignored \n " ) ;
return ;
}
CL_Reconnect ( true ) ;
UI_SetActiveMenu ( cl . background ) ;
}
else if ( ! Q_strcmp ( c , " info " ) )
{
// server responding to a status broadcast
CL_ParseStatusMessage ( from , msg ) ;
}
else if ( ! Q_strcmp ( c , " netinfo " ) )
{
// server responding to a status broadcast
CL_ParseNETInfoMessage ( from , msg , args ) ;
}
else if ( ! Q_strcmp ( c , " cmd " ) )
{
// remote command from gui front end
if ( ! NET_IsLocalAddress ( from ) )
{
Con_Printf ( " Command packet from remote host. Ignored. \n " ) ;
return ;
}
# if XASH_SDL == 2
SDL_ShowWindow ( host . hWnd ) ;
# endif
args = MSG_ReadString ( msg ) ;
Cbuf_AddText ( args ) ;
Cbuf_AddText ( " \n " ) ;
}
else if ( ! Q_strcmp ( c , " print " ) )
{
// print command from somewhere
Con_Printf ( " %s " , MSG_ReadString ( msg ) ) ;
}
else if ( ! Q_strcmp ( c , " testpacket " ) )
{
byte recv_buf [ NET_MAX_FRAGMENT ] ;
dword crcValue ;
int realsize ;
dword crcValue2 = 0 ;
// this message only used during connection
// it doesn't make sense after client_connect
if ( cls . state ! = ca_connecting )
return ;
if ( ! CL_IsFromConnectingServer ( from ) )
return ;
crcValue = MSG_ReadLong ( msg ) ;
realsize = MSG_GetMaxBytes ( msg ) - MSG_GetNumBytesRead ( msg ) ;
if ( cls . max_fragment_size ! = MSG_GetMaxBytes ( msg ) )
{
if ( cls . connect_retry > = CL_TEST_RETRIES )
{
// too many fails use default connection method
Con_Printf ( " hi-speed connection is failed, use default method \n " ) ;
Netchan_OutOfBandPrint ( NS_CLIENT , from , " getchallenge \n " ) ;
Cvar_SetValue ( " cl_dlmax " , FRAGMENT_DEFAULT_SIZE ) ;
cls . connect_time = host . realtime ;
return ;
}
// if we waiting more than cl_timeout or packet was trashed
cls . connect_time = MAX_HEARTBEAT ;
return ; // just wait for a next responce
}
// reading test buffer
MSG_ReadBytes ( msg , recv_buf , realsize ) ;
// procssing the CRC
CRC32_ProcessBuffer ( & crcValue2 , recv_buf , realsize ) ;
if ( crcValue = = crcValue2 )
{
// packet was sucessfully delivered, adjust the fragment size and get challenge
Con_DPrintf ( " CRC %x is matched, get challenge, fragment size %d \n " , crcValue , cls . max_fragment_size ) ;
Netchan_OutOfBandPrint ( NS_CLIENT , from , " getchallenge \n " ) ;
Cvar_SetValue ( " cl_dlmax " , cls . max_fragment_size ) ;
cls . connect_time = host . realtime ;
}
else
{
if ( cls . connect_retry > = CL_TEST_RETRIES )
{
// too many fails use default connection method
Con_Printf ( " hi-speed connection is failed, use default method \n " ) ;
Netchan_OutOfBandPrint ( NS_CLIENT , from , " getchallenge \n " ) ;
Cvar_SetValue ( " cl_dlmax " , FRAGMENT_MIN_SIZE ) ;
cls . connect_time = host . realtime ;
return ;
}
Msg ( " got testpacket, CRC mismatched 0x%08x should be 0x%08x, trying next fragment size %d \n " , crcValue2 , crcValue , cls . max_fragment_size > > 1 ) ;
// trying the next size of packet
cls . connect_time = MAX_HEARTBEAT ;
}
}
else if ( ! Q_strcmp ( c , " ping " ) )
{
// ping from somewhere
Netchan_OutOfBandPrint ( NS_CLIENT , from , " ack " ) ;
}
else if ( ! Q_strcmp ( c , " challenge " ) )
{
// this message only used during connection
// it doesn't make sense after client_connect
if ( cls . state ! = ca_connecting )
return ;
if ( ! CL_IsFromConnectingServer ( from ) )
return ;
// challenge from the server we are connecting to
cls . challenge = Q_atoi ( Cmd_Argv ( 1 ) ) ;
CL_SendConnectPacket ( ) ;
return ;
}
else if ( ! Q_strcmp ( c , " echo " ) )
{
if ( ! CL_IsFromConnectingServer ( from ) )
return ;
// echo request from server
Netchan_OutOfBandPrint ( NS_CLIENT , from , " %s " , Cmd_Argv ( 1 ) ) ;
}
else if ( ! Q_strcmp ( c , " disconnect " ) )
{
// this message only used during connection
// it doesn't make sense after client_connect
if ( cls . state ! = ca_connecting )
return ;
if ( ! CL_IsFromConnectingServer ( from ) )
return ;
// a disconnect message from the server, which will happen if the server
// dropped the connection but it is still getting packets from us
CL_Disconnect_f ( ) ;
if ( NET_CompareAdr ( from , cls . legacyserver ) )
{
Cbuf_AddTextf ( " connect %s legacy \n " , NET_AdrToString ( from ) ) ;
memset ( & cls . legacyserver , 0 , sizeof ( cls . legacyserver ) ) ;
}
}
else if ( ! Q_strcmp ( c , " errormsg " ) )
{
if ( ! CL_IsFromConnectingServer ( from ) )
return ;
args = MSG_ReadString ( msg ) ;
if ( ! Q_strcmp ( args , " Server uses protocol version 48. \n " ) )
{
cls . legacyserver = from ;
}
else
{
if ( UI_IsVisible ( ) )
UI_ShowMessageBox ( va ( " ^3Server message^7 \n %s " , args ) ) ;
Msg ( " %s " , args ) ;
}
}
else if ( ! Q_strcmp ( c , " updatemsg " ) )
{
// got an update message from master server
// show update dialog from menu
netadr_t adr ;
qboolean preferStore = true ;
if ( ! Q_strcmp ( Cmd_Argv ( 1 ) , " nostore " ) )
preferStore = false ;
// trust only hardcoded master server
if ( NET_StringToAdr ( MASTERSERVER_ADR , & adr ) )
{
if ( NET_CompareAdr ( from , adr ) )
{
UI_ShowUpdateDialog ( preferStore ) ;
}
}
else
{
// in case we don't have master anymore
UI_ShowUpdateDialog ( preferStore ) ;
}
}
else if ( ! Q_strcmp ( c , " f " ) )
{
if ( ! NET_IsMasterAdr ( from ) )
{
Con_Printf ( S_WARN " unexpected server list packet from %s \n " , NET_AdrToString ( from ) ) ;
return ;
}
// check the extra header
if ( MSG_ReadByte ( msg ) = = 0x7f )
{
uint32_t key = MSG_ReadDword ( msg ) ;
if ( cls . internetservers_key ! = key )
{
Con_Printf ( S_WARN " unexpected server list packet from %s (invalid key) \n " , NET_AdrToString ( from ) ) ;
return ;
}
MSG_ReadByte ( msg ) ; // reserved byte
}
else
{
Con_Printf ( S_WARN " invalid server list packet from %s (missing extra header) \n " , NET_AdrToString ( from ) ) ;
return ;
}
// serverlist got from masterserver
while ( MSG_GetNumBitsLeft ( msg ) > 8 )
{
uint8_t addr [ 16 ] ;
if ( from . type6 = = NA_IP6 ) // IPv6 master server only sends IPv6 addresses
{
MSG_ReadBytes ( msg , addr , sizeof ( addr ) ) ;
NET_IP6BytesToNetadr ( & servadr , addr ) ;
servadr . type6 = NA_IP6 ;
}
else
{
MSG_ReadBytes ( msg , servadr . ip , sizeof ( servadr . ip ) ) ; // 4 bytes for IP
servadr . type = NA_IP ;
}
servadr . port = MSG_ReadShort ( msg ) ; // 2 bytes for Port
// list is ends here
if ( ! servadr . port )
break ;
NET_Config ( true , false ) ; // allow remote
Netchan_OutOfBandPrint ( NS_CLIENT , servadr , " info %i " , PROTOCOL_VERSION ) ;
}
if ( cls . internetservers_pending )
{
UI_ResetPing ( ) ;
cls . internetservers_pending = false ;
}
}
else if ( clgame . dllFuncs . pfnConnectionlessPacket ( & from , args , buf , & len ) )
{
// user out of band message (must be handled in CL_ConnectionlessPacket)
if ( len > 0 ) Netchan_OutOfBand ( NS_SERVER , from , len , ( byte * ) buf ) ;
}
else Con_DPrintf ( S_ERROR " bad connectionless packet from %s: \n %s \n " , NET_AdrToString ( from ) , args ) ;
}
/*
= = = = = = = = = = = = = = = = = = = =
CL_GetMessage
Handles recording and playback of demos , on top of NET_ code
= = = = = = = = = = = = = = = = = = = =
*/
static int CL_GetMessage ( byte * data , size_t * length )
{
if ( cls . demoplayback )
{
if ( CL_DemoReadMessage ( data , length ) )
return true ;
return false ;
}
if ( NET_GetPacket ( NS_CLIENT , & net_from , data , length ) )
return true ;
return false ;
}
/*
= = = = = = = = = = = = = = = = =
CL_ReadNetMessage
= = = = = = = = = = = = = = = = =
*/
static void CL_ReadNetMessage ( void )
{
size_t curSize ;
while ( CL_GetMessage ( net_message_buffer , & curSize ) )
{
const int split_header = LittleLong ( 0xFFFFFFFE ) ;
if ( cls . legacymode & & ! memcmp ( & split_header , net_message_buffer , sizeof ( split_header ) ) )
{
// Will rewrite existing packet by merged
if ( ! NetSplit_GetLong ( & cls . netchan . netsplit , & net_from , net_message_buffer , & curSize ) )
continue ;
}
MSG_Init ( & net_message , " ServerData " , net_message_buffer , curSize ) ;
// check for connectionless packet (0xffffffff) first
if ( MSG_GetMaxBytes ( & net_message ) > = 4 & & * ( int * ) net_message . pData = = - 1 )
{
CL_ConnectionlessPacket ( net_from , & net_message ) ;
continue ;
}
// can't be a valid sequenced packet
if ( cls . state < ca_connected ) continue ;
if ( ! cls . demoplayback & & MSG_GetMaxBytes ( & net_message ) < 8 )
{
Con_Printf ( S_WARN " CL_ReadPackets: %s:runt packet \n " , NET_AdrToString ( net_from ) ) ;
continue ;
}
// packet from server
if ( ! cls . demoplayback & & ! NET_CompareAdr ( net_from , cls . netchan . remote_address ) )
{
Con_DPrintf ( S_ERROR " CL_ReadPackets: %s:sequenced packet without connection \n " , NET_AdrToString ( net_from ) ) ;
continue ;
}
if ( ! cls . demoplayback & & ! Netchan_Process ( & cls . netchan , & net_message ) )
continue ; // wasn't accepted for some reason
// run special handler for quake demos
if ( cls . demoplayback = = DEMO_QUAKE1 )
CL_ParseQuakeMessage ( & net_message , true ) ;
else if ( cls . legacymode ) CL_ParseLegacyServerMessage ( & net_message , true ) ;
else CL_ParseServerMessage ( & net_message , true ) ;
cl . send_reply = true ;
}
// build list of all solid entities per next frame (exclude clients)
CL_SetSolidEntities ( ) ;
// check for fragmentation/reassembly related packets.
if ( cls . state ! = ca_disconnected & & Netchan_IncomingReady ( & cls . netchan ) )
{
// process the incoming buffer(s)
if ( Netchan_CopyNormalFragments ( & cls . netchan , & net_message , & curSize ) )
{
MSG_Init ( & net_message , " ServerData " , net_message_buffer , curSize ) ;
CL_ParseServerMessage ( & net_message , false ) ;
}
if ( Netchan_CopyFileFragments ( & cls . netchan , & net_message ) )
{
// remove from resource request stuff.
CL_ProcessFile ( true , cls . netchan . incomingfilename ) ;
}
}
Netchan_UpdateProgress ( & cls . netchan ) ;
// check requests for time-expire
CL_ProcessNetRequests ( ) ;
}
/*
= = = = = = = = = = = = = = = = =
CL_ReadPackets
Updates the local time and reads / handles messages
on client net connection .
= = = = = = = = = = = = = = = = =
*/
static void CL_ReadPackets ( void )
{
// decide the simulation time
cl . oldtime = cl . time ;
if ( ! cl . paused )
cl . time + = host . frametime ;
// demo time
if ( cls . demorecording & & ! cls . demowaiting )
cls . demotime + = host . frametime ;
CL_ReadNetMessage ( ) ;
CL_ApplyAddAngle ( ) ;
#if 0
// keep cheat cvars are unchanged
if ( cl . maxclients > 1 & & cls . state = = ca_active & & ! host_developer . value )
Cvar_SetCheatState ( ) ;
# endif
// hot precache and downloading resources
if ( cls . signon = = SIGNONS & & cl . lastresourcecheck < host . realtime )
{
double checktime = Host_IsLocalGame ( ) ? 0.1 : 1.0 ;
if ( ! cls . dl . custom & & cl . resourcesneeded . pNext ! = & cl . resourcesneeded )
{
// check resource for downloading and precache
CL_EstimateNeededResources ( ) ;
CL_BatchResourceRequest ( false ) ;
cls . dl . doneregistering = false ;
cls . dl . custom = true ;
}
cl . lastresourcecheck = host . realtime + checktime ;
}
// singleplayer never has connection timeout
if ( NET_IsLocalAddress ( cls . netchan . remote_address ) )
return ;
// if in the debugger last frame, don't timeout
if ( host . frametime > 5.0f ) cls . netchan . last_received = Sys_DoubleTime ( ) ;
// check timeout
if ( cls . state > = ca_connected & & cls . state ! = ca_cinematic & & ! cls . demoplayback )
{
if ( host . realtime - cls . netchan . last_received > cl_timeout . value )
{
Con_Printf ( " \n Server connection timed out. \n " ) ;
CL_Disconnect ( ) ;
return ;
}
}
}
/*
= = = = = = = = = = = = = = = = = = = =
CL_CleanFileName
Replace the displayed name for some resources
= = = = = = = = = = = = = = = = = = = =
*/
static const char * CL_CleanFileName ( const char * filename )
{
if ( COM_CheckString ( filename ) & & filename [ 0 ] = = ' ! ' )
return " customization " ;
return filename ;
}
/*
= = = = = = = = = = = = = = = = = = = =
CL_RegisterCustomization
register custom resource for player
= = = = = = = = = = = = = = = = = = = =
*/
static void CL_RegisterCustomization ( resource_t * resource )
{
qboolean bFound = false ;
customization_t * pList ;
for ( pList = cl . players [ resource - > playernum ] . customdata . pNext ; pList ; pList = pList - > pNext )
{
if ( ! memcmp ( pList - > resource . rgucMD5_hash , resource - > rgucMD5_hash , 16 ) )
{
bFound = true ;
break ;
}
}
if ( ! bFound )
{
player_info_t * player = & cl . players [ resource - > playernum ] ;
if ( ! COM_CreateCustomization ( & player - > customdata , resource , resource - > playernum , FCUST_FROMHPAK , NULL , NULL ) )
Con_Printf ( " Unable to create custom decal for player %i \n " , resource - > playernum ) ;
}
else
{
Con_DPrintf ( " Duplicate resource received and ignored. \n " ) ;
}
}
/*
= = = = = = = = = = = = = = = = = = = =
CL_ProcessFile
A file has been received via the fragmentation / reassembly layer , put it in the right spot and
see if we have finished downloading files .
= = = = = = = = = = = = = = = = = = = =
*/
void CL_ProcessFile ( qboolean successfully_received , const char * filename )
{
int sound_len = sizeof ( DEFAULT_SOUNDPATH ) - 1 ;
byte rgucMD5_hash [ 16 ] ;
const char * pfilename ;
resource_t * p ;
if ( COM_CheckString ( filename ) & & successfully_received )
{
if ( filename [ 0 ] ! = ' ! ' )
Con_Printf ( " processing %s \n " , filename ) ;
if ( ! Q_strnicmp ( filename , " downloaded/ " , 11 ) )
{
// skip "downloaded/" part to avoid mismatch with needed resources list
filename + = 11 ;
}
}
else if ( ! successfully_received )
{
Con_Printf ( S_ERROR " server failed to transmit file '%s' \n " , CL_CleanFileName ( filename ) ) ;
}
if ( cls . legacymode )
{
if ( host . downloadcount > 0 )
host . downloadcount - - ;
if ( ! host . downloadcount )
{
MSG_WriteByte ( & cls . netchan . message , clc_stringcmd ) ;
MSG_WriteString ( & cls . netchan . message , " continueloading " ) ;
}
return ;
}
pfilename = filename ;
if ( ! Q_strnicmp ( filename , DEFAULT_SOUNDPATH , sound_len ) )
pfilename + = sound_len ;
for ( p = cl . resourcesneeded . pNext ; p ! = & cl . resourcesneeded ; p = p - > pNext )
{
if ( ! Q_strnicmp ( filename , " !MD5 " , 4 ) )
{
COM_HexConvert ( filename + 4 , 32 , rgucMD5_hash ) ;
if ( ! memcmp ( p - > rgucMD5_hash , rgucMD5_hash , 16 ) )
break ;
}
else
{
if ( p - > type = = t_generic )
{
if ( ! Q_stricmp ( p - > szFileName , filename ) )
break ;
}
else
{
if ( ! Q_stricmp ( p - > szFileName , pfilename ) )
break ;
}
}
}
if ( p ! = & cl . resourcesneeded )
{
if ( successfully_received )
ClearBits ( p - > ucFlags , RES_WASMISSING ) ;
if ( filename [ 0 ] = = ' ! ' )
{
if ( cls . netchan . tempbuffer )
{
if ( p - > nDownloadSize = = cls . netchan . tempbuffersize )
{
if ( p - > ucFlags & RES_CUSTOM )
{
HPAK_AddLump ( true , CUSTOM_RES_PATH , p , cls . netchan . tempbuffer , NULL ) ;
CL_RegisterCustomization ( p ) ;
}
}
else
{
Con_Printf ( " Downloaded %i bytes for purported %i byte file, ignoring download \n " ,
cls . netchan . tempbuffersize , p - > nDownloadSize ) ;
}
if ( cls . netchan . tempbuffer )
Mem_Free ( cls . netchan . tempbuffer ) ;
}
cls . netchan . tempbuffersize = 0 ;
cls . netchan . tempbuffer = NULL ;
}
// moving to 'onhandle' list even if file was missed
CL_MoveToOnHandList ( p ) ;
}
if ( cls . state ! = ca_disconnected )
{
host . downloadcount = 0 ;
for ( p = cl . resourcesneeded . pNext ; p ! = & cl . resourcesneeded ; p = p - > pNext )
host . downloadcount + + ;
if ( cl . resourcesneeded . pNext = = & cl . resourcesneeded )
{
byte msg_buf [ MAX_INIT_MSG ] ;
sizebuf_t msg ;
MSG_Init ( & msg , " Resource Registration " , msg_buf , sizeof ( msg_buf ) ) ;
if ( CL_PrecacheResources ( ) )
CL_RegisterResources ( & msg ) ;
if ( MSG_GetNumBytesWritten ( & msg ) > 0 )
{
Netchan_CreateFragments ( & cls . netchan , & msg ) ;
Netchan_FragSend ( & cls . netchan ) ;
}
}
if ( cls . netchan . tempbuffer )
{
Con_Printf ( " Received a decal %s, but didn't find it in resources needed list! \n " , pfilename ) ;
Mem_Free ( cls . netchan . tempbuffer ) ;
}
cls . netchan . tempbuffer = NULL ;
cls . netchan . tempbuffersize = 0 ;
}
}
/*
= = = = = = = = = = = = = = = = = = = =
CL_ServerCommand
send command to a server
= = = = = = = = = = = = = = = = = = = =
*/
void CL_ServerCommand ( qboolean reliable , const char * fmt , . . . )
{
char string [ MAX_SYSPATH ] ;
va_list argptr ;
if ( cls . state < ca_connecting )
return ;
va_start ( argptr , fmt ) ;
Q_vsnprintf ( string , sizeof ( string ) , fmt , argptr ) ;
va_end ( argptr ) ;
if ( reliable )
{
MSG_BeginClientCmd ( & cls . netchan . message , clc_stringcmd ) ;
MSG_WriteString ( & cls . netchan . message , string ) ;
}
else
{
MSG_BeginClientCmd ( & cls . datagram , clc_stringcmd ) ;
MSG_WriteString ( & cls . datagram , string ) ;
}
}
//=============================================================================
/*
= = = = = = = = = = = = = =
CL_SetInfo_f
= = = = = = = = = = = = = =
*/
static void CL_SetInfo_f ( void )
{
convar_t * var ;
if ( Cmd_Argc ( ) = = 1 )
{
Con_Printf ( " User info settings: \n " ) ;
Info_Print ( cls . userinfo ) ;
Con_Printf ( " Total %i symbols \n " , Q_strlen ( cls . userinfo ) ) ;
return ;
}
if ( Cmd_Argc ( ) ! = 3 )
{
Con_Printf ( S_USAGE " setinfo [ <key> <value> ] \n " ) ;
return ;
}
// NOTE: some userinfo comed from cvars, e.g. cl_lw but we can call "setinfo cl_lw 1"
// without real cvar changing. So we need to lookup for cvar first to make sure what
// our key is not linked with console variable
var = Cvar_FindVar ( Cmd_Argv ( 1 ) ) ;
// make sure what cvar is existed and really part of userinfo
if ( var & & FBitSet ( var - > flags , FCVAR_USERINFO ) )
{
Cvar_DirectSet ( var , Cmd_Argv ( 2 ) ) ;
}
else if ( Info_SetValueForKey ( cls . userinfo , Cmd_Argv ( 1 ) , Cmd_Argv ( 2 ) , MAX_INFO_STRING ) )
{
// send update only on successfully changed userinfo
Cmd_ForwardToServer ( ) ;
}
}
/*
= = = = = = = = = = = = = =
CL_Physinfo_f
= = = = = = = = = = = = = =
*/
static void CL_Physinfo_f ( void )
{
Con_Printf ( " Phys info settings: \n " ) ;
Info_Print ( cls . physinfo ) ;
Con_Printf ( " Total %i symbols \n " , Q_strlen ( cls . physinfo ) ) ;
}
qboolean CL_PrecacheResources ( void )
{
resource_t * pRes ;
// NOTE: world need to be loaded as first model
for ( pRes = cl . resourcesonhand . pNext ; pRes & & pRes ! = & cl . resourcesonhand ; pRes = pRes - > pNext )
{
if ( FBitSet ( pRes - > ucFlags , RES_PRECACHED ) )
continue ;
if ( pRes - > type ! = t_model | | pRes - > nIndex ! = WORLD_INDEX )
continue ;
cl . models [ pRes - > nIndex ] = Mod_LoadWorld ( pRes - > szFileName , true ) ;
SetBits ( pRes - > ucFlags , RES_PRECACHED ) ;
cl . nummodels = 1 ;
break ;
}
// then we set up all the world submodels
for ( pRes = cl . resourcesonhand . pNext ; pRes & & pRes ! = & cl . resourcesonhand ; pRes = pRes - > pNext )
{
if ( FBitSet ( pRes - > ucFlags , RES_PRECACHED ) )
continue ;
if ( pRes - > type = = t_model & & pRes - > szFileName [ 0 ] = = ' * ' )
{
cl . models [ pRes - > nIndex ] = Mod_ForName ( pRes - > szFileName , false , false ) ;
cl . nummodels = Q_max ( cl . nummodels , pRes - > nIndex + 1 ) ;
SetBits ( pRes - > ucFlags , RES_PRECACHED ) ;
if ( cl . models [ pRes - > nIndex ] = = NULL )
{
Con_Printf ( S_ERROR " submodel %s not found \n " , pRes - > szFileName ) ;
if ( FBitSet ( pRes - > ucFlags , RES_FATALIFMISSING ) )
{
CL_Disconnect_f ( ) ;
return false ;
}
}
}
}
if ( cls . state ! = ca_active )
S_BeginRegistration ( ) ;
// precache all the remaining resources where order is doesn't matter
for ( pRes = cl . resourcesonhand . pNext ; pRes & & pRes ! = & cl . resourcesonhand ; pRes = pRes - > pNext )
{
if ( FBitSet ( pRes - > ucFlags , RES_PRECACHED ) )
continue ;
switch ( pRes - > type )
{
case t_sound :
if ( pRes - > nIndex ! = - 1 )
{
if ( FBitSet ( pRes - > ucFlags , RES_WASMISSING ) )
{
Con_Printf ( S_ERROR " Could not load sound " DEFAULT_SOUNDPATH " %s \n " , pRes - > szFileName ) ;
cl . sound_precache [ pRes - > nIndex ] [ 0 ] = 0 ;
cl . sound_index [ pRes - > nIndex ] = 0 ;
}
else
{
Q_strncpy ( cl . sound_precache [ pRes - > nIndex ] , pRes - > szFileName , sizeof ( cl . sound_precache [ 0 ] ) ) ;
cl . sound_index [ pRes - > nIndex ] = S_RegisterSound ( pRes - > szFileName ) ;
if ( ! cl . sound_index [ pRes - > nIndex ] )
{
if ( FBitSet ( pRes - > ucFlags , RES_FATALIFMISSING ) )
{
S_EndRegistration ( ) ;
CL_Disconnect_f ( ) ;
return false ;
}
}
}
}
else
{
// client sounds
S_RegisterSound ( pRes - > szFileName ) ;
}
break ;
case t_skin :
break ;
case t_model :
cl . nummodels = Q_max ( cl . nummodels , pRes - > nIndex + 1 ) ;
if ( pRes - > szFileName [ 0 ] ! = ' * ' )
{
if ( pRes - > nIndex ! = - 1 )
{
cl . models [ pRes - > nIndex ] = Mod_ForName ( pRes - > szFileName , false , true ) ;
if ( cl . models [ pRes - > nIndex ] = = NULL )
{
if ( FBitSet ( pRes - > ucFlags , RES_FATALIFMISSING ) )
{
S_EndRegistration ( ) ;
CL_Disconnect_f ( ) ;
return false ;
}
}
}
else
{
CL_LoadClientSprite ( pRes - > szFileName ) ;
}
}
break ;
case t_decal :
if ( ! FBitSet ( pRes - > ucFlags , RES_CUSTOM ) )
Q_strncpy ( host . draw_decals [ pRes - > nIndex ] , pRes - > szFileName , sizeof ( host . draw_decals [ 0 ] ) ) ;
break ;
case t_generic :
Q_strncpy ( cl . files_precache [ pRes - > nIndex ] , pRes - > szFileName , sizeof ( cl . files_precache [ 0 ] ) ) ;
cl . numfiles = Q_max ( cl . numfiles , pRes - > nIndex + 1 ) ;
break ;
case t_eventscript :
Q_strncpy ( cl . event_precache [ pRes - > nIndex ] , pRes - > szFileName , sizeof ( cl . event_precache [ 0 ] ) ) ;
CL_SetEventIndex ( cl . event_precache [ pRes - > nIndex ] , pRes - > nIndex ) ;
break ;
default :
break ;
}
SetBits ( pRes - > ucFlags , RES_PRECACHED ) ;
}
// make sure modelcount is in-range
cl . nummodels = bound ( 0 , cl . nummodels , MAX_MODELS ) ;
cl . numfiles = bound ( 0 , cl . numfiles , MAX_CUSTOM ) ;
if ( cls . state ! = ca_active )
S_EndRegistration ( ) ;
return true ;
}
/*
= = = = = = = = = = = = = = = = = =
CL_FullServerinfo_f
Sent by server when serverinfo changes
= = = = = = = = = = = = = = = = = =
*/
static void CL_FullServerinfo_f ( void )
{
if ( Cmd_Argc ( ) ! = 2 )
{
Con_Printf ( S_USAGE " fullserverinfo <complete info string> \n " ) ;
return ;
}
Q_strncpy ( cl . serverinfo , Cmd_Argv ( 1 ) , sizeof ( cl . serverinfo ) ) ;
}
/*
= = = = = = = = = = = = = = = = =
CL_Escape_f
Escape to menu from game
= = = = = = = = = = = = = = = = =
*/
static void CL_Escape_f ( void )
{
if ( cls . key_dest = = key_menu )
return ;
// the final credits is running
if ( UI_CreditsActive ( ) ) return ;
if ( cls . state = = ca_cinematic )
SCR_NextMovie ( ) ; // jump to next movie
else UI_SetActiveMenu ( true ) ;
}
/*
= = = = = = = = = = = = = = = = =
CL_InitLocal
= = = = = = = = = = = = = = = = =
*/
static void CL_InitLocal ( void )
{
cls . state = ca_disconnected ;
cls . signon = 0 ;
memset ( & cls . serveradr , 0 , sizeof ( cls . serveradr ) ) ;
cl . resourcesneeded . pNext = cl . resourcesneeded . pPrev = & cl . resourcesneeded ;
cl . resourcesonhand . pNext = cl . resourcesonhand . pPrev = & cl . resourcesonhand ;
Cvar_RegisterVariable ( & mp_decals ) ;
Cvar_RegisterVariable ( & dev_overview ) ;
Cvar_RegisterVariable ( & cl_resend ) ;
Cvar_RegisterVariable ( & cl_allow_upload ) ;
Cvar_RegisterVariable ( & cl_allow_download ) ;
Cvar_RegisterVariable ( & cl_download_ingame ) ;
Cvar_RegisterVariable ( & cl_logofile ) ;
Cvar_RegisterVariable ( & cl_logocolor ) ;
Cvar_RegisterVariable ( & cl_logoext ) ;
Cvar_RegisterVariable ( & cl_test_bandwidth ) ;
Voice_RegisterCvars ( ) ;
VGui_RegisterCvars ( ) ;
// register our variables
Cvar_RegisterVariable ( & cl_crosshair ) ;
Cvar_RegisterVariable ( & cl_nodelta ) ;
Cvar_RegisterVariable ( & cl_idealpitchscale ) ;
Cvar_RegisterVariable ( & cl_solid_players ) ;
Cvar_RegisterVariable ( & cl_interp ) ;
Cvar_RegisterVariable ( & cl_timeout ) ;
Cvar_RegisterVariable ( & cl_charset ) ;
Cvar_RegisterVariable ( & hud_utf8 ) ;
Cvar_RegisterVariable ( & rcon_address ) ;
Cvar_RegisterVariable ( & cl_trace_stufftext ) ;
Cvar_RegisterVariable ( & cl_trace_messages ) ;
Cvar_RegisterVariable ( & cl_trace_events ) ;
// userinfo
Cvar_RegisterVariable ( & cl_nopred ) ;
Cvar_RegisterVariable ( & name ) ;
Cvar_DirectSet ( & name , Sys_GetCurrentUser ( ) ) ;
Cvar_RegisterVariable ( & model ) ;
Cvar_RegisterVariable ( & cl_updaterate ) ;
Cvar_RegisterVariable ( & cl_dlmax ) ;
Cvar_RegisterVariable ( & cl_upmax ) ;
Cvar_RegisterVariable ( & cl_nat ) ;
Cvar_RegisterVariable ( & rate ) ;
Cvar_RegisterVariable ( & topcolor ) ;
Cvar_RegisterVariable ( & bottomcolor ) ;
Cvar_RegisterVariable ( & cl_lw ) ;
Cvar_Get ( " cl_lc " , " 1 " , FCVAR_ARCHIVE | FCVAR_USERINFO , " enable lag compensation " ) ;
Cvar_Get ( " password " , " " , FCVAR_USERINFO , " server password " ) ;
Cvar_Get ( " team " , " " , FCVAR_USERINFO , " player team " ) ;
Cvar_Get ( " skin " , " " , FCVAR_USERINFO , " player skin " ) ;
Cvar_RegisterVariable ( & cl_nosmooth ) ;
Cvar_RegisterVariable ( & cl_nointerp ) ;
Cvar_RegisterVariable ( & cl_smoothtime ) ;
Cvar_RegisterVariable ( & cl_cmdbackup ) ;
Cvar_RegisterVariable ( & cl_cmdrate ) ;
Cvar_RegisterVariable ( & cl_draw_particles ) ;
Cvar_RegisterVariable ( & cl_draw_tracers ) ;
Cvar_RegisterVariable ( & cl_draw_beams ) ;
Cvar_RegisterVariable ( & cl_lightstyle_lerping ) ;
Cvar_RegisterVariable ( & cl_showerror ) ;
Cvar_RegisterVariable ( & cl_bmodelinterp ) ;
Cvar_RegisterVariable ( & cl_clockreset ) ;
Cvar_RegisterVariable ( & cl_fixtimerate ) ;
Cvar_RegisterVariable ( & hud_fontscale ) ;
Cvar_RegisterVariable ( & hud_fontrender ) ;
Cvar_RegisterVariable ( & hud_scale ) ;
Cvar_RegisterVariable ( & hud_scale_minimal_width ) ;
Cvar_Get ( " cl_background " , " 0 " , FCVAR_READ_ONLY , " indicate what background map is running " ) ;
Cvar_RegisterVariable ( & cl_showevents ) ;
Cvar_Get ( " lastdemo " , " " , FCVAR_ARCHIVE , " last played demo " ) ;
Cvar_RegisterVariable ( & ui_renderworld ) ;
Cvar_RegisterVariable ( & cl_maxframetime ) ;
Cvar_RegisterVariable ( & cl_fixmodelinterpolationartifacts ) ;
// server commands
Cmd_AddCommand ( " noclip " , NULL , " enable or disable no clipping mode " ) ;
Cmd_AddCommand ( " notarget " , NULL , " notarget mode (monsters do not see you) " ) ;
Cmd_AddCommand ( " fullupdate " , NULL , " re-init HUD on start demo recording " ) ;
Cmd_AddCommand ( " give " , NULL , " give specified item or weapon " ) ;
Cmd_AddCommand ( " drop " , NULL , " drop current/specified item or weapon " ) ;
Cmd_AddCommand ( " gametitle " , NULL , " show game logo " ) ;
Cmd_AddRestrictedCommand ( " kill " , NULL , " die instantly " ) ;
Cmd_AddCommand ( " god " , NULL , " enable godmode " ) ;
Cmd_AddCommand ( " fov " , NULL , " set client field of view " ) ;
Cmd_AddRestrictedCommand ( " ent_list " , NULL , " list entities on server " ) ;
Cmd_AddRestrictedCommand ( " ent_fire " , NULL , " fire entity command (be careful) " ) ;
Cmd_AddRestrictedCommand ( " ent_info " , NULL , " dump entity information " ) ;
Cmd_AddRestrictedCommand ( " ent_create " , NULL , " create entity with specified values (be careful) " ) ;
Cmd_AddRestrictedCommand ( " ent_getvars " , NULL , " put parameters of specified entities to client's' ent_last_* cvars " ) ;
// register our commands
Cmd_AddCommand ( " pause " , NULL , " pause the game (if the server allows pausing) " ) ;
Cmd_AddCommand ( " localservers " , CL_LocalServers_f , " collect info about local servers " ) ;
Cmd_AddCommand ( " internetservers " , CL_InternetServers_f , " collect info about internet servers " ) ;
Cmd_AddCommand ( " cd " , CL_PlayCDTrack_f , " Play cd-track (not real cd-player of course) " ) ;
Cmd_AddCommand ( " mp3 " , CL_PlayCDTrack_f , " Play mp3-track (based on virtual cd-player) " ) ;
Cmd_AddCommand ( " waveplaylen " , CL_WavePlayLen_f , " Get approximate length of wave file " ) ;
Cmd_AddRestrictedCommand ( " setinfo " , CL_SetInfo_f , " examine or change the userinfo string (alias of userinfo) " ) ;
Cmd_AddRestrictedCommand ( " userinfo " , CL_SetInfo_f , " examine or change the userinfo string (alias of setinfo) " ) ;
Cmd_AddCommand ( " physinfo " , CL_Physinfo_f , " print current client physinfo " ) ;
Cmd_AddCommand ( " disconnect " , CL_Disconnect_f , " disconnect from server " ) ;
Cmd_AddCommand ( " record " , CL_Record_f , " record a demo " ) ;
Cmd_AddCommand ( " playdemo " , CL_PlayDemo_f , " play a demo " ) ;
Cmd_AddCommand ( " timedemo " , CL_TimeDemo_f , " demo benchmark " ) ;
Cmd_AddCommand ( " killdemo " , CL_DeleteDemo_f , " delete a specified demo file " ) ;
Cmd_AddCommand ( " startdemos " , CL_StartDemos_f , " start playing back the selected demos sequentially " ) ;
Cmd_AddCommand ( " demos " , CL_Demos_f , " restart looping demos defined by the last startdemos command " ) ;
Cmd_AddCommand ( " movie " , CL_PlayVideo_f , " play a movie " ) ;
Cmd_AddCommand ( " stop " , CL_Stop_f , " stop playing or recording a demo " ) ;
Cmd_AddCommand ( " info " , NULL , " collect info about local servers with specified protocol " ) ;
Cmd_AddCommand ( " escape " , CL_Escape_f , " escape from game to menu " ) ;
Cmd_AddCommand ( " togglemenu " , CL_Escape_f , " toggle between game and menu " ) ;
Cmd_AddCommand ( " pointfile " , CL_ReadPointFile_f , " show leaks on a map (if present of course) " ) ;
Cmd_AddCommand ( " linefile " , CL_ReadLineFile_f , " show leaks on a map (if present of course) " ) ;
Cmd_AddCommand ( " fullserverinfo " , CL_FullServerinfo_f , " sent by server when serverinfo changes " ) ;
Cmd_AddCommand ( " upload " , CL_BeginUpload_f , " uploading file to the server " ) ;
Cmd_AddRestrictedCommand ( " quit " , CL_Quit_f , " quit from game " ) ;
Cmd_AddRestrictedCommand ( " exit " , CL_Quit_f , " quit from game " ) ;
Cmd_AddCommand ( " screenshot " , CL_GenericShot_f , " takes a screenshot of the next rendered frame " ) ;
Cmd_AddCommand ( " snapshot " , CL_GenericShot_f , " takes a snapshot of the next rendered frame " ) ;
Cmd_AddCommand ( " envshot " , CL_GenericShot_f , " takes a six-sides cubemap shot with specified name " ) ;
Cmd_AddCommand ( " skyshot " , CL_GenericShot_f , " takes a six-sides envmap (skybox) shot with specified name " ) ;
Cmd_AddCommand ( " levelshot " , CL_LevelShot_f , " same as \" screenshot \" , used for create plaque images " ) ;
Cmd_AddCommand ( " saveshot " , CL_GenericShot_f , " used for create save previews with LoadGame menu " ) ;
Cmd_AddCommand ( " connect " , CL_Connect_f , " connect to a server by hostname " ) ;
Cmd_AddCommand ( " reconnect " , CL_Reconnect_f , " reconnect to current level " ) ;
Cmd_AddCommand ( " rcon " , CL_Rcon_f , " sends a command to the server console (rcon_password and rcon_address required) " ) ;
Cmd_AddCommand ( " precache " , CL_LegacyPrecache_f , " legacy server compatibility " ) ;
}
//============================================================================
/*
= = = = = = = = = = = = = = = = = =
CL_AdjustClock
slowly adjuct client clock
to smooth lag effect
= = = = = = = = = = = = = = = = = =
*/
static void CL_AdjustClock ( void )
{
if ( cl . timedelta = = 0.0f | | ! cl_fixtimerate . value )
return ;
if ( cl_fixtimerate . value < 0.0f )
Cvar_SetValue ( " cl_fixtimerate " , 7.5f ) ;
if ( fabs ( cl . timedelta ) > = 0.001f )
{
double msec , adjust ;
double sign ;
msec = ( cl . timedelta * 1000.0 ) ;
sign = ( msec < 0 ) ? 1.0 : - 1.0 ;
msec = Q_min ( cl_fixtimerate . value , fabs ( msec ) ) ;
adjust = sign * ( msec / 1000.0 ) ;
if ( fabs ( adjust ) < fabs ( cl . timedelta ) )
{
cl . timedelta + = adjust ;
cl . time + = adjust ;
}
if ( cl . oldtime > cl . time )
cl . oldtime = cl . time ;
}
}
/*
= = = = = = = = = = = = = = = = = =
Host_ClientBegin
= = = = = = = = = = = = = = = = = =
*/
void Host_ClientBegin ( void )
{
// exec console commands
Cbuf_Execute ( ) ;
// if client is not active, do nothing
if ( ! cls . initialized ) return ;
// finalize connection process if needs
CL_CheckClientState ( ) ;
// tell the client.dll about client data
CL_UpdateClientData ( ) ;
// if running the server locally, make intentions now
if ( SV_Active ( ) ) CL_SendCommand ( ) ;
}
/*
= = = = = = = = = = = = = = = = = =
Host_ClientFrame
= = = = = = = = = = = = = = = = = =
*/
void Host_ClientFrame ( void )
{
// if client is not active, do nothing
if ( ! cls . initialized ) return ;
if ( cls . key_dest = = key_game & & cls . state = = ca_active & & ! Con_Visible ( ) )
Platform_SetTimer ( cl_maxframetime . value ) ;
// if running the server remotely, send intentions now after
// the incoming messages have been read
if ( ! SV_Active ( ) ) CL_SendCommand ( ) ;
clgame . dllFuncs . pfnFrame ( host . frametime ) ;
// remember last received framenum
CL_SetLastUpdate ( ) ;
// read updates from server
CL_ReadPackets ( ) ;
// do prediction again in case we got
// a new portion updates from server
CL_RedoPrediction ( ) ;
// update voice
Voice_Idle ( host . frametime ) ;
// emit visible entities
CL_EmitEntities ( ) ;
// in case we lost connection
CL_CheckForResend ( ) ;
// procssing resources on handle
while ( CL_RequestMissingResources ( ) ) ;
// handle thirdperson camera
CL_MoveThirdpersonCamera ( ) ;
// handle spectator movement
CL_MoveSpectatorCamera ( ) ;
// catch changes video settings
VID_CheckChanges ( ) ;
// update the screen
SCR_UpdateScreen ( ) ;
// update audio
SND_UpdateSound ( ) ;
// play avi-files
SCR_RunCinematic ( ) ;
// adjust client time
CL_AdjustClock ( ) ;
}
//============================================================================
/*
= = = = = = = = = = = = = = = = = = = =
CL_Init
= = = = = = = = = = = = = = = = = = = =
*/
void CL_Init ( void )
{
string libpath ;
if ( host . type = = HOST_DEDICATED )
return ; // nothing running on the client
CL_InitLocal ( ) ;
VID_Init ( ) ; // init video
S_Init ( ) ; // init sound
Voice_Init ( VOICE_DEFAULT_CODEC , 3 , true ) ; // init voice (do not open the device)
// unreliable buffer. unsed for unreliable commands and voice stream
MSG_Init ( & cls . datagram , " cls.datagram " , cls . datagram_buf , sizeof ( cls . datagram_buf ) ) ;
// IN_TouchInit();
COM_GetCommonLibraryPath ( LIBRARY_CLIENT , libpath , sizeof ( libpath ) ) ;
if ( ! CL_LoadProgs ( libpath ) )
Host_Error ( " can't initialize %s: %s \n " , libpath , COM_GetLibraryError ( ) ) ;
cls . initialized = true ;
cl . maxclients = 1 ; // allow to drawing player in menu
cls . olddemonum = - 1 ;
cls . demonum = - 1 ;
}
/*
= = = = = = = = = = = = = = =
CL_Shutdown
= = = = = = = = = = = = = = =
*/
void CL_Shutdown ( void )
{
Con_Printf ( " CL_Shutdown() \n " ) ;
if ( ! host . crashed & & cls . initialized )
{
Host_WriteOpenGLConfig ( ) ;
Host_WriteVideoConfig ( ) ;
Touch_WriteConfig ( ) ;
}
// IN_TouchShutdown ();
Joy_Shutdown ( ) ;
CL_CloseDemoHeader ( ) ;
IN_Shutdown ( ) ;
Mobile_Shutdown ( ) ;
SCR_Shutdown ( ) ;
CL_UnloadProgs ( ) ;
cls . initialized = false ;
// for client-side VGUI support we use other order
if ( FI & & FI - > GameInfo & & ! FI - > GameInfo - > internal_vgui_support )
VGui_Shutdown ( ) ;
if ( g_fsapi . Delete )
g_fsapi . Delete ( " demoheader.tmp " ) ; // remove tmp file
SCR_FreeCinematic ( ) ; // release AVI's *after* client.dll because custom renderer may use them
S_Shutdown ( ) ;
R_Shutdown ( ) ;
Con_Shutdown ( ) ;
}