1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 */
20 // cl_main.c  -- client main loop
21 
22 #include <ctype.h>
23 
24 #ifdef _WIN32
25 #include <windows.h>
26 #include <winsock2.h>
27 #include "winquake.h"
28 #else
29 #include <sys/types.h>
30 #include <netinet/in.h>
31 #endif
32 
33 #include "cdaudio.h"
34 #include "client.h"
35 #include "cmd.h"
36 #include "console.h"
37 #include "draw.h"
38 #include "input.h"
39 #include "keys.h"
40 #include "menu.h"
41 #include "model.h"
42 #include "pmove.h"
43 #include "quakedef.h"
44 #include "sbar.h"
45 #include "screen.h"
46 #include "sys.h"
47 #include "view.h"
48 
49 #include "d_iface.h"
50 
51 /* Argument completion function for the skin cvar */
52 static struct stree_root * CL_Skin_Arg_f(const char *arg);
53 
54 // FIXME - header hacks
55 extern cvar_t cl_hightrack;
56 extern cvar_t baseskin;
57 extern cvar_t noskins;
58 
59 // we need to declare some mouse variables here, because the menu system
60 // references them even when on a unix system.
61 
62 qboolean noclip_anglehack;	// remnant from old quake
63 
64 
65 cvar_t rcon_password = { "rcon_password", "", false };
66 
67 cvar_t rcon_address = { "rcon_address", "" };
68 
69 cvar_t cl_timeout = { "cl_timeout", "60" };
70 
71 cvar_t cl_shownet = { "cl_shownet", "0" };	// can be 0, 1, or 2
72 
73 cvar_t cl_sbar = { "cl_sbar", "0", true };
74 cvar_t cl_hudswap = { "cl_hudswap", "0", true };
75 cvar_t cl_maxfps = { "cl_maxfps", "0", true };
76 
77 cvar_t lookspring = { "lookspring", "0", true };
78 cvar_t lookstrafe = { "lookstrafe", "0", true };
79 cvar_t sensitivity = { "sensitivity", "3", true };
80 
81 cvar_t m_pitch = { "m_pitch", "0.022", true };
82 cvar_t m_yaw = { "m_yaw", "0.022" };
83 cvar_t m_forward = { "m_forward", "1" };
84 cvar_t m_side = { "m_side", "0.8" };
85 
86 cvar_t entlatency = { "entlatency", "20" };
87 cvar_t cl_predict_players = { "cl_predict_players", "1" };
88 cvar_t cl_predict_players2 = { "cl_predict_players2", "1" };
89 cvar_t cl_solid_players = { "cl_solid_players", "1" };
90 
91 cvar_t localid = { "localid", "" };
92 
93 static qboolean allowremotecmd = true;
94 
95 //
96 // info mirrors
97 //
98 cvar_t password = { "password", "", false, true };
99 cvar_t spectator = { "spectator", "", false, true };
100 cvar_t name = { "name", "unnamed", true, true };
101 cvar_t team = { "team", "", true, true };
102 cvar_t topcolor = { "topcolor", "0", true, true };
103 cvar_t bottomcolor = { "bottomcolor", "0", true, true };
104 cvar_t rate = { "rate", "2500", true, true };
105 cvar_t noaim = { "noaim", "0", true, true };
106 cvar_t msg = { "msg", "1", true, true };
107 
108 cvar_t skin = {
109     .name = "skin",
110     .string = "",
111     .archive = true,
112     .info = true,
113     .completion = CL_Skin_Arg_f
114 };
115 
116 
117 client_static_t cls;
118 client_state_t cl;
119 
120 entity_state_t cl_baselines[MAX_EDICTS];
121 efrag_t cl_efrags[MAX_EFRAGS];
122 entity_t cl_static_entities[MAX_STATIC_ENTITIES];
123 lightstyle_t cl_lightstyle[MAX_LIGHTSTYLES];
124 dlight_t cl_dlights[MAX_DLIGHTS];
125 
126 double connect_time = -1;	// for connection retransmits
127 
128 quakeparms_t host_parms;
129 
130 qboolean host_initialized;	// true if into command execution
131 
132 double host_frametime;
133 double realtime;		// without any filtering or bounding
134 static double oldrealtime;	// last frame run
135 int host_framecount;
136 
137 int host_hunklevel;
138 
139 int minimum_memory;
140 
141 byte *host_basepal;
142 byte *host_colormap;
143 
144 netadr_t master_adr;		// address of the master server
145 
146 cvar_t host_speeds = { "host_speeds", "0" };	// set for running times
147 cvar_t developer = { "developer", "0" };
148 
149 int fps_count;
150 
151 static jmp_buf host_abort;
152 static float server_version = 0;// version of server we connected to
153 
154 /*
155 ==================
156 CL_Quit_f
157 ==================
158 */
159 void
CL_Quit_f(void)160 CL_Quit_f(void)
161 {
162     if (1 /* key_dest != key_console *//* && cls.state != ca_dedicated */ ) {
163 	M_Menu_Quit_f();
164 	return;
165     }
166     CL_Disconnect();
167     Sys_Quit();
168 }
169 
170 /*
171 =======================
172 CL_Version_f
173 ======================
174 */
175 void
CL_Version_f(void)176 CL_Version_f(void)
177 {
178     Con_Printf("Version TyrQuake-%s\n", stringify(TYR_VERSION));
179     Con_Printf("Exe: " __TIME__ " " __DATE__ "\n");
180 }
181 
182 
183 /*
184 =======================
185 CL_SendConnectPacket
186 
187 called by CL_Connect_f and CL_CheckResend
188 ======================
189 */
190 void
CL_SendConnectPacket(void)191 CL_SendConnectPacket(void)
192 {
193     netadr_t adr;
194     char data[2048];
195     double t1, t2;
196 
197 // JACK: Fixed bug where DNS lookups would cause two connects real fast
198 //       Now, adds lookup time to the connect time.
199 //               Should I add it to realtime instead?!?!
200 
201     if (cls.state != ca_disconnected)
202 	return;
203 
204     t1 = Sys_DoubleTime();
205 
206     if (!NET_StringToAdr(cls.servername, &adr)) {
207 	Con_Printf("Bad server address\n");
208 	connect_time = -1;
209 	return;
210     }
211 
212     if (adr.port == 0)
213 	adr.port = BigShort(27500);
214     t2 = Sys_DoubleTime();
215 
216     connect_time = realtime + t2 - t1;	// for retransmit requests
217 
218     cls.qport = Cvar_VariableValue("qport");
219 
220     Info_SetValueForStarKey(cls.userinfo, "*ip", NET_AdrToString(adr),
221 			    MAX_INFO_STRING);
222 
223 //      Con_Printf ("Connecting to %s...\n", cls.servername);
224     sprintf(data, "%c%c%c%cconnect %i %i %i \"%s\"\n",
225 	    255, 255, 255, 255, PROTOCOL_VERSION, cls.qport, cls.challenge,
226 	    cls.userinfo);
227     NET_SendPacket(strlen(data), data, adr);
228 }
229 
230 /*
231 =================
232 CL_CheckForResend
233 
234 Resend a connect message if the last one has timed out
235 
236 =================
237 */
238 void
CL_CheckForResend(void)239 CL_CheckForResend(void)
240 {
241     netadr_t adr;
242     char data[2048];
243     double t1, t2;
244 
245     if (connect_time == -1)
246 	return;
247     if (cls.state != ca_disconnected)
248 	return;
249     if (connect_time && realtime - connect_time < 5.0)
250 	return;
251 
252     t1 = Sys_DoubleTime();
253     if (!NET_StringToAdr(cls.servername, &adr)) {
254 	Con_Printf("Bad server address\n");
255 	connect_time = -1;
256 	return;
257     }
258 
259     if (adr.port == 0)
260 	adr.port = BigShort(27500);
261     t2 = Sys_DoubleTime();
262 
263     connect_time = realtime + t2 - t1;	// for retransmit requests
264 
265     Con_Printf("Connecting to %s...\n", cls.servername);
266     sprintf(data, "%c%c%c%cgetchallenge\n", 255, 255, 255, 255);
267     NET_SendPacket(strlen(data), data, adr);
268 }
269 
270 void
CL_BeginServerConnect(void)271 CL_BeginServerConnect(void)
272 {
273     connect_time = 0;
274     CL_CheckForResend();
275 }
276 
277 /*
278 ================
279 CL_Connect_f
280 
281 ================
282 */
283 void
CL_Connect_f(void)284 CL_Connect_f(void)
285 {
286     const char *server;
287 
288     if (Cmd_Argc() != 2) {
289 	Con_Printf("usage: connect <server>\n");
290 	return;
291     }
292 
293     server = Cmd_Argv(1);
294 
295     CL_Disconnect();
296 
297     strncpy(cls.servername, server, sizeof(cls.servername) - 1);
298     CL_BeginServerConnect();
299 }
300 
301 
302 /*
303 =====================
304 CL_Rcon_f
305 
306   Send the rest of the command line over as
307   an unconnected command.
308 =====================
309 */
310 void
CL_Rcon_f(void)311 CL_Rcon_f(void)
312 {
313     char message[1024];
314     int i;
315     netadr_t to;
316 
317     if (!rcon_password.string) {
318 	Con_Printf("You must set 'rcon_password' before\n"
319 		   "issuing an rcon command.\n");
320 	return;
321     }
322 
323     message[0] = 255;
324     message[1] = 255;
325     message[2] = 255;
326     message[3] = 255;
327     message[4] = 0;
328 
329     strcat(message, "rcon ");
330 
331     strcat(message, rcon_password.string);
332     strcat(message, " ");
333 
334     for (i = 1; i < Cmd_Argc(); i++) {
335 	strcat(message, Cmd_Argv(i));
336 	strcat(message, " ");
337     }
338 
339     if (cls.state >= ca_connected)
340 	to = cls.netchan.remote_address;
341     else {
342 	if (!strlen(rcon_address.string)) {
343 	    Con_Printf("You must either be connected,\n"
344 		       "or set the 'rcon_address' cvar\n"
345 		       "to issue rcon commands\n");
346 
347 	    return;
348 	}
349 	NET_StringToAdr(rcon_address.string, &to);
350     }
351 
352     NET_SendPacket(strlen(message) + 1, message, to);
353 }
354 
355 /*
356 =====================
357 CL_ClearState
358 
359 =====================
360 */
361 void
CL_ClearState(void)362 CL_ClearState(void)
363 {
364     int i;
365 
366     S_StopAllSounds(true);
367 
368     Con_DPrintf("Clearing memory\n");
369     D_FlushCaches();
370     Mod_ClearAll();
371     if (host_hunklevel)		// FIXME: check this...
372 	Hunk_FreeToLowMark(host_hunklevel);
373 
374     CL_ClearTEnts();
375 
376 // wipe the entire cl structure
377     memset(&cl, 0, sizeof(cl));
378 
379     SZ_Clear(&cls.netchan.message);
380 
381 // clear other arrays
382     memset(cl_efrags, 0, sizeof(cl_efrags));
383     memset(cl_dlights, 0, sizeof(cl_dlights));
384     memset(cl_lightstyle, 0, sizeof(cl_lightstyle));
385 
386 //
387 // allocate the efrags and chain together into a free list
388 //
389     cl.free_efrags = cl_efrags;
390     for (i = 0; i < MAX_EFRAGS - 1; i++)
391 	cl.free_efrags[i].entnext = &cl.free_efrags[i + 1];
392     cl.free_efrags[i].entnext = NULL;
393 }
394 
395 /*
396 =====================
397 CL_Disconnect
398 
399 Sends a disconnect message to the server
400 This is also called on Host_Error, so it shouldn't cause any errors
401 =====================
402 */
403 void
CL_Disconnect(void)404 CL_Disconnect(void)
405 {
406     byte final[10];
407 
408     connect_time = -1;
409 
410 #ifdef _WIN32
411     SetWindowText(mainwindow, "QuakeWorld: disconnected");
412 #endif
413 
414 // stop sounds (especially looping!)
415     S_StopAllSounds(true);
416 
417 // if running a local server, shut it down
418     if (cls.demoplayback)
419 	CL_StopPlayback();
420     else if (cls.state != ca_disconnected) {
421 	if (cls.demorecording)
422 	    CL_Stop_f();
423 
424 	final[0] = clc_stringcmd;
425 	strcpy((char *)final + 1, "drop");
426 	Netchan_Transmit(&cls.netchan, 6, final);
427 	Netchan_Transmit(&cls.netchan, 6, final);
428 	Netchan_Transmit(&cls.netchan, 6, final);
429 
430 	cls.state = ca_disconnected;
431 
432 	cls.demoplayback = cls.demorecording = cls.timedemo = false;
433     }
434     Cam_Reset();
435 
436     if (cls.download) {
437 	fclose(cls.download);
438 	cls.download = NULL;
439     }
440 
441     CL_StopUpload();
442 
443 }
444 
445 void
CL_Disconnect_f(void)446 CL_Disconnect_f(void)
447 {
448     CL_Disconnect();
449 }
450 
451 /*
452 ====================
453 CL_User_f
454 
455 user <name or userid>
456 
457 Dump userdata / masterdata for a user
458 ====================
459 */
460 void
CL_User_f(void)461 CL_User_f(void)
462 {
463     int uid;
464     int i;
465 
466     if (Cmd_Argc() != 2) {
467 	Con_Printf("Usage: user <username / userid>\n");
468 	return;
469     }
470 
471     uid = atoi(Cmd_Argv(1));
472 
473     for (i = 0; i < MAX_CLIENTS; i++) {
474 	if (!cl.players[i].name[0])
475 	    continue;
476 	if (cl.players[i].userid == uid
477 	    || !strcmp(cl.players[i].name, Cmd_Argv(1))) {
478 	    Info_Print(cl.players[i].userinfo);
479 	    return;
480 	}
481     }
482     Con_Printf("User not in server.\n");
483 }
484 
485 /*
486 ====================
487 CL_Users_f
488 
489 Dump userids for all current players
490 ====================
491 */
492 void
CL_Users_f(void)493 CL_Users_f(void)
494 {
495     int i;
496     int c;
497 
498     c = 0;
499     Con_Printf("userid frags name\n");
500     Con_Printf("------ ----- ----\n");
501     for (i = 0; i < MAX_CLIENTS; i++) {
502 	if (cl.players[i].name[0]) {
503 	    Con_Printf("%6i %4i %s\n", cl.players[i].userid,
504 		       cl.players[i].frags, cl.players[i].name);
505 	    c++;
506 	}
507     }
508 
509     Con_Printf("%i total users\n", c);
510 }
511 
512 void
CL_Color_f(void)513 CL_Color_f(void)
514 {
515     // just for quake compatability...
516     int top, bottom;
517     char num[16];
518 
519     if (Cmd_Argc() == 1) {
520 	Con_Printf("\"color\" is \"%s %s\"\n",
521 		   Info_ValueForKey(cls.userinfo, "topcolor"),
522 		   Info_ValueForKey(cls.userinfo, "bottomcolor"));
523 	Con_Printf("color <0-13> [0-13]\n");
524 	return;
525     }
526 
527     if (Cmd_Argc() == 2)
528 	top = bottom = atoi(Cmd_Argv(1));
529     else {
530 	top = atoi(Cmd_Argv(1));
531 	bottom = atoi(Cmd_Argv(2));
532     }
533 
534     top &= 15;
535     if (top > 13)
536 	top = 13;
537     bottom &= 15;
538     if (bottom > 13)
539 	bottom = 13;
540 
541     sprintf(num, "%i", top);
542     Cvar_Set("topcolor", num);
543     sprintf(num, "%i", bottom);
544     Cvar_Set("bottomcolor", num);
545 }
546 
547 /*
548 ==================
549 CL_FullServerinfo_f
550 
551 Sent by server when serverinfo changes
552 ==================
553 */
554 void
CL_FullServerinfo_f(void)555 CL_FullServerinfo_f(void)
556 {
557     char *p;
558     float v;
559 
560     if (Cmd_Argc() != 2) {
561 	Con_Printf("usage: fullserverinfo <complete info string>\n");
562 	return;
563     }
564 
565     strcpy(cl.serverinfo, Cmd_Argv(1));
566 
567     if ((p = Info_ValueForKey(cl.serverinfo, "*vesion")) && *p) {
568 	v = Q_atof(p);
569 	if (v) {
570 	    if (!server_version)
571 		Con_Printf("Version %1.2f Server\n", v);
572 	    server_version = v;
573 	}
574     }
575 }
576 
577 /*
578 ==================
579 CL_FullInfo_f
580 
581 Allow clients to change userinfo
582 ==================
583 Casey was here :)
584 */
585 void
CL_FullInfo_f(void)586 CL_FullInfo_f(void)
587 {
588     char key[512];
589     char value[512];
590     char *o;
591     const char *s;
592 
593     if (Cmd_Argc() != 2) {
594 	Con_Printf("fullinfo <complete info string>\n");
595 	return;
596     }
597 
598     s = Cmd_Argv(1);
599     if (*s == '\\')
600 	s++;
601     while (*s) {
602 	o = key;
603 	while (*s && *s != '\\')
604 	    *o++ = *s++;
605 	*o = 0;
606 
607 	if (!*s) {
608 	    Con_Printf("MISSING VALUE\n");
609 	    return;
610 	}
611 
612 	o = value;
613 	s++;
614 	while (*s && *s != '\\')
615 	    *o++ = *s++;
616 	*o = 0;
617 
618 	if (*s)
619 	    s++;
620 
621 	if (!strcasecmp(key, "pmodel") || !strcasecmp(key, "emodel"))
622 	    continue;
623 
624 	Info_SetValueForKey(cls.userinfo, key, value, MAX_INFO_STRING);
625     }
626 }
627 
628 /*
629 ==================
630 CL_SetInfo_f
631 
632 Allow clients to change userinfo
633 ==================
634 */
635 void
CL_SetInfo_f(void)636 CL_SetInfo_f(void)
637 {
638     if (Cmd_Argc() == 1) {
639 	Info_Print(cls.userinfo);
640 	return;
641     }
642     if (Cmd_Argc() != 3) {
643 	Con_Printf("usage: setinfo [ <key> <value> ]\n");
644 	return;
645     }
646     if (!strcasecmp(Cmd_Argv(1), "pmodel") || !strcmp(Cmd_Argv(1), "emodel"))
647 	return;
648 
649     Info_SetValueForKey(cls.userinfo, Cmd_Argv(1), Cmd_Argv(2),
650 			MAX_INFO_STRING);
651     if (cls.state >= ca_connected)
652 	Cmd_ForwardToServer();
653 }
654 
655 /*
656 ====================
657 CL_Packet_f
658 
659 packet <destination> <contents>
660 
661 Contents allows \n escape character
662 ====================
663 */
664 void
CL_Packet_f(void)665 CL_Packet_f(void)
666 {
667     char send[2048];
668     int i, l;
669     const char *in;
670     char *out;
671     netadr_t adr;
672 
673     if (Cmd_Argc() != 3) {
674 	Con_Printf("packet <destination> <contents>\n");
675 	return;
676     }
677 
678     if (!NET_StringToAdr(Cmd_Argv(1), &adr)) {
679 	Con_Printf("Bad address\n");
680 	return;
681     }
682 
683     in = Cmd_Argv(2);
684     out = send + 4;
685     send[0] = send[1] = send[2] = send[3] = 0xff;
686 
687     l = strlen(in);
688     for (i = 0; i < l; i++) {
689 	if (in[i] == '\\' && in[i + 1] == 'n') {
690 	    *out++ = '\n';
691 	    i++;
692 	} else
693 	    *out++ = in[i];
694     }
695     *out = 0;
696 
697     NET_SendPacket(out - send, send, adr);
698 }
699 
700 
701 /*
702 =====================
703 CL_NextDemo
704 
705 Called to play the next demo in the demo loop
706 =====================
707 */
708 void
CL_NextDemo(void)709 CL_NextDemo(void)
710 {
711     char str[1024];
712 
713     if (cls.demonum == -1)
714 	return;			// don't play demos
715 
716     if (!cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS) {
717 	cls.demonum = 0;
718 	if (!cls.demos[cls.demonum][0]) {
719 //                      Con_Printf ("No demos listed with startdemos\n");
720 	    cls.demonum = -1;
721 	    return;
722 	}
723     }
724 
725     sprintf(str, "playdemo %s\n", cls.demos[cls.demonum]);
726     Cbuf_InsertText(str);
727     cls.demonum++;
728 }
729 
730 
731 /*
732 =================
733 CL_Changing_f
734 
735 Just sent as a hint to the client that they should
736 drop to full console
737 =================
738 */
739 void
CL_Changing_f(void)740 CL_Changing_f(void)
741 {
742     if (cls.download)		// don't change when downloading
743 	return;
744 
745     S_StopAllSounds(true);
746     cl.intermission = 0;
747     cls.state = ca_connected;	// not active anymore, but not disconnected
748     Con_Printf("\nChanging map...\n");
749 }
750 
751 
752 /*
753 =================
754 CL_Reconnect_f
755 
756 The server is changing levels
757 =================
758 */
759 void
CL_Reconnect_f(void)760 CL_Reconnect_f(void)
761 {
762     if (cls.download)		// don't change when downloading
763 	return;
764 
765     S_StopAllSounds(true);
766 
767     if (cls.state == ca_connected) {
768 	Con_Printf("reconnecting...\n");
769 	MSG_WriteChar(&cls.netchan.message, clc_stringcmd);
770 	MSG_WriteString(&cls.netchan.message, "new");
771 	return;
772     }
773 
774     if (!*cls.servername) {
775 	Con_Printf("No server to reconnect to...\n");
776 	return;
777     }
778 
779     CL_Disconnect();
780     CL_BeginServerConnect();
781 }
782 
783 /*
784 =================
785 CL_ConnectionlessPacket
786 
787 Responses to broadcasts, etc
788 =================
789 */
790 void
CL_ConnectionlessPacket(void)791 CL_ConnectionlessPacket(void)
792 {
793     char *cmdtext, *idstring;
794     int c;
795 
796     MSG_BeginReading();
797     MSG_ReadLong();		// skip the -1
798 
799     c = MSG_ReadByte();
800     if (!cls.demoplayback)
801 	Con_Printf("%s: ", NET_AdrToString(net_from));
802 //      Con_DPrintf ("%s", net_message.data + 5);
803     if (c == S2C_CONNECTION) {
804 	Con_Printf("connection\n");
805 	if (cls.state >= ca_connected) {
806 	    if (!cls.demoplayback)
807 		Con_Printf("Dup connect received.  Ignored.\n");
808 	    return;
809 	}
810 	Netchan_Setup(&cls.netchan, net_from, cls.qport);
811 	MSG_WriteChar(&cls.netchan.message, clc_stringcmd);
812 	MSG_WriteString(&cls.netchan.message, "new");
813 	cls.state = ca_connected;
814 	Con_Printf("Connected.\n");
815 	allowremotecmd = false;	// localid required now for remote cmds
816 	return;
817     }
818     // remote command from gui front end
819     if (c == A2C_CLIENT_COMMAND) {
820 	Con_Printf("client command\n");
821 
822 	if (net_from.ip.l != net_local_adr.ip.l
823 	    && net_from.ip.l != htonl(INADDR_LOOPBACK)) {
824 	    Con_Printf("Command packet from remote host.  Ignored.\n");
825 	    return;
826 	}
827 #ifdef _WIN32
828 	ShowWindow(mainwindow, SW_RESTORE);
829 	SetForegroundWindow(mainwindow);
830 #endif
831 	cmdtext = MSG_ReadString();
832 	idstring = MSG_ReadString();
833 
834 	/* Strip leading and trailing spaces */
835 	while (*idstring && isspace(*idstring))
836 	    idstring++;
837 	while (*idstring && isspace(idstring[strlen(idstring) - 1]))
838 	    idstring[strlen(idstring) - 1] = 0;
839 
840 	if (!allowremotecmd && !*localid.string) {
841 	    Con_Printf("===========================\n"
842 		       "Command packet received from local host, but no "
843 		       "localid has been set.  You may need to upgrade your "
844 		       "server browser.\n"
845 		       "===========================\n");
846 	    return;
847 	}
848 	if (!allowremotecmd && strcmp(localid.string, idstring)) {
849 	    Con_Printf("===========================\n"
850 		       "Invalid localid on command packet received from local "
851 		       "host.\n"
852 		       " |%s| != |%s|\n"
853 		       "You may need to reload your server browser and "
854 		       "QuakeWorld.\n"
855 		       "===========================\n",
856 		       idstring, localid.string);
857 	    Cvar_Set("localid", "");
858 	    return;
859 	}
860 
861 	Cbuf_AddText("%s", cmdtext);
862 	allowremotecmd = false;
863 	return;
864     }
865     // print command from somewhere
866     if (c == A2C_PRINT) {
867 	Con_Printf("print\n");
868 	Con_Print(MSG_ReadString());
869 	return;
870     }
871     // ping from somewhere
872     if (c == A2A_PING) {
873 	char data[6];
874 
875 	Con_Printf("ping\n");
876 
877 	data[0] = 0xff;
878 	data[1] = 0xff;
879 	data[2] = 0xff;
880 	data[3] = 0xff;
881 	data[4] = A2A_ACK;
882 	data[5] = 0;
883 
884 	NET_SendPacket(6, &data, net_from);
885 	return;
886     }
887 
888     if (c == S2C_CHALLENGE) {
889 	Con_Printf("challenge\n");
890 	cls.challenge = atoi(MSG_ReadString());
891 	CL_SendConnectPacket();
892 	return;
893     }
894 #if 0
895     if (c == svc_disconnect) {
896 	Con_Printf("disconnect\n");
897 	Host_EndGame("Server disconnected");
898     }
899 #endif
900 
901     Con_Printf("unknown:  %c\n", c);
902 }
903 
904 
905 /*
906 =================
907 CL_ReadPackets
908 =================
909 */
910 void
CL_ReadPackets(void)911 CL_ReadPackets(void)
912 {
913     while (CL_GetMessage()) {
914 	//
915 	// remote command packet
916 	//
917 	if (*(int *)net_message.data == -1) {
918 	    CL_ConnectionlessPacket();
919 	    continue;
920 	}
921 
922 	if (net_message.cursize < 8) {
923 	    Con_Printf("%s: Runt packet\n", NET_AdrToString(net_from));
924 	    continue;
925 	}
926 	//
927 	// packet from server
928 	//
929 	if (!cls.demoplayback &&
930 	    !NET_CompareAdr(net_from, cls.netchan.remote_address)) {
931 	    Con_DPrintf("%s:sequenced packet without connection\n",
932 			NET_AdrToString(net_from));
933 	    continue;
934 	}
935 	if (!Netchan_Process(&cls.netchan))
936 	    continue;		// wasn't accepted for some reason
937 	CL_ParseServerMessage();
938 
939 //              if (cls.demoplayback && cls.state >= ca_active && !CL_DemoBehind())
940 //                      return;
941     }
942 
943     //
944     // check timeout
945     //
946     if (cls.state >= ca_connected
947 	&& realtime - cls.netchan.last_received > cl_timeout.value) {
948 	Con_Printf("\nServer connection timed out.\n");
949 	CL_Disconnect();
950 	return;
951     }
952 
953 }
954 
955 //=============================================================================
956 
957 /*
958 =====================
959 CL_Download_f
960 =====================
961 */
962 void
CL_Download_f(void)963 CL_Download_f(void)
964 {
965     char *p, *q;
966 
967     if (cls.state == ca_disconnected) {
968 	Con_Printf("Must be connected.\n");
969 	return;
970     }
971 
972     if (Cmd_Argc() != 2) {
973 	Con_Printf("Usage: download <datafile>\n");
974 	return;
975     }
976 
977     sprintf(cls.downloadname, "%s/%s", com_gamedir, Cmd_Argv(1));
978 
979     p = cls.downloadname;
980     for (;;) {
981 	if ((q = strchr(p, '/')) != NULL) {
982 	    *q = 0;
983 	    Sys_mkdir(cls.downloadname);
984 	    *q = '/';
985 	    p = q + 1;
986 	} else
987 	    break;
988     }
989 
990     strcpy(cls.downloadtempname, cls.downloadname);
991     cls.download = fopen(cls.downloadname, "wb");
992     cls.downloadtype = dl_single;
993 
994     MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
995     MSG_WriteStringf(&cls.netchan.message, "download %s\n", Cmd_Argv(1));
996 }
997 
998 /* FIXME - more hacks... */
999 #ifdef _WIN32
1000 #include <windows.h>
1001 /*
1002 =================
1003 CL_Minimize_f
1004 =================
1005 */
1006 void
CL_Windows_f(void)1007 CL_Windows_f(void)
1008 {
1009     SendMessage(mainwindow, WM_SYSKEYUP, VK_TAB, 1 | (0x0F << 16) | (1 << 29));
1010 }
1011 #endif
1012 
1013 static struct stree_root *
CL_Skin_Arg_f(const char * arg)1014 CL_Skin_Arg_f(const char *arg)
1015 {
1016     struct stree_root *root;
1017 
1018     root = Z_Malloc(sizeof(struct stree_root));
1019     if (root) {
1020 	*root = STREE_ROOT;
1021 	STree_AllocInit();
1022 	COM_ScanDir(root, "skins", arg, ".pcx", true);
1023     }
1024 
1025     return root;
1026 }
1027 
1028 
1029 /*
1030 =================
1031 CL_Init
1032 =================
1033 */
1034 void
CL_Init(void)1035 CL_Init(void)
1036 {
1037     char st[80];
1038 
1039     cls.state = ca_disconnected;
1040 
1041     Info_SetValueForKey(cls.userinfo, "name", "unnamed", MAX_INFO_STRING);
1042     Info_SetValueForKey(cls.userinfo, "topcolor", "0", MAX_INFO_STRING);
1043     Info_SetValueForKey(cls.userinfo, "bottomcolor", "0", MAX_INFO_STRING);
1044     Info_SetValueForKey(cls.userinfo, "rate", "2500", MAX_INFO_STRING);
1045     Info_SetValueForKey(cls.userinfo, "msg", "1", MAX_INFO_STRING);
1046     sprintf(st, "TyrQuake-%s", stringify(TYR_VERSION));
1047     Info_SetValueForStarKey(cls.userinfo, "*ver", st, MAX_INFO_STRING);
1048 
1049     CL_InitInput();
1050     CL_InitTEnts();
1051     CL_InitPrediction();
1052     CL_InitCam();
1053     Pmove_Init();
1054 
1055 //
1056 // register our commands
1057 //
1058     Cvar_RegisterVariable(&host_speeds);
1059     Cvar_RegisterVariable(&cl_warncmd);
1060     Cvar_RegisterVariable(&cl_upspeed);
1061     Cvar_RegisterVariable(&cl_forwardspeed);
1062     Cvar_RegisterVariable(&cl_backspeed);
1063     Cvar_RegisterVariable(&cl_sidespeed);
1064     Cvar_RegisterVariable(&cl_movespeedkey);
1065     Cvar_RegisterVariable(&cl_yawspeed);
1066     Cvar_RegisterVariable(&cl_pitchspeed);
1067     Cvar_RegisterVariable(&cl_anglespeedkey);
1068     Cvar_RegisterVariable(&cl_shownet);
1069     Cvar_RegisterVariable(&cl_sbar);
1070     Cvar_RegisterVariable(&cl_hudswap);
1071     Cvar_RegisterVariable(&cl_maxfps);
1072     Cvar_RegisterVariable(&cl_timeout);
1073     Cvar_RegisterVariable(&lookspring);
1074     Cvar_RegisterVariable(&lookstrafe);
1075     Cvar_RegisterVariable(&sensitivity);
1076 
1077     Cvar_RegisterVariable(&m_pitch);
1078     Cvar_RegisterVariable(&m_yaw);
1079     Cvar_RegisterVariable(&m_forward);
1080     Cvar_RegisterVariable(&m_side);
1081 
1082     Cvar_RegisterVariable(&rcon_password);
1083     Cvar_RegisterVariable(&rcon_address);
1084 
1085     Cvar_RegisterVariable(&entlatency);
1086     Cvar_RegisterVariable(&cl_predict_players2);
1087     Cvar_RegisterVariable(&cl_predict_players);
1088     Cvar_RegisterVariable(&cl_solid_players);
1089 
1090     Cvar_RegisterVariable(&localid);
1091 
1092     Cvar_RegisterVariable(&baseskin);
1093     Cvar_RegisterVariable(&noskins);
1094 
1095     //
1096     // info mirrors
1097     //
1098     Cvar_RegisterVariable(&name);
1099     Cvar_RegisterVariable(&password);
1100     Cvar_RegisterVariable(&spectator);
1101     Cvar_RegisterVariable(&skin);
1102     Cvar_RegisterVariable(&team);
1103     Cvar_RegisterVariable(&topcolor);
1104     Cvar_RegisterVariable(&bottomcolor);
1105     Cvar_RegisterVariable(&rate);
1106     Cvar_RegisterVariable(&msg);
1107     Cvar_RegisterVariable(&noaim);
1108 
1109     Cvar_RegisterVariable(&developer);
1110     if (COM_CheckParm("-developer"))
1111 	Cvar_SetValue("developer", 1);
1112 
1113     Cmd_AddCommand("version", CL_Version_f);
1114 
1115     Cmd_AddCommand("changing", CL_Changing_f);
1116     Cmd_AddCommand("disconnect", CL_Disconnect_f);
1117     Cmd_AddCommand("record", CL_Record_f);
1118     Cmd_AddCommand("rerecord", CL_ReRecord_f);
1119     Cmd_AddCommand("stop", CL_Stop_f);
1120     Cmd_AddCommand("playdemo", CL_PlayDemo_f);
1121     Cmd_SetCompletion("playdemo", CL_Demo_Arg_f);
1122     Cmd_AddCommand("timedemo", CL_TimeDemo_f);
1123     Cmd_SetCompletion("timedemo", CL_Demo_Arg_f);
1124 
1125     Cmd_AddCommand("skins", Skin_Skins_f);
1126     Cmd_AddCommand("allskins", Skin_AllSkins_f);
1127 
1128     Cmd_AddCommand("quit", CL_Quit_f);
1129 
1130     Cmd_AddCommand("connect", CL_Connect_f);
1131     Cmd_AddCommand("reconnect", CL_Reconnect_f);
1132 
1133     Cmd_AddCommand("rcon", CL_Rcon_f);
1134     Cmd_AddCommand("packet", CL_Packet_f);
1135     Cmd_AddCommand("user", CL_User_f);
1136     Cmd_AddCommand("users", CL_Users_f);
1137 
1138     Cmd_AddCommand("setinfo", CL_SetInfo_f);
1139     Cmd_AddCommand("fullinfo", CL_FullInfo_f);
1140     Cmd_AddCommand("fullserverinfo", CL_FullServerinfo_f);
1141 
1142     Cmd_AddCommand("color", CL_Color_f);
1143     Cmd_AddCommand("download", CL_Download_f);
1144 
1145     Cmd_AddCommand("nextul", CL_NextUpload);
1146     Cmd_AddCommand("stopul", CL_StopUpload);
1147 
1148     Cmd_AddCommand("mcache", Mod_Print);
1149 
1150 //
1151 // forward to server commands
1152 //
1153     Cmd_AddCommand("kill", NULL);
1154     Cmd_AddCommand("pause", NULL);
1155     Cmd_AddCommand("say", NULL);
1156     Cmd_AddCommand("say_team", NULL);
1157     Cmd_AddCommand("serverinfo", NULL);
1158 
1159 /* FIXME - more hacks... */
1160 //
1161 //  Windows commands
1162 //
1163 #ifdef _WIN32
1164     Cmd_AddCommand("windows", CL_Windows_f);
1165 #endif
1166 }
1167 
1168 
1169 /*
1170 ================
1171 Host_EndGame
1172 
1173 Call this to drop to a console without exiting the qwcl
1174 ================
1175 */
1176 void
Host_EndGame(const char * message,...)1177 Host_EndGame(const char *message, ...)
1178 {
1179     va_list argptr;
1180     char string[MAX_PRINTMSG];
1181 
1182     va_start(argptr, message);
1183     vsnprintf(string, sizeof(string), message, argptr);
1184     va_end(argptr);
1185     Con_Printf("\n===========================\n");
1186     Con_Printf("Host_EndGame: %s\n", string);
1187     Con_Printf("===========================\n\n");
1188 
1189     CL_Disconnect();
1190 
1191     longjmp(host_abort, 1);
1192 }
1193 
1194 /*
1195 ================
1196 Host_Error
1197 
1198 This shuts down the client and exits qwcl
1199 ================
1200 */
1201 void
Host_Error(const char * error,...)1202 Host_Error(const char *error, ...)
1203 {
1204     va_list argptr;
1205     char string[MAX_PRINTMSG];
1206     static qboolean inerror = false;
1207 
1208     if (inerror)
1209 	Sys_Error("Host_Error: recursively entered");
1210     inerror = true;
1211 
1212     va_start(argptr, error);
1213     vsnprintf(string, sizeof(string), error, argptr);
1214     va_end(argptr);
1215     Con_Printf("Host_Error: %s\n", string);
1216 
1217     CL_Disconnect();
1218     cls.demonum = -1;
1219 
1220     inerror = false;
1221 
1222 // FIXME
1223     Sys_Error("Host_Error: %s", string);
1224 }
1225 
1226 
1227 /*
1228 ===============
1229 Host_WriteConfiguration
1230 
1231 Writes key bindings and archived cvars to config.cfg
1232 ===============
1233 */
1234 void
Host_WriteConfiguration(void)1235 Host_WriteConfiguration(void)
1236 {
1237     FILE *f;
1238 
1239     if (host_initialized) {
1240 	f = fopen(va("%s/config.cfg", com_gamedir), "w");
1241 	if (!f) {
1242 	    Con_Printf("Couldn't write config.cfg.\n");
1243 	    return;
1244 	}
1245 
1246 	Key_WriteBindings(f);
1247 	Cvar_WriteVariables(f);
1248 
1249 	/* Save the mlook state (rarely used as an actual key binding) */
1250 	if (in_mlook.state & 1)
1251 	    fprintf(f, "+mlook\n");
1252 
1253 	fclose(f);
1254     }
1255 }
1256 
1257 
1258 //============================================================================
1259 
1260 #if 0
1261 /*
1262 ==================
1263 Host_SimulationTime
1264 
1265 This determines if enough time has passed to run a simulation frame
1266 ==================
1267 */
1268 qboolean
1269 Host_SimulationTime(float time)
1270 {
1271     float fps;
1272 
1273     if (oldrealtime > realtime)
1274 	oldrealtime = 0;
1275 
1276     if (cl_maxfps.value)
1277 	fps = qmax(30.0, qmin(cl_maxfps.value, 72.0));
1278     else
1279 	fps = qmax(30.0, qmin(rate.value / 80.0, 72.0));
1280 
1281     if (!cls.timedemo && (realtime + time) - oldrealtime < 1.0 / fps)
1282 	return false;		// framerate is too high
1283     return true;
1284 }
1285 #endif
1286 
1287 
1288 /*
1289 ==================
1290 Host_Frame
1291 
1292 Runs all active servers
1293 ==================
1294 */
1295 void
Host_Frame(float time)1296 Host_Frame(float time)
1297 {
1298     static double time1 = 0;
1299     static double time2 = 0;
1300     static double time3 = 0;
1301     int pass1, pass2, pass3;
1302     float fps;
1303 
1304     /* something bad happened, or the server disconnected */
1305     if (setjmp(host_abort))
1306 	return;
1307 
1308     // decide the simulation time
1309     realtime += time;
1310 
1311     if (cl_maxfps.value)
1312 	fps = qmax(30.0f, qmin(cl_maxfps.value, 72.0f));
1313     else
1314 	fps = qmax(30.0f, qmin(rate.value / 80.0f, 72.0f));
1315 
1316     if (!cls.timedemo && realtime - oldrealtime < 1.0 / fps)
1317 	return;		// framerate is too high
1318 
1319     host_frametime = realtime - oldrealtime;
1320     oldrealtime = realtime;
1321     if (host_frametime > 0.2)
1322 	host_frametime = 0.2;
1323 
1324     // get new key events
1325     Sys_SendKeyEvents();
1326 
1327     /* allow mice or other external controllers to add commands */
1328     IN_Commands();
1329 
1330     /* process console commands */
1331     Cbuf_Execute();
1332 
1333     // fetch results from server
1334     CL_ReadPackets();
1335 
1336     // send intentions now
1337     // resend a connection request if necessary
1338     if (cls.state == ca_disconnected) {
1339 	CL_CheckForResend();
1340     } else
1341 	CL_SendCmd();
1342 
1343     // Set up prediction for other players
1344     CL_SetUpPlayerPrediction(false);
1345 
1346     // do client side motion prediction
1347     CL_PredictMove();
1348 
1349     // Set up prediction for other players
1350     CL_SetUpPlayerPrediction(true);
1351 
1352     // build a refresh entity list
1353     CL_EmitEntities();
1354 
1355     // update video
1356     if (host_speeds.value)
1357 	time1 = Sys_DoubleTime();
1358 
1359     SCR_UpdateScreen();
1360     CL_RunParticles();
1361 
1362     if (host_speeds.value)
1363 	time2 = Sys_DoubleTime();
1364 
1365     /* update audio */
1366     if (cls.state == ca_active) {
1367 	S_Update(r_origin, vpn, vright, vup);
1368 	CL_DecayLights();
1369     } else
1370 	S_Update(vec3_origin, vec3_origin, vec3_origin, vec3_origin);
1371 
1372     CDAudio_Update();
1373 
1374     if (host_speeds.value) {
1375 	pass1 = (time1 - time3) * 1000;
1376 	time3 = Sys_DoubleTime();
1377 	pass2 = (time2 - time1) * 1000;
1378 	pass3 = (time3 - time2) * 1000;
1379 	Con_Printf("%3i tot %3i server %3i gfx %3i snd\n",
1380 		   pass1 + pass2 + pass3, pass1, pass2, pass3);
1381     }
1382 
1383     host_framecount++;
1384     fps_count++;
1385 }
1386 
1387 //============================================================================
1388 
1389 /*
1390 ====================
1391 Host_Init
1392 ====================
1393 */
1394 void
Host_Init(quakeparms_t * parms)1395 Host_Init(quakeparms_t *parms)
1396 {
1397     COM_InitArgv(parms->argc, parms->argv);
1398     COM_AddParm("-game");
1399     COM_AddParm("qw");
1400 
1401     Sys_mkdir("qw");
1402 
1403     minimum_memory = MINIMUM_MEMORY;
1404     if (COM_CheckParm("-minmemory"))
1405 	parms->memsize = minimum_memory;
1406 
1407     host_parms = *parms;
1408 
1409     if (parms->memsize < minimum_memory)
1410 	Sys_Error("Only %4.1f megs of memory reported, can't execute game",
1411 		  parms->memsize / (float)0x100000);
1412 
1413     Memory_Init(parms->membase, parms->memsize);
1414     Cbuf_Init();
1415     Cmd_Init();
1416     V_Init();
1417 
1418     COM_Init();
1419 
1420     NET_Init(PORT_CLIENT);
1421     Netchan_Init();
1422 
1423     W_LoadWadFile("gfx.wad");
1424     Key_Init();
1425     Con_Init();
1426     M_Init();
1427     Mod_Init(R_ModelLoader());
1428 
1429 //      Con_Printf ("Exe: "__TIME__" "__DATE__"\n");
1430     Con_Printf("%4.1f megs RAM used.\n", parms->memsize / (1024 * 1024.0));
1431 
1432     R_InitTextures();
1433 
1434     host_basepal = COM_LoadHunkFile("gfx/palette.lmp");
1435     if (!host_basepal)
1436 	Sys_Error("Couldn't load gfx/palette.lmp");
1437     host_colormap = COM_LoadHunkFile("gfx/colormap.lmp");
1438     if (!host_colormap)
1439 	Sys_Error("Couldn't load gfx/colormap.lmp");
1440 
1441     VID_Init(host_basepal);
1442     Draw_Init();
1443     SCR_Init();
1444     R_Init();
1445     Sbar_Init();
1446 
1447     cls.state = ca_disconnected;
1448 
1449     S_Init();
1450     CDAudio_Init();
1451     CL_Init();
1452     IN_Init();
1453 
1454     Hunk_AllocName(0, "-HOST_HUNKLEVEL-");
1455     host_hunklevel = Hunk_LowMark();
1456 
1457     host_initialized = true;
1458     Con_Printf("\nClient Version TyrQuake-%s\n\n", stringify(TYR_VERSION));
1459     Con_Printf("\200\201\201\201\201\201\201 QuakeWorld Initialized "
1460 	       "\201\201\201\201\201\201\202\n");
1461 
1462     /* In case exec of quake.rc fails */
1463     if (!setjmp(host_abort)) {
1464 	Cbuf_InsertText("exec quake.rc\n");
1465 	Cbuf_Execute();
1466     }
1467 
1468     Cbuf_AddText("echo Type connect <internet address> or use GameSpy to "
1469 		 "connect to a game.\n");
1470     Cbuf_AddText("cl_warncmd 1\n");
1471 }
1472 
1473 
1474 /*
1475 ===============
1476 Host_Shutdown
1477 
1478 FIXME: this is a callback from Sys_Quit and Sys_Error.  It would be better
1479 to run quit through here before the final handoff to the sys code.
1480 ===============
1481 */
1482 void
Host_Shutdown(void)1483 Host_Shutdown(void)
1484 {
1485     static qboolean isdown = false;
1486 
1487     if (isdown) {
1488 	printf("recursive shutdown\n");
1489 	return;
1490     }
1491     isdown = true;
1492 
1493     Host_WriteConfiguration();
1494 
1495     CDAudio_Shutdown();
1496     NET_Shutdown();
1497     S_Shutdown();
1498     IN_Shutdown();
1499     if (host_basepal)
1500 	VID_Shutdown();
1501 }
1502