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