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