Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

cl_main.c (77864B)


      1 /*
      2 ===========================================================================
      3 Copyright (C) 1999-2005 Id Software, Inc.
      4 
      5 This file is part of Quake III Arena source code.
      6 
      7 Quake III Arena source code is free software; you can redistribute it
      8 and/or modify it under the terms of the GNU General Public License as
      9 published by the Free Software Foundation; either version 2 of the License,
     10 or (at your option) any later version.
     11 
     12 Quake III Arena source code is distributed in the hope that it will be
     13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15 GNU General Public License for more details.
     16 
     17 You should have received a copy of the GNU General Public License
     18 along with Foobar; if not, write to the Free Software
     19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     20 ===========================================================================
     21 */
     22 // cl_main.c  -- client main loop
     23 
     24 #include "client.h"
     25 #include <limits.h>
     26 
     27 cvar_t	*cl_nodelta;
     28 cvar_t	*cl_debugMove;
     29 
     30 cvar_t	*cl_noprint;
     31 cvar_t	*cl_motd;
     32 
     33 cvar_t	*rcon_client_password;
     34 cvar_t	*rconAddress;
     35 
     36 cvar_t	*cl_timeout;
     37 cvar_t	*cl_maxpackets;
     38 cvar_t	*cl_packetdup;
     39 cvar_t	*cl_timeNudge;
     40 cvar_t	*cl_showTimeDelta;
     41 cvar_t	*cl_freezeDemo;
     42 
     43 cvar_t	*cl_shownet;
     44 cvar_t	*cl_showSend;
     45 cvar_t	*cl_timedemo;
     46 cvar_t	*cl_avidemo;
     47 cvar_t	*cl_forceavidemo;
     48 
     49 cvar_t	*cl_freelook;
     50 cvar_t	*cl_sensitivity;
     51 
     52 cvar_t	*cl_mouseAccel;
     53 cvar_t	*cl_showMouseRate;
     54 
     55 cvar_t	*m_pitch;
     56 cvar_t	*m_yaw;
     57 cvar_t	*m_forward;
     58 cvar_t	*m_side;
     59 cvar_t	*m_filter;
     60 
     61 cvar_t	*cl_activeAction;
     62 
     63 cvar_t	*cl_motdString;
     64 
     65 cvar_t	*cl_allowDownload;
     66 cvar_t	*cl_conXOffset;
     67 cvar_t	*cl_inGameVideo;
     68 
     69 cvar_t	*cl_serverStatusResendTime;
     70 cvar_t	*cl_trn;
     71 
     72 clientActive_t		cl;
     73 clientConnection_t	clc;
     74 clientStatic_t		cls;
     75 vm_t				*cgvm;
     76 
     77 // Structure containing functions exported from refresh DLL
     78 refexport_t	re;
     79 
     80 ping_t	cl_pinglist[MAX_PINGREQUESTS];
     81 
     82 typedef struct serverStatus_s
     83 {
     84 	char string[BIG_INFO_STRING];
     85 	netadr_t address;
     86 	int time, startTime;
     87 	qboolean pending;
     88 	qboolean print;
     89 	qboolean retrieved;
     90 } serverStatus_t;
     91 
     92 serverStatus_t cl_serverStatusList[MAX_SERVERSTATUSREQUESTS];
     93 int serverStatusCount;
     94 
     95 #if defined __USEA3D && defined __A3D_GEOM
     96 	void hA3Dg_ExportRenderGeom (refexport_t *incoming_re);
     97 #endif
     98 
     99 extern void SV_BotFrame( int time );
    100 void CL_CheckForResend( void );
    101 void CL_ShowIP_f(void);
    102 void CL_ServerStatus_f(void);
    103 void CL_ServerStatusResponse( netadr_t from, msg_t *msg );
    104 
    105 /*
    106 ===============
    107 CL_CDDialog
    108 
    109 Called by Com_Error when a cd is needed
    110 ===============
    111 */
    112 void CL_CDDialog( void ) {
    113 	cls.cddialog = qtrue;	// start it next frame
    114 }
    115 
    116 
    117 /*
    118 =======================================================================
    119 
    120 CLIENT RELIABLE COMMAND COMMUNICATION
    121 
    122 =======================================================================
    123 */
    124 
    125 /*
    126 ======================
    127 CL_AddReliableCommand
    128 
    129 The given command will be transmitted to the server, and is gauranteed to
    130 not have future usercmd_t executed before it is executed
    131 ======================
    132 */
    133 void CL_AddReliableCommand( const char *cmd ) {
    134 	int		index;
    135 
    136 	// if we would be losing an old command that hasn't been acknowledged,
    137 	// we must drop the connection
    138 	if ( clc.reliableSequence - clc.reliableAcknowledge > MAX_RELIABLE_COMMANDS ) {
    139 		Com_Error( ERR_DROP, "Client command overflow" );
    140 	}
    141 	clc.reliableSequence++;
    142 	index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 );
    143 	Q_strncpyz( clc.reliableCommands[ index ], cmd, sizeof( clc.reliableCommands[ index ] ) );
    144 }
    145 
    146 /*
    147 ======================
    148 CL_ChangeReliableCommand
    149 ======================
    150 */
    151 void CL_ChangeReliableCommand( void ) {
    152 	int r, index, l;
    153 
    154 	r = clc.reliableSequence - (random() * 5);
    155 	index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 );
    156 	l = strlen(clc.reliableCommands[ index ]);
    157 	if ( l >= MAX_STRING_CHARS - 1 ) {
    158 		l = MAX_STRING_CHARS - 2;
    159 	}
    160 	clc.reliableCommands[ index ][ l ] = '\n';
    161 	clc.reliableCommands[ index ][ l+1 ] = '\0';
    162 }
    163 
    164 /*
    165 =======================================================================
    166 
    167 CLIENT SIDE DEMO RECORDING
    168 
    169 =======================================================================
    170 */
    171 
    172 /*
    173 ====================
    174 CL_WriteDemoMessage
    175 
    176 Dumps the current net message, prefixed by the length
    177 ====================
    178 */
    179 void CL_WriteDemoMessage ( msg_t *msg, int headerBytes ) {
    180 	int		len, swlen;
    181 
    182 	// write the packet sequence
    183 	len = clc.serverMessageSequence;
    184 	swlen = LittleLong( len );
    185 	FS_Write (&swlen, 4, clc.demofile);
    186 
    187 	// skip the packet sequencing information
    188 	len = msg->cursize - headerBytes;
    189 	swlen = LittleLong(len);
    190 	FS_Write (&swlen, 4, clc.demofile);
    191 	FS_Write ( msg->data + headerBytes, len, clc.demofile );
    192 }
    193 
    194 
    195 /*
    196 ====================
    197 CL_StopRecording_f
    198 
    199 stop recording a demo
    200 ====================
    201 */
    202 void CL_StopRecord_f( void ) {
    203 	int		len;
    204 
    205 	if ( !clc.demorecording ) {
    206 		Com_Printf ("Not recording a demo.\n");
    207 		return;
    208 	}
    209 
    210 	// finish up
    211 	len = -1;
    212 	FS_Write (&len, 4, clc.demofile);
    213 	FS_Write (&len, 4, clc.demofile);
    214 	FS_FCloseFile (clc.demofile);
    215 	clc.demofile = 0;
    216 	clc.demorecording = qfalse;
    217 	clc.spDemoRecording = qfalse;
    218 	Com_Printf ("Stopped demo.\n");
    219 }
    220 
    221 /* 
    222 ================== 
    223 CL_DemoFilename
    224 ================== 
    225 */  
    226 void CL_DemoFilename( int number, char *fileName ) {
    227 	int		a,b,c,d;
    228 
    229 	if ( number < 0 || number > 9999 ) {
    230 		Com_sprintf( fileName, MAX_OSPATH, "demo9999.tga" );
    231 		return;
    232 	}
    233 
    234 	a = number / 1000;
    235 	number -= a*1000;
    236 	b = number / 100;
    237 	number -= b*100;
    238 	c = number / 10;
    239 	number -= c*10;
    240 	d = number;
    241 
    242 	Com_sprintf( fileName, MAX_OSPATH, "demo%i%i%i%i"
    243 		, a, b, c, d );
    244 }
    245 
    246 /*
    247 ====================
    248 CL_Record_f
    249 
    250 record <demoname>
    251 
    252 Begins recording a demo from the current position
    253 ====================
    254 */
    255 static char		demoName[MAX_QPATH];	// compiler bug workaround
    256 void CL_Record_f( void ) {
    257 	char		name[MAX_OSPATH];
    258 	byte		bufData[MAX_MSGLEN];
    259 	msg_t	buf;
    260 	int			i;
    261 	int			len;
    262 	entityState_t	*ent;
    263 	entityState_t	nullstate;
    264 	char		*s;
    265 
    266 	if ( Cmd_Argc() > 2 ) {
    267 		Com_Printf ("record <demoname>\n");
    268 		return;
    269 	}
    270 
    271 	if ( clc.demorecording ) {
    272 		if (!clc.spDemoRecording) {
    273 			Com_Printf ("Already recording.\n");
    274 		}
    275 		return;
    276 	}
    277 
    278 	if ( cls.state != CA_ACTIVE ) {
    279 		Com_Printf ("You must be in a level to record.\n");
    280 		return;
    281 	}
    282 
    283   // sync 0 doesn't prevent recording, so not forcing it off .. everyone does g_sync 1 ; record ; g_sync 0 ..
    284 	if ( !Cvar_VariableValue( "g_synchronousClients" ) ) {
    285 		Com_Printf (S_COLOR_YELLOW "WARNING: You should set 'g_synchronousClients 1' for smoother demo recording\n");
    286 	}
    287 
    288 	if ( Cmd_Argc() == 2 ) {
    289 		s = Cmd_Argv(1);
    290 		Q_strncpyz( demoName, s, sizeof( demoName ) );
    291 		Com_sprintf (name, sizeof(name), "demos/%s.dm_%d", demoName, PROTOCOL_VERSION );
    292 	} else {
    293 		int		number;
    294 
    295 		// scan for a free demo name
    296 		for ( number = 0 ; number <= 9999 ; number++ ) {
    297 			CL_DemoFilename( number, demoName );
    298 			Com_sprintf (name, sizeof(name), "demos/%s.dm_%d", demoName, PROTOCOL_VERSION );
    299 
    300 			len = FS_ReadFile( name, NULL );
    301 			if ( len <= 0 ) {
    302 				break;	// file doesn't exist
    303 			}
    304 		}
    305 	}
    306 
    307 	// open the demo file
    308 
    309 	Com_Printf ("recording to %s.\n", name);
    310 	clc.demofile = FS_FOpenFileWrite( name );
    311 	if ( !clc.demofile ) {
    312 		Com_Printf ("ERROR: couldn't open.\n");
    313 		return;
    314 	}
    315 	clc.demorecording = qtrue;
    316 	if (Cvar_VariableValue("ui_recordSPDemo")) {
    317 	  clc.spDemoRecording = qtrue;
    318 	} else {
    319 	  clc.spDemoRecording = qfalse;
    320 	}
    321 
    322 
    323 	Q_strncpyz( clc.demoName, demoName, sizeof( clc.demoName ) );
    324 
    325 	// don't start saving messages until a non-delta compressed message is received
    326 	clc.demowaiting = qtrue;
    327 
    328 	// write out the gamestate message
    329 	MSG_Init (&buf, bufData, sizeof(bufData));
    330 	MSG_Bitstream(&buf);
    331 
    332 	// NOTE, MRE: all server->client messages now acknowledge
    333 	MSG_WriteLong( &buf, clc.reliableSequence );
    334 
    335 	MSG_WriteByte (&buf, svc_gamestate);
    336 	MSG_WriteLong (&buf, clc.serverCommandSequence );
    337 
    338 	// configstrings
    339 	for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) {
    340 		if ( !cl.gameState.stringOffsets[i] ) {
    341 			continue;
    342 		}
    343 		s = cl.gameState.stringData + cl.gameState.stringOffsets[i];
    344 		MSG_WriteByte (&buf, svc_configstring);
    345 		MSG_WriteShort (&buf, i);
    346 		MSG_WriteBigString (&buf, s);
    347 	}
    348 
    349 	// baselines
    350 	Com_Memset (&nullstate, 0, sizeof(nullstate));
    351 	for ( i = 0; i < MAX_GENTITIES ; i++ ) {
    352 		ent = &cl.entityBaselines[i];
    353 		if ( !ent->number ) {
    354 			continue;
    355 		}
    356 		MSG_WriteByte (&buf, svc_baseline);		
    357 		MSG_WriteDeltaEntity (&buf, &nullstate, ent, qtrue );
    358 	}
    359 
    360 	MSG_WriteByte( &buf, svc_EOF );
    361 	
    362 	// finished writing the gamestate stuff
    363 
    364 	// write the client num
    365 	MSG_WriteLong(&buf, clc.clientNum);
    366 	// write the checksum feed
    367 	MSG_WriteLong(&buf, clc.checksumFeed);
    368 
    369 	// finished writing the client packet
    370 	MSG_WriteByte( &buf, svc_EOF );
    371 
    372 	// write it to the demo file
    373 	len = LittleLong( clc.serverMessageSequence - 1 );
    374 	FS_Write (&len, 4, clc.demofile);
    375 
    376 	len = LittleLong (buf.cursize);
    377 	FS_Write (&len, 4, clc.demofile);
    378 	FS_Write (buf.data, buf.cursize, clc.demofile);
    379 
    380 	// the rest of the demo file will be copied from net messages
    381 }
    382 
    383 /*
    384 =======================================================================
    385 
    386 CLIENT SIDE DEMO PLAYBACK
    387 
    388 =======================================================================
    389 */
    390 
    391 /*
    392 =================
    393 CL_DemoCompleted
    394 =================
    395 */
    396 void CL_DemoCompleted( void ) {
    397 	if (cl_timedemo && cl_timedemo->integer) {
    398 		int	time;
    399 		
    400 		time = Sys_Milliseconds() - clc.timeDemoStart;
    401 		if ( time > 0 ) {
    402 			Com_Printf ("%i frames, %3.1f seconds: %3.1f fps\n", clc.timeDemoFrames,
    403 			time/1000.0, clc.timeDemoFrames*1000.0 / time);
    404 		}
    405 	}
    406 
    407 	CL_Disconnect( qtrue );
    408 	CL_NextDemo();
    409 }
    410 
    411 /*
    412 =================
    413 CL_ReadDemoMessage
    414 =================
    415 */
    416 void CL_ReadDemoMessage( void ) {
    417 	int			r;
    418 	msg_t		buf;
    419 	byte		bufData[ MAX_MSGLEN ];
    420 	int			s;
    421 
    422 	if ( !clc.demofile ) {
    423 		CL_DemoCompleted ();
    424 		return;
    425 	}
    426 
    427 	// get the sequence number
    428 	r = FS_Read( &s, 4, clc.demofile);
    429 	if ( r != 4 ) {
    430 		CL_DemoCompleted ();
    431 		return;
    432 	}
    433 	clc.serverMessageSequence = LittleLong( s );
    434 
    435 	// init the message
    436 	MSG_Init( &buf, bufData, sizeof( bufData ) );
    437 
    438 	// get the length
    439 	r = FS_Read (&buf.cursize, 4, clc.demofile);
    440 	if ( r != 4 ) {
    441 		CL_DemoCompleted ();
    442 		return;
    443 	}
    444 	buf.cursize = LittleLong( buf.cursize );
    445 	if ( buf.cursize == -1 ) {
    446 		CL_DemoCompleted ();
    447 		return;
    448 	}
    449 	if ( buf.cursize > buf.maxsize ) {
    450 		Com_Error (ERR_DROP, "CL_ReadDemoMessage: demoMsglen > MAX_MSGLEN");
    451 	}
    452 	r = FS_Read( buf.data, buf.cursize, clc.demofile );
    453 	if ( r != buf.cursize ) {
    454 		Com_Printf( "Demo file was truncated.\n");
    455 		CL_DemoCompleted ();
    456 		return;
    457 	}
    458 
    459 	clc.lastPacketTime = cls.realtime;
    460 	buf.readcount = 0;
    461 	CL_ParseServerMessage( &buf );
    462 }
    463 
    464 /*
    465 ====================
    466 CL_WalkDemoExt
    467 ====================
    468 */
    469 static void CL_WalkDemoExt(char *arg, char *name, int *demofile)
    470 {
    471 	int i = 0;
    472 	*demofile = 0;
    473 	while(demo_protocols[i])
    474 	{
    475 		Com_sprintf (name, MAX_OSPATH, "demos/%s.dm_%d", arg, demo_protocols[i]);
    476 		FS_FOpenFileRead( name, demofile, qtrue );
    477 		if (*demofile)
    478 		{
    479 			Com_Printf("Demo file: %s\n", name);
    480 			break;
    481 		}
    482 		else
    483 			Com_Printf("Not found: %s\n", name);
    484 		i++;
    485 	}
    486 }
    487 
    488 /*
    489 ====================
    490 CL_PlayDemo_f
    491 
    492 demo <demoname>
    493 
    494 ====================
    495 */
    496 void CL_PlayDemo_f( void ) {
    497 	char		name[MAX_OSPATH];
    498 	char		*arg, *ext_test;
    499 	int			protocol, i;
    500 	char		retry[MAX_OSPATH];
    501 
    502 	if (Cmd_Argc() != 2) {
    503 		Com_Printf ("playdemo <demoname>\n");
    504 		return;
    505 	}
    506 
    507 	// make sure a local server is killed
    508 	Cvar_Set( "sv_killserver", "1" );
    509 
    510 	CL_Disconnect( qtrue );
    511 
    512 	// open the demo file
    513 	arg = Cmd_Argv(1);
    514 	
    515 	// check for an extension .dm_?? (?? is protocol)
    516 	ext_test = arg + strlen(arg) - 6;
    517 	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] == '_'))
    518 	{
    519 		protocol = atoi(ext_test+4);
    520 		i=0;
    521 		while(demo_protocols[i])
    522 		{
    523 			if (demo_protocols[i] == protocol)
    524 				break;
    525 			i++;
    526 		}
    527 		if (demo_protocols[i])
    528 		{
    529 			Com_sprintf (name, sizeof(name), "demos/%s", arg);
    530 			FS_FOpenFileRead( name, &clc.demofile, qtrue );
    531 		} else {
    532 			Com_Printf("Protocol %d not supported for demos\n", protocol);
    533 			Q_strncpyz(retry, arg, sizeof(retry));
    534 			retry[strlen(retry)-6] = 0;
    535 			CL_WalkDemoExt( retry, name, &clc.demofile );
    536 		}
    537 	} else {
    538 		CL_WalkDemoExt( arg, name, &clc.demofile );
    539 	}
    540 	
    541 	if (!clc.demofile) {
    542 		Com_Error( ERR_DROP, "couldn't open %s", name);
    543 		return;
    544 	}
    545 	Q_strncpyz( clc.demoName, Cmd_Argv(1), sizeof( clc.demoName ) );
    546 
    547 	Con_Close();
    548 
    549 	cls.state = CA_CONNECTED;
    550 	clc.demoplaying = qtrue;
    551 	Q_strncpyz( cls.servername, Cmd_Argv(1), sizeof( cls.servername ) );
    552 
    553 	// read demo messages until connected
    554 	while ( cls.state >= CA_CONNECTED && cls.state < CA_PRIMED ) {
    555 		CL_ReadDemoMessage();
    556 	}
    557 	// don't get the first snapshot this frame, to prevent the long
    558 	// time from the gamestate load from messing causing a time skip
    559 	clc.firstDemoFrameSkipped = qfalse;
    560 }
    561 
    562 
    563 /*
    564 ====================
    565 CL_StartDemoLoop
    566 
    567 Closing the main menu will restart the demo loop
    568 ====================
    569 */
    570 void CL_StartDemoLoop( void ) {
    571 	// start the demo loop again
    572 	Cbuf_AddText ("d1\n");
    573 	cls.keyCatchers = 0;
    574 }
    575 
    576 /*
    577 ==================
    578 CL_NextDemo
    579 
    580 Called when a demo or cinematic finishes
    581 If the "nextdemo" cvar is set, that command will be issued
    582 ==================
    583 */
    584 void CL_NextDemo( void ) {
    585 	char	v[MAX_STRING_CHARS];
    586 
    587 	Q_strncpyz( v, Cvar_VariableString ("nextdemo"), sizeof(v) );
    588 	v[MAX_STRING_CHARS-1] = 0;
    589 	Com_DPrintf("CL_NextDemo: %s\n", v );
    590 	if (!v[0]) {
    591 		return;
    592 	}
    593 
    594 	Cvar_Set ("nextdemo","");
    595 	Cbuf_AddText (v);
    596 	Cbuf_AddText ("\n");
    597 	Cbuf_Execute();
    598 }
    599 
    600 
    601 //======================================================================
    602 
    603 /*
    604 =====================
    605 CL_ShutdownAll
    606 =====================
    607 */
    608 void CL_ShutdownAll(void) {
    609 
    610 	// clear sounds
    611 	S_DisableSounds();
    612 	// shutdown CGame
    613 	CL_ShutdownCGame();
    614 	// shutdown UI
    615 	CL_ShutdownUI();
    616 
    617 	// shutdown the renderer
    618 	if ( re.Shutdown ) {
    619 		re.Shutdown( qfalse );		// don't destroy window or context
    620 	}
    621 
    622 	cls.uiStarted = qfalse;
    623 	cls.cgameStarted = qfalse;
    624 	cls.rendererStarted = qfalse;
    625 	cls.soundRegistered = qfalse;
    626 }
    627 
    628 /*
    629 =================
    630 CL_FlushMemory
    631 
    632 Called by CL_MapLoading, CL_Connect_f, CL_PlayDemo_f, and CL_ParseGamestate the only
    633 ways a client gets into a game
    634 Also called by Com_Error
    635 =================
    636 */
    637 void CL_FlushMemory( void ) {
    638 
    639 	// shutdown all the client stuff
    640 	CL_ShutdownAll();
    641 
    642 	// if not running a server clear the whole hunk
    643 	if ( !com_sv_running->integer ) {
    644 		// clear the whole hunk
    645 		Hunk_Clear();
    646 		// clear collision map data
    647 		CM_ClearMap();
    648 	}
    649 	else {
    650 		// clear all the client data on the hunk
    651 		Hunk_ClearToMark();
    652 	}
    653 
    654 	CL_StartHunkUsers();
    655 }
    656 
    657 /*
    658 =====================
    659 CL_MapLoading
    660 
    661 A local server is starting to load a map, so update the
    662 screen to let the user know about it, then dump all client
    663 memory on the hunk from cgame, ui, and renderer
    664 =====================
    665 */
    666 void CL_MapLoading( void ) {
    667 	if ( !com_cl_running->integer ) {
    668 		return;
    669 	}
    670 
    671 	Con_Close();
    672 	cls.keyCatchers = 0;
    673 
    674 	// if we are already connected to the local host, stay connected
    675 	if ( cls.state >= CA_CONNECTED && !Q_stricmp( cls.servername, "localhost" ) ) {
    676 		cls.state = CA_CONNECTED;		// so the connect screen is drawn
    677 		Com_Memset( cls.updateInfoString, 0, sizeof( cls.updateInfoString ) );
    678 		Com_Memset( clc.serverMessage, 0, sizeof( clc.serverMessage ) );
    679 		Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) );
    680 		clc.lastPacketSentTime = -9999;
    681 		SCR_UpdateScreen();
    682 	} else {
    683 		// clear nextmap so the cinematic shutdown doesn't execute it
    684 		Cvar_Set( "nextmap", "" );
    685 		CL_Disconnect( qtrue );
    686 		Q_strncpyz( cls.servername, "localhost", sizeof(cls.servername) );
    687 		cls.state = CA_CHALLENGING;		// so the connect screen is drawn
    688 		cls.keyCatchers = 0;
    689 		SCR_UpdateScreen();
    690 		clc.connectTime = -RETRANSMIT_TIMEOUT;
    691 		NET_StringToAdr( cls.servername, &clc.serverAddress);
    692 		// we don't need a challenge on the localhost
    693 
    694 		CL_CheckForResend();
    695 	}
    696 }
    697 
    698 /*
    699 =====================
    700 CL_ClearState
    701 
    702 Called before parsing a gamestate
    703 =====================
    704 */
    705 void CL_ClearState (void) {
    706 
    707 //	S_StopAllSounds();
    708 
    709 	Com_Memset( &cl, 0, sizeof( cl ) );
    710 }
    711 
    712 
    713 /*
    714 =====================
    715 CL_Disconnect
    716 
    717 Called when a connection, demo, or cinematic is being terminated.
    718 Goes from a connected state to either a menu state or a console state
    719 Sends a disconnect message to the server
    720 This is also called on Com_Error and Com_Quit, so it shouldn't cause any errors
    721 =====================
    722 */
    723 void CL_Disconnect( qboolean showMainMenu ) {
    724 	if ( !com_cl_running || !com_cl_running->integer ) {
    725 		return;
    726 	}
    727 
    728 	// shutting down the client so enter full screen ui mode
    729 	Cvar_Set("r_uiFullScreen", "1");
    730 
    731 	if ( clc.demorecording ) {
    732 		CL_StopRecord_f ();
    733 	}
    734 
    735 	if (clc.download) {
    736 		FS_FCloseFile( clc.download );
    737 		clc.download = 0;
    738 	}
    739 	*clc.downloadTempName = *clc.downloadName = 0;
    740 	Cvar_Set( "cl_downloadName", "" );
    741 
    742 	if ( clc.demofile ) {
    743 		FS_FCloseFile( clc.demofile );
    744 		clc.demofile = 0;
    745 	}
    746 
    747 	if ( uivm && showMainMenu ) {
    748 		VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE );
    749 	}
    750 
    751 	SCR_StopCinematic ();
    752 	S_ClearSoundBuffer();
    753 
    754 	// send a disconnect message to the server
    755 	// send it a few times in case one is dropped
    756 	if ( cls.state >= CA_CONNECTED ) {
    757 		CL_AddReliableCommand( "disconnect" );
    758 		CL_WritePacket();
    759 		CL_WritePacket();
    760 		CL_WritePacket();
    761 	}
    762 	
    763 	CL_ClearState ();
    764 
    765 	// wipe the client connection
    766 	Com_Memset( &clc, 0, sizeof( clc ) );
    767 
    768 	cls.state = CA_DISCONNECTED;
    769 
    770 	// allow cheats locally
    771 	Cvar_Set( "sv_cheats", "1" );
    772 
    773 	// not connected to a pure server anymore
    774 	cl_connectedToPureServer = qfalse;
    775 }
    776 
    777 
    778 /*
    779 ===================
    780 CL_ForwardCommandToServer
    781 
    782 adds the current command line as a clientCommand
    783 things like godmode, noclip, etc, are commands directed to the server,
    784 so when they are typed in at the console, they will need to be forwarded.
    785 ===================
    786 */
    787 void CL_ForwardCommandToServer( const char *string ) {
    788 	char	*cmd;
    789 
    790 	cmd = Cmd_Argv(0);
    791 
    792 	// ignore key up commands
    793 	if ( cmd[0] == '-' ) {
    794 		return;
    795 	}
    796 
    797 	if ( clc.demoplaying || cls.state < CA_CONNECTED || cmd[0] == '+' ) {
    798 		Com_Printf ("Unknown command \"%s\"\n", cmd);
    799 		return;
    800 	}
    801 
    802 	if ( Cmd_Argc() > 1 ) {
    803 		CL_AddReliableCommand( string );
    804 	} else {
    805 		CL_AddReliableCommand( cmd );
    806 	}
    807 }
    808 
    809 /*
    810 ===================
    811 CL_RequestMotd
    812 
    813 ===================
    814 */
    815 void CL_RequestMotd( void ) {
    816 	char		info[MAX_INFO_STRING];
    817 
    818 	if ( !cl_motd->integer ) {
    819 		return;
    820 	}
    821 	Com_Printf( "Resolving %s\n", UPDATE_SERVER_NAME );
    822 	if ( !NET_StringToAdr( UPDATE_SERVER_NAME, &cls.updateServer  ) ) {
    823 		Com_Printf( "Couldn't resolve address\n" );
    824 		return;
    825 	}
    826 	cls.updateServer.port = BigShort( PORT_UPDATE );
    827 	Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", UPDATE_SERVER_NAME,
    828 		cls.updateServer.ip[0], cls.updateServer.ip[1],
    829 		cls.updateServer.ip[2], cls.updateServer.ip[3],
    830 		BigShort( cls.updateServer.port ) );
    831 	
    832 	info[0] = 0;
    833   // NOTE TTimo xoring against Com_Milliseconds, otherwise we may not have a true randomization
    834   // only srand I could catch before here is tr_noise.c l:26 srand(1001)
    835   // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=382
    836   // NOTE: the Com_Milliseconds xoring only affects the lower 16-bit word,
    837   //   but I decided it was enough randomization
    838 	Com_sprintf( cls.updateChallenge, sizeof( cls.updateChallenge ), "%i", ((rand() << 16) ^ rand()) ^ Com_Milliseconds());
    839 
    840 	Info_SetValueForKey( info, "challenge", cls.updateChallenge );
    841 	Info_SetValueForKey( info, "renderer", cls.glconfig.renderer_string );
    842 	Info_SetValueForKey( info, "version", com_version->string );
    843 
    844 	NET_OutOfBandPrint( NS_CLIENT, cls.updateServer, "getmotd \"%s\"\n", info );
    845 }
    846 
    847 /*
    848 ===================
    849 CL_RequestAuthorization
    850 
    851 Authorization server protocol
    852 -----------------------------
    853 
    854 All commands are text in Q3 out of band packets (leading 0xff 0xff 0xff 0xff).
    855 
    856 Whenever the client tries to get a challenge from the server it wants to
    857 connect to, it also blindly fires off a packet to the authorize server:
    858 
    859 getKeyAuthorize <challenge> <cdkey>
    860 
    861 cdkey may be "demo"
    862 
    863 
    864 #OLD The authorize server returns a:
    865 #OLD 
    866 #OLD keyAthorize <challenge> <accept | deny>
    867 #OLD 
    868 #OLD A client will be accepted if the cdkey is valid and it has not been used by any other IP
    869 #OLD address in the last 15 minutes.
    870 
    871 
    872 The server sends a:
    873 
    874 getIpAuthorize <challenge> <ip>
    875 
    876 The authorize server returns a:
    877 
    878 ipAuthorize <challenge> <accept | deny | demo | unknown >
    879 
    880 A client will be accepted if a valid cdkey was sent by that ip (only) in the last 15 minutes.
    881 If no response is received from the authorize server after two tries, the client will be let
    882 in anyway.
    883 ===================
    884 */
    885 void CL_RequestAuthorization( void ) {
    886 	char	nums[64];
    887 	int		i, j, l;
    888 	cvar_t	*fs;
    889 
    890 	if ( !cls.authorizeServer.port ) {
    891 		Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME );
    892 		if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &cls.authorizeServer  ) ) {
    893 			Com_Printf( "Couldn't resolve address\n" );
    894 			return;
    895 		}
    896 
    897 		cls.authorizeServer.port = BigShort( PORT_AUTHORIZE );
    898 		Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME,
    899 			cls.authorizeServer.ip[0], cls.authorizeServer.ip[1],
    900 			cls.authorizeServer.ip[2], cls.authorizeServer.ip[3],
    901 			BigShort( cls.authorizeServer.port ) );
    902 	}
    903 	if ( cls.authorizeServer.type == NA_BAD ) {
    904 		return;
    905 	}
    906 
    907 	if ( Cvar_VariableValue( "fs_restrict" ) ) {
    908 		Q_strncpyz( nums, "demota", sizeof( nums ) );
    909 	} else {
    910 		// only grab the alphanumeric values from the cdkey, to avoid any dashes or spaces
    911 		j = 0;
    912 		l = strlen( cl_cdkey );
    913 		if ( l > 32 ) {
    914 			l = 32;
    915 		}
    916 		for ( i = 0 ; i < l ; i++ ) {
    917 			if ( ( cl_cdkey[i] >= '0' && cl_cdkey[i] <= '9' )
    918 				|| ( cl_cdkey[i] >= 'a' && cl_cdkey[i] <= 'z' )
    919 				|| ( cl_cdkey[i] >= 'A' && cl_cdkey[i] <= 'Z' )
    920 				) {
    921 				nums[j] = cl_cdkey[i];
    922 				j++;
    923 			}
    924 		}
    925 		nums[j] = 0;
    926 	}
    927 
    928 	fs = Cvar_Get ("cl_anonymous", "0", CVAR_INIT|CVAR_SYSTEMINFO );
    929 
    930 	NET_OutOfBandPrint(NS_CLIENT, cls.authorizeServer, va("getKeyAuthorize %i %s", fs->integer, nums) );
    931 }
    932 
    933 /*
    934 ======================================================================
    935 
    936 CONSOLE COMMANDS
    937 
    938 ======================================================================
    939 */
    940 
    941 /*
    942 ==================
    943 CL_ForwardToServer_f
    944 ==================
    945 */
    946 void CL_ForwardToServer_f( void ) {
    947 	if ( cls.state != CA_ACTIVE || clc.demoplaying ) {
    948 		Com_Printf ("Not connected to a server.\n");
    949 		return;
    950 	}
    951 	
    952 	// don't forward the first argument
    953 	if ( Cmd_Argc() > 1 ) {
    954 		CL_AddReliableCommand( Cmd_Args() );
    955 	}
    956 }
    957 
    958 /*
    959 ==================
    960 CL_Setenv_f
    961 
    962 Mostly for controlling voodoo environment variables
    963 ==================
    964 */
    965 void CL_Setenv_f( void ) {
    966 	int argc = Cmd_Argc();
    967 
    968 	if ( argc > 2 ) {
    969 		char buffer[1024];
    970 		int i;
    971 
    972 		strcpy( buffer, Cmd_Argv(1) );
    973 		strcat( buffer, "=" );
    974 
    975 		for ( i = 2; i < argc; i++ ) {
    976 			strcat( buffer, Cmd_Argv( i ) );
    977 			strcat( buffer, " " );
    978 		}
    979 
    980 		putenv( buffer );
    981 	} else if ( argc == 2 ) {
    982 		char *env = getenv( Cmd_Argv(1) );
    983 
    984 		if ( env ) {
    985 			Com_Printf( "%s=%s\n", Cmd_Argv(1), env );
    986 		} else {
    987 			Com_Printf( "%s undefined\n", Cmd_Argv(1), env );
    988 		}
    989 	}
    990 }
    991 
    992 
    993 /*
    994 ==================
    995 CL_Disconnect_f
    996 ==================
    997 */
    998 void CL_Disconnect_f( void ) {
    999 	SCR_StopCinematic();
   1000 	Cvar_Set("ui_singlePlayerActive", "0");
   1001 	if ( cls.state != CA_DISCONNECTED && cls.state != CA_CINEMATIC ) {
   1002 		Com_Error (ERR_DISCONNECT, "Disconnected from server");
   1003 	}
   1004 }
   1005 
   1006 
   1007 /*
   1008 ================
   1009 CL_Reconnect_f
   1010 
   1011 ================
   1012 */
   1013 void CL_Reconnect_f( void ) {
   1014 	if ( !strlen( cls.servername ) || !strcmp( cls.servername, "localhost" ) ) {
   1015 		Com_Printf( "Can't reconnect to localhost.\n" );
   1016 		return;
   1017 	}
   1018 	Cvar_Set("ui_singlePlayerActive", "0");
   1019 	Cbuf_AddText( va("connect %s\n", cls.servername ) );
   1020 }
   1021 
   1022 /*
   1023 ================
   1024 CL_Connect_f
   1025 
   1026 ================
   1027 */
   1028 void CL_Connect_f( void ) {
   1029 	char	*server;
   1030 
   1031 	if ( Cmd_Argc() != 2 ) {
   1032 		Com_Printf( "usage: connect [server]\n");
   1033 		return;	
   1034 	}
   1035 
   1036 	Cvar_Set("ui_singlePlayerActive", "0");
   1037 
   1038 	// fire a message off to the motd server
   1039 	CL_RequestMotd();
   1040 
   1041 	// clear any previous "server full" type messages
   1042 	clc.serverMessage[0] = 0;
   1043 
   1044 	server = Cmd_Argv (1);
   1045 
   1046 	if ( com_sv_running->integer && !strcmp( server, "localhost" ) ) {
   1047 		// if running a local server, kill it
   1048 		SV_Shutdown( "Server quit\n" );
   1049 	}
   1050 
   1051 	// make sure a local server is killed
   1052 	Cvar_Set( "sv_killserver", "1" );
   1053 	SV_Frame( 0 );
   1054 
   1055 	CL_Disconnect( qtrue );
   1056 	Con_Close();
   1057 
   1058 	/* MrE: 2000-09-13: now called in CL_DownloadsComplete
   1059 	CL_FlushMemory( );
   1060 	*/
   1061 
   1062 	Q_strncpyz( cls.servername, server, sizeof(cls.servername) );
   1063 
   1064 	if (!NET_StringToAdr( cls.servername, &clc.serverAddress) ) {
   1065 		Com_Printf ("Bad server address\n");
   1066 		cls.state = CA_DISCONNECTED;
   1067 		return;
   1068 	}
   1069 	if (clc.serverAddress.port == 0) {
   1070 		clc.serverAddress.port = BigShort( PORT_SERVER );
   1071 	}
   1072 	Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", cls.servername,
   1073 		clc.serverAddress.ip[0], clc.serverAddress.ip[1],
   1074 		clc.serverAddress.ip[2], clc.serverAddress.ip[3],
   1075 		BigShort( clc.serverAddress.port ) );
   1076 
   1077 	// if we aren't playing on a lan, we need to authenticate
   1078 	// with the cd key
   1079 	if ( NET_IsLocalAddress( clc.serverAddress ) ) {
   1080 		cls.state = CA_CHALLENGING;
   1081 	} else {
   1082 		cls.state = CA_CONNECTING;
   1083 	}
   1084 
   1085 	cls.keyCatchers = 0;
   1086 	clc.connectTime = -99999;	// CL_CheckForResend() will fire immediately
   1087 	clc.connectPacketCount = 0;
   1088 
   1089 	// server connection string
   1090 	Cvar_Set( "cl_currentServerAddress", server );
   1091 }
   1092 
   1093 
   1094 /*
   1095 =====================
   1096 CL_Rcon_f
   1097 
   1098   Send the rest of the command line over as
   1099   an unconnected command.
   1100 =====================
   1101 */
   1102 void CL_Rcon_f( void ) {
   1103 	char	message[1024];
   1104 	netadr_t	to;
   1105 
   1106 	if ( !rcon_client_password->string ) {
   1107 		Com_Printf ("You must set 'rconpassword' before\n"
   1108 					"issuing an rcon command.\n");
   1109 		return;
   1110 	}
   1111 
   1112 	message[0] = -1;
   1113 	message[1] = -1;
   1114 	message[2] = -1;
   1115 	message[3] = -1;
   1116 	message[4] = 0;
   1117 
   1118 	strcat (message, "rcon ");
   1119 
   1120 	strcat (message, rcon_client_password->string);
   1121 	strcat (message, " ");
   1122 
   1123 	// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543
   1124 	strcat (message, Cmd_Cmd()+5);
   1125 
   1126 	if ( cls.state >= CA_CONNECTED ) {
   1127 		to = clc.netchan.remoteAddress;
   1128 	} else {
   1129 		if (!strlen(rconAddress->string)) {
   1130 			Com_Printf ("You must either be connected,\n"
   1131 						"or set the 'rconAddress' cvar\n"
   1132 						"to issue rcon commands\n");
   1133 
   1134 			return;
   1135 		}
   1136 		NET_StringToAdr (rconAddress->string, &to);
   1137 		if (to.port == 0) {
   1138 			to.port = BigShort (PORT_SERVER);
   1139 		}
   1140 	}
   1141 	
   1142 	NET_SendPacket (NS_CLIENT, strlen(message)+1, message, to);
   1143 }
   1144 
   1145 /*
   1146 =================
   1147 CL_SendPureChecksums
   1148 =================
   1149 */
   1150 void CL_SendPureChecksums( void ) {
   1151 	const char *pChecksums;
   1152 	char cMsg[MAX_INFO_VALUE];
   1153 	int i;
   1154 
   1155 	// if we are pure we need to send back a command with our referenced pk3 checksums
   1156 	pChecksums = FS_ReferencedPakPureChecksums();
   1157 
   1158 	// "cp"
   1159 	// "Yf"
   1160 	Com_sprintf(cMsg, sizeof(cMsg), "Yf ");
   1161 	Q_strcat(cMsg, sizeof(cMsg), va("%d ", cl.serverId) );
   1162 	Q_strcat(cMsg, sizeof(cMsg), pChecksums);
   1163 	for (i = 0; i < 2; i++) {
   1164 		cMsg[i] += 10;
   1165 	}
   1166 	CL_AddReliableCommand( cMsg );
   1167 }
   1168 
   1169 /*
   1170 =================
   1171 CL_ResetPureClientAtServer
   1172 =================
   1173 */
   1174 void CL_ResetPureClientAtServer( void ) {
   1175 	CL_AddReliableCommand( va("vdr") );
   1176 }
   1177 
   1178 /*
   1179 =================
   1180 CL_Vid_Restart_f
   1181 
   1182 Restart the video subsystem
   1183 
   1184 we also have to reload the UI and CGame because the renderer
   1185 doesn't know what graphics to reload
   1186 =================
   1187 */
   1188 void CL_Vid_Restart_f( void ) {
   1189 
   1190 	// don't let them loop during the restart
   1191 	S_StopAllSounds();
   1192 	// shutdown the UI
   1193 	CL_ShutdownUI();
   1194 	// shutdown the CGame
   1195 	CL_ShutdownCGame();
   1196 	// shutdown the renderer and clear the renderer interface
   1197 	CL_ShutdownRef();
   1198 	// client is no longer pure untill new checksums are sent
   1199 	CL_ResetPureClientAtServer();
   1200 	// clear pak references
   1201 	FS_ClearPakReferences( FS_UI_REF | FS_CGAME_REF );
   1202 	// reinitialize the filesystem if the game directory or checksum has changed
   1203 	FS_ConditionalRestart( clc.checksumFeed );
   1204 
   1205 	cls.rendererStarted = qfalse;
   1206 	cls.uiStarted = qfalse;
   1207 	cls.cgameStarted = qfalse;
   1208 	cls.soundRegistered = qfalse;
   1209 
   1210 	// unpause so the cgame definately gets a snapshot and renders a frame
   1211 	Cvar_Set( "cl_paused", "0" );
   1212 
   1213 	// if not running a server clear the whole hunk
   1214 	if ( !com_sv_running->integer ) {
   1215 		// clear the whole hunk
   1216 		Hunk_Clear();
   1217 	}
   1218 	else {
   1219 		// clear all the client data on the hunk
   1220 		Hunk_ClearToMark();
   1221 	}
   1222 
   1223 	// initialize the renderer interface
   1224 	CL_InitRef();
   1225 
   1226 	// startup all the client stuff
   1227 	CL_StartHunkUsers();
   1228 
   1229 	// start the cgame if connected
   1230 	if ( cls.state > CA_CONNECTED && cls.state != CA_CINEMATIC ) {
   1231 		cls.cgameStarted = qtrue;
   1232 		CL_InitCGame();
   1233 		// send pure checksums
   1234 		CL_SendPureChecksums();
   1235 	}
   1236 }
   1237 
   1238 /*
   1239 =================
   1240 CL_Snd_Restart_f
   1241 
   1242 Restart the sound subsystem
   1243 The cgame and game must also be forced to restart because
   1244 handles will be invalid
   1245 =================
   1246 */
   1247 void CL_Snd_Restart_f( void ) {
   1248 	S_Shutdown();
   1249 	S_Init();
   1250 
   1251 	CL_Vid_Restart_f();
   1252 }
   1253 
   1254 
   1255 /*
   1256 ==================
   1257 CL_PK3List_f
   1258 ==================
   1259 */
   1260 void CL_OpenedPK3List_f( void ) {
   1261 	Com_Printf("Opened PK3 Names: %s\n", FS_LoadedPakNames());
   1262 }
   1263 
   1264 /*
   1265 ==================
   1266 CL_PureList_f
   1267 ==================
   1268 */
   1269 void CL_ReferencedPK3List_f( void ) {
   1270 	Com_Printf("Referenced PK3 Names: %s\n", FS_ReferencedPakNames());
   1271 }
   1272 
   1273 /*
   1274 ==================
   1275 CL_Configstrings_f
   1276 ==================
   1277 */
   1278 void CL_Configstrings_f( void ) {
   1279 	int		i;
   1280 	int		ofs;
   1281 
   1282 	if ( cls.state != CA_ACTIVE ) {
   1283 		Com_Printf( "Not connected to a server.\n");
   1284 		return;
   1285 	}
   1286 
   1287 	for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) {
   1288 		ofs = cl.gameState.stringOffsets[ i ];
   1289 		if ( !ofs ) {
   1290 			continue;
   1291 		}
   1292 		Com_Printf( "%4i: %s\n", i, cl.gameState.stringData + ofs );
   1293 	}
   1294 }
   1295 
   1296 /*
   1297 ==============
   1298 CL_Clientinfo_f
   1299 ==============
   1300 */
   1301 void CL_Clientinfo_f( void ) {
   1302 	Com_Printf( "--------- Client Information ---------\n" );
   1303 	Com_Printf( "state: %i\n", cls.state );
   1304 	Com_Printf( "Server: %s\n", cls.servername );
   1305 	Com_Printf ("User info settings:\n");
   1306 	Info_Print( Cvar_InfoString( CVAR_USERINFO ) );
   1307 	Com_Printf( "--------------------------------------\n" );
   1308 }
   1309 
   1310 
   1311 //====================================================================
   1312 
   1313 /*
   1314 =================
   1315 CL_DownloadsComplete
   1316 
   1317 Called when all downloading has been completed
   1318 =================
   1319 */
   1320 void CL_DownloadsComplete( void ) {
   1321 
   1322 	// if we downloaded files we need to restart the file system
   1323 	if (clc.downloadRestart) {
   1324 		clc.downloadRestart = qfalse;
   1325 
   1326 		FS_Restart(clc.checksumFeed); // We possibly downloaded a pak, restart the file system to load it
   1327 
   1328 		// inform the server so we get new gamestate info
   1329 		CL_AddReliableCommand( "donedl" );
   1330 
   1331 		// by sending the donedl command we request a new gamestate
   1332 		// so we don't want to load stuff yet
   1333 		return;
   1334 	}
   1335 
   1336 	// let the client game init and load data
   1337 	cls.state = CA_LOADING;
   1338 
   1339 	// Pump the loop, this may change gamestate!
   1340 	Com_EventLoop();
   1341 
   1342 	// if the gamestate was changed by calling Com_EventLoop
   1343 	// then we loaded everything already and we don't want to do it again.
   1344 	if ( cls.state != CA_LOADING ) {
   1345 		return;
   1346 	}
   1347 
   1348 	// starting to load a map so we get out of full screen ui mode
   1349 	Cvar_Set("r_uiFullScreen", "0");
   1350 
   1351 	// flush client memory and start loading stuff
   1352 	// this will also (re)load the UI
   1353 	// if this is a local client then only the client part of the hunk
   1354 	// will be cleared, note that this is done after the hunk mark has been set
   1355 	CL_FlushMemory();
   1356 
   1357 	// initialize the CGame
   1358 	cls.cgameStarted = qtrue;
   1359 	CL_InitCGame();
   1360 
   1361 	// set pure checksums
   1362 	CL_SendPureChecksums();
   1363 
   1364 	CL_WritePacket();
   1365 	CL_WritePacket();
   1366 	CL_WritePacket();
   1367 }
   1368 
   1369 /*
   1370 =================
   1371 CL_BeginDownload
   1372 
   1373 Requests a file to download from the server.  Stores it in the current
   1374 game directory.
   1375 =================
   1376 */
   1377 void CL_BeginDownload( const char *localName, const char *remoteName ) {
   1378 
   1379 	Com_DPrintf("***** CL_BeginDownload *****\n"
   1380 				"Localname: %s\n"
   1381 				"Remotename: %s\n"
   1382 				"****************************\n", localName, remoteName);
   1383 
   1384 	Q_strncpyz ( clc.downloadName, localName, sizeof(clc.downloadName) );
   1385 	Com_sprintf( clc.downloadTempName, sizeof(clc.downloadTempName), "%s.tmp", localName );
   1386 
   1387 	// Set so UI gets access to it
   1388 	Cvar_Set( "cl_downloadName", remoteName );
   1389 	Cvar_Set( "cl_downloadSize", "0" );
   1390 	Cvar_Set( "cl_downloadCount", "0" );
   1391 	Cvar_SetValue( "cl_downloadTime", cls.realtime );
   1392 
   1393 	clc.downloadBlock = 0; // Starting new file
   1394 	clc.downloadCount = 0;
   1395 
   1396 	CL_AddReliableCommand( va("download %s", remoteName) );
   1397 }
   1398 
   1399 /*
   1400 =================
   1401 CL_NextDownload
   1402 
   1403 A download completed or failed
   1404 =================
   1405 */
   1406 void CL_NextDownload(void) {
   1407 	char *s;
   1408 	char *remoteName, *localName;
   1409 
   1410 	// We are looking to start a download here
   1411 	if (*clc.downloadList) {
   1412 		s = clc.downloadList;
   1413 
   1414 		// format is:
   1415 		//  @remotename@localname@remotename@localname, etc.
   1416 
   1417 		if (*s == '@')
   1418 			s++;
   1419 		remoteName = s;
   1420 		
   1421 		if ( (s = strchr(s, '@')) == NULL ) {
   1422 			CL_DownloadsComplete();
   1423 			return;
   1424 		}
   1425 
   1426 		*s++ = 0;
   1427 		localName = s;
   1428 		if ( (s = strchr(s, '@')) != NULL )
   1429 			*s++ = 0;
   1430 		else
   1431 			s = localName + strlen(localName); // point at the nul byte
   1432 
   1433 		CL_BeginDownload( localName, remoteName );
   1434 
   1435 		clc.downloadRestart = qtrue;
   1436 
   1437 		// move over the rest
   1438 		memmove( clc.downloadList, s, strlen(s) + 1);
   1439 
   1440 		return;
   1441 	}
   1442 
   1443 	CL_DownloadsComplete();
   1444 }
   1445 
   1446 /*
   1447 =================
   1448 CL_InitDownloads
   1449 
   1450 After receiving a valid game state, we valid the cgame and local zip files here
   1451 and determine if we need to download them
   1452 =================
   1453 */
   1454 void CL_InitDownloads(void) {
   1455   char missingfiles[1024];
   1456 
   1457   if ( !cl_allowDownload->integer )
   1458   {
   1459     // autodownload is disabled on the client
   1460     // but it's possible that some referenced files on the server are missing
   1461     if (FS_ComparePaks( missingfiles, sizeof( missingfiles ), qfalse ) )
   1462     {      
   1463       // NOTE TTimo I would rather have that printed as a modal message box
   1464       //   but at this point while joining the game we don't know wether we will successfully join or not
   1465       Com_Printf( "\nWARNING: You are missing some files referenced by the server:\n%s"
   1466                   "You might not be able to join the game\n"
   1467                   "Go to the setting menu to turn on autodownload, or get the file elsewhere\n\n", missingfiles );
   1468     }
   1469   }
   1470   else if ( FS_ComparePaks( clc.downloadList, sizeof( clc.downloadList ) , qtrue ) ) {
   1471 
   1472     Com_Printf("Need paks: %s\n", clc.downloadList );
   1473 
   1474 		if ( *clc.downloadList ) {
   1475 			// if autodownloading is not enabled on the server
   1476 			cls.state = CA_CONNECTED;
   1477 			CL_NextDownload();
   1478 			return;
   1479 		}
   1480 
   1481 	}
   1482 		
   1483 	CL_DownloadsComplete();
   1484 }
   1485 
   1486 /*
   1487 =================
   1488 CL_CheckForResend
   1489 
   1490 Resend a connect message if the last one has timed out
   1491 =================
   1492 */
   1493 void CL_CheckForResend( void ) {
   1494 	int		port, i;
   1495 	char	info[MAX_INFO_STRING];
   1496 	char	data[MAX_INFO_STRING];
   1497 
   1498 	// don't send anything if playing back a demo
   1499 	if ( clc.demoplaying ) {
   1500 		return;
   1501 	}
   1502 
   1503 	// resend if we haven't gotten a reply yet
   1504 	if ( cls.state != CA_CONNECTING && cls.state != CA_CHALLENGING ) {
   1505 		return;
   1506 	}
   1507 
   1508 	if ( cls.realtime - clc.connectTime < RETRANSMIT_TIMEOUT ) {
   1509 		return;
   1510 	}
   1511 
   1512 	clc.connectTime = cls.realtime;	// for retransmit requests
   1513 	clc.connectPacketCount++;
   1514 
   1515 
   1516 	switch ( cls.state ) {
   1517 	case CA_CONNECTING:
   1518 		// requesting a challenge
   1519 		if ( !Sys_IsLANAddress( clc.serverAddress ) ) {
   1520 			CL_RequestAuthorization();
   1521 		}
   1522 		NET_OutOfBandPrint(NS_CLIENT, clc.serverAddress, "getchallenge");
   1523 		break;
   1524 		
   1525 	case CA_CHALLENGING:
   1526 		// sending back the challenge
   1527 		port = Cvar_VariableValue ("net_qport");
   1528 
   1529 		Q_strncpyz( info, Cvar_InfoString( CVAR_USERINFO ), sizeof( info ) );
   1530 		Info_SetValueForKey( info, "protocol", va("%i", PROTOCOL_VERSION ) );
   1531 		Info_SetValueForKey( info, "qport", va("%i", port ) );
   1532 		Info_SetValueForKey( info, "challenge", va("%i", clc.challenge ) );
   1533 		
   1534 		strcpy(data, "connect ");
   1535     // TTimo adding " " around the userinfo string to avoid truncated userinfo on the server
   1536     //   (Com_TokenizeString tokenizes around spaces)
   1537     data[8] = '"';
   1538 
   1539 		for(i=0;i<strlen(info);i++) {
   1540 			data[9+i] = info[i];	// + (clc.challenge)&0x3;
   1541 		}
   1542     data[9+i] = '"';
   1543 		data[10+i] = 0;
   1544 
   1545     // NOTE TTimo don't forget to set the right data length!
   1546 		NET_OutOfBandData( NS_CLIENT, clc.serverAddress, &data[0], i+10 );
   1547 		// the most current userinfo has been sent, so watch for any
   1548 		// newer changes to userinfo variables
   1549 		cvar_modifiedFlags &= ~CVAR_USERINFO;
   1550 		break;
   1551 
   1552 	default:
   1553 		Com_Error( ERR_FATAL, "CL_CheckForResend: bad cls.state" );
   1554 	}
   1555 }
   1556 
   1557 /*
   1558 ===================
   1559 CL_DisconnectPacket
   1560 
   1561 Sometimes the server can drop the client and the netchan based
   1562 disconnect can be lost.  If the client continues to send packets
   1563 to the server, the server will send out of band disconnect packets
   1564 to the client so it doesn't have to wait for the full timeout period.
   1565 ===================
   1566 */
   1567 void CL_DisconnectPacket( netadr_t from ) {
   1568 	if ( cls.state < CA_AUTHORIZING ) {
   1569 		return;
   1570 	}
   1571 
   1572 	// if not from our server, ignore it
   1573 	if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) {
   1574 		return;
   1575 	}
   1576 
   1577 	// if we have received packets within three seconds, ignore it
   1578 	// (it might be a malicious spoof)
   1579 	if ( cls.realtime - clc.lastPacketTime < 3000 ) {
   1580 		return;
   1581 	}
   1582 
   1583 	// drop the connection
   1584 	Com_Printf( "Server disconnected for unknown reason\n" );
   1585 	Cvar_Set("com_errorMessage", "Server disconnected for unknown reason\n" );
   1586 	CL_Disconnect( qtrue );
   1587 }
   1588 
   1589 
   1590 /*
   1591 ===================
   1592 CL_MotdPacket
   1593 
   1594 ===================
   1595 */
   1596 void CL_MotdPacket( netadr_t from ) {
   1597 	char	*challenge;
   1598 	char	*info;
   1599 
   1600 	// if not from our server, ignore it
   1601 	if ( !NET_CompareAdr( from, cls.updateServer ) ) {
   1602 		return;
   1603 	}
   1604 
   1605 	info = Cmd_Argv(1);
   1606 
   1607 	// check challenge
   1608 	challenge = Info_ValueForKey( info, "challenge" );
   1609 	if ( strcmp( challenge, cls.updateChallenge ) ) {
   1610 		return;
   1611 	}
   1612 
   1613 	challenge = Info_ValueForKey( info, "motd" );
   1614 
   1615 	Q_strncpyz( cls.updateInfoString, info, sizeof( cls.updateInfoString ) );
   1616 	Cvar_Set( "cl_motdString", challenge );
   1617 }
   1618 
   1619 /*
   1620 ===================
   1621 CL_InitServerInfo
   1622 ===================
   1623 */
   1624 void CL_InitServerInfo( serverInfo_t *server, serverAddress_t *address ) {
   1625 	server->adr.type  = NA_IP;
   1626 	server->adr.ip[0] = address->ip[0];
   1627 	server->adr.ip[1] = address->ip[1];
   1628 	server->adr.ip[2] = address->ip[2];
   1629 	server->adr.ip[3] = address->ip[3];
   1630 	server->adr.port  = address->port;
   1631 	server->clients = 0;
   1632 	server->hostName[0] = '\0';
   1633 	server->mapName[0] = '\0';
   1634 	server->maxClients = 0;
   1635 	server->maxPing = 0;
   1636 	server->minPing = 0;
   1637 	server->ping = -1;
   1638 	server->game[0] = '\0';
   1639 	server->gameType = 0;
   1640 	server->netType = 0;
   1641 }
   1642 
   1643 #define MAX_SERVERSPERPACKET	256
   1644 
   1645 /*
   1646 ===================
   1647 CL_ServersResponsePacket
   1648 ===================
   1649 */
   1650 void CL_ServersResponsePacket( netadr_t from, msg_t *msg ) {
   1651 	int				i, count, max, total;
   1652 	serverAddress_t addresses[MAX_SERVERSPERPACKET];
   1653 	int				numservers;
   1654 	byte*			buffptr;
   1655 	byte*			buffend;
   1656 	
   1657 	Com_Printf("CL_ServersResponsePacket\n");
   1658 
   1659 	if (cls.numglobalservers == -1) {
   1660 		// state to detect lack of servers or lack of response
   1661 		cls.numglobalservers = 0;
   1662 		cls.numGlobalServerAddresses = 0;
   1663 	}
   1664 
   1665 	if (cls.nummplayerservers == -1) {
   1666 		cls.nummplayerservers = 0;
   1667 	}
   1668 
   1669 	// parse through server response string
   1670 	numservers = 0;
   1671 	buffptr    = msg->data;
   1672 	buffend    = buffptr + msg->cursize;
   1673 	while (buffptr+1 < buffend) {
   1674 		// advance to initial token
   1675 		do {
   1676 			if (*buffptr++ == '\\')
   1677 				break;		
   1678 		}
   1679 		while (buffptr < buffend);
   1680 
   1681 		if ( buffptr >= buffend - 6 ) {
   1682 			break;
   1683 		}
   1684 
   1685 		// parse out ip
   1686 		addresses[numservers].ip[0] = *buffptr++;
   1687 		addresses[numservers].ip[1] = *buffptr++;
   1688 		addresses[numservers].ip[2] = *buffptr++;
   1689 		addresses[numservers].ip[3] = *buffptr++;
   1690 
   1691 		// parse out port
   1692 		addresses[numservers].port = (*buffptr++)<<8;
   1693 		addresses[numservers].port += *buffptr++;
   1694 		addresses[numservers].port = BigShort( addresses[numservers].port );
   1695 
   1696 		// syntax check
   1697 		if (*buffptr != '\\') {
   1698 			break;
   1699 		}
   1700 
   1701 		Com_DPrintf( "server: %d ip: %d.%d.%d.%d:%d\n",numservers,
   1702 				addresses[numservers].ip[0],
   1703 				addresses[numservers].ip[1],
   1704 				addresses[numservers].ip[2],
   1705 				addresses[numservers].ip[3],
   1706 				addresses[numservers].port );
   1707 
   1708 		numservers++;
   1709 		if (numservers >= MAX_SERVERSPERPACKET) {
   1710 			break;
   1711 		}
   1712 
   1713 		// parse out EOT
   1714 		if (buffptr[1] == 'E' && buffptr[2] == 'O' && buffptr[3] == 'T') {
   1715 			break;
   1716 		}
   1717 	}
   1718 
   1719 	if (cls.masterNum == 0) {
   1720 		count = cls.numglobalservers;
   1721 		max = MAX_GLOBAL_SERVERS;
   1722 	} else {
   1723 		count = cls.nummplayerservers;
   1724 		max = MAX_OTHER_SERVERS;
   1725 	}
   1726 
   1727 	for (i = 0; i < numservers && count < max; i++) {
   1728 		// build net address
   1729 		serverInfo_t *server = (cls.masterNum == 0) ? &cls.globalServers[count] : &cls.mplayerServers[count];
   1730 
   1731 		CL_InitServerInfo( server, &addresses[i] );
   1732 		// advance to next slot
   1733 		count++;
   1734 	}
   1735 
   1736 	// if getting the global list
   1737 	if (cls.masterNum == 0) {
   1738 		if ( cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS ) {
   1739 			// if we couldn't store the servers in the main list anymore
   1740 			for (; i < numservers && count >= max; i++) {
   1741 				serverAddress_t *addr;
   1742 				// just store the addresses in an additional list
   1743 				addr = &cls.globalServerAddresses[cls.numGlobalServerAddresses++];
   1744 				addr->ip[0] = addresses[i].ip[0];
   1745 				addr->ip[1] = addresses[i].ip[1];
   1746 				addr->ip[2] = addresses[i].ip[2];
   1747 				addr->ip[3] = addresses[i].ip[3];
   1748 				addr->port  = addresses[i].port;
   1749 			}
   1750 		}
   1751 	}
   1752 
   1753 	if (cls.masterNum == 0) {
   1754 		cls.numglobalservers = count;
   1755 		total = count + cls.numGlobalServerAddresses;
   1756 	} else {
   1757 		cls.nummplayerservers = count;
   1758 		total = count;
   1759 	}
   1760 
   1761 	Com_Printf("%d servers parsed (total %d)\n", numservers, total);
   1762 }
   1763 
   1764 /*
   1765 =================
   1766 CL_ConnectionlessPacket
   1767 
   1768 Responses to broadcasts, etc
   1769 =================
   1770 */
   1771 void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
   1772 	char	*s;
   1773 	char	*c;
   1774 
   1775 	MSG_BeginReadingOOB( msg );
   1776 	MSG_ReadLong( msg );	// skip the -1
   1777 
   1778 	s = MSG_ReadStringLine( msg );
   1779 
   1780 	Cmd_TokenizeString( s );
   1781 
   1782 	c = Cmd_Argv(0);
   1783 
   1784 	Com_DPrintf ("CL packet %s: %s\n", NET_AdrToString(from), c);
   1785 
   1786 	// challenge from the server we are connecting to
   1787 	if ( !Q_stricmp(c, "challengeResponse") ) {
   1788 		if ( cls.state != CA_CONNECTING ) {
   1789 			Com_Printf( "Unwanted challenge response received.  Ignored.\n" );
   1790 		} else {
   1791 			// start sending challenge repsonse instead of challenge request packets
   1792 			clc.challenge = atoi(Cmd_Argv(1));
   1793 			cls.state = CA_CHALLENGING;
   1794 			clc.connectPacketCount = 0;
   1795 			clc.connectTime = -99999;
   1796 
   1797 			// take this address as the new server address.  This allows
   1798 			// a server proxy to hand off connections to multiple servers
   1799 			clc.serverAddress = from;
   1800 			Com_DPrintf ("challengeResponse: %d\n", clc.challenge);
   1801 		}
   1802 		return;
   1803 	}
   1804 
   1805 	// server connection
   1806 	if ( !Q_stricmp(c, "connectResponse") ) {
   1807 		if ( cls.state >= CA_CONNECTED ) {
   1808 			Com_Printf ("Dup connect received.  Ignored.\n");
   1809 			return;
   1810 		}
   1811 		if ( cls.state != CA_CHALLENGING ) {
   1812 			Com_Printf ("connectResponse packet while not connecting.  Ignored.\n");
   1813 			return;
   1814 		}
   1815 		if ( !NET_CompareBaseAdr( from, clc.serverAddress ) ) {
   1816 			Com_Printf( "connectResponse from a different address.  Ignored.\n" );
   1817 			Com_Printf( "%s should have been %s\n", NET_AdrToString( from ), 
   1818 				NET_AdrToString( clc.serverAddress ) );
   1819 			return;
   1820 		}
   1821 		Netchan_Setup (NS_CLIENT, &clc.netchan, from, Cvar_VariableValue( "net_qport" ) );
   1822 		cls.state = CA_CONNECTED;
   1823 		clc.lastPacketSentTime = -9999;		// send first packet immediately
   1824 		return;
   1825 	}
   1826 
   1827 	// server responding to an info broadcast
   1828 	if ( !Q_stricmp(c, "infoResponse") ) {
   1829 		CL_ServerInfoPacket( from, msg );
   1830 		return;
   1831 	}
   1832 
   1833 	// server responding to a get playerlist
   1834 	if ( !Q_stricmp(c, "statusResponse") ) {
   1835 		CL_ServerStatusResponse( from, msg );
   1836 		return;
   1837 	}
   1838 
   1839 	// a disconnect message from the server, which will happen if the server
   1840 	// dropped the connection but it is still getting packets from us
   1841 	if (!Q_stricmp(c, "disconnect")) {
   1842 		CL_DisconnectPacket( from );
   1843 		return;
   1844 	}
   1845 
   1846 	// echo request from server
   1847 	if ( !Q_stricmp(c, "echo") ) {
   1848 		NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) );
   1849 		return;
   1850 	}
   1851 
   1852 	// cd check
   1853 	if ( !Q_stricmp(c, "keyAuthorize") ) {
   1854 		// we don't use these now, so dump them on the floor
   1855 		return;
   1856 	}
   1857 
   1858 	// global MOTD from id
   1859 	if ( !Q_stricmp(c, "motd") ) {
   1860 		CL_MotdPacket( from );
   1861 		return;
   1862 	}
   1863 
   1864 	// echo request from server
   1865 	if ( !Q_stricmp(c, "print") ) {
   1866 		s = MSG_ReadString( msg );
   1867 		Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) );
   1868 		Com_Printf( "%s", s );
   1869 		return;
   1870 	}
   1871 
   1872 	// echo request from server
   1873 	if ( !Q_strncmp(c, "getserversResponse", 18) ) {
   1874 		CL_ServersResponsePacket( from, msg );
   1875 		return;
   1876 	}
   1877 
   1878 	Com_DPrintf ("Unknown connectionless packet command.\n");
   1879 }
   1880 
   1881 
   1882 /*
   1883 =================
   1884 CL_PacketEvent
   1885 
   1886 A packet has arrived from the main event loop
   1887 =================
   1888 */
   1889 void CL_PacketEvent( netadr_t from, msg_t *msg ) {
   1890 	int		headerBytes;
   1891 
   1892 	clc.lastPacketTime = cls.realtime;
   1893 
   1894 	if ( msg->cursize >= 4 && *(int *)msg->data == -1 ) {
   1895 		CL_ConnectionlessPacket( from, msg );
   1896 		return;
   1897 	}
   1898 
   1899 	if ( cls.state < CA_CONNECTED ) {
   1900 		return;		// can't be a valid sequenced packet
   1901 	}
   1902 
   1903 	if ( msg->cursize < 4 ) {
   1904 		Com_Printf ("%s: Runt packet\n",NET_AdrToString( from ));
   1905 		return;
   1906 	}
   1907 
   1908 	//
   1909 	// packet from server
   1910 	//
   1911 	if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) {
   1912 		Com_DPrintf ("%s:sequenced packet without connection\n"
   1913 			,NET_AdrToString( from ) );
   1914 		// FIXME: send a client disconnect?
   1915 		return;
   1916 	}
   1917 
   1918 	if (!CL_Netchan_Process( &clc.netchan, msg) ) {
   1919 		return;		// out of order, duplicated, etc
   1920 	}
   1921 
   1922 	// the header is different lengths for reliable and unreliable messages
   1923 	headerBytes = msg->readcount;
   1924 
   1925 	// track the last message received so it can be returned in 
   1926 	// client messages, allowing the server to detect a dropped
   1927 	// gamestate
   1928 	clc.serverMessageSequence = LittleLong( *(int *)msg->data );
   1929 
   1930 	clc.lastPacketTime = cls.realtime;
   1931 	CL_ParseServerMessage( msg );
   1932 
   1933 	//
   1934 	// we don't know if it is ok to save a demo message until
   1935 	// after we have parsed the frame
   1936 	//
   1937 	if ( clc.demorecording && !clc.demowaiting ) {
   1938 		CL_WriteDemoMessage( msg, headerBytes );
   1939 	}
   1940 }
   1941 
   1942 /*
   1943 ==================
   1944 CL_CheckTimeout
   1945 
   1946 ==================
   1947 */
   1948 void CL_CheckTimeout( void ) {
   1949 	//
   1950 	// check timeout
   1951 	//
   1952 	if ( ( !cl_paused->integer || !sv_paused->integer ) 
   1953 		&& cls.state >= CA_CONNECTED && cls.state != CA_CINEMATIC
   1954 	    && cls.realtime - clc.lastPacketTime > cl_timeout->value*1000) {
   1955 		if (++cl.timeoutcount > 5) {	// timeoutcount saves debugger
   1956 			Com_Printf ("\nServer connection timed out.\n");
   1957 			CL_Disconnect( qtrue );
   1958 			return;
   1959 		}
   1960 	} else {
   1961 		cl.timeoutcount = 0;
   1962 	}
   1963 }
   1964 
   1965 
   1966 //============================================================================
   1967 
   1968 /*
   1969 ==================
   1970 CL_CheckUserinfo
   1971 
   1972 ==================
   1973 */
   1974 void CL_CheckUserinfo( void ) {
   1975 	// don't add reliable commands when not yet connected
   1976 	if ( cls.state < CA_CHALLENGING ) {
   1977 		return;
   1978 	}
   1979 	// don't overflow the reliable command buffer when paused
   1980 	if ( cl_paused->integer ) {
   1981 		return;
   1982 	}
   1983 	// send a reliable userinfo update if needed
   1984 	if ( cvar_modifiedFlags & CVAR_USERINFO ) {
   1985 		cvar_modifiedFlags &= ~CVAR_USERINFO;
   1986 		CL_AddReliableCommand( va("userinfo \"%s\"", Cvar_InfoString( CVAR_USERINFO ) ) );
   1987 	}
   1988 
   1989 }
   1990 
   1991 /*
   1992 ==================
   1993 CL_Frame
   1994 
   1995 ==================
   1996 */
   1997 void CL_Frame ( int msec ) {
   1998 
   1999 	if ( !com_cl_running->integer ) {
   2000 		return;
   2001 	}
   2002 
   2003 	if ( cls.cddialog ) {
   2004 		// bring up the cd error dialog if needed
   2005 		cls.cddialog = qfalse;
   2006 		VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NEED_CD );
   2007 	} else	if ( cls.state == CA_DISCONNECTED && !( cls.keyCatchers & KEYCATCH_UI )
   2008 		&& !com_sv_running->integer ) {
   2009 		// if disconnected, bring up the menu
   2010 		S_StopAllSounds();
   2011 		VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN );
   2012 	}
   2013 
   2014 	// if recording an avi, lock to a fixed fps
   2015 	if ( cl_avidemo->integer && msec) {
   2016 		// save the current screen
   2017 		if ( cls.state == CA_ACTIVE || cl_forceavidemo->integer) {
   2018 			Cbuf_ExecuteText( EXEC_NOW, "screenshot silent\n" );
   2019 		}
   2020 		// fixed time for next frame'
   2021 		msec = (1000 / cl_avidemo->integer) * com_timescale->value;
   2022 		if (msec == 0) {
   2023 			msec = 1;
   2024 		}
   2025 	}
   2026 	
   2027 	// save the msec before checking pause
   2028 	cls.realFrametime = msec;
   2029 
   2030 	// decide the simulation time
   2031 	cls.frametime = msec;
   2032 
   2033 	cls.realtime += cls.frametime;
   2034 
   2035 	if ( cl_timegraph->integer ) {
   2036 		SCR_DebugGraph ( cls.realFrametime * 0.25, 0 );
   2037 	}
   2038 
   2039 	// see if we need to update any userinfo
   2040 	CL_CheckUserinfo();
   2041 
   2042 	// if we haven't gotten a packet in a long time,
   2043 	// drop the connection
   2044 	CL_CheckTimeout();
   2045 
   2046 	// send intentions now
   2047 	CL_SendCmd();
   2048 
   2049 	// resend a connection request if necessary
   2050 	CL_CheckForResend();
   2051 
   2052 	// decide on the serverTime to render
   2053 	CL_SetCGameTime();
   2054 
   2055 	// update the screen
   2056 	SCR_UpdateScreen();
   2057 
   2058 	// update audio
   2059 	S_Update();
   2060 
   2061 	// advance local effects for next frame
   2062 	SCR_RunCinematic();
   2063 
   2064 	Con_RunConsole();
   2065 
   2066 	cls.framecount++;
   2067 }
   2068 
   2069 
   2070 //============================================================================
   2071 
   2072 /*
   2073 ================
   2074 CL_RefPrintf
   2075 
   2076 DLL glue
   2077 ================
   2078 */
   2079 void QDECL CL_RefPrintf( int print_level, const char *fmt, ...) {
   2080 	va_list		argptr;
   2081 	char		msg[MAXPRINTMSG];
   2082 	
   2083 	va_start (argptr,fmt);
   2084 	Q_vsnprintf (msg, sizeof(msg), fmt, argptr);
   2085 	va_end (argptr);
   2086 
   2087 	if ( print_level == PRINT_ALL ) {
   2088 		Com_Printf ("%s", msg);
   2089 	} else if ( print_level == PRINT_WARNING ) {
   2090 		Com_Printf (S_COLOR_YELLOW "%s", msg);		// yellow
   2091 	} else if ( print_level == PRINT_DEVELOPER ) {
   2092 		Com_DPrintf (S_COLOR_RED "%s", msg);		// red
   2093 	}
   2094 }
   2095 
   2096 
   2097 
   2098 /*
   2099 ============
   2100 CL_ShutdownRef
   2101 ============
   2102 */
   2103 void CL_ShutdownRef( void ) {
   2104 	if ( !re.Shutdown ) {
   2105 		return;
   2106 	}
   2107 	re.Shutdown( qtrue );
   2108 	Com_Memset( &re, 0, sizeof( re ) );
   2109 }
   2110 
   2111 /*
   2112 ============
   2113 CL_InitRenderer
   2114 ============
   2115 */
   2116 void CL_InitRenderer( void ) {
   2117 	// this sets up the renderer and calls R_Init
   2118 	re.BeginRegistration( &cls.glconfig );
   2119 
   2120 	// load character sets
   2121 	cls.charSetShader = re.RegisterShader( "gfx/2d/bigchars" );
   2122 	cls.whiteShader = re.RegisterShader( "white" );
   2123 	cls.consoleShader = re.RegisterShader( "console" );
   2124 	g_console_field_width = cls.glconfig.vidWidth / SMALLCHAR_WIDTH - 2;
   2125 	g_consoleField.widthInChars = g_console_field_width;
   2126 }
   2127 
   2128 /*
   2129 ============================
   2130 CL_StartHunkUsers
   2131 
   2132 After the server has cleared the hunk, these will need to be restarted
   2133 This is the only place that any of these functions are called from
   2134 ============================
   2135 */
   2136 void CL_StartHunkUsers( void ) {
   2137 	if (!com_cl_running) {
   2138 		return;
   2139 	}
   2140 
   2141 	if ( !com_cl_running->integer ) {
   2142 		return;
   2143 	}
   2144 
   2145 	if ( !cls.rendererStarted ) {
   2146 		cls.rendererStarted = qtrue;
   2147 		CL_InitRenderer();
   2148 	}
   2149 
   2150 	if ( !cls.soundStarted ) {
   2151 		cls.soundStarted = qtrue;
   2152 		S_Init();
   2153 	}
   2154 
   2155 	if ( !cls.soundRegistered ) {
   2156 		cls.soundRegistered = qtrue;
   2157 		S_BeginRegistration();
   2158 	}
   2159 
   2160 	if ( !cls.uiStarted ) {
   2161 		cls.uiStarted = qtrue;
   2162 		CL_InitUI();
   2163 	}
   2164 }
   2165 
   2166 /*
   2167 ============
   2168 CL_RefMalloc
   2169 ============
   2170 */
   2171 void *CL_RefMalloc( int size ) {
   2172 	return Z_TagMalloc( size, TAG_RENDERER );
   2173 }
   2174 
   2175 int CL_ScaledMilliseconds(void) {
   2176 	return Sys_Milliseconds()*com_timescale->value;
   2177 }
   2178 
   2179 /*
   2180 ============
   2181 CL_InitRef
   2182 ============
   2183 */
   2184 void CL_InitRef( void ) {
   2185 	refimport_t	ri;
   2186 	refexport_t	*ret;
   2187 
   2188 	Com_Printf( "----- Initializing Renderer ----\n" );
   2189 
   2190 	ri.Cmd_AddCommand = Cmd_AddCommand;
   2191 	ri.Cmd_RemoveCommand = Cmd_RemoveCommand;
   2192 	ri.Cmd_Argc = Cmd_Argc;
   2193 	ri.Cmd_Argv = Cmd_Argv;
   2194 	ri.Cmd_ExecuteText = Cbuf_ExecuteText;
   2195 	ri.Printf = CL_RefPrintf;
   2196 	ri.Error = Com_Error;
   2197 	ri.Milliseconds = CL_ScaledMilliseconds;
   2198 	ri.Malloc = CL_RefMalloc;
   2199 	ri.Free = Z_Free;
   2200 #ifdef HUNK_DEBUG
   2201 	ri.Hunk_AllocDebug = Hunk_AllocDebug;
   2202 #else
   2203 	ri.Hunk_Alloc = Hunk_Alloc;
   2204 #endif
   2205 	ri.Hunk_AllocateTempMemory = Hunk_AllocateTempMemory;
   2206 	ri.Hunk_FreeTempMemory = Hunk_FreeTempMemory;
   2207 	ri.CM_DrawDebugSurface = CM_DrawDebugSurface;
   2208 	ri.FS_ReadFile = FS_ReadFile;
   2209 	ri.FS_FreeFile = FS_FreeFile;
   2210 	ri.FS_WriteFile = FS_WriteFile;
   2211 	ri.FS_FreeFileList = FS_FreeFileList;
   2212 	ri.FS_ListFiles = FS_ListFiles;
   2213 	ri.FS_FileIsInPAK = FS_FileIsInPAK;
   2214 	ri.FS_FileExists = FS_FileExists;
   2215 	ri.Cvar_Get = Cvar_Get;
   2216 	ri.Cvar_Set = Cvar_Set;
   2217 
   2218 	// cinematic stuff
   2219 
   2220 	ri.CIN_UploadCinematic = CIN_UploadCinematic;
   2221 	ri.CIN_PlayCinematic = CIN_PlayCinematic;
   2222 	ri.CIN_RunCinematic = CIN_RunCinematic;
   2223 
   2224 	ret = GetRefAPI( REF_API_VERSION, &ri );
   2225 
   2226 #if defined __USEA3D && defined __A3D_GEOM
   2227 	hA3Dg_ExportRenderGeom (ret);
   2228 #endif
   2229 
   2230 	Com_Printf( "-------------------------------\n");
   2231 
   2232 	if ( !ret ) {
   2233 		Com_Error (ERR_FATAL, "Couldn't initialize refresh" );
   2234 	}
   2235 
   2236 	re = *ret;
   2237 
   2238 	// unpause so the cgame definately gets a snapshot and renders a frame
   2239 	Cvar_Set( "cl_paused", "0" );
   2240 }
   2241 
   2242 
   2243 //===========================================================================================
   2244 
   2245 
   2246 void CL_SetModel_f( void ) {
   2247 	char	*arg;
   2248 	char	name[256];
   2249 
   2250 	arg = Cmd_Argv( 1 );
   2251 	if (arg[0]) {
   2252 		Cvar_Set( "model", arg );
   2253 		Cvar_Set( "headmodel", arg );
   2254 	} else {
   2255 		Cvar_VariableStringBuffer( "model", name, sizeof(name) );
   2256 		Com_Printf("model is set to %s\n", name);
   2257 	}
   2258 }
   2259 
   2260 /*
   2261 ====================
   2262 CL_Init
   2263 ====================
   2264 */
   2265 void CL_Init( void ) {
   2266 	Com_Printf( "----- Client Initialization -----\n" );
   2267 
   2268 	Con_Init ();	
   2269 
   2270 	CL_ClearState ();
   2271 
   2272 	cls.state = CA_DISCONNECTED;	// no longer CA_UNINITIALIZED
   2273 
   2274 	cls.realtime = 0;
   2275 
   2276 	CL_InitInput ();
   2277 
   2278 	//
   2279 	// register our variables
   2280 	//
   2281 	cl_noprint = Cvar_Get( "cl_noprint", "0", 0 );
   2282 	cl_motd = Cvar_Get ("cl_motd", "1", 0);
   2283 
   2284 	cl_timeout = Cvar_Get ("cl_timeout", "200", 0);
   2285 
   2286 	cl_timeNudge = Cvar_Get ("cl_timeNudge", "0", CVAR_TEMP );
   2287 	cl_shownet = Cvar_Get ("cl_shownet", "0", CVAR_TEMP );
   2288 	cl_showSend = Cvar_Get ("cl_showSend", "0", CVAR_TEMP );
   2289 	cl_showTimeDelta = Cvar_Get ("cl_showTimeDelta", "0", CVAR_TEMP );
   2290 	cl_freezeDemo = Cvar_Get ("cl_freezeDemo", "0", CVAR_TEMP );
   2291 	rcon_client_password = Cvar_Get ("rconPassword", "", CVAR_TEMP );
   2292 	cl_activeAction = Cvar_Get( "activeAction", "", CVAR_TEMP );
   2293 
   2294 	cl_timedemo = Cvar_Get ("timedemo", "0", 0);
   2295 	cl_avidemo = Cvar_Get ("cl_avidemo", "0", 0);
   2296 	cl_forceavidemo = Cvar_Get ("cl_forceavidemo", "0", 0);
   2297 
   2298 	rconAddress = Cvar_Get ("rconAddress", "", 0);
   2299 
   2300 	cl_yawspeed = Cvar_Get ("cl_yawspeed", "140", CVAR_ARCHIVE);
   2301 	cl_pitchspeed = Cvar_Get ("cl_pitchspeed", "140", CVAR_ARCHIVE);
   2302 	cl_anglespeedkey = Cvar_Get ("cl_anglespeedkey", "1.5", 0);
   2303 
   2304 	cl_maxpackets = Cvar_Get ("cl_maxpackets", "30", CVAR_ARCHIVE );
   2305 	cl_packetdup = Cvar_Get ("cl_packetdup", "1", CVAR_ARCHIVE );
   2306 
   2307 	cl_run = Cvar_Get ("cl_run", "1", CVAR_ARCHIVE);
   2308 	cl_sensitivity = Cvar_Get ("sensitivity", "5", CVAR_ARCHIVE);
   2309 	cl_mouseAccel = Cvar_Get ("cl_mouseAccel", "0", CVAR_ARCHIVE);
   2310 	cl_freelook = Cvar_Get( "cl_freelook", "1", CVAR_ARCHIVE );
   2311 
   2312 	cl_showMouseRate = Cvar_Get ("cl_showmouserate", "0", 0);
   2313 
   2314 	cl_allowDownload = Cvar_Get ("cl_allowDownload", "0", CVAR_ARCHIVE);
   2315 
   2316 	cl_conXOffset = Cvar_Get ("cl_conXOffset", "0", 0);
   2317 #ifdef MACOS_X
   2318         // In game video is REALLY slow in Mac OS X right now due to driver slowness
   2319 	cl_inGameVideo = Cvar_Get ("r_inGameVideo", "0", CVAR_ARCHIVE);
   2320 #else
   2321 	cl_inGameVideo = Cvar_Get ("r_inGameVideo", "1", CVAR_ARCHIVE);
   2322 #endif
   2323 
   2324 	cl_serverStatusResendTime = Cvar_Get ("cl_serverStatusResendTime", "750", 0);
   2325 
   2326 	// init autoswitch so the ui will have it correctly even
   2327 	// if the cgame hasn't been started
   2328 	Cvar_Get ("cg_autoswitch", "1", CVAR_ARCHIVE);
   2329 
   2330 	m_pitch = Cvar_Get ("m_pitch", "0.022", CVAR_ARCHIVE);
   2331 	m_yaw = Cvar_Get ("m_yaw", "0.022", CVAR_ARCHIVE);
   2332 	m_forward = Cvar_Get ("m_forward", "0.25", CVAR_ARCHIVE);
   2333 	m_side = Cvar_Get ("m_side", "0.25", CVAR_ARCHIVE);
   2334 #ifdef MACOS_X
   2335         // Input is jittery on OS X w/o this
   2336 	m_filter = Cvar_Get ("m_filter", "1", CVAR_ARCHIVE);
   2337 #else
   2338 	m_filter = Cvar_Get ("m_filter", "0", CVAR_ARCHIVE);
   2339 #endif
   2340 
   2341 	cl_motdString = Cvar_Get( "cl_motdString", "", CVAR_ROM );
   2342 
   2343 	Cvar_Get( "cl_maxPing", "800", CVAR_ARCHIVE );
   2344 
   2345 
   2346 	// userinfo
   2347 	Cvar_Get ("name", "UnnamedPlayer", CVAR_USERINFO | CVAR_ARCHIVE );
   2348 	Cvar_Get ("rate", "3000", CVAR_USERINFO | CVAR_ARCHIVE );
   2349 	Cvar_Get ("snaps", "20", CVAR_USERINFO | CVAR_ARCHIVE );
   2350 	Cvar_Get ("model", "sarge", CVAR_USERINFO | CVAR_ARCHIVE );
   2351 	Cvar_Get ("headmodel", "sarge", CVAR_USERINFO | CVAR_ARCHIVE );
   2352 	Cvar_Get ("team_model", "james", CVAR_USERINFO | CVAR_ARCHIVE );
   2353 	Cvar_Get ("team_headmodel", "*james", CVAR_USERINFO | CVAR_ARCHIVE );
   2354 	Cvar_Get ("g_redTeam", "Stroggs", CVAR_SERVERINFO | CVAR_ARCHIVE);
   2355 	Cvar_Get ("g_blueTeam", "Pagans", CVAR_SERVERINFO | CVAR_ARCHIVE);
   2356 	Cvar_Get ("color1",  "4", CVAR_USERINFO | CVAR_ARCHIVE );
   2357 	Cvar_Get ("color2", "5", CVAR_USERINFO | CVAR_ARCHIVE );
   2358 	Cvar_Get ("handicap", "100", CVAR_USERINFO | CVAR_ARCHIVE );
   2359 	Cvar_Get ("teamtask", "0", CVAR_USERINFO );
   2360 	Cvar_Get ("sex", "male", CVAR_USERINFO | CVAR_ARCHIVE );
   2361 	Cvar_Get ("cl_anonymous", "0", CVAR_USERINFO | CVAR_ARCHIVE );
   2362 
   2363 	Cvar_Get ("password", "", CVAR_USERINFO);
   2364 	Cvar_Get ("cg_predictItems", "1", CVAR_USERINFO | CVAR_ARCHIVE );
   2365 
   2366 
   2367 	// cgame might not be initialized before menu is used
   2368 	Cvar_Get ("cg_viewsize", "100", CVAR_ARCHIVE );
   2369 
   2370 	//
   2371 	// register our commands
   2372 	//
   2373 	Cmd_AddCommand ("cmd", CL_ForwardToServer_f);
   2374 	Cmd_AddCommand ("configstrings", CL_Configstrings_f);
   2375 	Cmd_AddCommand ("clientinfo", CL_Clientinfo_f);
   2376 	Cmd_AddCommand ("snd_restart", CL_Snd_Restart_f);
   2377 	Cmd_AddCommand ("vid_restart", CL_Vid_Restart_f);
   2378 	Cmd_AddCommand ("disconnect", CL_Disconnect_f);
   2379 	Cmd_AddCommand ("record", CL_Record_f);
   2380 	Cmd_AddCommand ("demo", CL_PlayDemo_f);
   2381 	Cmd_AddCommand ("cinematic", CL_PlayCinematic_f);
   2382 	Cmd_AddCommand ("stoprecord", CL_StopRecord_f);
   2383 	Cmd_AddCommand ("connect", CL_Connect_f);
   2384 	Cmd_AddCommand ("reconnect", CL_Reconnect_f);
   2385 	Cmd_AddCommand ("localservers", CL_LocalServers_f);
   2386 	Cmd_AddCommand ("globalservers", CL_GlobalServers_f);
   2387 	Cmd_AddCommand ("rcon", CL_Rcon_f);
   2388 	Cmd_AddCommand ("setenv", CL_Setenv_f );
   2389 	Cmd_AddCommand ("ping", CL_Ping_f );
   2390 	Cmd_AddCommand ("serverstatus", CL_ServerStatus_f );
   2391 	Cmd_AddCommand ("showip", CL_ShowIP_f );
   2392 	Cmd_AddCommand ("fs_openedList", CL_OpenedPK3List_f );
   2393 	Cmd_AddCommand ("fs_referencedList", CL_ReferencedPK3List_f );
   2394 	Cmd_AddCommand ("model", CL_SetModel_f );
   2395 	CL_InitRef();
   2396 
   2397 	SCR_Init ();
   2398 
   2399 	Cbuf_Execute ();
   2400 
   2401 	Cvar_Set( "cl_running", "1" );
   2402 
   2403 	Com_Printf( "----- Client Initialization Complete -----\n" );
   2404 }
   2405 
   2406 
   2407 /*
   2408 ===============
   2409 CL_Shutdown
   2410 
   2411 ===============
   2412 */
   2413 void CL_Shutdown( void ) {
   2414 	static qboolean recursive = qfalse;
   2415 	
   2416 	Com_Printf( "----- CL_Shutdown -----\n" );
   2417 
   2418 	if ( recursive ) {
   2419 		printf ("recursive shutdown\n");
   2420 		return;
   2421 	}
   2422 	recursive = qtrue;
   2423 
   2424 	CL_Disconnect( qtrue );
   2425 
   2426 	S_Shutdown();
   2427 	CL_ShutdownRef();
   2428 	
   2429 	CL_ShutdownUI();
   2430 
   2431 	Cmd_RemoveCommand ("cmd");
   2432 	Cmd_RemoveCommand ("configstrings");
   2433 	Cmd_RemoveCommand ("userinfo");
   2434 	Cmd_RemoveCommand ("snd_restart");
   2435 	Cmd_RemoveCommand ("vid_restart");
   2436 	Cmd_RemoveCommand ("disconnect");
   2437 	Cmd_RemoveCommand ("record");
   2438 	Cmd_RemoveCommand ("demo");
   2439 	Cmd_RemoveCommand ("cinematic");
   2440 	Cmd_RemoveCommand ("stoprecord");
   2441 	Cmd_RemoveCommand ("connect");
   2442 	Cmd_RemoveCommand ("localservers");
   2443 	Cmd_RemoveCommand ("globalservers");
   2444 	Cmd_RemoveCommand ("rcon");
   2445 	Cmd_RemoveCommand ("setenv");
   2446 	Cmd_RemoveCommand ("ping");
   2447 	Cmd_RemoveCommand ("serverstatus");
   2448 	Cmd_RemoveCommand ("showip");
   2449 	Cmd_RemoveCommand ("model");
   2450 
   2451 	Cvar_Set( "cl_running", "0" );
   2452 
   2453 	recursive = qfalse;
   2454 
   2455 	Com_Memset( &cls, 0, sizeof( cls ) );
   2456 
   2457 	Com_Printf( "-----------------------\n" );
   2458 
   2459 }
   2460 
   2461 static void CL_SetServerInfo(serverInfo_t *server, const char *info, int ping) {
   2462 	if (server) {
   2463 		if (info) {
   2464 			server->clients = atoi(Info_ValueForKey(info, "clients"));
   2465 			Q_strncpyz(server->hostName,Info_ValueForKey(info, "hostname"), MAX_NAME_LENGTH);
   2466 			Q_strncpyz(server->mapName, Info_ValueForKey(info, "mapname"), MAX_NAME_LENGTH);
   2467 			server->maxClients = atoi(Info_ValueForKey(info, "sv_maxclients"));
   2468 			Q_strncpyz(server->game,Info_ValueForKey(info, "game"), MAX_NAME_LENGTH);
   2469 			server->gameType = atoi(Info_ValueForKey(info, "gametype"));
   2470 			server->netType = atoi(Info_ValueForKey(info, "nettype"));
   2471 			server->minPing = atoi(Info_ValueForKey(info, "minping"));
   2472 			server->maxPing = atoi(Info_ValueForKey(info, "maxping"));
   2473 			server->punkbuster = atoi(Info_ValueForKey(info, "punkbuster"));
   2474 		}
   2475 		server->ping = ping;
   2476 	}
   2477 }
   2478 
   2479 static void CL_SetServerInfoByAddress(netadr_t from, const char *info, int ping) {
   2480 	int i;
   2481 
   2482 	for (i = 0; i < MAX_OTHER_SERVERS; i++) {
   2483 		if (NET_CompareAdr(from, cls.localServers[i].adr)) {
   2484 			CL_SetServerInfo(&cls.localServers[i], info, ping);
   2485 		}
   2486 	}
   2487 
   2488 	for (i = 0; i < MAX_OTHER_SERVERS; i++) {
   2489 		if (NET_CompareAdr(from, cls.mplayerServers[i].adr)) {
   2490 			CL_SetServerInfo(&cls.mplayerServers[i], info, ping);
   2491 		}
   2492 	}
   2493 
   2494 	for (i = 0; i < MAX_GLOBAL_SERVERS; i++) {
   2495 		if (NET_CompareAdr(from, cls.globalServers[i].adr)) {
   2496 			CL_SetServerInfo(&cls.globalServers[i], info, ping);
   2497 		}
   2498 	}
   2499 
   2500 	for (i = 0; i < MAX_OTHER_SERVERS; i++) {
   2501 		if (NET_CompareAdr(from, cls.favoriteServers[i].adr)) {
   2502 			CL_SetServerInfo(&cls.favoriteServers[i], info, ping);
   2503 		}
   2504 	}
   2505 
   2506 }
   2507 
   2508 /*
   2509 ===================
   2510 CL_ServerInfoPacket
   2511 ===================
   2512 */
   2513 void CL_ServerInfoPacket( netadr_t from, msg_t *msg ) {
   2514 	int		i, type;
   2515 	char	info[MAX_INFO_STRING];
   2516 	char*	str;
   2517 	char	*infoString;
   2518 	int		prot;
   2519 
   2520 	infoString = MSG_ReadString( msg );
   2521 
   2522 	// if this isn't the correct protocol version, ignore it
   2523 	prot = atoi( Info_ValueForKey( infoString, "protocol" ) );
   2524 	if ( prot != PROTOCOL_VERSION ) {
   2525 		Com_DPrintf( "Different protocol info packet: %s\n", infoString );
   2526 		return;
   2527 	}
   2528 
   2529 	// iterate servers waiting for ping response
   2530 	for (i=0; i<MAX_PINGREQUESTS; i++)
   2531 	{
   2532 		if ( cl_pinglist[i].adr.port && !cl_pinglist[i].time && NET_CompareAdr( from, cl_pinglist[i].adr ) )
   2533 		{
   2534 			// calc ping time
   2535 			cl_pinglist[i].time = cls.realtime - cl_pinglist[i].start + 1;
   2536 			Com_DPrintf( "ping time %dms from %s\n", cl_pinglist[i].time, NET_AdrToString( from ) );
   2537 
   2538 			// save of info
   2539 			Q_strncpyz( cl_pinglist[i].info, infoString, sizeof( cl_pinglist[i].info ) );
   2540 
   2541 			// tack on the net type
   2542 			// NOTE: make sure these types are in sync with the netnames strings in the UI
   2543 			switch (from.type)
   2544 			{
   2545 				case NA_BROADCAST:
   2546 				case NA_IP:
   2547 					str = "udp";
   2548 					type = 1;
   2549 					break;
   2550 
   2551 				case NA_IPX:
   2552 				case NA_BROADCAST_IPX:
   2553 					str = "ipx";
   2554 					type = 2;
   2555 					break;
   2556 
   2557 				default:
   2558 					str = "???";
   2559 					type = 0;
   2560 					break;
   2561 			}
   2562 			Info_SetValueForKey( cl_pinglist[i].info, "nettype", va("%d", type) );
   2563 			CL_SetServerInfoByAddress(from, infoString, cl_pinglist[i].time);
   2564 
   2565 			return;
   2566 		}
   2567 	}
   2568 
   2569 	// if not just sent a local broadcast or pinging local servers
   2570 	if (cls.pingUpdateSource != AS_LOCAL) {
   2571 		return;
   2572 	}
   2573 
   2574 	for ( i = 0 ; i < MAX_OTHER_SERVERS ; i++ ) {
   2575 		// empty slot
   2576 		if ( cls.localServers[i].adr.port == 0 ) {
   2577 			break;
   2578 		}
   2579 
   2580 		// avoid duplicate
   2581 		if ( NET_CompareAdr( from, cls.localServers[i].adr ) ) {
   2582 			return;
   2583 		}
   2584 	}
   2585 
   2586 	if ( i == MAX_OTHER_SERVERS ) {
   2587 		Com_DPrintf( "MAX_OTHER_SERVERS hit, dropping infoResponse\n" );
   2588 		return;
   2589 	}
   2590 
   2591 	// add this to the list
   2592 	cls.numlocalservers = i+1;
   2593 	cls.localServers[i].adr = from;
   2594 	cls.localServers[i].clients = 0;
   2595 	cls.localServers[i].hostName[0] = '\0';
   2596 	cls.localServers[i].mapName[0] = '\0';
   2597 	cls.localServers[i].maxClients = 0;
   2598 	cls.localServers[i].maxPing = 0;
   2599 	cls.localServers[i].minPing = 0;
   2600 	cls.localServers[i].ping = -1;
   2601 	cls.localServers[i].game[0] = '\0';
   2602 	cls.localServers[i].gameType = 0;
   2603 	cls.localServers[i].netType = from.type;
   2604 	cls.localServers[i].punkbuster = 0;
   2605 									 
   2606 	Q_strncpyz( info, MSG_ReadString( msg ), MAX_INFO_STRING );
   2607 	if (strlen(info)) {
   2608 		if (info[strlen(info)-1] != '\n') {
   2609 			strncat(info, "\n", sizeof(info));
   2610 		}
   2611 		Com_Printf( "%s: %s", NET_AdrToString( from ), info );
   2612 	}
   2613 }
   2614 
   2615 /*
   2616 ===================
   2617 CL_GetServerStatus
   2618 ===================
   2619 */
   2620 serverStatus_t *CL_GetServerStatus( netadr_t from ) {
   2621 	serverStatus_t *serverStatus;
   2622 	int i, oldest, oldestTime;
   2623 
   2624 	serverStatus = NULL;
   2625 	for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
   2626 		if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) {
   2627 			return &cl_serverStatusList[i];
   2628 		}
   2629 	}
   2630 	for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
   2631 		if ( cl_serverStatusList[i].retrieved ) {
   2632 			return &cl_serverStatusList[i];
   2633 		}
   2634 	}
   2635 	oldest = -1;
   2636 	oldestTime = 0;
   2637 	for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
   2638 		if (oldest == -1 || cl_serverStatusList[i].startTime < oldestTime) {
   2639 			oldest = i;
   2640 			oldestTime = cl_serverStatusList[i].startTime;
   2641 		}
   2642 	}
   2643 	if (oldest != -1) {
   2644 		return &cl_serverStatusList[oldest];
   2645 	}
   2646 	serverStatusCount++;
   2647 	return &cl_serverStatusList[serverStatusCount & (MAX_SERVERSTATUSREQUESTS-1)];
   2648 }
   2649 
   2650 /*
   2651 ===================
   2652 CL_ServerStatus
   2653 ===================
   2654 */
   2655 int CL_ServerStatus( char *serverAddress, char *serverStatusString, int maxLen ) {
   2656 	int i;
   2657 	netadr_t	to;
   2658 	serverStatus_t *serverStatus;
   2659 
   2660 	// if no server address then reset all server status requests
   2661 	if ( !serverAddress ) {
   2662 		for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
   2663 			cl_serverStatusList[i].address.port = 0;
   2664 			cl_serverStatusList[i].retrieved = qtrue;
   2665 		}
   2666 		return qfalse;
   2667 	}
   2668 	// get the address
   2669 	if ( !NET_StringToAdr( serverAddress, &to ) ) {
   2670 		return qfalse;
   2671 	}
   2672 	serverStatus = CL_GetServerStatus( to );
   2673 	// if no server status string then reset the server status request for this address
   2674 	if ( !serverStatusString ) {
   2675 		serverStatus->retrieved = qtrue;
   2676 		return qfalse;
   2677 	}
   2678 
   2679 	// if this server status request has the same address
   2680 	if ( NET_CompareAdr( to, serverStatus->address) ) {
   2681 		// if we recieved an response for this server status request
   2682 		if (!serverStatus->pending) {
   2683 			Q_strncpyz(serverStatusString, serverStatus->string, maxLen);
   2684 			serverStatus->retrieved = qtrue;
   2685 			serverStatus->startTime = 0;
   2686 			return qtrue;
   2687 		}
   2688 		// resend the request regularly
   2689 		else if ( serverStatus->startTime < Com_Milliseconds() - cl_serverStatusResendTime->integer ) {
   2690 			serverStatus->print = qfalse;
   2691 			serverStatus->pending = qtrue;
   2692 			serverStatus->retrieved = qfalse;
   2693 			serverStatus->time = 0;
   2694 			serverStatus->startTime = Com_Milliseconds();
   2695 			NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" );
   2696 			return qfalse;
   2697 		}
   2698 	}
   2699 	// if retrieved
   2700 	else if ( serverStatus->retrieved ) {
   2701 		serverStatus->address = to;
   2702 		serverStatus->print = qfalse;
   2703 		serverStatus->pending = qtrue;
   2704 		serverStatus->retrieved = qfalse;
   2705 		serverStatus->startTime = Com_Milliseconds();
   2706 		serverStatus->time = 0;
   2707 		NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" );
   2708 		return qfalse;
   2709 	}
   2710 	return qfalse;
   2711 }
   2712 
   2713 /*
   2714 ===================
   2715 CL_ServerStatusResponse
   2716 ===================
   2717 */
   2718 void CL_ServerStatusResponse( netadr_t from, msg_t *msg ) {
   2719 	char	*s;
   2720 	char	info[MAX_INFO_STRING];
   2721 	int		i, l, score, ping;
   2722 	int		len;
   2723 	serverStatus_t *serverStatus;
   2724 
   2725 	serverStatus = NULL;
   2726 	for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
   2727 		if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) {
   2728 			serverStatus = &cl_serverStatusList[i];
   2729 			break;
   2730 		}
   2731 	}
   2732 	// if we didn't request this server status
   2733 	if (!serverStatus) {
   2734 		return;
   2735 	}
   2736 
   2737 	s = MSG_ReadStringLine( msg );
   2738 
   2739 	len = 0;
   2740 	Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "%s", s);
   2741 
   2742 	if (serverStatus->print) {
   2743 		Com_Printf("Server settings:\n");
   2744 		// print cvars
   2745 		while (*s) {
   2746 			for (i = 0; i < 2 && *s; i++) {
   2747 				if (*s == '\\')
   2748 					s++;
   2749 				l = 0;
   2750 				while (*s) {
   2751 					info[l++] = *s;
   2752 					if (l >= MAX_INFO_STRING-1)
   2753 						break;
   2754 					s++;
   2755 					if (*s == '\\') {
   2756 						break;
   2757 					}
   2758 				}
   2759 				info[l] = '\0';
   2760 				if (i) {
   2761 					Com_Printf("%s\n", info);
   2762 				}
   2763 				else {
   2764 					Com_Printf("%-24s", info);
   2765 				}
   2766 			}
   2767 		}
   2768 	}
   2769 
   2770 	len = strlen(serverStatus->string);
   2771 	Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\");
   2772 
   2773 	if (serverStatus->print) {
   2774 		Com_Printf("\nPlayers:\n");
   2775 		Com_Printf("num: score: ping: name:\n");
   2776 	}
   2777 	for (i = 0, s = MSG_ReadStringLine( msg ); *s; s = MSG_ReadStringLine( msg ), i++) {
   2778 
   2779 		len = strlen(serverStatus->string);
   2780 		Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\%s", s);
   2781 
   2782 		if (serverStatus->print) {
   2783 			score = ping = 0;
   2784 			sscanf(s, "%d %d", &score, &ping);
   2785 			s = strchr(s, ' ');
   2786 			if (s)
   2787 				s = strchr(s+1, ' ');
   2788 			if (s)
   2789 				s++;
   2790 			else
   2791 				s = "unknown";
   2792 			Com_Printf("%-2d   %-3d    %-3d   %s\n", i, score, ping, s );
   2793 		}
   2794 	}
   2795 	len = strlen(serverStatus->string);
   2796 	Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\");
   2797 
   2798 	serverStatus->time = Com_Milliseconds();
   2799 	serverStatus->address = from;
   2800 	serverStatus->pending = qfalse;
   2801 	if (serverStatus->print) {
   2802 		serverStatus->retrieved = qtrue;
   2803 	}
   2804 }
   2805 
   2806 /*
   2807 ==================
   2808 CL_LocalServers_f
   2809 ==================
   2810 */
   2811 void CL_LocalServers_f( void ) {
   2812 	char		*message;
   2813 	int			i, j;
   2814 	netadr_t	to;
   2815 
   2816 	Com_Printf( "Scanning for servers on the local network...\n");
   2817 
   2818 	// reset the list, waiting for response
   2819 	cls.numlocalservers = 0;
   2820 	cls.pingUpdateSource = AS_LOCAL;
   2821 
   2822 	for (i = 0; i < MAX_OTHER_SERVERS; i++) {
   2823 		qboolean b = cls.localServers[i].visible;
   2824 		Com_Memset(&cls.localServers[i], 0, sizeof(cls.localServers[i]));
   2825 		cls.localServers[i].visible = b;
   2826 	}
   2827 	Com_Memset( &to, 0, sizeof( to ) );
   2828 
   2829 	// The 'xxx' in the message is a challenge that will be echoed back
   2830 	// by the server.  We don't care about that here, but master servers
   2831 	// can use that to prevent spoofed server responses from invalid ip
   2832 	message = "\377\377\377\377getinfo xxx";
   2833 
   2834 	// send each message twice in case one is dropped
   2835 	for ( i = 0 ; i < 2 ; i++ ) {
   2836 		// send a broadcast packet on each server port
   2837 		// we support multiple server ports so a single machine
   2838 		// can nicely run multiple servers
   2839 		for ( j = 0 ; j < NUM_SERVER_PORTS ; j++ ) {
   2840 			to.port = BigShort( (short)(PORT_SERVER + j) );
   2841 
   2842 			to.type = NA_BROADCAST;
   2843 			NET_SendPacket( NS_CLIENT, strlen( message ), message, to );
   2844 
   2845 			to.type = NA_BROADCAST_IPX;
   2846 			NET_SendPacket( NS_CLIENT, strlen( message ), message, to );
   2847 		}
   2848 	}
   2849 }
   2850 
   2851 /*
   2852 ==================
   2853 CL_GlobalServers_f
   2854 ==================
   2855 */
   2856 void CL_GlobalServers_f( void ) {
   2857 	netadr_t	to;
   2858 	int			i;
   2859 	int			count;
   2860 	char		*buffptr;
   2861 	char		command[1024];
   2862 	
   2863 	if ( Cmd_Argc() < 3) {
   2864 		Com_Printf( "usage: globalservers <master# 0-1> <protocol> [keywords]\n");
   2865 		return;	
   2866 	}
   2867 
   2868 	cls.masterNum = atoi( Cmd_Argv(1) );
   2869 
   2870 	Com_Printf( "Requesting servers from the master...\n");
   2871 
   2872 	// reset the list, waiting for response
   2873 	// -1 is used to distinguish a "no response"
   2874 
   2875 	if( cls.masterNum == 1 ) {
   2876 		NET_StringToAdr( MASTER_SERVER_NAME, &to );
   2877 		cls.nummplayerservers = -1;
   2878 		cls.pingUpdateSource = AS_MPLAYER;
   2879 	}
   2880 	else {
   2881 		NET_StringToAdr( MASTER_SERVER_NAME, &to );
   2882 		cls.numglobalservers = -1;
   2883 		cls.pingUpdateSource = AS_GLOBAL;
   2884 	}
   2885 	to.type = NA_IP;
   2886 	to.port = BigShort(PORT_MASTER);
   2887 
   2888 	sprintf( command, "getservers %s", Cmd_Argv(2) );
   2889 
   2890 	// tack on keywords
   2891 	buffptr = command + strlen( command );
   2892 	count   = Cmd_Argc();
   2893 	for (i=3; i<count; i++)
   2894 		buffptr += sprintf( buffptr, " %s", Cmd_Argv(i) );
   2895 
   2896 	// if we are a demo, automatically add a "demo" keyword
   2897 	if ( Cvar_VariableValue( "fs_restrict" ) ) {
   2898 		buffptr += sprintf( buffptr, " demo" );
   2899 	}
   2900 
   2901 	NET_OutOfBandPrint( NS_SERVER, to, command );
   2902 }
   2903 
   2904 
   2905 /*
   2906 ==================
   2907 CL_GetPing
   2908 ==================
   2909 */
   2910 void CL_GetPing( int n, char *buf, int buflen, int *pingtime )
   2911 {
   2912 	const char	*str;
   2913 	int		time;
   2914 	int		maxPing;
   2915 
   2916 	if (!cl_pinglist[n].adr.port)
   2917 	{
   2918 		// empty slot
   2919 		buf[0]    = '\0';
   2920 		*pingtime = 0;
   2921 		return;
   2922 	}
   2923 
   2924 	str = NET_AdrToString( cl_pinglist[n].adr );
   2925 	Q_strncpyz( buf, str, buflen );
   2926 
   2927 	time = cl_pinglist[n].time;
   2928 	if (!time)
   2929 	{
   2930 		// check for timeout
   2931 		time = cls.realtime - cl_pinglist[n].start;
   2932 		maxPing = Cvar_VariableIntegerValue( "cl_maxPing" );
   2933 		if( maxPing < 100 ) {
   2934 			maxPing = 100;
   2935 		}
   2936 		if (time < maxPing)
   2937 		{
   2938 			// not timed out yet
   2939 			time = 0;
   2940 		}
   2941 	}
   2942 
   2943 	CL_SetServerInfoByAddress(cl_pinglist[n].adr, cl_pinglist[n].info, cl_pinglist[n].time);
   2944 
   2945 	*pingtime = time;
   2946 }
   2947 
   2948 /*
   2949 ==================
   2950 CL_UpdateServerInfo
   2951 ==================
   2952 */
   2953 void CL_UpdateServerInfo( int n )
   2954 {
   2955 	if (!cl_pinglist[n].adr.port)
   2956 	{
   2957 		return;
   2958 	}
   2959 
   2960 	CL_SetServerInfoByAddress(cl_pinglist[n].adr, cl_pinglist[n].info, cl_pinglist[n].time );
   2961 }
   2962 
   2963 /*
   2964 ==================
   2965 CL_GetPingInfo
   2966 ==================
   2967 */
   2968 void CL_GetPingInfo( int n, char *buf, int buflen )
   2969 {
   2970 	if (!cl_pinglist[n].adr.port)
   2971 	{
   2972 		// empty slot
   2973 		if (buflen)
   2974 			buf[0] = '\0';
   2975 		return;
   2976 	}
   2977 
   2978 	Q_strncpyz( buf, cl_pinglist[n].info, buflen );
   2979 }
   2980 
   2981 /*
   2982 ==================
   2983 CL_ClearPing
   2984 ==================
   2985 */
   2986 void CL_ClearPing( int n )
   2987 {
   2988 	if (n < 0 || n >= MAX_PINGREQUESTS)
   2989 		return;
   2990 
   2991 	cl_pinglist[n].adr.port = 0;
   2992 }
   2993 
   2994 /*
   2995 ==================
   2996 CL_GetPingQueueCount
   2997 ==================
   2998 */
   2999 int CL_GetPingQueueCount( void )
   3000 {
   3001 	int		i;
   3002 	int		count;
   3003 	ping_t*	pingptr;
   3004 
   3005 	count   = 0;
   3006 	pingptr = cl_pinglist;
   3007 
   3008 	for (i=0; i<MAX_PINGREQUESTS; i++, pingptr++ ) {
   3009 		if (pingptr->adr.port) {
   3010 			count++;
   3011 		}
   3012 	}
   3013 
   3014 	return (count);
   3015 }
   3016 
   3017 /*
   3018 ==================
   3019 CL_GetFreePing
   3020 ==================
   3021 */
   3022 ping_t* CL_GetFreePing( void )
   3023 {
   3024 	ping_t*	pingptr;
   3025 	ping_t*	best;	
   3026 	int		oldest;
   3027 	int		i;
   3028 	int		time;
   3029 
   3030 	pingptr = cl_pinglist;
   3031 	for (i=0; i<MAX_PINGREQUESTS; i++, pingptr++ )
   3032 	{
   3033 		// find free ping slot
   3034 		if (pingptr->adr.port)
   3035 		{
   3036 			if (!pingptr->time)
   3037 			{
   3038 				if (cls.realtime - pingptr->start < 500)
   3039 				{
   3040 					// still waiting for response
   3041 					continue;
   3042 				}
   3043 			}
   3044 			else if (pingptr->time < 500)
   3045 			{
   3046 				// results have not been queried
   3047 				continue;
   3048 			}
   3049 		}
   3050 
   3051 		// clear it
   3052 		pingptr->adr.port = 0;
   3053 		return (pingptr);
   3054 	}
   3055 
   3056 	// use oldest entry
   3057 	pingptr = cl_pinglist;
   3058 	best    = cl_pinglist;
   3059 	oldest  = INT_MIN;
   3060 	for (i=0; i<MAX_PINGREQUESTS; i++, pingptr++ )
   3061 	{
   3062 		// scan for oldest
   3063 		time = cls.realtime - pingptr->start;
   3064 		if (time > oldest)
   3065 		{
   3066 			oldest = time;
   3067 			best   = pingptr;
   3068 		}
   3069 	}
   3070 
   3071 	return (best);
   3072 }
   3073 
   3074 /*
   3075 ==================
   3076 CL_Ping_f
   3077 ==================
   3078 */
   3079 void CL_Ping_f( void ) {
   3080 	netadr_t	to;
   3081 	ping_t*		pingptr;
   3082 	char*		server;
   3083 
   3084 	if ( Cmd_Argc() != 2 ) {
   3085 		Com_Printf( "usage: ping [server]\n");
   3086 		return;	
   3087 	}
   3088 
   3089 	Com_Memset( &to, 0, sizeof(netadr_t) );
   3090 
   3091 	server = Cmd_Argv(1);
   3092 
   3093 	if ( !NET_StringToAdr( server, &to ) ) {
   3094 		return;
   3095 	}
   3096 
   3097 	pingptr = CL_GetFreePing();
   3098 
   3099 	memcpy( &pingptr->adr, &to, sizeof (netadr_t) );
   3100 	pingptr->start = cls.realtime;
   3101 	pingptr->time  = 0;
   3102 
   3103 	CL_SetServerInfoByAddress(pingptr->adr, NULL, 0);
   3104 		
   3105 	NET_OutOfBandPrint( NS_CLIENT, to, "getinfo xxx" );
   3106 }
   3107 
   3108 /*
   3109 ==================
   3110 CL_UpdateVisiblePings_f
   3111 ==================
   3112 */
   3113 qboolean CL_UpdateVisiblePings_f(int source) {
   3114 	int			slots, i;
   3115 	char		buff[MAX_STRING_CHARS];
   3116 	int			pingTime;
   3117 	int			max;
   3118 	qboolean status = qfalse;
   3119 
   3120 	if (source < 0 || source > AS_FAVORITES) {
   3121 		return qfalse;
   3122 	}
   3123 
   3124 	cls.pingUpdateSource = source;
   3125 
   3126 	slots = CL_GetPingQueueCount();
   3127 	if (slots < MAX_PINGREQUESTS) {
   3128 		serverInfo_t *server = NULL;
   3129 
   3130 		max = (source == AS_GLOBAL) ? MAX_GLOBAL_SERVERS : MAX_OTHER_SERVERS;
   3131 		switch (source) {
   3132 			case AS_LOCAL :
   3133 				server = &cls.localServers[0];
   3134 				max = cls.numlocalservers;
   3135 			break;
   3136 			case AS_MPLAYER :
   3137 				server = &cls.mplayerServers[0];
   3138 				max = cls.nummplayerservers;
   3139 			break;
   3140 			case AS_GLOBAL :
   3141 				server = &cls.globalServers[0];
   3142 				max = cls.numglobalservers;
   3143 			break;
   3144 			case AS_FAVORITES :
   3145 				server = &cls.favoriteServers[0];
   3146 				max = cls.numfavoriteservers;
   3147 			break;
   3148 		}
   3149 		for (i = 0; i < max; i++) {
   3150 			if (server[i].visible) {
   3151 				if (server[i].ping == -1) {
   3152 					int j;
   3153 
   3154 					if (slots >= MAX_PINGREQUESTS) {
   3155 						break;
   3156 					}
   3157 					for (j = 0; j < MAX_PINGREQUESTS; j++) {
   3158 						if (!cl_pinglist[j].adr.port) {
   3159 							continue;
   3160 						}
   3161 						if (NET_CompareAdr( cl_pinglist[j].adr, server[i].adr)) {
   3162 							// already on the list
   3163 							break;
   3164 						}
   3165 					}
   3166 					if (j >= MAX_PINGREQUESTS) {
   3167 						status = qtrue;
   3168 						for (j = 0; j < MAX_PINGREQUESTS; j++) {
   3169 							if (!cl_pinglist[j].adr.port) {
   3170 								break;
   3171 							}
   3172 						}
   3173 						memcpy(&cl_pinglist[j].adr, &server[i].adr, sizeof(netadr_t));
   3174 						cl_pinglist[j].start = cls.realtime;
   3175 						cl_pinglist[j].time = 0;
   3176 						NET_OutOfBandPrint( NS_CLIENT, cl_pinglist[j].adr, "getinfo xxx" );
   3177 						slots++;
   3178 					}
   3179 				}
   3180 				// if the server has a ping higher than cl_maxPing or
   3181 				// the ping packet got lost
   3182 				else if (server[i].ping == 0) {
   3183 					// if we are updating global servers
   3184 					if (source == AS_GLOBAL) {
   3185 						//
   3186 						if ( cls.numGlobalServerAddresses > 0 ) {
   3187 							// overwrite this server with one from the additional global servers
   3188 							cls.numGlobalServerAddresses--;
   3189 							CL_InitServerInfo(&server[i], &cls.globalServerAddresses[cls.numGlobalServerAddresses]);
   3190 							// NOTE: the server[i].visible flag stays untouched
   3191 						}
   3192 					}
   3193 				}
   3194 			}
   3195 		}
   3196 	} 
   3197 
   3198 	if (slots) {
   3199 		status = qtrue;
   3200 	}
   3201 	for (i = 0; i < MAX_PINGREQUESTS; i++) {
   3202 		if (!cl_pinglist[i].adr.port) {
   3203 			continue;
   3204 		}
   3205 		CL_GetPing( i, buff, MAX_STRING_CHARS, &pingTime );
   3206 		if (pingTime != 0) {
   3207 			CL_ClearPing(i);
   3208 			status = qtrue;
   3209 		}
   3210 	}
   3211 
   3212 	return status;
   3213 }
   3214 
   3215 /*
   3216 ==================
   3217 CL_ServerStatus_f
   3218 ==================
   3219 */
   3220 void CL_ServerStatus_f(void) {
   3221 	netadr_t	to;
   3222 	char		*server;
   3223 	serverStatus_t *serverStatus;
   3224 
   3225 	Com_Memset( &to, 0, sizeof(netadr_t) );
   3226 
   3227 	if ( Cmd_Argc() != 2 ) {
   3228 		if ( cls.state != CA_ACTIVE || clc.demoplaying ) {
   3229 			Com_Printf ("Not connected to a server.\n");
   3230 			Com_Printf( "Usage: serverstatus [server]\n");
   3231 			return;	
   3232 		}
   3233 		server = cls.servername;
   3234 	}
   3235 	else {
   3236 		server = Cmd_Argv(1);
   3237 	}
   3238 
   3239 	if ( !NET_StringToAdr( server, &to ) ) {
   3240 		return;
   3241 	}
   3242 
   3243 	NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" );
   3244 
   3245 	serverStatus = CL_GetServerStatus( to );
   3246 	serverStatus->address = to;
   3247 	serverStatus->print = qtrue;
   3248 	serverStatus->pending = qtrue;
   3249 }
   3250 
   3251 /*
   3252 ==================
   3253 CL_ShowIP_f
   3254 ==================
   3255 */
   3256 void CL_ShowIP_f(void) {
   3257 	Sys_ShowIP();
   3258 }
   3259 
   3260 /*
   3261 =================
   3262 bool CL_CDKeyValidate
   3263 =================
   3264 */
   3265 qboolean CL_CDKeyValidate( const char *key, const char *checksum ) {
   3266 	char	ch;
   3267 	byte	sum;
   3268 	char	chs[3];
   3269 	int i, len;
   3270 
   3271 	len = strlen(key);
   3272 	if( len != CDKEY_LEN ) {
   3273 		return qfalse;
   3274 	}
   3275 
   3276 	if( checksum && strlen( checksum ) != CDCHKSUM_LEN ) {
   3277 		return qfalse;
   3278 	}
   3279 
   3280 	sum = 0;
   3281 	// for loop gets rid of conditional assignment warning
   3282 	for (i = 0; i < len; i++) {
   3283 		ch = *key++;
   3284 		if (ch>='a' && ch<='z') {
   3285 			ch -= 32;
   3286 		}
   3287 		switch( ch ) {
   3288 		case '2':
   3289 		case '3':
   3290 		case '7':
   3291 		case 'A':
   3292 		case 'B':
   3293 		case 'C':
   3294 		case 'D':
   3295 		case 'G':
   3296 		case 'H':
   3297 		case 'J':
   3298 		case 'L':
   3299 		case 'P':
   3300 		case 'R':
   3301 		case 'S':
   3302 		case 'T':
   3303 		case 'W':
   3304 			sum += ch;
   3305 			continue;
   3306 		default:
   3307 			return qfalse;
   3308 		}
   3309 	}
   3310 
   3311 	sprintf(chs, "%02x", sum);
   3312 	
   3313 	if (checksum && !Q_stricmp(chs, checksum)) {
   3314 		return qtrue;
   3315 	}
   3316 
   3317 	if (!checksum) {
   3318 		return qtrue;
   3319 	}
   3320 
   3321 	return qfalse;
   3322 }
   3323 
   3324