1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 */
20 // cl_main.c  -- client main loop
21 
22 #include "client.h"
23 
24 cvar_t	*freelook;
25 
26 cvar_t	*adr0;
27 cvar_t	*adr1;
28 cvar_t	*adr2;
29 cvar_t	*adr3;
30 cvar_t	*adr4;
31 cvar_t	*adr5;
32 cvar_t	*adr6;
33 cvar_t	*adr7;
34 cvar_t	*adr8;
35 
36 cvar_t	*cl_stereo_separation;
37 cvar_t	*cl_stereo;
38 
39 extern cvar_t	*rcon_password;
40 cvar_t	*rcon_address;
41 
42 cvar_t	*cl_noskins;
43 cvar_t	*cl_autoskins;
44 cvar_t	*cl_footsteps;
45 cvar_t	*cl_timeout;
46 cvar_t	*cl_predict;
47 cvar_t	*cl_gun;
48 cvar_t	*cl_maxfps;
49 cvar_t	*cl_async;
50 cvar_t	*r_maxfps;
51 
52 cvar_t	*cl_add_particles;
53 cvar_t	*cl_add_lights;
54 cvar_t	*cl_add_entities;
55 cvar_t	*cl_add_blend;
56 cvar_t	*cl_kickangles;
57 
58 cvar_t	*cl_shownet;
59 cvar_t	*cl_showmiss;
60 cvar_t	*cl_showclamp;
61 
62 cvar_t	*lookspring;
63 cvar_t	*lookstrafe;
64 cvar_t	*sensitivity;
65 
66 cvar_t	*m_pitch;
67 cvar_t	*m_yaw;
68 cvar_t	*m_forward;
69 cvar_t	*m_side;
70 
71 cvar_t	*cl_thirdperson;
72 cvar_t	*cl_thirdperson_angle;
73 cvar_t	*cl_thirdperson_range;
74 
75 cvar_t *cl_railtrail_type;
76 cvar_t *cl_railtrail_time;
77 cvar_t *cl_railtrail_alpha;
78 cvar_t *cl_railcore_color;
79 cvar_t *cl_railcore_width;
80 cvar_t *cl_railrings_color;
81 cvar_t *cl_railrings_width;
82 
83 cvar_t	*cl_disable_particles;
84 cvar_t	*cl_disable_explosions;
85 cvar_t	*cl_chat_notify;
86 cvar_t	*cl_chat_beep;
87 
88 cvar_t *cl_gibs;
89 
90 cvar_t *cl_protocol;
91 
92 cvar_t	*gender_auto;
93 
94 cvar_t	*cl_vwep;
95 
96 client_static_t	cls;
97 client_state_t	cl;
98 
99 clientAPI_t		client;
100 
101 centity_t	cl_entities[ MAX_EDICTS ];
102 
103 qboolean CL_SendStatusRequest( char *buffer, int bufferSize );
104 
105 //======================================================================
106 
107 typedef enum {
108 	REQ_FREE,
109 	REQ_STATUS,
110 	REQ_INFO,
111 	REQ_PING,
112 	REQ_RCON
113 } requestType_t;
114 
115 typedef struct {
116 	requestType_t type;
117 	netadr_t adr;
118 	int time;
119 } request_t;
120 
121 #define MAX_REQUESTS	32
122 #define REQUEST_MASK	( MAX_REQUESTS - 1 )
123 
124 static request_t	clientRequests[MAX_REQUESTS];
125 static int			currentRequest;
126 
CL_AddRequest(netadr_t * adr,requestType_t type)127 static request_t *CL_AddRequest( netadr_t *adr, requestType_t type ) {
128 	request_t *r;
129 
130 	r = &clientRequests[currentRequest & REQUEST_MASK];
131 	currentRequest++;
132 
133 	r->adr = *adr;
134 	r->type = type;
135 	if( adr->type == NA_BROADCAST ) {
136 		r->time = cls.realtime + 3000;
137 	} else {
138 		r->time = cls.realtime + 6000;
139 	}
140 
141 	return r;
142 }
143 
144 /*
145 ===================
146 CL_UpdateGunSetting
147 ===================
148 */
CL_UpdateGunSetting(void)149 static void CL_UpdateGunSetting( void ) {
150     int nogun;
151 
152     if ( cls.state < ca_connected || cls.state > ca_active ) {
153         return;
154     }
155 
156     if ( cls.serverProtocol < PROTOCOL_VERSION_R1Q2 ) {
157         return;
158     }
159 
160 	if( cl_gun->integer == -1 ) {
161 		nogun = 2;
162 	} else if( cl_gun->integer == 0 || info_hand->integer == 2 ) {
163 		nogun = 1;
164 	} else {
165 		nogun = 0;
166 	}
167 
168     MSG_WriteByte( clc_setting );
169     MSG_WriteShort( CLS_NOGUN );
170     MSG_WriteShort( nogun );
171     MSG_FlushTo( &cls.netchan->message );
172 }
173 
174 /*
175 ===================
176 CL_UpdateGibSetting
177 ===================
178 */
CL_UpdateGibSetting(void)179 static void CL_UpdateGibSetting( void ) {
180     if ( cls.state < ca_connected || cls.state > ca_active ) {
181         return;
182     }
183 
184     if ( cls.serverProtocol != PROTOCOL_VERSION_Q2PRO ) {
185         return;
186     }
187 
188     MSG_WriteByte( clc_setting );
189     MSG_WriteShort( CLS_NOGIBS );
190     MSG_WriteShort( !cl_gibs->integer );
191     MSG_FlushTo( &cls.netchan->message );
192 }
193 
194 /*
195 ===================
196 CL_UpdateFootstepsSetting
197 ===================
198 */
CL_UpdateFootstepsSetting(void)199 static void CL_UpdateFootstepsSetting( void ) {
200     if ( cls.state < ca_connected || cls.state > ca_active ) {
201         return;
202     }
203 
204     if ( cls.serverProtocol != PROTOCOL_VERSION_Q2PRO ) {
205         return;
206     }
207 
208     MSG_WriteByte( clc_setting );
209     MSG_WriteShort( CLS_NOFOOTSTEPS );
210     MSG_WriteShort( !cl_footsteps->integer );
211     MSG_FlushTo( &cls.netchan->message );
212 }
213 
214 /*
215 ===================
216 CL_UpdatePredictSetting
217 ===================
218 */
CL_UpdatePredictSetting(void)219 static void CL_UpdatePredictSetting( void ) {
220     if ( cls.state < ca_connected || cls.state > ca_active ) {
221         return;
222     }
223 
224     if ( cls.serverProtocol != PROTOCOL_VERSION_Q2PRO ) {
225         return;
226     }
227 
228     MSG_WriteByte( clc_setting );
229     MSG_WriteShort( CLS_NOPREDICT );
230     MSG_WriteShort( !cl_predict->integer );
231     MSG_FlushTo( &cls.netchan->message );
232 }
233 
234 /*
235 ===================
236 CL_UpdateLocalFovSetting
237 ===================
238 */
CL_UpdateLocalFovSetting(void)239 void CL_UpdateLocalFovSetting( void ) {
240     if ( cls.state < ca_connected || cls.state > ca_active ) {
241         return;
242     }
243 
244     if ( cls.serverProtocol != PROTOCOL_VERSION_Q2PRO ) {
245         return;
246     }
247 
248     MSG_WriteByte( clc_setting );
249     MSG_WriteShort( CLS_LOCALFOV );
250     MSG_WriteShort( cl_demo_local_fov->integer );
251     MSG_FlushTo( &cls.netchan->message );
252 }
253 
254 /*
255 ===================
256 CL_ClientCommand
257 ===================
258 */
CL_ClientCommand(const char * string)259 void CL_ClientCommand( const char *string ) {
260     if ( !cls.netchan ) {
261         return;
262     }
263     MSG_WriteByte( clc_stringcmd );
264     MSG_WriteString( string );
265     MSG_FlushTo( &cls.netchan->message );
266 }
267 
268 
269 /*
270 ===================
271 Cmd_ForwardToServer
272 
273 adds the current command line as a clc_stringcmd to the client message.
274 things like godmode, noclip, etc, are commands directed to the server,
275 so when they are typed in at the console, they will need to be forwarded.
276 ===================
277 */
Cmd_ForwardToServer(void)278 void Cmd_ForwardToServer( void ) {
279     char	* cmd;
280 
281     cmd = Cmd_Argv( 0 );
282     if ( cls.state < ca_active || *cmd == '-' || *cmd == '+' ) {
283         Com_Printf( "Unknown command \"%s\"\n", cmd );
284         return;
285     }
286 
287     if ( cls.demoplayback ) {
288         return;
289     }
290 
291     CL_ClientCommand( Cmd_RawArgsFrom( 0 ) );
292 }
293 
294 /*
295 ==================
296 CL_ForwardToServer_f
297 ==================
298 */
CL_ForwardToServer_f(void)299 void CL_ForwardToServer_f( void ) {
300     if ( cls.state < ca_connected ) {
301         Com_Printf( "Can't \"%s\", not connected\n", Cmd_Argv( 0 ) );
302         return;
303     }
304 
305     if ( cls.demoplayback ) {
306         return;
307     }
308 
309     // don't forward the first argument
310     if ( Cmd_Argc() > 1 ) {
311         CL_ClientCommand( Cmd_RawArgs() );
312     }
313 }
314 
CL_Setenv_f(void)315 void CL_Setenv_f( void ) {
316 #ifndef _WIN32_WCE
317     int argc = Cmd_Argc();
318 
319     if ( argc > 2 ) {
320         char buffer[ MAX_STRING_CHARS ];
321 
322         Q_strncpyz( buffer, Cmd_Argv( 1 ), sizeof( buffer ) );
323         Q_strcat( buffer, sizeof( buffer ), "=" );
324 		Q_strcat( buffer, sizeof( buffer ), Cmd_ArgsFrom( 2 ) );
325 
326         putenv( buffer );
327     } else if ( argc == 2 ) {
328         char * env = getenv( Cmd_Argv( 1 ) );
329 
330         if ( env ) {
331             Com_Printf( "%s=%s\n", Cmd_Argv( 1 ), env );
332         } else {
333             Com_Printf( "%s undefined\n", Cmd_Argv( 1 ) );
334         }
335     }
336 #endif
337 }
338 
339 /*
340 ==================
341 CL_Pause_f
342 ==================
343 */
CL_Pause_f(void)344 void CL_Pause_f( void ) {
345     Cvar_SetInteger( "cl_paused", !cl_paused->integer );
346 }
347 
348 /*
349 =================
350 CL_CheckForResend
351 
352 Resend a connect message if the last one has timed out
353 =================
354 */
CL_CheckForResend(void)355 static void CL_CheckForResend( void ) {
356 	int ret;
357     char tail[32];
358 
359     if ( cls.demoplayback ) {
360         return;
361     }
362 
363     // if the local server is running and we aren't
364     // then connect
365     if ( cls.state < ca_connecting && sv_running->integer > ss_loading ) {
366         strcpy( cls.servername, "localhost" );
367         NET_StringToAdr( cls.servername, &cls.serverAddress );
368 		if( sv_running->integer == ss_game || sv_running->integer == ss_broadcast ) {
369 			cls.serverProtocol = PROTOCOL_VERSION_Q2PRO;
370 		} else {
371 			cls.serverProtocol = PROTOCOL_VERSION_DEFAULT;
372 		}
373 
374         Com_DPrintf( "Going from ca_disconnected to ca_connecting\n" );
375 
376         // we don't need a challenge on the localhost
377         cls.state = ca_connecting;
378         cls.connect_time = -9999;
379     }
380 
381     // resend if we haven't gotten a reply yet
382     if ( cls.state != ca_connecting && cls.state != ca_challenging ) {
383         return;
384     }
385 
386     if ( cls.realtime - cls.connect_time < 3000 )
387         return;
388 
389     cls.connect_time = cls.realtime;	// for retransmit requests
390 
391     cls.connectCount++;
392 
393     if ( cls.state == ca_challenging ) {
394         Com_Printf( "Requesting challenge... %i\n", cls.connectCount );
395         ret = Netchan_OutOfBandPrint( NS_CLIENT, &cls.serverAddress,
396                 "getchallenge\n" );
397 		if( ret == -1 ) {
398 			Com_Error( ERR_DISCONNECT, "%s to %s\n", Sys_NetErrorString(),
399                     NET_AdrToString( &cls.serverAddress ) );
400 		}
401         return;
402     }
403 
404     //
405     // We have gotten a challenge from the server, so try and connect.
406     //
407     Com_Printf( "Requesting connection... %i\n", cls.connectCount );
408 
409     cls.userinfo_modified = 0;
410 
411     cls.quakePort = net_qport->integer;
412 	if( cls.serverProtocol != PROTOCOL_VERSION_DEFAULT ) {
413         Com_sprintf( tail, sizeof( tail ), " %d",
414                 net_maxmsglen->integer );
415         if( cls.serverProtocol == PROTOCOL_VERSION_Q2PRO ) {
416             strcat( tail, net_chantype->integer ? " 1" : " 0" );
417         }
418         cls.quakePort &= 0xFF;
419 	} else {
420         tail[0] = 0;
421 	}
422     ret = Netchan_OutOfBandPrint( NS_CLIENT, &cls.serverAddress,
423         "connect %i %i %i \"%s\"%s\n",
424         cls.serverProtocol,
425         cls.quakePort,
426         cls.challenge,
427         Cvar_Userinfo(),
428         tail );
429 	if( ret == -1 ) {
430 		Com_Error( ERR_DISCONNECT, "%s to %s\n", Sys_NetErrorString(),
431                 NET_AdrToString( &cls.serverAddress ) );
432 	}
433 }
434 
435 
436 /*
437 ================
438 CL_Connect_f
439 
440 ================
441 */
CL_Connect_f(void)442 void CL_Connect_f( void ) {
443     char	*server;
444     netadr_t	address;
445 	int	protocol;
446 
447     if ( Cmd_Argc() < 2 ) {
448 usage:
449         Com_Printf( "Usage: connect <server> [protocol]\n"
450 					"Protocol argument overrides cl_protocol setting\n"
451 					"Supported protocols: %d, %d and %d\n",
452 			PROTOCOL_VERSION_DEFAULT,
453 			PROTOCOL_VERSION_R1Q2,
454 			PROTOCOL_VERSION_Q2PRO );
455         return;
456     }
457 
458     server = Cmd_Argv( 1 );
459 	protocol = cl_protocol->integer;
460 	if( Cmd_Argc() > 2 ) {
461 		protocol = atoi( Cmd_Argv( 2 ) );
462 		if( protocol != PROTOCOL_VERSION_DEFAULT &&
463 			protocol != PROTOCOL_VERSION_R1Q2 &&
464 			protocol != PROTOCOL_VERSION_Q2PRO )
465 		{
466 			goto usage;
467 		}
468 	}
469 
470     if ( !NET_StringToAdr( server, &address ) ) {
471         Com_Printf( "Bad server address\n" );
472         return;
473     }
474     if ( address.port == 0 ) {
475         address.port = BigShort( PORT_SERVER );
476     }
477 
478     if ( sv_running->integer ) {
479         // if running a local server, kill it and reissue
480         SV_Shutdown( "Server was killed\n", KILL_DROP );
481     }
482 
483 	NET_Config( NET_CLIENT );
484 
485     CL_Disconnect( ERR_DISCONNECT, NULL );
486 
487     cls.serverAddress = address;
488     cls.serverProtocol = protocol;
489     Q_strncpyz( cls.servername, server, sizeof( cls.servername ) );
490 
491     Com_DPrintf( "Going from ca_disconnected to ca_challenging\n" );
492 
493     cls.state = ca_challenging;
494     cls.connectCount = 0;
495     cls.connect_time = -99999;	// CL_CheckForResend() will fire immediately
496 
497 	CL_CheckForResend();
498 
499     Cvar_Set( "cl_paused", "0" );
500 	Cvar_Set( "timedemo", "0" );
501 
502     Con_Close();
503     UI_OpenMenu( UIMENU_NONE );
504 }
505 
CL_Connect_g(const char * partial,int state)506 static const char *CL_Connect_g( const char *partial, int state ) {
507     static int length;
508     static int index;
509     const char *adrstring;
510 	char buffer[MAX_QPATH];
511 
512     if( !state ) {
513         length = strlen( partial );
514         index = 0;
515     }
516 
517 	while( index < MAX_LOCAL_SERVERS ) {
518 		Com_sprintf( buffer, sizeof( buffer ), "adr%i", index );
519 		index++;
520 		adrstring = Cvar_VariableString( buffer );
521 		if( !adrstring[ 0 ] ) {
522 			continue;
523 		}
524 		if( !Q_stricmpn( partial, adrstring, length ) ) {
525 			return adrstring;
526 		}
527 	}
528 
529     return NULL;
530 }
531 
532 
533 /*
534 =====================
535 CL_Rcon_f
536 
537   Send the rest of the command line over as
538   an unconnected command.
539 =====================
540 */
CL_Rcon_f(void)541 void CL_Rcon_f( void ) {
542     netadr_t	address;
543 	int			ret;
544 
545 	if( Cmd_Argc() < 2 ) {
546 		Com_Printf( "Usage: %s <command>\n", Cmd_Argv( 0 ) );
547 		return;
548 	}
549 
550     if( !rcon_password->string[0] ) {
551         Com_Printf( "You must set 'rcon_password' before "
552                     "issuing an rcon command.\n" );
553         return;
554     }
555 
556 	if( !cls.netchan ) {
557         if( !rcon_address->string[0] ) {
558             Com_Printf( "You must either be connected, "
559                         "or set the 'rcon_address' cvar "
560                         "to issue rcon commands.\n" );
561             return;
562         }
563 		if( !NET_StringToAdr( rcon_address->string, &address ) ) {
564 			Com_Printf( "Bad address: %s\n", rcon_address->string );
565 			return;
566 		}
567         if( !address.port )
568             address.port = BigShort( PORT_SERVER );
569 	} else {
570 		address = cls.netchan->remote_address;
571 	}
572 
573 	NET_Config( NET_CLIENT );
574 
575 	CL_AddRequest( &address, REQ_RCON );
576 
577     ret = Netchan_OutOfBandPrint( NS_CLIENT, &address,
578 		"rcon \"%s\" %s", rcon_password->string, Cmd_RawArgs() );
579 	if( ret == -1 ) {
580 		Com_Printf( "%s to %s\n", Sys_NetErrorString(),
581                 NET_AdrToString( &address ) );
582 	}
583 }
584 
585 
586 /*
587 =====================
588 CL_ClearState
589 
590 =====================
591 */
CL_ClearState(void)592 void CL_ClearState( void ) {
593     S_StopAllSounds();
594     CL_ClearEffects();
595     CL_ClearTEnts();
596     LOC_FreeLocations();
597 
598     // wipe the entire cl structure
599 	CM_FreeMap( &cl.cm );
600     memset( &cl, 0, sizeof( cl ) );
601     memset( &cl_entities, 0, sizeof( cl_entities ) );
602 }
603 
604 /*
605 =====================
606 CL_Disconnect
607 
608 Goes from a connected state to full screen console state
609 Sends a disconnect message to the server
610 This is also called on Com_Error, so it shouldn't cause any errors
611 =====================
612 */
CL_Disconnect(comErrorType_t type,const char * text)613 void CL_Disconnect( comErrorType_t type, const char *text ) {
614     if ( cls.state != ca_disconnected ) {
615         Cmd_ExecTrigger( TRIG_CLIENT_SYSTEM, "disconnect" );
616     }
617 
618     if ( cls.ref_initialized )
619         ref.CinematicSetPalette( NULL );
620 
621     cls.connect_time = 0;
622 	cls.connectCount = 0;
623 
624     SCR_StopCinematic ();
625 
626     if ( cls.demoplayback ) {
627         CL_CloseDemoFile();
628 
629         if ( com_timedemo->integer ) {
630             float seconds, fps;
631 
632             seconds = ( Sys_Milliseconds() - cls.timeDemoStart ) * 0.001f;
633             fps = cls.timeDemoFrames / seconds;
634 
635             Com_Printf( "%i frames, %3.1f seconds: %3.1f fps\n",
636                     cls.timeDemoFrames, seconds, fps );
637         }
638     }
639 
640     if ( cls.demorecording )
641         CL_Stop_f();
642 
643     if( cls.netchan ) {
644         // send a disconnect message to the server
645         MSG_WriteByte( clc_stringcmd );
646         MSG_WriteString( "disconnect" );
647 
648         cls.netchan->Transmit( cls.netchan, msg_write.cursize, msg_write.data );
649         cls.netchan->Transmit( cls.netchan, msg_write.cursize, msg_write.data );
650         cls.netchan->Transmit( cls.netchan, msg_write.cursize, msg_write.data );
651 
652         SZ_Clear( &msg_write );
653 
654         Netchan_Close( cls.netchan );
655         cls.netchan = NULL;
656     }
657 
658     // stop download
659     if ( cls.download ) {
660         FS_FCloseFile( cls.download );
661         cls.download = 0;
662     }
663 
664     cls.downloadtempname[ 0 ] = 0;
665     cls.downloadname[ 0 ] = 0;
666 
667     CL_ClearState ();
668 
669     Cvar_Set( "cl_paused", "0" );
670 
671     cls.demoplayback = qfalse;
672     cls.state = ca_disconnected;
673     cls.messageString[ 0 ] = 0;
674 	cls.userinfo_modified = 0;
675 
676 	if( cls.ui_initialized ) {
677 		UI_ErrorMenu( type, text );
678 	}
679 
680 }
681 
682 /*
683 ================
684 CL_Disconnect_f
685 ================
686 */
CL_Disconnect_f(void)687 static void CL_Disconnect_f( void ) {
688 	if( cls.state > ca_disconnected ) {
689 		Com_Error( ERR_SILENT, "Disconnected from server" );
690 	}
691 }
692 
693 
694 /*
695 ================
696 CL_ServerStatus_f
697 ================
698 */
CL_ServerStatus_f(void)699 static void CL_ServerStatus_f( void ) {
700     char		*s;
701     netadr_t	adr;
702 	int			ret;
703 
704     if ( Cmd_Argc() < 2 ) {
705         Com_Printf( "Usage: %s <server>\n", Cmd_Argv( 0 ) );
706         return;
707     }
708 
709     s = Cmd_Argv( 1 );
710     if ( !NET_StringToAdr( s, &adr ) ) {
711         Com_Printf( "Bad address: %s\n", s );
712         return;
713     }
714 
715     if ( !adr.port ) {
716         adr.port = BigShort( PORT_SERVER );
717     }
718 
719 	CL_AddRequest( &adr, REQ_STATUS );
720 
721 	NET_Config( NET_CLIENT );
722 
723     ret = Netchan_OutOfBandPrint( NS_CLIENT, &adr, "status p=%d,%d,%d",
724 		PROTOCOL_VERSION_DEFAULT, PROTOCOL_VERSION_R1Q2, PROTOCOL_VERSION_Q2PRO );
725 	if( ret == -1 ) {
726 		Com_Printf( "%s to %s\n", Sys_NetErrorString(), NET_AdrToString( &adr ) );
727 	}
728 
729 }
730 
731 /*
732 ====================
733 SortPlayers
734 ====================
735 */
SortPlayers(const void * v1,const void * v2)736 static int SortPlayers( const void *v1, const void *v2 ) {
737     const playerStatus_t * p1 = ( const playerStatus_t * ) v1;
738     const playerStatus_t *p2 = ( const playerStatus_t * ) v2;
739 
740     if ( p1->score > p2->score ) {
741         return -1;
742     }
743 
744     if ( p1->score < p2->score ) {
745         return 1;
746     }
747 
748     return 0;
749 }
750 
751 /*
752 ====================
753 CL_ServerStatusResponse
754 ====================
755 */
CL_ServerStatusResponse(const char * status,const netadr_t * from,serverStatus_t * dest)756 static qboolean CL_ServerStatusResponse( const char *status,
757         const netadr_t *from, serverStatus_t *dest )
758 {
759 	const char *s;
760     playerStatus_t *player;
761     int length;
762 
763     memset( dest, 0, sizeof( *dest ) );
764 
765     s = strchr( status, '\n' );
766     if ( !s ) {
767 		return qfalse;
768     }
769     length = s - status;
770 	if( length > MAX_STRING_CHARS - 1 ) {
771 		return qfalse;
772 	}
773     s++;
774 
775     strcpy( dest->address, NET_AdrToString( from ) );
776     strncpy( dest->infostring, status, length );
777 
778     // HACK: check if this is a status response
779 	if( !strstr( dest->infostring, "\\hostname\\" ) ) {
780 		return qfalse;
781     }
782 
783     // parse player list
784     if( *s < 32 ) {
785         return qtrue;
786     }
787     do {
788         player = &dest->players[dest->numPlayers];
789         player->score = atoi( COM_Parse( &s ) );
790         player->ping = atoi( COM_Parse( &s ) );
791         if( !s ) {
792             break;
793         }
794         Q_strncpyz( player->name, COM_Parse( &s ), sizeof( player->name ) );
795 
796         if ( ++dest->numPlayers == MAX_STATUS_PLAYERS ) {
797             break;
798         }
799     } while( s );
800 
801     qsort( dest->players, dest->numPlayers, sizeof( dest->players[ 0 ] ),
802         SortPlayers );
803 
804     return qtrue;
805 
806 }
807 
CL_DumpServerInfo(const serverStatus_t * status)808 void CL_DumpServerInfo( const serverStatus_t *status ) {
809 	char	key[MAX_STRING_CHARS];
810 	char	value[MAX_STRING_CHARS];
811     const   playerStatus_t *player, *last;
812     const char    *infostring;
813 
814     Com_Printf( "Info response from %s:\n",
815         NET_AdrToString( &net_from ) );
816 
817     infostring = status->infostring;
818 	do {
819 		Info_NextPair( &infostring, key, value );
820 
821 		if( !key[0] ) {
822 			break;
823 		}
824 
825 		if( value[0] ) {
826 			Com_Printf( "%-20s %s\n", key, value );
827 		} else {
828 			Com_Printf( "%-20s <MISSING VALUE>\n", key );
829 		}
830 	} while( infostring );
831 
832     Com_Printf( "\nScore Ping Name\n" );
833     last = status->players + status->numPlayers;
834     for( player = status->players; player != last; player++ ) {
835         Com_Printf( "%5i %4i %s\n", player->score, player->ping,
836             player->name );
837     }
838 }
839 
840 /*
841 ====================
842 CL_ParsePrintMessage
843 ====================
844 */
CL_ParsePrintMessage(void)845 static void CL_ParsePrintMessage( void ) {
846 	request_t *r;
847     serverStatus_t serverStatus;
848     char *string;
849     int i, oldest;
850 
851     string = MSG_ReadString();
852 
853     if ( ( cls.state == ca_challenging || cls.state == ca_connecting ) &&
854             NET_IsEqualBaseAdr( &net_from, &cls.serverAddress ) )
855 	{
856 		/* server rejected our connect request */
857 		if( NET_IsLocalAddress( &cls.serverAddress ) ) {
858 			Com_Error( ERR_DROP, "Server rejected loopback connection" );
859 		}
860 		Com_Printf( "%s", string );
861         Q_strncpyz( cls.messageString, string, sizeof( cls.messageString ) );
862 		cls.state = ca_challenging;
863 		cls.connectCount = 0;
864 		return;
865     }
866 
867 	oldest = currentRequest - MAX_REQUESTS;
868 	if( oldest < 0 ) {
869 		oldest = 0;
870 	}
871 	for( i = currentRequest - 1; i >= oldest; i-- ) {
872 		r = &clientRequests[i & REQUEST_MASK];
873 		if( !r->type ) {
874 			continue;
875 		}
876 		if( r->adr.type == NA_BROADCAST ) {
877 			if( r->time < cls.realtime ) {
878 				continue;
879 			}
880 		} else {
881 			if( r->time < cls.realtime ) {
882 				break;
883 			}
884 			if( !NET_IsEqualBaseAdr( &net_from, &r->adr ) ) {
885 				continue;
886 			}
887 		}
888 		switch( r->type ) {
889 		case REQ_STATUS:
890 			if( CL_ServerStatusResponse( string, &net_from, &serverStatus ) ) {
891 			    CL_DumpServerInfo( &serverStatus );
892             }
893 			break;
894 		case REQ_INFO:
895 			break;
896 		case REQ_PING:
897 			if( CL_ServerStatusResponse( string, &net_from, &serverStatus ) ) {
898 				UI_AddToServerList( &serverStatus );
899 			}
900 			break;
901 		case REQ_RCON:
902 			Com_Printf( "%s", string );
903 			CL_AddRequest( &net_from, REQ_RCON );
904 			break;
905 		default:
906 			break;
907 		}
908 
909 		r->type = REQ_FREE;
910 		return;
911 	}
912 
913 	Com_DPrintf( "Dropped unrequested packet\n" );
914 }
915 
916 
917 /*
918 ====================
919 CL_Packet_f
920 
921 packet <destination> <contents>
922 
923 Contents allows \n escape character
924 ====================
925 */
926 /*
927 void CL_Packet_f (void)
928 {
929 	char	send[2048];
930 	int		i, l;
931 	char	*in, *out;
932 	netadr_t	adr;
933 
934 	if (Cmd_Argc() != 3)
935 	{
936 		Com_Printf ("packet <destination> <contents>\n");
937 		return;
938 	}
939 
940 	if (!NET_StringToAdr (Cmd_Argv(1), &adr))
941 	{
942 		Com_Printf ("Bad address\n");
943 		return;
944 	}
945 	if (!adr.port)
946 		adr.port = BigShort (PORT_SERVER);
947 
948 	in = Cmd_Argv(2);
949 	out = send+4;
950 	send[0] = send[1] = send[2] = send[3] = (char)0xff;
951 
952 	l = strlen (in);
953 	for (i=0 ; i<l ; i++)
954 	{
955 		if (in[i] == '\\' && in[i+1] == 'n')
956 		{
957 			*out++ = '\n';
958 			i++;
959 		}
960 		else
961 			*out++ = in[i];
962 	}
963 	*out = 0;
964 
965 	NET_SendPacket (NS_CLIENT, out-send, send, &adr);
966 }
967 */
968 
969 /*
970 =================
971 CL_Changing_f
972 
973 Just sent as a hint to the client that they should
974 drop to full console
975 =================
976 */
CL_Changing_f(void)977 static void CL_Changing_f( void ) {
978     if ( cls.state < ca_connected ) {
979         return;
980     }
981 
982     //ZOID
983     //if we are downloading, we don't change!
984     //This so we don't suddenly stop downloading a map
985     if ( cls.download )
986         return;
987 
988     Com_Printf( "Changing map...\n" );
989 
990     Cmd_ExecTrigger( TRIG_CLIENT_SYSTEM, "changelevel" );
991 
992     SCR_BeginLoadingPlaque();
993 
994     cls.state = ca_connected;	// not active anymore, but not disconnected
995 
996     SCR_UpdateScreen();
997 }
998 
999 
1000 /*
1001 =================
1002 CL_Reconnect_f
1003 
1004 The server is changing levels
1005 =================
1006 */
CL_Reconnect_f(void)1007 void CL_Reconnect_f( void ) {
1008     if ( cls.state < ca_connected ) {
1009         if( cls.serverAddress.type == NA_BAD ) {
1010             Com_Printf( "No server to reconnect to.\n" );
1011             return;
1012         }
1013         if( cls.serverAddress.type == NA_LOOPBACK ) {
1014             Com_Printf( "Cannot reconnect to loopback.\n" );
1015             return;
1016         }
1017     } else {
1018         S_StopAllSounds();
1019 
1020         if ( cls.demoplayback ) {
1021             cls.state = ca_connected;
1022             return;
1023         }
1024 
1025         if ( cls.state == ca_connected ) {
1026             if ( cls.download ) {
1027                 return; // if we are downloading, we don't change!
1028             }
1029 
1030             Com_Printf( "Reconnecting (fast)...\n" );
1031             cls.state = ca_connected;
1032 
1033             CL_ClientCommand( "new" );
1034             return;
1035         }
1036 
1037         CL_Disconnect( ERR_SILENT, NULL );
1038     }
1039 
1040     Com_Printf( "Reconnecting...\n" );
1041 
1042     cls.connect_time = -9999;
1043     cls.state = ca_challenging;
1044 
1045     SCR_UpdateScreen();
1046 }
1047 
1048 #if 0
1049 /*
1050 =================
1051 CL_ParseStatusMessage
1052 
1053 Handle a reply from a ping
1054 =================
1055 */
1056 void CL_ParseStatusMessage ( void ) {}
1057 #endif
1058 
1059 /*
1060 =================
1061 CL_SendStatusRequest
1062 =================
1063 */
CL_SendStatusRequest(char * buffer,int bufferSize)1064 qboolean CL_SendStatusRequest( char *buffer, int bufferSize ) {
1065     netadr_t	address;
1066 
1067     memset( &address, 0, sizeof( address ) );
1068 
1069 	NET_Config( NET_CLIENT );
1070 
1071     // send a broadcast packet
1072     if ( !strcmp( buffer, "broadcast" ) ) {
1073         address.type = NA_BROADCAST;
1074         address.port = BigShort( PORT_SERVER );
1075     } else {
1076         if ( !NET_StringToAdr( buffer, &address ) ) {
1077             return qfalse;
1078         }
1079 
1080         if ( !address.port ) {
1081             address.port = BigShort( PORT_SERVER );
1082         }
1083 
1084         Q_strncpyz( buffer, NET_AdrToString( &address ), bufferSize );
1085     }
1086 
1087 	CL_AddRequest( &address, REQ_PING );
1088 
1089     Netchan_OutOfBandPrint( NS_CLIENT, &address, "status" );
1090 
1091     Com_ProcessEvents();
1092 
1093     return qtrue;
1094 
1095 }
1096 
1097 
1098 /*
1099 =================
1100 CL_PingServers_f
1101 =================
1102 */
CL_PingServers_f(void)1103 static void CL_PingServers_f( void ) {
1104     int	i;
1105     char	buffer[ 32 ];
1106     char	*adrstring;
1107     netadr_t	address;
1108 
1109     memset( &address, 0, sizeof( address ) );
1110 
1111 	NET_Config( NET_CLIENT );
1112 
1113     // send a broadcast packet
1114     Com_Printf( "pinging broadcast...\n" );
1115     address.type = NA_BROADCAST;
1116     address.port = BigShort( PORT_SERVER );
1117 
1118 	CL_AddRequest( &address, REQ_STATUS );
1119 
1120     Netchan_OutOfBandPrint( NS_CLIENT, &address, "status" );
1121 
1122     SCR_UpdateScreen();
1123 
1124     // send a packet to each address book entry
1125     for ( i = 0; i < MAX_LOCAL_SERVERS; i++ ) {
1126         Com_sprintf( buffer, sizeof( buffer ), "adr%i", i );
1127         adrstring = Cvar_VariableString( buffer );
1128         if ( !adrstring[ 0 ] )
1129             continue;
1130 
1131         if ( !NET_StringToAdr( adrstring, &address ) ) {
1132             Com_Printf( "bad address: %s\n", adrstring );
1133             continue;
1134         }
1135 
1136         if ( !address.port ) {
1137             address.port = BigShort( PORT_SERVER );
1138         }
1139 
1140         Com_Printf( "pinging %s...\n", adrstring );
1141 	    CL_AddRequest( &address, REQ_STATUS );
1142 
1143         Netchan_OutOfBandPrint( NS_CLIENT, &address, "status" );
1144 
1145         Com_ProcessEvents();
1146         SCR_UpdateScreen();
1147 
1148     }
1149 }
1150 
1151 /*
1152 =================
1153 CL_Skins_f
1154 
1155 Load or download any custom player skins and models
1156 =================
1157 */
CL_Skins_f(void)1158 void CL_Skins_f ( void ) {
1159     int	i;
1160 
1161     for ( i = 0 ; i < MAX_CLIENTS ; i++ ) {
1162         if ( !cl.configstrings[ CS_PLAYERSKINS + i ][ 0 ] )
1163             continue;
1164         Com_Printf ( "client %i: %s\n", i, cl.configstrings[ CS_PLAYERSKINS + i ] );
1165         SCR_UpdateScreen ();
1166         CL_ParseClientinfo ( i );
1167     }
1168 }
1169 
1170 /*
1171 =================
1172 CL_ConnectionlessPacket
1173 
1174 Responses to broadcasts, etc
1175 =================
1176 */
CL_ConnectionlessPacket(void)1177 static void CL_ConnectionlessPacket( void ) {
1178     char	*s;
1179     char	*c;
1180     int	i, j, k;
1181 
1182     MSG_BeginReading();
1183     MSG_ReadLong();	// skip the -1
1184 
1185     s = MSG_ReadStringLine();
1186 
1187     Cmd_TokenizeString( s, qfalse );
1188 
1189     c = Cmd_Argv( 0 );
1190 
1191     Com_DPrintf( "%s: %s\n", NET_AdrToString ( &net_from ), s );
1192 
1193     // challenge from the server we are connecting to
1194     if ( !strcmp( c, "challenge" ) ) {
1195 		qboolean proto35 = qfalse, proto36 = qfalse;
1196 
1197         if ( cls.state < ca_challenging ) {
1198             Com_DPrintf( "Challenge received while not connecting.  Ignored.\n" );
1199             return;
1200         }
1201         if ( !NET_IsEqualBaseAdr( &net_from, &cls.serverAddress ) ) {
1202             Com_DPrintf( "Challenge from different address.  Ignored.\n" );
1203             return;
1204         }
1205         if ( cls.state > ca_challenging ) {
1206             Com_DPrintf( "Dup challenge received.  Ignored.\n" );
1207             return;
1208         }
1209 
1210         cls.challenge = atoi( Cmd_Argv( 1 ) );
1211         cls.state = ca_connecting;
1212         cls.connect_time = -9999;
1213         cls.connectCount = 0;
1214 
1215 		/* parse additional parameters */
1216         j = Cmd_Argc();
1217 		for( i = 2; i < j; i++ ) {
1218 			s = Cmd_Argv( i );
1219 			if( !strncmp( s, "p=", 2 ) ) {
1220 				s += 2;
1221 				while( *s ) {
1222 					k = atoi( s );
1223 					if( k == PROTOCOL_VERSION_R1Q2 ) {
1224 						proto35 = qtrue;
1225 					} else if( k == PROTOCOL_VERSION_Q2PRO ) {
1226 						proto36 = qtrue;
1227 					}
1228 					s = strchr( s, ',' );
1229 					if( s == NULL ) {
1230 						break;
1231 					}
1232 					s++;
1233 				}
1234 			}
1235         }
1236 
1237 		/* select the 'best' protocol available, unless told otherwise */
1238 		if( cls.serverProtocol == 0 ||
1239 			( cls.serverProtocol == PROTOCOL_VERSION_R1Q2 && !proto35 ) ||
1240 			( cls.serverProtocol == PROTOCOL_VERSION_Q2PRO && !proto36 ) )
1241 		{
1242 			if( proto36 ) {
1243 				cls.serverProtocol = PROTOCOL_VERSION_Q2PRO;
1244 			} else if( proto35 ) {
1245 				cls.serverProtocol = PROTOCOL_VERSION_R1Q2;
1246 			} else {
1247 				cls.serverProtocol = PROTOCOL_VERSION_DEFAULT;
1248 			}
1249 		}
1250 		Com_DPrintf( "Selected protocol %d\n", cls.serverProtocol );
1251 
1252 		cls.messageString[0] = 0;
1253 
1254         CL_CheckForResend();
1255         return;
1256     }
1257 
1258     // server connection
1259     if ( !strcmp( c, "client_connect" ) ) {
1260 		netchan_type_t type;
1261         int anticheat = 0;
1262 
1263         if ( cls.state < ca_connecting ) {
1264             Com_DPrintf( "Connect received while not connecting.  Ignored.\n" );
1265             return;
1266         }
1267         if ( !NET_IsEqualBaseAdr( &net_from, &cls.serverAddress ) ) {
1268             Com_DPrintf( "Connect from different address.  Ignored.\n" );
1269             return;
1270         }
1271         if ( cls.state > ca_connecting ) {
1272             Com_DPrintf( "Dup connect received.  Ignored.\n" );
1273             return;
1274         }
1275 
1276         if ( cls.serverProtocol == PROTOCOL_VERSION_Q2PRO ) {
1277 			type = NETCHAN_NEW;
1278         } else {
1279             type = NETCHAN_OLD;
1280         }
1281 
1282 		/* parse additional parameters */
1283         j = Cmd_Argc();
1284 		for( i = 1; i < j; i++ ) {
1285 			s = Cmd_Argv( i );
1286 			if( !strncmp( s, "ac=", 3 ) ) {
1287                 s += 3;
1288                 if( *s ) {
1289                     anticheat = atoi( s );
1290                 }
1291             } else if( !strncmp( s, "nc=", 3 ) ) {
1292                 s += 3;
1293                 if( *s ) {
1294                     type = atoi( s );
1295                     if( type != NETCHAN_OLD && type != NETCHAN_NEW ) {
1296 			            Com_Error( ERR_DISCONNECT,
1297                             "Server returned invalid netchan type" );
1298                     }
1299                 }
1300             }
1301         }
1302 
1303 		Com_Printf( "Connection to %s established (protocol %d).\n",
1304 			NET_AdrToString( &cls.serverAddress ), cls.serverProtocol );
1305 		if( cls.netchan ) {
1306 			// this may happen after svc_reconnect
1307 			Netchan_Close( cls.netchan );
1308 		}
1309 		cls.netchan = Netchan_Setup( NS_CLIENT, type, &cls.serverAddress,
1310                 cls.quakePort, 1024, cls.serverProtocol );
1311 
1312 #ifdef USE_ANTICHEAT
1313         if( anticheat ) {
1314 			MSG_WriteByte( clc_nop );
1315 			MSG_FlushTo( &cls.netchan->message );
1316 			cls.netchan->Transmit( cls.netchan, 0, NULL );
1317 			S_StopAllSounds();
1318 			Com_Printf( "Loading anticheat, this may take a few moments...\n" );
1319 			SCR_UpdateScreen();
1320 			if( !Sys_GetAntiCheatAPI() ) {
1321 				Com_Printf( "Trying to connect without anticheat.\n" );
1322 			} else {
1323 				Com_Printf( S_COLOR_CYAN "Anticheat loaded successfully.\n" );
1324 			}
1325         }
1326 #else
1327         if( anticheat >= 2 ) {
1328             Com_Printf( "Anticheat required by server, "
1329                     "but no anticheat support linked in.\n" );
1330         }
1331 #endif
1332 
1333         CL_ClientCommand( "new" );
1334         cls.state = ca_connected;
1335 		cls.messageString[0] = 0;
1336         return;
1337     }
1338 
1339 #if 0
1340     // server responding to a status broadcast
1341     if ( !strcmp( c, "info" ) ) {
1342         CL_ParseStatusMessage();
1343         return;
1344     }
1345 #endif
1346 
1347     // print command from somewhere
1348     if ( !strcmp( c, "print" ) ) {
1349         CL_ParsePrintMessage();
1350         return;
1351     }
1352 
1353     Com_DPrintf( "Unknown connectionless packet command.\n" );
1354 }
1355 
1356 
1357 /*
1358 =================
1359 CL_PacketEvent
1360 =================
1361 */
CL_PacketEvent(int ret)1362 void CL_PacketEvent( int ret ) {
1363     int bytesToSkip;
1364 
1365     if ( !cl_running || !cl_running->integer ) {
1366         return;
1367     }
1368 
1369     //
1370     // remote command packet
1371     //
1372     if ( ret == 1 && *( int * )msg_read.data == -1 ) {
1373         CL_ConnectionlessPacket();
1374         return;
1375     }
1376 
1377 	if( cls.state < ca_connected ) {
1378 		return;
1379 	}
1380 
1381 	if ( !cls.netchan ) {
1382         return;		// dump it if not connected
1383 	}
1384 
1385     if ( ret == 1 && msg_read.cursize < 8 ) {
1386         Com_DPrintf( "%s: runt packet\n", NET_AdrToString( &net_from ) );
1387         return;
1388     }
1389 
1390     //
1391     // packet from server
1392     //
1393     if ( !NET_IsEqualAdr( &net_from, &cls.netchan->remote_address ) ) {
1394         Com_DPrintf( "%s: sequenced packet without connection\n", NET_AdrToString( &net_from ) );
1395         return;
1396     }
1397 
1398 	if( ret == -1 ) {
1399 		Com_Error( ERR_DISCONNECT, "Connection reset by peer" );
1400 	}
1401 
1402     if ( !cls.netchan->Process( cls.netchan ) )
1403         return;		// wasn't accepted for some reason
1404 
1405     bytesToSkip = msg_read.readcount;
1406 
1407     CL_ParseServerMessage();
1408 
1409     //
1410     // if recording demos, copy the message out
1411     //
1412     //
1413     // we don't know if it is ok to save a demo message until
1414     // after we have parsed the frame
1415     //
1416     if ( cls.demorecording && !cls.demowaiting )
1417         CL_WriteDemoMessage( &msg_read, bytesToSkip );
1418 
1419     CL_AddNetgraph();
1420 
1421     SCR_AddLagometerPacketInfo();
1422 }
1423 
1424 
1425 //=============================================================================
1426 
1427 /*
1428 ==============
1429 CL_FixUpGender_f
1430 ==============
1431 */
CL_FixUpGender(void)1432 static void CL_FixUpGender( void ) {
1433     char *p;
1434     char sk[MAX_QPATH];
1435 
1436     Q_strncpyz( sk, info_skin->string, sizeof( sk ) );
1437     if ( ( p = strchr( sk, '/' ) ) != NULL )
1438         *p = 0;
1439     if ( Q_stricmp( sk, "male" ) == 0 || Q_stricmp( sk, "cyborg" ) == 0 )
1440         Cvar_Set ( "gender", "male" );
1441     else if ( Q_stricmp( sk, "female" ) == 0 || Q_stricmp( sk, "crackhor" ) == 0 )
1442         Cvar_Set ( "gender", "female" );
1443     else
1444         Cvar_Set ( "gender", "none" );
1445     info_gender->modified = qfalse;
1446 
1447 }
1448 
CL_UpdateUserinfo(cvar_t * var,cvarSetSource_t source)1449 void CL_UpdateUserinfo( cvar_t *var, cvarSetSource_t source ) {
1450 	int i;
1451 
1452 	if( var == info_skin && source != CVAR_SET_CONSOLE &&
1453             gender_auto->integer )
1454     {
1455 		 CL_FixUpGender();
1456 	}
1457 	if( !cls.netchan ) {
1458 		return;
1459 	}
1460 	if( cls.serverProtocol != PROTOCOL_VERSION_Q2PRO ) {
1461         // transmit at next oportunity
1462 		cls.userinfo_modified = MAX_PACKET_USERINFOS;
1463 		return;
1464 	}
1465 
1466 	if( cls.userinfo_modified == MAX_PACKET_USERINFOS ) {
1467 		return; // can't hold any more
1468 	}
1469 
1470 	// check for the same variable being modified twice
1471 	for( i = 0; i < cls.userinfo_modified; i++ ) {
1472 		if( cls.userinfo_updates[i] == var ) {
1473 			Com_DPrintf( "Dup modified %s at frame %u\n", var->name, com_framenum );
1474 			return;
1475 		}
1476 	}
1477 
1478 	Com_DPrintf( "Modified %s at frame %u\n", var->name, com_framenum );
1479 
1480 	cls.userinfo_updates[cls.userinfo_modified++] = var;
1481 }
1482 
1483 /*
1484 ==============
1485 CL_Userinfo_f
1486 ==============
1487 */
CL_Userinfo_f(void)1488 void CL_Userinfo_f ( void ) {
1489     Com_Printf ( "User info settings:\n" );
1490     Info_Print ( Cvar_Userinfo() );
1491 }
1492 
1493 /*
1494 ======================
1495 CL_RegisterSounds
1496 ======================
1497 */
CL_RegisterSounds(void)1498 void CL_RegisterSounds( void ) {
1499     int	i;
1500     char	*s;
1501 
1502     SCR_LoadingString( "sounds" );
1503 
1504     S_BeginRegistration ();
1505     CL_RegisterTEntSounds ();
1506     for ( i = 1; i < MAX_SOUNDS; i++ ) {
1507         s = cl.configstrings[ CS_SOUNDS + i ];
1508         if ( !s[ 0 ] )
1509             break;
1510         cl.sound_precache[ i ] = S_RegisterSound( s );
1511     }
1512     S_EndRegistration ();
1513 }
1514 
1515 /*
1516 =================
1517 CL_Snd_Restart_f
1518 
1519 Restart the sound subsystem so it can pick up
1520 new parameters and flush all sounds
1521 =================
1522 */
CL_Snd_Restart_f(void)1523 void CL_Snd_Restart_f ( void ) {
1524     S_Shutdown ();
1525     S_Init ();
1526     CL_RegisterSounds ();
1527 }
1528 
1529 int precache_check; // for autodownload of precache items
1530 int precache_spawncount;
1531 int precache_tex;
1532 int precache_model_skin;
1533 
1534 byte *precache_model; // used for skin checking in alias models
1535 
1536 #define PLAYER_MULT 5
1537 
1538 // ENV_CNT is map load, ENV_CNT+1 is first env map
1539 #define ENV_CNT (CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT)
1540 #define TEXTURE_CNT (ENV_CNT+13)
1541 
1542 static const char *env_suf[ 6 ] = {"rt", "bk", "lf", "ft", "up", "dn"
1543                                   };
1544 
CL_RequestNextDownload(void)1545 void CL_RequestNextDownload ( void ) {
1546     unsigned	map_checksum;		// for detecting cheater maps
1547     char fn[ MAX_QPATH ];
1548     dmdl_t *pheader;
1549 
1550     if ( cls.state != ca_connected && cls.state != ca_loading )
1551         return;
1552 
1553 	if ( ( !allow_download->integer || NET_IsLocalAddress( &cls.serverAddress )) && precache_check < ENV_CNT )
1554         precache_check = ENV_CNT;
1555 
1556     //ZOID
1557     if ( precache_check == CS_MODELS ) { // confirm map
1558         precache_check = CS_MODELS + 2; // 0 isn't used
1559         if ( allow_download_maps->integer )
1560             if ( !CL_CheckOrDownloadFile( cl.configstrings[ CS_MODELS + 1 ] ) )
1561                 return; // started a download
1562     }
1563     if ( precache_check >= CS_MODELS && precache_check < CS_MODELS + MAX_MODELS ) {
1564         if ( allow_download_models->integer ) {
1565             while ( precache_check < CS_MODELS + MAX_MODELS &&
1566                     cl.configstrings[ precache_check ][ 0 ] ) {
1567                 if ( cl.configstrings[ precache_check ][ 0 ] == '*' ||
1568                         cl.configstrings[ precache_check ][ 0 ] == '#' ) {
1569                     precache_check++;
1570                     continue;
1571                 }
1572                 if ( precache_model_skin == 0 ) {
1573                     if ( !CL_CheckOrDownloadFile( cl.configstrings[ precache_check ] ) ) {
1574                         precache_model_skin = 1;
1575                         return; // started a download
1576                     }
1577                     precache_model_skin = 1;
1578                 }
1579 
1580                 // checking for skins in the model
1581                 if ( !precache_model ) {
1582 
1583                     FS_LoadFile ( cl.configstrings[ precache_check ], ( void ** ) & precache_model );
1584                     if ( !precache_model ) {
1585                         precache_model_skin = 0;
1586                         precache_check++;
1587                         continue; // couldn't load it
1588                     }
1589                     if ( LittleLong( *( unsigned * ) precache_model ) != IDALIASHEADER ) {
1590                         // not an alias model
1591                         FS_FreeFile( precache_model );
1592                         precache_model = 0;
1593                         precache_model_skin = 0;
1594                         precache_check++;
1595                         continue;
1596                     }
1597                     pheader = ( dmdl_t * ) precache_model;
1598                     if ( LittleLong ( pheader->version ) != ALIAS_VERSION ) {
1599                         precache_check++;
1600                         precache_model_skin = 0;
1601                         continue; // couldn't load it
1602                     }
1603                 }
1604 
1605                 pheader = ( dmdl_t * ) precache_model;
1606 
1607                 while ( precache_model_skin - 1 < LittleLong( pheader->num_skins ) ) {
1608                     if ( !CL_CheckOrDownloadFile( ( char * ) precache_model +
1609                                                   LittleLong( pheader->ofs_skins ) +
1610                                                   ( precache_model_skin - 1 ) * MAX_SKINNAME ) ) {
1611                         precache_model_skin++;
1612                         return; // started a download
1613                     }
1614                     precache_model_skin++;
1615                 }
1616                 if ( precache_model ) {
1617                     FS_FreeFile( precache_model );
1618                     precache_model = 0;
1619                 }
1620                 precache_model_skin = 0;
1621                 precache_check++;
1622             }
1623         }
1624         precache_check = CS_SOUNDS;
1625     }
1626     if ( precache_check >= CS_SOUNDS && precache_check < CS_SOUNDS + MAX_SOUNDS ) {
1627         if ( allow_download_sounds->integer ) {
1628             if ( precache_check == CS_SOUNDS )
1629                 precache_check++; // zero is blank
1630             while ( precache_check < CS_SOUNDS + MAX_SOUNDS &&
1631                     cl.configstrings[ precache_check ][ 0 ] ) {
1632                 if ( cl.configstrings[ precache_check ][ 0 ] == '*' ) {
1633                     precache_check++;
1634                     continue;
1635                 }
1636                 Com_sprintf( fn, sizeof( fn ), "sound/%s", cl.configstrings[ precache_check++ ] );
1637                 if ( !CL_CheckOrDownloadFile( fn ) )
1638                     return; // started a download
1639             }
1640         }
1641         precache_check = CS_IMAGES;
1642     }
1643     if ( precache_check >= CS_IMAGES && precache_check < CS_IMAGES + MAX_IMAGES ) {
1644         if ( precache_check == CS_IMAGES )
1645             precache_check++; // zero is blank
1646         while ( precache_check < CS_IMAGES + MAX_IMAGES &&
1647                 cl.configstrings[ precache_check ][ 0 ] ) {
1648             Com_sprintf( fn, sizeof( fn ), "pics/%s.pcx", cl.configstrings[ precache_check++ ] );
1649             if ( !CL_CheckOrDownloadFile( fn ) )
1650                 return; // started a download
1651         }
1652         precache_check = CS_PLAYERSKINS;
1653     }
1654     // skins are special, since a player has three things to download:
1655     // model, weapon model and skin
1656     // so precache_check is now *3
1657     if ( precache_check >= CS_PLAYERSKINS && precache_check < CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT ) {
1658         if ( allow_download_players->integer ) {
1659             while ( precache_check < CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT ) {
1660                 int i, n;
1661                 char model[ MAX_QPATH ], skin[ MAX_QPATH ], *p;
1662 
1663                 i = ( precache_check - CS_PLAYERSKINS ) / PLAYER_MULT;
1664                 n = ( precache_check - CS_PLAYERSKINS ) % PLAYER_MULT;
1665 
1666                 if ( !cl.configstrings[ CS_PLAYERSKINS + i ][ 0 ] ) {
1667                     precache_check = CS_PLAYERSKINS + ( i + 1 ) * PLAYER_MULT;
1668                     continue;
1669                 }
1670 
1671                 if ( ( p = strchr( cl.configstrings[ CS_PLAYERSKINS + i ], '\\' ) ) != NULL )
1672                     p++;
1673                 else
1674                     p = cl.configstrings[ CS_PLAYERSKINS + i ];
1675                 Q_strncpyz( model, p, sizeof( model ) );
1676                 p = strchr( model, '/' );
1677                 if ( !p )
1678                     p = strchr( model, '\\' );
1679                 if ( p ) {
1680                     *p++ = 0;
1681                     strcpy( skin, p );
1682                 } else
1683                     *skin = 0;
1684 
1685                 switch ( n ) {
1686                 case 0:   // model
1687                     Com_sprintf( fn, sizeof( fn ), "players/%s/tris.md2", model );
1688                     if ( !CL_CheckOrDownloadFile( fn ) ) {
1689                         precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 1;
1690                         return; // started a download
1691                     }
1692                     n++;
1693                     /*FALL THROUGH*/
1694 
1695                 case 1:   // weapon model
1696                     Com_sprintf( fn, sizeof( fn ), "players/%s/weapon.md2", model );
1697                     if ( !CL_CheckOrDownloadFile( fn ) ) {
1698                         precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 2;
1699                         return; // started a download
1700                     }
1701                     n++;
1702                     /*FALL THROUGH*/
1703 
1704                 case 2:   // weapon skin
1705                     Com_sprintf( fn, sizeof( fn ), "players/%s/weapon.pcx", model );
1706                     if ( !CL_CheckOrDownloadFile( fn ) ) {
1707                         precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 3;
1708                         return; // started a download
1709                     }
1710                     n++;
1711                     /*FALL THROUGH*/
1712 
1713                 case 3:   // skin
1714                     Com_sprintf( fn, sizeof( fn ), "players/%s/%s.pcx", model, skin );
1715                     if ( !CL_CheckOrDownloadFile( fn ) ) {
1716                         precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 4;
1717                         return; // started a download
1718                     }
1719                     n++;
1720                     /*FALL THROUGH*/
1721 
1722                 case 4:   // skin_i
1723                     Com_sprintf( fn, sizeof( fn ), "players/%s/%s_i.pcx", model, skin );
1724                     if ( !CL_CheckOrDownloadFile( fn ) ) {
1725                         precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 5;
1726                         return; // started a download
1727                     }
1728                     // move on to next model
1729                     precache_check = CS_PLAYERSKINS + ( i + 1 ) * PLAYER_MULT;
1730                 }
1731             }
1732         }
1733         // precache phase completed
1734         precache_check = ENV_CNT;
1735     }
1736 
1737     if ( precache_check == ENV_CNT ) {
1738         precache_check = ENV_CNT + 1;
1739         SCR_LoadingString( "collision map" );
1740 
1741         CM_LoadMap ( &cl.cm, cl.configstrings[ CS_MODELS + 1 ], CM_LOAD_CLIENT, &map_checksum );
1742 
1743         if ( map_checksum != atoi( cl.configstrings[ CS_MAPCHECKSUM ] ) ) {
1744             Com_Error ( ERR_DROP, "Local map version differs from server: %i != '%s'\n",
1745                         map_checksum, cl.configstrings[ CS_MAPCHECKSUM ] );
1746             return;
1747         }
1748     }
1749 
1750     if ( precache_check > ENV_CNT && precache_check < TEXTURE_CNT ) {
1751         if ( allow_download->integer && allow_download_maps->integer ) {
1752             while ( precache_check < TEXTURE_CNT ) {
1753                 int n = precache_check++ - ENV_CNT - 1;
1754 
1755                 if ( n & 1 )
1756                     Com_sprintf( fn, sizeof( fn ), "env/%s%s.pcx",
1757                                  cl.configstrings[ CS_SKY ], env_suf[ n / 2 ] );
1758                 else
1759                     Com_sprintf( fn, sizeof( fn ), "env/%s%s.tga",
1760                                  cl.configstrings[ CS_SKY ], env_suf[ n / 2 ] );
1761                 if ( !CL_CheckOrDownloadFile( fn ) )
1762                     return; // started a download
1763             }
1764         }
1765         precache_check = TEXTURE_CNT;
1766     }
1767 
1768     if ( precache_check == TEXTURE_CNT ) {
1769         precache_check = TEXTURE_CNT + 1;
1770         precache_tex = 0;
1771     }
1772 
1773     // confirm existance of textures, download any that don't exist
1774     if ( precache_check == TEXTURE_CNT + 1 ) {
1775         if ( allow_download->integer && allow_download_maps->integer ) {
1776 			while ( precache_tex < cl.cm.cache->numtexinfo ) {
1777 				char *texname = cl.cm.cache->surfaces[ precache_tex++ ].rname;
1778 
1779                 // Also check if 32bit images are present
1780                 Com_sprintf( fn, sizeof( fn ), "textures/%s.jpg", texname );
1781                 if ( FS_LoadFile( fn, NULL ) == -1 ) {
1782                     Com_sprintf( fn, sizeof( fn ), "textures/%s.tga", texname );
1783                     if ( FS_LoadFile( fn, NULL ) == -1 ) {
1784                         Com_sprintf( fn, sizeof( fn ), "textures/%s.wal", texname );
1785                         if ( !CL_CheckOrDownloadFile( fn ) ) {
1786                             return; // started a download
1787                         }
1788                     }
1789                 }
1790             }
1791         }
1792         precache_check = TEXTURE_CNT + 999;
1793     }
1794 
1795 
1796     //ZOID
1797     CL_RegisterSounds ();
1798     CL_PrepRefresh ();
1799 
1800     LOC_LoadLocations();
1801 
1802     CL_ClientCommand( va( "begin %i\n", precache_spawncount ) );
1803 
1804     Cvar_FixCheats();
1805 
1806     CL_UpdateGunSetting();
1807     CL_UpdateGibSetting();
1808     CL_UpdateFootstepsSetting();
1809 	CL_UpdatePredictSetting();
1810 	CL_UpdateLocalFovSetting();
1811 
1812     cls.state = ca_precached;
1813 }
1814 
1815 /*
1816 =================
1817 CL_Precache_f
1818 
1819 The server will send this command right
1820 before allowing the client into the server
1821 =================
1822 */
CL_Precache_f(void)1823 static void CL_Precache_f( void ) {
1824     if ( cls.state < ca_connected ) {
1825         return;
1826     }
1827 
1828     cls.state = ca_loading;
1829 
1830     S_StopAllSounds();
1831 
1832     //Yet another hack to let old demos work
1833     //the old precache sequence
1834     if ( cls.demoplayback || Cmd_Argc() < 2 ) {
1835         uint32	map_checksum;		// for detecting cheater maps
1836 
1837         SCR_LoadingString( "collision map" );
1838         CM_LoadMap( &cl.cm, cl.configstrings[ CS_MODELS + 1 ],
1839                 CM_LOAD_CLIENT, &map_checksum );
1840         CL_RegisterSounds();
1841         CL_PrepRefresh();
1842         cls.state = ca_precached;
1843         return;
1844     }
1845 
1846     precache_check = CS_MODELS;
1847     precache_spawncount = atoi( Cmd_Argv( 1 ) );
1848     precache_model = 0;
1849     precache_model_skin = 0;
1850 
1851     CL_RequestNextDownload();
1852 
1853     if( cls.state != ca_precached ) {
1854         cls.state = ca_connected;
1855     }
1856 }
1857 
1858 
CL_DumpClients_f(void)1859 static void CL_DumpClients_f( void ) {
1860     int i;
1861 
1862     if ( cls.state != ca_active ) {
1863 		Com_Printf( "Must be in a level to dump\n" );
1864         return;
1865     }
1866 
1867     for ( i = 0 ; i < MAX_CLIENTS ; i++ ) {
1868         if ( !cl.clientinfo[ i ].name[ 0 ] ) {
1869             continue;
1870         }
1871 
1872         Com_Printf( "%3i: %s\n", i, cl.clientinfo[ i ].name );
1873     }
1874 }
1875 
CL_DumpStatusbar_f(void)1876 static void CL_DumpStatusbar_f( void ) {
1877 	char buffer[MAX_QPATH];
1878 	fileHandle_t f;
1879 
1880     if ( cls.state != ca_active ) {
1881 		Com_Printf( "Must be in a level to dump.\n" );
1882         return;
1883     }
1884 
1885 	if( Cmd_Argc() != 2 ) {
1886 		Com_Printf( "Usage: %s <filename>\n", Cmd_Argv( 0 ) );
1887 		return;
1888 	}
1889 
1890 	Cmd_ArgvBuffer( 1, buffer, sizeof( buffer ) );
1891 	COM_DefaultExtension( buffer, ".txt", sizeof( buffer ) );
1892 
1893 	FS_FOpenFile( buffer, &f, FS_MODE_WRITE );
1894 	if( !f ) {
1895 		Com_EPrintf( "Couldn't open %s for writing.\n", buffer );
1896 		return;
1897 	}
1898 
1899 	FS_FPrintf( f, "// status bar program dump of '%s' mod\n",
1900             Cvar_VariableString( "gamedir" ) );
1901 	FS_FPrintf( f, "%s\n", cl.configstrings[CS_STATUSBAR] );
1902 
1903 	FS_FCloseFile( f );
1904 
1905 	Com_Printf( "Dumped status bar program to %s.\n", buffer );
1906 }
1907 
CL_DumpLayout_f(void)1908 static void CL_DumpLayout_f( void ) {
1909 	char buffer[MAX_QPATH];
1910 	fileHandle_t f;
1911 
1912     if ( cls.state != ca_active ) {
1913 		Com_Printf( "Must be in a level to dump.\n" );
1914         return;
1915     }
1916 
1917 	if( Cmd_Argc() != 2 ) {
1918 		Com_Printf( "Usage: %s <filename>\n", Cmd_Argv( 0 ) );
1919 		return;
1920 	}
1921 
1922 	if( !cl.layout[0] ) {
1923 		Com_Printf( "No layout to dump.\n" );
1924 		return;
1925 	}
1926 
1927 	Cmd_ArgvBuffer( 1, buffer, sizeof( buffer ) );
1928 	COM_DefaultExtension( buffer, ".txt", sizeof( buffer ) );
1929 
1930 	FS_FOpenFile( buffer, &f, FS_MODE_WRITE );
1931 	if( !f ) {
1932 		Com_EPrintf( "Couldn't open %s for writing.\n", buffer );
1933 		return;
1934 	}
1935 
1936 	FS_FPrintf( f, "// layout program dump of '%s' mod\n",
1937             Cvar_VariableString( "gamedir" ) );
1938 	FS_FPrintf( f, "%s\n", cl.layout );
1939 
1940 	FS_FCloseFile( f );
1941 
1942 	Com_Printf( "Dumped layout program to %s.\n", buffer );
1943 }
1944 
1945 /*
1946 ====================
1947 CL_Mapname_m
1948 ====================
1949 */
CL_Mapname_m(char * buffer,int bufferSize)1950 static void CL_Mapname_m( char *buffer, int bufferSize ) {
1951     if ( !cl.mapname[ 0 ] ) {
1952         Q_strncpyz( buffer, "nomap", bufferSize );
1953         return;
1954     }
1955 
1956     Q_strncpyz( buffer, cl.mapname, bufferSize );
1957 }
1958 
1959 /*
1960 ====================
1961 CL_Server_m
1962 ====================
1963 */
CL_Server_m(char * buffer,int bufferSize)1964 static void CL_Server_m( char *buffer, int bufferSize ) {
1965     if ( cls.state <= ca_disconnected ) {
1966         Q_strncpyz( buffer, "noserver", bufferSize );
1967         return;
1968     }
1969 
1970     Q_strncpyz( buffer, cls.servername, bufferSize );
1971 }
1972 
1973 /*
1974 ====================
1975 CL_DemoState_m
1976 ====================
1977 */
CL_DemoState_m(char * buffer,int bufferSize)1978 static void CL_DemoState_m( char *buffer, int bufferSize ) {
1979     Q_strncpyz( buffer, "0", bufferSize );
1980 
1981     if ( cls.state < ca_connected ) {
1982         return;
1983     }
1984 
1985     if ( cls.demorecording ) {
1986         Q_strncpyz( buffer, "1", bufferSize );
1987     } else if ( cls.demoplayback ) {
1988         Q_strncpyz( buffer, "2", bufferSize );
1989     }
1990 }
1991 
1992 /*
1993 ==============
1994 CL_Ups_m
1995 ==============
1996 */
CL_Ups_m(char * buffer,int bufferSize)1997 static void CL_Ups_m( char *buffer, int bufferSize ) {
1998 	vec3_t vel;
1999 	int ups;
2000 	player_state_t *ps;
2001 
2002 	if( cl.frame.ps.clientNum == CLIENTNUM_NONE ) {
2003 		buffer[0] = 0;
2004 		return;
2005 	}
2006 
2007 	if( !cls.demoplayback && cl.frame.ps.clientNum == cl.clientNum &&
2008             cl_predict->integer )
2009     {
2010 		VectorCopy( cl.predicted_velocity, vel );
2011 	} else {
2012 		ps = &cl.frame.ps.ps;
2013 
2014 		vel[0] = ps->pmove.velocity[0] * 0.125f;
2015 		vel[1] = ps->pmove.velocity[1] * 0.125f;
2016 		vel[2] = ps->pmove.velocity[2] * 0.125f;
2017 	}
2018 
2019 	ups = VectorLength( vel );
2020 	Com_sprintf( buffer, bufferSize, "%d", ups );
2021 }
2022 
2023 /*
2024 ==============
2025 CL_Timer_m
2026 ==============
2027 */
CL_Timer_m(char * buffer,int bufferSize)2028 static void CL_Timer_m( char *buffer, int bufferSize ) {
2029 	int hour, min, sec;
2030 
2031 	sec = cl.time / 1000;
2032 	min = sec / 60;
2033 	hour = min / 60;
2034 	min %= 60;
2035 	sec %= 60;
2036 
2037 	if( hour ) {
2038 		Com_sprintf( buffer, bufferSize, "%i:%i:%02i", hour, min, sec );
2039 	} else {
2040 		Com_sprintf( buffer, bufferSize, "%i:%02i", min, sec );
2041 	}
2042 
2043 }
2044 
CL_Fps_m(char * buffer,int bufferSize)2045 static void CL_Fps_m( char *buffer, int bufferSize ) {
2046 	Com_sprintf( buffer, bufferSize, "%i", cls.currentFPS );
2047 }
2048 
CL_Health_m(char * buffer,int bufferSize)2049 static void CL_Health_m( char *buffer, int bufferSize ) {
2050 	Com_sprintf( buffer, bufferSize, "%i", cl.frame.ps.ps.stats[STAT_HEALTH] );
2051 }
CL_Ammo_m(char * buffer,int bufferSize)2052 static void CL_Ammo_m( char *buffer, int bufferSize ) {
2053 	Com_sprintf( buffer, bufferSize, "%i", cl.frame.ps.ps.stats[STAT_AMMO] );
2054 }
CL_Armor_m(char * buffer,int bufferSize)2055 static void CL_Armor_m( char *buffer, int bufferSize ) {
2056 	Com_sprintf( buffer, bufferSize, "%i", cl.frame.ps.ps.stats[STAT_ARMOR] );
2057 }
2058 
2059 /*
2060 ====================
2061 CL_RestartFilesystem
2062 
2063 Flush caches and restart the VFS.
2064 ====================
2065 */
CL_RestartFilesystem(void)2066 void CL_RestartFilesystem( void ) {
2067     int	cls_state;
2068 
2069     if ( !cl_running->integer ) {
2070         FS_Restart();
2071         return;
2072     }
2073 
2074     Com_DPrintf( "CL_RestartFilesystem()\n" );
2075 
2076     /* temporary switch to loading state */
2077     cls_state = cls.state;
2078     if ( cls.state >= ca_precached ) {
2079         cls.state = ca_loading;
2080     }
2081 
2082     UI_OpenMenu( UIMENU_NONE );
2083     CL_ShutdownUI();
2084 
2085     S_StopAllSounds();
2086 	S_FreeAllSounds();
2087 
2088     ref.Shutdown( qfalse );
2089 
2090     FS_Restart();
2091 
2092     ref.Init( qfalse );
2093 
2094     SCR_RegisterMedia();
2095     Con_SetupDC();
2096     CL_InitUI();
2097 
2098     if ( cls_state == ca_disconnected ) {
2099         UI_OpenMenu( UIMENU_MAIN );
2100     } else if ( cls_state >= ca_loading ) {
2101 		CL_RegisterSounds();
2102         CL_PrepRefresh();
2103     }
2104 
2105     /* switch back to original state */
2106     cls.state = cls_state;
2107 
2108 }
2109 
2110 /*
2111 ====================
2112 CL_RestartRefresh
2113 ====================
2114 */
CL_RestartRefresh_f(void)2115 void CL_RestartRefresh_f( void ) {
2116     int	cls_state;
2117 
2118     Com_DPrintf( "CL_RestartRefresh()\n" );
2119 
2120     /* temporary switch to loading state */
2121     cls_state = cls.state;
2122     if ( cls.state >= ca_precached ) {
2123         cls.state = ca_loading;
2124     }
2125 
2126     UI_OpenMenu( UIMENU_NONE );
2127     CL_ShutdownUI();
2128 
2129     S_StopAllSounds();
2130 
2131     CL_ShutdownInput();
2132     CL_ShutdownRefresh();
2133 
2134     CL_InitRefresh();
2135     CL_InitInput();
2136 
2137     SCR_RegisterMedia();
2138     Con_SetupDC();
2139     CL_InitUI();
2140 
2141     if ( cls_state == ca_disconnected ) {
2142         UI_OpenMenu( UIMENU_MAIN );
2143     } else if ( cls_state >= ca_loading ) {
2144         CL_PrepRefresh();
2145     }
2146 
2147     /* switch back to original state */
2148     cls.state = cls_state;
2149 
2150 }
2151 
2152 /*
2153 ============
2154 CL_LocalConnect
2155 ============
2156 */
CL_LocalConnect(void)2157 void CL_LocalConnect( void ) {
2158     if ( FS_NeedRestart() ) {
2159         cls.state = ca_challenging;
2160         CL_RestartFilesystem();
2161     }
2162 }
2163 
2164 /*
2165 ============
2166 CL_RestartFilesystem_f
2167 
2168 Console command to re-start the file system.
2169 ============
2170 */
CL_RestartFilesystem_f(void)2171 static void CL_RestartFilesystem_f( void ) {
2172     if( !FS_SafeToRestart() ) {
2173         Com_Printf( "Can't \"%s\", there are some open file handles.\n", Cmd_Argv( 0 ) );
2174         return;
2175     }
2176 
2177     CL_RestartFilesystem();
2178 }
2179 
CL_Gun_OnChange(cvar_t * self,void * arg)2180 static void CL_Gun_OnChange( cvar_t *self, void *arg ) {
2181     CL_UpdateGunSetting();
2182 }
2183 
CL_Hand_OnChange(cvar_t * self,void * arg)2184 static void CL_Hand_OnChange( cvar_t *self, void *arg ) {
2185     CL_UpdateGunSetting();
2186 }
2187 
CL_Gibs_OnChange(cvar_t * self,void * arg)2188 static void CL_Gibs_OnChange( cvar_t *self, void *arg ) {
2189     CL_UpdateGibSetting();
2190 }
2191 
CL_Footsteps_OnChange(cvar_t * self,void * arg)2192 static void CL_Footsteps_OnChange( cvar_t *self, void *arg ) {
2193     CL_UpdateFootstepsSetting();
2194 }
2195 
CL_Predict_OnChange(cvar_t * self,void * arg)2196 static void CL_Predict_OnChange( cvar_t *self, void *arg ) {
2197     CL_UpdatePredictSetting();
2198 }
2199 
CL_GetClientState(uiClientState_t * state)2200 static void CL_GetClientState( uiClientState_t *state ) {
2201 	if( !state ) {
2202 		Com_Error( ERR_DROP, "CL_GetClientState: NULL" );
2203 	}
2204 	state->connState = cls.state;
2205 	state->connectCount = cls.connectCount;
2206 	state->demoplayback = cls.demoplayback;
2207 	Q_strncpyz( state->servername, cls.servername, sizeof( state->servername ) );
2208 	Q_strncpyz( state->mapname, cl.mapname, sizeof( state->mapname ) );
2209 	Q_strncpyz( state->fullname, cl.configstrings[CS_NAME], sizeof( state->fullname ) );
2210 	if( cls.state > ca_connected ) {
2211 		Q_strncpyz( state->loadingString, cl.loadingString, sizeof( state->loadingString ) );
2212 	} else {
2213 		Q_strncpyz( state->loadingString, cls.messageString, sizeof( state->loadingString ) );
2214 	}
2215 }
2216 
CL_FillAPI(clientAPI_t * api)2217 void CL_FillAPI( clientAPI_t *api ) {
2218 	api->StartLocalSound = S_StartLocalSound;
2219 	api->StopAllSounds = S_StopAllSounds;
2220 	api->SendStatusRequest = CL_SendStatusRequest;
2221 	api->GetClientState = CL_GetClientState;
2222 	api->GetDemoInfo = CL_GetDemoInfo;
2223 	api->UpdateScreen = SCR_UpdateScreen;
2224 }
2225 
2226 /*
2227 =================
2228 CL_InitLocal
2229 =================
2230 */
CL_InitLocal(void)2231 void CL_InitLocal ( void ) {
2232     int i;
2233 
2234     cls.state = ca_disconnected;
2235     cls.realtime = 0;
2236 
2237 	CL_FillAPI( &client );
2238 
2239     CL_RegisterInput();
2240 
2241     CL_InitDemos();
2242 
2243     LOC_Init();
2244 
2245     for ( i = 0 ; i < MAX_LOCAL_SERVERS ; i++ ) {
2246         Cvar_Get( va( "adr%i", i ), "", CVAR_ARCHIVE );
2247     }
2248 
2249     //
2250     // register our variables
2251     //
2252     cl_stereo_separation = Cvar_Get( "cl_stereo_separation", "0.4", CVAR_ARCHIVE );
2253     cl_stereo = Cvar_Get( "cl_stereo", "0", 0 );
2254 
2255     cl_add_blend = Cvar_Get ( "cl_blend", "1", CVAR_ARCHIVE );
2256     cl_add_lights = Cvar_Get ( "cl_lights", "1", 0 );
2257     cl_add_particles = Cvar_Get ( "cl_particles", "1", 0 );
2258     cl_add_entities = Cvar_Get ( "cl_entities", "1", 0 );
2259     cl_gun = Cvar_Get ( "cl_gun", "1", 0 );
2260 	cl_gun->changedFunc = CL_Gun_OnChange;
2261     cl_footsteps = Cvar_Get( "cl_footsteps", "1", 0 );
2262 	cl_footsteps->changedFunc = CL_Footsteps_OnChange;
2263     cl_noskins = Cvar_Get ( "cl_noskins", "0", 0 );
2264     cl_autoskins = Cvar_Get ( "cl_autoskins", "0", 0 );
2265     cl_predict = Cvar_Get ( "cl_predict", "1", 0 );
2266 	cl_predict->changedFunc = CL_Predict_OnChange;
2267     cl_kickangles = Cvar_Get( "cl_kickangles", "1", CVAR_CHEAT );
2268 	cl_maxfps = Cvar_Get( "cl_maxfps", "90", CVAR_ARCHIVE );
2269 	cl_async = Cvar_Get( "cl_async", "0", CVAR_ARCHIVE );
2270 	r_maxfps = Cvar_Get( "r_maxfps", "0", CVAR_ARCHIVE );
2271 
2272     cl_upspeed = Cvar_Get ( "cl_upspeed", "200", 0 );
2273     cl_forwardspeed = Cvar_Get ( "cl_forwardspeed", "200", 0 );
2274     cl_sidespeed = Cvar_Get ( "cl_sidespeed", "200", 0 );
2275     cl_yawspeed = Cvar_Get ( "cl_yawspeed", "140", 0 );
2276     cl_pitchspeed = Cvar_Get ( "cl_pitchspeed", "150", CVAR_CHEAT );
2277     cl_anglespeedkey = Cvar_Get ( "cl_anglespeedkey", "1.5", CVAR_CHEAT );
2278 
2279     freelook = Cvar_Get( "freelook", "0", CVAR_ARCHIVE );
2280     lookspring = Cvar_Get ( "lookspring", "0", CVAR_ARCHIVE );
2281     lookstrafe = Cvar_Get ( "lookstrafe", "0", CVAR_ARCHIVE );
2282     sensitivity = Cvar_Get ( "sensitivity", "3", CVAR_ARCHIVE );
2283 
2284     cl_run = Cvar_Get( "cl_run", "1", CVAR_ARCHIVE );
2285     m_pitch = Cvar_Get ( "m_pitch", "0.022", CVAR_ARCHIVE );
2286     m_yaw = Cvar_Get ( "m_yaw", "0.022", 0 );
2287     m_forward = Cvar_Get ( "m_forward", "1", 0 );
2288     m_side = Cvar_Get ( "m_side", "1", 0 );
2289 
2290     cl_shownet = Cvar_Get( "cl_shownet", "0", 0 );
2291     cl_showmiss = Cvar_Get ( "cl_showmiss", "0", 0 );
2292     cl_showclamp = Cvar_Get ( "showclamp", "0", 0 );
2293     cl_timeout = Cvar_Get ( "cl_timeout", "120", 0 );
2294 
2295     rcon_password = Cvar_Get ( "rcon_password", "", CVAR_PRIVATE );
2296     rcon_address = Cvar_Get ( "rcon_address", "", 0 );
2297 
2298     cl_thirdperson = Cvar_Get( "cl_thirdperson", "0", CVAR_CHEAT );
2299     cl_thirdperson_angle = Cvar_Get( "cl_thirdperson_angle", "0", 0 );
2300     cl_thirdperson_range = Cvar_Get( "cl_thirdperson_range", "60", 0 );
2301 
2302     cl_railtrail_type = Cvar_Get( "cl_railtrail_type", "0", 0 );
2303     cl_railtrail_time = Cvar_Get( "cl_railtrail_time", "1.0", 0 );
2304     cl_railtrail_alpha = Cvar_Get( "cl_railtrail_alpha", "1.0", 0 );
2305     cl_railcore_color = Cvar_Get( "cl_railcore_color", "0xFF0000", 0 );
2306     cl_railcore_width = Cvar_Get( "cl_railcore_width", "3", 0 );
2307     cl_railrings_color = Cvar_Get( "cl_railrings_color", "0x0000FF", 0 );
2308     cl_railrings_width = Cvar_Get( "cl_railrings_width", "3", 0 );
2309 
2310     cl_disable_particles = Cvar_Get( "cl_disable_particles", "0", 0 );
2311 	cl_disable_explosions = Cvar_Get( "cl_disable_explosions", "0", 0 );
2312     cl_gibs = Cvar_Get( "cl_gibs", "1", 0 );
2313 	cl_gibs->changedFunc = CL_Gibs_OnChange;
2314 
2315     cl_chat_notify = Cvar_Get( "cl_chat_notify", "1", 0 );
2316     cl_chat_beep = Cvar_Get( "cl_chat_beep", "1", 0 );
2317 
2318     cl_protocol = Cvar_Get( "cl_protocol", "0", 0 );
2319 
2320 	info_hand->changedFunc = CL_Hand_OnChange;
2321     info_gender->modified = qfalse; // clear this so we know when user sets it manually
2322     gender_auto = Cvar_Get ( "gender_auto", "1", CVAR_ARCHIVE );
2323 
2324     cl_vwep = Cvar_Get ( "cl_vwep", "1", CVAR_ARCHIVE );
2325 
2326     //
2327     // register our commands
2328     //
2329     Cmd_AddCommand ( "cmd", CL_ForwardToServer_f );
2330     Cmd_AddCommand ( "pause", CL_Pause_f );
2331     Cmd_AddCommand ( "pingservers", CL_PingServers_f );
2332     Cmd_AddCommand ( "skins", CL_Skins_f );
2333 
2334     Cmd_AddCommand ( "userinfo", CL_Userinfo_f );
2335     Cmd_AddCommand ( "snd_restart", CL_Snd_Restart_f );
2336 
2337     Cmd_AddCommand ( "changing", CL_Changing_f );
2338     Cmd_AddCommand ( "disconnect", CL_Disconnect_f );
2339 
2340     Cmd_AddCommandEx ( "connect", CL_Connect_f, CL_Connect_g );
2341     Cmd_AddCommand ( "reconnect", CL_Reconnect_f );
2342 
2343     Cmd_AddCommand ( "rcon", CL_Rcon_f );
2344 
2345     // 	Cmd_AddCommand ("packet", CL_Packet_f); // this is dangerous to leave in
2346 
2347     Cmd_AddCommand ( "setenv", CL_Setenv_f );
2348 
2349     Cmd_AddCommand ( "precache", CL_Precache_f );
2350 
2351     Cmd_AddCommand ( "download", CL_Download_f );
2352 
2353     Cmd_AddCommandEx( "serverstatus", CL_ServerStatus_f, CL_Connect_g );
2354 
2355     Cmd_AddCommand( "dumpclients", CL_DumpClients_f );
2356 	Cmd_AddCommand( "dumpstatusbar", CL_DumpStatusbar_f );
2357 	Cmd_AddCommand( "dumplayout", CL_DumpLayout_f );
2358 
2359     Cmd_AddCommand( "vid_restart", CL_RestartRefresh_f );
2360     Cmd_AddCommand( "fs_restart", CL_RestartFilesystem_f );
2361 
2362 
2363     //
2364     // forward to server commands
2365     //
2366     // the only thing this does is allow command completion
2367     // to work -- all unknown commands are automatically
2368     // forwarded to the server
2369     Cmd_AddCommand ( "wave", NULL );
2370     Cmd_AddCommand ( "inven", NULL );
2371     Cmd_AddCommand ( "kill", NULL );
2372     Cmd_AddCommand ( "use", NULL );
2373     Cmd_AddCommand ( "drop", NULL );
2374     Cmd_AddCommand ( "say", NULL );
2375     Cmd_AddCommand ( "say_team", NULL );
2376     Cmd_AddCommand ( "info", NULL );
2377     Cmd_AddCommand ( "prog", NULL );
2378     Cmd_AddCommand ( "give", NULL );
2379     Cmd_AddCommand ( "god", NULL );
2380     Cmd_AddCommand ( "notarget", NULL );
2381     Cmd_AddCommand ( "noclip", NULL );
2382     Cmd_AddCommand ( "invuse", NULL );
2383     Cmd_AddCommand ( "invprev", NULL );
2384     Cmd_AddCommand ( "invnext", NULL );
2385     Cmd_AddCommand ( "invdrop", NULL );
2386     Cmd_AddCommand ( "weapnext", NULL );
2387     Cmd_AddCommand ( "weapprev", NULL );
2388     Cmd_AddCommand ( "vote", NULL );
2389     Cmd_AddCommand ( "observe", NULL );
2390 	Cmd_AddCommand ( "follow", NULL );
2391 	Cmd_AddCommand ( "time", NULL );
2392 	Cmd_AddCommand ( "playernext", NULL );
2393 	Cmd_AddCommand ( "playerprev", NULL );
2394 	Cmd_AddCommand ( "playertoggle", NULL );
2395 	Cmd_AddCommand ( "mvdls", NULL );
2396 	Cmd_AddCommand ( "mvddl", NULL );
2397 	Cmd_AddCommand ( "!mvdadmin", NULL );
2398 
2399     Cmd_AddMacro( "cl_mapname", CL_Mapname_m );
2400     Cmd_AddMacro( "cl_server", CL_Server_m );
2401     Cmd_AddMacro( "cl_demo", CL_DemoState_m );
2402 	Cmd_AddMacro( "cl_timer", CL_Timer_m );
2403 	Cmd_AddMacro( "cl_ups", CL_Ups_m );
2404 	Cmd_AddMacro( "cl_fps", CL_Fps_m );
2405 	Cmd_AddMacro( "cl_health", CL_Health_m );
2406 	Cmd_AddMacro( "cl_ammo", CL_Ammo_m );
2407 	Cmd_AddMacro( "cl_armor", CL_Armor_m );
2408 
2409 
2410 }
2411 
2412 /*
2413 ==================
2414 CL_CheatsOK
2415 ==================
2416 */
CL_CheatsOK(void)2417 qboolean CL_CheatsOK( void ) {
2418     if ( cls.state < ca_connected ) {
2419         return qtrue;
2420     }
2421 
2422     if ( cls.demoplayback ) {
2423         return qtrue;
2424     }
2425 
2426     // developer option
2427 	if ( sv_running->integer ) {
2428 		if( sv_running->integer != ss_game || Cvar_VariableInteger( "cheats" ) ) {
2429 			return qtrue;
2430 		}
2431     }
2432 
2433     // single player can cheat
2434     if ( atoi( cl.configstrings[ CS_MAXCLIENTS ] ) < 2 ) {
2435         return qtrue;
2436     }
2437 
2438     return qfalse;
2439 }
2440 
2441 //============================================================================
2442 
CL_AppActivate(qboolean active)2443 void CL_AppActivate( qboolean active ) {
2444 	cls.appactive = active;
2445     Key_ClearStates();
2446     CL_InputActivate( active );
2447     S_Activate( active );
2448     CDAudio_Activate( active );
2449 }
2450 
2451 /*
2452 ==================
2453 CL_SetClientTime
2454 ==================
2455 */
CL_SetClientTime(void)2456 static void CL_SetClientTime( void ) {
2457     int	prevtime;
2458 
2459     prevtime = cl.oldframe.serverFrame * 100;
2460     if ( prevtime >= cl.serverTime ) {
2461         /* this may happen on a very first frame */
2462         cl.lerpfrac = 0;
2463         return;
2464     }
2465 
2466     if ( cl.time > cl.serverTime ) {
2467         if ( cl_showclamp->integer )
2468             Com_Printf( "high clamp %i\n", cl.time - cl.serverTime );
2469         cl.time = cl.serverTime;
2470         cl.lerpfrac = 1.0;
2471     } else if ( cl.time < prevtime ) {
2472         if ( cl_showclamp->integer )
2473             Com_Printf( "low clamp %i\n", cl.serverTime - prevtime );
2474         cl.time = prevtime;
2475         cl.lerpfrac = 0;
2476     } else {
2477         cl.lerpfrac = ( float ) ( cl.time - prevtime ) / ( float ) ( cl.serverTime - prevtime );
2478     }
2479 
2480     if ( com_timedemo->integer ) {
2481         cl.lerpfrac = 1.0;
2482     }
2483 
2484     if ( cl_showclamp->integer == 2 )
2485         Com_Printf( "time %i, lerpfrac %.3f\n", cl.time, cl.lerpfrac );
2486 
2487 }
2488 
CL_MeasureFPS(void)2489 static void CL_MeasureFPS( void ) {
2490 	int time;
2491 
2492 	time = Sys_Milliseconds();
2493 	if( time - cls.measureTime > 1000 ) {
2494 		cls.measureTime = time;
2495 		cls.currentFPS = cls.measureFramecount;
2496 		cls.measureFramecount = 0;
2497 	}
2498 	cls.measureFramecount++;
2499 }
2500 
2501 /*
2502 ==================
2503 CL_Frame
2504 
2505 ==================
2506 */
CL_Frame(int msec)2507 void CL_Frame( int msec ) {
2508 	static int extratime;
2509 	static int extraphystime;
2510 	int minFrameTime, minPhysTime, delta;
2511 
2512 	time_after_ref = time_before_ref = 0;
2513 
2514 	if ( !cl_running->integer ) {
2515 		Cbuf_Execute();
2516         return;
2517 	}
2518 
2519 	extratime += msec;
2520 	cls.realtime += msec;
2521 
2522 	minFrameTime = 1;
2523 	if( cl_async->integer ) {
2524 		extraphystime += msec;
2525 
2526 		Cvar_ClampInteger( cl_maxfps, 10, 120 );
2527 		minPhysTime = 1000 / cl_maxfps->integer;
2528 		if( extraphystime >= minPhysTime ) {
2529 			CL_FinalizeCmd();
2530 			extraphystime = 0;
2531 		}
2532 
2533 		if( r_maxfps->integer ) {
2534 			if( r_maxfps->integer < 10 ) {
2535 				Cvar_Set( "r_maxfps", "10" );
2536 			}
2537 			minFrameTime = 1000 / r_maxfps->integer;
2538 		}
2539 	} else {
2540 		extraphystime = 0;
2541 		if( cl_maxfps->integer ) {
2542 			if( cl_maxfps->integer < 10 ) {
2543 				Cvar_Set( "cl_maxfps", "10" );
2544 			}
2545 			minFrameTime = 1000 / cl_maxfps->integer;
2546 		}
2547 	}
2548 
2549 	if( com_timedemo->integer ) {
2550 		minFrameTime = 1;
2551 	} else if( !cls.appactive && !sv_running->integer && com_sleep->integer ) {
2552 		// run at 10 fps if background app
2553 		minFrameTime = 100;
2554 	}
2555 
2556 	if( extratime < minFrameTime ) {
2557 		// always sleep in background, but
2558 		// never sleep if running a server
2559 		if( ( !cls.appactive || cls.state < ca_active ) &&
2560                 !sv_running->integer && com_sleep->integer )
2561         {
2562             if( com_sleep->integer > 1 ) {
2563     			delta = minFrameTime - extratime;
2564             } else {
2565                 delta = 1;
2566             }
2567 			Sys_Sleep( delta );
2568 		}
2569 		// send pending intentions
2570 		CL_SendCmd();
2571 		return;
2572 	}
2573 
2574     if ( cls.demoplayback ) {
2575         sv_paused->integer = cl_paused->integer; // FIXME: HACK
2576     }
2577 
2578     // decide the simulation time
2579     cls.frametime = extratime * 0.001f;
2580 
2581     if( cls.frametime > 1.0 / 5 )
2582         cls.frametime = 1.0 / 5;
2583 
2584 	// read next demo frame
2585 	CL_DemoFrame( extratime );
2586 
2587     // calculate local time
2588     CL_SetClientTime();
2589 
2590     CL_CheckForReply();
2591 
2592 	// read user intentions
2593 	CL_UpdateCmd( extratime );
2594 
2595 	// finalize them
2596 	if( !cl_async->integer ) {
2597 		CL_FinalizeCmd();
2598 	}
2599 
2600 	// send pending intentions
2601 	CL_SendCmd();
2602 
2603     // resend a connection request if necessary
2604     CL_CheckForResend();
2605 
2606     // predict all unacknowledged movements
2607     CL_PredictMovement();
2608 
2609     // update the screen
2610     if ( host_speeds->integer )
2611         time_before_ref = Sys_Milliseconds();
2612 
2613     SCR_UpdateScreen();
2614 
2615     if ( host_speeds->integer )
2616         time_after_ref = Sys_Milliseconds();
2617 
2618     // update audio
2619     S_Update();
2620 
2621     CDAudio_Update();
2622 
2623     // advance local effects for next frame
2624     CL_RunDLights();
2625     CL_RunLightStyles();
2626     SCR_RunCinematic();
2627     Con_RunConsole();
2628 
2629 	CL_MeasureFPS();
2630 
2631 	if( vid_autorestart->integer && ( cvar_latchedModified & ( 1 << CVAR_SYSTEM_VIDEO ) ) ) {
2632 		Cbuf_ExecuteText( EXEC_NOW, "vid_restart\n" );
2633 	}
2634 
2635     //
2636     // check timeout
2637     //
2638     if( cls.netchan && Sys_Milliseconds() - cls.netchan->last_received > cl_timeout->value * 1000 ) {
2639         // timeoutcount saves debugger
2640         if ( ++cl.timeoutcount > 5 ) {
2641             Com_Error( ERR_DISCONNECT, "Server connection timed out." );
2642         }
2643     } else {
2644         cl.timeoutcount = 0;
2645     }
2646 
2647 	extratime = 0;
2648     cls.framecount++;
2649 
2650 }
2651 
2652 
2653 //============================================================================
2654 
2655 /*
2656 ====================
2657 CL_CheckForPCMD
2658 ====================
2659 */
CL_CheckForPCMD(int level,const char * string)2660 void CL_CheckForPCMD( int level, const char *string ) {
2661     char * p;
2662 
2663     if ( cls.demoplayback ) {
2664         return;
2665     }
2666 
2667     if ( level != PRINT_CHAT ) {
2668         return;
2669     }
2670 
2671     p = strstr( string, ": " );
2672     if ( !p ) {
2673         return;
2674     }
2675 
2676     if ( strncmp( p + 2, "!version", 8 ) ) {
2677         return;
2678     }
2679 
2680     if ( cl.replyTime && cls.realtime - cl.replyTime < 120000 ) {
2681         return;
2682     }
2683 
2684 	Cvar_VariableStringBuffer( "version", cl.replyString, sizeof( cl.replyString ) );
2685     cl.replyTime = cls.realtime + 1024 + ( rand() & 1023 );
2686 }
2687 
2688 /*
2689 ====================
2690 CL_CheckForReply
2691 ====================
2692 */
CL_CheckForReply(void)2693 void CL_CheckForReply( void ) {
2694     if ( !cl.replyString[ 0 ] ) {
2695         return;
2696     }
2697 
2698     if ( cls.realtime < cl.replyTime ) {
2699         return;
2700     }
2701 
2702     Cbuf_AddText( "cmd say \"" );
2703     Cbuf_AddText( cl.replyString );
2704     Cbuf_AddText( "\"\n" );
2705 
2706     cl.replyString[ 0 ] = 0;
2707 }
2708 
2709 /*
2710 ====================
2711 CL_Init
2712 ====================
2713 */
CL_Init(void)2714 void CL_Init( void ) {
2715     if ( dedicated->integer ) {
2716         return; // nothing running on the client
2717     }
2718 
2719     if ( cl_running->integer ) {
2720         return;
2721     }
2722 
2723     // all archived variables will now be loaded
2724 
2725 #if defined __unix__ || defined __sgi
2726     S_Init();
2727     CL_InitRefresh();
2728 #else
2729 
2730     CL_InitRefresh();
2731     S_Init();	// sound must be initialized after window is created
2732 #endif
2733 
2734     V_Init();
2735     SCR_Init();
2736     CDAudio_Init();
2737     CL_InitLocal();
2738     CL_InitInput();
2739     SCR_RegisterMedia();
2740     Con_SetupDC();
2741     CL_InitUI();
2742 
2743     UI_OpenMenu( UIMENU_MAIN );
2744 
2745     Con_RunConsole();
2746 
2747     Cvar_Set( "cl_running", "1" );
2748 }
2749 
2750 
2751 /*
2752 ===============
2753 CL_Shutdown
2754 
2755 FIXME: this is a callback from Sys_Quit and Com_Error.  It would be better
2756 to run quit through here before the final handoff to the sys code.
2757 ===============
2758 */
CL_Shutdown(void)2759 void CL_Shutdown( void ) {
2760     static qboolean isdown = qfalse;
2761 
2762     if ( isdown ) {
2763         Com_Printf( "CL_Shutdown: recursive shutdown\n" );
2764         return;
2765     }
2766     isdown = qtrue;
2767 
2768     CL_Disconnect( ERR_SILENT, NULL );
2769 
2770     CL_ShutdownUI();
2771     CDAudio_Shutdown();
2772     S_Shutdown();
2773     CL_ShutdownInput();
2774     Con_Shutdown();
2775     CL_ShutdownRefresh();
2776 
2777     Cvar_Set( "cl_running", "0" );
2778 
2779     isdown = qfalse;
2780 }
2781