diff options
author | Andrey Nazarov <skuller@skuller.net> | 2007-08-14 20:18:08 +0000 |
---|---|---|
committer | Andrey Nazarov <skuller@skuller.net> | 2007-08-14 20:18:08 +0000 |
commit | f294db4ccf45f6274e65260dd6f9a2c5faa94313 (patch) | |
tree | e8cf1ba2bfe9c8417eec17faf912442f52fc4ef2 /source/cl_main.c |
Initial import of the new Q2PRO tree.
Diffstat (limited to 'source/cl_main.c')
-rw-r--r-- | source/cl_main.c | 2762 |
1 files changed, 2762 insertions, 0 deletions
diff --git a/source/cl_main.c b/source/cl_main.c new file mode 100644 index 0000000..13b47f2 --- /dev/null +++ b/source/cl_main.c @@ -0,0 +1,2762 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cl_main.c -- client main loop + +#include "cl_local.h" + +cvar_t *adr0; +cvar_t *adr1; +cvar_t *adr2; +cvar_t *adr3; +cvar_t *adr4; +cvar_t *adr5; +cvar_t *adr6; +cvar_t *adr7; +cvar_t *adr8; + +extern cvar_t *rcon_password; +cvar_t *rcon_address; + +cvar_t *cl_noskins; +cvar_t *cl_autoskins; +cvar_t *cl_footsteps; +cvar_t *cl_timeout; +cvar_t *cl_predict; +cvar_t *cl_gun; +cvar_t *cl_maxfps; +cvar_t *cl_async; +cvar_t *r_maxfps; + +cvar_t *cl_add_particles; +cvar_t *cl_add_lights; +cvar_t *cl_add_entities; +cvar_t *cl_add_blend; +cvar_t *cl_kickangles; + +cvar_t *cl_shownet; +cvar_t *cl_showmiss; +cvar_t *cl_showclamp; + +cvar_t *cl_thirdperson; +cvar_t *cl_thirdperson_angle; +cvar_t *cl_thirdperson_range; + +cvar_t *cl_railtrail_type; +cvar_t *cl_railtrail_time; +cvar_t *cl_railtrail_alpha; +cvar_t *cl_railcore_color; +cvar_t *cl_railcore_width; +cvar_t *cl_railrings_color; +cvar_t *cl_railrings_width; + +cvar_t *cl_disable_particles; +cvar_t *cl_disable_explosions; +cvar_t *cl_chat_notify; +cvar_t *cl_chat_beep; +cvar_t *cl_chat_clear; + +cvar_t *cl_gibs; + +cvar_t *cl_protocol; + +cvar_t *gender_auto; + +cvar_t *cl_vwep; + +// +// userinfo +// +cvar_t *info_password; +cvar_t *info_spectator; +cvar_t *info_name; +cvar_t *info_skin; +cvar_t *info_rate; +cvar_t *info_fov; +cvar_t *info_msg; +cvar_t *info_hand; +cvar_t *info_gender; + +client_static_t cls; +client_state_t cl; + +clientAPI_t client; + +centity_t cl_entities[ MAX_EDICTS ]; + +qboolean CL_SendStatusRequest( char *buffer, int bufferSize ); + +//====================================================================== + +typedef enum { + REQ_FREE, + REQ_STATUS, + REQ_INFO, + REQ_PING, + REQ_RCON +} requestType_t; + +typedef struct { + requestType_t type; + netadr_t adr; + int time; +} request_t; + +#define MAX_REQUESTS 32 +#define REQUEST_MASK ( MAX_REQUESTS - 1 ) + +static request_t clientRequests[MAX_REQUESTS]; +static int currentRequest; + +static request_t *CL_AddRequest( netadr_t *adr, requestType_t type ) { + request_t *r; + + r = &clientRequests[currentRequest & REQUEST_MASK]; + currentRequest++; + + r->adr = *adr; + r->type = type; + if( adr->type == NA_BROADCAST ) { + r->time = cls.realtime + 3000; + } else { + r->time = cls.realtime + 6000; + } + + return r; +} + +/* +=================== +CL_UpdateGunSetting +=================== +*/ +static void CL_UpdateGunSetting( void ) { + int nogun; + + if ( cls.state < ca_connected || cls.state > ca_active ) { + return; + } + + if ( cls.serverProtocol < PROTOCOL_VERSION_R1Q2 ) { + return; + } + + if( cl_gun->integer == -1 ) { + nogun = 2; + } else if( cl_gun->integer == 0 || info_hand->integer == 2 ) { + nogun = 1; + } else { + nogun = 0; + } + + MSG_WriteByte( clc_setting ); + MSG_WriteShort( CLS_NOGUN ); + MSG_WriteShort( nogun ); + MSG_FlushTo( &cls.netchan->message ); +} + +/* +=================== +CL_UpdateGibSetting +=================== +*/ +static void CL_UpdateGibSetting( void ) { + if ( cls.state < ca_connected || cls.state > ca_active ) { + return; + } + + if ( cls.serverProtocol != PROTOCOL_VERSION_Q2PRO ) { + return; + } + + MSG_WriteByte( clc_setting ); + MSG_WriteShort( CLS_NOGIBS ); + MSG_WriteShort( !cl_gibs->integer ); + MSG_FlushTo( &cls.netchan->message ); +} + +/* +=================== +CL_UpdateFootstepsSetting +=================== +*/ +static void CL_UpdateFootstepsSetting( void ) { + if ( cls.state < ca_connected || cls.state > ca_active ) { + return; + } + + if ( cls.serverProtocol != PROTOCOL_VERSION_Q2PRO ) { + return; + } + + MSG_WriteByte( clc_setting ); + MSG_WriteShort( CLS_NOFOOTSTEPS ); + MSG_WriteShort( !cl_footsteps->integer ); + MSG_FlushTo( &cls.netchan->message ); +} + +/* +=================== +CL_UpdatePredictSetting +=================== +*/ +static void CL_UpdatePredictSetting( void ) { + if ( cls.state < ca_connected || cls.state > ca_active ) { + return; + } + + if ( cls.serverProtocol != PROTOCOL_VERSION_Q2PRO ) { + return; + } + + MSG_WriteByte( clc_setting ); + MSG_WriteShort( CLS_NOPREDICT ); + MSG_WriteShort( !cl_predict->integer ); + MSG_FlushTo( &cls.netchan->message ); +} + +/* +=================== +CL_UpdateLocalFovSetting +=================== +*/ +void CL_UpdateLocalFovSetting( void ) { + if ( cls.state < ca_connected || cls.state > ca_active ) { + return; + } + + if ( cls.serverProtocol != PROTOCOL_VERSION_Q2PRO ) { + return; + } + + MSG_WriteByte( clc_setting ); + MSG_WriteShort( CLS_LOCALFOV ); + MSG_WriteShort( cl_demo_local_fov->integer ); + MSG_FlushTo( &cls.netchan->message ); +} + +/* +=================== +CL_ClientCommand +=================== +*/ +void CL_ClientCommand( const char *string ) { + if ( !cls.netchan ) { + return; + } + MSG_WriteByte( clc_stringcmd ); + MSG_WriteString( string ); + MSG_FlushTo( &cls.netchan->message ); +} + + +/* +=================== +Cmd_ForwardToServer + +adds the current command line as a clc_stringcmd to the client message. +things like godmode, noclip, etc, are commands directed to the server, +so when they are typed in at the console, they will need to be forwarded. +=================== +*/ +void Cmd_ForwardToServer( void ) { + char * cmd; + + cmd = Cmd_Argv( 0 ); + if ( cls.state < ca_active || *cmd == '-' || *cmd == '+' ) { + Com_Printf( "Unknown command \"%s\"\n", cmd ); + return; + } + + if ( cls.demoplayback ) { + return; + } + + CL_ClientCommand( Cmd_RawArgsFrom( 0 ) ); +} + +/* +================== +CL_ForwardToServer_f +================== +*/ +void CL_ForwardToServer_f( void ) { + if ( cls.state < ca_connected ) { + Com_Printf( "Can't \"%s\", not connected\n", Cmd_Argv( 0 ) ); + return; + } + + if ( cls.demoplayback ) { + return; + } + + // don't forward the first argument + if ( Cmd_Argc() > 1 ) { + CL_ClientCommand( Cmd_RawArgs() ); + } +} + +void CL_Setenv_f( void ) { + int argc = Cmd_Argc(); + + if ( argc > 2 ) { + char buffer[ MAX_STRING_CHARS ]; + + Q_strncpyz( buffer, Cmd_Argv( 1 ), sizeof( buffer ) ); + Q_strcat( buffer, sizeof( buffer ), "=" ); + Q_strcat( buffer, sizeof( buffer ), Cmd_ArgsFrom( 2 ) ); + + putenv( buffer ); + } else if ( argc == 2 ) { + char * env = getenv( Cmd_Argv( 1 ) ); + + if ( env ) { + Com_Printf( "%s=%s\n", Cmd_Argv( 1 ), env ); + } else { + Com_Printf( "%s undefined\n", Cmd_Argv( 1 ) ); + } + } +} + +/* +================== +CL_Pause_f +================== +*/ +void CL_Pause_f( void ) { + Cvar_SetInteger( "cl_paused", !cl_paused->integer ); +} + +/* +================= +CL_CheckForResend + +Resend a connect message if the last one has timed out +================= +*/ +static void CL_CheckForResend( void ) { + neterr_t ret; + char tail[MAX_QPATH]; + + if ( cls.demoplayback ) { + return; + } + + // if the local server is running and we aren't + // then connect + if ( cls.state < ca_connecting && sv_running->integer > ss_loading ) { + strcpy( cls.servername, "localhost" ); + cls.serverAddress.type = NA_LOOPBACK; + if( sv_running->integer < ss_cinematic ) { + cls.serverProtocol = cl_protocol->integer; + if( cls.serverProtocol < PROTOCOL_VERSION_DEFAULT || + cls.serverProtocol > PROTOCOL_VERSION_Q2PRO ) + { + cls.serverProtocol = PROTOCOL_VERSION_Q2PRO; + } + } else { + cls.serverProtocol = PROTOCOL_VERSION_DEFAULT; + } + + // we don't need a challenge on the localhost + cls.state = ca_connecting; + cls.connect_time = -9999; + + cls.passive = qfalse; + } + + // resend if we haven't gotten a reply yet + if ( cls.state != ca_connecting && cls.state != ca_challenging ) { + return; + } + + if ( cls.realtime - cls.connect_time < 3000 ) + return; + + cls.connect_time = cls.realtime; // for retransmit requests + + cls.connectCount++; + + if ( cls.state == ca_challenging ) { + Com_Printf( "Requesting challenge... %i\n", cls.connectCount ); + ret = Netchan_OutOfBandPrint( NS_CLIENT, &cls.serverAddress, + "getchallenge\n" ); + if( ret == NET_ERROR ) { + Com_Error( ERR_DISCONNECT, "%s to %s\n", NET_ErrorString(), + NET_AdrToString( &cls.serverAddress ) ); + } + return; + } + + // + // We have gotten a challenge from the server, so try and connect. + // + Com_Printf( "Requesting connection... %i\n", cls.connectCount ); + + cls.userinfo_modified = 0; + + tail[0] = 0; + cls.quakePort = net_qport->integer; + if( cls.serverProtocol != PROTOCOL_VERSION_DEFAULT ) { + Com_sprintf( tail, sizeof( tail ), " %d", net_maxmsglen->integer ); + if( cls.serverProtocol == PROTOCOL_VERSION_Q2PRO ) { + strcat( tail, net_chantype->integer ? " 1" : " 0" ); +#ifdef USE_ZLIB + strcat( tail, " 1" ); +#else + strcat( tail, " 0" ); +#endif + } + cls.quakePort &= 0xFF; + } + ret = Netchan_OutOfBandPrint( NS_CLIENT, &cls.serverAddress, + "connect %i %i %i \"%s\"%s\n", cls.serverProtocol, cls.quakePort, + cls.challenge, Cvar_Userinfo(), tail ); + if( ret == NET_ERROR ) { + Com_Error( ERR_DISCONNECT, "%s to %s\n", NET_ErrorString(), + NET_AdrToString( &cls.serverAddress ) ); + } +} + + +/* +================ +CL_Connect_f + +================ +*/ +void CL_Connect_f( void ) { + char *server; + netadr_t address; + int protocol; + + if ( Cmd_Argc() < 2 ) { +usage: + Com_Printf( "Usage: connect <server> [protocol]\n" + "Protocol argument overrides cl_protocol setting\n" + "Supported protocols: %d, %d and %d\n", + PROTOCOL_VERSION_DEFAULT, + PROTOCOL_VERSION_R1Q2, + PROTOCOL_VERSION_Q2PRO ); + return; + } + + server = Cmd_Argv( 1 ); + protocol = cl_protocol->integer; + if( Cmd_Argc() > 2 ) { + protocol = atoi( Cmd_Argv( 2 ) ); + if( protocol < PROTOCOL_VERSION_DEFAULT || + protocol > PROTOCOL_VERSION_Q2PRO ) + { + goto usage; + } + } + + if ( !NET_StringToAdr( server, &address ) ) { + Com_Printf( "Bad server address\n" ); + return; + } + if ( address.port == 0 ) { + address.port = BigShort( PORT_SERVER ); + } + + if ( sv_running->integer ) { + // if running a local server, kill it and reissue + SV_Shutdown( "Server was killed\n", KILL_DROP ); + } + + NET_Config( NET_CLIENT ); + + CL_Disconnect( ERR_DISCONNECT, NULL ); + + cls.serverAddress = address; + cls.serverProtocol = protocol; + Q_strncpyz( cls.servername, server, sizeof( cls.servername ) ); + cls.passive = qfalse; + + cls.state = ca_challenging; + cls.connectCount = 0; + cls.connect_time = -99999; // CL_CheckForResend() will fire immediately + + CL_CheckForResend(); + + Cvar_Set( "cl_paused", "0" ); + Cvar_Set( "timedemo", "0" ); + + Con_Close(); + UI_OpenMenu( UIMENU_NONE ); +} + +void CL_PassiveConnect_f( void ) { + netadr_t address; + + if( cls.passive ) { + cls.passive = qfalse; + Com_Printf( "No longer listening for passive connections.\n" ); + return; + } + if ( sv_running->integer ) { + // if running a local server, kill it and reissue + SV_Shutdown( "Server was killed\n", KILL_DROP ); + } + + NET_Config( NET_CLIENT ); + + CL_Disconnect( ERR_DISCONNECT, NULL ); + + if( !NET_GetAddress( NS_CLIENT, &address ) ) { + return; + } + + cls.passive = qtrue; + Com_Printf( "Listening for passive connections at %s.\n", + NET_AdrToString( &address ) ); +} + +static const char *CL_Connect_g( const char *partial, int state ) { + static int length; + static int index; + const char *adrstring; + char buffer[MAX_QPATH]; + + if( !state ) { + length = strlen( partial ); + index = 0; + } + + while( index < MAX_LOCAL_SERVERS ) { + Com_sprintf( buffer, sizeof( buffer ), "adr%i", index ); + index++; + adrstring = Cvar_VariableString( buffer ); + if( !adrstring[ 0 ] ) { + continue; + } + if( !strncmp( partial, adrstring, length ) ) { + return adrstring; + } + } + + return NULL; +} + + +/* +===================== +CL_Rcon_f + + Send the rest of the command line over as + an unconnected command. +===================== +*/ +void CL_Rcon_f( void ) { + netadr_t address; + neterr_t ret; + + if( Cmd_Argc() < 2 ) { + Com_Printf( "Usage: %s <command>\n", Cmd_Argv( 0 ) ); + return; + } + + if( !rcon_password->string[0] ) { + Com_Printf( "You must set 'rcon_password' before " + "issuing an rcon command.\n" ); + return; + } + + if( !cls.netchan ) { + if( !rcon_address->string[0] ) { + Com_Printf( "You must either be connected, " + "or set the 'rcon_address' cvar " + "to issue rcon commands.\n" ); + return; + } + if( !NET_StringToAdr( rcon_address->string, &address ) ) { + Com_Printf( "Bad address: %s\n", rcon_address->string ); + return; + } + if( !address.port ) + address.port = BigShort( PORT_SERVER ); + } else { + address = cls.netchan->remote_address; + } + + NET_Config( NET_CLIENT ); + + CL_AddRequest( &address, REQ_RCON ); + + ret = Netchan_OutOfBandPrint( NS_CLIENT, &address, + "rcon \"%s\" %s", rcon_password->string, Cmd_RawArgs() ); + if( ret == NET_ERROR ) { + Com_Printf( "%s to %s\n", NET_ErrorString(), + NET_AdrToString( &address ) ); + } +} + + +/* +===================== +CL_ClearState + +===================== +*/ +void CL_ClearState( void ) { + S_StopAllSounds(); + CL_ClearEffects(); + CL_ClearTEnts(); + LOC_FreeLocations(); + + // wipe the entire cl structure + CM_FreeMap( &cl.cm ); + memset( &cl, 0, sizeof( cl ) ); + memset( &cl_entities, 0, sizeof( cl_entities ) ); + + if( cls.state > ca_connected ) { + cls.state = ca_connected; + } +} + +/* +===================== +CL_Disconnect + +Goes from a connected state to full screen console state +Sends a disconnect message to the server +This is also called on Com_Error, so it shouldn't cause any errors +===================== +*/ +void CL_Disconnect( comErrorType_t type, const char *text ) { + if ( cls.state != ca_disconnected ) { + Cmd_ExecTrigger( TRIG_CLIENT_SYSTEM, "disconnect" ); + } + + if ( cls.ref_initialized ) + ref.CinematicSetPalette( NULL ); + + cls.connect_time = 0; + cls.connectCount = 0; + cls.passive = qfalse; + + SCR_StopCinematic (); + + if ( cls.demoplayback ) { + FS_FCloseFile( cls.demoplayback ); + cls.demoplayback = 0; + + if ( com_timedemo->integer ) { + float seconds, fps; + + seconds = ( Sys_Milliseconds() - cls.timeDemoStart ) * 0.001f; + fps = cls.timeDemoFrames / seconds; + + Com_Printf( "%i frames, %3.1f seconds: %3.1f fps\n", + cls.timeDemoFrames, seconds, fps ); + } + } + + if ( cls.demorecording ) + CL_Stop_f(); + + if( cls.netchan ) { + // send a disconnect message to the server + MSG_WriteByte( clc_stringcmd ); + MSG_WriteString( "disconnect" ); + + cls.netchan->Transmit( cls.netchan, msg_write.cursize, msg_write.data ); + cls.netchan->Transmit( cls.netchan, msg_write.cursize, msg_write.data ); + cls.netchan->Transmit( cls.netchan, msg_write.cursize, msg_write.data ); + + SZ_Clear( &msg_write ); + + Netchan_Close( cls.netchan ); + cls.netchan = NULL; + } + + // stop download + if ( cls.download ) { + FS_FCloseFile( cls.download ); + cls.download = 0; + } + + cls.downloadtempname[ 0 ] = 0; + cls.downloadname[ 0 ] = 0; + + CL_ClearState (); + + Cvar_Set( "cl_paused", "0" ); + + cls.state = ca_disconnected; + cls.messageString[ 0 ] = 0; + cls.userinfo_modified = 0; + + if( cls.ui_initialized ) { + UI_ErrorMenu( type, text ); + } + +} + +/* +================ +CL_Disconnect_f +================ +*/ +static void CL_Disconnect_f( void ) { + if( cls.state > ca_disconnected ) { + Com_Error( ERR_SILENT, "Disconnected from server" ); + } +} + + +/* +================ +CL_ServerStatus_f +================ +*/ +static void CL_ServerStatus_f( void ) { + char *s; + netadr_t adr; + neterr_t ret; + + if ( Cmd_Argc() < 2 ) { + Com_Printf( "Usage: %s <server>\n", Cmd_Argv( 0 ) ); + return; + } + + s = Cmd_Argv( 1 ); + if ( !NET_StringToAdr( s, &adr ) ) { + Com_Printf( "Bad address: %s\n", s ); + return; + } + + if ( !adr.port ) { + adr.port = BigShort( PORT_SERVER ); + } + + CL_AddRequest( &adr, REQ_STATUS ); + + NET_Config( NET_CLIENT ); + + ret = Netchan_OutOfBandPrint( NS_CLIENT, &adr, "status p=%d,%d,%d", + PROTOCOL_VERSION_DEFAULT, PROTOCOL_VERSION_R1Q2, PROTOCOL_VERSION_Q2PRO ); + if( ret == NET_ERROR ) { + Com_Printf( "%s to %s\n", NET_ErrorString(), NET_AdrToString( &adr ) ); + } + +} + +/* +==================== +SortPlayers +==================== +*/ +static int SortPlayers( const void *v1, const void *v2 ) { + const playerStatus_t *p1 = ( const playerStatus_t * )v1; + const playerStatus_t *p2 = ( const playerStatus_t * )v2; + + return p2->score - p1->score; +} + +/* +==================== +CL_ServerStatusResponse +==================== +*/ +static qboolean CL_ServerStatusResponse( const char *status, + const netadr_t *from, serverStatus_t *dest ) +{ + const char *s; + playerStatus_t *player; + int length; + + memset( dest, 0, sizeof( *dest ) ); + + s = strchr( status, '\n' ); + if ( !s ) { + return qfalse; + } + length = s - status; + if( length > MAX_STRING_CHARS - 1 ) { + return qfalse; + } + s++; + + strcpy( dest->address, NET_AdrToString( from ) ); + strncpy( dest->infostring, status, length ); + + // HACK: check if this is a status response + if( !strstr( dest->infostring, "\\hostname\\" ) ) { + return qfalse; + } + + // parse player list + if( *s < 32 ) { + return qtrue; + } + do { + player = &dest->players[dest->numPlayers]; + player->score = atoi( COM_Parse( &s ) ); + player->ping = atoi( COM_Parse( &s ) ); + if( !s ) { + break; + } + Q_strncpyz( player->name, COM_Parse( &s ), sizeof( player->name ) ); + + if ( ++dest->numPlayers == MAX_STATUS_PLAYERS ) { + break; + } + } while( s ); + + qsort( dest->players, dest->numPlayers, sizeof( dest->players[ 0 ] ), + SortPlayers ); + + return qtrue; +} + +void CL_DumpServerInfo( const serverStatus_t *status ) { + char key[MAX_STRING_CHARS]; + char value[MAX_STRING_CHARS]; + const playerStatus_t *player, *last; + const char *infostring; + + Com_Printf( "Info response from %s:\n", + NET_AdrToString( &net_from ) ); + + infostring = status->infostring; + do { + Info_NextPair( &infostring, key, value ); + + if( !key[0] ) { + break; + } + + if( value[0] ) { + Com_Printf( "%-20s %s\n", key, value ); + } else { + Com_Printf( "%-20s <MISSING VALUE>\n", key ); + } + } while( infostring ); + + Com_Printf( "\nScore Ping Name\n" ); + last = status->players + status->numPlayers; + for( player = status->players; player != last; player++ ) { + Com_Printf( "%5i %4i %s\n", player->score, player->ping, + player->name ); + } +} + +/* +==================== +CL_ParsePrintMessage +==================== +*/ +static void CL_ParsePrintMessage( void ) { + request_t *r; + serverStatus_t serverStatus; + char *string; + int i, oldest; + + string = MSG_ReadString(); + + if ( ( cls.state == ca_challenging || cls.state == ca_connecting ) && + NET_IsEqualBaseAdr( &net_from, &cls.serverAddress ) ) + { + /* server rejected our connect request */ + if( NET_IsLocalAddress( &cls.serverAddress ) ) { + Com_Error( ERR_DROP, "Server rejected loopback connection" ); + } + Com_Printf( "%s", string ); + Q_strncpyz( cls.messageString, string, sizeof( cls.messageString ) ); + cls.state = ca_challenging; + cls.connectCount = 0; + return; + } + + oldest = currentRequest - MAX_REQUESTS; + if( oldest < 0 ) { + oldest = 0; + } + for( i = currentRequest - 1; i >= oldest; i-- ) { + r = &clientRequests[i & REQUEST_MASK]; + if( !r->type ) { + continue; + } + if( r->adr.type == NA_BROADCAST ) { + if( r->time < cls.realtime ) { + continue; + } + } else { + if( r->time < cls.realtime ) { + break; + } + if( !NET_IsEqualBaseAdr( &net_from, &r->adr ) ) { + continue; + } + } + switch( r->type ) { + case REQ_STATUS: + if( CL_ServerStatusResponse( string, &net_from, &serverStatus ) ) { + CL_DumpServerInfo( &serverStatus ); + } + break; + case REQ_INFO: + break; + case REQ_PING: + if( CL_ServerStatusResponse( string, &net_from, &serverStatus ) ) { + UI_AddToServerList( &serverStatus ); + } + break; + case REQ_RCON: + Com_Printf( "%s", string ); + CL_AddRequest( &net_from, REQ_RCON ); + break; + default: + break; + } + + r->type = REQ_FREE; + return; + } + + Com_DPrintf( "Dropped unrequested packet\n" ); +} + + +/* +==================== +CL_Packet_f + +packet <destination> <contents> + +Contents allows \n escape character +==================== +*/ +/* +void CL_Packet_f (void) +{ + char send[2048]; + int i, l; + char *in, *out; + netadr_t adr; + + if (Cmd_Argc() != 3) + { + Com_Printf ("packet <destination> <contents>\n"); + return; + } + + if (!NET_StringToAdr (Cmd_Argv(1), &adr)) + { + Com_Printf ("Bad address\n"); + return; + } + if (!adr.port) + adr.port = BigShort (PORT_SERVER); + + in = Cmd_Argv(2); + out = send+4; + send[0] = send[1] = send[2] = send[3] = (char)0xff; + + l = strlen (in); + for (i=0 ; i<l ; i++) + { + if (in[i] == '\\' && in[i+1] == 'n') + { + *out++ = '\n'; + i++; + } + else + *out++ = in[i]; + } + *out = 0; + + NET_SendPacket (NS_CLIENT, out-send, send, &adr); +} +*/ + +/* +================= +CL_Changing_f + +Just sent as a hint to the client that they should +drop to full console +================= +*/ +static void CL_Changing_f( void ) { + if ( cls.state < ca_connected ) { + return; + } + + S_StopAllSounds(); + + Com_Printf( "Changing map...\n" ); + + Cmd_ExecTrigger( TRIG_CLIENT_SYSTEM, "changelevel" ); + + SCR_BeginLoadingPlaque(); + + cls.state = ca_connected; // not active anymore, but not disconnected + + SCR_UpdateScreen(); +} + + +/* +================= +CL_Reconnect_f + +The server is changing levels +================= +*/ +void CL_Reconnect_f( void ) { + if( cls.state >= ca_connected ) { + cls.state = ca_connected; + + if ( cls.demoplayback ) { + return; + } + if ( cls.download ) { + return; // if we are downloading, we don't change! + } + + Com_Printf( "Reconnecting...\n" ); + + CL_ClientCommand( "new" ); + return; + } + + if( cls.serverAddress.type == NA_BAD ) { + Com_Printf( "No server to reconnect to.\n" ); + return; + } + if( cls.serverAddress.type == NA_LOOPBACK ) { + Com_Printf( "Can not reconnect to loopback.\n" ); + return; + } + + Com_Printf( "Reconnecting...\n" ); + + cls.connect_time = -9999; + cls.state = ca_challenging; + + SCR_UpdateScreen(); +} + +#if 0 +/* +================= +CL_ParseStatusMessage + +Handle a reply from a ping +================= +*/ +void CL_ParseStatusMessage ( void ) {} +#endif + +/* +================= +CL_SendStatusRequest +================= +*/ +qboolean CL_SendStatusRequest( char *buffer, int bufferSize ) { + netadr_t address; + + memset( &address, 0, sizeof( address ) ); + + NET_Config( NET_CLIENT ); + + // send a broadcast packet + if ( !strcmp( buffer, "broadcast" ) ) { + address.type = NA_BROADCAST; + address.port = BigShort( PORT_SERVER ); + } else { + if ( !NET_StringToAdr( buffer, &address ) ) { + return qfalse; + } + + if ( !address.port ) { + address.port = BigShort( PORT_SERVER ); + } + + Q_strncpyz( buffer, NET_AdrToString( &address ), bufferSize ); + } + + CL_AddRequest( &address, REQ_PING ); + + Netchan_OutOfBandPrint( NS_CLIENT, &address, "status" ); + + Com_ProcessEvents(); + + return qtrue; + +} + + +/* +================= +CL_PingServers_f +================= +*/ +static void CL_PingServers_f( void ) { + int i; + char buffer[ 32 ]; + char *adrstring; + netadr_t address; + + memset( &address, 0, sizeof( address ) ); + + NET_Config( NET_CLIENT ); + + // send a broadcast packet + Com_Printf( "pinging broadcast...\n" ); + address.type = NA_BROADCAST; + address.port = BigShort( PORT_SERVER ); + + CL_AddRequest( &address, REQ_STATUS ); + + Netchan_OutOfBandPrint( NS_CLIENT, &address, "status" ); + + SCR_UpdateScreen(); + + // send a packet to each address book entry + for ( i = 0; i < MAX_LOCAL_SERVERS; i++ ) { + Com_sprintf( buffer, sizeof( buffer ), "adr%i", i ); + adrstring = Cvar_VariableString( buffer ); + if ( !adrstring[ 0 ] ) + continue; + + if ( !NET_StringToAdr( adrstring, &address ) ) { + Com_Printf( "bad address: %s\n", adrstring ); + continue; + } + + if ( !address.port ) { + address.port = BigShort( PORT_SERVER ); + } + + Com_Printf( "pinging %s...\n", adrstring ); + CL_AddRequest( &address, REQ_STATUS ); + + Netchan_OutOfBandPrint( NS_CLIENT, &address, "status" ); + + Com_ProcessEvents(); + SCR_UpdateScreen(); + + } +} + +/* +================= +CL_Skins_f + +Load or download any custom player skins and models +================= +*/ +void CL_Skins_f ( void ) { + int i; + char *s; + + for ( i = 0 ; i < MAX_CLIENTS ; i++ ) { + s = cl.configstrings[ CS_PLAYERSKINS + i ]; + if( !s[0] ) + continue; + Com_Printf ( "client %i: %s\n", i, s ); + SCR_UpdateScreen (); + CL_ParseClientinfo ( i ); + } +} + +/* +================= +CL_ConnectionlessPacket + +Responses to broadcasts, etc +================= +*/ +static void CL_ConnectionlessPacket( void ) { + char *s; + char *c; + int i, j, k; + + MSG_BeginReading(); + MSG_ReadLong(); // skip the -1 + + s = MSG_ReadStringLine(); + + Cmd_TokenizeString( s, qfalse ); + + c = Cmd_Argv( 0 ); + + Com_DPrintf( "%s: %s\n", NET_AdrToString ( &net_from ), s ); + + // challenge from the server we are connecting to + if ( !strcmp( c, "challenge" ) ) { + qboolean proto35 = qfalse, proto36 = qfalse; + + if ( cls.state < ca_challenging ) { + Com_DPrintf( "Challenge received while not connecting. Ignored.\n" ); + return; + } + if ( !NET_IsEqualBaseAdr( &net_from, &cls.serverAddress ) ) { + Com_DPrintf( "Challenge from different address. Ignored.\n" ); + return; + } + if ( cls.state > ca_challenging ) { + Com_DPrintf( "Dup challenge received. Ignored.\n" ); + return; + } + + cls.challenge = atoi( Cmd_Argv( 1 ) ); + cls.state = ca_connecting; + cls.connect_time = -9999; + cls.connectCount = 0; + + /* parse additional parameters */ + j = Cmd_Argc(); + for( i = 2; i < j; i++ ) { + s = Cmd_Argv( i ); + if( !strncmp( s, "p=", 2 ) ) { + s += 2; + while( *s ) { + k = atoi( s ); + if( k == PROTOCOL_VERSION_R1Q2 ) { + proto35 = qtrue; + } else if( k == PROTOCOL_VERSION_Q2PRO ) { + proto36 = qtrue; + } + s = strchr( s, ',' ); + if( s == NULL ) { + break; + } + s++; + } + } + } + + /* select the 'best' protocol available, unless told otherwise */ + if( cls.serverProtocol == 0 || + ( cls.serverProtocol == PROTOCOL_VERSION_R1Q2 && !proto35 ) || + ( cls.serverProtocol == PROTOCOL_VERSION_Q2PRO && !proto36 ) ) + { + if( proto36 ) { + cls.serverProtocol = PROTOCOL_VERSION_Q2PRO; + } else if( proto35 ) { + cls.serverProtocol = PROTOCOL_VERSION_R1Q2; + } else { + cls.serverProtocol = PROTOCOL_VERSION_DEFAULT; + } + } + Com_DPrintf( "Selected protocol %d\n", cls.serverProtocol ); + + cls.messageString[0] = 0; + + CL_CheckForResend(); + return; + } + + // server connection + if ( !strcmp( c, "client_connect" ) ) { + netchan_type_t type; + int anticheat = 0; + + if ( cls.state < ca_connecting ) { + Com_DPrintf( "Connect received while not connecting. Ignored.\n" ); + return; + } + if ( !NET_IsEqualBaseAdr( &net_from, &cls.serverAddress ) ) { + Com_DPrintf( "Connect from different address. Ignored.\n" ); + return; + } + if ( cls.state > ca_connecting ) { + Com_DPrintf( "Dup connect received. Ignored.\n" ); + return; + } + + if ( cls.serverProtocol == PROTOCOL_VERSION_Q2PRO ) { + type = NETCHAN_NEW; + } else { + type = NETCHAN_OLD; + } + + /* parse additional parameters */ + j = Cmd_Argc(); + for( i = 1; i < j; i++ ) { + s = Cmd_Argv( i ); + if( !strncmp( s, "ac=", 3 ) ) { + s += 3; + if( *s ) { + anticheat = atoi( s ); + } + } else if( !strncmp( s, "nc=", 3 ) ) { + s += 3; + if( *s ) { + type = atoi( s ); + if( type != NETCHAN_OLD && type != NETCHAN_NEW ) { + Com_Error( ERR_DISCONNECT, + "Server returned invalid netchan type" ); + } + } + } + } + + Com_Printf( "Connection to %s established (protocol %d).\n", + NET_AdrToString( &cls.serverAddress ), cls.serverProtocol ); + if( cls.netchan ) { + // this may happen after svc_reconnect + Netchan_Close( cls.netchan ); + } + cls.netchan = Netchan_Setup( NS_CLIENT, type, &cls.serverAddress, + cls.quakePort, 1024, cls.serverProtocol ); + +#ifdef USE_ANTICHEAT + if( anticheat ) { + MSG_WriteByte( clc_nop ); + MSG_FlushTo( &cls.netchan->message ); + cls.netchan->Transmit( cls.netchan, 0, NULL ); + S_StopAllSounds(); + Com_Printf( "Loading anticheat, this may take a few moments...\n" ); + SCR_UpdateScreen(); + if( !Sys_GetAntiCheatAPI() ) { + Com_Printf( "Trying to connect without anticheat.\n" ); + } else { + Com_Printf( S_COLOR_CYAN "Anticheat loaded successfully.\n" ); + } + } +#else + if( anticheat >= 2 ) { + Com_Printf( "Anticheat required by server, " + "but no anticheat support linked in.\n" ); + } +#endif + + CL_ClientCommand( "new" ); + cls.state = ca_connected; + cls.messageString[0] = 0; + return; + } + +#if 0 + // server responding to a status broadcast + if ( !strcmp( c, "info" ) ) { + CL_ParseStatusMessage(); + return; + } +#endif + + if ( !strcmp( c, "passive_connect" ) ) { + if( !cls.passive ) { + Com_DPrintf( "Passive connect received while not connecting. Ignored.\n" ); + return; + } + s = NET_AdrToString( &net_from ); + Com_Printf( "Received passive connect from %s.\n", s ); + + cls.serverAddress = net_from; + cls.serverProtocol = cl_protocol->integer; + Q_strncpyz( cls.servername, s, sizeof( cls.servername ) ); + cls.passive = qfalse; + + cls.state = ca_challenging; + cls.connect_time = -9999; + cls.connectCount = 0; + + CL_CheckForResend(); + return; + } + + // print command from somewhere + if ( !strcmp( c, "print" ) ) { + CL_ParsePrintMessage(); + return; + } + + Com_DPrintf( "Unknown connectionless packet command.\n" ); +} + + +/* +================= +CL_PacketEvent +================= +*/ +void CL_PacketEvent( neterr_t ret ) { + // + // remote command packet + // + if ( ret == NET_OK && *( int * )msg_read.data == -1 ) { + CL_ConnectionlessPacket(); + return; + } + + if( cls.state < ca_connected ) { + return; + } + + if ( !cls.netchan ) { + return; // dump it if not connected + } + + if ( ret == NET_OK && msg_read.cursize < 8 ) { + Com_DPrintf( "%s: runt packet\n", NET_AdrToString( &net_from ) ); + return; + } + + // + // packet from server + // + if ( !NET_IsEqualAdr( &net_from, &cls.netchan->remote_address ) ) { + Com_DPrintf( "%s: sequenced packet without connection\n", + NET_AdrToString( &net_from ) ); + return; + } + + if( ret == NET_ERROR ) { + Com_Error( ERR_DISCONNECT, "Connection reset by peer" ); + } + + if ( !cls.netchan->Process( cls.netchan ) ) + return; // wasn't accepted for some reason + + + CL_ParseServerMessage(); + + CL_AddNetgraph(); + + SCR_AddLagometerPacketInfo(); +} + + +//============================================================================= + +/* +============== +CL_FixUpGender_f +============== +*/ +static void CL_FixUpGender( void ) { + char *p; + char sk[MAX_QPATH]; + + Q_strncpyz( sk, info_skin->string, sizeof( sk ) ); + if ( ( p = strchr( sk, '/' ) ) != NULL ) + *p = 0; + if ( Q_stricmp( sk, "male" ) == 0 || Q_stricmp( sk, "cyborg" ) == 0 ) + Cvar_Set ( "gender", "male" ); + else if ( Q_stricmp( sk, "female" ) == 0 || Q_stricmp( sk, "crackhor" ) == 0 ) + Cvar_Set ( "gender", "female" ); + else + Cvar_Set ( "gender", "none" ); + info_gender->modified = qfalse; +} + +void CL_UpdateUserinfo( cvar_t *var, cvarSetSource_t source ) { + int i; + + if( var == info_skin && source != CVAR_SET_CONSOLE && + gender_auto->integer ) + { + CL_FixUpGender(); + } + if( !cls.netchan ) { + return; + } + if( cls.serverProtocol != PROTOCOL_VERSION_Q2PRO ) { + // transmit at next oportunity + cls.userinfo_modified = MAX_PACKET_USERINFOS; + return; + } + + if( cls.userinfo_modified == MAX_PACKET_USERINFOS ) { + return; // can't hold any more + } + + // check for the same variable being modified twice + for( i = 0; i < cls.userinfo_modified; i++ ) { + if( cls.userinfo_updates[i] == var ) { + Com_DPrintf( "Dup modified %s at frame %u\n", var->name, com_framenum ); + return; + } + } + + Com_DPrintf( "Modified %s at frame %u\n", var->name, com_framenum ); + + cls.userinfo_updates[cls.userinfo_modified++] = var; +} + +/* +============== +CL_Userinfo_f +============== +*/ +void CL_Userinfo_f ( void ) { + Com_Printf ( "User info settings:\n" ); + Info_Print ( Cvar_Userinfo() ); +} + +/* +====================== +CL_RegisterSounds +====================== +*/ +void CL_RegisterSounds( void ) { + int i; + char *s; + + SCR_LoadingString( "sounds" ); + + S_BeginRegistration (); + CL_RegisterTEntSounds (); + for ( i = 1; i < MAX_SOUNDS; i++ ) { + s = cl.configstrings[ CS_SOUNDS + i ]; + if ( !s[ 0 ] ) + break; + cl.sound_precache[ i ] = S_RegisterSound( s ); + } + S_EndRegistration (); +} + +/* +================= +CL_Snd_Restart_f + +Restart the sound subsystem so it can pick up +new parameters and flush all sounds +================= +*/ +void CL_Snd_Restart_f ( void ) { + S_Shutdown (); + S_Init (); + CL_RegisterSounds (); +} + +int precache_check; // for autodownload of precache items +int precache_spawncount; +int precache_tex; +int precache_model_skin; + +byte *precache_model; // used for skin checking in alias models + +#define PLAYER_MULT 5 + +// ENV_CNT is map load, ENV_CNT+1 is first env map +#define ENV_CNT (CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT) +#define TEXTURE_CNT (ENV_CNT+13) + +static const char *env_suf[ 6 ] = {"rt", "bk", "lf", "ft", "up", "dn" + }; + +void CL_RequestNextDownload ( void ) { + unsigned map_checksum; // for detecting cheater maps + char fn[ MAX_QPATH ]; + dmdl_t *pheader; + int length; + + if ( cls.state != ca_connected && cls.state != ca_loading ) + return; + + if ( ( !allow_download->integer || NET_IsLocalAddress( &cls.serverAddress )) && precache_check < ENV_CNT ) + precache_check = ENV_CNT; + + //ZOID + if ( precache_check == CS_MODELS ) { // confirm map + precache_check = CS_MODELS + 2; // 0 isn't used + if ( allow_download_maps->integer ) + if ( !CL_CheckOrDownloadFile( cl.configstrings[ CS_MODELS + 1 ] ) ) + return; // started a download + } + if ( precache_check >= CS_MODELS && precache_check < CS_MODELS + MAX_MODELS ) { + if ( allow_download_models->integer ) { + while ( precache_check < CS_MODELS + MAX_MODELS && + cl.configstrings[ precache_check ][ 0 ] ) { + int num_skins, ofs_skins; + + if ( cl.configstrings[ precache_check ][ 0 ] == '*' || + cl.configstrings[ precache_check ][ 0 ] == '#' ) { + precache_check++; + continue; + } + if ( precache_model_skin == 0 ) { + if ( !CL_CheckOrDownloadFile( cl.configstrings[ precache_check ] ) ) { + precache_model_skin = 1; + return; // started a download + } + precache_model_skin = 1; + } + + // checking for skins in the model + if ( !precache_model ) { + length = FS_LoadFile ( cl.configstrings[ precache_check ], ( void ** ) & precache_model ); + if ( !precache_model ) { + precache_model_skin = 0; + precache_check++; + continue; // couldn't load it + } + pheader = ( dmdl_t * ) precache_model; + if( length < sizeof( *pheader ) || + LittleLong( pheader->ident ) != IDALIASHEADER || + LittleLong ( pheader->version ) != ALIAS_VERSION ) + { + // not an alias model + FS_FreeFile( precache_model ); + precache_model = 0; + precache_model_skin = 0; + precache_check++; + continue; + } + num_skins = LittleLong( pheader->num_skins ); + ofs_skins = LittleLong( pheader->ofs_skins ); + if( ofs_skins + num_skins * MAX_SKINNAME > length ) { + // bad alias model + FS_FreeFile( precache_model ); + precache_model = 0; + precache_model_skin = 0; + precache_check++; + continue; + } + } + + pheader = ( dmdl_t * ) precache_model; + num_skins = LittleLong( pheader->num_skins ); + ofs_skins = LittleLong( pheader->ofs_skins ); + + while ( precache_model_skin - 1 < num_skins ) { + Q_strncpyz( fn, ( char * )precache_model + ofs_skins + + ( precache_model_skin - 1 ) * MAX_SKINNAME, sizeof( fn ) ); + if ( !CL_CheckOrDownloadFile( fn ) ) { + precache_model_skin++; + return; // started a download + } + precache_model_skin++; + } + if ( precache_model ) { + FS_FreeFile( precache_model ); + precache_model = 0; + } + precache_model_skin = 0; + precache_check++; + } + } + precache_check = CS_SOUNDS; + } + if ( precache_check >= CS_SOUNDS && precache_check < CS_SOUNDS + MAX_SOUNDS ) { + if ( allow_download_sounds->integer ) { + if ( precache_check == CS_SOUNDS ) + precache_check++; // zero is blank + while ( precache_check < CS_SOUNDS + MAX_SOUNDS && + cl.configstrings[ precache_check ][ 0 ] ) { + if ( cl.configstrings[ precache_check ][ 0 ] == '*' ) { + precache_check++; + continue; + } + Com_sprintf( fn, sizeof( fn ), "sound/%s", cl.configstrings[ precache_check++ ] ); + if ( !CL_CheckOrDownloadFile( fn ) ) + return; // started a download + } + } + precache_check = CS_IMAGES; + } + if ( precache_check >= CS_IMAGES && precache_check < CS_IMAGES + MAX_IMAGES ) { + if ( precache_check == CS_IMAGES ) + precache_check++; // zero is blank + while ( precache_check < CS_IMAGES + MAX_IMAGES && + cl.configstrings[ precache_check ][ 0 ] ) { + Com_sprintf( fn, sizeof( fn ), "pics/%s.pcx", cl.configstrings[ precache_check++ ] ); + if ( !CL_CheckOrDownloadFile( fn ) ) + return; // started a download + } + precache_check = CS_PLAYERSKINS; + } + // skins are special, since a player has three things to download: + // model, weapon model and skin + // so precache_check is now *3 + if ( precache_check >= CS_PLAYERSKINS && precache_check < CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT ) { + if ( allow_download_players->integer ) { + while ( precache_check < CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT ) { + int i, n; + char model[ MAX_QPATH ], skin[ MAX_QPATH ], *p; + + i = ( precache_check - CS_PLAYERSKINS ) / PLAYER_MULT; + n = ( precache_check - CS_PLAYERSKINS ) % PLAYER_MULT; + + if ( !cl.configstrings[ CS_PLAYERSKINS + i ][ 0 ] ) { + precache_check = CS_PLAYERSKINS + ( i + 1 ) * PLAYER_MULT; + continue; + } + + if ( ( p = strchr( cl.configstrings[ CS_PLAYERSKINS + i ], '\\' ) ) != NULL ) + p++; + else + p = cl.configstrings[ CS_PLAYERSKINS + i ]; + Q_strncpyz( model, p, sizeof( model ) ); + p = strchr( model, '/' ); + if ( !p ) + p = strchr( model, '\\' ); + if ( p ) { + *p++ = 0; + strcpy( skin, p ); + } else + *skin = 0; + + switch ( n ) { + case 0: // model + Com_sprintf( fn, sizeof( fn ), "players/%s/tris.md2", model ); + if ( !CL_CheckOrDownloadFile( fn ) ) { + precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 1; + return; // started a download + } + n++; + /*FALL THROUGH*/ + + case 1: // weapon model + Com_sprintf( fn, sizeof( fn ), "players/%s/weapon.md2", model ); + if ( !CL_CheckOrDownloadFile( fn ) ) { + precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 2; + return; // started a download + } + n++; + /*FALL THROUGH*/ + + case 2: // weapon skin + Com_sprintf( fn, sizeof( fn ), "players/%s/weapon.pcx", model ); + if ( !CL_CheckOrDownloadFile( fn ) ) { + precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 3; + return; // started a download + } + n++; + /*FALL THROUGH*/ + + case 3: // skin + Com_sprintf( fn, sizeof( fn ), "players/%s/%s.pcx", model, skin ); + if ( !CL_CheckOrDownloadFile( fn ) ) { + precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 4; + return; // started a download + } + n++; + /*FALL THROUGH*/ + + case 4: // skin_i + Com_sprintf( fn, sizeof( fn ), "players/%s/%s_i.pcx", model, skin ); + if ( !CL_CheckOrDownloadFile( fn ) ) { + precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 5; + return; // started a download + } + // move on to next model + precache_check = CS_PLAYERSKINS + ( i + 1 ) * PLAYER_MULT; + } + } + } + // precache phase completed + precache_check = ENV_CNT; + } + + if ( precache_check == ENV_CNT ) { + precache_check = ENV_CNT + 1; + SCR_LoadingString( "collision map" ); + + CM_LoadMap ( &cl.cm, cl.configstrings[ CS_MODELS + 1 ], CM_LOAD_CLIENT, &map_checksum ); + +#if 0 + if ( map_checksum != atoi( cl.configstrings[ CS_MAPCHECKSUM ] ) ) { + Com_Error ( ERR_DROP, "Local map version differs from server: %i != '%s'\n", + map_checksum, cl.configstrings[ CS_MAPCHECKSUM ] ); + return; + } +#endif + } + + if ( precache_check > ENV_CNT && precache_check < TEXTURE_CNT ) { + if ( allow_download->integer && allow_download_maps->integer ) { + while ( precache_check < TEXTURE_CNT ) { + int n = precache_check++ - ENV_CNT - 1; + + if ( n & 1 ) + Com_sprintf( fn, sizeof( fn ), "env/%s%s.pcx", + cl.configstrings[ CS_SKY ], env_suf[ n / 2 ] ); + else + Com_sprintf( fn, sizeof( fn ), "env/%s%s.tga", + cl.configstrings[ CS_SKY ], env_suf[ n / 2 ] ); + if ( !CL_CheckOrDownloadFile( fn ) ) + return; // started a download + } + } + precache_check = TEXTURE_CNT; + } + + if ( precache_check == TEXTURE_CNT ) { + precache_check = TEXTURE_CNT + 1; + precache_tex = 0; + } + + // confirm existance of textures, download any that don't exist + if ( precache_check == TEXTURE_CNT + 1 ) { + if ( allow_download->integer && allow_download_maps->integer ) { + while ( precache_tex < cl.cm.cache->numtexinfo ) { + char *texname = cl.cm.cache->surfaces[ precache_tex++ ].rname; + + // Also check if 32bit images are present + Com_sprintf( fn, sizeof( fn ), "textures/%s.jpg", texname ); + if ( FS_LoadFile( fn, NULL ) == -1 ) { + Com_sprintf( fn, sizeof( fn ), "textures/%s.tga", texname ); + if ( FS_LoadFile( fn, NULL ) == -1 ) { + Com_sprintf( fn, sizeof( fn ), "textures/%s.wal", texname ); + if ( !CL_CheckOrDownloadFile( fn ) ) { + return; // started a download + } + } + } + } + } + precache_check = TEXTURE_CNT + 999; + } + + + //ZOID + CL_RegisterSounds (); + CL_PrepRefresh (); + + LOC_LoadLocations(); + + CL_ClientCommand( va( "begin %i\n", precache_spawncount ) ); + + Cvar_FixCheats(); + + CL_UpdateGunSetting(); + CL_UpdateGibSetting(); + CL_UpdateFootstepsSetting(); + CL_UpdatePredictSetting(); + CL_UpdateLocalFovSetting(); + + cls.state = ca_precached; +} + +/* +================= +CL_Precache_f + +The server will send this command right +before allowing the client into the server +================= +*/ +static void CL_Precache_f( void ) { + if ( cls.state < ca_connected ) { + return; + } + + cls.state = ca_loading; + + S_StopAllSounds(); + + //Yet another hack to let old demos work + //the old precache sequence + if ( cls.demoplayback || Cmd_Argc() < 2 ) { + uint32 map_checksum; // for detecting cheater maps + + SCR_LoadingString( "collision map" ); + CM_LoadMap( &cl.cm, cl.configstrings[ CS_MODELS + 1 ], + CM_LOAD_CLIENT, &map_checksum ); + CL_RegisterSounds(); + CL_PrepRefresh(); + cls.state = ca_precached; + return; + } + + precache_check = CS_MODELS; + precache_spawncount = atoi( Cmd_Argv( 1 ) ); + precache_model = 0; + precache_model_skin = 0; + + CL_RequestNextDownload(); + + if( cls.state != ca_precached ) { + cls.state = ca_connected; + } +} + + +static void CL_DumpClients_f( void ) { + int i; + + if ( cls.state != ca_active ) { + Com_Printf( "Must be in a level to dump\n" ); + return; + } + + for ( i = 0 ; i < MAX_CLIENTS ; i++ ) { + if ( !cl.clientinfo[ i ].name[ 0 ] ) { + continue; + } + + Com_Printf( "%3i: %s\n", i, cl.clientinfo[ i ].name ); + } +} + +static void CL_DumpStatusbar_f( void ) { + char buffer[MAX_QPATH]; + fileHandle_t f; + + if ( cls.state != ca_active ) { + Com_Printf( "Must be in a level to dump.\n" ); + return; + } + + if( Cmd_Argc() != 2 ) { + Com_Printf( "Usage: %s <filename>\n", Cmd_Argv( 0 ) ); + return; + } + + Cmd_ArgvBuffer( 1, buffer, sizeof( buffer ) ); + COM_DefaultExtension( buffer, ".txt", sizeof( buffer ) ); + + FS_FOpenFile( buffer, &f, FS_MODE_WRITE ); + if( !f ) { + Com_EPrintf( "Couldn't open %s for writing.\n", buffer ); + return; + } + + FS_FPrintf( f, "// status bar program dump of '%s' mod\n", + Cvar_VariableString( "gamedir" ) ); + FS_FPrintf( f, "%s\n", cl.configstrings[CS_STATUSBAR] ); + + FS_FCloseFile( f ); + + Com_Printf( "Dumped status bar program to %s.\n", buffer ); +} + +static void CL_DumpLayout_f( void ) { + char buffer[MAX_QPATH]; + fileHandle_t f; + + if ( cls.state != ca_active ) { + Com_Printf( "Must be in a level to dump.\n" ); + return; + } + + if( Cmd_Argc() != 2 ) { + Com_Printf( "Usage: %s <filename>\n", Cmd_Argv( 0 ) ); + return; + } + + if( !cl.layout[0] ) { + Com_Printf( "No layout to dump.\n" ); + return; + } + + Cmd_ArgvBuffer( 1, buffer, sizeof( buffer ) ); + COM_DefaultExtension( buffer, ".txt", sizeof( buffer ) ); + + FS_FOpenFile( buffer, &f, FS_MODE_WRITE ); + if( !f ) { + Com_EPrintf( "Couldn't open %s for writing.\n", buffer ); + return; + } + + FS_FPrintf( f, "// layout program dump of '%s' mod\n", + Cvar_VariableString( "gamedir" ) ); + FS_FPrintf( f, "%s\n", cl.layout ); + + FS_FCloseFile( f ); + + Com_Printf( "Dumped layout program to %s.\n", buffer ); +} + +/* +==================== +CL_Mapname_m +==================== +*/ +static void CL_Mapname_m( char *buffer, int bufferSize ) { + if ( !cl.mapname[ 0 ] ) { + Q_strncpyz( buffer, "nomap", bufferSize ); + return; + } + + Q_strncpyz( buffer, cl.mapname, bufferSize ); +} + +/* +==================== +CL_Server_m +==================== +*/ +static void CL_Server_m( char *buffer, int bufferSize ) { + if ( cls.state <= ca_disconnected ) { + Q_strncpyz( buffer, "noserver", bufferSize ); + return; + } + + Q_strncpyz( buffer, cls.servername, bufferSize ); +} + +/* +==================== +CL_DemoState_m +==================== +*/ +static void CL_DemoState_m( char *buffer, int bufferSize ) { + Q_strncpyz( buffer, "0", bufferSize ); + + if ( cls.state < ca_connected ) { + return; + } + + if ( cls.demorecording ) { + Q_strncpyz( buffer, "1", bufferSize ); + } else if ( cls.demoplayback ) { + Q_strncpyz( buffer, "2", bufferSize ); + } +} + +/* +============== +CL_Ups_m +============== +*/ +static void CL_Ups_m( char *buffer, int bufferSize ) { + vec3_t vel; + int ups; + player_state_t *ps; + + if( cl.frame.clientNum == CLIENTNUM_NONE ) { + buffer[0] = 0; + return; + } + + if( !cls.demoplayback && cl.frame.clientNum == cl.clientNum && + cl_predict->integer ) + { + VectorCopy( cl.predicted_velocity, vel ); + } else { + ps = &cl.frame.ps; + + vel[0] = ps->pmove.velocity[0] * 0.125f; + vel[1] = ps->pmove.velocity[1] * 0.125f; + vel[2] = ps->pmove.velocity[2] * 0.125f; + } + + ups = VectorLength( vel ); + Com_sprintf( buffer, bufferSize, "%d", ups ); +} + +/* +============== +CL_Timer_m +============== +*/ +static void CL_Timer_m( char *buffer, int bufferSize ) { + int hour, min, sec; + + sec = cl.time / 1000; + min = sec / 60; + hour = min / 60; + min %= 60; + sec %= 60; + + if( hour ) { + Com_sprintf( buffer, bufferSize, "%i:%i:%02i", hour, min, sec ); + } else { + Com_sprintf( buffer, bufferSize, "%i:%02i", min, sec ); + } + +} + +static void CL_Fps_m( char *buffer, int bufferSize ) { + Com_sprintf( buffer, bufferSize, "%i", cls.currentFPS ); +} + +static void CL_Health_m( char *buffer, int bufferSize ) { + Com_sprintf( buffer, bufferSize, "%i", cl.frame.ps.stats[STAT_HEALTH] ); +} +static void CL_Ammo_m( char *buffer, int bufferSize ) { + Com_sprintf( buffer, bufferSize, "%i", cl.frame.ps.stats[STAT_AMMO] ); +} +static void CL_Armor_m( char *buffer, int bufferSize ) { + Com_sprintf( buffer, bufferSize, "%i", cl.frame.ps.stats[STAT_ARMOR] ); +} + +/* +==================== +CL_RestartFilesystem + +Flush caches and restart the VFS. +==================== +*/ +void CL_RestartFilesystem( void ) { + int cls_state; + + if ( !cl_running->integer ) { + FS_Restart(); + return; + } + + Com_DPrintf( "CL_RestartFilesystem()\n" ); + + /* temporary switch to loading state */ + cls_state = cls.state; + if ( cls.state >= ca_precached ) { + cls.state = ca_loading; + } + + UI_OpenMenu( UIMENU_NONE ); + CL_ShutdownUI(); + + S_StopAllSounds(); + S_FreeAllSounds(); + + ref.Shutdown( qfalse ); + + FS_Restart(); + + ref.Init( qfalse ); + + SCR_RegisterMedia(); + Con_SetupDC(); + CL_InitUI(); + + if ( cls_state == ca_disconnected ) { + UI_OpenMenu( UIMENU_MAIN ); + } else if ( cls_state >= ca_loading ) { + CL_RegisterSounds(); + CL_PrepRefresh(); + } + + /* switch back to original state */ + cls.state = cls_state; + +} + +/* +==================== +CL_RestartRefresh +==================== +*/ +void CL_RestartRefresh_f( void ) { + int cls_state; + + Com_DPrintf( "CL_RestartRefresh()\n" ); + + /* temporary switch to loading state */ + cls_state = cls.state; + if ( cls.state >= ca_precached ) { + cls.state = ca_loading; + } + + UI_OpenMenu( UIMENU_NONE ); + CL_ShutdownUI(); + + S_StopAllSounds(); + + CL_ShutdownInput(); + CL_ShutdownRefresh(); + + CL_InitRefresh(); + CL_InitInput(); + + SCR_RegisterMedia(); + Con_SetupDC(); + CL_InitUI(); + + if ( cls_state == ca_disconnected ) { + UI_OpenMenu( UIMENU_MAIN ); + } else if ( cls_state >= ca_loading ) { + CL_PrepRefresh(); + } + + /* switch back to original state */ + cls.state = cls_state; + +} + +/* +============ +CL_LocalConnect +============ +*/ +void CL_LocalConnect( void ) { + if ( FS_NeedRestart() ) { + cls.state = ca_challenging; + CL_RestartFilesystem(); + } +} + +/* +============ +CL_RestartFilesystem_f + +Console command to re-start the file system. +============ +*/ +static void CL_RestartFilesystem_f( void ) { + if( !FS_SafeToRestart() ) { + Com_Printf( "Can't \"%s\", there are some open file handles.\n", Cmd_Argv( 0 ) ); + return; + } + + CL_RestartFilesystem(); +} + +static void cl_gun_changed( cvar_t *self ) { + CL_UpdateGunSetting(); +} + +static void info_hand_changed( cvar_t *self ) { + CL_UpdateGunSetting(); +} + +static void cl_gibs_changed( cvar_t *self ) { + CL_UpdateGibSetting(); +} + +static void cl_footsteps_changed( cvar_t *self ) { + CL_UpdateFootstepsSetting(); +} + +static void cl_predict_changed( cvar_t *self ) { + CL_UpdatePredictSetting(); +} + +static void CL_GetClientStatus( clientStatus_t *status ) { + if( !status ) { + Com_Error( ERR_DROP, "CL_GetClientStatus: NULL" ); + } + status->connState = cls.state; + status->connectCount = cls.connectCount; + status->demoplayback = cls.demoplayback; + status->servername = cls.servername; + status->mapname = cl.mapname; + status->fullname = cl.configstrings[CS_NAME]; + status->loadingString = cls.state > ca_connected ? + cl.loadingString : cls.messageString; +} + +void CL_FillAPI( clientAPI_t *api ) { + api->StartLocalSound = S_StartLocalSound; + api->StopAllSounds = S_StopAllSounds; + api->SendStatusRequest = CL_SendStatusRequest; + api->GetClientStatus = CL_GetClientStatus; + api->GetDemoInfo = CL_GetDemoInfo; + api->UpdateScreen = SCR_UpdateScreen; +} + +static const cmdreg_t c_client[] = { + { "cmd", CL_ForwardToServer_f }, + { "pause", CL_Pause_f }, + { "pingservers", CL_PingServers_f }, + { "skins", CL_Skins_f }, + { "userinfo", CL_Userinfo_f }, + { "snd_restart", CL_Snd_Restart_f }, + { "changing", CL_Changing_f }, + { "disconnect", CL_Disconnect_f }, + { "connect", CL_Connect_f, CL_Connect_g }, + { "passive", CL_PassiveConnect_f }, + { "reconnect", CL_Reconnect_f }, + { "rcon", CL_Rcon_f }, + { "setenv", CL_Setenv_f }, + { "precache", CL_Precache_f }, + { "download", CL_Download_f }, + { "serverstatus", CL_ServerStatus_f, CL_Connect_g }, + { "dumpclients", CL_DumpClients_f }, + { "dumpstatusbar", CL_DumpStatusbar_f }, + { "dumplayout", CL_DumpLayout_f }, + { "vid_restart", CL_RestartRefresh_f }, + { "fs_restart", CL_RestartFilesystem_f }, + + // + // forward to server commands + // + // the only thing this does is allow command completion + // to work -- all unknown commands are automatically + // forwarded to the server + { "wave" }, { "inven" }, { "kill" }, { "use" }, + { "drop" }, { "say" }, { "say_team" }, { "info" }, + { "prog" }, { "give" }, { "god" }, { "notarget" }, + { "noclip" }, { "invuse" }, { "invprev" }, { "invnext" }, + { "invdrop" }, { "weapnext" }, { "weapprev" }, { "vote" }, + { "observe" }, { "follow" }, { "time" }, { "playernext" }, + { "playerprev" }, { "playertoggle" }, { "!mvdadmin" }, + + { NULL } +}; + +/* +================= +CL_InitLocal +================= +*/ +void CL_InitLocal ( void ) { + int i; + + cls.state = ca_disconnected; + cls.realtime = 0; + + CL_FillAPI( &client ); + + CL_RegisterInput(); + + CL_InitDemos(); + + LOC_Init(); + + Cmd_Register( c_client ); + + for ( i = 0 ; i < MAX_LOCAL_SERVERS ; i++ ) { + Cvar_Get( va( "adr%i", i ), "", CVAR_ARCHIVE ); + } + + // + // register our variables + // + cl_add_blend = Cvar_Get ( "cl_blend", "1", CVAR_ARCHIVE ); + cl_add_lights = Cvar_Get ( "cl_lights", "1", 0 ); + cl_add_particles = Cvar_Get ( "cl_particles", "1", 0 ); + cl_add_entities = Cvar_Get ( "cl_entities", "1", 0 ); + cl_gun = Cvar_Get ( "cl_gun", "1", 0 ); + cl_gun->changed = cl_gun_changed; + cl_footsteps = Cvar_Get( "cl_footsteps", "1", 0 ); + cl_footsteps->changed = cl_footsteps_changed; + cl_noskins = Cvar_Get ( "cl_noskins", "0", 0 ); + cl_autoskins = Cvar_Get ( "cl_autoskins", "0", 0 ); + cl_predict = Cvar_Get ( "cl_predict", "1", 0 ); + cl_predict->changed = cl_predict_changed; + cl_kickangles = Cvar_Get( "cl_kickangles", "1", CVAR_CHEAT ); + cl_maxfps = Cvar_Get( "cl_maxfps", "60", CVAR_ARCHIVE ); + cl_async = Cvar_Get( "cl_async", "1", CVAR_ARCHIVE ); + r_maxfps = Cvar_Get( "r_maxfps", "0", CVAR_ARCHIVE ); + + cl_shownet = Cvar_Get( "cl_shownet", "0", 0 ); + cl_showmiss = Cvar_Get ( "cl_showmiss", "0", 0 ); + cl_showclamp = Cvar_Get ( "showclamp", "0", 0 ); + cl_timeout = Cvar_Get ( "cl_timeout", "120", 0 ); + + rcon_password = Cvar_Get ( "rcon_password", "", CVAR_PRIVATE ); + rcon_address = Cvar_Get ( "rcon_address", "", CVAR_PRIVATE ); + + cl_thirdperson = Cvar_Get( "cl_thirdperson", "0", CVAR_CHEAT ); + cl_thirdperson_angle = Cvar_Get( "cl_thirdperson_angle", "0", 0 ); + cl_thirdperson_range = Cvar_Get( "cl_thirdperson_range", "60", 0 ); + + cl_railtrail_type = Cvar_Get( "cl_railtrail_type", "0", 0 ); + cl_railtrail_time = Cvar_Get( "cl_railtrail_time", "1.0", 0 ); + cl_railtrail_alpha = Cvar_Get( "cl_railtrail_alpha", "1.0", 0 ); + cl_railcore_color = Cvar_Get( "cl_railcore_color", "0xFF0000", 0 ); + cl_railcore_width = Cvar_Get( "cl_railcore_width", "3", 0 ); + cl_railrings_color = Cvar_Get( "cl_railrings_color", "0x0000FF", 0 ); + cl_railrings_width = Cvar_Get( "cl_railrings_width", "3", 0 ); + + cl_disable_particles = Cvar_Get( "cl_disable_particles", "0", 0 ); + cl_disable_explosions = Cvar_Get( "cl_disable_explosions", "0", 0 ); + cl_gibs = Cvar_Get( "cl_gibs", "1", 0 ); + cl_gibs->changed = cl_gibs_changed; + + cl_chat_notify = Cvar_Get( "cl_chat_notify", "1", 0 ); + cl_chat_beep = Cvar_Get( "cl_chat_beep", "1", 0 ); + cl_chat_clear = Cvar_Get( "cl_chat_clear", "0", 0 ); + + cl_protocol = Cvar_Get( "cl_protocol", "0", 0 ); + + gender_auto = Cvar_Get ( "gender_auto", "1", CVAR_ARCHIVE ); + + cl_vwep = Cvar_Get ( "cl_vwep", "1", CVAR_ARCHIVE ); + + // + // userinfo + // + info_password = Cvar_Get( "password", "", CVAR_USERINFO ); + info_spectator = Cvar_Get( "spectator", "0", CVAR_USERINFO ); + info_name = Cvar_Get( "name", "unnamed", CVAR_USERINFO | CVAR_ARCHIVE ); + info_skin = Cvar_Get( "skin", "male/grunt", CVAR_USERINFO | CVAR_ARCHIVE ); + info_rate = Cvar_Get( "rate", "5000", CVAR_USERINFO | CVAR_ARCHIVE ); + info_msg = Cvar_Get( "msg", "1", CVAR_USERINFO | CVAR_ARCHIVE ); + info_hand = Cvar_Get( "hand", "0", CVAR_USERINFO | CVAR_ARCHIVE ); + info_hand->changed = info_hand_changed; + info_fov = Cvar_Get( "fov", "90", CVAR_USERINFO | CVAR_ARCHIVE ); + info_gender = Cvar_Get( "gender", "male", CVAR_USERINFO | CVAR_ARCHIVE ); + info_gender->modified = qfalse; // clear this so we know when user sets it manually + + + // + // register our commands + // + + Cmd_AddMacro( "cl_mapname", CL_Mapname_m ); + Cmd_AddMacro( "cl_server", CL_Server_m ); + Cmd_AddMacro( "cl_demo", CL_DemoState_m ); + Cmd_AddMacro( "cl_timer", CL_Timer_m ); + Cmd_AddMacro( "cl_ups", CL_Ups_m ); + Cmd_AddMacro( "cl_fps", CL_Fps_m ); + Cmd_AddMacro( "cl_health", CL_Health_m ); + Cmd_AddMacro( "cl_ammo", CL_Ammo_m ); + Cmd_AddMacro( "cl_armor", CL_Armor_m ); + + +} + +/* +================== +CL_CheatsOK +================== +*/ +qboolean CL_CheatsOK( void ) { + if ( cls.state < ca_connected ) { + return qtrue; + } + + if ( cls.demoplayback ) { + return qtrue; + } + + // developer option + if ( sv_running->integer ) { + if( sv_running->integer != ss_game || Cvar_VariableInteger( "cheats" ) ) { + return qtrue; + } + } + + // single player can cheat + if ( atoi( cl.configstrings[ CS_MAXCLIENTS ] ) < 2 ) { + return qtrue; + } + + return qfalse; +} + +//============================================================================ + +void CL_AppActivate( qboolean active ) { + cls.appactive = active; + Key_ClearStates(); + CL_InputActivate(); + S_Activate( active ); +} + +/* +================== +CL_SetClientTime +================== +*/ +static void CL_SetClientTime( void ) { + int prevtime; + + prevtime = cl.oldframe.number * 100; + if ( prevtime >= cl.serverTime ) { + /* this may happen on a very first frame */ + cl.lerpfrac = 0; + return; + } + if( sv_paused->integer ) { + return; + } + + if ( cl.time > cl.serverTime ) { + if ( cl_showclamp->integer ) + Com_Printf( "high clamp %i\n", cl.time - cl.serverTime ); + cl.time = cl.serverTime; + cl.lerpfrac = 1.0; + } else if ( cl.time < prevtime ) { + if ( cl_showclamp->integer ) + Com_Printf( "low clamp %i\n", cl.serverTime - prevtime ); + cl.time = prevtime; + cl.lerpfrac = 0; + } else { + cl.lerpfrac = ( float ) ( cl.time - prevtime ) / ( float ) ( cl.serverTime - prevtime ); + } + + if ( com_timedemo->integer ) { + cl.lerpfrac = 1.0; + } + + if ( cl_showclamp->integer == 2 ) + Com_Printf( "time %i, lerpfrac %.3f\n", cl.time, cl.lerpfrac ); + +} + +static void CL_MeasureFPS( void ) { + int time; + + time = Sys_Milliseconds(); + if( time - cls.measureTime > 1000 ) { + cls.measureTime = time; + cls.currentFPS = cls.measureFramecount; + cls.measureFramecount = 0; + } + cls.measureFramecount++; +} + +/* +==================== +CL_CheckForReply +==================== +*/ +static void CL_CheckForReply( void ) { + if ( !cl.replyTime ) { + return; + } + + if ( cls.realtime < cl.replyTime ) { + return; + } + + Cbuf_AddText( "cmd say \"" ); + Cbuf_AddText( Cvar_VariableString( "version" ) ); + Cbuf_AddText( "\"\n" ); + + cl.replyTime = 0; +} + + +/* +================== +CL_Frame + +================== +*/ +void CL_Frame( int msec ) { + static int extratime; + static int extraphystime; + int minFrameTime, minPhysTime, delta; + + time_after_ref = time_before_ref = 0; + + if ( !cl_running->integer ) { + if( cmd_buffer.waitCount > 0 ) { + cmd_buffer.waitCount--; + } + return; + } + + extratime += msec; + cls.realtime += msec; + + minFrameTime = 1; + if( cl_async->integer ) { + extraphystime += msec; + + Cvar_ClampInteger( cl_maxfps, 10, 120 ); + minPhysTime = 1000 / cl_maxfps->integer; + if( extraphystime >= minPhysTime || cl.sendPacketNow ) { + CL_FinalizeCmd(); + extraphystime = 0; + } + + if( r_maxfps->integer ) { + if( r_maxfps->integer < 10 ) { + Cvar_Set( "r_maxfps", "10" ); + } + minFrameTime = 1000 / r_maxfps->integer; + } + } else { + extraphystime = 0; + if( cl_maxfps->integer ) { + if( cl_maxfps->integer < 10 ) { + Cvar_Set( "cl_maxfps", "10" ); + } + minFrameTime = 1000 / cl_maxfps->integer; + } + } + + if( com_timedemo->integer ) { + minFrameTime = 1; + } else if( !cls.appactive && !sv_running->integer && com_sleep->integer ) { + // run at 10 fps if background app + minFrameTime = 100; + } else if( cls.state < ca_active ) { + // run at 60 fps if not active + minFrameTime = 16; + } + + if( extratime < minFrameTime ) { + // always sleep in background, but never sleep if running a server + if( !sv_running->integer && com_sleep->integer ) { + delta = minFrameTime - extratime; + if( cls.state < ca_active ) { + NET_Sleep( delta ); + } else if( !cls.appactive ) { + if( com_sleep->integer < 2 ) { + delta = 1; + } + NET_Sleep( delta ); + } + } + // send pending intentions + CL_SendCmd(); + return; + } + + if ( cls.demoplayback ) { + sv_paused->integer = cl_paused->integer; // FIXME: HACK + } + + // decide the simulation time + cls.frametime = extratime * 0.001f; + + if( cls.frametime > 1.0 / 5 ) + cls.frametime = 1.0 / 5; + + // read next demo frame + CL_DemoFrame( extratime ); + + // calculate local time + CL_SetClientTime(); + + CL_CheckForReply(); + + // read user intentions + CL_UpdateCmd( extratime ); + + // finalize them + if( !cl_async->integer ) { + CL_FinalizeCmd(); + } + + // send pending intentions + CL_SendCmd(); + + // resend a connection request if necessary + CL_CheckForResend(); + + // predict all unacknowledged movements + CL_PredictMovement(); + + // update the screen + if ( host_speeds->integer ) + time_before_ref = Sys_Milliseconds(); + + SCR_UpdateScreen(); + + if ( host_speeds->integer ) + time_after_ref = Sys_Milliseconds(); + + // update audio after the 3D view was drawn + S_Update(); + + // advance local effects for next frame + CL_RunDLights(); + CL_RunLightStyles(); + SCR_RunCinematic(); + Con_RunConsole(); + + CL_MeasureFPS(); + + extratime = 0; + cls.framecount++; + + // + // check timeout + // + if( !cls.netchan ) { + return; + } + + if( cls.netchan->last_received > com_localTime ) { + cls.netchan->last_received = com_localTime; + } + if( com_localTime - cls.netchan->last_received > cl_timeout->value * 1000 ) + { + // timeoutcount saves debugger + if ( ++cl.timeoutcount > 5 ) { + Com_Error( ERR_DISCONNECT, "Server connection timed out." ); + } + } else { + cl.timeoutcount = 0; + } +} + +//============================================================================ + +static byte demo_buffer[MAX_PACKETLEN]; + +/* +==================== +CL_Init +==================== +*/ +void CL_Init( void ) { + if ( dedicated->integer ) { + return; // nothing running on the client + } + + if ( cl_running->integer ) { + return; + } + + // all archived variables will now be loaded + +#if defined __unix__ || defined __sgi + S_Init(); + CL_InitRefresh(); +#else + CL_InitRefresh(); + S_Init(); // sound must be initialized after window is created +#endif + + V_Init(); + SCR_Init(); + CL_InitLocal(); + CL_InitInput(); + SCR_RegisterMedia(); + Con_SetupDC(); + CL_InitUI(); + +#if USE_ZLIB + if( inflateInit2( &cls.z, -15 ) != Z_OK ) { + Com_Error( ERR_FATAL, "inflateInit2() failed" ); + } +#endif + + SZ_Init( &cls.demobuff, demo_buffer, sizeof( demo_buffer ) ); + cls.demobuff.allowoverflow = qtrue; + + UI_OpenMenu( UIMENU_MAIN ); + + Con_RunConsole(); + + Cvar_Set( "cl_running", "1" ); +} + + +/* +=============== +CL_Shutdown + +FIXME: this is a callback from Sys_Quit and Com_Error. It would be better +to run quit through here before the final handoff to the sys code. +=============== +*/ +void CL_Shutdown( void ) { + static qboolean isdown = qfalse; + + if ( isdown ) { + Com_Printf( "CL_Shutdown: recursive shutdown\n" ); + return; + } + isdown = qtrue; + + CL_Disconnect( ERR_SILENT, NULL ); + +#if USE_ZLIB + inflateEnd( &cls.z ); +#endif + + CL_ShutdownUI(); + S_Shutdown(); + CL_ShutdownInput(); + Con_Shutdown(); + CL_ShutdownRefresh(); + + Cvar_Set( "cl_running", "0" ); + + isdown = qfalse; +} |