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