summaryrefslogtreecommitdiff
path: root/src/cl_main.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cl_main.c')
-rw-r--r--src/cl_main.c3190
1 files changed, 3190 insertions, 0 deletions
diff --git a/src/cl_main.c b/src/cl_main.c
new file mode 100644
index 0000000..333129d
--- /dev/null
+++ b/src/cl_main.c
@@ -0,0 +1,3190 @@
+/*
+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"
+#include "d_md2.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;
+
+cvar_t *rcon_address;
+
+cvar_t *cl_noskins;
+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_kickangles;
+cvar_t *cl_rollhack;
+
+#ifdef _DEBUG
+cvar_t *cl_shownet;
+cvar_t *cl_showmiss;
+cvar_t *cl_showclamp;
+#endif
+
+cvar_t *cl_thirdperson;
+cvar_t *cl_thirdperson_angle;
+cvar_t *cl_thirdperson_range;
+
+cvar_t *cl_disable_particles;
+cvar_t *cl_disable_explosions;
+cvar_t *cl_chat_notify;
+cvar_t *cl_chat_sound;
+cvar_t *cl_chat_filter;
+
+cvar_t *cl_disconnectcmd;
+cvar_t *cl_changemapcmd;
+cvar_t *cl_beginmapcmd;
+
+cvar_t *cl_gibs;
+#if USE_FPS
+cvar_t *cl_updaterate;
+#endif
+
+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;
+cvar_t *info_uf;
+
+client_static_t cls;
+client_state_t cl;
+
+centity_t cl_entities[ MAX_EDICTS ];
+
+// used for executing stringcmds
+cmdbuf_t cl_cmdbuf;
+char cl_cmdbuf_text[MAX_STRING_CHARS];
+
+//======================================================================
+
+typedef enum {
+ REQ_FREE,
+ REQ_STATUS,
+ //REQ_INFO,
+ REQ_PING,
+ REQ_RCON
+} requestType_t;
+
+typedef struct {
+ requestType_t type;
+ netadr_t adr;
+ unsigned time;
+} request_t;
+
+#define MAX_REQUESTS 64
+#define REQUEST_MASK ( MAX_REQUESTS - 1 )
+
+static request_t clientRequests[MAX_REQUESTS];
+static int currentRequest;
+
+static request_t *CL_AddRequest( const netadr_t *adr, requestType_t type ) {
+ request_t *r;
+
+ r = &clientRequests[currentRequest & REQUEST_MASK];
+ currentRequest++;
+
+ r->adr = *adr;
+ r->type = type;
+ r->time = cls.realtime;
+
+ return r;
+}
+
+/*
+===================
+CL_UpdateGunSetting
+===================
+*/
+static void CL_UpdateGunSetting( void ) {
+ int nogun;
+
+ if( cls.state < ca_connected ) {
+ 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 ) {
+ 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 ) {
+ 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 ) {
+ 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 );
+}
+
+#if USE_FPS
+static void CL_UpdateRateSetting( void ) {
+ if( cls.state < ca_connected ) {
+ return;
+ }
+ if( cls.serverProtocol != PROTOCOL_VERSION_R1Q2 ) {
+ return;
+ }
+
+ MSG_WriteByte( clc_setting );
+ MSG_WriteShort( CLS_FPS );
+ MSG_WriteShort( cl_updaterate->integer );
+ MSG_FlushTo( &cls.netchan->message );
+}
+#endif
+
+/*
+===================
+CL_ClientCommand
+===================
+*/
+void CL_ClientCommand( const char *string ) {
+ if ( !cls.netchan ) {
+ return;
+ }
+ MSG_WriteByte( clc_stringcmd );
+ MSG_WriteString( string );
+ MSG_FlushTo( &cls.netchan->message );
+}
+
+
+/*
+===================
+CL_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.
+===================
+*/
+qboolean CL_ForwardToServer( void ) {
+ char *cmd;
+
+ cmd = Cmd_Argv( 0 );
+ if( cls.state < ca_active || *cmd == '-' || *cmd == '+' ) {
+ return qfalse;
+ }
+
+ CL_ClientCommand( Cmd_RawArgsFrom( 0 ) );
+ return qtrue;
+}
+
+/*
+==================
+CL_ForwardToServer_f
+==================
+*/
+static void CL_ForwardToServer_f( void ) {
+ if ( cls.state < ca_connected ) {
+ Com_Printf( "Can't \"%s\", not connected\n", Cmd_Argv( 0 ) );
+ return;
+ }
+
+ if ( cls.demo.playback ) {
+ return;
+ }
+
+ // don't forward the first argument
+ if ( Cmd_Argc() > 1 ) {
+ CL_ClientCommand( Cmd_RawArgs() );
+ }
+}
+
+/*
+==================
+CL_Pause_f
+==================
+*/
+static void CL_Pause_f( void ) {
+ if( cl_paused->integer == 2 ) {
+ if( cls.key_dest & (KEY_CONSOLE|KEY_MENU) ) {
+ // activate automatic pause
+ Cvar_Set( "cl_paused", "1" );
+ } else {
+ Cvar_Set( "cl_paused", "0" );
+ }
+ } else {
+ // activate manual pause
+ Cvar_Set( "cl_paused", "2" );
+ }
+}
+
+/*
+=================
+CL_CheckForResend
+
+Resend a connect message if the last one has timed out
+=================
+*/
+void CL_CheckForResend( void ) {
+ char tail[MAX_QPATH];
+ char userinfo[MAX_INFO_STRING];
+ int maxmsglen;
+
+ if ( cls.demo.playback ) {
+ 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;
+ cls.serverProtocol = cl_protocol->integer;
+ if( cls.serverProtocol < PROTOCOL_VERSION_DEFAULT ||
+ cls.serverProtocol > PROTOCOL_VERSION_Q2PRO )
+ {
+ cls.serverProtocol = PROTOCOL_VERSION_Q2PRO;
+ }
+
+ // we don't need a challenge on the localhost
+ cls.state = ca_connecting;
+ cls.connect_time = cls.realtime - CONNECT_DELAY;
+
+ cls.passive = qfalse;
+
+ Con_Close();
+#if USE_UI
+ UI_OpenMenu( UIMENU_NONE );
+#endif
+ }
+
+ // 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 < CONNECT_DELAY ) {
+ return;
+ }
+
+ cls.connect_time = cls.realtime; // for retransmit requests
+ cls.connect_count++;
+
+ if ( cls.state == ca_challenging ) {
+ Com_Printf( "Requesting challenge... %i\n", cls.connect_count );
+ OOB_PRINT( NS_CLIENT, &cls.serverAddress, "getchallenge\n" );
+ return;
+ }
+
+ //
+ // We have gotten a challenge from the server, so try and connect.
+ //
+ Com_Printf( "Requesting connection... %i\n", cls.connect_count );
+
+ cls.userinfo_modified = 0;
+
+ // use maximum allowed msglen for loopback
+ maxmsglen = net_maxmsglen->integer;
+ if( NET_IsLocalAddress( &cls.serverAddress ) ) {
+ maxmsglen = MAX_PACKETLEN_WRITABLE;
+ }
+
+ // add protocol dependent stuff
+ switch( cls.serverProtocol ) {
+ case PROTOCOL_VERSION_R1Q2:
+ Q_snprintf( tail, sizeof( tail ), " %d %d",
+ maxmsglen, PROTOCOL_VERSION_R1Q2_CURRENT );
+ cls.quakePort = net_qport->integer & 0xff;
+ break;
+ case PROTOCOL_VERSION_Q2PRO:
+ Q_snprintf( tail, sizeof( tail ), " %d %d %d %d",
+ maxmsglen, net_chantype->integer, USE_ZLIB,
+ PROTOCOL_VERSION_Q2PRO_CURRENT );
+ cls.quakePort = net_qport->integer & 0xff;
+ break;
+ default:
+ tail[0] = 0;
+ cls.quakePort = net_qport->integer;
+ break;
+ }
+
+ Cvar_BitInfo( userinfo, CVAR_USERINFO );
+ Netchan_OutOfBand( NS_CLIENT, &cls.serverAddress,
+ "connect %i %i %i \"%s\"%s\n", cls.serverProtocol, cls.quakePort,
+ cls.challenge, userinfo, tail );
+}
+
+static void CL_Connect_c( genctx_t *ctx, int argnum ) {
+ if( argnum == 1 ) {
+ Com_Address_g( ctx );
+ } else if( argnum == 2 ) {
+ if( !ctx->partial[0] || ( ctx->partial[0] == '3' && !ctx->partial[1] ) ) {
+ Prompt_AddMatch( ctx, "34" );
+ Prompt_AddMatch( ctx, "35" );
+ Prompt_AddMatch( ctx, "36" );
+ }
+ }
+}
+
+/*
+================
+CL_Connect_f
+
+================
+*/
+static void CL_Connect_f( void ) {
+ char *server, *p;
+ netadr_t address;
+ int protocol;
+ int argc = Cmd_Argc();
+
+ if( argc < 2 ) {
+usage:
+ Com_Printf( "Usage: %s <server> [34|35|36]\n", Cmd_Argv( 0 ) );
+ return;
+ }
+
+ if( argc > 2 ) {
+ protocol = atoi( Cmd_Argv( 2 ) );
+ if( protocol < PROTOCOL_VERSION_DEFAULT ||
+ protocol > PROTOCOL_VERSION_Q2PRO )
+ {
+ goto usage;
+ }
+ } else {
+ protocol = cl_protocol->integer;
+ if( !protocol ) {
+ protocol = PROTOCOL_VERSION_Q2PRO;
+ }
+ }
+
+ server = Cmd_Argv( 1 );
+
+ // support quake2://<address>[/] scheme
+ if( !Q_strncasecmp( server, "quake2://", 9 ) ) {
+ server += 9;
+ if( ( p = strchr( server, '/' ) ) != NULL ) {
+ *p = 0;
+ }
+ }
+
+ if ( !NET_StringToAdr( server, &address, PORT_SERVER ) ) {
+ Com_Printf( "Bad server address\n" );
+ return;
+ }
+
+ // copy early to avoid potential cmd_argv[1] clobbering
+ Q_strlcpy( cls.servername, server, sizeof( cls.servername ) );
+
+ 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;
+ cls.protocolVersion = 0;
+ cls.passive = qfalse;
+ cls.state = ca_challenging;
+ cls.connect_count = 0;
+ cls.connect_time = cls.realtime - CONNECT_DELAY; // CL_CheckForResend() will fire immediately
+
+ CL_CheckForResend();
+
+ Cvar_Set( "cl_paused", "0" );
+ Cvar_Set( "sv_paused", "0" );
+ Cvar_Set( "timedemo", "0" );
+
+ Con_Close();
+#if USE_UI
+ UI_OpenMenu( UIMENU_NONE );
+#endif
+}
+
+static 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 ) );
+}
+
+void CL_SendRcon( const netadr_t *adr, const char *pass, const char *cmd ) {
+ NET_Config( NET_CLIENT );
+
+ CL_AddRequest( adr, REQ_RCON );
+
+ Netchan_OutOfBand( NS_CLIENT, adr, "rcon \"%s\" %s", pass, cmd );
+}
+
+
+/*
+=====================
+CL_Rcon_f
+
+ Send the rest of the command line over as
+ an unconnected command.
+=====================
+*/
+static void CL_Rcon_f( void ) {
+ netadr_t address;
+
+ 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, PORT_SERVER ) ) {
+ Com_Printf( "Bad address: %s\n", rcon_address->string );
+ return;
+ }
+ } else {
+ address = cls.netchan->remote_address;
+ }
+
+ CL_SendRcon( &address, rcon_password->string, Cmd_RawArgs() );
+}
+
+static void CL_Rcon_c( genctx_t *ctx, int argnum ) {
+ Com_Generic_c( ctx, argnum - 1 );
+}
+
+/*
+=====================
+CL_ClearState
+
+=====================
+*/
+void CL_ClearState( void ) {
+ S_StopAllSounds();
+ CL_ClearEffects();
+ CL_ClearTEnts();
+ LOC_FreeLocations();
+
+ // wipe the entire cl structure
+ BSP_Free( cl.bsp );
+ memset( &cl, 0, sizeof( cl ) );
+ memset( &cl_entities, 0, sizeof( cl_entities ) );
+
+ if( cls.state > ca_connected ) {
+ cls.state = ca_connected;
+ }
+
+ // unprotect game cvar
+ fs_game->flags &= ~CVAR_ROM;
+}
+
+/*
+=====================
+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( error_type_t type, const char *text ) {
+ SCR_EndLoadingPlaque(); // get rid of loading plaque
+
+ if( cls.state > ca_disconnected && !cls.demo.playback ) {
+ EXEC_TRIGGER( cl_disconnectcmd );
+ }
+
+#if USE_REF == REF_SOFT
+ if( cls.ref_initialized ) {
+ R_CinematicSetPalette( NULL );
+ }
+#endif
+
+ cls.connect_time = 0;
+ cls.connect_count = 0;
+ cls.passive = qfalse;
+#if USE_ICMP
+ cls.errorReceived = qfalse;
+#endif
+
+ // stop demo
+ if( cls.demo.recording ) {
+ CL_Stop_f();
+ }
+ if( cls.demo.playback ) {
+ FS_FCloseFile( cls.demo.playback );
+
+ if( com_timedemo->integer ) {
+ unsigned msec = Sys_Milliseconds();
+ float sec = ( msec - cls.demo.time_start ) * 0.001f;
+ float fps = cls.demo.time_frames / sec;
+
+ Com_Printf( "%u frames, %3.1f seconds: %3.1f fps\n",
+ cls.demo.time_frames, sec, fps );
+ }
+ }
+ memset( &cls.demo, 0, sizeof( cls.demo ) );
+
+ if( cls.netchan ) {
+ // send a disconnect message to the server
+ MSG_WriteByte( clc_stringcmd );
+ MSG_WriteData( "disconnect", 11 );
+
+ cls.netchan->Transmit( cls.netchan, msg_write.cursize, msg_write.data, 3 );
+
+ SZ_Clear( &msg_write );
+
+ Netchan_Close( cls.netchan );
+ cls.netchan = NULL;
+ }
+
+#if USE_CURL
+ HTTP_CancelDownloads();
+#endif
+
+ // stop download
+ if( cls.download.file ) {
+ FS_FCloseFile( cls.download.file );
+ }
+ memset( &cls.download, 0, sizeof( cls.download ) );
+
+ CL_ClearState ();
+
+ Cvar_Set( "cl_paused", "0" );
+
+ cls.state = ca_disconnected;
+ cls.userinfo_modified = 0;
+
+#if USE_UI
+ UI_ErrorMenu( type, text );
+#endif
+}
+
+/*
+================
+CL_Disconnect_f
+================
+*/
+static void CL_Disconnect_f( void ) {
+ if( cls.state > ca_disconnected ) {
+ Com_Error( ERR_SILENT, "Disconnected from server" );
+ }
+}
+
+static void CL_ServerStatus_c( genctx_t *ctx, int argnum ) {
+ if( argnum == 1 ) {
+ Com_Address_g( ctx );
+ }
+}
+
+/*
+================
+CL_ServerStatus_f
+================
+*/
+static void CL_ServerStatus_f( void ) {
+ char *s;
+ netadr_t adr;
+ neterr_t ret;
+
+ if( Cmd_Argc() < 2 ) {
+ if( !cls.netchan ) {
+ Com_Printf( "Usage: %s [address]\n", Cmd_Argv( 0 ) );
+ return;
+ }
+ adr = cls.netchan->remote_address;
+ } else {
+ s = Cmd_Argv( 1 );
+ if( !NET_StringToAdr( s, &adr, PORT_SERVER ) ) {
+ Com_Printf( "Bad address: %s\n", s );
+ return;
+ }
+ }
+
+ CL_AddRequest( &adr, REQ_STATUS );
+
+ NET_Config( NET_CLIENT );
+
+ ret = OOB_PRINT( NS_CLIENT, &adr, "status" );
+ if( ret == NET_ERROR ) {
+ Com_Printf( "%s to %s\n", NET_ErrorString(), NET_AdrToString( &adr ) );
+ }
+}
+
+/*
+====================
+SortPlayers
+====================
+*/
+static int QDECL 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, size_t len, serverStatus_t *dest ) {
+ const char *s;
+ playerStatus_t *player;
+ size_t infolen;
+
+ memset( dest, 0, sizeof( *dest ) );
+
+ s = memchr( status, '\n', len );
+ if( !s ) {
+ return qfalse;
+ }
+ infolen = s - status;
+ if( !infolen || infolen >= MAX_STRING_CHARS ) {
+ return qfalse;
+ }
+ s++;
+
+ strcpy( dest->address, NET_AdrToString( &net_from ) );
+ memcpy( dest->infostring, status, infolen );
+
+ // HACK: check if this is a status response
+ if( !strstr( dest->infostring, "\\maxclients\\" ) ) {
+ 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_strlcpy( 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;
+ const char *infostring;
+ int i;
+
+ 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( "\nNum Score Ping Name\n" );
+ for( i = 0, player = status->players; i < status->numPlayers; i++, player++ ) {
+ Com_Printf( "%3i %5i %4i %s\n", i + 1, player->score, player->ping,
+ player->name );
+ }
+}
+
+/*
+====================
+CL_ParsePrintMessage
+====================
+*/
+static void CL_ParsePrintMessage( void ) {
+ request_t *r;
+ serverStatus_t serverStatus;
+ char string[MAX_NET_STRING];
+ int i, oldest;
+ unsigned delta;
+ size_t len;
+
+ len = MSG_ReadString( string, sizeof( string ) );
+
+ 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;
+ }
+ delta = cls.realtime - r->time;
+ if( r->adr.type == NA_BROADCAST ) {
+ if( delta > 3000 ) {
+ continue;
+ }
+ } else {
+ if( delta > 6000 ) {
+ break;
+ }
+ if( !NET_IsEqualBaseAdr( &net_from, &r->adr ) ) {
+ continue;
+ }
+ }
+ switch( r->type ) {
+ case REQ_STATUS:
+ if( !CL_ServerStatusResponse( string, len, &serverStatus ) ) {
+ continue;
+ }
+ CL_DumpServerInfo( &serverStatus );
+ break;
+ //case REQ_INFO:
+ // break;
+#if USE_UI
+ case REQ_PING:
+ if( !CL_ServerStatusResponse( string, len, &serverStatus ) ) {
+ continue;
+ }
+ UI_AddToServerList( &serverStatus );
+ break;
+#endif
+ case REQ_RCON:
+ Com_Printf( "%s", string );
+ return; // rcon may come in multiple packets
+ default:
+ break;
+ }
+
+ r->type = REQ_FREE;
+ return;
+ }
+
+ // finally, check is this is response from the server we are connecting to
+ // and if so, start channenge cycle again
+ if ( ( cls.state == ca_challenging || cls.state == ca_connecting ) &&
+ NET_IsEqualBaseAdr( &net_from, &cls.serverAddress ) )
+ {
+ Com_Printf( "%s", string );
+ cls.state = ca_challenging;
+ //cls.connectCount = 0;
+ return;
+ }
+
+ Com_DPrintf( "%s: dropped unrequested packet\n", __func__ );
+}
+
+
+/*
+====================
+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 ) {
+ int i, j;
+ char *s;
+
+ if ( cls.state < ca_connected ) {
+ return;
+ }
+
+ if ( cls.demo.recording )
+ CL_Stop_f();
+
+ S_StopAllSounds();
+
+ Com_Printf( "Changing map...\n" );
+
+ if( !cls.demo.playback ) {
+ EXEC_TRIGGER( cl_changemapcmd );
+ Cmd_ExecTrigger( "#cl_changelevel" );
+ }
+
+ SCR_BeginLoadingPlaque();
+
+ cls.state = ca_connected; // not active anymore, but not disconnected
+ cl.mapname[0] = 0;
+ cl.configstrings[CS_NAME][0] = 0;
+
+ // parse additional parameters
+ j = Cmd_Argc();
+ for( i = 1; i < j; i++ ) {
+ s = Cmd_Argv( i );
+ if( !strncmp( s, "map=", 4 ) ) {
+ Q_strlcpy( cl.mapname, s + 4, sizeof( cl.mapname ) );
+ }
+ }
+
+ SCR_UpdateScreen();
+}
+
+
+/*
+=================
+CL_Reconnect_f
+
+The server is changing levels
+=================
+*/
+static void CL_Reconnect_f( void ) {
+ if( cls.state >= ca_precached ) {
+ CL_Disconnect( ERR_SILENT, NULL );
+ }
+ if( cls.state >= ca_connected ) {
+ cls.state = ca_connected;
+
+ if( cls.demo.playback ) {
+ return;
+ }
+ if( cls.download.file ) {
+ return; // if we are downloading, we don't change!
+ }
+
+ Com_Printf( "Reconnecting...\n" );
+
+ CL_ClientCommand( "new" );
+ return;
+ }
+
+ // issued manually at console
+ 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 = cls.realtime - CONNECT_DELAY;
+ 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, size_t size ) {
+ netadr_t address;
+
+ memset( &address, 0, sizeof( address ) );
+
+ NET_Config( NET_CLIENT );
+
+ if( !buffer ) {
+ // send a broadcast packet
+ address.type = NA_BROADCAST;
+ address.port = BigShort( PORT_SERVER );
+ } else {
+ if ( !NET_StringToAdr( buffer, &address, PORT_SERVER ) ) {
+ return qfalse;
+ }
+
+ // return resolved address
+ if( size > 0 ) {
+ Q_strlcpy( buffer, NET_AdrToString( &address ), size );
+ }
+ }
+
+ CL_AddRequest( &address, REQ_PING );
+
+ OOB_PRINT( NS_CLIENT, &address, "status" );
+
+ // Com_ProcessEvents();
+
+ return qtrue;
+}
+
+
+/*
+=================
+CL_PingServers_f
+=================
+*/
+static void CL_PingServers_f( void ) {
+ int i;
+ char buffer[32];
+ cvar_t *var;
+ 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 );
+
+ OOB_PRINT( NS_CLIENT, &address, "status" );
+
+ SCR_UpdateScreen();
+
+ // send a packet to each address book entry
+ for( i = 0; i < 64; i++ ) {
+ Q_snprintf( buffer, sizeof( buffer ), "adr%i", i );
+ var = Cvar_FindVar( buffer );
+ if( !var ) {
+ break;
+ }
+ if( !var->string[0] )
+ continue;
+
+ if( !NET_StringToAdr( var->string, &address, PORT_SERVER ) ) {
+ Com_Printf( "bad address: %s\n", var->string );
+ continue;
+ }
+
+ Com_Printf( "pinging %s...\n", var->string );
+ CL_AddRequest( &address, REQ_STATUS );
+
+ OOB_PRINT( NS_CLIENT, &address, "status" );
+
+ // Com_ProcessEvents();
+ SCR_UpdateScreen();
+ }
+}
+
+/*
+=================
+CL_Skins_f
+
+Load or download any custom player skins and models
+=================
+*/
+static void CL_Skins_f( void ) {
+ int i;
+ char *s;
+ clientinfo_t *ci;
+
+ for( i = 0 ; i < MAX_CLIENTS; i++ ) {
+ s = cl.configstrings[ CS_PLAYERSKINS + i ];
+ if( !s[0] )
+ continue;
+ ci = &cl.clientinfo[i];
+ CL_LoadClientinfo( ci, s );
+ Com_Printf( "client %d: %s --> %s/%s\n", i, s,
+ ci->model_name, ci->skin_name );
+ SCR_UpdateScreen();
+ }
+}
+
+static void cl_noskins_changed( cvar_t *self ) {
+ int i;
+ char *s;
+ clientinfo_t *ci;
+
+ for( i = 0 ; i < MAX_CLIENTS; i++ ) {
+ s = cl.configstrings[ CS_PLAYERSKINS + i ];
+ if( !s[0] )
+ continue;
+ ci = &cl.clientinfo[i];
+ CL_LoadClientinfo( ci, s );
+ }
+}
+
+void CL_Name_g( genctx_t *ctx ) {
+ int i;
+ clientinfo_t *ci;
+
+ if( cls.state < ca_loading ) {
+ return;
+ }
+
+ for( i = 0; i < MAX_CLIENTS; i++ ) {
+ ci = &cl.clientinfo[i];
+ if( ci->name[0] && !Prompt_AddMatch( ctx, ci->name ) ) {
+ break;
+ }
+ }
+}
+
+
+/*
+=================
+CL_ConnectionlessPacket
+
+Responses to broadcasts, etc
+=================
+*/
+static void CL_ConnectionlessPacket( void ) {
+ char string[MAX_STRING_CHARS];
+ char *s, *c;
+ int i, j, k;
+ size_t len;
+
+ MSG_BeginReading();
+ MSG_ReadLong(); // skip the -1
+
+ len = MSG_ReadStringLine( string, sizeof( string ) );
+ if( len >= sizeof( string ) ) {
+ Com_DPrintf( "Oversize message received. Ignored.\n" );
+ return;
+ }
+
+ Cmd_TokenizeString( string, qfalse );
+
+ c = Cmd_Argv( 0 );
+
+ Com_DPrintf( "%s: %s\n", NET_AdrToString ( &net_from ), string );
+
+ // challenge from the server we are connecting to
+ if ( !strcmp( c, "challenge" ) ) {
+ int mask = 0;
+
+ 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 = cls.realtime - CONNECT_DELAY;
+ //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 = strtoul( s, &s, 10 );
+ if( k == PROTOCOL_VERSION_R1Q2 ) {
+ mask |= 1;
+ } else if( k == PROTOCOL_VERSION_Q2PRO ) {
+ mask |= 2;
+ }
+ s = strchr( s, ',' );
+ if( s == NULL ) {
+ break;
+ }
+ s++;
+ }
+ }
+ }
+
+ // choose supported protocol
+ switch( cls.serverProtocol ) {
+ case PROTOCOL_VERSION_Q2PRO:
+ if( mask & 2 ) {
+ break;
+ }
+ cls.serverProtocol = PROTOCOL_VERSION_R1Q2;
+ case PROTOCOL_VERSION_R1Q2:
+ if( mask & 1 ) {
+ break;
+ }
+ default:
+ cls.serverProtocol = PROTOCOL_VERSION_DEFAULT;
+ break;
+ }
+ Com_DPrintf( "Selected protocol %d\n", cls.serverProtocol );
+
+ CL_CheckForResend();
+ return;
+ }
+
+ // server connection
+ if ( !strcmp( c, "client_connect" ) ) {
+ netchan_type_t type;
+ int anticheat = 0;
+ char mapname[MAX_QPATH];
+
+ 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;
+ }
+
+ mapname[0] = 0;
+
+ // 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" );
+ }
+ }
+ } else if( !strncmp( s, "map=", 4 ) ) {
+ Q_strlcpy( mapname, s + 4, sizeof( mapname ) );
+ }
+#if USE_CURL
+ else if( !strncmp( s, "dlserver=", 9 ) ) {
+ HTTP_SetServer( s + 9 );
+ }
+#endif
+ }
+
+ Com_Printf( "Connected to %s (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 );
+
+#if USE_AC_CLIENT
+ if( anticheat ) {
+ MSG_WriteByte( clc_nop );
+ MSG_FlushTo( &cls.netchan->message );
+ cls.netchan->Transmit( cls.netchan, 0, NULL, 3 );
+ S_StopAllSounds();
+ cls.connect_count = -1;
+ 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.connect_count = 0;
+ strcpy( cl.mapname, mapname ); // for levelshot screen
+ 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_strlcpy( cls.servername, s, sizeof( cls.servername ) );
+ cls.passive = qfalse;
+
+ cls.state = ca_challenging;
+ cls.connect_time = cls.realtime - CONNECT_DELAY;
+ cls.connect_count = 0;
+
+ CL_CheckForResend();
+ return;
+ }
+
+ // print command from somewhere
+ if ( !strcmp( c, "print" ) ) {
+ CL_ParsePrintMessage();
+ return;
+ }
+
+ Com_DPrintf( "Unknown connectionless packet command.\n" );
+}
+
+
+/*
+=================
+CL_PacketEvent
+=================
+*/
+static void CL_PacketEvent( void ) {
+ //
+ // remote command packet
+ //
+ if( *( int * )msg_read.data == -1 ) {
+ CL_ConnectionlessPacket();
+ return;
+ }
+
+ if( cls.state < ca_connected ) {
+ return;
+ }
+
+ if( !cls.netchan ) {
+ return; // dump it if not connected
+ }
+
+ if( 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( !cls.netchan->Process( cls.netchan ) )
+ return; // wasn't accepted for some reason
+
+#if USE_ICMP
+ cls.errorReceived = qfalse; // don't drop
+#endif
+
+ CL_ParseServerMessage();
+
+#ifdef _DEBUG
+ CL_AddNetgraph();
+#endif
+
+ SCR_LagSample();
+}
+
+#if USE_ICMP
+void CL_ErrorEvent( void ) {
+ //
+ // error packet from server
+ //
+ if( cls.state < ca_connected ) {
+ return;
+ }
+ if( !cls.netchan ) {
+ return; // dump it if not connected
+ }
+ if( !NET_IsEqualBaseAdr( &net_from, &cls.netchan->remote_address ) ) {
+ return;
+ }
+ if( net_from.port && net_from.port != cls.netchan->remote_address.port ) {
+ return;
+ }
+
+ cls.errorReceived = qtrue; // drop connection soon
+}
+#endif
+
+
+//=============================================================================
+
+/*
+==============
+CL_FixUpGender_f
+==============
+*/
+static void CL_FixUpGender( void ) {
+ char *p;
+ char sk[MAX_QPATH];
+
+ Q_strlcpy( 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, from_t from ) {
+ int i;
+
+ if( var == info_skin && from > FROM_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
+==============
+*/
+static void CL_Userinfo_f ( void ) {
+ char userinfo[MAX_INFO_STRING];
+
+ Cvar_BitInfo( userinfo, CVAR_USERINFO );
+
+ Com_Printf( "User info settings:\n" );
+ Info_Print( userinfo );
+}
+
+/*
+======================
+CL_RegisterSounds
+======================
+*/
+static void CL_RegisterSounds( void ) {
+ int i;
+ char *s;
+
+ 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_RestartSound_f
+
+Restart the sound subsystem so it can pick up
+new parameters and flush all sounds
+=================
+*/
+static void CL_RestartSound_f( void ) {
+ S_Shutdown();
+ S_Init();
+ CL_RegisterSounds();
+}
+
+/*
+=================
+CL_PlaySound_f
+
+Moved here from sound code so that command is always registered.
+=================
+*/
+static void CL_PlaySound_c( genctx_t *ctx, int state ) {
+ FS_File_g( "sound", "*.wav", FS_SEARCH_SAVEPATH | FS_SEARCH_BYFILTER | 0x80000000, ctx );
+}
+
+static void CL_PlaySound_f( void ) {
+ int i;
+ char name[MAX_QPATH];
+
+ if( Cmd_Argc() < 2 ) {
+ Com_Printf( "Usage: %s <sound> [...]\n", Cmd_Argv( 0 ) );
+ return;
+ }
+
+ for( i = 1; i < Cmd_Argc(); i++ ) {
+ Cmd_ArgvBuffer( i, name, sizeof( name ) );
+ COM_DefaultExtension( name, ".wav", sizeof( name ) );
+ S_StartLocalSound( name );
+ }
+}
+
+
+static void CL_RegisterModels( void ) {
+ int i;
+ char *name;
+
+ for ( i = 1; i < MAX_MODELS; i++ ) {
+ name = cl.configstrings[CS_MODELS+i];
+ if( !name[0] ) {
+ break;
+ }
+ if( name[0] == '*' )
+ cl.model_clip[i] = BSP_InlineModel( cl.bsp, name );
+ else
+ cl.model_clip[i] = NULL;
+ }
+}
+
+void CL_LoadState( load_state_t state ) {
+#if 0
+ char *s;
+
+ switch( state ) {
+ case LOAD_MAP:
+ s = cl.configstrings[ CS_MODELS + 1 ];
+ break;
+ case LOAD_MODELS:
+ s = "models";
+ break;
+ case LOAD_IMAGES:
+ s = "images";
+ break;
+ case LOAD_CLIENTS:
+ s = "clients";
+ break;
+ case LOAD_SOUNDS:
+ s = "sounds";
+ break;
+ case LOAD_FINISH:
+ Con_Printf( "\r" );
+ return;
+ default:
+ return;
+ }
+ Con_Printf( "\rLoading %s...", s );
+#endif
+
+ // Com_ProcessEvents();
+ SCR_UpdateScreen();
+}
+
+static int precache_check; // for autodownload of precache items
+static int precache_spawncount;
+static int precache_tex;
+static int precache_model_skin;
+
+static void *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][3] = { "rt", "bk", "lf", "ft", "up", "dn" };
+
+void CL_RequestNextDownload ( void ) {
+ char fn[ MAX_QPATH ];
+ dmd2header_t *pheader;
+ size_t 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 ) {
+ if ( precache_model_skin == -1 ) {
+ // checking for models
+ while ( precache_check < CS_MODELS + MAX_MODELS &&
+ cl.configstrings[ precache_check ][ 0 ] ) {
+
+ if ( cl.configstrings[ precache_check ][ 0 ] == '*' ||
+ cl.configstrings[ precache_check ][ 0 ] == '#' ) {
+ precache_check++;
+ continue;
+ }
+ if ( !CL_CheckOrDownloadFile( cl.configstrings[ precache_check ] ) ) {
+ precache_check++;
+ return; // started a download
+ }
+ precache_check++;
+ }
+ precache_model_skin = 0;
+ precache_check = CS_MODELS + 2; // 0 isn't used
+#if USE_CURL
+ if( HTTP_DownloadsPending() ) {
+ //pending downloads (models), let's wait here before we can check skins.
+ return;
+ }
+#endif
+ }
+ // checking for skins
+ while ( precache_check < CS_MODELS + MAX_MODELS &&
+ cl.configstrings[ precache_check ][ 0 ] ) {
+ size_t num_skins, ofs_skins, end_skins;
+
+ if ( cl.configstrings[ precache_check ][ 0 ] == '*' ||
+ cl.configstrings[ precache_check ][ 0 ] == '#' ) {
+ precache_check++;
+ continue;
+ }
+ 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 = ( dmd2header_t * ) precache_model;
+ if( length < sizeof( *pheader ) ||
+ LittleLong( pheader->ident ) != MD2_IDENT ||
+ LittleLong( pheader->version ) != MD2_VERSION )
+ {
+ // not an alias model
+ FS_FreeFile( precache_model );
+ precache_model = NULL;
+ precache_model_skin = 0;
+ precache_check++;
+ continue;
+ }
+ num_skins = LittleLong( pheader->num_skins );
+ ofs_skins = LittleLong( pheader->ofs_skins );
+ end_skins = ofs_skins + num_skins * MD2_MAX_SKINNAME;
+ if( num_skins > MD2_MAX_SKINS || end_skins < ofs_skins || end_skins > length ) {
+ // bad alias model
+ FS_FreeFile( precache_model );
+ precache_model = NULL;
+ precache_model_skin = 0;
+ precache_check++;
+ continue;
+ }
+ }
+
+ pheader = ( dmd2header_t * ) precache_model;
+ num_skins = LittleLong( pheader->num_skins );
+ ofs_skins = LittleLong( pheader->ofs_skins );
+
+ while ( precache_model_skin < num_skins ) {
+ Q_strlcpy( fn, ( char * )precache_model + ofs_skins +
+ precache_model_skin * MD2_MAX_SKINNAME, sizeof( fn ) );
+ if ( !CL_CheckOrDownloadFile( fn ) ) {
+ precache_model_skin++;
+ return; // started a download
+ }
+ precache_model_skin++;
+ }
+ FS_FreeFile( precache_model );
+ precache_model = NULL;
+ 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;
+ }
+ Q_concat( fn, sizeof( fn ), "sound/", cl.configstrings[ precache_check++ ], NULL );
+ if ( !CL_CheckOrDownloadFile( fn ) )
+ return; // started a download
+ }
+ }
+ precache_check = CS_IMAGES;
+ }
+ if ( precache_check >= CS_IMAGES && precache_check < CS_IMAGES + MAX_IMAGES ) {
+ if ( allow_download_pics->integer ) {
+ if ( precache_check == CS_IMAGES )
+ precache_check++; // zero is blank
+ while ( precache_check < CS_IMAGES + MAX_IMAGES &&
+ cl.configstrings[ precache_check ][ 0 ] ) {
+ Q_concat( fn, sizeof( fn ), "pics/", cl.configstrings[ precache_check++ ], ".pcx", NULL );
+ 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_strlcpy( 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
+ Q_concat( fn, sizeof( fn ), "players/", model, "/tris.md2", NULL );
+ if ( !CL_CheckOrDownloadFile( fn ) ) {
+ precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 1;
+ return; // started a download
+ }
+ n++;
+ /*FALL THROUGH*/
+
+ case 1: // weapon model
+ Q_concat( fn, sizeof( fn ), "players/", model, "/weapon.md2", NULL );
+ if ( !CL_CheckOrDownloadFile( fn ) ) {
+ precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 2;
+ return; // started a download
+ }
+ n++;
+ /*FALL THROUGH*/
+
+ case 2: // weapon skin
+ Q_concat( fn, sizeof( fn ), "players/", model, "/weapon.pcx", NULL );
+ if ( !CL_CheckOrDownloadFile( fn ) ) {
+ precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 3;
+ return; // started a download
+ }
+ n++;
+ /*FALL THROUGH*/
+
+ case 3: // skin
+ Q_concat( fn, sizeof( fn ), "players/", model, "/", skin, ".pcx", NULL );
+ if ( !CL_CheckOrDownloadFile( fn ) ) {
+ precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 4;
+ return; // started a download
+ }
+ n++;
+ /*FALL THROUGH*/
+
+ case 4: // skin_i
+ Q_concat( fn, sizeof( fn ), "players/", model, "/", skin, "_i.pcx", NULL );
+ 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 USE_CURL
+ if( HTTP_DownloadsPending() ) {
+ //map might still be downloading?
+ return;
+ }
+#endif
+
+ if ( precache_check == ENV_CNT ) {
+ qerror_t ret = BSP_Load( cl.configstrings[ CS_MODELS + 1 ], &cl.bsp );
+ if( cl.bsp == NULL ) {
+ Com_Error( ERR_DROP, "Couldn't load %s: %s",
+ cl.configstrings[ CS_MODELS + 1 ], Q_ErrorString( ret ) );
+ }
+
+#if USE_MAPCHECKSUM
+ if ( cl.bsp->checksum != atoi( cl.configstrings[ CS_MAPCHECKSUM ] ) ) {
+ Com_Error ( ERR_DROP, "Local map version differs from server: %i != %s",
+ cl.bsp->checksum, cl.configstrings[ CS_MAPCHECKSUM ] );
+ }
+#endif
+ precache_check = ENV_CNT + 1;
+ CL_RegisterModels();
+ }
+
+ if ( precache_check > ENV_CNT && precache_check < TEXTURE_CNT ) {
+ if ( allow_download->integer && allow_download_textures->integer ) {
+ while ( precache_check < TEXTURE_CNT ) {
+ int n = precache_check++ - ENV_CNT - 1;
+
+ if ( n & 1 )
+ Q_concat( fn, sizeof( fn ),
+ "env/", cl.configstrings[ CS_SKY ], env_suf[ n / 2 ], ".pcx", NULL );
+ else
+ Q_concat( fn, sizeof( fn ),
+ "env/", cl.configstrings[ CS_SKY ], env_suf[ n / 2 ], ".tga", NULL );
+ 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_textures->integer ) {
+ while ( precache_tex < cl.bsp->numtexinfo ) {
+ char *texname = cl.bsp->texinfo[ precache_tex++ ].name;
+
+ // Also check if 32bit images are present
+ Q_concat( fn, sizeof( fn ), "textures/", texname, ".jpg", NULL );
+ if ( !FS_FileExists( fn ) ) {
+ Q_concat( fn, sizeof( fn ), "textures/", texname, ".tga", NULL );
+ if ( !FS_FileExists( fn ) ) {
+ Q_concat( fn, sizeof( fn ), "textures/", texname, ".wal", NULL );
+ if ( !CL_CheckOrDownloadFile( fn ) ) {
+ return; // started a download
+ }
+ }
+ }
+ }
+ }
+ precache_check = TEXTURE_CNT + 999;
+ }
+
+#if USE_CURL
+ if( HTTP_DownloadsPending() ) {
+ //pending downloads (possibly textures), let's wait here.
+ return;
+ }
+#endif
+
+ CL_PrepRefresh ();
+
+ CL_LoadState( LOAD_SOUNDS );
+ CL_RegisterSounds ();
+
+ LOC_LoadLocations();
+
+ CL_LoadState( LOAD_FINISH );
+
+ CL_ClientCommand( va( "begin %i\n", precache_spawncount ) );
+
+ Cvar_FixCheats();
+
+ CL_UpdateGunSetting();
+ CL_UpdateGibSetting();
+ CL_UpdateFootstepsSetting();
+ CL_UpdatePredictSetting();
+#if USE_FPS
+ CL_UpdateRateSetting();
+#endif
+
+ cls.state = ca_precached;
+}
+
+void CL_ResetPrecacheCheck( void ) {
+ precache_check = CS_MODELS;
+ if( precache_model ) {
+ FS_FreeFile( precache_model );
+ precache_model = NULL;
+ }
+ precache_model_skin = -1;
+}
+
+/*
+=================
+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;
+ CL_LoadState( LOAD_MAP );
+
+ S_StopAllSounds();
+
+ //Yet another hack to let old demos work
+ //the old precache sequence
+ if( cls.demo.playback ) {
+ qerror_t ret = BSP_Load( cl.configstrings[ CS_MODELS + 1 ], &cl.bsp );
+ if( cl.bsp == NULL ) {
+ Com_Error( ERR_DROP, "Couldn't load %s: %s",
+ cl.configstrings[ CS_MODELS + 1 ], Q_ErrorString( ret ) );
+ }
+ CL_RegisterModels();
+ CL_PrepRefresh();
+ CL_LoadState( LOAD_SOUNDS );
+ CL_RegisterSounds();
+ CL_LoadState( LOAD_FINISH );
+ cls.state = ca_precached;
+ return;
+ }
+
+ precache_spawncount = atoi( Cmd_Argv( 1 ) );
+ CL_ResetPrecacheCheck();
+
+ 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 dump_program( const char *text, const char *name ) {
+ char buffer[MAX_OSPATH];
+ size_t len;
+ qerror_t ret;
+
+ 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( !*text ) {
+ Com_Printf( "No %s to dump.\n", name );
+ return;
+ }
+
+ len = Q_concat( buffer, sizeof( buffer ), "layouts/", Cmd_Argv( 1 ), ".txt", NULL );
+ if( len >= sizeof( buffer ) ) {
+ Com_EPrintf( "Oversize filename specified.\n" );
+ return;
+ }
+
+ len = strlen( text );
+ ret = FS_WriteFile( buffer, text, len );
+ if( ret < 0 ) {
+ Com_EPrintf( "Couldn't write %s: %s\n", buffer, Q_ErrorString( ret ) );
+ return;
+ }
+
+ Com_Printf( "Dumped %s program to %s.\n", name, buffer );
+}
+
+static void CL_DumpStatusbar_f( void ) {
+ dump_program( cl.configstrings[CS_STATUSBAR], "status bar" );
+}
+
+static void CL_DumpLayout_f( void ) {
+ dump_program( cl.layout, "layout" );
+}
+
+static const cmd_option_t o_writeconfig[] = {
+ { "a", "aliases", "write aliases" },
+ { "b", "bindings", "write bindings" },
+ { "c", "cvars", "write archived cvars" },
+ { "h", "help", "display this help message" },
+ { "m", "modified", "write modified cvars" },
+ { NULL }
+};
+
+static void CL_WriteConfig_c( genctx_t *ctx, int argnum ) {
+ Cmd_Option_c( o_writeconfig, Cmd_Config_g, ctx, argnum );
+}
+
+/*
+===============
+CL_WriteConfig_f
+===============
+*/
+static void CL_WriteConfig_f( void ) {
+ char buffer[MAX_OSPATH];
+ qboolean aliases = qfalse, bindings = qfalse, modified = qfalse;
+ int c, mask = 0;
+ qhandle_t f;
+
+ while( ( c = Cmd_ParseOptions( o_writeconfig ) ) != -1 ) {
+ switch( c ) {
+ case 'a':
+ aliases = qtrue;
+ break;
+ case 'b':
+ bindings = qtrue;
+ break;
+ case 'c':
+ mask |= CVAR_ARCHIVE;
+ break;
+ case 'h':
+ Cmd_PrintUsage( o_writeconfig, "<filename>" );
+ Com_Printf( "Save current configuration into file.\n" );
+ Cmd_PrintHelp( o_writeconfig );
+ return;
+ case 'm':
+ modified = qtrue;
+ mask = ~0;
+ break;
+ default:
+ return;
+ }
+ }
+
+ if( !cmd_optarg[0] ) {
+ Com_Printf( "Missing filename argument.\n" );
+ Cmd_PrintHint();
+ return;
+ }
+
+ if( !aliases && !bindings && !mask ) {
+ bindings = qtrue;
+ mask = CVAR_ARCHIVE;
+ }
+
+ f = FS_EasyOpenFile( buffer, sizeof( buffer ), FS_MODE_WRITE,
+ "configs/", cmd_optarg, ".cfg" );
+ if( !f ) {
+ return;
+ }
+
+ FS_FPrintf( f, "// generated by q2pro\n" );
+
+ if( bindings ) {
+ FS_FPrintf( f, "\n// key bindings\n" );
+ Key_WriteBindings( f );
+ }
+ if( aliases ) {
+ FS_FPrintf( f, "\n// command aliases\n" );
+ Cmd_WriteAliases( f );
+ }
+ if( mask ) {
+ FS_FPrintf( f, "\n//%s cvars\n", modified ? "modified" : "archived" );
+ Cvar_WriteVariables( f, mask, modified );
+ }
+
+ FS_FCloseFile( f );
+
+ Com_Printf( "Wrote %s.\n", buffer );
+}
+
+static void CL_Say_c( genctx_t *ctx, int argnum ) {
+ CL_Name_g( ctx );
+}
+
+static size_t CL_Mapname_m( char *buffer, size_t size ) {
+ return Q_strlcpy( buffer, cl.mapname, size );
+}
+
+static size_t CL_Server_m( char *buffer, size_t size ) {
+ return Q_strlcpy( buffer, cls.servername, size );
+}
+
+static size_t CL_Ups_m( char *buffer, size_t size ) {
+ vec3_t vel;
+ int ups;
+ player_state_t *ps;
+
+ if( cl.frame.clientNum == CLIENTNUM_NONE ) {
+ if( size ) {
+ *buffer = 0;
+ }
+ return 0;
+ }
+
+ if( !cls.demo.playback && 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 );
+ return Q_scnprintf( buffer, size, "%d", ups );
+}
+
+static size_t CL_Timer_m( char *buffer, size_t size ) {
+ int hour, min, sec;
+
+ sec = cl.time / 1000;
+ min = sec / 60; sec %= 60;
+ hour = min / 60; min %= 60;
+
+ if( hour ) {
+ return Q_scnprintf( buffer, size, "%i:%i:%02i", hour, min, sec );
+ }
+ return Q_scnprintf( buffer, size, "%i:%02i", min, sec );
+}
+
+static size_t CL_Fps_m( char *buffer, size_t size ) {
+ return Q_scnprintf( buffer, size, "%i", C_FPS );
+}
+static size_t R_Fps_m( char *buffer, size_t size ) {
+ return Q_scnprintf( buffer, size, "%i", R_FPS );
+}
+static size_t CL_Mps_m( char *buffer, size_t size ) {
+ return Q_scnprintf( buffer, size, "%i", C_MPS );
+}
+static size_t CL_Pps_m( char *buffer, size_t size ) {
+ return Q_scnprintf( buffer, size, "%i", C_PPS );
+}
+static size_t CL_Ping_m( char *buffer, size_t size ) {
+ return Q_scnprintf( buffer, size, "%i", cls.measure.ping );
+}
+static size_t CL_Lag_m( char *buffer, size_t size ) {
+ return Q_scnprintf( buffer, size, "%.2f%%", cls.netchan ?
+ ( (float)cls.netchan->total_dropped /
+ cls.netchan->total_received ) * 100.0f : 0 );
+}
+static size_t CL_Health_m( char *buffer, size_t size ) {
+ return Q_scnprintf( buffer, size, "%i", cl.frame.ps.stats[STAT_HEALTH] );
+}
+static size_t CL_Ammo_m( char *buffer, size_t size ) {
+ return Q_scnprintf( buffer, size, "%i", cl.frame.ps.stats[STAT_AMMO] );
+}
+static size_t CL_Armor_m( char *buffer, size_t size ) {
+ return Q_scnprintf( buffer, size, "%i", cl.frame.ps.stats[STAT_ARMOR] );
+}
+
+/*
+====================
+CL_RestartFilesystem
+
+Flush caches and restart the VFS.
+====================
+*/
+void CL_RestartFilesystem( qboolean total ) {
+ int cls_state;
+
+ if( !cl_running->integer ) {
+ FS_Restart( total );
+ return;
+ }
+
+ Com_DPrintf( "%s(%d)\n", __func__, total );
+
+ // temporary switch to loading state
+ cls_state = cls.state;
+ if( cls.state >= ca_precached ) {
+ cls.state = ca_loading;
+ }
+
+#if USE_UI
+ UI_Shutdown();
+#endif
+
+ S_StopAllSounds();
+ S_FreeAllSounds();
+
+ if( cls.ref_initialized ) {
+ R_Shutdown( qfalse );
+
+ FS_Restart( total );
+
+ R_Init( qfalse );
+
+ SCR_RegisterMedia();
+ Con_RegisterMedia();
+#if USE_UI
+ UI_Init();
+#endif
+ } else {
+ FS_Restart( total );
+ }
+
+#if USE_UI
+ if( cls_state == ca_disconnected ) {
+ UI_OpenMenu( UIMENU_MAIN );
+ } else
+#endif
+ if( cls_state >= ca_loading ) {
+ CL_LoadState( LOAD_MAP );
+ CL_RegisterModels();
+ CL_PrepRefresh();
+ CL_LoadState( LOAD_SOUNDS );
+ CL_RegisterSounds();
+ CL_LoadState( LOAD_FINISH );
+ }
+
+ // switch back to original state
+ cls.state = cls_state;
+
+ cvar_modified &= ~CVAR_FILES;
+}
+
+void CL_RestartRefresh( qboolean total ) {
+ int cls_state;
+
+ if( !cls.ref_initialized ) {
+ return;
+ }
+
+ // temporary switch to loading state
+ cls_state = cls.state;
+ if ( cls.state >= ca_precached ) {
+ cls.state = ca_loading;
+ }
+
+ S_StopAllSounds();
+
+ if( total ) {
+ IN_Shutdown();
+ CL_ShutdownRefresh();
+ CL_InitRefresh();
+ IN_Init();
+ } else {
+#if USE_UI
+ UI_Shutdown();
+#endif
+ R_Shutdown( qfalse );
+ R_Init( qfalse );
+ SCR_RegisterMedia();
+ Con_RegisterMedia();
+#if USE_UI
+ UI_Init();
+#endif
+ }
+
+#if USE_UI
+ if( cls_state == ca_disconnected ) {
+ UI_OpenMenu( UIMENU_MAIN );
+ } else
+#endif
+ if( cls_state >= ca_loading ) {
+ CL_LoadState( LOAD_MAP );
+ CL_PrepRefresh();
+ CL_LoadState( LOAD_FINISH );
+ }
+
+ // switch back to original state
+ cls.state = cls_state;
+
+ cvar_modified &= ~CVAR_FILES;
+}
+
+/*
+====================
+CL_ReloadRefresh
+
+Flush caches and reload all models and textures.
+====================
+*/
+static void CL_ReloadRefresh_f( void ) {
+ CL_RestartRefresh( qfalse );
+}
+
+/*
+====================
+CL_RestartRefresh
+
+Perform complete restart of the renderer subsystem.
+====================
+*/
+static void CL_RestartRefresh_f( void ) {
+ CL_RestartRefresh( qtrue );
+}
+
+// execute string in server command buffer
+static void exec_server_string( cmdbuf_t *buf, const char *text ) {
+ char *s;
+
+ Cmd_TokenizeString( text, qtrue );
+
+ // execute the command line
+ if( !Cmd_Argc() ) {
+ return; // no tokens
+ }
+
+ s = Cmd_Argv( 0 );
+
+ Com_DPrintf( "stufftext: %s\n", s );
+
+ // handle private client commands
+ if( !strcmp( s, "changing" ) ) {
+ CL_Changing_f();
+ return;
+ }
+ if( !strcmp( s, "precache" ) ) {
+ CL_Precache_f();
+ return;
+ }
+
+ // forbid nearly every command from demos
+ if( cls.demo.playback ) {
+ if( strcmp( s, "play" ) ) {
+ return;
+ }
+ }
+
+ // execute regular commands
+ Cmd_ExecuteCommand( buf );
+}
+
+
+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();
+}
+
+#if USE_FPS
+static void cl_updaterate_changed( cvar_t *self ) {
+ CL_UpdateRateSetting();
+}
+#endif
+
+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_RestartSound_f },
+ { "play", CL_PlaySound_f, CL_PlaySound_c },
+ //{ "changing", CL_Changing_f },
+ { "disconnect", CL_Disconnect_f },
+ { "connect", CL_Connect_f, CL_Connect_c },
+ { "passive", CL_PassiveConnect_f },
+ { "reconnect", CL_Reconnect_f },
+ { "rcon", CL_Rcon_f, CL_Rcon_c },
+ //{ "precache", CL_Precache_f },
+ { "download", CL_Download_f },
+ { "serverstatus", CL_ServerStatus_f, CL_ServerStatus_c },
+ { "dumpclients", CL_DumpClients_f },
+ { "dumpstatusbar", CL_DumpStatusbar_f },
+ { "dumplayout", CL_DumpLayout_f },
+ { "writeconfig", CL_WriteConfig_f, CL_WriteConfig_c },
+// { "msgtab", CL_Msgtab_f, CL_Msgtab_g },
+ { "vid_restart", CL_RestartRefresh_f },
+ { "r_reload", CL_ReloadRefresh_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
+ { "say", NULL, CL_Say_c },
+ { "say_team", NULL, CL_Say_c },
+
+ { "wave" }, { "inven" }, { "kill" }, { "use" },
+ { "drop" }, { "info" }, { "prog" },
+ { "give" }, { "god" }, { "notarget" }, { "noclip" },
+ { "invuse" }, { "invprev" }, { "invnext" }, { "invdrop" },
+ { "weapnext" }, { "weapprev" },
+
+ { NULL }
+};
+
+/*
+=================
+CL_InitLocal
+=================
+*/
+static void CL_InitLocal ( void ) {
+ cvar_t *var;
+ int i;
+
+ cls.state = ca_disconnected;
+
+ CL_RegisterInput();
+ CL_InitDemos();
+ LOC_Init();
+ CL_InitAscii();
+ CL_InitEffects();
+ CL_InitTEnts();
+
+ Cmd_Register( c_client );
+
+ for ( i = 0 ; i < MAX_LOCAL_SERVERS ; i++ ) {
+ var = Cvar_Get( va( "adr%i", i ), "", CVAR_ARCHIVE );
+ var->generator = Com_Address_g;
+ }
+
+ //
+ // register our variables
+ //
+ 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_noskins->changed = cl_noskins_changed;
+ 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_rollhack = Cvar_Get( "cl_rollhack", "1", 0 );
+
+#ifdef _DEBUG
+ cl_shownet = Cvar_Get( "cl_shownet", "0", 0 );
+ cl_showmiss = Cvar_Get ( "cl_showmiss", "0", 0 );
+ cl_showclamp = Cvar_Get ( "showclamp", "0", 0 );
+#endif
+
+ cl_timeout = Cvar_Get ( "cl_timeout", "120", 0 );
+
+ rcon_address = Cvar_Get ( "rcon_address", "", CVAR_PRIVATE );
+ rcon_address->generator = Com_Address_g;
+
+ 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_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;
+
+#if USE_FPS
+ cl_updaterate = Cvar_Get( "cl_updaterate", "0", 0 );
+ cl_updaterate->changed = cl_updaterate_changed;
+#endif
+
+ cl_chat_notify = Cvar_Get( "cl_chat_notify", "1", 0 );
+ cl_chat_sound = Cvar_Get( "cl_chat_sound", "misc/talk.wav", 0 );
+ cl_chat_filter = Cvar_Get( "cl_chat_filter", "0", 0 );
+
+ cl_disconnectcmd = Cvar_Get( "cl_disconnectcmd", "", 0 );
+ cl_changemapcmd = Cvar_Get( "cl_changemapcmd", "", 0 );
+ cl_beginmapcmd = Cvar_Get( "cl_beginmapcmd", "", 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
+ info_uf = Cvar_Get( "uf", va( "%d", UF_LOCALFOV ), CVAR_USERINFO );
+
+
+ //
+ // macros
+ //
+ Cmd_AddMacro( "cl_mapname", CL_Mapname_m );
+ Cmd_AddMacro( "cl_server", CL_Server_m );
+ Cmd_AddMacro( "cl_timer", CL_Timer_m );
+ Cmd_AddMacro( "cl_ups", CL_Ups_m );
+ Cmd_AddMacro( "cl_fps", CL_Fps_m );
+ Cmd_AddMacro( "r_fps", R_Fps_m );
+ Cmd_AddMacro( "cl_mps", CL_Mps_m ); // moves per second
+ Cmd_AddMacro( "cl_pps", CL_Pps_m ); // packets per second
+ Cmd_AddMacro( "cl_ping", CL_Ping_m );
+ Cmd_AddMacro( "cl_lag", CL_Lag_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 || cls.demo.playback ) {
+ return qtrue;
+ }
+
+ if( !sv_running->integer ) {
+ return qfalse;
+ }
+
+ // developer option
+ if( Cvar_VariableInteger( "cheats" ) ) {
+ return qtrue;
+ }
+
+ // single player can cheat
+ if( cls.state > ca_connected && cl.maxclients < 2 ) {
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+//============================================================================
+
+/*
+==================
+CL_Activate
+==================
+*/
+void CL_Activate( active_t active ) {
+ if( cls.active != active ) {
+ Com_DPrintf( "%s: %u\n", __func__, active );
+ cls.active = active;
+ Key_ClearStates();
+ IN_Activate();
+ S_Activate();
+ }
+}
+
+/*
+==================
+CL_SetClientTime
+==================
+*/
+static void CL_SetClientTime( void ) {
+ int prevtime;
+
+ if( com_timedemo->integer ) {
+ cl.time = cl.servertime;
+ cl.lerpfrac = 1.0f;
+ return;
+ }
+
+ prevtime = cl.servertime - cl.frametime;
+ if( cl.time > cl.servertime ) {
+ SHOWCLAMP( 1, "high clamp %i\n", cl.time - cl.servertime );
+ cl.time = cl.servertime;
+ cl.lerpfrac = 1.0f;
+ } else if( cl.time < prevtime ) {
+ SHOWCLAMP( 1, "low clamp %i\n", prevtime - cl.time );
+ cl.time = prevtime;
+ cl.lerpfrac = 0;
+ } else {
+ cl.lerpfrac = ( cl.time - prevtime ) * cl.framefrac;
+ }
+
+ SHOWCLAMP( 2, "time %i, lerpfrac %.3f\n", cl.time, cl.lerpfrac );
+}
+
+static void CL_MeasureStats( void ) {
+ int i;
+
+ if( com_localTime - cls.measure.time < 1000 ) {
+ return;
+ }
+
+ // measure average ping
+ if( cls.netchan ) {
+ int ack = cls.netchan->incoming_acknowledged;
+ int ping = 0;
+ int j, k = 0;
+
+ i = ack - 16 + 1;
+ if( i < cl.initialSeq ) {
+ i = cl.initialSeq;
+ }
+ for( j = i; j <= ack; j++ ) {
+ client_history_t *h = &cl.history[j & CMD_MASK];
+ if( h->rcvd > h->sent ) {
+ ping += h->rcvd - h->sent;
+ k++;
+ }
+ }
+
+ cls.measure.ping = k ? ping / k : 0;
+ }
+
+ // measure main/refresh frame counts
+ for( i = 0; i < 4; i++ ) {
+ cls.measure.fps[i] = cls.measure.frames[i];
+ cls.measure.frames[i] = 0;
+ }
+
+ cls.measure.time = com_localTime;
+}
+
+#if USE_AUTOREPLY
+/*
+====================
+CL_CheckForReply
+====================
+*/
+static void CL_CheckForReply( void ) {
+ if( !cl.reply_delta ) {
+ return;
+ }
+
+ if( cls.realtime - cl.reply_time < cl.reply_delta ) {
+ return;
+ }
+
+ CL_ClientCommand( va( "say \"%s\"", com_version->string ) );
+
+ cl.reply_delta = 0;
+}
+#endif
+
+static void CL_CheckTimeout( void ) {
+ unsigned delta;
+
+ if( NET_IsLocalAddress( &cls.netchan->remote_address ) ) {
+ return;
+ }
+
+#if USE_ICMP
+ if( cls.errorReceived ) {
+ delta = 5000;
+ if( com_localTime - cls.netchan->last_received > delta ) {
+ Com_Error( ERR_DISCONNECT, "Server connection was reset." );
+ }
+ }
+#endif
+
+ delta = cl_timeout->value * 1000;
+ if( delta && com_localTime - cls.netchan->last_received > delta ) {
+ // timeoutcount saves debugger
+ if ( ++cl.timeoutcount > 5 ) {
+ Com_Error( ERR_DISCONNECT, "Server connection timed out." );
+ }
+ } else {
+ cl.timeoutcount = 0;
+ }
+}
+
+
+/*
+==================
+CL_Frame
+
+==================
+*/
+void CL_Frame( unsigned msec ) {
+ static unsigned ref_extra, phys_extra, main_extra;
+ unsigned ref_msec, phys_msec;
+ qboolean phys_frame, ref_frame;
+
+ time_after_ref = time_before_ref = 0;
+
+ if( !cl_running->integer ) {
+ return;
+ }
+
+ CL_ProcessEvents();
+
+ Cbuf_Execute( &cl_cmdbuf );
+
+ ref_extra += msec;
+ main_extra += msec;
+ cls.realtime += msec;
+
+ ref_msec = 1;
+ if( cl_async->integer ) {
+ phys_extra += msec;
+ phys_frame = qtrue;
+
+ Cvar_ClampInteger( cl_maxfps, 10, 120 );
+ phys_msec = 1000 / cl_maxfps->integer;
+ if( phys_extra < phys_msec ) {
+ phys_frame = qfalse;
+ }
+
+ if( r_maxfps->integer ) {
+ if( r_maxfps->integer < 10 ) {
+ Cvar_Set( "r_maxfps", "10" );
+ }
+ ref_msec = 1000 / r_maxfps->integer;
+ }
+ } else {
+ phys_frame = qtrue;
+ if( cl_maxfps->integer ) {
+ if( cl_maxfps->integer < 10 ) {
+ Cvar_Set( "cl_maxfps", "10" );
+ }
+ ref_msec = 1000 / cl_maxfps->integer;
+ }
+ }
+
+ ref_frame = qtrue;
+ if( !com_timedemo->integer ) {
+ if( !sv_running->integer ) {
+ if( cls.active == ACT_MINIMIZED ) {
+ // run at 10 fps if minimized
+ if( main_extra < 100 ) {
+ IO_Sleep( 100 - main_extra );
+ return;
+ }
+ ref_frame = qfalse;
+ } else if( cls.active == ACT_RESTORED || cls.state < ca_active ) {
+ // run at 60 fps if not active
+ if( main_extra < 16 ) {
+ IO_Sleep( 16 - main_extra );
+ return;
+ }
+ }
+ }
+ if( ref_extra < ref_msec ) {
+ if( !cl_async->integer && !cl.sendPacketNow ) {
+#if 0
+ if( cls.demo.playback || cl.frame.ps.pmove.pm_type == PM_FREEZE ) {
+ IO_Sleep( ref_msec - ref_extra );
+ }
+#endif
+ return; // everything ticks in sync with refresh
+ }
+ ref_frame = qfalse;
+ }
+ }
+
+ if ( cls.demo.playback ) { // FIXME: HACK
+ if( cl_paused->integer ) {
+ if( !sv_paused->integer ) {
+ Cvar_Set( "sv_paused", "1" );
+ IN_Activate();
+ }
+ } else {
+ if( sv_paused->integer ) {
+ Cvar_Set( "sv_paused", "0" );
+ IN_Activate();
+ }
+ }
+ }
+
+ // decide the simulation time
+ cls.frametime = main_extra * 0.001f;
+
+ if( cls.frametime > 1.0 / 5 )
+ cls.frametime = 1.0 / 5;
+
+ if( !sv_paused->integer ) {
+ cl.time += main_extra;
+ }
+
+ // read next demo frame
+ if( cls.demo.playback ) {
+ if( cls.demo.recording && cl_paused->integer == 2 && !cls.demo.paused ) {
+ static int demo_extra;
+
+ // XXX: record zero frames when manually paused
+ // for syncing with audio comments, etc
+ demo_extra += main_extra;
+ if( demo_extra > 100 ) {
+ CL_EmitZeroFrame();
+ demo_extra = 0;
+ }
+ }
+
+ CL_DemoFrame();
+ }
+
+ // calculate local time
+ if( cls.state == ca_active && !sv_paused->integer ) {
+ CL_SetClientTime();
+ }
+
+#if USE_AUTOREPLY
+ // check for version reply
+ CL_CheckForReply();
+#endif
+
+ // resend a connection request if necessary
+ CL_CheckForResend();
+
+ // read user intentions
+ CL_UpdateCmd( main_extra );
+
+ // finalize pending cmd
+ phys_frame |= cl.sendPacketNow;
+ if( phys_frame ) {
+ CL_FinalizeCmd();
+ phys_extra = 0;
+ M_FRAMES++;
+ }
+
+ // send pending cmds
+ CL_SendCmd();
+
+ // predict all unacknowledged movements
+ CL_PredictMovement();
+
+ Con_RunConsole();
+
+ if( ref_frame ) {
+ // update the screen
+ if ( host_speeds->integer )
+ time_before_ref = Sys_Milliseconds();
+
+ SCR_UpdateScreen();
+
+ if ( host_speeds->integer )
+ time_after_ref = Sys_Milliseconds();
+
+ ref_extra = 0;
+ R_FRAMES++;
+
+ // update audio after the 3D view was drawn
+ S_Update();
+ }
+
+ // advance local effects for next frame
+#if USE_DLIGHTS
+ CL_RunDLights();
+#endif
+#if USE_LIGHTSTYLES
+ CL_RunLightStyles();
+#endif
+
+ // check connection timeout
+ if( cls.netchan ) {
+ CL_CheckTimeout();
+ }
+
+ C_FRAMES++;
+
+ CL_MeasureStats();
+
+ cls.framecount++;
+
+ main_extra = 0;
+}
+
+/*
+============
+CL_ProcessEvents
+============
+*/
+void CL_ProcessEvents( void ) {
+ if( !cl_running->integer ) {
+ return;
+ }
+
+ CL_RunRefresh();
+
+ IN_Frame();
+
+ memset( &net_from, 0, sizeof( net_from ) );
+ net_from.type = NA_LOOPBACK;
+
+ // process loopback packets
+ while( NET_GetLoopPacket( NS_CLIENT ) ) {
+ CL_PacketEvent();
+ }
+
+ // process network packets
+ while( NET_GetPacket( NS_CLIENT ) ) {
+ CL_PacketEvent();
+ }
+
+#if USE_CURL
+ HTTP_RunDownloads();
+#endif
+}
+
+//============================================================================
+
+/*
+====================
+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
+
+ // start with full screen console
+ cls.key_dest = KEY_CONSOLE;
+
+#ifdef _WIN32
+ CL_InitRefresh();
+ S_Init(); // sound must be initialized after window is created
+#else
+ S_Init();
+ CL_InitRefresh();
+#endif
+
+ CL_InitLocal();
+ IN_Init();
+
+#if USE_ZLIB
+ if( inflateInit2( &cls.z, -MAX_WBITS ) != Z_OK ) {
+ Com_Error( ERR_FATAL, "%s: inflateInit2() failed", __func__ );
+ }
+#endif
+
+#if USE_CURL
+ HTTP_Init();
+#endif
+
+#if USE_UI
+ UI_OpenMenu( UIMENU_MAIN );
+#endif
+
+ Con_PostInit();
+ Con_RunConsole();
+
+ cl_cmdbuf.from = FROM_STUFFTEXT;
+ cl_cmdbuf.text = cl_cmdbuf_text;
+ cl_cmdbuf.maxsize = sizeof( cl_cmdbuf_text );
+ cl_cmdbuf.exec = exec_server_string;
+
+ Cvar_Set( "cl_running", "1" );
+}
+
+/*
+===============
+CL_WriteConfig
+
+Writes key bindings and archived cvars to config.cfg
+===============
+*/
+static void CL_WriteConfig( void ) {
+ qhandle_t f;
+ qerror_t ret;
+
+ ret = FS_FOpenFile( COM_CONFIG_NAME, &f, FS_MODE_WRITE );
+ if( !f ) {
+ Com_EPrintf( "Couldn't open %s for writing: %s\n",
+ COM_CONFIG_NAME, Q_ErrorString( ret ) );
+ return;
+ }
+
+ FS_FPrintf( f, "// generated by q2pro, do not modify\n" );
+
+ Key_WriteBindings( f );
+ Cvar_WriteVariables( f, CVAR_ARCHIVE, qfalse );
+
+ FS_FCloseFile( f );
+}
+
+
+/*
+===============
+CL_Shutdown
+
+FIXME: this is a callback from Com_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;
+
+ if( !cl_running || !cl_running->integer ) {
+ return;
+ }
+
+ CL_Disconnect( ERR_SILENT, NULL );
+
+#if USE_ZLIB
+ inflateEnd( &cls.z );
+#endif
+
+#if USE_CURL
+ HTTP_Shutdown();
+#endif
+
+ S_Shutdown();
+ IN_Shutdown();
+ Con_Shutdown();
+ CL_ShutdownRefresh();
+ CL_WriteConfig();
+
+ Cvar_Set( "cl_running", "0" );
+
+ isdown = qfalse;
+}
+