From 6bf20c78f5b69d40bcc4931df93d29198435ab67 Mon Sep 17 00:00:00 2001 From: zakk Date: Fri, 26 Aug 2005 17:39:27 +0000 Subject: newlines fixed git-svn-id: svn://svn.icculus.org/quake3/trunk@6 edf5b092-35ff-0310-97b2-ce42778d08ea --- code/client/cl_main.c | 6648 ++++++++++++++++++++++++------------------------- 1 file changed, 3324 insertions(+), 3324 deletions(-) (limited to 'code/client/cl_main.c') diff --git a/code/client/cl_main.c b/code/client/cl_main.c index 89d3b48..61dbcc7 100755 --- a/code/client/cl_main.c +++ b/code/client/cl_main.c @@ -1,3324 +1,3324 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// cl_main.c -- client main loop - -#include "client.h" -#include - -cvar_t *cl_nodelta; -cvar_t *cl_debugMove; - -cvar_t *cl_noprint; -cvar_t *cl_motd; - -cvar_t *rcon_client_password; -cvar_t *rconAddress; - -cvar_t *cl_timeout; -cvar_t *cl_maxpackets; -cvar_t *cl_packetdup; -cvar_t *cl_timeNudge; -cvar_t *cl_showTimeDelta; -cvar_t *cl_freezeDemo; - -cvar_t *cl_shownet; -cvar_t *cl_showSend; -cvar_t *cl_timedemo; -cvar_t *cl_avidemo; -cvar_t *cl_forceavidemo; - -cvar_t *cl_freelook; -cvar_t *cl_sensitivity; - -cvar_t *cl_mouseAccel; -cvar_t *cl_showMouseRate; - -cvar_t *m_pitch; -cvar_t *m_yaw; -cvar_t *m_forward; -cvar_t *m_side; -cvar_t *m_filter; - -cvar_t *cl_activeAction; - -cvar_t *cl_motdString; - -cvar_t *cl_allowDownload; -cvar_t *cl_conXOffset; -cvar_t *cl_inGameVideo; - -cvar_t *cl_serverStatusResendTime; -cvar_t *cl_trn; - -clientActive_t cl; -clientConnection_t clc; -clientStatic_t cls; -vm_t *cgvm; - -// Structure containing functions exported from refresh DLL -refexport_t re; - -ping_t cl_pinglist[MAX_PINGREQUESTS]; - -typedef struct serverStatus_s -{ - char string[BIG_INFO_STRING]; - netadr_t address; - int time, startTime; - qboolean pending; - qboolean print; - qboolean retrieved; -} serverStatus_t; - -serverStatus_t cl_serverStatusList[MAX_SERVERSTATUSREQUESTS]; -int serverStatusCount; - -#if defined __USEA3D && defined __A3D_GEOM - void hA3Dg_ExportRenderGeom (refexport_t *incoming_re); -#endif - -extern void SV_BotFrame( int time ); -void CL_CheckForResend( void ); -void CL_ShowIP_f(void); -void CL_ServerStatus_f(void); -void CL_ServerStatusResponse( netadr_t from, msg_t *msg ); - -/* -=============== -CL_CDDialog - -Called by Com_Error when a cd is needed -=============== -*/ -void CL_CDDialog( void ) { - cls.cddialog = qtrue; // start it next frame -} - - -/* -======================================================================= - -CLIENT RELIABLE COMMAND COMMUNICATION - -======================================================================= -*/ - -/* -====================== -CL_AddReliableCommand - -The given command will be transmitted to the server, and is gauranteed to -not have future usercmd_t executed before it is executed -====================== -*/ -void CL_AddReliableCommand( const char *cmd ) { - int index; - - // if we would be losing an old command that hasn't been acknowledged, - // we must drop the connection - if ( clc.reliableSequence - clc.reliableAcknowledge > MAX_RELIABLE_COMMANDS ) { - Com_Error( ERR_DROP, "Client command overflow" ); - } - clc.reliableSequence++; - index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); - Q_strncpyz( clc.reliableCommands[ index ], cmd, sizeof( clc.reliableCommands[ index ] ) ); -} - -/* -====================== -CL_ChangeReliableCommand -====================== -*/ -void CL_ChangeReliableCommand( void ) { - int r, index, l; - - r = clc.reliableSequence - (random() * 5); - index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); - l = strlen(clc.reliableCommands[ index ]); - if ( l >= MAX_STRING_CHARS - 1 ) { - l = MAX_STRING_CHARS - 2; - } - clc.reliableCommands[ index ][ l ] = '\n'; - clc.reliableCommands[ index ][ l+1 ] = '\0'; -} - -/* -======================================================================= - -CLIENT SIDE DEMO RECORDING - -======================================================================= -*/ - -/* -==================== -CL_WriteDemoMessage - -Dumps the current net message, prefixed by the length -==================== -*/ -void CL_WriteDemoMessage ( msg_t *msg, int headerBytes ) { - int len, swlen; - - // write the packet sequence - len = clc.serverMessageSequence; - swlen = LittleLong( len ); - FS_Write (&swlen, 4, clc.demofile); - - // skip the packet sequencing information - len = msg->cursize - headerBytes; - swlen = LittleLong(len); - FS_Write (&swlen, 4, clc.demofile); - FS_Write ( msg->data + headerBytes, len, clc.demofile ); -} - - -/* -==================== -CL_StopRecording_f - -stop recording a demo -==================== -*/ -void CL_StopRecord_f( void ) { - int len; - - if ( !clc.demorecording ) { - Com_Printf ("Not recording a demo.\n"); - return; - } - - // finish up - len = -1; - FS_Write (&len, 4, clc.demofile); - FS_Write (&len, 4, clc.demofile); - FS_FCloseFile (clc.demofile); - clc.demofile = 0; - clc.demorecording = qfalse; - clc.spDemoRecording = qfalse; - Com_Printf ("Stopped demo.\n"); -} - -/* -================== -CL_DemoFilename -================== -*/ -void CL_DemoFilename( int number, char *fileName ) { - int a,b,c,d; - - if ( number < 0 || number > 9999 ) { - Com_sprintf( fileName, MAX_OSPATH, "demo9999.tga" ); - return; - } - - a = number / 1000; - number -= a*1000; - b = number / 100; - number -= b*100; - c = number / 10; - number -= c*10; - d = number; - - Com_sprintf( fileName, MAX_OSPATH, "demo%i%i%i%i" - , a, b, c, d ); -} - -/* -==================== -CL_Record_f - -record - -Begins recording a demo from the current position -==================== -*/ -static char demoName[MAX_QPATH]; // compiler bug workaround -void CL_Record_f( void ) { - char name[MAX_OSPATH]; - byte bufData[MAX_MSGLEN]; - msg_t buf; - int i; - int len; - entityState_t *ent; - entityState_t nullstate; - char *s; - - if ( Cmd_Argc() > 2 ) { - Com_Printf ("record \n"); - return; - } - - if ( clc.demorecording ) { - if (!clc.spDemoRecording) { - Com_Printf ("Already recording.\n"); - } - return; - } - - if ( cls.state != CA_ACTIVE ) { - Com_Printf ("You must be in a level to record.\n"); - return; - } - - // sync 0 doesn't prevent recording, so not forcing it off .. everyone does g_sync 1 ; record ; g_sync 0 .. - if ( !Cvar_VariableValue( "g_synchronousClients" ) ) { - Com_Printf (S_COLOR_YELLOW "WARNING: You should set 'g_synchronousClients 1' for smoother demo recording\n"); - } - - if ( Cmd_Argc() == 2 ) { - s = Cmd_Argv(1); - Q_strncpyz( demoName, s, sizeof( demoName ) ); - Com_sprintf (name, sizeof(name), "demos/%s.dm_%d", demoName, PROTOCOL_VERSION ); - } else { - int number; - - // scan for a free demo name - for ( number = 0 ; number <= 9999 ; number++ ) { - CL_DemoFilename( number, demoName ); - Com_sprintf (name, sizeof(name), "demos/%s.dm_%d", demoName, PROTOCOL_VERSION ); - - len = FS_ReadFile( name, NULL ); - if ( len <= 0 ) { - break; // file doesn't exist - } - } - } - - // open the demo file - - Com_Printf ("recording to %s.\n", name); - clc.demofile = FS_FOpenFileWrite( name ); - if ( !clc.demofile ) { - Com_Printf ("ERROR: couldn't open.\n"); - return; - } - clc.demorecording = qtrue; - if (Cvar_VariableValue("ui_recordSPDemo")) { - clc.spDemoRecording = qtrue; - } else { - clc.spDemoRecording = qfalse; - } - - - Q_strncpyz( clc.demoName, demoName, sizeof( clc.demoName ) ); - - // don't start saving messages until a non-delta compressed message is received - clc.demowaiting = qtrue; - - // write out the gamestate message - MSG_Init (&buf, bufData, sizeof(bufData)); - MSG_Bitstream(&buf); - - // NOTE, MRE: all server->client messages now acknowledge - MSG_WriteLong( &buf, clc.reliableSequence ); - - MSG_WriteByte (&buf, svc_gamestate); - MSG_WriteLong (&buf, clc.serverCommandSequence ); - - // configstrings - for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { - if ( !cl.gameState.stringOffsets[i] ) { - continue; - } - s = cl.gameState.stringData + cl.gameState.stringOffsets[i]; - MSG_WriteByte (&buf, svc_configstring); - MSG_WriteShort (&buf, i); - MSG_WriteBigString (&buf, s); - } - - // baselines - Com_Memset (&nullstate, 0, sizeof(nullstate)); - for ( i = 0; i < MAX_GENTITIES ; i++ ) { - ent = &cl.entityBaselines[i]; - if ( !ent->number ) { - continue; - } - MSG_WriteByte (&buf, svc_baseline); - MSG_WriteDeltaEntity (&buf, &nullstate, ent, qtrue ); - } - - MSG_WriteByte( &buf, svc_EOF ); - - // finished writing the gamestate stuff - - // write the client num - MSG_WriteLong(&buf, clc.clientNum); - // write the checksum feed - MSG_WriteLong(&buf, clc.checksumFeed); - - // finished writing the client packet - MSG_WriteByte( &buf, svc_EOF ); - - // write it to the demo file - len = LittleLong( clc.serverMessageSequence - 1 ); - FS_Write (&len, 4, clc.demofile); - - len = LittleLong (buf.cursize); - FS_Write (&len, 4, clc.demofile); - FS_Write (buf.data, buf.cursize, clc.demofile); - - // the rest of the demo file will be copied from net messages -} - -/* -======================================================================= - -CLIENT SIDE DEMO PLAYBACK - -======================================================================= -*/ - -/* -================= -CL_DemoCompleted -================= -*/ -void CL_DemoCompleted( void ) { - if (cl_timedemo && cl_timedemo->integer) { - int time; - - time = Sys_Milliseconds() - clc.timeDemoStart; - if ( time > 0 ) { - Com_Printf ("%i frames, %3.1f seconds: %3.1f fps\n", clc.timeDemoFrames, - time/1000.0, clc.timeDemoFrames*1000.0 / time); - } - } - - CL_Disconnect( qtrue ); - CL_NextDemo(); -} - -/* -================= -CL_ReadDemoMessage -================= -*/ -void CL_ReadDemoMessage( void ) { - int r; - msg_t buf; - byte bufData[ MAX_MSGLEN ]; - int s; - - if ( !clc.demofile ) { - CL_DemoCompleted (); - return; - } - - // get the sequence number - r = FS_Read( &s, 4, clc.demofile); - if ( r != 4 ) { - CL_DemoCompleted (); - return; - } - clc.serverMessageSequence = LittleLong( s ); - - // init the message - MSG_Init( &buf, bufData, sizeof( bufData ) ); - - // get the length - r = FS_Read (&buf.cursize, 4, clc.demofile); - if ( r != 4 ) { - CL_DemoCompleted (); - return; - } - buf.cursize = LittleLong( buf.cursize ); - if ( buf.cursize == -1 ) { - CL_DemoCompleted (); - return; - } - if ( buf.cursize > buf.maxsize ) { - Com_Error (ERR_DROP, "CL_ReadDemoMessage: demoMsglen > MAX_MSGLEN"); - } - r = FS_Read( buf.data, buf.cursize, clc.demofile ); - if ( r != buf.cursize ) { - Com_Printf( "Demo file was truncated.\n"); - CL_DemoCompleted (); - return; - } - - clc.lastPacketTime = cls.realtime; - buf.readcount = 0; - CL_ParseServerMessage( &buf ); -} - -/* -==================== -CL_WalkDemoExt -==================== -*/ -static void CL_WalkDemoExt(char *arg, char *name, int *demofile) -{ - int i = 0; - *demofile = 0; - while(demo_protocols[i]) - { - Com_sprintf (name, MAX_OSPATH, "demos/%s.dm_%d", arg, demo_protocols[i]); - FS_FOpenFileRead( name, demofile, qtrue ); - if (*demofile) - { - Com_Printf("Demo file: %s\n", name); - break; - } - else - Com_Printf("Not found: %s\n", name); - i++; - } -} - -/* -==================== -CL_PlayDemo_f - -demo - -==================== -*/ -void CL_PlayDemo_f( void ) { - char name[MAX_OSPATH]; - char *arg, *ext_test; - int protocol, i; - char retry[MAX_OSPATH]; - - if (Cmd_Argc() != 2) { - Com_Printf ("playdemo \n"); - return; - } - - // make sure a local server is killed - Cvar_Set( "sv_killserver", "1" ); - - CL_Disconnect( qtrue ); - - // open the demo file - arg = Cmd_Argv(1); - - // check for an extension .dm_?? (?? is protocol) - ext_test = arg + strlen(arg) - 6; - if ((strlen(arg) > 6) && (ext_test[0] == '.') && ((ext_test[1] == 'd') || (ext_test[1] == 'D')) && ((ext_test[2] == 'm') || (ext_test[2] == 'M')) && (ext_test[3] == '_')) - { - protocol = atoi(ext_test+4); - i=0; - while(demo_protocols[i]) - { - if (demo_protocols[i] == protocol) - break; - i++; - } - if (demo_protocols[i]) - { - Com_sprintf (name, sizeof(name), "demos/%s", arg); - FS_FOpenFileRead( name, &clc.demofile, qtrue ); - } else { - Com_Printf("Protocol %d not supported for demos\n", protocol); - Q_strncpyz(retry, arg, sizeof(retry)); - retry[strlen(retry)-6] = 0; - CL_WalkDemoExt( retry, name, &clc.demofile ); - } - } else { - CL_WalkDemoExt( arg, name, &clc.demofile ); - } - - if (!clc.demofile) { - Com_Error( ERR_DROP, "couldn't open %s", name); - return; - } - Q_strncpyz( clc.demoName, Cmd_Argv(1), sizeof( clc.demoName ) ); - - Con_Close(); - - cls.state = CA_CONNECTED; - clc.demoplaying = qtrue; - Q_strncpyz( cls.servername, Cmd_Argv(1), sizeof( cls.servername ) ); - - // read demo messages until connected - while ( cls.state >= CA_CONNECTED && cls.state < CA_PRIMED ) { - CL_ReadDemoMessage(); - } - // don't get the first snapshot this frame, to prevent the long - // time from the gamestate load from messing causing a time skip - clc.firstDemoFrameSkipped = qfalse; -} - - -/* -==================== -CL_StartDemoLoop - -Closing the main menu will restart the demo loop -==================== -*/ -void CL_StartDemoLoop( void ) { - // start the demo loop again - Cbuf_AddText ("d1\n"); - cls.keyCatchers = 0; -} - -/* -================== -CL_NextDemo - -Called when a demo or cinematic finishes -If the "nextdemo" cvar is set, that command will be issued -================== -*/ -void CL_NextDemo( void ) { - char v[MAX_STRING_CHARS]; - - Q_strncpyz( v, Cvar_VariableString ("nextdemo"), sizeof(v) ); - v[MAX_STRING_CHARS-1] = 0; - Com_DPrintf("CL_NextDemo: %s\n", v ); - if (!v[0]) { - return; - } - - Cvar_Set ("nextdemo",""); - Cbuf_AddText (v); - Cbuf_AddText ("\n"); - Cbuf_Execute(); -} - - -//====================================================================== - -/* -===================== -CL_ShutdownAll -===================== -*/ -void CL_ShutdownAll(void) { - - // clear sounds - S_DisableSounds(); - // shutdown CGame - CL_ShutdownCGame(); - // shutdown UI - CL_ShutdownUI(); - - // shutdown the renderer - if ( re.Shutdown ) { - re.Shutdown( qfalse ); // don't destroy window or context - } - - cls.uiStarted = qfalse; - cls.cgameStarted = qfalse; - cls.rendererStarted = qfalse; - cls.soundRegistered = qfalse; -} - -/* -================= -CL_FlushMemory - -Called by CL_MapLoading, CL_Connect_f, CL_PlayDemo_f, and CL_ParseGamestate the only -ways a client gets into a game -Also called by Com_Error -================= -*/ -void CL_FlushMemory( void ) { - - // shutdown all the client stuff - CL_ShutdownAll(); - - // if not running a server clear the whole hunk - if ( !com_sv_running->integer ) { - // clear the whole hunk - Hunk_Clear(); - // clear collision map data - CM_ClearMap(); - } - else { - // clear all the client data on the hunk - Hunk_ClearToMark(); - } - - CL_StartHunkUsers(); -} - -/* -===================== -CL_MapLoading - -A local server is starting to load a map, so update the -screen to let the user know about it, then dump all client -memory on the hunk from cgame, ui, and renderer -===================== -*/ -void CL_MapLoading( void ) { - if ( !com_cl_running->integer ) { - return; - } - - Con_Close(); - cls.keyCatchers = 0; - - // if we are already connected to the local host, stay connected - if ( cls.state >= CA_CONNECTED && !Q_stricmp( cls.servername, "localhost" ) ) { - cls.state = CA_CONNECTED; // so the connect screen is drawn - Com_Memset( cls.updateInfoString, 0, sizeof( cls.updateInfoString ) ); - Com_Memset( clc.serverMessage, 0, sizeof( clc.serverMessage ) ); - Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) ); - clc.lastPacketSentTime = -9999; - SCR_UpdateScreen(); - } else { - // clear nextmap so the cinematic shutdown doesn't execute it - Cvar_Set( "nextmap", "" ); - CL_Disconnect( qtrue ); - Q_strncpyz( cls.servername, "localhost", sizeof(cls.servername) ); - cls.state = CA_CHALLENGING; // so the connect screen is drawn - cls.keyCatchers = 0; - SCR_UpdateScreen(); - clc.connectTime = -RETRANSMIT_TIMEOUT; - NET_StringToAdr( cls.servername, &clc.serverAddress); - // we don't need a challenge on the localhost - - CL_CheckForResend(); - } -} - -/* -===================== -CL_ClearState - -Called before parsing a gamestate -===================== -*/ -void CL_ClearState (void) { - -// S_StopAllSounds(); - - Com_Memset( &cl, 0, sizeof( cl ) ); -} - - -/* -===================== -CL_Disconnect - -Called when a connection, demo, or cinematic is being terminated. -Goes from a connected state to either a menu state or a console state -Sends a disconnect message to the server -This is also called on Com_Error and Com_Quit, so it shouldn't cause any errors -===================== -*/ -void CL_Disconnect( qboolean showMainMenu ) { - if ( !com_cl_running || !com_cl_running->integer ) { - return; - } - - // shutting down the client so enter full screen ui mode - Cvar_Set("r_uiFullScreen", "1"); - - if ( clc.demorecording ) { - CL_StopRecord_f (); - } - - if (clc.download) { - FS_FCloseFile( clc.download ); - clc.download = 0; - } - *clc.downloadTempName = *clc.downloadName = 0; - Cvar_Set( "cl_downloadName", "" ); - - if ( clc.demofile ) { - FS_FCloseFile( clc.demofile ); - clc.demofile = 0; - } - - if ( uivm && showMainMenu ) { - VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE ); - } - - SCR_StopCinematic (); - S_ClearSoundBuffer(); - - // send a disconnect message to the server - // send it a few times in case one is dropped - if ( cls.state >= CA_CONNECTED ) { - CL_AddReliableCommand( "disconnect" ); - CL_WritePacket(); - CL_WritePacket(); - CL_WritePacket(); - } - - CL_ClearState (); - - // wipe the client connection - Com_Memset( &clc, 0, sizeof( clc ) ); - - cls.state = CA_DISCONNECTED; - - // allow cheats locally - Cvar_Set( "sv_cheats", "1" ); - - // not connected to a pure server anymore - cl_connectedToPureServer = qfalse; -} - - -/* -=================== -CL_ForwardCommandToServer - -adds the current command line as a clientCommand -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 CL_ForwardCommandToServer( const char *string ) { - char *cmd; - - cmd = Cmd_Argv(0); - - // ignore key up commands - if ( cmd[0] == '-' ) { - return; - } - - if ( clc.demoplaying || cls.state < CA_CONNECTED || cmd[0] == '+' ) { - Com_Printf ("Unknown command \"%s\"\n", cmd); - return; - } - - if ( Cmd_Argc() > 1 ) { - CL_AddReliableCommand( string ); - } else { - CL_AddReliableCommand( cmd ); - } -} - -/* -=================== -CL_RequestMotd - -=================== -*/ -void CL_RequestMotd( void ) { - char info[MAX_INFO_STRING]; - - if ( !cl_motd->integer ) { - return; - } - Com_Printf( "Resolving %s\n", UPDATE_SERVER_NAME ); - if ( !NET_StringToAdr( UPDATE_SERVER_NAME, &cls.updateServer ) ) { - Com_Printf( "Couldn't resolve address\n" ); - return; - } - cls.updateServer.port = BigShort( PORT_UPDATE ); - Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", UPDATE_SERVER_NAME, - cls.updateServer.ip[0], cls.updateServer.ip[1], - cls.updateServer.ip[2], cls.updateServer.ip[3], - BigShort( cls.updateServer.port ) ); - - info[0] = 0; - // NOTE TTimo xoring against Com_Milliseconds, otherwise we may not have a true randomization - // only srand I could catch before here is tr_noise.c l:26 srand(1001) - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=382 - // NOTE: the Com_Milliseconds xoring only affects the lower 16-bit word, - // but I decided it was enough randomization - Com_sprintf( cls.updateChallenge, sizeof( cls.updateChallenge ), "%i", ((rand() << 16) ^ rand()) ^ Com_Milliseconds()); - - Info_SetValueForKey( info, "challenge", cls.updateChallenge ); - Info_SetValueForKey( info, "renderer", cls.glconfig.renderer_string ); - Info_SetValueForKey( info, "version", com_version->string ); - - NET_OutOfBandPrint( NS_CLIENT, cls.updateServer, "getmotd \"%s\"\n", info ); -} - -/* -=================== -CL_RequestAuthorization - -Authorization server protocol ------------------------------ - -All commands are text in Q3 out of band packets (leading 0xff 0xff 0xff 0xff). - -Whenever the client tries to get a challenge from the server it wants to -connect to, it also blindly fires off a packet to the authorize server: - -getKeyAuthorize - -cdkey may be "demo" - - -#OLD The authorize server returns a: -#OLD -#OLD keyAthorize -#OLD -#OLD A client will be accepted if the cdkey is valid and it has not been used by any other IP -#OLD address in the last 15 minutes. - - -The server sends a: - -getIpAuthorize - -The authorize server returns a: - -ipAuthorize - -A client will be accepted if a valid cdkey was sent by that ip (only) in the last 15 minutes. -If no response is received from the authorize server after two tries, the client will be let -in anyway. -=================== -*/ -void CL_RequestAuthorization( void ) { - char nums[64]; - int i, j, l; - cvar_t *fs; - - if ( !cls.authorizeServer.port ) { - Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); - if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &cls.authorizeServer ) ) { - Com_Printf( "Couldn't resolve address\n" ); - return; - } - - cls.authorizeServer.port = BigShort( PORT_AUTHORIZE ); - Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, - cls.authorizeServer.ip[0], cls.authorizeServer.ip[1], - cls.authorizeServer.ip[2], cls.authorizeServer.ip[3], - BigShort( cls.authorizeServer.port ) ); - } - if ( cls.authorizeServer.type == NA_BAD ) { - return; - } - - if ( Cvar_VariableValue( "fs_restrict" ) ) { - Q_strncpyz( nums, "demota", sizeof( nums ) ); - } else { - // only grab the alphanumeric values from the cdkey, to avoid any dashes or spaces - j = 0; - l = strlen( cl_cdkey ); - if ( l > 32 ) { - l = 32; - } - for ( i = 0 ; i < l ; i++ ) { - if ( ( cl_cdkey[i] >= '0' && cl_cdkey[i] <= '9' ) - || ( cl_cdkey[i] >= 'a' && cl_cdkey[i] <= 'z' ) - || ( cl_cdkey[i] >= 'A' && cl_cdkey[i] <= 'Z' ) - ) { - nums[j] = cl_cdkey[i]; - j++; - } - } - nums[j] = 0; - } - - fs = Cvar_Get ("cl_anonymous", "0", CVAR_INIT|CVAR_SYSTEMINFO ); - - NET_OutOfBandPrint(NS_CLIENT, cls.authorizeServer, va("getKeyAuthorize %i %s", fs->integer, nums) ); -} - -/* -====================================================================== - -CONSOLE COMMANDS - -====================================================================== -*/ - -/* -================== -CL_ForwardToServer_f -================== -*/ -void CL_ForwardToServer_f( void ) { - if ( cls.state != CA_ACTIVE || clc.demoplaying ) { - Com_Printf ("Not connected to a server.\n"); - return; - } - - // don't forward the first argument - if ( Cmd_Argc() > 1 ) { - CL_AddReliableCommand( Cmd_Args() ); - } -} - -/* -================== -CL_Setenv_f - -Mostly for controlling voodoo environment variables -================== -*/ -void CL_Setenv_f( void ) { - int argc = Cmd_Argc(); - - if ( argc > 2 ) { - char buffer[1024]; - int i; - - strcpy( buffer, Cmd_Argv(1) ); - strcat( buffer, "=" ); - - for ( i = 2; i < argc; i++ ) { - strcat( buffer, Cmd_Argv( i ) ); - strcat( buffer, " " ); - } - - 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), env ); - } - } -} - - -/* -================== -CL_Disconnect_f -================== -*/ -void CL_Disconnect_f( void ) { - SCR_StopCinematic(); - Cvar_Set("ui_singlePlayerActive", "0"); - if ( cls.state != CA_DISCONNECTED && cls.state != CA_CINEMATIC ) { - Com_Error (ERR_DISCONNECT, "Disconnected from server"); - } -} - - -/* -================ -CL_Reconnect_f - -================ -*/ -void CL_Reconnect_f( void ) { - if ( !strlen( cls.servername ) || !strcmp( cls.servername, "localhost" ) ) { - Com_Printf( "Can't reconnect to localhost.\n" ); - return; - } - Cvar_Set("ui_singlePlayerActive", "0"); - Cbuf_AddText( va("connect %s\n", cls.servername ) ); -} - -/* -================ -CL_Connect_f - -================ -*/ -void CL_Connect_f( void ) { - char *server; - - if ( Cmd_Argc() != 2 ) { - Com_Printf( "usage: connect [server]\n"); - return; - } - - Cvar_Set("ui_singlePlayerActive", "0"); - - // fire a message off to the motd server - CL_RequestMotd(); - - // clear any previous "server full" type messages - clc.serverMessage[0] = 0; - - server = Cmd_Argv (1); - - if ( com_sv_running->integer && !strcmp( server, "localhost" ) ) { - // if running a local server, kill it - SV_Shutdown( "Server quit\n" ); - } - - // make sure a local server is killed - Cvar_Set( "sv_killserver", "1" ); - SV_Frame( 0 ); - - CL_Disconnect( qtrue ); - Con_Close(); - - /* MrE: 2000-09-13: now called in CL_DownloadsComplete - CL_FlushMemory( ); - */ - - Q_strncpyz( cls.servername, server, sizeof(cls.servername) ); - - if (!NET_StringToAdr( cls.servername, &clc.serverAddress) ) { - Com_Printf ("Bad server address\n"); - cls.state = CA_DISCONNECTED; - return; - } - if (clc.serverAddress.port == 0) { - clc.serverAddress.port = BigShort( PORT_SERVER ); - } - Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", cls.servername, - clc.serverAddress.ip[0], clc.serverAddress.ip[1], - clc.serverAddress.ip[2], clc.serverAddress.ip[3], - BigShort( clc.serverAddress.port ) ); - - // if we aren't playing on a lan, we need to authenticate - // with the cd key - if ( NET_IsLocalAddress( clc.serverAddress ) ) { - cls.state = CA_CHALLENGING; - } else { - cls.state = CA_CONNECTING; - } - - cls.keyCatchers = 0; - clc.connectTime = -99999; // CL_CheckForResend() will fire immediately - clc.connectPacketCount = 0; - - // server connection string - Cvar_Set( "cl_currentServerAddress", server ); -} - - -/* -===================== -CL_Rcon_f - - Send the rest of the command line over as - an unconnected command. -===================== -*/ -void CL_Rcon_f( void ) { - char message[1024]; - netadr_t to; - - if ( !rcon_client_password->string ) { - Com_Printf ("You must set 'rconpassword' before\n" - "issuing an rcon command.\n"); - return; - } - - message[0] = -1; - message[1] = -1; - message[2] = -1; - message[3] = -1; - message[4] = 0; - - strcat (message, "rcon "); - - strcat (message, rcon_client_password->string); - strcat (message, " "); - - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 - strcat (message, Cmd_Cmd()+5); - - if ( cls.state >= CA_CONNECTED ) { - to = clc.netchan.remoteAddress; - } else { - if (!strlen(rconAddress->string)) { - Com_Printf ("You must either be connected,\n" - "or set the 'rconAddress' cvar\n" - "to issue rcon commands\n"); - - return; - } - NET_StringToAdr (rconAddress->string, &to); - if (to.port == 0) { - to.port = BigShort (PORT_SERVER); - } - } - - NET_SendPacket (NS_CLIENT, strlen(message)+1, message, to); -} - -/* -================= -CL_SendPureChecksums -================= -*/ -void CL_SendPureChecksums( void ) { - const char *pChecksums; - char cMsg[MAX_INFO_VALUE]; - int i; - - // if we are pure we need to send back a command with our referenced pk3 checksums - pChecksums = FS_ReferencedPakPureChecksums(); - - // "cp" - // "Yf" - Com_sprintf(cMsg, sizeof(cMsg), "Yf "); - Q_strcat(cMsg, sizeof(cMsg), va("%d ", cl.serverId) ); - Q_strcat(cMsg, sizeof(cMsg), pChecksums); - for (i = 0; i < 2; i++) { - cMsg[i] += 10; - } - CL_AddReliableCommand( cMsg ); -} - -/* -================= -CL_ResetPureClientAtServer -================= -*/ -void CL_ResetPureClientAtServer( void ) { - CL_AddReliableCommand( va("vdr") ); -} - -/* -================= -CL_Vid_Restart_f - -Restart the video subsystem - -we also have to reload the UI and CGame because the renderer -doesn't know what graphics to reload -================= -*/ -void CL_Vid_Restart_f( void ) { - - // don't let them loop during the restart - S_StopAllSounds(); - // shutdown the UI - CL_ShutdownUI(); - // shutdown the CGame - CL_ShutdownCGame(); - // shutdown the renderer and clear the renderer interface - CL_ShutdownRef(); - // client is no longer pure untill new checksums are sent - CL_ResetPureClientAtServer(); - // clear pak references - FS_ClearPakReferences( FS_UI_REF | FS_CGAME_REF ); - // reinitialize the filesystem if the game directory or checksum has changed - FS_ConditionalRestart( clc.checksumFeed ); - - cls.rendererStarted = qfalse; - cls.uiStarted = qfalse; - cls.cgameStarted = qfalse; - cls.soundRegistered = qfalse; - - // unpause so the cgame definately gets a snapshot and renders a frame - Cvar_Set( "cl_paused", "0" ); - - // if not running a server clear the whole hunk - if ( !com_sv_running->integer ) { - // clear the whole hunk - Hunk_Clear(); - } - else { - // clear all the client data on the hunk - Hunk_ClearToMark(); - } - - // initialize the renderer interface - CL_InitRef(); - - // startup all the client stuff - CL_StartHunkUsers(); - - // start the cgame if connected - if ( cls.state > CA_CONNECTED && cls.state != CA_CINEMATIC ) { - cls.cgameStarted = qtrue; - CL_InitCGame(); - // send pure checksums - CL_SendPureChecksums(); - } -} - -/* -================= -CL_Snd_Restart_f - -Restart the sound subsystem -The cgame and game must also be forced to restart because -handles will be invalid -================= -*/ -void CL_Snd_Restart_f( void ) { - S_Shutdown(); - S_Init(); - - CL_Vid_Restart_f(); -} - - -/* -================== -CL_PK3List_f -================== -*/ -void CL_OpenedPK3List_f( void ) { - Com_Printf("Opened PK3 Names: %s\n", FS_LoadedPakNames()); -} - -/* -================== -CL_PureList_f -================== -*/ -void CL_ReferencedPK3List_f( void ) { - Com_Printf("Referenced PK3 Names: %s\n", FS_ReferencedPakNames()); -} - -/* -================== -CL_Configstrings_f -================== -*/ -void CL_Configstrings_f( void ) { - int i; - int ofs; - - if ( cls.state != CA_ACTIVE ) { - Com_Printf( "Not connected to a server.\n"); - return; - } - - for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { - ofs = cl.gameState.stringOffsets[ i ]; - if ( !ofs ) { - continue; - } - Com_Printf( "%4i: %s\n", i, cl.gameState.stringData + ofs ); - } -} - -/* -============== -CL_Clientinfo_f -============== -*/ -void CL_Clientinfo_f( void ) { - Com_Printf( "--------- Client Information ---------\n" ); - Com_Printf( "state: %i\n", cls.state ); - Com_Printf( "Server: %s\n", cls.servername ); - Com_Printf ("User info settings:\n"); - Info_Print( Cvar_InfoString( CVAR_USERINFO ) ); - Com_Printf( "--------------------------------------\n" ); -} - - -//==================================================================== - -/* -================= -CL_DownloadsComplete - -Called when all downloading has been completed -================= -*/ -void CL_DownloadsComplete( void ) { - - // if we downloaded files we need to restart the file system - if (clc.downloadRestart) { - clc.downloadRestart = qfalse; - - FS_Restart(clc.checksumFeed); // We possibly downloaded a pak, restart the file system to load it - - // inform the server so we get new gamestate info - CL_AddReliableCommand( "donedl" ); - - // by sending the donedl command we request a new gamestate - // so we don't want to load stuff yet - return; - } - - // let the client game init and load data - cls.state = CA_LOADING; - - // Pump the loop, this may change gamestate! - Com_EventLoop(); - - // if the gamestate was changed by calling Com_EventLoop - // then we loaded everything already and we don't want to do it again. - if ( cls.state != CA_LOADING ) { - return; - } - - // starting to load a map so we get out of full screen ui mode - Cvar_Set("r_uiFullScreen", "0"); - - // flush client memory and start loading stuff - // this will also (re)load the UI - // if this is a local client then only the client part of the hunk - // will be cleared, note that this is done after the hunk mark has been set - CL_FlushMemory(); - - // initialize the CGame - cls.cgameStarted = qtrue; - CL_InitCGame(); - - // set pure checksums - CL_SendPureChecksums(); - - CL_WritePacket(); - CL_WritePacket(); - CL_WritePacket(); -} - -/* -================= -CL_BeginDownload - -Requests a file to download from the server. Stores it in the current -game directory. -================= -*/ -void CL_BeginDownload( const char *localName, const char *remoteName ) { - - Com_DPrintf("***** CL_BeginDownload *****\n" - "Localname: %s\n" - "Remotename: %s\n" - "****************************\n", localName, remoteName); - - Q_strncpyz ( clc.downloadName, localName, sizeof(clc.downloadName) ); - Com_sprintf( clc.downloadTempName, sizeof(clc.downloadTempName), "%s.tmp", localName ); - - // Set so UI gets access to it - Cvar_Set( "cl_downloadName", remoteName ); - Cvar_Set( "cl_downloadSize", "0" ); - Cvar_Set( "cl_downloadCount", "0" ); - Cvar_SetValue( "cl_downloadTime", cls.realtime ); - - clc.downloadBlock = 0; // Starting new file - clc.downloadCount = 0; - - CL_AddReliableCommand( va("download %s", remoteName) ); -} - -/* -================= -CL_NextDownload - -A download completed or failed -================= -*/ -void CL_NextDownload(void) { - char *s; - char *remoteName, *localName; - - // We are looking to start a download here - if (*clc.downloadList) { - s = clc.downloadList; - - // format is: - // @remotename@localname@remotename@localname, etc. - - if (*s == '@') - s++; - remoteName = s; - - if ( (s = strchr(s, '@')) == NULL ) { - CL_DownloadsComplete(); - return; - } - - *s++ = 0; - localName = s; - if ( (s = strchr(s, '@')) != NULL ) - *s++ = 0; - else - s = localName + strlen(localName); // point at the nul byte - - CL_BeginDownload( localName, remoteName ); - - clc.downloadRestart = qtrue; - - // move over the rest - memmove( clc.downloadList, s, strlen(s) + 1); - - return; - } - - CL_DownloadsComplete(); -} - -/* -================= -CL_InitDownloads - -After receiving a valid game state, we valid the cgame and local zip files here -and determine if we need to download them -================= -*/ -void CL_InitDownloads(void) { - char missingfiles[1024]; - - if ( !cl_allowDownload->integer ) - { - // autodownload is disabled on the client - // but it's possible that some referenced files on the server are missing - if (FS_ComparePaks( missingfiles, sizeof( missingfiles ), qfalse ) ) - { - // NOTE TTimo I would rather have that printed as a modal message box - // but at this point while joining the game we don't know wether we will successfully join or not - Com_Printf( "\nWARNING: You are missing some files referenced by the server:\n%s" - "You might not be able to join the game\n" - "Go to the setting menu to turn on autodownload, or get the file elsewhere\n\n", missingfiles ); - } - } - else if ( FS_ComparePaks( clc.downloadList, sizeof( clc.downloadList ) , qtrue ) ) { - - Com_Printf("Need paks: %s\n", clc.downloadList ); - - if ( *clc.downloadList ) { - // if autodownloading is not enabled on the server - cls.state = CA_CONNECTED; - CL_NextDownload(); - return; - } - - } - - CL_DownloadsComplete(); -} - -/* -================= -CL_CheckForResend - -Resend a connect message if the last one has timed out -================= -*/ -void CL_CheckForResend( void ) { - int port, i; - char info[MAX_INFO_STRING]; - char data[MAX_INFO_STRING]; - - // don't send anything if playing back a demo - if ( clc.demoplaying ) { - return; - } - - // resend if we haven't gotten a reply yet - if ( cls.state != CA_CONNECTING && cls.state != CA_CHALLENGING ) { - return; - } - - if ( cls.realtime - clc.connectTime < RETRANSMIT_TIMEOUT ) { - return; - } - - clc.connectTime = cls.realtime; // for retransmit requests - clc.connectPacketCount++; - - - switch ( cls.state ) { - case CA_CONNECTING: - // requesting a challenge - if ( !Sys_IsLANAddress( clc.serverAddress ) ) { - CL_RequestAuthorization(); - } - NET_OutOfBandPrint(NS_CLIENT, clc.serverAddress, "getchallenge"); - break; - - case CA_CHALLENGING: - // sending back the challenge - port = Cvar_VariableValue ("net_qport"); - - Q_strncpyz( info, Cvar_InfoString( CVAR_USERINFO ), sizeof( info ) ); - Info_SetValueForKey( info, "protocol", va("%i", PROTOCOL_VERSION ) ); - Info_SetValueForKey( info, "qport", va("%i", port ) ); - Info_SetValueForKey( info, "challenge", va("%i", clc.challenge ) ); - - strcpy(data, "connect "); - // TTimo adding " " around the userinfo string to avoid truncated userinfo on the server - // (Com_TokenizeString tokenizes around spaces) - data[8] = '"'; - - for(i=0;iadr.type = NA_IP; - server->adr.ip[0] = address->ip[0]; - server->adr.ip[1] = address->ip[1]; - server->adr.ip[2] = address->ip[2]; - server->adr.ip[3] = address->ip[3]; - server->adr.port = address->port; - server->clients = 0; - server->hostName[0] = '\0'; - server->mapName[0] = '\0'; - server->maxClients = 0; - server->maxPing = 0; - server->minPing = 0; - server->ping = -1; - server->game[0] = '\0'; - server->gameType = 0; - server->netType = 0; -} - -#define MAX_SERVERSPERPACKET 256 - -/* -=================== -CL_ServersResponsePacket -=================== -*/ -void CL_ServersResponsePacket( netadr_t from, msg_t *msg ) { - int i, count, max, total; - serverAddress_t addresses[MAX_SERVERSPERPACKET]; - int numservers; - byte* buffptr; - byte* buffend; - - Com_Printf("CL_ServersResponsePacket\n"); - - if (cls.numglobalservers == -1) { - // state to detect lack of servers or lack of response - cls.numglobalservers = 0; - cls.numGlobalServerAddresses = 0; - } - - if (cls.nummplayerservers == -1) { - cls.nummplayerservers = 0; - } - - // parse through server response string - numservers = 0; - buffptr = msg->data; - buffend = buffptr + msg->cursize; - while (buffptr+1 < buffend) { - // advance to initial token - do { - if (*buffptr++ == '\\') - break; - } - while (buffptr < buffend); - - if ( buffptr >= buffend - 6 ) { - break; - } - - // parse out ip - addresses[numservers].ip[0] = *buffptr++; - addresses[numservers].ip[1] = *buffptr++; - addresses[numservers].ip[2] = *buffptr++; - addresses[numservers].ip[3] = *buffptr++; - - // parse out port - addresses[numservers].port = (*buffptr++)<<8; - addresses[numservers].port += *buffptr++; - addresses[numservers].port = BigShort( addresses[numservers].port ); - - // syntax check - if (*buffptr != '\\') { - break; - } - - Com_DPrintf( "server: %d ip: %d.%d.%d.%d:%d\n",numservers, - addresses[numservers].ip[0], - addresses[numservers].ip[1], - addresses[numservers].ip[2], - addresses[numservers].ip[3], - addresses[numservers].port ); - - numservers++; - if (numservers >= MAX_SERVERSPERPACKET) { - break; - } - - // parse out EOT - if (buffptr[1] == 'E' && buffptr[2] == 'O' && buffptr[3] == 'T') { - break; - } - } - - if (cls.masterNum == 0) { - count = cls.numglobalservers; - max = MAX_GLOBAL_SERVERS; - } else { - count = cls.nummplayerservers; - max = MAX_OTHER_SERVERS; - } - - for (i = 0; i < numservers && count < max; i++) { - // build net address - serverInfo_t *server = (cls.masterNum == 0) ? &cls.globalServers[count] : &cls.mplayerServers[count]; - - CL_InitServerInfo( server, &addresses[i] ); - // advance to next slot - count++; - } - - // if getting the global list - if (cls.masterNum == 0) { - if ( cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS ) { - // if we couldn't store the servers in the main list anymore - for (; i < numservers && count >= max; i++) { - serverAddress_t *addr; - // just store the addresses in an additional list - addr = &cls.globalServerAddresses[cls.numGlobalServerAddresses++]; - addr->ip[0] = addresses[i].ip[0]; - addr->ip[1] = addresses[i].ip[1]; - addr->ip[2] = addresses[i].ip[2]; - addr->ip[3] = addresses[i].ip[3]; - addr->port = addresses[i].port; - } - } - } - - if (cls.masterNum == 0) { - cls.numglobalservers = count; - total = count + cls.numGlobalServerAddresses; - } else { - cls.nummplayerservers = count; - total = count; - } - - Com_Printf("%d servers parsed (total %d)\n", numservers, total); -} - -/* -================= -CL_ConnectionlessPacket - -Responses to broadcasts, etc -================= -*/ -void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { - char *s; - char *c; - - MSG_BeginReadingOOB( msg ); - MSG_ReadLong( msg ); // skip the -1 - - s = MSG_ReadStringLine( msg ); - - Cmd_TokenizeString( s ); - - c = Cmd_Argv(0); - - Com_DPrintf ("CL packet %s: %s\n", NET_AdrToString(from), c); - - // challenge from the server we are connecting to - if ( !Q_stricmp(c, "challengeResponse") ) { - if ( cls.state != CA_CONNECTING ) { - Com_Printf( "Unwanted challenge response received. Ignored.\n" ); - } else { - // start sending challenge repsonse instead of challenge request packets - clc.challenge = atoi(Cmd_Argv(1)); - cls.state = CA_CHALLENGING; - clc.connectPacketCount = 0; - clc.connectTime = -99999; - - // take this address as the new server address. This allows - // a server proxy to hand off connections to multiple servers - clc.serverAddress = from; - Com_DPrintf ("challengeResponse: %d\n", clc.challenge); - } - return; - } - - // server connection - if ( !Q_stricmp(c, "connectResponse") ) { - if ( cls.state >= CA_CONNECTED ) { - Com_Printf ("Dup connect received. Ignored.\n"); - return; - } - if ( cls.state != CA_CHALLENGING ) { - Com_Printf ("connectResponse packet while not connecting. Ignored.\n"); - return; - } - if ( !NET_CompareBaseAdr( from, clc.serverAddress ) ) { - Com_Printf( "connectResponse from a different address. Ignored.\n" ); - Com_Printf( "%s should have been %s\n", NET_AdrToString( from ), - NET_AdrToString( clc.serverAddress ) ); - return; - } - Netchan_Setup (NS_CLIENT, &clc.netchan, from, Cvar_VariableValue( "net_qport" ) ); - cls.state = CA_CONNECTED; - clc.lastPacketSentTime = -9999; // send first packet immediately - return; - } - - // server responding to an info broadcast - if ( !Q_stricmp(c, "infoResponse") ) { - CL_ServerInfoPacket( from, msg ); - return; - } - - // server responding to a get playerlist - if ( !Q_stricmp(c, "statusResponse") ) { - CL_ServerStatusResponse( from, msg ); - return; - } - - // a disconnect message from the server, which will happen if the server - // dropped the connection but it is still getting packets from us - if (!Q_stricmp(c, "disconnect")) { - CL_DisconnectPacket( from ); - return; - } - - // echo request from server - if ( !Q_stricmp(c, "echo") ) { - NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) ); - return; - } - - // cd check - if ( !Q_stricmp(c, "keyAuthorize") ) { - // we don't use these now, so dump them on the floor - return; - } - - // global MOTD from id - if ( !Q_stricmp(c, "motd") ) { - CL_MotdPacket( from ); - return; - } - - // echo request from server - if ( !Q_stricmp(c, "print") ) { - s = MSG_ReadString( msg ); - Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) ); - Com_Printf( "%s", s ); - return; - } - - // echo request from server - if ( !Q_strncmp(c, "getserversResponse", 18) ) { - CL_ServersResponsePacket( from, msg ); - return; - } - - Com_DPrintf ("Unknown connectionless packet command.\n"); -} - - -/* -================= -CL_PacketEvent - -A packet has arrived from the main event loop -================= -*/ -void CL_PacketEvent( netadr_t from, msg_t *msg ) { - int headerBytes; - - clc.lastPacketTime = cls.realtime; - - if ( msg->cursize >= 4 && *(int *)msg->data == -1 ) { - CL_ConnectionlessPacket( from, msg ); - return; - } - - if ( cls.state < CA_CONNECTED ) { - return; // can't be a valid sequenced packet - } - - if ( msg->cursize < 4 ) { - Com_Printf ("%s: Runt packet\n",NET_AdrToString( from )); - return; - } - - // - // packet from server - // - if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) { - Com_DPrintf ("%s:sequenced packet without connection\n" - ,NET_AdrToString( from ) ); - // FIXME: send a client disconnect? - return; - } - - if (!CL_Netchan_Process( &clc.netchan, msg) ) { - return; // out of order, duplicated, etc - } - - // the header is different lengths for reliable and unreliable messages - headerBytes = msg->readcount; - - // track the last message received so it can be returned in - // client messages, allowing the server to detect a dropped - // gamestate - clc.serverMessageSequence = LittleLong( *(int *)msg->data ); - - clc.lastPacketTime = cls.realtime; - CL_ParseServerMessage( msg ); - - // - // we don't know if it is ok to save a demo message until - // after we have parsed the frame - // - if ( clc.demorecording && !clc.demowaiting ) { - CL_WriteDemoMessage( msg, headerBytes ); - } -} - -/* -================== -CL_CheckTimeout - -================== -*/ -void CL_CheckTimeout( void ) { - // - // check timeout - // - if ( ( !cl_paused->integer || !sv_paused->integer ) - && cls.state >= CA_CONNECTED && cls.state != CA_CINEMATIC - && cls.realtime - clc.lastPacketTime > cl_timeout->value*1000) { - if (++cl.timeoutcount > 5) { // timeoutcount saves debugger - Com_Printf ("\nServer connection timed out.\n"); - CL_Disconnect( qtrue ); - return; - } - } else { - cl.timeoutcount = 0; - } -} - - -//============================================================================ - -/* -================== -CL_CheckUserinfo - -================== -*/ -void CL_CheckUserinfo( void ) { - // don't add reliable commands when not yet connected - if ( cls.state < CA_CHALLENGING ) { - return; - } - // don't overflow the reliable command buffer when paused - if ( cl_paused->integer ) { - return; - } - // send a reliable userinfo update if needed - if ( cvar_modifiedFlags & CVAR_USERINFO ) { - cvar_modifiedFlags &= ~CVAR_USERINFO; - CL_AddReliableCommand( va("userinfo \"%s\"", Cvar_InfoString( CVAR_USERINFO ) ) ); - } - -} - -/* -================== -CL_Frame - -================== -*/ -void CL_Frame ( int msec ) { - - if ( !com_cl_running->integer ) { - return; - } - - if ( cls.cddialog ) { - // bring up the cd error dialog if needed - cls.cddialog = qfalse; - VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NEED_CD ); - } else if ( cls.state == CA_DISCONNECTED && !( cls.keyCatchers & KEYCATCH_UI ) - && !com_sv_running->integer ) { - // if disconnected, bring up the menu - S_StopAllSounds(); - VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); - } - - // if recording an avi, lock to a fixed fps - if ( cl_avidemo->integer && msec) { - // save the current screen - if ( cls.state == CA_ACTIVE || cl_forceavidemo->integer) { - Cbuf_ExecuteText( EXEC_NOW, "screenshot silent\n" ); - } - // fixed time for next frame' - msec = (1000 / cl_avidemo->integer) * com_timescale->value; - if (msec == 0) { - msec = 1; - } - } - - // save the msec before checking pause - cls.realFrametime = msec; - - // decide the simulation time - cls.frametime = msec; - - cls.realtime += cls.frametime; - - if ( cl_timegraph->integer ) { - SCR_DebugGraph ( cls.realFrametime * 0.25, 0 ); - } - - // see if we need to update any userinfo - CL_CheckUserinfo(); - - // if we haven't gotten a packet in a long time, - // drop the connection - CL_CheckTimeout(); - - // send intentions now - CL_SendCmd(); - - // resend a connection request if necessary - CL_CheckForResend(); - - // decide on the serverTime to render - CL_SetCGameTime(); - - // update the screen - SCR_UpdateScreen(); - - // update audio - S_Update(); - - // advance local effects for next frame - SCR_RunCinematic(); - - Con_RunConsole(); - - cls.framecount++; -} - - -//============================================================================ - -/* -================ -CL_RefPrintf - -DLL glue -================ -*/ -void QDECL CL_RefPrintf( int print_level, const char *fmt, ...) { - va_list argptr; - char msg[MAXPRINTMSG]; - - va_start (argptr,fmt); - Q_vsnprintf (msg, sizeof(msg), fmt, argptr); - va_end (argptr); - - if ( print_level == PRINT_ALL ) { - Com_Printf ("%s", msg); - } else if ( print_level == PRINT_WARNING ) { - Com_Printf (S_COLOR_YELLOW "%s", msg); // yellow - } else if ( print_level == PRINT_DEVELOPER ) { - Com_DPrintf (S_COLOR_RED "%s", msg); // red - } -} - - - -/* -============ -CL_ShutdownRef -============ -*/ -void CL_ShutdownRef( void ) { - if ( !re.Shutdown ) { - return; - } - re.Shutdown( qtrue ); - Com_Memset( &re, 0, sizeof( re ) ); -} - -/* -============ -CL_InitRenderer -============ -*/ -void CL_InitRenderer( void ) { - // this sets up the renderer and calls R_Init - re.BeginRegistration( &cls.glconfig ); - - // load character sets - cls.charSetShader = re.RegisterShader( "gfx/2d/bigchars" ); - cls.whiteShader = re.RegisterShader( "white" ); - cls.consoleShader = re.RegisterShader( "console" ); - g_console_field_width = cls.glconfig.vidWidth / SMALLCHAR_WIDTH - 2; - g_consoleField.widthInChars = g_console_field_width; -} - -/* -============================ -CL_StartHunkUsers - -After the server has cleared the hunk, these will need to be restarted -This is the only place that any of these functions are called from -============================ -*/ -void CL_StartHunkUsers( void ) { - if (!com_cl_running) { - return; - } - - if ( !com_cl_running->integer ) { - return; - } - - if ( !cls.rendererStarted ) { - cls.rendererStarted = qtrue; - CL_InitRenderer(); - } - - if ( !cls.soundStarted ) { - cls.soundStarted = qtrue; - S_Init(); - } - - if ( !cls.soundRegistered ) { - cls.soundRegistered = qtrue; - S_BeginRegistration(); - } - - if ( !cls.uiStarted ) { - cls.uiStarted = qtrue; - CL_InitUI(); - } -} - -/* -============ -CL_RefMalloc -============ -*/ -void *CL_RefMalloc( int size ) { - return Z_TagMalloc( size, TAG_RENDERER ); -} - -int CL_ScaledMilliseconds(void) { - return Sys_Milliseconds()*com_timescale->value; -} - -/* -============ -CL_InitRef -============ -*/ -void CL_InitRef( void ) { - refimport_t ri; - refexport_t *ret; - - Com_Printf( "----- Initializing Renderer ----\n" ); - - ri.Cmd_AddCommand = Cmd_AddCommand; - ri.Cmd_RemoveCommand = Cmd_RemoveCommand; - ri.Cmd_Argc = Cmd_Argc; - ri.Cmd_Argv = Cmd_Argv; - ri.Cmd_ExecuteText = Cbuf_ExecuteText; - ri.Printf = CL_RefPrintf; - ri.Error = Com_Error; - ri.Milliseconds = CL_ScaledMilliseconds; - ri.Malloc = CL_RefMalloc; - ri.Free = Z_Free; -#ifdef HUNK_DEBUG - ri.Hunk_AllocDebug = Hunk_AllocDebug; -#else - ri.Hunk_Alloc = Hunk_Alloc; -#endif - ri.Hunk_AllocateTempMemory = Hunk_AllocateTempMemory; - ri.Hunk_FreeTempMemory = Hunk_FreeTempMemory; - ri.CM_DrawDebugSurface = CM_DrawDebugSurface; - ri.FS_ReadFile = FS_ReadFile; - ri.FS_FreeFile = FS_FreeFile; - ri.FS_WriteFile = FS_WriteFile; - ri.FS_FreeFileList = FS_FreeFileList; - ri.FS_ListFiles = FS_ListFiles; - ri.FS_FileIsInPAK = FS_FileIsInPAK; - ri.FS_FileExists = FS_FileExists; - ri.Cvar_Get = Cvar_Get; - ri.Cvar_Set = Cvar_Set; - - // cinematic stuff - - ri.CIN_UploadCinematic = CIN_UploadCinematic; - ri.CIN_PlayCinematic = CIN_PlayCinematic; - ri.CIN_RunCinematic = CIN_RunCinematic; - - ret = GetRefAPI( REF_API_VERSION, &ri ); - -#if defined __USEA3D && defined __A3D_GEOM - hA3Dg_ExportRenderGeom (ret); -#endif - - Com_Printf( "-------------------------------\n"); - - if ( !ret ) { - Com_Error (ERR_FATAL, "Couldn't initialize refresh" ); - } - - re = *ret; - - // unpause so the cgame definately gets a snapshot and renders a frame - Cvar_Set( "cl_paused", "0" ); -} - - -//=========================================================================================== - - -void CL_SetModel_f( void ) { - char *arg; - char name[256]; - - arg = Cmd_Argv( 1 ); - if (arg[0]) { - Cvar_Set( "model", arg ); - Cvar_Set( "headmodel", arg ); - } else { - Cvar_VariableStringBuffer( "model", name, sizeof(name) ); - Com_Printf("model is set to %s\n", name); - } -} - -/* -==================== -CL_Init -==================== -*/ -void CL_Init( void ) { - Com_Printf( "----- Client Initialization -----\n" ); - - Con_Init (); - - CL_ClearState (); - - cls.state = CA_DISCONNECTED; // no longer CA_UNINITIALIZED - - cls.realtime = 0; - - CL_InitInput (); - - // - // register our variables - // - cl_noprint = Cvar_Get( "cl_noprint", "0", 0 ); - cl_motd = Cvar_Get ("cl_motd", "1", 0); - - cl_timeout = Cvar_Get ("cl_timeout", "200", 0); - - cl_timeNudge = Cvar_Get ("cl_timeNudge", "0", CVAR_TEMP ); - cl_shownet = Cvar_Get ("cl_shownet", "0", CVAR_TEMP ); - cl_showSend = Cvar_Get ("cl_showSend", "0", CVAR_TEMP ); - cl_showTimeDelta = Cvar_Get ("cl_showTimeDelta", "0", CVAR_TEMP ); - cl_freezeDemo = Cvar_Get ("cl_freezeDemo", "0", CVAR_TEMP ); - rcon_client_password = Cvar_Get ("rconPassword", "", CVAR_TEMP ); - cl_activeAction = Cvar_Get( "activeAction", "", CVAR_TEMP ); - - cl_timedemo = Cvar_Get ("timedemo", "0", 0); - cl_avidemo = Cvar_Get ("cl_avidemo", "0", 0); - cl_forceavidemo = Cvar_Get ("cl_forceavidemo", "0", 0); - - rconAddress = Cvar_Get ("rconAddress", "", 0); - - cl_yawspeed = Cvar_Get ("cl_yawspeed", "140", CVAR_ARCHIVE); - cl_pitchspeed = Cvar_Get ("cl_pitchspeed", "140", CVAR_ARCHIVE); - cl_anglespeedkey = Cvar_Get ("cl_anglespeedkey", "1.5", 0); - - cl_maxpackets = Cvar_Get ("cl_maxpackets", "30", CVAR_ARCHIVE ); - cl_packetdup = Cvar_Get ("cl_packetdup", "1", CVAR_ARCHIVE ); - - cl_run = Cvar_Get ("cl_run", "1", CVAR_ARCHIVE); - cl_sensitivity = Cvar_Get ("sensitivity", "5", CVAR_ARCHIVE); - cl_mouseAccel = Cvar_Get ("cl_mouseAccel", "0", CVAR_ARCHIVE); - cl_freelook = Cvar_Get( "cl_freelook", "1", CVAR_ARCHIVE ); - - cl_showMouseRate = Cvar_Get ("cl_showmouserate", "0", 0); - - cl_allowDownload = Cvar_Get ("cl_allowDownload", "0", CVAR_ARCHIVE); - - cl_conXOffset = Cvar_Get ("cl_conXOffset", "0", 0); -#ifdef MACOS_X - // In game video is REALLY slow in Mac OS X right now due to driver slowness - cl_inGameVideo = Cvar_Get ("r_inGameVideo", "0", CVAR_ARCHIVE); -#else - cl_inGameVideo = Cvar_Get ("r_inGameVideo", "1", CVAR_ARCHIVE); -#endif - - cl_serverStatusResendTime = Cvar_Get ("cl_serverStatusResendTime", "750", 0); - - // init autoswitch so the ui will have it correctly even - // if the cgame hasn't been started - Cvar_Get ("cg_autoswitch", "1", CVAR_ARCHIVE); - - m_pitch = Cvar_Get ("m_pitch", "0.022", CVAR_ARCHIVE); - m_yaw = Cvar_Get ("m_yaw", "0.022", CVAR_ARCHIVE); - m_forward = Cvar_Get ("m_forward", "0.25", CVAR_ARCHIVE); - m_side = Cvar_Get ("m_side", "0.25", CVAR_ARCHIVE); -#ifdef MACOS_X - // Input is jittery on OS X w/o this - m_filter = Cvar_Get ("m_filter", "1", CVAR_ARCHIVE); -#else - m_filter = Cvar_Get ("m_filter", "0", CVAR_ARCHIVE); -#endif - - cl_motdString = Cvar_Get( "cl_motdString", "", CVAR_ROM ); - - Cvar_Get( "cl_maxPing", "800", CVAR_ARCHIVE ); - - - // userinfo - Cvar_Get ("name", "UnnamedPlayer", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("rate", "3000", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("snaps", "20", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("model", "sarge", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("headmodel", "sarge", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("team_model", "james", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("team_headmodel", "*james", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("g_redTeam", "Stroggs", CVAR_SERVERINFO | CVAR_ARCHIVE); - Cvar_Get ("g_blueTeam", "Pagans", CVAR_SERVERINFO | CVAR_ARCHIVE); - Cvar_Get ("color1", "4", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("color2", "5", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("handicap", "100", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("teamtask", "0", CVAR_USERINFO ); - Cvar_Get ("sex", "male", CVAR_USERINFO | CVAR_ARCHIVE ); - Cvar_Get ("cl_anonymous", "0", CVAR_USERINFO | CVAR_ARCHIVE ); - - Cvar_Get ("password", "", CVAR_USERINFO); - Cvar_Get ("cg_predictItems", "1", CVAR_USERINFO | CVAR_ARCHIVE ); - - - // cgame might not be initialized before menu is used - Cvar_Get ("cg_viewsize", "100", CVAR_ARCHIVE ); - - // - // register our commands - // - Cmd_AddCommand ("cmd", CL_ForwardToServer_f); - Cmd_AddCommand ("configstrings", CL_Configstrings_f); - Cmd_AddCommand ("clientinfo", CL_Clientinfo_f); - Cmd_AddCommand ("snd_restart", CL_Snd_Restart_f); - Cmd_AddCommand ("vid_restart", CL_Vid_Restart_f); - Cmd_AddCommand ("disconnect", CL_Disconnect_f); - Cmd_AddCommand ("record", CL_Record_f); - Cmd_AddCommand ("demo", CL_PlayDemo_f); - Cmd_AddCommand ("cinematic", CL_PlayCinematic_f); - Cmd_AddCommand ("stoprecord", CL_StopRecord_f); - Cmd_AddCommand ("connect", CL_Connect_f); - Cmd_AddCommand ("reconnect", CL_Reconnect_f); - Cmd_AddCommand ("localservers", CL_LocalServers_f); - Cmd_AddCommand ("globalservers", CL_GlobalServers_f); - Cmd_AddCommand ("rcon", CL_Rcon_f); - Cmd_AddCommand ("setenv", CL_Setenv_f ); - Cmd_AddCommand ("ping", CL_Ping_f ); - Cmd_AddCommand ("serverstatus", CL_ServerStatus_f ); - Cmd_AddCommand ("showip", CL_ShowIP_f ); - Cmd_AddCommand ("fs_openedList", CL_OpenedPK3List_f ); - Cmd_AddCommand ("fs_referencedList", CL_ReferencedPK3List_f ); - Cmd_AddCommand ("model", CL_SetModel_f ); - CL_InitRef(); - - SCR_Init (); - - Cbuf_Execute (); - - Cvar_Set( "cl_running", "1" ); - - Com_Printf( "----- Client Initialization Complete -----\n" ); -} - - -/* -=============== -CL_Shutdown - -=============== -*/ -void CL_Shutdown( void ) { - static qboolean recursive = qfalse; - - Com_Printf( "----- CL_Shutdown -----\n" ); - - if ( recursive ) { - printf ("recursive shutdown\n"); - return; - } - recursive = qtrue; - - CL_Disconnect( qtrue ); - - S_Shutdown(); - CL_ShutdownRef(); - - CL_ShutdownUI(); - - Cmd_RemoveCommand ("cmd"); - Cmd_RemoveCommand ("configstrings"); - Cmd_RemoveCommand ("userinfo"); - Cmd_RemoveCommand ("snd_restart"); - Cmd_RemoveCommand ("vid_restart"); - Cmd_RemoveCommand ("disconnect"); - Cmd_RemoveCommand ("record"); - Cmd_RemoveCommand ("demo"); - Cmd_RemoveCommand ("cinematic"); - Cmd_RemoveCommand ("stoprecord"); - Cmd_RemoveCommand ("connect"); - Cmd_RemoveCommand ("localservers"); - Cmd_RemoveCommand ("globalservers"); - Cmd_RemoveCommand ("rcon"); - Cmd_RemoveCommand ("setenv"); - Cmd_RemoveCommand ("ping"); - Cmd_RemoveCommand ("serverstatus"); - Cmd_RemoveCommand ("showip"); - Cmd_RemoveCommand ("model"); - - Cvar_Set( "cl_running", "0" ); - - recursive = qfalse; - - Com_Memset( &cls, 0, sizeof( cls ) ); - - Com_Printf( "-----------------------\n" ); - -} - -static void CL_SetServerInfo(serverInfo_t *server, const char *info, int ping) { - if (server) { - if (info) { - server->clients = atoi(Info_ValueForKey(info, "clients")); - Q_strncpyz(server->hostName,Info_ValueForKey(info, "hostname"), MAX_NAME_LENGTH); - Q_strncpyz(server->mapName, Info_ValueForKey(info, "mapname"), MAX_NAME_LENGTH); - server->maxClients = atoi(Info_ValueForKey(info, "sv_maxclients")); - Q_strncpyz(server->game,Info_ValueForKey(info, "game"), MAX_NAME_LENGTH); - server->gameType = atoi(Info_ValueForKey(info, "gametype")); - server->netType = atoi(Info_ValueForKey(info, "nettype")); - server->minPing = atoi(Info_ValueForKey(info, "minping")); - server->maxPing = atoi(Info_ValueForKey(info, "maxping")); - server->punkbuster = atoi(Info_ValueForKey(info, "punkbuster")); - } - server->ping = ping; - } -} - -static void CL_SetServerInfoByAddress(netadr_t from, const char *info, int ping) { - int i; - - for (i = 0; i < MAX_OTHER_SERVERS; i++) { - if (NET_CompareAdr(from, cls.localServers[i].adr)) { - CL_SetServerInfo(&cls.localServers[i], info, ping); - } - } - - for (i = 0; i < MAX_OTHER_SERVERS; i++) { - if (NET_CompareAdr(from, cls.mplayerServers[i].adr)) { - CL_SetServerInfo(&cls.mplayerServers[i], info, ping); - } - } - - for (i = 0; i < MAX_GLOBAL_SERVERS; i++) { - if (NET_CompareAdr(from, cls.globalServers[i].adr)) { - CL_SetServerInfo(&cls.globalServers[i], info, ping); - } - } - - for (i = 0; i < MAX_OTHER_SERVERS; i++) { - if (NET_CompareAdr(from, cls.favoriteServers[i].adr)) { - CL_SetServerInfo(&cls.favoriteServers[i], info, ping); - } - } - -} - -/* -=================== -CL_ServerInfoPacket -=================== -*/ -void CL_ServerInfoPacket( netadr_t from, msg_t *msg ) { - int i, type; - char info[MAX_INFO_STRING]; - char* str; - char *infoString; - int prot; - - infoString = MSG_ReadString( msg ); - - // if this isn't the correct protocol version, ignore it - prot = atoi( Info_ValueForKey( infoString, "protocol" ) ); - if ( prot != PROTOCOL_VERSION ) { - Com_DPrintf( "Different protocol info packet: %s\n", infoString ); - return; - } - - // iterate servers waiting for ping response - for (i=0; iretrieved = qtrue; - return qfalse; - } - - // if this server status request has the same address - if ( NET_CompareAdr( to, serverStatus->address) ) { - // if we recieved an response for this server status request - if (!serverStatus->pending) { - Q_strncpyz(serverStatusString, serverStatus->string, maxLen); - serverStatus->retrieved = qtrue; - serverStatus->startTime = 0; - return qtrue; - } - // resend the request regularly - else if ( serverStatus->startTime < Com_Milliseconds() - cl_serverStatusResendTime->integer ) { - serverStatus->print = qfalse; - serverStatus->pending = qtrue; - serverStatus->retrieved = qfalse; - serverStatus->time = 0; - serverStatus->startTime = Com_Milliseconds(); - NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); - return qfalse; - } - } - // if retrieved - else if ( serverStatus->retrieved ) { - serverStatus->address = to; - serverStatus->print = qfalse; - serverStatus->pending = qtrue; - serverStatus->retrieved = qfalse; - serverStatus->startTime = Com_Milliseconds(); - serverStatus->time = 0; - NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); - return qfalse; - } - return qfalse; -} - -/* -=================== -CL_ServerStatusResponse -=================== -*/ -void CL_ServerStatusResponse( netadr_t from, msg_t *msg ) { - char *s; - char info[MAX_INFO_STRING]; - int i, l, score, ping; - int len; - serverStatus_t *serverStatus; - - serverStatus = NULL; - for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { - if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) { - serverStatus = &cl_serverStatusList[i]; - break; - } - } - // if we didn't request this server status - if (!serverStatus) { - return; - } - - s = MSG_ReadStringLine( msg ); - - len = 0; - Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "%s", s); - - if (serverStatus->print) { - Com_Printf("Server settings:\n"); - // print cvars - while (*s) { - for (i = 0; i < 2 && *s; i++) { - if (*s == '\\') - s++; - l = 0; - while (*s) { - info[l++] = *s; - if (l >= MAX_INFO_STRING-1) - break; - s++; - if (*s == '\\') { - break; - } - } - info[l] = '\0'; - if (i) { - Com_Printf("%s\n", info); - } - else { - Com_Printf("%-24s", info); - } - } - } - } - - len = strlen(serverStatus->string); - Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\"); - - if (serverStatus->print) { - Com_Printf("\nPlayers:\n"); - Com_Printf("num: score: ping: name:\n"); - } - for (i = 0, s = MSG_ReadStringLine( msg ); *s; s = MSG_ReadStringLine( msg ), i++) { - - len = strlen(serverStatus->string); - Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\%s", s); - - if (serverStatus->print) { - score = ping = 0; - sscanf(s, "%d %d", &score, &ping); - s = strchr(s, ' '); - if (s) - s = strchr(s+1, ' '); - if (s) - s++; - else - s = "unknown"; - Com_Printf("%-2d %-3d %-3d %s\n", i, score, ping, s ); - } - } - len = strlen(serverStatus->string); - Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\"); - - serverStatus->time = Com_Milliseconds(); - serverStatus->address = from; - serverStatus->pending = qfalse; - if (serverStatus->print) { - serverStatus->retrieved = qtrue; - } -} - -/* -================== -CL_LocalServers_f -================== -*/ -void CL_LocalServers_f( void ) { - char *message; - int i, j; - netadr_t to; - - Com_Printf( "Scanning for servers on the local network...\n"); - - // reset the list, waiting for response - cls.numlocalservers = 0; - cls.pingUpdateSource = AS_LOCAL; - - for (i = 0; i < MAX_OTHER_SERVERS; i++) { - qboolean b = cls.localServers[i].visible; - Com_Memset(&cls.localServers[i], 0, sizeof(cls.localServers[i])); - cls.localServers[i].visible = b; - } - Com_Memset( &to, 0, sizeof( to ) ); - - // The 'xxx' in the message is a challenge that will be echoed back - // by the server. We don't care about that here, but master servers - // can use that to prevent spoofed server responses from invalid ip - message = "\377\377\377\377getinfo xxx"; - - // send each message twice in case one is dropped - for ( i = 0 ; i < 2 ; i++ ) { - // send a broadcast packet on each server port - // we support multiple server ports so a single machine - // can nicely run multiple servers - for ( j = 0 ; j < NUM_SERVER_PORTS ; j++ ) { - to.port = BigShort( (short)(PORT_SERVER + j) ); - - to.type = NA_BROADCAST; - NET_SendPacket( NS_CLIENT, strlen( message ), message, to ); - - to.type = NA_BROADCAST_IPX; - NET_SendPacket( NS_CLIENT, strlen( message ), message, to ); - } - } -} - -/* -================== -CL_GlobalServers_f -================== -*/ -void CL_GlobalServers_f( void ) { - netadr_t to; - int i; - int count; - char *buffptr; - char command[1024]; - - if ( Cmd_Argc() < 3) { - Com_Printf( "usage: globalservers [keywords]\n"); - return; - } - - cls.masterNum = atoi( Cmd_Argv(1) ); - - Com_Printf( "Requesting servers from the master...\n"); - - // reset the list, waiting for response - // -1 is used to distinguish a "no response" - - if( cls.masterNum == 1 ) { - NET_StringToAdr( MASTER_SERVER_NAME, &to ); - cls.nummplayerservers = -1; - cls.pingUpdateSource = AS_MPLAYER; - } - else { - NET_StringToAdr( MASTER_SERVER_NAME, &to ); - cls.numglobalservers = -1; - cls.pingUpdateSource = AS_GLOBAL; - } - to.type = NA_IP; - to.port = BigShort(PORT_MASTER); - - sprintf( command, "getservers %s", Cmd_Argv(2) ); - - // tack on keywords - buffptr = command + strlen( command ); - count = Cmd_Argc(); - for (i=3; i= MAX_PINGREQUESTS) - return; - - cl_pinglist[n].adr.port = 0; -} - -/* -================== -CL_GetPingQueueCount -================== -*/ -int CL_GetPingQueueCount( void ) -{ - int i; - int count; - ping_t* pingptr; - - count = 0; - pingptr = cl_pinglist; - - for (i=0; iadr.port) { - count++; - } - } - - return (count); -} - -/* -================== -CL_GetFreePing -================== -*/ -ping_t* CL_GetFreePing( void ) -{ - ping_t* pingptr; - ping_t* best; - int oldest; - int i; - int time; - - pingptr = cl_pinglist; - for (i=0; iadr.port) - { - if (!pingptr->time) - { - if (cls.realtime - pingptr->start < 500) - { - // still waiting for response - continue; - } - } - else if (pingptr->time < 500) - { - // results have not been queried - continue; - } - } - - // clear it - pingptr->adr.port = 0; - return (pingptr); - } - - // use oldest entry - pingptr = cl_pinglist; - best = cl_pinglist; - oldest = INT_MIN; - for (i=0; istart; - if (time > oldest) - { - oldest = time; - best = pingptr; - } - } - - return (best); -} - -/* -================== -CL_Ping_f -================== -*/ -void CL_Ping_f( void ) { - netadr_t to; - ping_t* pingptr; - char* server; - - if ( Cmd_Argc() != 2 ) { - Com_Printf( "usage: ping [server]\n"); - return; - } - - Com_Memset( &to, 0, sizeof(netadr_t) ); - - server = Cmd_Argv(1); - - if ( !NET_StringToAdr( server, &to ) ) { - return; - } - - pingptr = CL_GetFreePing(); - - memcpy( &pingptr->adr, &to, sizeof (netadr_t) ); - pingptr->start = cls.realtime; - pingptr->time = 0; - - CL_SetServerInfoByAddress(pingptr->adr, NULL, 0); - - NET_OutOfBandPrint( NS_CLIENT, to, "getinfo xxx" ); -} - -/* -================== -CL_UpdateVisiblePings_f -================== -*/ -qboolean CL_UpdateVisiblePings_f(int source) { - int slots, i; - char buff[MAX_STRING_CHARS]; - int pingTime; - int max; - qboolean status = qfalse; - - if (source < 0 || source > AS_FAVORITES) { - return qfalse; - } - - cls.pingUpdateSource = source; - - slots = CL_GetPingQueueCount(); - if (slots < MAX_PINGREQUESTS) { - serverInfo_t *server = NULL; - - max = (source == AS_GLOBAL) ? MAX_GLOBAL_SERVERS : MAX_OTHER_SERVERS; - switch (source) { - case AS_LOCAL : - server = &cls.localServers[0]; - max = cls.numlocalservers; - break; - case AS_MPLAYER : - server = &cls.mplayerServers[0]; - max = cls.nummplayerservers; - break; - case AS_GLOBAL : - server = &cls.globalServers[0]; - max = cls.numglobalservers; - break; - case AS_FAVORITES : - server = &cls.favoriteServers[0]; - max = cls.numfavoriteservers; - break; - } - for (i = 0; i < max; i++) { - if (server[i].visible) { - if (server[i].ping == -1) { - int j; - - if (slots >= MAX_PINGREQUESTS) { - break; - } - for (j = 0; j < MAX_PINGREQUESTS; j++) { - if (!cl_pinglist[j].adr.port) { - continue; - } - if (NET_CompareAdr( cl_pinglist[j].adr, server[i].adr)) { - // already on the list - break; - } - } - if (j >= MAX_PINGREQUESTS) { - status = qtrue; - for (j = 0; j < MAX_PINGREQUESTS; j++) { - if (!cl_pinglist[j].adr.port) { - break; - } - } - memcpy(&cl_pinglist[j].adr, &server[i].adr, sizeof(netadr_t)); - cl_pinglist[j].start = cls.realtime; - cl_pinglist[j].time = 0; - NET_OutOfBandPrint( NS_CLIENT, cl_pinglist[j].adr, "getinfo xxx" ); - slots++; - } - } - // if the server has a ping higher than cl_maxPing or - // the ping packet got lost - else if (server[i].ping == 0) { - // if we are updating global servers - if (source == AS_GLOBAL) { - // - if ( cls.numGlobalServerAddresses > 0 ) { - // overwrite this server with one from the additional global servers - cls.numGlobalServerAddresses--; - CL_InitServerInfo(&server[i], &cls.globalServerAddresses[cls.numGlobalServerAddresses]); - // NOTE: the server[i].visible flag stays untouched - } - } - } - } - } - } - - if (slots) { - status = qtrue; - } - for (i = 0; i < MAX_PINGREQUESTS; i++) { - if (!cl_pinglist[i].adr.port) { - continue; - } - CL_GetPing( i, buff, MAX_STRING_CHARS, &pingTime ); - if (pingTime != 0) { - CL_ClearPing(i); - status = qtrue; - } - } - - return status; -} - -/* -================== -CL_ServerStatus_f -================== -*/ -void CL_ServerStatus_f(void) { - netadr_t to; - char *server; - serverStatus_t *serverStatus; - - Com_Memset( &to, 0, sizeof(netadr_t) ); - - if ( Cmd_Argc() != 2 ) { - if ( cls.state != CA_ACTIVE || clc.demoplaying ) { - Com_Printf ("Not connected to a server.\n"); - Com_Printf( "Usage: serverstatus [server]\n"); - return; - } - server = cls.servername; - } - else { - server = Cmd_Argv(1); - } - - if ( !NET_StringToAdr( server, &to ) ) { - return; - } - - NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); - - serverStatus = CL_GetServerStatus( to ); - serverStatus->address = to; - serverStatus->print = qtrue; - serverStatus->pending = qtrue; -} - -/* -================== -CL_ShowIP_f -================== -*/ -void CL_ShowIP_f(void) { - Sys_ShowIP(); -} - -/* -================= -bool CL_CDKeyValidate -================= -*/ -qboolean CL_CDKeyValidate( const char *key, const char *checksum ) { - char ch; - byte sum; - char chs[3]; - int i, len; - - len = strlen(key); - if( len != CDKEY_LEN ) { - return qfalse; - } - - if( checksum && strlen( checksum ) != CDCHKSUM_LEN ) { - return qfalse; - } - - sum = 0; - // for loop gets rid of conditional assignment warning - for (i = 0; i < len; i++) { - ch = *key++; - if (ch>='a' && ch<='z') { - ch -= 32; - } - switch( ch ) { - case '2': - case '3': - case '7': - case 'A': - case 'B': - case 'C': - case 'D': - case 'G': - case 'H': - case 'J': - case 'L': - case 'P': - case 'R': - case 'S': - case 'T': - case 'W': - sum += ch; - continue; - default: - return qfalse; - } - } - - sprintf(chs, "%02x", sum); - - if (checksum && !Q_stricmp(chs, checksum)) { - return qtrue; - } - - if (!checksum) { - return qtrue; - } - - return qfalse; -} - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cl_main.c -- client main loop + +#include "client.h" +#include + +cvar_t *cl_nodelta; +cvar_t *cl_debugMove; + +cvar_t *cl_noprint; +cvar_t *cl_motd; + +cvar_t *rcon_client_password; +cvar_t *rconAddress; + +cvar_t *cl_timeout; +cvar_t *cl_maxpackets; +cvar_t *cl_packetdup; +cvar_t *cl_timeNudge; +cvar_t *cl_showTimeDelta; +cvar_t *cl_freezeDemo; + +cvar_t *cl_shownet; +cvar_t *cl_showSend; +cvar_t *cl_timedemo; +cvar_t *cl_avidemo; +cvar_t *cl_forceavidemo; + +cvar_t *cl_freelook; +cvar_t *cl_sensitivity; + +cvar_t *cl_mouseAccel; +cvar_t *cl_showMouseRate; + +cvar_t *m_pitch; +cvar_t *m_yaw; +cvar_t *m_forward; +cvar_t *m_side; +cvar_t *m_filter; + +cvar_t *cl_activeAction; + +cvar_t *cl_motdString; + +cvar_t *cl_allowDownload; +cvar_t *cl_conXOffset; +cvar_t *cl_inGameVideo; + +cvar_t *cl_serverStatusResendTime; +cvar_t *cl_trn; + +clientActive_t cl; +clientConnection_t clc; +clientStatic_t cls; +vm_t *cgvm; + +// Structure containing functions exported from refresh DLL +refexport_t re; + +ping_t cl_pinglist[MAX_PINGREQUESTS]; + +typedef struct serverStatus_s +{ + char string[BIG_INFO_STRING]; + netadr_t address; + int time, startTime; + qboolean pending; + qboolean print; + qboolean retrieved; +} serverStatus_t; + +serverStatus_t cl_serverStatusList[MAX_SERVERSTATUSREQUESTS]; +int serverStatusCount; + +#if defined __USEA3D && defined __A3D_GEOM + void hA3Dg_ExportRenderGeom (refexport_t *incoming_re); +#endif + +extern void SV_BotFrame( int time ); +void CL_CheckForResend( void ); +void CL_ShowIP_f(void); +void CL_ServerStatus_f(void); +void CL_ServerStatusResponse( netadr_t from, msg_t *msg ); + +/* +=============== +CL_CDDialog + +Called by Com_Error when a cd is needed +=============== +*/ +void CL_CDDialog( void ) { + cls.cddialog = qtrue; // start it next frame +} + + +/* +======================================================================= + +CLIENT RELIABLE COMMAND COMMUNICATION + +======================================================================= +*/ + +/* +====================== +CL_AddReliableCommand + +The given command will be transmitted to the server, and is gauranteed to +not have future usercmd_t executed before it is executed +====================== +*/ +void CL_AddReliableCommand( const char *cmd ) { + int index; + + // if we would be losing an old command that hasn't been acknowledged, + // we must drop the connection + if ( clc.reliableSequence - clc.reliableAcknowledge > MAX_RELIABLE_COMMANDS ) { + Com_Error( ERR_DROP, "Client command overflow" ); + } + clc.reliableSequence++; + index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); + Q_strncpyz( clc.reliableCommands[ index ], cmd, sizeof( clc.reliableCommands[ index ] ) ); +} + +/* +====================== +CL_ChangeReliableCommand +====================== +*/ +void CL_ChangeReliableCommand( void ) { + int r, index, l; + + r = clc.reliableSequence - (random() * 5); + index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); + l = strlen(clc.reliableCommands[ index ]); + if ( l >= MAX_STRING_CHARS - 1 ) { + l = MAX_STRING_CHARS - 2; + } + clc.reliableCommands[ index ][ l ] = '\n'; + clc.reliableCommands[ index ][ l+1 ] = '\0'; +} + +/* +======================================================================= + +CLIENT SIDE DEMO RECORDING + +======================================================================= +*/ + +/* +==================== +CL_WriteDemoMessage + +Dumps the current net message, prefixed by the length +==================== +*/ +void CL_WriteDemoMessage ( msg_t *msg, int headerBytes ) { + int len, swlen; + + // write the packet sequence + len = clc.serverMessageSequence; + swlen = LittleLong( len ); + FS_Write (&swlen, 4, clc.demofile); + + // skip the packet sequencing information + len = msg->cursize - headerBytes; + swlen = LittleLong(len); + FS_Write (&swlen, 4, clc.demofile); + FS_Write ( msg->data + headerBytes, len, clc.demofile ); +} + + +/* +==================== +CL_StopRecording_f + +stop recording a demo +==================== +*/ +void CL_StopRecord_f( void ) { + int len; + + if ( !clc.demorecording ) { + Com_Printf ("Not recording a demo.\n"); + return; + } + + // finish up + len = -1; + FS_Write (&len, 4, clc.demofile); + FS_Write (&len, 4, clc.demofile); + FS_FCloseFile (clc.demofile); + clc.demofile = 0; + clc.demorecording = qfalse; + clc.spDemoRecording = qfalse; + Com_Printf ("Stopped demo.\n"); +} + +/* +================== +CL_DemoFilename +================== +*/ +void CL_DemoFilename( int number, char *fileName ) { + int a,b,c,d; + + if ( number < 0 || number > 9999 ) { + Com_sprintf( fileName, MAX_OSPATH, "demo9999.tga" ); + return; + } + + a = number / 1000; + number -= a*1000; + b = number / 100; + number -= b*100; + c = number / 10; + number -= c*10; + d = number; + + Com_sprintf( fileName, MAX_OSPATH, "demo%i%i%i%i" + , a, b, c, d ); +} + +/* +==================== +CL_Record_f + +record + +Begins recording a demo from the current position +==================== +*/ +static char demoName[MAX_QPATH]; // compiler bug workaround +void CL_Record_f( void ) { + char name[MAX_OSPATH]; + byte bufData[MAX_MSGLEN]; + msg_t buf; + int i; + int len; + entityState_t *ent; + entityState_t nullstate; + char *s; + + if ( Cmd_Argc() > 2 ) { + Com_Printf ("record \n"); + return; + } + + if ( clc.demorecording ) { + if (!clc.spDemoRecording) { + Com_Printf ("Already recording.\n"); + } + return; + } + + if ( cls.state != CA_ACTIVE ) { + Com_Printf ("You must be in a level to record.\n"); + return; + } + + // sync 0 doesn't prevent recording, so not forcing it off .. everyone does g_sync 1 ; record ; g_sync 0 .. + if ( !Cvar_VariableValue( "g_synchronousClients" ) ) { + Com_Printf (S_COLOR_YELLOW "WARNING: You should set 'g_synchronousClients 1' for smoother demo recording\n"); + } + + if ( Cmd_Argc() == 2 ) { + s = Cmd_Argv(1); + Q_strncpyz( demoName, s, sizeof( demoName ) ); + Com_sprintf (name, sizeof(name), "demos/%s.dm_%d", demoName, PROTOCOL_VERSION ); + } else { + int number; + + // scan for a free demo name + for ( number = 0 ; number <= 9999 ; number++ ) { + CL_DemoFilename( number, demoName ); + Com_sprintf (name, sizeof(name), "demos/%s.dm_%d", demoName, PROTOCOL_VERSION ); + + len = FS_ReadFile( name, NULL ); + if ( len <= 0 ) { + break; // file doesn't exist + } + } + } + + // open the demo file + + Com_Printf ("recording to %s.\n", name); + clc.demofile = FS_FOpenFileWrite( name ); + if ( !clc.demofile ) { + Com_Printf ("ERROR: couldn't open.\n"); + return; + } + clc.demorecording = qtrue; + if (Cvar_VariableValue("ui_recordSPDemo")) { + clc.spDemoRecording = qtrue; + } else { + clc.spDemoRecording = qfalse; + } + + + Q_strncpyz( clc.demoName, demoName, sizeof( clc.demoName ) ); + + // don't start saving messages until a non-delta compressed message is received + clc.demowaiting = qtrue; + + // write out the gamestate message + MSG_Init (&buf, bufData, sizeof(bufData)); + MSG_Bitstream(&buf); + + // NOTE, MRE: all server->client messages now acknowledge + MSG_WriteLong( &buf, clc.reliableSequence ); + + MSG_WriteByte (&buf, svc_gamestate); + MSG_WriteLong (&buf, clc.serverCommandSequence ); + + // configstrings + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + if ( !cl.gameState.stringOffsets[i] ) { + continue; + } + s = cl.gameState.stringData + cl.gameState.stringOffsets[i]; + MSG_WriteByte (&buf, svc_configstring); + MSG_WriteShort (&buf, i); + MSG_WriteBigString (&buf, s); + } + + // baselines + Com_Memset (&nullstate, 0, sizeof(nullstate)); + for ( i = 0; i < MAX_GENTITIES ; i++ ) { + ent = &cl.entityBaselines[i]; + if ( !ent->number ) { + continue; + } + MSG_WriteByte (&buf, svc_baseline); + MSG_WriteDeltaEntity (&buf, &nullstate, ent, qtrue ); + } + + MSG_WriteByte( &buf, svc_EOF ); + + // finished writing the gamestate stuff + + // write the client num + MSG_WriteLong(&buf, clc.clientNum); + // write the checksum feed + MSG_WriteLong(&buf, clc.checksumFeed); + + // finished writing the client packet + MSG_WriteByte( &buf, svc_EOF ); + + // write it to the demo file + len = LittleLong( clc.serverMessageSequence - 1 ); + FS_Write (&len, 4, clc.demofile); + + len = LittleLong (buf.cursize); + FS_Write (&len, 4, clc.demofile); + FS_Write (buf.data, buf.cursize, clc.demofile); + + // the rest of the demo file will be copied from net messages +} + +/* +======================================================================= + +CLIENT SIDE DEMO PLAYBACK + +======================================================================= +*/ + +/* +================= +CL_DemoCompleted +================= +*/ +void CL_DemoCompleted( void ) { + if (cl_timedemo && cl_timedemo->integer) { + int time; + + time = Sys_Milliseconds() - clc.timeDemoStart; + if ( time > 0 ) { + Com_Printf ("%i frames, %3.1f seconds: %3.1f fps\n", clc.timeDemoFrames, + time/1000.0, clc.timeDemoFrames*1000.0 / time); + } + } + + CL_Disconnect( qtrue ); + CL_NextDemo(); +} + +/* +================= +CL_ReadDemoMessage +================= +*/ +void CL_ReadDemoMessage( void ) { + int r; + msg_t buf; + byte bufData[ MAX_MSGLEN ]; + int s; + + if ( !clc.demofile ) { + CL_DemoCompleted (); + return; + } + + // get the sequence number + r = FS_Read( &s, 4, clc.demofile); + if ( r != 4 ) { + CL_DemoCompleted (); + return; + } + clc.serverMessageSequence = LittleLong( s ); + + // init the message + MSG_Init( &buf, bufData, sizeof( bufData ) ); + + // get the length + r = FS_Read (&buf.cursize, 4, clc.demofile); + if ( r != 4 ) { + CL_DemoCompleted (); + return; + } + buf.cursize = LittleLong( buf.cursize ); + if ( buf.cursize == -1 ) { + CL_DemoCompleted (); + return; + } + if ( buf.cursize > buf.maxsize ) { + Com_Error (ERR_DROP, "CL_ReadDemoMessage: demoMsglen > MAX_MSGLEN"); + } + r = FS_Read( buf.data, buf.cursize, clc.demofile ); + if ( r != buf.cursize ) { + Com_Printf( "Demo file was truncated.\n"); + CL_DemoCompleted (); + return; + } + + clc.lastPacketTime = cls.realtime; + buf.readcount = 0; + CL_ParseServerMessage( &buf ); +} + +/* +==================== +CL_WalkDemoExt +==================== +*/ +static void CL_WalkDemoExt(char *arg, char *name, int *demofile) +{ + int i = 0; + *demofile = 0; + while(demo_protocols[i]) + { + Com_sprintf (name, MAX_OSPATH, "demos/%s.dm_%d", arg, demo_protocols[i]); + FS_FOpenFileRead( name, demofile, qtrue ); + if (*demofile) + { + Com_Printf("Demo file: %s\n", name); + break; + } + else + Com_Printf("Not found: %s\n", name); + i++; + } +} + +/* +==================== +CL_PlayDemo_f + +demo + +==================== +*/ +void CL_PlayDemo_f( void ) { + char name[MAX_OSPATH]; + char *arg, *ext_test; + int protocol, i; + char retry[MAX_OSPATH]; + + if (Cmd_Argc() != 2) { + Com_Printf ("playdemo \n"); + return; + } + + // make sure a local server is killed + Cvar_Set( "sv_killserver", "1" ); + + CL_Disconnect( qtrue ); + + // open the demo file + arg = Cmd_Argv(1); + + // check for an extension .dm_?? (?? is protocol) + ext_test = arg + strlen(arg) - 6; + if ((strlen(arg) > 6) && (ext_test[0] == '.') && ((ext_test[1] == 'd') || (ext_test[1] == 'D')) && ((ext_test[2] == 'm') || (ext_test[2] == 'M')) && (ext_test[3] == '_')) + { + protocol = atoi(ext_test+4); + i=0; + while(demo_protocols[i]) + { + if (demo_protocols[i] == protocol) + break; + i++; + } + if (demo_protocols[i]) + { + Com_sprintf (name, sizeof(name), "demos/%s", arg); + FS_FOpenFileRead( name, &clc.demofile, qtrue ); + } else { + Com_Printf("Protocol %d not supported for demos\n", protocol); + Q_strncpyz(retry, arg, sizeof(retry)); + retry[strlen(retry)-6] = 0; + CL_WalkDemoExt( retry, name, &clc.demofile ); + } + } else { + CL_WalkDemoExt( arg, name, &clc.demofile ); + } + + if (!clc.demofile) { + Com_Error( ERR_DROP, "couldn't open %s", name); + return; + } + Q_strncpyz( clc.demoName, Cmd_Argv(1), sizeof( clc.demoName ) ); + + Con_Close(); + + cls.state = CA_CONNECTED; + clc.demoplaying = qtrue; + Q_strncpyz( cls.servername, Cmd_Argv(1), sizeof( cls.servername ) ); + + // read demo messages until connected + while ( cls.state >= CA_CONNECTED && cls.state < CA_PRIMED ) { + CL_ReadDemoMessage(); + } + // don't get the first snapshot this frame, to prevent the long + // time from the gamestate load from messing causing a time skip + clc.firstDemoFrameSkipped = qfalse; +} + + +/* +==================== +CL_StartDemoLoop + +Closing the main menu will restart the demo loop +==================== +*/ +void CL_StartDemoLoop( void ) { + // start the demo loop again + Cbuf_AddText ("d1\n"); + cls.keyCatchers = 0; +} + +/* +================== +CL_NextDemo + +Called when a demo or cinematic finishes +If the "nextdemo" cvar is set, that command will be issued +================== +*/ +void CL_NextDemo( void ) { + char v[MAX_STRING_CHARS]; + + Q_strncpyz( v, Cvar_VariableString ("nextdemo"), sizeof(v) ); + v[MAX_STRING_CHARS-1] = 0; + Com_DPrintf("CL_NextDemo: %s\n", v ); + if (!v[0]) { + return; + } + + Cvar_Set ("nextdemo",""); + Cbuf_AddText (v); + Cbuf_AddText ("\n"); + Cbuf_Execute(); +} + + +//====================================================================== + +/* +===================== +CL_ShutdownAll +===================== +*/ +void CL_ShutdownAll(void) { + + // clear sounds + S_DisableSounds(); + // shutdown CGame + CL_ShutdownCGame(); + // shutdown UI + CL_ShutdownUI(); + + // shutdown the renderer + if ( re.Shutdown ) { + re.Shutdown( qfalse ); // don't destroy window or context + } + + cls.uiStarted = qfalse; + cls.cgameStarted = qfalse; + cls.rendererStarted = qfalse; + cls.soundRegistered = qfalse; +} + +/* +================= +CL_FlushMemory + +Called by CL_MapLoading, CL_Connect_f, CL_PlayDemo_f, and CL_ParseGamestate the only +ways a client gets into a game +Also called by Com_Error +================= +*/ +void CL_FlushMemory( void ) { + + // shutdown all the client stuff + CL_ShutdownAll(); + + // if not running a server clear the whole hunk + if ( !com_sv_running->integer ) { + // clear the whole hunk + Hunk_Clear(); + // clear collision map data + CM_ClearMap(); + } + else { + // clear all the client data on the hunk + Hunk_ClearToMark(); + } + + CL_StartHunkUsers(); +} + +/* +===================== +CL_MapLoading + +A local server is starting to load a map, so update the +screen to let the user know about it, then dump all client +memory on the hunk from cgame, ui, and renderer +===================== +*/ +void CL_MapLoading( void ) { + if ( !com_cl_running->integer ) { + return; + } + + Con_Close(); + cls.keyCatchers = 0; + + // if we are already connected to the local host, stay connected + if ( cls.state >= CA_CONNECTED && !Q_stricmp( cls.servername, "localhost" ) ) { + cls.state = CA_CONNECTED; // so the connect screen is drawn + Com_Memset( cls.updateInfoString, 0, sizeof( cls.updateInfoString ) ); + Com_Memset( clc.serverMessage, 0, sizeof( clc.serverMessage ) ); + Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) ); + clc.lastPacketSentTime = -9999; + SCR_UpdateScreen(); + } else { + // clear nextmap so the cinematic shutdown doesn't execute it + Cvar_Set( "nextmap", "" ); + CL_Disconnect( qtrue ); + Q_strncpyz( cls.servername, "localhost", sizeof(cls.servername) ); + cls.state = CA_CHALLENGING; // so the connect screen is drawn + cls.keyCatchers = 0; + SCR_UpdateScreen(); + clc.connectTime = -RETRANSMIT_TIMEOUT; + NET_StringToAdr( cls.servername, &clc.serverAddress); + // we don't need a challenge on the localhost + + CL_CheckForResend(); + } +} + +/* +===================== +CL_ClearState + +Called before parsing a gamestate +===================== +*/ +void CL_ClearState (void) { + +// S_StopAllSounds(); + + Com_Memset( &cl, 0, sizeof( cl ) ); +} + + +/* +===================== +CL_Disconnect + +Called when a connection, demo, or cinematic is being terminated. +Goes from a connected state to either a menu state or a console state +Sends a disconnect message to the server +This is also called on Com_Error and Com_Quit, so it shouldn't cause any errors +===================== +*/ +void CL_Disconnect( qboolean showMainMenu ) { + if ( !com_cl_running || !com_cl_running->integer ) { + return; + } + + // shutting down the client so enter full screen ui mode + Cvar_Set("r_uiFullScreen", "1"); + + if ( clc.demorecording ) { + CL_StopRecord_f (); + } + + if (clc.download) { + FS_FCloseFile( clc.download ); + clc.download = 0; + } + *clc.downloadTempName = *clc.downloadName = 0; + Cvar_Set( "cl_downloadName", "" ); + + if ( clc.demofile ) { + FS_FCloseFile( clc.demofile ); + clc.demofile = 0; + } + + if ( uivm && showMainMenu ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE ); + } + + SCR_StopCinematic (); + S_ClearSoundBuffer(); + + // send a disconnect message to the server + // send it a few times in case one is dropped + if ( cls.state >= CA_CONNECTED ) { + CL_AddReliableCommand( "disconnect" ); + CL_WritePacket(); + CL_WritePacket(); + CL_WritePacket(); + } + + CL_ClearState (); + + // wipe the client connection + Com_Memset( &clc, 0, sizeof( clc ) ); + + cls.state = CA_DISCONNECTED; + + // allow cheats locally + Cvar_Set( "sv_cheats", "1" ); + + // not connected to a pure server anymore + cl_connectedToPureServer = qfalse; +} + + +/* +=================== +CL_ForwardCommandToServer + +adds the current command line as a clientCommand +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 CL_ForwardCommandToServer( const char *string ) { + char *cmd; + + cmd = Cmd_Argv(0); + + // ignore key up commands + if ( cmd[0] == '-' ) { + return; + } + + if ( clc.demoplaying || cls.state < CA_CONNECTED || cmd[0] == '+' ) { + Com_Printf ("Unknown command \"%s\"\n", cmd); + return; + } + + if ( Cmd_Argc() > 1 ) { + CL_AddReliableCommand( string ); + } else { + CL_AddReliableCommand( cmd ); + } +} + +/* +=================== +CL_RequestMotd + +=================== +*/ +void CL_RequestMotd( void ) { + char info[MAX_INFO_STRING]; + + if ( !cl_motd->integer ) { + return; + } + Com_Printf( "Resolving %s\n", UPDATE_SERVER_NAME ); + if ( !NET_StringToAdr( UPDATE_SERVER_NAME, &cls.updateServer ) ) { + Com_Printf( "Couldn't resolve address\n" ); + return; + } + cls.updateServer.port = BigShort( PORT_UPDATE ); + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", UPDATE_SERVER_NAME, + cls.updateServer.ip[0], cls.updateServer.ip[1], + cls.updateServer.ip[2], cls.updateServer.ip[3], + BigShort( cls.updateServer.port ) ); + + info[0] = 0; + // NOTE TTimo xoring against Com_Milliseconds, otherwise we may not have a true randomization + // only srand I could catch before here is tr_noise.c l:26 srand(1001) + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=382 + // NOTE: the Com_Milliseconds xoring only affects the lower 16-bit word, + // but I decided it was enough randomization + Com_sprintf( cls.updateChallenge, sizeof( cls.updateChallenge ), "%i", ((rand() << 16) ^ rand()) ^ Com_Milliseconds()); + + Info_SetValueForKey( info, "challenge", cls.updateChallenge ); + Info_SetValueForKey( info, "renderer", cls.glconfig.renderer_string ); + Info_SetValueForKey( info, "version", com_version->string ); + + NET_OutOfBandPrint( NS_CLIENT, cls.updateServer, "getmotd \"%s\"\n", info ); +} + +/* +=================== +CL_RequestAuthorization + +Authorization server protocol +----------------------------- + +All commands are text in Q3 out of band packets (leading 0xff 0xff 0xff 0xff). + +Whenever the client tries to get a challenge from the server it wants to +connect to, it also blindly fires off a packet to the authorize server: + +getKeyAuthorize + +cdkey may be "demo" + + +#OLD The authorize server returns a: +#OLD +#OLD keyAthorize +#OLD +#OLD A client will be accepted if the cdkey is valid and it has not been used by any other IP +#OLD address in the last 15 minutes. + + +The server sends a: + +getIpAuthorize + +The authorize server returns a: + +ipAuthorize + +A client will be accepted if a valid cdkey was sent by that ip (only) in the last 15 minutes. +If no response is received from the authorize server after two tries, the client will be let +in anyway. +=================== +*/ +void CL_RequestAuthorization( void ) { + char nums[64]; + int i, j, l; + cvar_t *fs; + + if ( !cls.authorizeServer.port ) { + Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); + if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &cls.authorizeServer ) ) { + Com_Printf( "Couldn't resolve address\n" ); + return; + } + + cls.authorizeServer.port = BigShort( PORT_AUTHORIZE ); + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, + cls.authorizeServer.ip[0], cls.authorizeServer.ip[1], + cls.authorizeServer.ip[2], cls.authorizeServer.ip[3], + BigShort( cls.authorizeServer.port ) ); + } + if ( cls.authorizeServer.type == NA_BAD ) { + return; + } + + if ( Cvar_VariableValue( "fs_restrict" ) ) { + Q_strncpyz( nums, "demota", sizeof( nums ) ); + } else { + // only grab the alphanumeric values from the cdkey, to avoid any dashes or spaces + j = 0; + l = strlen( cl_cdkey ); + if ( l > 32 ) { + l = 32; + } + for ( i = 0 ; i < l ; i++ ) { + if ( ( cl_cdkey[i] >= '0' && cl_cdkey[i] <= '9' ) + || ( cl_cdkey[i] >= 'a' && cl_cdkey[i] <= 'z' ) + || ( cl_cdkey[i] >= 'A' && cl_cdkey[i] <= 'Z' ) + ) { + nums[j] = cl_cdkey[i]; + j++; + } + } + nums[j] = 0; + } + + fs = Cvar_Get ("cl_anonymous", "0", CVAR_INIT|CVAR_SYSTEMINFO ); + + NET_OutOfBandPrint(NS_CLIENT, cls.authorizeServer, va("getKeyAuthorize %i %s", fs->integer, nums) ); +} + +/* +====================================================================== + +CONSOLE COMMANDS + +====================================================================== +*/ + +/* +================== +CL_ForwardToServer_f +================== +*/ +void CL_ForwardToServer_f( void ) { + if ( cls.state != CA_ACTIVE || clc.demoplaying ) { + Com_Printf ("Not connected to a server.\n"); + return; + } + + // don't forward the first argument + if ( Cmd_Argc() > 1 ) { + CL_AddReliableCommand( Cmd_Args() ); + } +} + +/* +================== +CL_Setenv_f + +Mostly for controlling voodoo environment variables +================== +*/ +void CL_Setenv_f( void ) { + int argc = Cmd_Argc(); + + if ( argc > 2 ) { + char buffer[1024]; + int i; + + strcpy( buffer, Cmd_Argv(1) ); + strcat( buffer, "=" ); + + for ( i = 2; i < argc; i++ ) { + strcat( buffer, Cmd_Argv( i ) ); + strcat( buffer, " " ); + } + + 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), env ); + } + } +} + + +/* +================== +CL_Disconnect_f +================== +*/ +void CL_Disconnect_f( void ) { + SCR_StopCinematic(); + Cvar_Set("ui_singlePlayerActive", "0"); + if ( cls.state != CA_DISCONNECTED && cls.state != CA_CINEMATIC ) { + Com_Error (ERR_DISCONNECT, "Disconnected from server"); + } +} + + +/* +================ +CL_Reconnect_f + +================ +*/ +void CL_Reconnect_f( void ) { + if ( !strlen( cls.servername ) || !strcmp( cls.servername, "localhost" ) ) { + Com_Printf( "Can't reconnect to localhost.\n" ); + return; + } + Cvar_Set("ui_singlePlayerActive", "0"); + Cbuf_AddText( va("connect %s\n", cls.servername ) ); +} + +/* +================ +CL_Connect_f + +================ +*/ +void CL_Connect_f( void ) { + char *server; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "usage: connect [server]\n"); + return; + } + + Cvar_Set("ui_singlePlayerActive", "0"); + + // fire a message off to the motd server + CL_RequestMotd(); + + // clear any previous "server full" type messages + clc.serverMessage[0] = 0; + + server = Cmd_Argv (1); + + if ( com_sv_running->integer && !strcmp( server, "localhost" ) ) { + // if running a local server, kill it + SV_Shutdown( "Server quit\n" ); + } + + // make sure a local server is killed + Cvar_Set( "sv_killserver", "1" ); + SV_Frame( 0 ); + + CL_Disconnect( qtrue ); + Con_Close(); + + /* MrE: 2000-09-13: now called in CL_DownloadsComplete + CL_FlushMemory( ); + */ + + Q_strncpyz( cls.servername, server, sizeof(cls.servername) ); + + if (!NET_StringToAdr( cls.servername, &clc.serverAddress) ) { + Com_Printf ("Bad server address\n"); + cls.state = CA_DISCONNECTED; + return; + } + if (clc.serverAddress.port == 0) { + clc.serverAddress.port = BigShort( PORT_SERVER ); + } + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", cls.servername, + clc.serverAddress.ip[0], clc.serverAddress.ip[1], + clc.serverAddress.ip[2], clc.serverAddress.ip[3], + BigShort( clc.serverAddress.port ) ); + + // if we aren't playing on a lan, we need to authenticate + // with the cd key + if ( NET_IsLocalAddress( clc.serverAddress ) ) { + cls.state = CA_CHALLENGING; + } else { + cls.state = CA_CONNECTING; + } + + cls.keyCatchers = 0; + clc.connectTime = -99999; // CL_CheckForResend() will fire immediately + clc.connectPacketCount = 0; + + // server connection string + Cvar_Set( "cl_currentServerAddress", server ); +} + + +/* +===================== +CL_Rcon_f + + Send the rest of the command line over as + an unconnected command. +===================== +*/ +void CL_Rcon_f( void ) { + char message[1024]; + netadr_t to; + + if ( !rcon_client_password->string ) { + Com_Printf ("You must set 'rconpassword' before\n" + "issuing an rcon command.\n"); + return; + } + + message[0] = -1; + message[1] = -1; + message[2] = -1; + message[3] = -1; + message[4] = 0; + + strcat (message, "rcon "); + + strcat (message, rcon_client_password->string); + strcat (message, " "); + + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 + strcat (message, Cmd_Cmd()+5); + + if ( cls.state >= CA_CONNECTED ) { + to = clc.netchan.remoteAddress; + } else { + if (!strlen(rconAddress->string)) { + Com_Printf ("You must either be connected,\n" + "or set the 'rconAddress' cvar\n" + "to issue rcon commands\n"); + + return; + } + NET_StringToAdr (rconAddress->string, &to); + if (to.port == 0) { + to.port = BigShort (PORT_SERVER); + } + } + + NET_SendPacket (NS_CLIENT, strlen(message)+1, message, to); +} + +/* +================= +CL_SendPureChecksums +================= +*/ +void CL_SendPureChecksums( void ) { + const char *pChecksums; + char cMsg[MAX_INFO_VALUE]; + int i; + + // if we are pure we need to send back a command with our referenced pk3 checksums + pChecksums = FS_ReferencedPakPureChecksums(); + + // "cp" + // "Yf" + Com_sprintf(cMsg, sizeof(cMsg), "Yf "); + Q_strcat(cMsg, sizeof(cMsg), va("%d ", cl.serverId) ); + Q_strcat(cMsg, sizeof(cMsg), pChecksums); + for (i = 0; i < 2; i++) { + cMsg[i] += 10; + } + CL_AddReliableCommand( cMsg ); +} + +/* +================= +CL_ResetPureClientAtServer +================= +*/ +void CL_ResetPureClientAtServer( void ) { + CL_AddReliableCommand( va("vdr") ); +} + +/* +================= +CL_Vid_Restart_f + +Restart the video subsystem + +we also have to reload the UI and CGame because the renderer +doesn't know what graphics to reload +================= +*/ +void CL_Vid_Restart_f( void ) { + + // don't let them loop during the restart + S_StopAllSounds(); + // shutdown the UI + CL_ShutdownUI(); + // shutdown the CGame + CL_ShutdownCGame(); + // shutdown the renderer and clear the renderer interface + CL_ShutdownRef(); + // client is no longer pure untill new checksums are sent + CL_ResetPureClientAtServer(); + // clear pak references + FS_ClearPakReferences( FS_UI_REF | FS_CGAME_REF ); + // reinitialize the filesystem if the game directory or checksum has changed + FS_ConditionalRestart( clc.checksumFeed ); + + cls.rendererStarted = qfalse; + cls.uiStarted = qfalse; + cls.cgameStarted = qfalse; + cls.soundRegistered = qfalse; + + // unpause so the cgame definately gets a snapshot and renders a frame + Cvar_Set( "cl_paused", "0" ); + + // if not running a server clear the whole hunk + if ( !com_sv_running->integer ) { + // clear the whole hunk + Hunk_Clear(); + } + else { + // clear all the client data on the hunk + Hunk_ClearToMark(); + } + + // initialize the renderer interface + CL_InitRef(); + + // startup all the client stuff + CL_StartHunkUsers(); + + // start the cgame if connected + if ( cls.state > CA_CONNECTED && cls.state != CA_CINEMATIC ) { + cls.cgameStarted = qtrue; + CL_InitCGame(); + // send pure checksums + CL_SendPureChecksums(); + } +} + +/* +================= +CL_Snd_Restart_f + +Restart the sound subsystem +The cgame and game must also be forced to restart because +handles will be invalid +================= +*/ +void CL_Snd_Restart_f( void ) { + S_Shutdown(); + S_Init(); + + CL_Vid_Restart_f(); +} + + +/* +================== +CL_PK3List_f +================== +*/ +void CL_OpenedPK3List_f( void ) { + Com_Printf("Opened PK3 Names: %s\n", FS_LoadedPakNames()); +} + +/* +================== +CL_PureList_f +================== +*/ +void CL_ReferencedPK3List_f( void ) { + Com_Printf("Referenced PK3 Names: %s\n", FS_ReferencedPakNames()); +} + +/* +================== +CL_Configstrings_f +================== +*/ +void CL_Configstrings_f( void ) { + int i; + int ofs; + + if ( cls.state != CA_ACTIVE ) { + Com_Printf( "Not connected to a server.\n"); + return; + } + + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + ofs = cl.gameState.stringOffsets[ i ]; + if ( !ofs ) { + continue; + } + Com_Printf( "%4i: %s\n", i, cl.gameState.stringData + ofs ); + } +} + +/* +============== +CL_Clientinfo_f +============== +*/ +void CL_Clientinfo_f( void ) { + Com_Printf( "--------- Client Information ---------\n" ); + Com_Printf( "state: %i\n", cls.state ); + Com_Printf( "Server: %s\n", cls.servername ); + Com_Printf ("User info settings:\n"); + Info_Print( Cvar_InfoString( CVAR_USERINFO ) ); + Com_Printf( "--------------------------------------\n" ); +} + + +//==================================================================== + +/* +================= +CL_DownloadsComplete + +Called when all downloading has been completed +================= +*/ +void CL_DownloadsComplete( void ) { + + // if we downloaded files we need to restart the file system + if (clc.downloadRestart) { + clc.downloadRestart = qfalse; + + FS_Restart(clc.checksumFeed); // We possibly downloaded a pak, restart the file system to load it + + // inform the server so we get new gamestate info + CL_AddReliableCommand( "donedl" ); + + // by sending the donedl command we request a new gamestate + // so we don't want to load stuff yet + return; + } + + // let the client game init and load data + cls.state = CA_LOADING; + + // Pump the loop, this may change gamestate! + Com_EventLoop(); + + // if the gamestate was changed by calling Com_EventLoop + // then we loaded everything already and we don't want to do it again. + if ( cls.state != CA_LOADING ) { + return; + } + + // starting to load a map so we get out of full screen ui mode + Cvar_Set("r_uiFullScreen", "0"); + + // flush client memory and start loading stuff + // this will also (re)load the UI + // if this is a local client then only the client part of the hunk + // will be cleared, note that this is done after the hunk mark has been set + CL_FlushMemory(); + + // initialize the CGame + cls.cgameStarted = qtrue; + CL_InitCGame(); + + // set pure checksums + CL_SendPureChecksums(); + + CL_WritePacket(); + CL_WritePacket(); + CL_WritePacket(); +} + +/* +================= +CL_BeginDownload + +Requests a file to download from the server. Stores it in the current +game directory. +================= +*/ +void CL_BeginDownload( const char *localName, const char *remoteName ) { + + Com_DPrintf("***** CL_BeginDownload *****\n" + "Localname: %s\n" + "Remotename: %s\n" + "****************************\n", localName, remoteName); + + Q_strncpyz ( clc.downloadName, localName, sizeof(clc.downloadName) ); + Com_sprintf( clc.downloadTempName, sizeof(clc.downloadTempName), "%s.tmp", localName ); + + // Set so UI gets access to it + Cvar_Set( "cl_downloadName", remoteName ); + Cvar_Set( "cl_downloadSize", "0" ); + Cvar_Set( "cl_downloadCount", "0" ); + Cvar_SetValue( "cl_downloadTime", cls.realtime ); + + clc.downloadBlock = 0; // Starting new file + clc.downloadCount = 0; + + CL_AddReliableCommand( va("download %s", remoteName) ); +} + +/* +================= +CL_NextDownload + +A download completed or failed +================= +*/ +void CL_NextDownload(void) { + char *s; + char *remoteName, *localName; + + // We are looking to start a download here + if (*clc.downloadList) { + s = clc.downloadList; + + // format is: + // @remotename@localname@remotename@localname, etc. + + if (*s == '@') + s++; + remoteName = s; + + if ( (s = strchr(s, '@')) == NULL ) { + CL_DownloadsComplete(); + return; + } + + *s++ = 0; + localName = s; + if ( (s = strchr(s, '@')) != NULL ) + *s++ = 0; + else + s = localName + strlen(localName); // point at the nul byte + + CL_BeginDownload( localName, remoteName ); + + clc.downloadRestart = qtrue; + + // move over the rest + memmove( clc.downloadList, s, strlen(s) + 1); + + return; + } + + CL_DownloadsComplete(); +} + +/* +================= +CL_InitDownloads + +After receiving a valid game state, we valid the cgame and local zip files here +and determine if we need to download them +================= +*/ +void CL_InitDownloads(void) { + char missingfiles[1024]; + + if ( !cl_allowDownload->integer ) + { + // autodownload is disabled on the client + // but it's possible that some referenced files on the server are missing + if (FS_ComparePaks( missingfiles, sizeof( missingfiles ), qfalse ) ) + { + // NOTE TTimo I would rather have that printed as a modal message box + // but at this point while joining the game we don't know wether we will successfully join or not + Com_Printf( "\nWARNING: You are missing some files referenced by the server:\n%s" + "You might not be able to join the game\n" + "Go to the setting menu to turn on autodownload, or get the file elsewhere\n\n", missingfiles ); + } + } + else if ( FS_ComparePaks( clc.downloadList, sizeof( clc.downloadList ) , qtrue ) ) { + + Com_Printf("Need paks: %s\n", clc.downloadList ); + + if ( *clc.downloadList ) { + // if autodownloading is not enabled on the server + cls.state = CA_CONNECTED; + CL_NextDownload(); + return; + } + + } + + CL_DownloadsComplete(); +} + +/* +================= +CL_CheckForResend + +Resend a connect message if the last one has timed out +================= +*/ +void CL_CheckForResend( void ) { + int port, i; + char info[MAX_INFO_STRING]; + char data[MAX_INFO_STRING]; + + // don't send anything if playing back a demo + if ( clc.demoplaying ) { + return; + } + + // resend if we haven't gotten a reply yet + if ( cls.state != CA_CONNECTING && cls.state != CA_CHALLENGING ) { + return; + } + + if ( cls.realtime - clc.connectTime < RETRANSMIT_TIMEOUT ) { + return; + } + + clc.connectTime = cls.realtime; // for retransmit requests + clc.connectPacketCount++; + + + switch ( cls.state ) { + case CA_CONNECTING: + // requesting a challenge + if ( !Sys_IsLANAddress( clc.serverAddress ) ) { + CL_RequestAuthorization(); + } + NET_OutOfBandPrint(NS_CLIENT, clc.serverAddress, "getchallenge"); + break; + + case CA_CHALLENGING: + // sending back the challenge + port = Cvar_VariableValue ("net_qport"); + + Q_strncpyz( info, Cvar_InfoString( CVAR_USERINFO ), sizeof( info ) ); + Info_SetValueForKey( info, "protocol", va("%i", PROTOCOL_VERSION ) ); + Info_SetValueForKey( info, "qport", va("%i", port ) ); + Info_SetValueForKey( info, "challenge", va("%i", clc.challenge ) ); + + strcpy(data, "connect "); + // TTimo adding " " around the userinfo string to avoid truncated userinfo on the server + // (Com_TokenizeString tokenizes around spaces) + data[8] = '"'; + + for(i=0;iadr.type = NA_IP; + server->adr.ip[0] = address->ip[0]; + server->adr.ip[1] = address->ip[1]; + server->adr.ip[2] = address->ip[2]; + server->adr.ip[3] = address->ip[3]; + server->adr.port = address->port; + server->clients = 0; + server->hostName[0] = '\0'; + server->mapName[0] = '\0'; + server->maxClients = 0; + server->maxPing = 0; + server->minPing = 0; + server->ping = -1; + server->game[0] = '\0'; + server->gameType = 0; + server->netType = 0; +} + +#define MAX_SERVERSPERPACKET 256 + +/* +=================== +CL_ServersResponsePacket +=================== +*/ +void CL_ServersResponsePacket( netadr_t from, msg_t *msg ) { + int i, count, max, total; + serverAddress_t addresses[MAX_SERVERSPERPACKET]; + int numservers; + byte* buffptr; + byte* buffend; + + Com_Printf("CL_ServersResponsePacket\n"); + + if (cls.numglobalservers == -1) { + // state to detect lack of servers or lack of response + cls.numglobalservers = 0; + cls.numGlobalServerAddresses = 0; + } + + if (cls.nummplayerservers == -1) { + cls.nummplayerservers = 0; + } + + // parse through server response string + numservers = 0; + buffptr = msg->data; + buffend = buffptr + msg->cursize; + while (buffptr+1 < buffend) { + // advance to initial token + do { + if (*buffptr++ == '\\') + break; + } + while (buffptr < buffend); + + if ( buffptr >= buffend - 6 ) { + break; + } + + // parse out ip + addresses[numservers].ip[0] = *buffptr++; + addresses[numservers].ip[1] = *buffptr++; + addresses[numservers].ip[2] = *buffptr++; + addresses[numservers].ip[3] = *buffptr++; + + // parse out port + addresses[numservers].port = (*buffptr++)<<8; + addresses[numservers].port += *buffptr++; + addresses[numservers].port = BigShort( addresses[numservers].port ); + + // syntax check + if (*buffptr != '\\') { + break; + } + + Com_DPrintf( "server: %d ip: %d.%d.%d.%d:%d\n",numservers, + addresses[numservers].ip[0], + addresses[numservers].ip[1], + addresses[numservers].ip[2], + addresses[numservers].ip[3], + addresses[numservers].port ); + + numservers++; + if (numservers >= MAX_SERVERSPERPACKET) { + break; + } + + // parse out EOT + if (buffptr[1] == 'E' && buffptr[2] == 'O' && buffptr[3] == 'T') { + break; + } + } + + if (cls.masterNum == 0) { + count = cls.numglobalservers; + max = MAX_GLOBAL_SERVERS; + } else { + count = cls.nummplayerservers; + max = MAX_OTHER_SERVERS; + } + + for (i = 0; i < numservers && count < max; i++) { + // build net address + serverInfo_t *server = (cls.masterNum == 0) ? &cls.globalServers[count] : &cls.mplayerServers[count]; + + CL_InitServerInfo( server, &addresses[i] ); + // advance to next slot + count++; + } + + // if getting the global list + if (cls.masterNum == 0) { + if ( cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS ) { + // if we couldn't store the servers in the main list anymore + for (; i < numservers && count >= max; i++) { + serverAddress_t *addr; + // just store the addresses in an additional list + addr = &cls.globalServerAddresses[cls.numGlobalServerAddresses++]; + addr->ip[0] = addresses[i].ip[0]; + addr->ip[1] = addresses[i].ip[1]; + addr->ip[2] = addresses[i].ip[2]; + addr->ip[3] = addresses[i].ip[3]; + addr->port = addresses[i].port; + } + } + } + + if (cls.masterNum == 0) { + cls.numglobalservers = count; + total = count + cls.numGlobalServerAddresses; + } else { + cls.nummplayerservers = count; + total = count; + } + + Com_Printf("%d servers parsed (total %d)\n", numservers, total); +} + +/* +================= +CL_ConnectionlessPacket + +Responses to broadcasts, etc +================= +*/ +void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { + char *s; + char *c; + + MSG_BeginReadingOOB( msg ); + MSG_ReadLong( msg ); // skip the -1 + + s = MSG_ReadStringLine( msg ); + + Cmd_TokenizeString( s ); + + c = Cmd_Argv(0); + + Com_DPrintf ("CL packet %s: %s\n", NET_AdrToString(from), c); + + // challenge from the server we are connecting to + if ( !Q_stricmp(c, "challengeResponse") ) { + if ( cls.state != CA_CONNECTING ) { + Com_Printf( "Unwanted challenge response received. Ignored.\n" ); + } else { + // start sending challenge repsonse instead of challenge request packets + clc.challenge = atoi(Cmd_Argv(1)); + cls.state = CA_CHALLENGING; + clc.connectPacketCount = 0; + clc.connectTime = -99999; + + // take this address as the new server address. This allows + // a server proxy to hand off connections to multiple servers + clc.serverAddress = from; + Com_DPrintf ("challengeResponse: %d\n", clc.challenge); + } + return; + } + + // server connection + if ( !Q_stricmp(c, "connectResponse") ) { + if ( cls.state >= CA_CONNECTED ) { + Com_Printf ("Dup connect received. Ignored.\n"); + return; + } + if ( cls.state != CA_CHALLENGING ) { + Com_Printf ("connectResponse packet while not connecting. Ignored.\n"); + return; + } + if ( !NET_CompareBaseAdr( from, clc.serverAddress ) ) { + Com_Printf( "connectResponse from a different address. Ignored.\n" ); + Com_Printf( "%s should have been %s\n", NET_AdrToString( from ), + NET_AdrToString( clc.serverAddress ) ); + return; + } + Netchan_Setup (NS_CLIENT, &clc.netchan, from, Cvar_VariableValue( "net_qport" ) ); + cls.state = CA_CONNECTED; + clc.lastPacketSentTime = -9999; // send first packet immediately + return; + } + + // server responding to an info broadcast + if ( !Q_stricmp(c, "infoResponse") ) { + CL_ServerInfoPacket( from, msg ); + return; + } + + // server responding to a get playerlist + if ( !Q_stricmp(c, "statusResponse") ) { + CL_ServerStatusResponse( from, msg ); + return; + } + + // a disconnect message from the server, which will happen if the server + // dropped the connection but it is still getting packets from us + if (!Q_stricmp(c, "disconnect")) { + CL_DisconnectPacket( from ); + return; + } + + // echo request from server + if ( !Q_stricmp(c, "echo") ) { + NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) ); + return; + } + + // cd check + if ( !Q_stricmp(c, "keyAuthorize") ) { + // we don't use these now, so dump them on the floor + return; + } + + // global MOTD from id + if ( !Q_stricmp(c, "motd") ) { + CL_MotdPacket( from ); + return; + } + + // echo request from server + if ( !Q_stricmp(c, "print") ) { + s = MSG_ReadString( msg ); + Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) ); + Com_Printf( "%s", s ); + return; + } + + // echo request from server + if ( !Q_strncmp(c, "getserversResponse", 18) ) { + CL_ServersResponsePacket( from, msg ); + return; + } + + Com_DPrintf ("Unknown connectionless packet command.\n"); +} + + +/* +================= +CL_PacketEvent + +A packet has arrived from the main event loop +================= +*/ +void CL_PacketEvent( netadr_t from, msg_t *msg ) { + int headerBytes; + + clc.lastPacketTime = cls.realtime; + + if ( msg->cursize >= 4 && *(int *)msg->data == -1 ) { + CL_ConnectionlessPacket( from, msg ); + return; + } + + if ( cls.state < CA_CONNECTED ) { + return; // can't be a valid sequenced packet + } + + if ( msg->cursize < 4 ) { + Com_Printf ("%s: Runt packet\n",NET_AdrToString( from )); + return; + } + + // + // packet from server + // + if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) { + Com_DPrintf ("%s:sequenced packet without connection\n" + ,NET_AdrToString( from ) ); + // FIXME: send a client disconnect? + return; + } + + if (!CL_Netchan_Process( &clc.netchan, msg) ) { + return; // out of order, duplicated, etc + } + + // the header is different lengths for reliable and unreliable messages + headerBytes = msg->readcount; + + // track the last message received so it can be returned in + // client messages, allowing the server to detect a dropped + // gamestate + clc.serverMessageSequence = LittleLong( *(int *)msg->data ); + + clc.lastPacketTime = cls.realtime; + CL_ParseServerMessage( msg ); + + // + // we don't know if it is ok to save a demo message until + // after we have parsed the frame + // + if ( clc.demorecording && !clc.demowaiting ) { + CL_WriteDemoMessage( msg, headerBytes ); + } +} + +/* +================== +CL_CheckTimeout + +================== +*/ +void CL_CheckTimeout( void ) { + // + // check timeout + // + if ( ( !cl_paused->integer || !sv_paused->integer ) + && cls.state >= CA_CONNECTED && cls.state != CA_CINEMATIC + && cls.realtime - clc.lastPacketTime > cl_timeout->value*1000) { + if (++cl.timeoutcount > 5) { // timeoutcount saves debugger + Com_Printf ("\nServer connection timed out.\n"); + CL_Disconnect( qtrue ); + return; + } + } else { + cl.timeoutcount = 0; + } +} + + +//============================================================================ + +/* +================== +CL_CheckUserinfo + +================== +*/ +void CL_CheckUserinfo( void ) { + // don't add reliable commands when not yet connected + if ( cls.state < CA_CHALLENGING ) { + return; + } + // don't overflow the reliable command buffer when paused + if ( cl_paused->integer ) { + return; + } + // send a reliable userinfo update if needed + if ( cvar_modifiedFlags & CVAR_USERINFO ) { + cvar_modifiedFlags &= ~CVAR_USERINFO; + CL_AddReliableCommand( va("userinfo \"%s\"", Cvar_InfoString( CVAR_USERINFO ) ) ); + } + +} + +/* +================== +CL_Frame + +================== +*/ +void CL_Frame ( int msec ) { + + if ( !com_cl_running->integer ) { + return; + } + + if ( cls.cddialog ) { + // bring up the cd error dialog if needed + cls.cddialog = qfalse; + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NEED_CD ); + } else if ( cls.state == CA_DISCONNECTED && !( cls.keyCatchers & KEYCATCH_UI ) + && !com_sv_running->integer ) { + // if disconnected, bring up the menu + S_StopAllSounds(); + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); + } + + // if recording an avi, lock to a fixed fps + if ( cl_avidemo->integer && msec) { + // save the current screen + if ( cls.state == CA_ACTIVE || cl_forceavidemo->integer) { + Cbuf_ExecuteText( EXEC_NOW, "screenshot silent\n" ); + } + // fixed time for next frame' + msec = (1000 / cl_avidemo->integer) * com_timescale->value; + if (msec == 0) { + msec = 1; + } + } + + // save the msec before checking pause + cls.realFrametime = msec; + + // decide the simulation time + cls.frametime = msec; + + cls.realtime += cls.frametime; + + if ( cl_timegraph->integer ) { + SCR_DebugGraph ( cls.realFrametime * 0.25, 0 ); + } + + // see if we need to update any userinfo + CL_CheckUserinfo(); + + // if we haven't gotten a packet in a long time, + // drop the connection + CL_CheckTimeout(); + + // send intentions now + CL_SendCmd(); + + // resend a connection request if necessary + CL_CheckForResend(); + + // decide on the serverTime to render + CL_SetCGameTime(); + + // update the screen + SCR_UpdateScreen(); + + // update audio + S_Update(); + + // advance local effects for next frame + SCR_RunCinematic(); + + Con_RunConsole(); + + cls.framecount++; +} + + +//============================================================================ + +/* +================ +CL_RefPrintf + +DLL glue +================ +*/ +void QDECL CL_RefPrintf( int print_level, const char *fmt, ...) { + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start (argptr,fmt); + Q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + + if ( print_level == PRINT_ALL ) { + Com_Printf ("%s", msg); + } else if ( print_level == PRINT_WARNING ) { + Com_Printf (S_COLOR_YELLOW "%s", msg); // yellow + } else if ( print_level == PRINT_DEVELOPER ) { + Com_DPrintf (S_COLOR_RED "%s", msg); // red + } +} + + + +/* +============ +CL_ShutdownRef +============ +*/ +void CL_ShutdownRef( void ) { + if ( !re.Shutdown ) { + return; + } + re.Shutdown( qtrue ); + Com_Memset( &re, 0, sizeof( re ) ); +} + +/* +============ +CL_InitRenderer +============ +*/ +void CL_InitRenderer( void ) { + // this sets up the renderer and calls R_Init + re.BeginRegistration( &cls.glconfig ); + + // load character sets + cls.charSetShader = re.RegisterShader( "gfx/2d/bigchars" ); + cls.whiteShader = re.RegisterShader( "white" ); + cls.consoleShader = re.RegisterShader( "console" ); + g_console_field_width = cls.glconfig.vidWidth / SMALLCHAR_WIDTH - 2; + g_consoleField.widthInChars = g_console_field_width; +} + +/* +============================ +CL_StartHunkUsers + +After the server has cleared the hunk, these will need to be restarted +This is the only place that any of these functions are called from +============================ +*/ +void CL_StartHunkUsers( void ) { + if (!com_cl_running) { + return; + } + + if ( !com_cl_running->integer ) { + return; + } + + if ( !cls.rendererStarted ) { + cls.rendererStarted = qtrue; + CL_InitRenderer(); + } + + if ( !cls.soundStarted ) { + cls.soundStarted = qtrue; + S_Init(); + } + + if ( !cls.soundRegistered ) { + cls.soundRegistered = qtrue; + S_BeginRegistration(); + } + + if ( !cls.uiStarted ) { + cls.uiStarted = qtrue; + CL_InitUI(); + } +} + +/* +============ +CL_RefMalloc +============ +*/ +void *CL_RefMalloc( int size ) { + return Z_TagMalloc( size, TAG_RENDERER ); +} + +int CL_ScaledMilliseconds(void) { + return Sys_Milliseconds()*com_timescale->value; +} + +/* +============ +CL_InitRef +============ +*/ +void CL_InitRef( void ) { + refimport_t ri; + refexport_t *ret; + + Com_Printf( "----- Initializing Renderer ----\n" ); + + ri.Cmd_AddCommand = Cmd_AddCommand; + ri.Cmd_RemoveCommand = Cmd_RemoveCommand; + ri.Cmd_Argc = Cmd_Argc; + ri.Cmd_Argv = Cmd_Argv; + ri.Cmd_ExecuteText = Cbuf_ExecuteText; + ri.Printf = CL_RefPrintf; + ri.Error = Com_Error; + ri.Milliseconds = CL_ScaledMilliseconds; + ri.Malloc = CL_RefMalloc; + ri.Free = Z_Free; +#ifdef HUNK_DEBUG + ri.Hunk_AllocDebug = Hunk_AllocDebug; +#else + ri.Hunk_Alloc = Hunk_Alloc; +#endif + ri.Hunk_AllocateTempMemory = Hunk_AllocateTempMemory; + ri.Hunk_FreeTempMemory = Hunk_FreeTempMemory; + ri.CM_DrawDebugSurface = CM_DrawDebugSurface; + ri.FS_ReadFile = FS_ReadFile; + ri.FS_FreeFile = FS_FreeFile; + ri.FS_WriteFile = FS_WriteFile; + ri.FS_FreeFileList = FS_FreeFileList; + ri.FS_ListFiles = FS_ListFiles; + ri.FS_FileIsInPAK = FS_FileIsInPAK; + ri.FS_FileExists = FS_FileExists; + ri.Cvar_Get = Cvar_Get; + ri.Cvar_Set = Cvar_Set; + + // cinematic stuff + + ri.CIN_UploadCinematic = CIN_UploadCinematic; + ri.CIN_PlayCinematic = CIN_PlayCinematic; + ri.CIN_RunCinematic = CIN_RunCinematic; + + ret = GetRefAPI( REF_API_VERSION, &ri ); + +#if defined __USEA3D && defined __A3D_GEOM + hA3Dg_ExportRenderGeom (ret); +#endif + + Com_Printf( "-------------------------------\n"); + + if ( !ret ) { + Com_Error (ERR_FATAL, "Couldn't initialize refresh" ); + } + + re = *ret; + + // unpause so the cgame definately gets a snapshot and renders a frame + Cvar_Set( "cl_paused", "0" ); +} + + +//=========================================================================================== + + +void CL_SetModel_f( void ) { + char *arg; + char name[256]; + + arg = Cmd_Argv( 1 ); + if (arg[0]) { + Cvar_Set( "model", arg ); + Cvar_Set( "headmodel", arg ); + } else { + Cvar_VariableStringBuffer( "model", name, sizeof(name) ); + Com_Printf("model is set to %s\n", name); + } +} + +/* +==================== +CL_Init +==================== +*/ +void CL_Init( void ) { + Com_Printf( "----- Client Initialization -----\n" ); + + Con_Init (); + + CL_ClearState (); + + cls.state = CA_DISCONNECTED; // no longer CA_UNINITIALIZED + + cls.realtime = 0; + + CL_InitInput (); + + // + // register our variables + // + cl_noprint = Cvar_Get( "cl_noprint", "0", 0 ); + cl_motd = Cvar_Get ("cl_motd", "1", 0); + + cl_timeout = Cvar_Get ("cl_timeout", "200", 0); + + cl_timeNudge = Cvar_Get ("cl_timeNudge", "0", CVAR_TEMP ); + cl_shownet = Cvar_Get ("cl_shownet", "0", CVAR_TEMP ); + cl_showSend = Cvar_Get ("cl_showSend", "0", CVAR_TEMP ); + cl_showTimeDelta = Cvar_Get ("cl_showTimeDelta", "0", CVAR_TEMP ); + cl_freezeDemo = Cvar_Get ("cl_freezeDemo", "0", CVAR_TEMP ); + rcon_client_password = Cvar_Get ("rconPassword", "", CVAR_TEMP ); + cl_activeAction = Cvar_Get( "activeAction", "", CVAR_TEMP ); + + cl_timedemo = Cvar_Get ("timedemo", "0", 0); + cl_avidemo = Cvar_Get ("cl_avidemo", "0", 0); + cl_forceavidemo = Cvar_Get ("cl_forceavidemo", "0", 0); + + rconAddress = Cvar_Get ("rconAddress", "", 0); + + cl_yawspeed = Cvar_Get ("cl_yawspeed", "140", CVAR_ARCHIVE); + cl_pitchspeed = Cvar_Get ("cl_pitchspeed", "140", CVAR_ARCHIVE); + cl_anglespeedkey = Cvar_Get ("cl_anglespeedkey", "1.5", 0); + + cl_maxpackets = Cvar_Get ("cl_maxpackets", "30", CVAR_ARCHIVE ); + cl_packetdup = Cvar_Get ("cl_packetdup", "1", CVAR_ARCHIVE ); + + cl_run = Cvar_Get ("cl_run", "1", CVAR_ARCHIVE); + cl_sensitivity = Cvar_Get ("sensitivity", "5", CVAR_ARCHIVE); + cl_mouseAccel = Cvar_Get ("cl_mouseAccel", "0", CVAR_ARCHIVE); + cl_freelook = Cvar_Get( "cl_freelook", "1", CVAR_ARCHIVE ); + + cl_showMouseRate = Cvar_Get ("cl_showmouserate", "0", 0); + + cl_allowDownload = Cvar_Get ("cl_allowDownload", "0", CVAR_ARCHIVE); + + cl_conXOffset = Cvar_Get ("cl_conXOffset", "0", 0); +#ifdef MACOS_X + // In game video is REALLY slow in Mac OS X right now due to driver slowness + cl_inGameVideo = Cvar_Get ("r_inGameVideo", "0", CVAR_ARCHIVE); +#else + cl_inGameVideo = Cvar_Get ("r_inGameVideo", "1", CVAR_ARCHIVE); +#endif + + cl_serverStatusResendTime = Cvar_Get ("cl_serverStatusResendTime", "750", 0); + + // init autoswitch so the ui will have it correctly even + // if the cgame hasn't been started + Cvar_Get ("cg_autoswitch", "1", CVAR_ARCHIVE); + + m_pitch = Cvar_Get ("m_pitch", "0.022", CVAR_ARCHIVE); + m_yaw = Cvar_Get ("m_yaw", "0.022", CVAR_ARCHIVE); + m_forward = Cvar_Get ("m_forward", "0.25", CVAR_ARCHIVE); + m_side = Cvar_Get ("m_side", "0.25", CVAR_ARCHIVE); +#ifdef MACOS_X + // Input is jittery on OS X w/o this + m_filter = Cvar_Get ("m_filter", "1", CVAR_ARCHIVE); +#else + m_filter = Cvar_Get ("m_filter", "0", CVAR_ARCHIVE); +#endif + + cl_motdString = Cvar_Get( "cl_motdString", "", CVAR_ROM ); + + Cvar_Get( "cl_maxPing", "800", CVAR_ARCHIVE ); + + + // userinfo + Cvar_Get ("name", "UnnamedPlayer", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("rate", "3000", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("snaps", "20", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("model", "sarge", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("headmodel", "sarge", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("team_model", "james", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("team_headmodel", "*james", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("g_redTeam", "Stroggs", CVAR_SERVERINFO | CVAR_ARCHIVE); + Cvar_Get ("g_blueTeam", "Pagans", CVAR_SERVERINFO | CVAR_ARCHIVE); + Cvar_Get ("color1", "4", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("color2", "5", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("handicap", "100", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("teamtask", "0", CVAR_USERINFO ); + Cvar_Get ("sex", "male", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("cl_anonymous", "0", CVAR_USERINFO | CVAR_ARCHIVE ); + + Cvar_Get ("password", "", CVAR_USERINFO); + Cvar_Get ("cg_predictItems", "1", CVAR_USERINFO | CVAR_ARCHIVE ); + + + // cgame might not be initialized before menu is used + Cvar_Get ("cg_viewsize", "100", CVAR_ARCHIVE ); + + // + // register our commands + // + Cmd_AddCommand ("cmd", CL_ForwardToServer_f); + Cmd_AddCommand ("configstrings", CL_Configstrings_f); + Cmd_AddCommand ("clientinfo", CL_Clientinfo_f); + Cmd_AddCommand ("snd_restart", CL_Snd_Restart_f); + Cmd_AddCommand ("vid_restart", CL_Vid_Restart_f); + Cmd_AddCommand ("disconnect", CL_Disconnect_f); + Cmd_AddCommand ("record", CL_Record_f); + Cmd_AddCommand ("demo", CL_PlayDemo_f); + Cmd_AddCommand ("cinematic", CL_PlayCinematic_f); + Cmd_AddCommand ("stoprecord", CL_StopRecord_f); + Cmd_AddCommand ("connect", CL_Connect_f); + Cmd_AddCommand ("reconnect", CL_Reconnect_f); + Cmd_AddCommand ("localservers", CL_LocalServers_f); + Cmd_AddCommand ("globalservers", CL_GlobalServers_f); + Cmd_AddCommand ("rcon", CL_Rcon_f); + Cmd_AddCommand ("setenv", CL_Setenv_f ); + Cmd_AddCommand ("ping", CL_Ping_f ); + Cmd_AddCommand ("serverstatus", CL_ServerStatus_f ); + Cmd_AddCommand ("showip", CL_ShowIP_f ); + Cmd_AddCommand ("fs_openedList", CL_OpenedPK3List_f ); + Cmd_AddCommand ("fs_referencedList", CL_ReferencedPK3List_f ); + Cmd_AddCommand ("model", CL_SetModel_f ); + CL_InitRef(); + + SCR_Init (); + + Cbuf_Execute (); + + Cvar_Set( "cl_running", "1" ); + + Com_Printf( "----- Client Initialization Complete -----\n" ); +} + + +/* +=============== +CL_Shutdown + +=============== +*/ +void CL_Shutdown( void ) { + static qboolean recursive = qfalse; + + Com_Printf( "----- CL_Shutdown -----\n" ); + + if ( recursive ) { + printf ("recursive shutdown\n"); + return; + } + recursive = qtrue; + + CL_Disconnect( qtrue ); + + S_Shutdown(); + CL_ShutdownRef(); + + CL_ShutdownUI(); + + Cmd_RemoveCommand ("cmd"); + Cmd_RemoveCommand ("configstrings"); + Cmd_RemoveCommand ("userinfo"); + Cmd_RemoveCommand ("snd_restart"); + Cmd_RemoveCommand ("vid_restart"); + Cmd_RemoveCommand ("disconnect"); + Cmd_RemoveCommand ("record"); + Cmd_RemoveCommand ("demo"); + Cmd_RemoveCommand ("cinematic"); + Cmd_RemoveCommand ("stoprecord"); + Cmd_RemoveCommand ("connect"); + Cmd_RemoveCommand ("localservers"); + Cmd_RemoveCommand ("globalservers"); + Cmd_RemoveCommand ("rcon"); + Cmd_RemoveCommand ("setenv"); + Cmd_RemoveCommand ("ping"); + Cmd_RemoveCommand ("serverstatus"); + Cmd_RemoveCommand ("showip"); + Cmd_RemoveCommand ("model"); + + Cvar_Set( "cl_running", "0" ); + + recursive = qfalse; + + Com_Memset( &cls, 0, sizeof( cls ) ); + + Com_Printf( "-----------------------\n" ); + +} + +static void CL_SetServerInfo(serverInfo_t *server, const char *info, int ping) { + if (server) { + if (info) { + server->clients = atoi(Info_ValueForKey(info, "clients")); + Q_strncpyz(server->hostName,Info_ValueForKey(info, "hostname"), MAX_NAME_LENGTH); + Q_strncpyz(server->mapName, Info_ValueForKey(info, "mapname"), MAX_NAME_LENGTH); + server->maxClients = atoi(Info_ValueForKey(info, "sv_maxclients")); + Q_strncpyz(server->game,Info_ValueForKey(info, "game"), MAX_NAME_LENGTH); + server->gameType = atoi(Info_ValueForKey(info, "gametype")); + server->netType = atoi(Info_ValueForKey(info, "nettype")); + server->minPing = atoi(Info_ValueForKey(info, "minping")); + server->maxPing = atoi(Info_ValueForKey(info, "maxping")); + server->punkbuster = atoi(Info_ValueForKey(info, "punkbuster")); + } + server->ping = ping; + } +} + +static void CL_SetServerInfoByAddress(netadr_t from, const char *info, int ping) { + int i; + + for (i = 0; i < MAX_OTHER_SERVERS; i++) { + if (NET_CompareAdr(from, cls.localServers[i].adr)) { + CL_SetServerInfo(&cls.localServers[i], info, ping); + } + } + + for (i = 0; i < MAX_OTHER_SERVERS; i++) { + if (NET_CompareAdr(from, cls.mplayerServers[i].adr)) { + CL_SetServerInfo(&cls.mplayerServers[i], info, ping); + } + } + + for (i = 0; i < MAX_GLOBAL_SERVERS; i++) { + if (NET_CompareAdr(from, cls.globalServers[i].adr)) { + CL_SetServerInfo(&cls.globalServers[i], info, ping); + } + } + + for (i = 0; i < MAX_OTHER_SERVERS; i++) { + if (NET_CompareAdr(from, cls.favoriteServers[i].adr)) { + CL_SetServerInfo(&cls.favoriteServers[i], info, ping); + } + } + +} + +/* +=================== +CL_ServerInfoPacket +=================== +*/ +void CL_ServerInfoPacket( netadr_t from, msg_t *msg ) { + int i, type; + char info[MAX_INFO_STRING]; + char* str; + char *infoString; + int prot; + + infoString = MSG_ReadString( msg ); + + // if this isn't the correct protocol version, ignore it + prot = atoi( Info_ValueForKey( infoString, "protocol" ) ); + if ( prot != PROTOCOL_VERSION ) { + Com_DPrintf( "Different protocol info packet: %s\n", infoString ); + return; + } + + // iterate servers waiting for ping response + for (i=0; iretrieved = qtrue; + return qfalse; + } + + // if this server status request has the same address + if ( NET_CompareAdr( to, serverStatus->address) ) { + // if we recieved an response for this server status request + if (!serverStatus->pending) { + Q_strncpyz(serverStatusString, serverStatus->string, maxLen); + serverStatus->retrieved = qtrue; + serverStatus->startTime = 0; + return qtrue; + } + // resend the request regularly + else if ( serverStatus->startTime < Com_Milliseconds() - cl_serverStatusResendTime->integer ) { + serverStatus->print = qfalse; + serverStatus->pending = qtrue; + serverStatus->retrieved = qfalse; + serverStatus->time = 0; + serverStatus->startTime = Com_Milliseconds(); + NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); + return qfalse; + } + } + // if retrieved + else if ( serverStatus->retrieved ) { + serverStatus->address = to; + serverStatus->print = qfalse; + serverStatus->pending = qtrue; + serverStatus->retrieved = qfalse; + serverStatus->startTime = Com_Milliseconds(); + serverStatus->time = 0; + NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); + return qfalse; + } + return qfalse; +} + +/* +=================== +CL_ServerStatusResponse +=================== +*/ +void CL_ServerStatusResponse( netadr_t from, msg_t *msg ) { + char *s; + char info[MAX_INFO_STRING]; + int i, l, score, ping; + int len; + serverStatus_t *serverStatus; + + serverStatus = NULL; + for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { + if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) { + serverStatus = &cl_serverStatusList[i]; + break; + } + } + // if we didn't request this server status + if (!serverStatus) { + return; + } + + s = MSG_ReadStringLine( msg ); + + len = 0; + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "%s", s); + + if (serverStatus->print) { + Com_Printf("Server settings:\n"); + // print cvars + while (*s) { + for (i = 0; i < 2 && *s; i++) { + if (*s == '\\') + s++; + l = 0; + while (*s) { + info[l++] = *s; + if (l >= MAX_INFO_STRING-1) + break; + s++; + if (*s == '\\') { + break; + } + } + info[l] = '\0'; + if (i) { + Com_Printf("%s\n", info); + } + else { + Com_Printf("%-24s", info); + } + } + } + } + + len = strlen(serverStatus->string); + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\"); + + if (serverStatus->print) { + Com_Printf("\nPlayers:\n"); + Com_Printf("num: score: ping: name:\n"); + } + for (i = 0, s = MSG_ReadStringLine( msg ); *s; s = MSG_ReadStringLine( msg ), i++) { + + len = strlen(serverStatus->string); + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\%s", s); + + if (serverStatus->print) { + score = ping = 0; + sscanf(s, "%d %d", &score, &ping); + s = strchr(s, ' '); + if (s) + s = strchr(s+1, ' '); + if (s) + s++; + else + s = "unknown"; + Com_Printf("%-2d %-3d %-3d %s\n", i, score, ping, s ); + } + } + len = strlen(serverStatus->string); + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\"); + + serverStatus->time = Com_Milliseconds(); + serverStatus->address = from; + serverStatus->pending = qfalse; + if (serverStatus->print) { + serverStatus->retrieved = qtrue; + } +} + +/* +================== +CL_LocalServers_f +================== +*/ +void CL_LocalServers_f( void ) { + char *message; + int i, j; + netadr_t to; + + Com_Printf( "Scanning for servers on the local network...\n"); + + // reset the list, waiting for response + cls.numlocalservers = 0; + cls.pingUpdateSource = AS_LOCAL; + + for (i = 0; i < MAX_OTHER_SERVERS; i++) { + qboolean b = cls.localServers[i].visible; + Com_Memset(&cls.localServers[i], 0, sizeof(cls.localServers[i])); + cls.localServers[i].visible = b; + } + Com_Memset( &to, 0, sizeof( to ) ); + + // The 'xxx' in the message is a challenge that will be echoed back + // by the server. We don't care about that here, but master servers + // can use that to prevent spoofed server responses from invalid ip + message = "\377\377\377\377getinfo xxx"; + + // send each message twice in case one is dropped + for ( i = 0 ; i < 2 ; i++ ) { + // send a broadcast packet on each server port + // we support multiple server ports so a single machine + // can nicely run multiple servers + for ( j = 0 ; j < NUM_SERVER_PORTS ; j++ ) { + to.port = BigShort( (short)(PORT_SERVER + j) ); + + to.type = NA_BROADCAST; + NET_SendPacket( NS_CLIENT, strlen( message ), message, to ); + + to.type = NA_BROADCAST_IPX; + NET_SendPacket( NS_CLIENT, strlen( message ), message, to ); + } + } +} + +/* +================== +CL_GlobalServers_f +================== +*/ +void CL_GlobalServers_f( void ) { + netadr_t to; + int i; + int count; + char *buffptr; + char command[1024]; + + if ( Cmd_Argc() < 3) { + Com_Printf( "usage: globalservers [keywords]\n"); + return; + } + + cls.masterNum = atoi( Cmd_Argv(1) ); + + Com_Printf( "Requesting servers from the master...\n"); + + // reset the list, waiting for response + // -1 is used to distinguish a "no response" + + if( cls.masterNum == 1 ) { + NET_StringToAdr( MASTER_SERVER_NAME, &to ); + cls.nummplayerservers = -1; + cls.pingUpdateSource = AS_MPLAYER; + } + else { + NET_StringToAdr( MASTER_SERVER_NAME, &to ); + cls.numglobalservers = -1; + cls.pingUpdateSource = AS_GLOBAL; + } + to.type = NA_IP; + to.port = BigShort(PORT_MASTER); + + sprintf( command, "getservers %s", Cmd_Argv(2) ); + + // tack on keywords + buffptr = command + strlen( command ); + count = Cmd_Argc(); + for (i=3; i= MAX_PINGREQUESTS) + return; + + cl_pinglist[n].adr.port = 0; +} + +/* +================== +CL_GetPingQueueCount +================== +*/ +int CL_GetPingQueueCount( void ) +{ + int i; + int count; + ping_t* pingptr; + + count = 0; + pingptr = cl_pinglist; + + for (i=0; iadr.port) { + count++; + } + } + + return (count); +} + +/* +================== +CL_GetFreePing +================== +*/ +ping_t* CL_GetFreePing( void ) +{ + ping_t* pingptr; + ping_t* best; + int oldest; + int i; + int time; + + pingptr = cl_pinglist; + for (i=0; iadr.port) + { + if (!pingptr->time) + { + if (cls.realtime - pingptr->start < 500) + { + // still waiting for response + continue; + } + } + else if (pingptr->time < 500) + { + // results have not been queried + continue; + } + } + + // clear it + pingptr->adr.port = 0; + return (pingptr); + } + + // use oldest entry + pingptr = cl_pinglist; + best = cl_pinglist; + oldest = INT_MIN; + for (i=0; istart; + if (time > oldest) + { + oldest = time; + best = pingptr; + } + } + + return (best); +} + +/* +================== +CL_Ping_f +================== +*/ +void CL_Ping_f( void ) { + netadr_t to; + ping_t* pingptr; + char* server; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "usage: ping [server]\n"); + return; + } + + Com_Memset( &to, 0, sizeof(netadr_t) ); + + server = Cmd_Argv(1); + + if ( !NET_StringToAdr( server, &to ) ) { + return; + } + + pingptr = CL_GetFreePing(); + + memcpy( &pingptr->adr, &to, sizeof (netadr_t) ); + pingptr->start = cls.realtime; + pingptr->time = 0; + + CL_SetServerInfoByAddress(pingptr->adr, NULL, 0); + + NET_OutOfBandPrint( NS_CLIENT, to, "getinfo xxx" ); +} + +/* +================== +CL_UpdateVisiblePings_f +================== +*/ +qboolean CL_UpdateVisiblePings_f(int source) { + int slots, i; + char buff[MAX_STRING_CHARS]; + int pingTime; + int max; + qboolean status = qfalse; + + if (source < 0 || source > AS_FAVORITES) { + return qfalse; + } + + cls.pingUpdateSource = source; + + slots = CL_GetPingQueueCount(); + if (slots < MAX_PINGREQUESTS) { + serverInfo_t *server = NULL; + + max = (source == AS_GLOBAL) ? MAX_GLOBAL_SERVERS : MAX_OTHER_SERVERS; + switch (source) { + case AS_LOCAL : + server = &cls.localServers[0]; + max = cls.numlocalservers; + break; + case AS_MPLAYER : + server = &cls.mplayerServers[0]; + max = cls.nummplayerservers; + break; + case AS_GLOBAL : + server = &cls.globalServers[0]; + max = cls.numglobalservers; + break; + case AS_FAVORITES : + server = &cls.favoriteServers[0]; + max = cls.numfavoriteservers; + break; + } + for (i = 0; i < max; i++) { + if (server[i].visible) { + if (server[i].ping == -1) { + int j; + + if (slots >= MAX_PINGREQUESTS) { + break; + } + for (j = 0; j < MAX_PINGREQUESTS; j++) { + if (!cl_pinglist[j].adr.port) { + continue; + } + if (NET_CompareAdr( cl_pinglist[j].adr, server[i].adr)) { + // already on the list + break; + } + } + if (j >= MAX_PINGREQUESTS) { + status = qtrue; + for (j = 0; j < MAX_PINGREQUESTS; j++) { + if (!cl_pinglist[j].adr.port) { + break; + } + } + memcpy(&cl_pinglist[j].adr, &server[i].adr, sizeof(netadr_t)); + cl_pinglist[j].start = cls.realtime; + cl_pinglist[j].time = 0; + NET_OutOfBandPrint( NS_CLIENT, cl_pinglist[j].adr, "getinfo xxx" ); + slots++; + } + } + // if the server has a ping higher than cl_maxPing or + // the ping packet got lost + else if (server[i].ping == 0) { + // if we are updating global servers + if (source == AS_GLOBAL) { + // + if ( cls.numGlobalServerAddresses > 0 ) { + // overwrite this server with one from the additional global servers + cls.numGlobalServerAddresses--; + CL_InitServerInfo(&server[i], &cls.globalServerAddresses[cls.numGlobalServerAddresses]); + // NOTE: the server[i].visible flag stays untouched + } + } + } + } + } + } + + if (slots) { + status = qtrue; + } + for (i = 0; i < MAX_PINGREQUESTS; i++) { + if (!cl_pinglist[i].adr.port) { + continue; + } + CL_GetPing( i, buff, MAX_STRING_CHARS, &pingTime ); + if (pingTime != 0) { + CL_ClearPing(i); + status = qtrue; + } + } + + return status; +} + +/* +================== +CL_ServerStatus_f +================== +*/ +void CL_ServerStatus_f(void) { + netadr_t to; + char *server; + serverStatus_t *serverStatus; + + Com_Memset( &to, 0, sizeof(netadr_t) ); + + if ( Cmd_Argc() != 2 ) { + if ( cls.state != CA_ACTIVE || clc.demoplaying ) { + Com_Printf ("Not connected to a server.\n"); + Com_Printf( "Usage: serverstatus [server]\n"); + return; + } + server = cls.servername; + } + else { + server = Cmd_Argv(1); + } + + if ( !NET_StringToAdr( server, &to ) ) { + return; + } + + NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); + + serverStatus = CL_GetServerStatus( to ); + serverStatus->address = to; + serverStatus->print = qtrue; + serverStatus->pending = qtrue; +} + +/* +================== +CL_ShowIP_f +================== +*/ +void CL_ShowIP_f(void) { + Sys_ShowIP(); +} + +/* +================= +bool CL_CDKeyValidate +================= +*/ +qboolean CL_CDKeyValidate( const char *key, const char *checksum ) { + char ch; + byte sum; + char chs[3]; + int i, len; + + len = strlen(key); + if( len != CDKEY_LEN ) { + return qfalse; + } + + if( checksum && strlen( checksum ) != CDCHKSUM_LEN ) { + return qfalse; + } + + sum = 0; + // for loop gets rid of conditional assignment warning + for (i = 0; i < len; i++) { + ch = *key++; + if (ch>='a' && ch<='z') { + ch -= 32; + } + switch( ch ) { + case '2': + case '3': + case '7': + case 'A': + case 'B': + case 'C': + case 'D': + case 'G': + case 'H': + case 'J': + case 'L': + case 'P': + case 'R': + case 'S': + case 'T': + case 'W': + sum += ch; + continue; + default: + return qfalse; + } + } + + sprintf(chs, "%02x", sum); + + if (checksum && !Q_stricmp(chs, checksum)) { + return qtrue; + } + + if (!checksum) { + return qtrue; + } + + return qfalse; +} + + -- cgit v1.2.3