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