File: client\cl_parse.c
1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 */
20 // cl_parse.c -- parse a message received from the server
21
22 #include "client.h"
23
24 char *svc_strings[256] =
25 {
26 "svc_bad",
27
28 "svc_muzzleflash",
29 "svc_muzzlflash2",
30 "svc_temp_entity",
31 "svc_layout",
32 "svc_inventory",
33
34 "svc_nop",
35 "svc_disconnect",
36 "svc_reconnect",
37 "svc_sound",
38 "svc_print",
39 "svc_stufftext",
40 "svc_serverdata",
41 "svc_configstring",
42 "svc_spawnbaseline",
43 "svc_centerprint",
44 "svc_download",
45 "svc_playerinfo",
46 "svc_packetentities",
47 "svc_deltapacketentities",
48 "svc_frame"
49 };
50
51 //=============================================================================
52
53 void CL_DownloadFileName(char *dest, int destlen, char *fn)
54 {
55 if (strncmp(fn, "players", 7) == 0)
56 Com_sprintf (dest, destlen, "%s/%s", BASEDIRNAME, fn);
57 else
58 Com_sprintf (dest, destlen, "%s/%s", FS_Gamedir(), fn);
59 }
60
61 /*
62 ===============
63 CL_CheckOrDownloadFile
64
65 Returns true if the file exists, otherwise it attempts
66 to start a download from the server.
67 ===============
68 */
69 qboolean CL_CheckOrDownloadFile (char *filename)
70 {
71 FILE *fp;
72 char name[MAX_OSPATH];
73
74 if (strstr (filename, ".."))
75 {
76 Com_Printf ("Refusing to download a path with ..\n");
77 return true;
78 }
79
80 if (FS_LoadFile (filename, NULL) != -1)
81 { // it exists, no need to download
82 return true;
83 }
84
85 strcpy (cls.downloadname, filename);
86
87 // download to a temp name, and only rename
88 // to the real name when done, so if interrupted
89 // a runt file wont be left
90 COM_StripExtension (cls.downloadname, cls.downloadtempname);
91 strcat (cls.downloadtempname, ".tmp");
92
93 //ZOID
94 // check to see if we already have a tmp for this file, if so, try to resume
95 // open the file if not opened yet
96 CL_DownloadFileName(name, sizeof(name), cls.downloadtempname);
97
98 // FS_CreatePath (name);
99
100 fp = fopen (name, "r+b");
101 if (fp) { // it exists
102 int len;
103 fseek(fp, 0, SEEK_END);
104 len = ftell(fp);
105
106 cls.download = fp;
107
108 // give the server an offset to start the download
109 Com_Printf ("Resuming %s\n", cls.downloadname);
110 MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
111 MSG_WriteString (&cls.netchan.message,
112 va("download %s %i", cls.downloadname, len));
113 } else {
114 Com_Printf ("Downloading %s\n", cls.downloadname);
115 MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
116 MSG_WriteString (&cls.netchan.message,
117 va("download %s", cls.downloadname));
118 }
119
120 cls.downloadnumber++;
121
122 return false;
123 }
124
125 /*
126 ===============
127 CL_Download_f
128
129 Request a download from the server
130 ===============
131 */
132 void CL_Download_f (void)
133 {
134 char filename[MAX_OSPATH];
135
136 if (Cmd_Argc() != 2) {
137 Com_Printf("Usage: download <filename>\n");
138 return;
139 }
140
141 Com_sprintf(filename, sizeof(filename), "%s", Cmd_Argv(1));
142
143 if (strstr (filename, ".."))
144 {
145 Com_Printf ("Refusing to download a path with ..\n");
146 return;
147 }
148
149 if (FS_LoadFile (filename, NULL) != -1)
150 { // it exists, no need to download
151 Com_Printf("File already exists.\n");
152 return;
153 }
154
155 strcpy (cls.downloadname, filename);
156 Com_Printf ("Downloading %s\n", cls.downloadname);
157
158 // download to a temp name, and only rename
159 // to the real name when done, so if interrupted
160 // a runt file wont be left
161 COM_StripExtension (cls.downloadname, cls.downloadtempname);
162 strcat (cls.downloadtempname, ".tmp");
163
164 MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
165 MSG_WriteString (&cls.netchan.message,
166 va("download %s", cls.downloadname));
167
168 cls.downloadnumber++;
169 }
170
171 /*
172 ======================
173 CL_RegisterSounds
174 ======================
175 */
176 void CL_RegisterSounds (void)
177 {
178 int i;
179
180 S_BeginRegistration ();
181 CL_RegisterTEntSounds ();
182 for (i=1 ; i<MAX_SOUNDS ; i++)
183 {
184 if (!cl.configstrings[CS_SOUNDS+i][0])
185 break;
186 cl.sound_precache[i] = S_RegisterSound (cl.configstrings[CS_SOUNDS+i]);
187 Sys_SendKeyEvents (); // pump message loop
188 }
189 S_EndRegistration ();
190 }
191
192
193 /*
194 =====================
195 CL_ParseDownload
196
197 A download message has been received from the server
198 =====================
199 */
200 void CL_ParseDownload (void)
201 {
202 int size, percent;
203 char name[MAX_OSPATH];
204 int r;
205
206 // read the data
207 size = MSG_ReadShort (&net_message);
208 percent = MSG_ReadByte (&net_message);
209 if (size == -1)
210 {
211 Com_Printf ("Server does not have this file.\n");
212 if (cls.download)
213 {
214 // if here, we tried to resume a file but the server said no
215 fclose (cls.download);
216 cls.download = NULL;
217 }
218 CL_RequestNextDownload ();
219 return;
220 }
221
222 // open the file if not opened yet
223 if (!cls.download)
224 {
225 CL_DownloadFileName(name, sizeof(name), cls.downloadtempname);
226
227 FS_CreatePath (name);
228
229 cls.download = fopen (name, "wb");
230 if (!cls.download)
231 {
232 net_message.readcount += size;
233 Com_Printf ("Failed to open %s\n", cls.downloadtempname);
234 CL_RequestNextDownload ();
235 return;
236 }
237 }
238
239 fwrite (net_message.data + net_message.readcount, 1, size, cls.download);
240 net_message.readcount += size;
241
242 if (percent != 100)
243 {
244 // request next block
245 // change display routines by zoid
246 #if 0
253 #endif
254 cls.downloadpercent = percent;
255
256 MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
257 SZ_Print (&cls.netchan.message, "nextdl");
258 }
259 else
260 {
261 char oldn[MAX_OSPATH];
262 char newn[MAX_OSPATH];
263
264 // Com_Printf ("100%%\n");
265
266 fclose (cls.download);
267
268 // rename the temp file to it's final name
269 CL_DownloadFileName(oldn, sizeof(oldn), cls.downloadtempname);
270 CL_DownloadFileName(newn, sizeof(newn), cls.downloadname);
271 r = rename (oldn, newn);
272 if (r)
273 Com_Printf ("failed to rename.\n");
274
275 cls.download = NULL;
276 cls.downloadpercent = 0;
277
278 // get another file if needed
279
280 CL_RequestNextDownload ();
281 }
282 }
283
284
285 /*
286 =====================================================================
287
288 SERVER CONNECTING MESSAGES
289
290 =====================================================================
291 */
292
293 /*
294 ==================
295 CL_ParseServerData
296 ==================
297 */
298 void CL_ParseServerData (void)
299 {
300 extern cvar_t *fs_gamedirvar;
301 char *str;
302 int i;
303
304 Com_DPrintf ("Serverdata packet received.\n");
305 //
306 // wipe the client_state_t struct
307 //
308 CL_ClearState ();
309 cls.state = ca_connected;
310
311 // parse protocol version number
312 i = MSG_ReadLong (&net_message);
313 cls.serverProtocol = i;
314
315 // BIG HACK to let demos from release work with the 3.0x patch!!!
316 if (Com_ServerState() && PROTOCOL_VERSION == 34)
317 {
318 }
319 else if (i != PROTOCOL_VERSION)
320 Com_Error (ERR_DROP,"Server returned version %i, not %i", i, PROTOCOL_VERSION);
321
322 cl.servercount = MSG_ReadLong (&net_message);
323 cl.attractloop = MSG_ReadByte (&net_message);
324
325 // game directory
326 str = MSG_ReadString (&net_message);
327 strncpy (cl.gamedir, str, sizeof(cl.gamedir)-1);
328
329 // set gamedir
330 if ((*str && (!fs_gamedirvar->string || !*fs_gamedirvar->string || strcmp(fs_gamedirvar->string, str))) || (!*str && (fs_gamedirvar->string || *fs_gamedirvar->string)))
331 Cvar_Set("game", str);
332
333 // parse player entity number
334 cl.playernum = MSG_ReadShort (&net_message);
335
336 // get the full level name
337 str = MSG_ReadString (&net_message);
338
339 if (cl.playernum == -1)
340 { // playing a cinematic or showing a pic, not a level
341 SCR_PlayCinematic (str);
342 }
343 else
344 {
345 // seperate the printfs so the server message can have a color
346 Com_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n");
347 Com_Printf ("%c%s\n", 2, str);
348
349 // need to prep refresh at next oportunity
350 cl.refresh_prepped = false;
351 }
352 }
353
354 /*
355 ==================
356 CL_ParseBaseline
357 ==================
358 */
359 void CL_ParseBaseline (void)
360 {
361 entity_state_t *es;
362 int bits;
363 int newnum;
364 entity_state_t nullstate;
365
366 memset (&nullstate, 0, sizeof(nullstate));
367
368 newnum = CL_ParseEntityBits (&bits);
369 es = &cl_entities[newnum].baseline;
370 CL_ParseDelta (&nullstate, es, newnum, bits);
371 }
372
373
374 /*
375 ================
376 CL_LoadClientinfo
377
378 ================
379 */
380 void CL_LoadClientinfo (clientinfo_t *ci, char *s)
381 {
382 int i;
383 char *t;
384 char model_name[MAX_QPATH];
385 char skin_name[MAX_QPATH];
386 char model_filename[MAX_QPATH];
387 char skin_filename[MAX_QPATH];
388 char weapon_filename[MAX_QPATH];
389
390 strncpy(ci->cinfo, s, sizeof(ci->cinfo));
391 ci->cinfo[sizeof(ci->cinfo)-1] = 0;
392
393 // isolate the player's name
394 strncpy(ci->name, s, sizeof(ci->name));
395 ci->name[sizeof(ci->name)-1] = 0;
396 t = strstr (s, "\\");
397 if (t)
398 {
399 ci->name[t-s] = 0;
400 s = t+1;
401 }
402
403 if (cl_noskins->value || *s == 0)
404 {
405 Com_sprintf (model_filename, sizeof(model_filename), "players/male/tris.md2");
406 Com_sprintf (weapon_filename, sizeof(weapon_filename), "players/male/weapon.md2");
407 Com_sprintf (skin_filename, sizeof(skin_filename), "players/male/grunt.pcx");
408 Com_sprintf (ci->iconname, sizeof(ci->iconname), "/players/male/grunt_i.pcx");
409 ci->model = re.RegisterModel (model_filename);
410 memset(ci->weaponmodel, 0, sizeof(ci->weaponmodel));
411 ci->weaponmodel[0] = re.RegisterModel (weapon_filename);
412 ci->skin = re.RegisterSkin (skin_filename);
413 ci->icon = re.RegisterPic (ci->iconname);
414 }
415 else
416 {
417 // isolate the model name
418 strcpy (model_name, s);
419 t = strstr(model_name, "/");
420 if (!t)
421 t = strstr(model_name, "\\");
422 if (!t)
423 t = model_name;
424 *t = 0;
425
426 // isolate the skin name
427 strcpy (skin_name, s + strlen(model_name) + 1);
428
429 // model file
430 Com_sprintf (model_filename, sizeof(model_filename), "players/%s/tris.md2", model_name);
431 ci->model = re.RegisterModel (model_filename);
432 if (!ci->model)
433 {
434 strcpy(model_name, "male");
435 Com_sprintf (model_filename, sizeof(model_filename), "players/male/tris.md2");
436 ci->model = re.RegisterModel (model_filename);
437 }
438
439 // skin file
440 Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/%s.pcx", model_name, skin_name);
441 ci->skin = re.RegisterSkin (skin_filename);
442
443 // if we don't have the skin and the model wasn't male,
444 // see if the male has it (this is for CTF's skins)
445 if (!ci->skin && Q_stricmp(model_name, "male"))
446 {
447 // change model to male
448 strcpy(model_name, "male");
449 Com_sprintf (model_filename, sizeof(model_filename), "players/male/tris.md2");
450 ci->model = re.RegisterModel (model_filename);
451
452 // see if the skin exists for the male model
453 Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/%s.pcx", model_name, skin_name);
454 ci->skin = re.RegisterSkin (skin_filename);
455 }
456
457 // if we still don't have a skin, it means that the male model didn't have
458 // it, so default to grunt
459 if (!ci->skin) {
460 // see if the skin exists for the male model
461 Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/grunt.pcx", model_name, skin_name);
462 ci->skin = re.RegisterSkin (skin_filename);
463 }
464
465 // weapon file
466 for (i = 0; i < num_cl_weaponmodels; i++) {
467 Com_sprintf (weapon_filename, sizeof(weapon_filename), "players/%s/%s", model_name, cl_weaponmodels[i]);
468 ci->weaponmodel[i] = re.RegisterModel(weapon_filename);
469 if (!ci->weaponmodel[i] && strcmp(model_name, "cyborg") == 0) {
470 // try male
471 Com_sprintf (weapon_filename, sizeof(weapon_filename), "players/male/%s", cl_weaponmodels[i]);
472 ci->weaponmodel[i] = re.RegisterModel(weapon_filename);
473 }
474 if (!cl_vwep->value)
475 break; // only one when vwep is off
476 }
477
478 // icon file
479 Com_sprintf (ci->iconname, sizeof(ci->iconname), "/players/%s/%s_i.pcx", model_name, skin_name);
480 ci->icon = re.RegisterPic (ci->iconname);
481 }
482
483 // must have loaded all data types to be valud
484 if (!ci->skin || !ci->icon || !ci->model || !ci->weaponmodel[0])
485 {
486 ci->skin = NULL;
487 ci->icon = NULL;
488 ci->model = NULL;
489 ci->weaponmodel[0] = NULL;
490 return;
491 }
492 }
493
494 /*
495 ================
496 CL_ParseClientinfo
497
498 Load the skin, icon, and model for a client
499 ================
500 */
501 void CL_ParseClientinfo (int player)
502 {
503 char *s;
504 clientinfo_t *ci;
505
506 s = cl.configstrings[player+CS_PLAYERSKINS];
507
508 ci = &cl.clientinfo[player];
509
510 CL_LoadClientinfo (ci, s);
511 }
512
513
514 /*
515 ================
516 CL_ParseConfigString
517 ================
518 */
519 void CL_ParseConfigString (void)
520 {
521 int i;
522 char *s;
523 char olds[MAX_QPATH];
524
525 i = MSG_ReadShort (&net_message);
526 if (i < 0 || i >= MAX_CONFIGSTRINGS)
527 Com_Error (ERR_DROP, "configstring > MAX_CONFIGSTRINGS");
528 s = MSG_ReadString(&net_message);
529
530 strncpy (olds, cl.configstrings[i], sizeof(olds));
531 olds[sizeof(olds) - 1] = 0;
532
533 strcpy (cl.configstrings[i], s);
534
535 // do something apropriate
536
537 if (i >= CS_LIGHTS && i < CS_LIGHTS+MAX_LIGHTSTYLES)
538 CL_SetLightstyle (i - CS_LIGHTS);
539 else if (i == CS_CDTRACK)
540 {
541 if (cl.refresh_prepped)
542 CDAudio_Play (atoi(cl.configstrings[CS_CDTRACK]), true);
543 }
544 else if (i >= CS_MODELS && i < CS_MODELS+MAX_MODELS)
545 {
546 if (cl.refresh_prepped)
547 {
548 cl.model_draw[i-CS_MODELS] = re.RegisterModel (cl.configstrings[i]);
549 if (cl.configstrings[i][0] == '*')
550 cl.model_clip[i-CS_MODELS] = CM_InlineModel (cl.configstrings[i]);
551 else
552 cl.model_clip[i-CS_MODELS] = NULL;
553 }
554 }
555 else if (i >= CS_SOUNDS && i < CS_SOUNDS+MAX_MODELS)
556 {
557 if (cl.refresh_prepped)
558 cl.sound_precache[i-CS_SOUNDS] = S_RegisterSound (cl.configstrings[i]);
559 }
560 else if (i >= CS_IMAGES && i < CS_IMAGES+MAX_MODELS)
561 {
562 if (cl.refresh_prepped)
563 cl.image_precache[i-CS_IMAGES] = re.RegisterPic (cl.configstrings[i]);
564 }
565 else if (i >= CS_PLAYERSKINS && i < CS_PLAYERSKINS+MAX_CLIENTS)
566 {
567 if (cl.refresh_prepped && strcmp(olds, s))
568 CL_ParseClientinfo (i-CS_PLAYERSKINS);
569 }
570 }
571
572
573 /*
574 =====================================================================
575
576 ACTION MESSAGES
577
578 =====================================================================
579 */
580
581 /*
582 ==================
583 CL_ParseStartSoundPacket
584 ==================
585 */
586 void CL_ParseStartSoundPacket(void)
587 {
588 vec3_t pos_v;
589 float *pos;
590 int channel, ent;
591 int sound_num;
592 float volume;
593 float attenuation;
594 int flags;
595 float ofs;
596
597 flags = MSG_ReadByte (&net_message);
598 sound_num = MSG_ReadByte (&net_message);
599
600 if (flags & SND_VOLUME)
601 volume = MSG_ReadByte (&net_message) / 255.0;
602 else
603 volume = DEFAULT_SOUND_PACKET_VOLUME;
604
605 if (flags & SND_ATTENUATION)
606 attenuation = MSG_ReadByte (&net_message) / 64.0;
607 else
608 attenuation = DEFAULT_SOUND_PACKET_ATTENUATION;
609
610 if (flags & SND_OFFSET)
611 ofs = MSG_ReadByte (&net_message) / 1000.0;
612 else
613 ofs = 0;
614
615 if (flags & SND_ENT)
616 { // entity reletive
617 channel = MSG_ReadShort(&net_message);
618 ent = channel>>3;
619 if (ent > MAX_EDICTS)
620 Com_Error (ERR_DROP,"CL_ParseStartSoundPacket: ent = %i", ent);
621
622 channel &= 7;
623 }
624 else
625 {
626 ent = 0;
627 channel = 0;
628 }
629
630 if (flags & SND_POS)
631 { // positioned in space
632 MSG_ReadPos (&net_message, pos_v);
633
634 pos = pos_v;
635 }
636 else // use entity number
637 pos = NULL;
638
639 if (!cl.sound_precache[sound_num])
640 return;
641
642 S_StartSound (pos, ent, channel, cl.sound_precache[sound_num], volume, attenuation, ofs);
643 }
644
645
646 void SHOWNET(char *s)
647 {
648 if (cl_shownet->value>=2)
649 Com_Printf ("%3i:%s\n", net_message.readcount-1, s);
650 }
651
652 /*
653 =====================
654 CL_ParseServerMessage
655 =====================
656 */
657 void CL_ParseServerMessage (void)
658 {
659 int cmd;
660 char *s;
661 int i;
662
663 //
664 // if recording demos, copy the message out
665 //
666 if (cl_shownet->value == 1)
667 Com_Printf ("%i ",net_message.cursize);
668 else if (cl_shownet->value >= 2)
669 Com_Printf ("------------------\n");
670
671
672 //
673 // parse the message
674 //
675 while (1)
676 {
677 if (net_message.readcount > net_message.cursize)
678 {
679 Com_Error (ERR_DROP,"CL_ParseServerMessage: Bad server message");
680 break;
681 }
682
683 cmd = MSG_ReadByte (&net_message);
684
685 if (cmd == -1)
686 {
687 SHOWNET("END OF MESSAGE");
688 break;
689 }
690
691 if (cl_shownet->value>=2)
692 {
693 if (!svc_strings[cmd])
694 Com_Printf ("%3i:BAD CMD %i\n", net_message.readcount-1,cmd);
695 else
696 SHOWNET(svc_strings[cmd]);
697 }
698
699 // other commands
700 switch (cmd)
701 {
702 default:
703 Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message\n");
704 break;
705
706 case svc_nop:
707 // Com_Printf ("svc_nop\n");
708 break;
709
710 case svc_disconnect:
711 Com_Error (ERR_DISCONNECT,"Server disconnected\n");
712 break;
713
714 case svc_reconnect:
715 Com_Printf ("Server disconnected, reconnecting\n");
716 if (cls.download) {
717 //ZOID, close download
718 fclose (cls.download);
719 cls.download = NULL;
720 }
721 cls.state = ca_connecting;
722 cls.connect_time = -99999; // CL_CheckForResend() will fire immediately
723 break;
724
725 case svc_print:
726 i = MSG_ReadByte (&net_message);
727 if (i == PRINT_CHAT)
728 {
729 S_StartLocalSound ("misc/talk.wav");
730 con.ormask = 128;
731 }
732 Com_Printf ("%s", MSG_ReadString (&net_message));
733 con.ormask = 0;
734 break;
735
736 case svc_centerprint:
737 SCR_CenterPrint (MSG_ReadString (&net_message));
738 break;
739
740 case svc_stufftext:
741 s = MSG_ReadString (&net_message);
742 Com_DPrintf ("stufftext: %s\n", s);
743 Cbuf_AddText (s);
744 break;
745
746 case svc_serverdata:
747 Cbuf_Execute (); // make sure any stuffed commands are done
748 CL_ParseServerData ();
749 break;
750
751 case svc_configstring:
752 CL_ParseConfigString ();
753 break;
754
755 case svc_sound:
756 CL_ParseStartSoundPacket();
757 break;
758
759 case svc_spawnbaseline:
760 CL_ParseBaseline ();
761 break;
762
763 case svc_temp_entity:
764 CL_ParseTEnt ();
765 break;
766
767 case svc_muzzleflash:
768 CL_ParseMuzzleFlash ();
769 break;
770
771 case svc_muzzleflash2:
772 CL_ParseMuzzleFlash2 ();
773 break;
774
775 case svc_download:
776 CL_ParseDownload ();
777 break;
778
779 case svc_frame:
780 CL_ParseFrame ();
781 break;
782
783 case svc_inventory:
784 CL_ParseInventory ();
785 break;
786
787 case svc_layout:
788 s = MSG_ReadString (&net_message);
789 strncpy (cl.layout, s, sizeof(cl.layout)-1);
790 break;
791
792 case svc_playerinfo:
793 case svc_packetentities:
794 case svc_deltapacketentities:
795 Com_Error (ERR_DROP, "Out of place frame data");
796 break;
797 }
798 }
799
800 CL_AddNetgraph ();
801
802 //
803 // we don't know if it is ok to save a demo message until
804 // after we have parsed the frame
805 //
806 if (cls.demorecording && !cls.demowaiting)
807 CL_WriteDemoMessage ();
808
809 }
810
811
812