summaryrefslogtreecommitdiff
path: root/source/cl_main.c
diff options
context:
space:
mode:
authorAndrey Nazarov <skuller@skuller.net>2007-08-14 20:18:08 +0000
committerAndrey Nazarov <skuller@skuller.net>2007-08-14 20:18:08 +0000
commitf294db4ccf45f6274e65260dd6f9a2c5faa94313 (patch)
treee8cf1ba2bfe9c8417eec17faf912442f52fc4ef2 /source/cl_main.c
Initial import of the new Q2PRO tree.
Diffstat (limited to 'source/cl_main.c')
-rw-r--r--source/cl_main.c2762
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;
+}