/* 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 [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 \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 \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 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, 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 \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 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 \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= 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 \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 \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; }