2 Copyright (C) 1996-1997 Id Software, Inc.
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.
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.
13 See the GNU General Public License for more details.
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.
20 // cl_parse.c -- parse a message received from the server
24 #include "cl_collision.h"
27 char *svc_strings[128] =
33 "svc_version", // [int] server version
34 "svc_setview", // [short] entity number
35 "svc_sound", // <see code>
36 "svc_time", // [float] server time
37 "svc_print", // [string] null terminated string
38 "svc_stufftext", // [string] stuffed into client's console buffer
39 // the string should be \n terminated
40 "svc_setangle", // [vec3] set the view angle to this absolute value
42 "svc_serverinfo", // [int] version
43 // [string] signon string
44 // [string]..[0]model cache [string]...[0]sounds cache
45 // [string]..[0]item cache
46 "svc_lightstyle", // [byte] [string]
47 "svc_updatename", // [byte] [string]
48 "svc_updatefrags", // [byte] [short]
49 "svc_clientdata", // <shortbits + data>
50 "svc_stopsound", // <see code>
51 "svc_updatecolors", // [byte] [byte]
52 "svc_particle", // [vec3] <variable>
53 "svc_damage", // [byte] impact [byte] blood [vec3] from
56 "OBSOLETE svc_spawnbinary",
59 "svc_temp_entity", // <variable>
65 "svc_spawnstaticsound",
67 "svc_finale", // [string] music [string] text
68 "svc_cdtrack", // [byte] track [byte] looptrack
71 "svc_showlmp", // [string] iconlabel [string] lmpfile [short] x [short] y
72 "svc_hidelmp", // [string] iconlabel
73 "svc_skybox", // [string] skyname
86 "svc_downloaddata", // 50 // [int] start [short] size [variable length] data
87 "svc_updatestatubyte", // 51 // [byte] stat [byte] value
88 "svc_effect", // 52 // [vector] org [byte] modelindex [byte] startframe [byte] framecount [byte] framerate
89 "svc_effect2", // 53 // [vector] org [short] modelindex [short] startframe [byte] framecount [byte] framerate
90 "svc_sound2", // 54 // short soundindex instead of byte
91 "svc_spawnbaseline2", // 55 // short modelindex instead of byte
92 "svc_spawnstatic2", // 56 // short modelindex instead of byte
93 "svc_entities", // 57 // [int] deltaframe [int] thisframe [float vector] eye [variable length] entitydata
94 "svc_csqcentities", // 58 // [short] entnum [variable length] entitydata ... [short] 0x0000
95 "svc_spawnstaticsound2", // 59 // [coord3] [short] samp [byte] vol [byte] aten
98 char *qw_svc_strings[128] =
102 "qw_svc_disconnect", // 2
103 "qw_svc_updatestat", // 3 // [byte] [byte]
105 "qw_svc_setview", // 5 // [short] entity number
106 "qw_svc_sound", // 6 // <see code>
108 "qw_svc_print", // 8 // [byte] id [string] null terminated string
109 "qw_svc_stufftext", // 9 // [string] stuffed into client's console buffer
110 "qw_svc_setangle", // 10 // [angle3] set the view angle to this absolute value
111 "qw_svc_serverdata", // 11 // [long] protocol ...
112 "qw_svc_lightstyle", // 12 // [byte] [string]
114 "qw_svc_updatefrags", // 14 // [byte] [short]
116 "qw_svc_stopsound", // 16 // <see code>
119 "qw_svc_damage", // 19
120 "qw_svc_spawnstatic", // 20
122 "qw_svc_spawnbaseline", // 22
123 "qw_svc_temp_entity", // 23 // variable
124 "qw_svc_setpause", // 24 // [byte] on / off
126 "qw_svc_centerprint", // 26 // [string] to put in center of the screen
127 "qw_svc_killedmonster", // 27
128 "qw_svc_foundsecret", // 28
129 "qw_svc_spawnstaticsound", // 29 // [coord3] [byte] samp [byte] vol [byte] aten
130 "qw_svc_intermission", // 30 // [vec3_t] origin [vec3_t] angle
131 "qw_svc_finale", // 31 // [string] text
132 "qw_svc_cdtrack", // 32 // [byte] track
133 "qw_svc_sellscreen", // 33
134 "qw_svc_smallkick", // 34 // set client punchangle to 2
135 "qw_svc_bigkick", // 35 // set client punchangle to 4
136 "qw_svc_updateping", // 36 // [byte] [short]
137 "qw_svc_updateentertime", // 37 // [byte] [float]
138 "qw_svc_updatestatlong", // 38 // [byte] [long]
139 "qw_svc_muzzleflash", // 39 // [short] entity
140 "qw_svc_updateuserinfo", // 40 // [byte] slot [long] uid
141 "qw_svc_download", // 41 // [short] size [size bytes]
142 "qw_svc_playerinfo", // 42 // variable
143 "qw_svc_nails", // 43 // [byte] num [48 bits] xyzpy 12 12 12 4 8
144 "qw_svc_chokecount", // 44 // [byte] packets choked
145 "qw_svc_modellist", // 45 // [strings]
146 "qw_svc_soundlist", // 46 // [strings]
147 "qw_svc_packetentities", // 47 // [...]
148 "qw_svc_deltapacketentities", // 48 // [...]
149 "qw_svc_maxspeed", // 49 // maxspeed change, for prediction
150 "qw_svc_entgravity", // 50 // gravity change, for prediction
151 "qw_svc_setinfo", // 51 // setinfo on a client
152 "qw_svc_serverinfo", // 52 // serverinfo
153 "qw_svc_updatepl", // 53 // [byte] [byte]
156 //=============================================================================
158 cvar_t demo_nehahra = {0, "demo_nehahra", "0", "reads all quake demos as nehahra movie protocol"};
159 cvar_t developer_networkentities = {0, "developer_networkentities", "0", "prints received entities, value is 0-4 (higher for more info)"};
160 cvar_t cl_sound_wizardhit = {0, "cl_sound_wizardhit", "wizard/hit.wav", "sound to play during TE_WIZSPIKE (empty cvar disables sound)"};
161 cvar_t cl_sound_hknighthit = {0, "cl_sound_hknighthit", "hknight/hit.wav", "sound to play during TE_KNIGHTSPIKE (empty cvar disables sound)"};
162 cvar_t cl_sound_tink1 = {0, "cl_sound_tink1", "weapons/tink1.wav", "sound to play with 80% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"};
163 cvar_t cl_sound_ric1 = {0, "cl_sound_ric1", "weapons/ric1.wav", "sound to play with 5% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"};
164 cvar_t cl_sound_ric2 = {0, "cl_sound_ric2", "weapons/ric2.wav", "sound to play with 5% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"};
165 cvar_t cl_sound_ric3 = {0, "cl_sound_ric3", "weapons/ric3.wav", "sound to play with 10% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"};
166 cvar_t cl_sound_r_exp3 = {0, "cl_sound_r_exp3", "weapons/r_exp3.wav", "sound to play during TE_EXPLOSION and related effects (empty cvar disables sound)"};
167 cvar_t cl_serverextension_download = {0, "cl_serverextension_download", "0", "indicates whether the server supports the download command"};
168 cvar_t cl_joinbeforedownloadsfinish = {0, "cl_joinbeforedownloadsfinish", "1", "if non-zero the game will begin after the map is loaded before other downloads finish"};
169 cvar_t cl_nettimesyncmode = {0, "cl_nettimesyncmode", "2", "selects method of time synchronization in client with regard to server packets, values are: 0 = no sync, 1 = exact sync (reset timing each packet), 2 = loose sync (reset timing only if it is out of bounds), 3 = tight sync and bounding"};
171 static qboolean QW_CL_CheckOrDownloadFile(const char *filename);
172 static void QW_CL_RequestNextDownload(void);
173 static void QW_CL_NextUpload(void);
174 void QW_CL_StartUpload(unsigned char *data, int size);
175 //static qboolean QW_CL_IsUploading(void);
176 static void QW_CL_StopUpload(void);
180 CL_ParseStartSoundPacket
183 void CL_ParseStartSoundPacket(int largesoundindex)
192 if (cls.protocol == PROTOCOL_QUAKEWORLD)
194 channel = MSG_ReadShort();
196 if (channel & (1<<15))
197 volume = MSG_ReadByte ();
199 volume = DEFAULT_SOUND_PACKET_VOLUME;
201 if (channel & (1<<14))
202 attenuation = MSG_ReadByte () / 64.0;
204 attenuation = DEFAULT_SOUND_PACKET_ATTENUATION;
206 ent = (channel>>3)&1023;
209 sound_num = MSG_ReadByte ();
213 field_mask = MSG_ReadByte();
215 if (field_mask & SND_VOLUME)
216 volume = MSG_ReadByte ();
218 volume = DEFAULT_SOUND_PACKET_VOLUME;
220 if (field_mask & SND_ATTENUATION)
221 attenuation = MSG_ReadByte () / 64.0;
223 attenuation = DEFAULT_SOUND_PACKET_ATTENUATION;
225 if (field_mask & SND_LARGEENTITY)
227 ent = (unsigned short) MSG_ReadShort ();
228 channel = MSG_ReadByte ();
232 channel = (unsigned short) MSG_ReadShort ();
237 if (largesoundindex || field_mask & SND_LARGESOUND)
238 sound_num = (unsigned short) MSG_ReadShort ();
240 sound_num = MSG_ReadByte ();
243 MSG_ReadVector(pos, cls.protocol);
245 if (sound_num >= MAX_SOUNDS)
247 Con_Printf("CL_ParseStartSoundPacket: sound_num (%i) >= MAX_SOUNDS (%i)\n", sound_num, MAX_SOUNDS);
251 if (ent >= MAX_EDICTS)
253 Con_Printf("CL_ParseStartSoundPacket: ent = %i", ent);
257 S_StartSound (ent, channel, cl.sound_precache[sound_num], pos, volume/255.0f, attenuation);
264 When the client is taking a long time to load stuff, send keepalive messages
265 so the server doesn't disconnect.
269 static unsigned char olddata[NET_MAXMESSAGE];
270 void CL_KeepaliveMessage (qboolean readmessages)
273 static double nextmsg = -1;
278 // no need if server is local and definitely not if this is a demo
279 if (sv.active || !cls.netcon || cls.protocol == PROTOCOL_QUAKEWORLD)
284 // read messages from server, should just be nops
285 oldreadcount = msg_readcount;
286 oldbadread = msg_badread;
288 memcpy(olddata, net_message.data, net_message.cursize);
290 NetConn_ClientFrame();
292 msg_readcount = oldreadcount;
293 msg_badread = oldbadread;
295 memcpy(net_message.data, olddata, net_message.cursize);
298 if (cls.netcon && (time = Sys_DoubleTime()) >= nextmsg)
301 unsigned char buf[4];
304 // LordHavoc: must use unreliable because reliable could kill the sigon message!
305 Con_Print("--> client to server keepalive\n");
306 memset(&msg, 0, sizeof(msg));
308 msg.maxsize = sizeof(buf);
309 MSG_WriteChar(&msg, clc_nop);
310 NetConn_SendUnreliableMessage(cls.netcon, &msg, cls.protocol);
314 void CL_ParseEntityLump(char *entdata)
317 char key[128], value[MAX_INPUTLINE];
318 FOG_clear(); // LordHavoc: no fog until set
319 // LordHavoc: default to the map's sky (q3 shader parsing sets this)
320 R_SetSkyBox(cl.worldmodel->brush.skybox);
324 if (!COM_ParseTokenConsole(&data))
326 if (com_token[0] != '{')
330 if (!COM_ParseTokenConsole(&data))
332 if (com_token[0] == '}')
333 break; // end of worldspawn
334 if (com_token[0] == '_')
335 strlcpy (key, com_token + 1, sizeof (key));
337 strlcpy (key, com_token, sizeof (key));
338 while (key[strlen(key)-1] == ' ') // remove trailing spaces
339 key[strlen(key)-1] = 0;
340 if (!COM_ParseTokenConsole(&data))
342 strlcpy (value, com_token, sizeof (value));
343 if (!strcmp("sky", key))
345 else if (!strcmp("skyname", key)) // non-standard, introduced by QuakeForge... sigh.
347 else if (!strcmp("qlsky", key)) // non-standard, introduced by QuakeLives (EEK)
349 else if (!strcmp("fog", key))
350 sscanf(value, "%f %f %f %f", &r_refdef.fog_density, &r_refdef.fog_red, &r_refdef.fog_green, &r_refdef.fog_blue);
351 else if (!strcmp("fog_density", key))
352 r_refdef.fog_density = atof(value);
353 else if (!strcmp("fog_red", key))
354 r_refdef.fog_red = atof(value);
355 else if (!strcmp("fog_green", key))
356 r_refdef.fog_green = atof(value);
357 else if (!strcmp("fog_blue", key))
358 r_refdef.fog_blue = atof(value);
362 static qboolean QW_CL_CheckOrDownloadFile(const char *filename)
366 // see if the file already exists
367 file = FS_Open(filename, "rb", true, false);
374 // download messages in a demo would be bad
375 if (cls.demorecording)
377 Con_Printf("Unable to download \"%s\" when recording.\n", filename);
381 // don't try to download when playing a demo
385 strlcpy(cls.qw_downloadname, filename, sizeof(cls.qw_downloadname));
386 Con_Printf("Downloading %s\n", filename);
388 if (!cls.qw_downloadmemory)
390 cls.qw_downloadmemory = NULL;
391 cls.qw_downloadmemorycursize = 0;
392 cls.qw_downloadmemorymaxsize = 1024*1024; // start out with a 1MB buffer
395 MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
396 MSG_WriteString(&cls.netcon->message, va("download %s", filename));
398 cls.qw_downloadnumber++;
399 cls.qw_downloadpercent = 0;
400 cls.qw_downloadmemorycursize = 0;
405 static void QW_CL_ProcessUserInfo(int slot);
406 static void QW_CL_RequestNextDownload(void)
410 // clear name of file that just finished
411 cls.qw_downloadname[0] = 0;
413 switch (cls.qw_downloadtype)
418 if (cls.qw_downloadnumber == 0)
419 Con_Printf("Checking skins...\n");
420 for (;cls.qw_downloadnumber < cl.maxclients;cls.qw_downloadnumber++)
422 if (!cl.scores[cls.qw_downloadnumber].name[0])
424 // check if we need to download the file, and return if so
425 if (!QW_CL_CheckOrDownloadFile(va("skins/%s.pcx", cl.scores[cls.qw_downloadnumber].qw_skin)))
429 cls.qw_downloadtype = dl_none;
431 // load any newly downloaded skins
432 for (i = 0;i < cl.maxclients;i++)
433 QW_CL_ProcessUserInfo(i);
435 // if we're still in signon stages, request the next one
436 if (cls.signon != SIGNONS)
438 cls.signon = SIGNONS-1;
439 // we'll go to SIGNONS when the first entity update is received
440 MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
441 MSG_WriteString(&cls.netcon->message, va("begin %i", cl.qw_servercount));
445 if (cls.qw_downloadnumber == 0)
447 Con_Printf("Checking models...\n");
448 cls.qw_downloadnumber = 1;
451 for (;cls.qw_downloadnumber < MAX_MODELS && cl.model_name[cls.qw_downloadnumber][0];cls.qw_downloadnumber++)
454 if (cl.model_name[cls.qw_downloadnumber][0] == '*')
456 if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/spike.mdl"))
457 cl.qw_modelindex_spike = cls.qw_downloadnumber;
458 if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/player.mdl"))
459 cl.qw_modelindex_player = cls.qw_downloadnumber;
460 if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/flag.mdl"))
461 cl.qw_modelindex_flag = cls.qw_downloadnumber;
462 if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/s_explod.spr"))
463 cl.qw_modelindex_s_explod = cls.qw_downloadnumber;
464 // check if we need to download the file, and return if so
465 if (!QW_CL_CheckOrDownloadFile(cl.model_name[cls.qw_downloadnumber]))
469 cls.qw_downloadtype = dl_none;
471 // touch all of the precached models that are still loaded so we can free
472 // anything that isn't needed
474 for (i = 1;i < MAX_MODELS && cl.model_name[i][0];i++)
475 Mod_FindName(cl.model_name[i]);
476 // precache any models used by the client (this also marks them used)
477 cl.model_bolt = Mod_ForName("progs/bolt.mdl", false, false, false);
478 cl.model_bolt2 = Mod_ForName("progs/bolt2.mdl", false, false, false);
479 cl.model_bolt3 = Mod_ForName("progs/bolt3.mdl", false, false, false);
480 cl.model_beam = Mod_ForName("progs/beam.mdl", false, false, false);
483 // now we try to load everything that is new
486 cl.model_precache[1] = Mod_ForName(cl.model_name[1], false, false, true);
487 if (cl.model_precache[1]->Draw == NULL)
488 Con_Printf("Map %s could not be found or downloaded\n", cl.model_name[1]);
491 for (i = 2;i < MAX_MODELS && cl.model_name[i][0];i++)
492 if ((cl.model_precache[i] = Mod_ForName(cl.model_name[i], false, false, false))->Draw == NULL)
493 Con_Printf("Model %s could not be found or downloaded\n", cl.model_name[i]);
495 // check memory integrity
496 Mem_CheckSentinelsGlobal();
498 // now that we have a world model, set up the world entity, renderer
500 cl.entities[0].render.model = cl.worldmodel = cl.model_precache[1];
501 CL_UpdateRenderEntity(&cl.entities[0].render);
505 // TODO: add pmodel/emodel player.mdl/eyes.mdl CRCs to userinfo
507 // done checking sounds and models, send a prespawn command now
508 MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
509 MSG_WriteString(&cls.netcon->message, va("prespawn %i 0 %i", cl.qw_servercount, cl.model_precache[1]->brush.qw_md4sum2));
511 if (cls.qw_downloadmemory)
513 Mem_Free(cls.qw_downloadmemory);
514 cls.qw_downloadmemory = NULL;
518 cl.loadfinished = true;
521 if (cls.qw_downloadnumber == 0)
523 Con_Printf("Checking sounds...\n");
524 cls.qw_downloadnumber = 1;
527 for (;cl.sound_name[cls.qw_downloadnumber][0];cls.qw_downloadnumber++)
529 // check if we need to download the file, and return if so
530 if (!QW_CL_CheckOrDownloadFile(va("sound/%s", cl.sound_name[cls.qw_downloadnumber])))
534 cls.qw_downloadtype = dl_none;
536 // load new sounds and unload old ones
537 // FIXME: S_ServerSounds does not know about cl.sfx_ sounds
538 S_ServerSounds(cl.sound_name, cls.qw_downloadnumber);
540 // precache any sounds used by the client
541 cl.sfx_wizhit = S_PrecacheSound(cl_sound_wizardhit.string, false, true);
542 cl.sfx_knighthit = S_PrecacheSound(cl_sound_hknighthit.string, false, true);
543 cl.sfx_tink1 = S_PrecacheSound(cl_sound_tink1.string, false, true);
544 cl.sfx_ric1 = S_PrecacheSound(cl_sound_ric1.string, false, true);
545 cl.sfx_ric2 = S_PrecacheSound(cl_sound_ric2.string, false, true);
546 cl.sfx_ric3 = S_PrecacheSound(cl_sound_ric3.string, false, true);
547 cl.sfx_r_exp3 = S_PrecacheSound(cl_sound_r_exp3.string, false, true);
550 for (i = 1;i < MAX_SOUNDS && cl.sound_name[i][0];i++)
552 // Don't lock the sfx here, S_ServerSounds already did that
553 cl.sound_precache[i] = S_PrecacheSound(cl.sound_name[i], true, false);
556 // check memory integrity
557 Mem_CheckSentinelsGlobal();
559 // done with sound downloads, next we check models
560 MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
561 MSG_WriteString(&cls.netcon->message, va("modellist %i %i", cl.qw_servercount, 0));
565 Con_Printf("Unknown download type.\n");
569 static void QW_CL_ParseDownload(void)
571 int size = (signed short)MSG_ReadShort();
572 int percent = MSG_ReadByte();
574 //Con_Printf("download %i %i%% (%i/%i)\n", size, percent, cls.qw_downloadmemorycursize, cls.qw_downloadmemorymaxsize);
576 // skip the download fragment if playing a demo
580 msg_readcount += size;
586 Con_Printf("File not found.\n");
587 QW_CL_RequestNextDownload();
591 if (msg_readcount + (unsigned short)size > net_message.cursize)
592 Host_Error("corrupt download message\n");
594 // make sure the buffer is big enough to include this new fragment
595 if (!cls.qw_downloadmemory || cls.qw_downloadmemorymaxsize < cls.qw_downloadmemorycursize + size)
598 while (cls.qw_downloadmemorymaxsize < cls.qw_downloadmemorycursize + size)
599 cls.qw_downloadmemorymaxsize *= 2;
600 old = cls.qw_downloadmemory;
601 cls.qw_downloadmemory = (unsigned char *)Mem_Alloc(cls.permanentmempool, cls.qw_downloadmemorymaxsize);
604 memcpy(cls.qw_downloadmemory, old, cls.qw_downloadmemorycursize);
609 // read the fragment out of the packet
610 MSG_ReadBytes(size, cls.qw_downloadmemory + cls.qw_downloadmemorycursize);
611 cls.qw_downloadmemorycursize += size;
612 cls.qw_downloadspeedcount += size;
614 cls.qw_downloadpercent = percent;
618 // request next fragment
619 MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
620 MSG_WriteString(&cls.netcon->message, "nextdl");
625 Con_Printf("Downloaded \"%s\"\n", cls.qw_downloadname);
627 FS_WriteFile(cls.qw_downloadname, cls.qw_downloadmemory, cls.qw_downloadmemorycursize);
629 cls.qw_downloadpercent = 0;
631 // start downloading the next file (or join the game)
632 QW_CL_RequestNextDownload();
636 static void QW_CL_ParseModelList(void)
639 int nummodels = MSG_ReadByte();
642 // parse model precache list
645 str = MSG_ReadString();
649 if (nummodels==MAX_MODELS)
650 Host_Error("Server sent too many model precaches");
651 if (strlen(str) >= MAX_QPATH)
652 Host_Error("Server sent a precache name of %i characters (max %i)", (int)strlen(str), MAX_QPATH - 1);
653 strlcpy(cl.model_name[nummodels], str, sizeof (cl.model_name[nummodels]));
659 MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
660 MSG_WriteString(&cls.netcon->message, va("modellist %i %i", cl.qw_servercount, n));
665 cls.qw_downloadnumber = 0;
666 cls.qw_downloadtype = dl_model;
667 QW_CL_RequestNextDownload();
670 static void QW_CL_ParseSoundList(void)
673 int numsounds = MSG_ReadByte();
676 // parse sound precache list
679 str = MSG_ReadString();
683 if (numsounds==MAX_SOUNDS)
684 Host_Error("Server sent too many sound precaches");
685 if (strlen(str) >= MAX_QPATH)
686 Host_Error("Server sent a precache name of %i characters (max %i)", (int)strlen(str), MAX_QPATH - 1);
687 strlcpy(cl.sound_name[numsounds], str, sizeof (cl.sound_name[numsounds]));
694 MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
695 MSG_WriteString(&cls.netcon->message, va("soundlist %i %i", cl.qw_servercount, n));
700 cls.qw_downloadnumber = 0;
701 cls.qw_downloadtype = dl_sound;
702 QW_CL_RequestNextDownload();
705 static void QW_CL_Skins_f(void)
707 cls.qw_downloadnumber = 0;
708 cls.qw_downloadtype = dl_skin;
709 QW_CL_RequestNextDownload();
712 static void QW_CL_Changing_f(void)
714 if (cls.qw_downloadmemory) // don't change when downloading
719 cls.signon = 1; // not active anymore, but not disconnected
720 Con_Printf("\nChanging map...\n");
723 void QW_CL_NextUpload(void)
725 int r, percent, size;
727 if (!cls.qw_uploaddata)
730 r = cls.qw_uploadsize - cls.qw_uploadpos;
733 size = min(1, cls.qw_uploadsize);
734 percent = (cls.qw_uploadpos+r)*100/size;
736 MSG_WriteByte(&cls.netcon->message, qw_clc_upload);
737 MSG_WriteShort(&cls.netcon->message, r);
738 MSG_WriteByte(&cls.netcon->message, percent);
739 SZ_Write(&cls.netcon->message, cls.qw_uploaddata + cls.qw_uploadpos, r);
741 Con_DPrintf("UPLOAD: %6d: %d written\n", cls.qw_uploadpos, r);
743 cls.qw_uploadpos += r;
745 if (cls.qw_uploadpos < cls.qw_uploadsize)
748 Con_Printf("Upload completed\n");
753 void QW_CL_StartUpload(unsigned char *data, int size)
755 // do nothing in demos or if not connected
759 // abort existing upload if in progress
762 Con_DPrintf("Starting upload of %d bytes...\n", size);
764 cls.qw_uploaddata = (unsigned char *)Mem_Alloc(cls.permanentmempool, size);
765 memcpy(cls.qw_uploaddata, data, size);
766 cls.qw_uploadsize = size;
767 cls.qw_uploadpos = 0;
773 qboolean QW_CL_IsUploading(void)
775 return cls.qw_uploaddata != NULL;
779 void QW_CL_StopUpload(void)
781 if (cls.qw_uploaddata)
782 Mem_Free(cls.qw_uploaddata);
783 cls.qw_uploaddata = NULL;
784 cls.qw_uploadsize = 0;
785 cls.qw_uploadpos = 0;
788 static void QW_CL_ProcessUserInfo(int slot)
790 int topcolor, bottomcolor;
792 InfoString_GetValue(cl.scores[slot].qw_userinfo, "name", cl.scores[slot].name, sizeof(cl.scores[slot].name));
793 InfoString_GetValue(cl.scores[slot].qw_userinfo, "topcolor", temp, sizeof(temp));topcolor = atoi(temp);
794 InfoString_GetValue(cl.scores[slot].qw_userinfo, "bottomcolor", temp, sizeof(temp));bottomcolor = atoi(temp);
795 cl.scores[slot].colors = topcolor * 16 + bottomcolor;
796 InfoString_GetValue(cl.scores[slot].qw_userinfo, "*spectator", temp, sizeof(temp));
797 cl.scores[slot].qw_spectator = temp[0] != 0;
798 InfoString_GetValue(cl.scores[slot].qw_userinfo, "team", cl.scores[slot].qw_team, sizeof(cl.scores[slot].qw_team));
799 InfoString_GetValue(cl.scores[slot].qw_userinfo, "skin", cl.scores[slot].qw_skin, sizeof(cl.scores[slot].qw_skin));
800 if (!cl.scores[slot].qw_skin[0])
801 strlcpy(cl.scores[slot].qw_skin, "base", sizeof(cl.scores[slot].qw_skin));
805 static void QW_CL_UpdateUserInfo(void)
808 slot = MSG_ReadByte();
809 if (slot >= cl.maxclients)
811 Con_Printf("svc_updateuserinfo >= cl.maxclients\n");
816 cl.scores[slot].qw_userid = MSG_ReadLong();
817 strlcpy(cl.scores[slot].qw_userinfo, MSG_ReadString(), sizeof(cl.scores[slot].qw_userinfo));
819 QW_CL_ProcessUserInfo(slot);
822 static void QW_CL_SetInfo(void)
827 slot = MSG_ReadByte();
828 strlcpy(key, MSG_ReadString(), sizeof(key));
829 strlcpy(value, MSG_ReadString(), sizeof(value));
830 if (slot >= cl.maxclients)
832 Con_Printf("svc_setinfo >= cl.maxclients\n");
835 InfoString_SetValue(cl.scores[slot].qw_userinfo, sizeof(cl.scores[slot].qw_userinfo), key, value);
837 QW_CL_ProcessUserInfo(slot);
840 static void QW_CL_ServerInfo(void)
845 strlcpy(key, MSG_ReadString(), sizeof(key));
846 strlcpy(value, MSG_ReadString(), sizeof(value));
847 Con_DPrintf("SERVERINFO: %s=%s\n", key, value);
848 InfoString_SetValue(cl.qw_serverinfo, sizeof(cl.qw_serverinfo), key, value);
849 InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
850 cl.qw_teamplay = atoi(temp);
853 static void QW_CL_ParseNails(void)
856 int numnails = MSG_ReadByte();
858 unsigned char bits[6];
859 for (i = 0;i < numnails;i++)
861 for (j = 0;j < 6;j++)
862 bits[j] = MSG_ReadByte();
863 if (cl.qw_num_nails > 255)
865 v = cl.qw_nails[cl.qw_num_nails++];
866 v[0] = ( ( bits[0] + ((bits[1]&15)<<8) ) <<1) - 4096;
867 v[1] = ( ( (bits[1]>>4) + (bits[2]<<4) ) <<1) - 4096;
868 v[2] = ( ( bits[3] + ((bits[4]&15)<<8) ) <<1) - 4096;
869 v[3] = -360*(bits[4]>>4)/16;
870 v[4] = 360*bits[5]/256;
875 static void CL_UpdateItemsAndWeapon(void)
878 // check for important changes
881 if (cl.olditems != cl.stats[STAT_ITEMS])
882 for (j = 0;j < 32;j++)
883 if ((cl.stats[STAT_ITEMS] & (1<<j)) && !(cl.olditems & (1<<j)))
884 cl.item_gettime[j] = cl.time;
885 cl.olditems = cl.stats[STAT_ITEMS];
887 // GAME_NEXUIZ hud needs weapon change time
888 if (cl.activeweapon != cl.stats[STAT_ACTIVEWEAPON])
889 cl.weapontime = cl.time;
890 cl.activeweapon = cl.stats[STAT_ACTIVEWEAPON];
893 void CL_BeginDownloads(qboolean aborteddownload)
895 // quakeworld works differently
896 if (cls.protocol == PROTOCOL_QUAKEWORLD)
899 // TODO: this would be a good place to do curl downloads
904 cl.downloadcsqc = false;
907 && csqc_progname.string
908 && csqc_progname.string[0]
909 && csqc_progcrc.integer >= 0
910 && cl_serverextension_download.integer
911 && (FS_CRCFile(csqc_progname.string, &progsize) != csqc_progcrc.integer || ((int)progsize != csqc_progsize.integer && csqc_progsize.integer != -1))
912 && !FS_FileExists(va("dlcache/%s.%i.%i", csqc_progname.string, csqc_progsize.integer, csqc_progcrc.integer)))
913 Cmd_ForwardStringToServer(va("download %s", csqc_progname.string));
916 if (cl.loadmodel_current < cl.loadmodel_total)
920 for (;cl.loadmodel_current < cl.loadmodel_total;cl.loadmodel_current++)
922 if (cl.model_precache[cl.loadmodel_current] && cl.model_precache[cl.loadmodel_current]->Draw)
924 if (cls.signon < SIGNONS)
925 CL_KeepaliveMessage(true);
926 cl.model_precache[cl.loadmodel_current] = Mod_ForName(cl.model_name[cl.loadmodel_current], false, false, cl.loadmodel_current == 1);
927 if (cl.model_precache[cl.loadmodel_current] && cl.model_precache[cl.loadmodel_current]->Draw && cl.loadmodel_current == 1)
929 // we now have the worldmodel so we can set up the game world
930 cl.entities[0].render.model = cl.worldmodel = cl.model_precache[1];
931 CL_UpdateRenderEntity(&cl.entities[0].render);
933 // check memory integrity
934 Mem_CheckSentinelsGlobal();
935 if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer)
937 cl.loadfinished = true;
938 // now issue the spawn to move on to signon 3 like normal
940 Cmd_ForwardStringToServer("spawn");
945 // finished loading models
948 if (cl.loadsound_current < cl.loadsound_total)
952 for (;cl.loadsound_current < cl.loadsound_total;cl.loadsound_current++)
954 if (cl.sound_precache[cl.loadsound_current] && S_IsSoundPrecached(cl.sound_precache[cl.loadsound_current]))
956 if (cls.signon < SIGNONS)
957 CL_KeepaliveMessage(true);
958 // Don't lock the sfx here, S_ServerSounds already did that
959 cl.sound_precache[cl.loadsound_current] = S_PrecacheSound(cl.sound_name[cl.loadsound_current], false, false);
962 // finished loading sounds
965 // note: the reason these loops skip already-loaded things is that it
966 // enables this command to be issued during the game if desired
968 if (cl.downloadmodel_current < cl.loadmodel_total)
972 for (;cl.downloadmodel_current < cl.loadmodel_total;cl.downloadmodel_current++)
976 if (cl.downloadmodel_current == 1)
978 // the worldmodel failed, but we need to set up anyway
979 cl.entities[0].render.model = cl.worldmodel = cl.model_precache[1];
980 CL_UpdateRenderEntity(&cl.entities[0].render);
982 // check memory integrity
983 Mem_CheckSentinelsGlobal();
984 if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer)
986 cl.loadfinished = true;
987 // now issue the spawn to move on to signon 3 like normal
989 Cmd_ForwardStringToServer("spawn");
992 aborteddownload = false;
995 if (cl.model_precache[cl.downloadmodel_current] && cl.model_precache[cl.downloadmodel_current]->Draw)
997 if (cls.signon < SIGNONS)
998 CL_KeepaliveMessage(true);
999 if (!FS_FileExists(cl.model_name[cl.downloadmodel_current]))
1001 if (cl.downloadmodel_current == 1)
1002 Con_Printf("Map %s not found\n", cl.model_name[cl.downloadmodel_current]);
1004 Con_Printf("Model %s not found\n", cl.model_name[cl.downloadmodel_current]);
1005 // regarding the * check: don't try to download submodels
1006 if (cl_serverextension_download.integer && cls.netcon && cl.model_name[cl.downloadmodel_current][0] != '*' && !sv.active)
1008 Cmd_ForwardStringToServer(va("download %s", cl.model_name[cl.downloadmodel_current]));
1009 // we'll try loading again when the download finishes
1013 cl.model_precache[cl.downloadmodel_current] = Mod_ForName(cl.model_name[cl.downloadmodel_current], false, false, cl.downloadmodel_current == 1);
1014 if (cl.downloadmodel_current == 1)
1016 // we now have the worldmodel so we can set up the game world
1017 cl.entities[0].render.model = cl.worldmodel = cl.model_precache[1];
1018 CL_UpdateRenderEntity(&cl.entities[0].render);
1020 // check memory integrity
1021 Mem_CheckSentinelsGlobal();
1022 if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer)
1024 cl.loadfinished = true;
1025 // now issue the spawn to move on to signon 3 like normal
1027 Cmd_ForwardStringToServer("spawn");
1032 // finished loading models
1035 if (cl.downloadsound_current < cl.loadsound_total)
1039 for (;cl.downloadsound_current < cl.loadsound_total;cl.downloadsound_current++)
1041 char soundname[MAX_QPATH];
1042 if (aborteddownload)
1044 aborteddownload = false;
1047 if (cl.sound_precache[cl.downloadsound_current] && S_IsSoundPrecached(cl.sound_precache[cl.downloadsound_current]))
1049 if (cls.signon < SIGNONS)
1050 CL_KeepaliveMessage(true);
1051 dpsnprintf(soundname, sizeof(soundname), "sound/%s", cl.sound_name[cl.downloadsound_current]);
1052 if (!FS_FileExists(soundname) && !FS_FileExists(cl.sound_name[cl.downloadsound_current]))
1054 Con_Printf("Sound %s not found\n", soundname);
1055 if (cl_serverextension_download.integer && cls.netcon && !sv.active)
1057 Cmd_ForwardStringToServer(va("download %s", soundname));
1058 // we'll try loading again when the download finishes
1062 // Don't lock the sfx here, S_ServerSounds already did that
1063 cl.sound_precache[cl.downloadsound_current] = S_PrecacheSound(cl.sound_name[cl.downloadsound_current], false, false);
1066 // finished loading sounds
1069 if (!cl.loadfinished)
1071 cl.loadfinished = true;
1073 // check memory integrity
1074 Mem_CheckSentinelsGlobal();
1076 // now issue the spawn to move on to signon 3 like normal
1078 Cmd_ForwardStringToServer("spawn");
1082 void CL_BeginDownloads_f(void)
1084 CL_BeginDownloads(false);
1087 void CL_StopDownload(int size, int crc)
1089 if (cls.qw_downloadmemory && cls.qw_downloadmemorycursize == size && CRC_Block(cls.qw_downloadmemory, size) == crc)
1092 size_t existingsize;
1093 const char *extension;
1096 // save to disk only if we don't already have it
1097 // (this is mainly for playing back demos)
1098 existingcrc = FS_CRCFile(cls.qw_downloadname, &existingsize);
1101 if ((int)existingsize != size || existingcrc != crc)
1103 // we have a mismatching file, pick another name for it
1104 char name[MAX_QPATH*2];
1105 dpsnprintf(name, sizeof(name), "dlcache/%s.%i.%i", cls.qw_downloadname, size, crc);
1106 if (!FS_FileExists(name))
1108 Con_Printf("Downloaded \"%s\" (%i bytes, %i CRC)\n", name, size, crc);
1109 FS_WriteFile(name, cls.qw_downloadmemory, cls.qw_downloadmemorycursize);
1115 // we either don't have it or have a mismatching file...
1116 // so it's time to accept the file
1117 // but if we already have a mismatching file we need to rename
1118 // this new one, and if we already have this file in renamed form,
1120 Con_Printf("Downloaded \"%s\" (%i bytes, %i CRC)\n", cls.qw_downloadname, size, crc);
1121 FS_WriteFile(cls.qw_downloadname, cls.qw_downloadmemory, cls.qw_downloadmemorycursize);
1122 extension = FS_FileExtension(cls.qw_downloadname);
1123 if (!strcasecmp(extension, "pak") || !strcasecmp(extension, "pk3"))
1128 if (cls.qw_downloadmemory)
1129 Mem_Free(cls.qw_downloadmemory);
1130 cls.qw_downloadmemory = NULL;
1131 cls.qw_downloadname[0] = 0;
1132 cls.qw_downloadmemorymaxsize = 0;
1133 cls.qw_downloadmemorycursize = 0;
1134 cls.qw_downloadpercent = 0;
1137 void CL_ParseDownload(void)
1140 unsigned char data[65536];
1141 start = MSG_ReadLong();
1142 size = (unsigned short)MSG_ReadShort();
1144 // record the start/size information to ack in the next input packet
1145 for (i = 0;i < CL_MAX_DOWNLOADACKS;i++)
1147 if (!cls.dp_downloadack[i].start && !cls.dp_downloadack[i].size)
1149 cls.dp_downloadack[i].start = start;
1150 cls.dp_downloadack[i].size = size;
1155 MSG_ReadBytes(size, data);
1157 if (!cls.qw_downloadname[0])
1160 Con_Printf("CL_ParseDownload: received %i bytes with no download active\n", size);
1164 if (start + size > cls.qw_downloadmemorymaxsize)
1165 Host_Error("corrupt download message\n");
1167 // only advance cursize if the data is at the expected position
1168 // (gaps are unacceptable)
1169 memcpy(cls.qw_downloadmemory + start, data, size);
1170 cls.qw_downloadmemorycursize = start + size;
1171 cls.qw_downloadpercent = (int)floor((start+size) * 100.0 / cls.qw_downloadmemorymaxsize);
1172 cls.qw_downloadpercent = bound(0, cls.qw_downloadpercent, 100);
1173 cls.qw_downloadspeedcount += size;
1176 void CL_DownloadBegin_f(void)
1178 int size = atoi(Cmd_Argv(1));
1180 if (size < 0 || size > 1<<30 || FS_CheckNastyPath(Cmd_Argv(2), false))
1182 Con_Printf("cl_downloadbegin: received bogus information\n");
1183 CL_StopDownload(0, 0);
1187 if (cls.qw_downloadname[0])
1188 Con_Printf("Download of %s aborted\n", cls.qw_downloadname);
1190 CL_StopDownload(0, 0);
1192 // we're really beginning a download now, so initialize stuff
1193 strlcpy(cls.qw_downloadname, Cmd_Argv(2), sizeof(cls.qw_downloadname));
1194 cls.qw_downloadmemorymaxsize = size;
1195 cls.qw_downloadmemory = Mem_Alloc(cls.permanentmempool, cls.qw_downloadmemorymaxsize);
1196 cls.qw_downloadnumber++;
1198 Cmd_ForwardStringToServer("sv_startdownload");
1201 void CL_StopDownload_f(void)
1203 if (cls.qw_downloadname[0])
1205 Con_Printf("Download of %s aborted\n", cls.qw_downloadname);
1206 CL_StopDownload(0, 0);
1208 CL_BeginDownloads(true);
1211 void CL_DownloadFinished_f(void)
1215 Con_Printf("Malformed cl_downloadfinished command\n");
1218 CL_StopDownload(atoi(Cmd_Argv(1)), atoi(Cmd_Argv(2)));
1219 CL_BeginDownloads(false);
1223 =====================
1226 An svc_signonnum has been received, perform a client side setup
1227 =====================
1229 static void CL_SignonReply (void)
1231 Con_DPrintf("CL_SignonReply: %i\n", cls.signon);
1238 MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
1239 MSG_WriteString (&cls.netcon->message, "prespawn");
1241 else // playing a demo... make sure loading occurs as soon as possible
1242 CL_BeginDownloads(false);
1248 MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
1249 MSG_WriteString (&cls.netcon->message, va("name \"%s\"", cl_name.string));
1251 MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
1252 MSG_WriteString (&cls.netcon->message, va("color %i %i", cl_color.integer >> 4, cl_color.integer & 15));
1254 if (cl_pmodel.integer)
1256 MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
1257 MSG_WriteString (&cls.netcon->message, va("pmodel %i", cl_pmodel.integer));
1259 if (*cl_playermodel.string)
1261 MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
1262 MSG_WriteString (&cls.netcon->message, va("playermodel %s", cl_playermodel.string));
1264 if (*cl_playerskin.string)
1266 MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
1267 MSG_WriteString (&cls.netcon->message, va("playerskin %s", cl_playerskin.string));
1270 MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
1271 MSG_WriteString (&cls.netcon->message, va("rate %i", cl_rate.integer));
1273 // LordHavoc: changed to begin a loading stage and issue this when done
1274 //MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
1275 //MSG_WriteString (&cls.netcon->message, "spawn");
1277 // execute cl_begindownloads next frame after this message is sent
1278 // (so that the server can see the player name while downloading)
1279 Cbuf_AddText("\ncl_begindownloads\n");
1286 MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
1287 MSG_WriteString (&cls.netcon->message, "begin");
1302 void CL_ParseServerInfo (void)
1306 protocolversion_t protocol;
1307 int nummodels, numsounds;
1309 Con_DPrint("Serverinfo packet received.\n");
1311 // if server is active, we already began a loading plaque
1313 SCR_BeginLoadingPlaque();
1315 // check memory integrity
1316 Mem_CheckSentinelsGlobal();
1318 // clear cl_serverextension cvars
1319 Cvar_SetValueQuick(&cl_serverextension_download, 0);
1322 // wipe the client_state_t struct
1326 // parse protocol version number
1327 i = MSG_ReadLong ();
1328 protocol = Protocol_EnumForNumber(i);
1329 if (protocol == PROTOCOL_UNKNOWN)
1331 Host_Error("CL_ParseServerInfo: Server is unrecognized protocol number (%i)", i);
1334 // hack for unmarked Nehahra movie demos which had a custom protocol
1335 if (protocol == PROTOCOL_QUAKEDP && cls.demoplayback && demo_nehahra.integer)
1336 protocol = PROTOCOL_NEHAHRAMOVIE;
1337 cls.protocol = protocol;
1338 Con_DPrintf("Server protocol is %s\n", Protocol_NameForEnum(cls.protocol));
1340 cl.num_entities = 1;
1342 if (protocol == PROTOCOL_QUAKEWORLD)
1344 char gamedir[1][MAX_QPATH];
1346 cl.qw_servercount = MSG_ReadLong();
1348 str = MSG_ReadString();
1349 Con_Printf("server gamedir is %s\n", str);
1350 strlcpy(gamedir[0], str, sizeof(gamedir[0]));
1352 // change gamedir if needed
1353 if (!FS_ChangeGameDirs(1, gamedir, true, false))
1354 Host_Error("CL_ParseServerInfo: unable to switch to server specified gamedir");
1356 cl.gametype = GAME_DEATHMATCH;
1359 // parse player number
1361 // cl.qw_spectator is an unneeded flag, cl.scores[cl.playerentity].qw_spectator works better (it can be updated by the server during the game)
1362 //cl.qw_spectator = (i & 128) != 0;
1363 cl.playerentity = cl.viewentity = (i & 127) + 1;
1364 cl.scores = (scoreboard_t *)Mem_Alloc(cls.levelmempool, cl.maxclients*sizeof(*cl.scores));
1366 // get the full level name
1367 str = MSG_ReadString ();
1368 strlcpy (cl.levelname, str, sizeof(cl.levelname));
1371 cl.qw_movevars_gravity = MSG_ReadFloat();
1372 cl.qw_movevars_stopspeed = MSG_ReadFloat();
1373 cl.qw_movevars_maxspeed = MSG_ReadFloat();
1374 cl.qw_movevars_spectatormaxspeed = MSG_ReadFloat();
1375 cl.qw_movevars_accelerate = MSG_ReadFloat();
1376 cl.qw_movevars_airaccelerate = MSG_ReadFloat();
1377 cl.qw_movevars_wateraccelerate = MSG_ReadFloat();
1378 cl.qw_movevars_friction = MSG_ReadFloat();
1379 cl.qw_movevars_waterfriction = MSG_ReadFloat();
1380 cl.qw_movevars_entgravity = MSG_ReadFloat();
1382 // seperate the printfs so the server message can have a color
1383 Con_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\2%s\n", str);
1385 // check memory integrity
1386 Mem_CheckSentinelsGlobal();
1390 MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
1391 MSG_WriteString(&cls.netcon->message, va("soundlist %i %i", cl.qw_servercount, 0));
1394 cl.loadfinished = false;
1396 cls.state = ca_connected;
1399 // note: on QW protocol we can't set up the gameworld until after
1400 // downloads finish...
1401 // (we don't even know the name of the map yet)
1406 cl.maxclients = MSG_ReadByte ();
1407 if (cl.maxclients < 1 || cl.maxclients > MAX_SCOREBOARD)
1409 Host_Error("Bad maxclients (%u) from server", cl.maxclients);
1412 cl.scores = (scoreboard_t *)Mem_Alloc(cls.levelmempool, cl.maxclients*sizeof(*cl.scores));
1415 cl.gametype = MSG_ReadByte ();
1417 // parse signon message
1418 str = MSG_ReadString ();
1419 strlcpy (cl.levelname, str, sizeof(cl.levelname));
1421 // seperate the printfs so the server message can have a color
1422 if (cls.protocol != PROTOCOL_NEHAHRAMOVIE) // no messages when playing the Nehahra movie
1423 Con_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\2%s\n", str);
1425 // check memory integrity
1426 Mem_CheckSentinelsGlobal();
1428 // parse model precache list
1429 for (nummodels=1 ; ; nummodels++)
1431 str = MSG_ReadString();
1434 if (nummodels==MAX_MODELS)
1435 Host_Error ("Server sent too many model precaches");
1436 if (strlen(str) >= MAX_QPATH)
1437 Host_Error ("Server sent a precache name of %i characters (max %i)", (int)strlen(str), MAX_QPATH - 1);
1438 strlcpy (cl.model_name[nummodels], str, sizeof (cl.model_name[nummodels]));
1440 // parse sound precache list
1441 for (numsounds=1 ; ; numsounds++)
1443 str = MSG_ReadString();
1446 if (numsounds==MAX_SOUNDS)
1447 Host_Error("Server sent too many sound precaches");
1448 if (strlen(str) >= MAX_QPATH)
1449 Host_Error("Server sent a precache name of %i characters (max %i)", (int)strlen(str), MAX_QPATH - 1);
1450 strlcpy (cl.sound_name[numsounds], str, sizeof (cl.sound_name[numsounds]));
1453 // touch all of the precached models that are still loaded so we can free
1454 // anything that isn't needed
1456 for (i = 1;i < nummodels;i++)
1457 Mod_FindName(cl.model_name[i]);
1458 // precache any models used by the client (this also marks them used)
1459 cl.model_bolt = Mod_ForName("progs/bolt.mdl", false, false, false);
1460 cl.model_bolt2 = Mod_ForName("progs/bolt2.mdl", false, false, false);
1461 cl.model_bolt3 = Mod_ForName("progs/bolt3.mdl", false, false, false);
1462 cl.model_beam = Mod_ForName("progs/beam.mdl", false, false, false);
1465 // do the same for sounds
1466 // FIXME: S_ServerSounds does not know about cl.sfx_ sounds
1467 S_ServerSounds (cl.sound_name, numsounds);
1469 // precache any sounds used by the client
1470 cl.sfx_wizhit = S_PrecacheSound(cl_sound_wizardhit.string, false, true);
1471 cl.sfx_knighthit = S_PrecacheSound(cl_sound_hknighthit.string, false, true);
1472 cl.sfx_tink1 = S_PrecacheSound(cl_sound_tink1.string, false, true);
1473 cl.sfx_ric1 = S_PrecacheSound(cl_sound_ric1.string, false, true);
1474 cl.sfx_ric2 = S_PrecacheSound(cl_sound_ric2.string, false, true);
1475 cl.sfx_ric3 = S_PrecacheSound(cl_sound_ric3.string, false, true);
1476 cl.sfx_r_exp3 = S_PrecacheSound(cl_sound_r_exp3.string, false, true);
1478 // now we try to load everything that is new
1479 cl.loadmodel_current = 1;
1480 cl.downloadmodel_current = 1;
1481 cl.loadmodel_total = nummodels;
1482 cl.loadsound_current = 1;
1483 cl.downloadsound_current = 1;
1484 cl.loadsound_total = numsounds;
1485 cl.downloadcsqc = true;
1486 cl.loadfinished = false;
1489 // check memory integrity
1490 Mem_CheckSentinelsGlobal();
1493 void CL_ValidateState(entity_state_t *s)
1500 if (s->modelindex >= MAX_MODELS)
1501 Host_Error("CL_ValidateState: modelindex (%i) >= MAX_MODELS (%i)\n", s->modelindex, MAX_MODELS);
1503 // colormap is client index + 1
1504 if ((!s->flags & RENDER_COLORMAPPED) && s->colormap > cl.maxclients)
1506 Con_DPrintf("CL_ValidateState: colormap (%i) > cl.maxclients (%i)\n", s->colormap, cl.maxclients);
1510 model = cl.model_precache[s->modelindex];
1511 if (model && model->type && s->frame >= model->numframes)
1513 Con_DPrintf("CL_ValidateState: no such frame %i in \"%s\" (which has %i frames)\n", s->frame, model->name, model->numframes);
1516 if (model && model->type && s->skin > 0 && s->skin >= model->numskins && !(s->lightpflags & PFLAGS_FULLDYNAMIC))
1518 Con_DPrintf("CL_ValidateState: no such skin %i in \"%s\" (which has %i skins)\n", s->skin, model->name, model->numskins);
1523 void CL_MoveLerpEntityStates(entity_t *ent)
1525 float odelta[3], adelta[3];
1526 CL_ValidateState(&ent->state_current);
1527 VectorSubtract(ent->state_current.origin, ent->persistent.neworigin, odelta);
1528 VectorSubtract(ent->state_current.angles, ent->persistent.newangles, adelta);
1529 if (!ent->state_previous.active || ent->state_previous.modelindex != ent->state_current.modelindex)
1531 // reset all persistent stuff if this is a new entity
1532 ent->persistent.lerpdeltatime = 0;
1533 ent->persistent.lerpstarttime = cl.mtime[1];
1534 VectorCopy(ent->state_current.origin, ent->persistent.oldorigin);
1535 VectorCopy(ent->state_current.angles, ent->persistent.oldangles);
1536 VectorCopy(ent->state_current.origin, ent->persistent.neworigin);
1537 VectorCopy(ent->state_current.angles, ent->persistent.newangles);
1538 // reset animation interpolation as well
1539 ent->render.frame = ent->render.frame1 = ent->render.frame2 = ent->state_current.frame;
1540 ent->render.frame1time = ent->render.frame2time = cl.time;
1541 ent->render.framelerp = 1;
1542 // reset various persistent stuff
1543 ent->persistent.muzzleflash = 0;
1544 VectorCopy(ent->state_current.origin, ent->persistent.trail_origin);
1546 else if (cls.timedemo || cl_nolerp.integer || DotProduct(odelta, odelta) > 1000*1000 || (cl.fixangle[0] && !cl.fixangle[1]))
1548 // don't interpolate the move
1549 // (the fixangle[] check detects teleports, but not constant fixangles
1550 // such as when spectating)
1551 ent->persistent.lerpdeltatime = 0;
1552 ent->persistent.lerpstarttime = cl.mtime[1];
1553 VectorCopy(ent->state_current.origin, ent->persistent.oldorigin);
1554 VectorCopy(ent->state_current.angles, ent->persistent.oldangles);
1555 VectorCopy(ent->state_current.origin, ent->persistent.neworigin);
1556 VectorCopy(ent->state_current.angles, ent->persistent.newangles);
1558 else if (ent->state_current.flags & RENDER_STEP)
1560 // monster interpolation
1561 if (DotProduct(odelta, odelta) + DotProduct(adelta, adelta) > 0.01)
1563 ent->persistent.lerpdeltatime = bound(0, cl.mtime[1] - ent->persistent.lerpstarttime, 0.1);
1564 ent->persistent.lerpstarttime = cl.mtime[1];
1565 VectorCopy(ent->persistent.neworigin, ent->persistent.oldorigin);
1566 VectorCopy(ent->persistent.newangles, ent->persistent.oldangles);
1567 VectorCopy(ent->state_current.origin, ent->persistent.neworigin);
1568 VectorCopy(ent->state_current.angles, ent->persistent.newangles);
1574 ent->persistent.lerpstarttime = ent->state_previous.time;
1575 // no lerp if it's singleplayer
1576 if (cl.islocalgame && !sv_fixedframeratesingleplayer.integer)
1577 ent->persistent.lerpdeltatime = 0;
1579 ent->persistent.lerpdeltatime = bound(0, ent->state_current.time - ent->state_previous.time, 0.1);
1580 VectorCopy(ent->persistent.neworigin, ent->persistent.oldorigin);
1581 VectorCopy(ent->persistent.newangles, ent->persistent.oldangles);
1582 VectorCopy(ent->state_current.origin, ent->persistent.neworigin);
1583 VectorCopy(ent->state_current.angles, ent->persistent.newangles);
1585 // trigger muzzleflash effect if necessary
1586 if (ent->state_current.effects & EF_MUZZLEFLASH)
1587 ent->persistent.muzzleflash = 1;
1595 void CL_ParseBaseline (entity_t *ent, int large)
1599 ent->state_baseline = defaultstate;
1600 // FIXME: set ent->state_baseline.number?
1601 ent->state_baseline.active = true;
1604 ent->state_baseline.modelindex = (unsigned short) MSG_ReadShort ();
1605 ent->state_baseline.frame = (unsigned short) MSG_ReadShort ();
1609 ent->state_baseline.modelindex = MSG_ReadByte ();
1610 ent->state_baseline.frame = MSG_ReadByte ();
1612 ent->state_baseline.colormap = MSG_ReadByte();
1613 ent->state_baseline.skin = MSG_ReadByte();
1614 for (i = 0;i < 3;i++)
1616 ent->state_baseline.origin[i] = MSG_ReadCoord(cls.protocol);
1617 ent->state_baseline.angles[i] = MSG_ReadAngle(cls.protocol);
1619 ent->state_previous = ent->state_current = ent->state_baseline;
1627 Server information pertaining to this client only
1630 void CL_ParseClientdata (void)
1634 VectorCopy (cl.mpunchangle[0], cl.mpunchangle[1]);
1635 VectorCopy (cl.mpunchvector[0], cl.mpunchvector[1]);
1636 VectorCopy (cl.mvelocity[0], cl.mvelocity[1]);
1637 cl.mviewzoom[1] = cl.mviewzoom[0];
1639 if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE || cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4 || cls.protocol == PROTOCOL_DARKPLACES5)
1641 cl.stats[STAT_VIEWHEIGHT] = DEFAULT_VIEWHEIGHT;
1642 cl.stats[STAT_ITEMS] = 0;
1643 cl.stats[STAT_VIEWZOOM] = 255;
1646 cl.mpunchangle[0][0] = 0;
1647 cl.mpunchangle[0][1] = 0;
1648 cl.mpunchangle[0][2] = 0;
1649 cl.mpunchvector[0][0] = 0;
1650 cl.mpunchvector[0][1] = 0;
1651 cl.mpunchvector[0][2] = 0;
1652 cl.mvelocity[0][0] = 0;
1653 cl.mvelocity[0][1] = 0;
1654 cl.mvelocity[0][2] = 0;
1655 cl.mviewzoom[0] = 1;
1657 bits = (unsigned short) MSG_ReadShort ();
1658 if (bits & SU_EXTEND1)
1659 bits |= (MSG_ReadByte() << 16);
1660 if (bits & SU_EXTEND2)
1661 bits |= (MSG_ReadByte() << 24);
1663 if (bits & SU_VIEWHEIGHT)
1664 cl.stats[STAT_VIEWHEIGHT] = MSG_ReadChar ();
1666 if (bits & SU_IDEALPITCH)
1667 cl.idealpitch = MSG_ReadChar ();
1669 for (i = 0;i < 3;i++)
1671 if (bits & (SU_PUNCH1<<i) )
1673 if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE)
1674 cl.mpunchangle[0][i] = MSG_ReadChar();
1676 cl.mpunchangle[0][i] = MSG_ReadAngle16i();
1678 if (bits & (SU_PUNCHVEC1<<i))
1680 if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4)
1681 cl.mpunchvector[0][i] = MSG_ReadCoord16i();
1683 cl.mpunchvector[0][i] = MSG_ReadCoord32f();
1685 if (bits & (SU_VELOCITY1<<i) )
1687 if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE || cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4)
1688 cl.mvelocity[0][i] = MSG_ReadChar()*16;
1690 cl.mvelocity[0][i] = MSG_ReadCoord32f();
1694 // LordHavoc: hipnotic demos don't have this bit set but should
1695 if (bits & SU_ITEMS || cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE || cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4 || cls.protocol == PROTOCOL_DARKPLACES5)
1696 cl.stats[STAT_ITEMS] = MSG_ReadLong ();
1698 cl.onground = (bits & SU_ONGROUND) != 0;
1699 cl.inwater = (bits & SU_INWATER) != 0;
1701 if (cls.protocol == PROTOCOL_DARKPLACES5)
1703 cl.stats[STAT_WEAPONFRAME] = (bits & SU_WEAPONFRAME) ? MSG_ReadShort() : 0;
1704 cl.stats[STAT_ARMOR] = (bits & SU_ARMOR) ? MSG_ReadShort() : 0;
1705 cl.stats[STAT_WEAPON] = (bits & SU_WEAPON) ? MSG_ReadShort() : 0;
1706 cl.stats[STAT_HEALTH] = MSG_ReadShort();
1707 cl.stats[STAT_AMMO] = MSG_ReadShort();
1708 cl.stats[STAT_SHELLS] = MSG_ReadShort();
1709 cl.stats[STAT_NAILS] = MSG_ReadShort();
1710 cl.stats[STAT_ROCKETS] = MSG_ReadShort();
1711 cl.stats[STAT_CELLS] = MSG_ReadShort();
1712 cl.stats[STAT_ACTIVEWEAPON] = (unsigned short) MSG_ReadShort ();
1714 else if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE || cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4)
1716 cl.stats[STAT_WEAPONFRAME] = (bits & SU_WEAPONFRAME) ? MSG_ReadByte() : 0;
1717 cl.stats[STAT_ARMOR] = (bits & SU_ARMOR) ? MSG_ReadByte() : 0;
1718 cl.stats[STAT_WEAPON] = (bits & SU_WEAPON) ? MSG_ReadByte() : 0;
1719 cl.stats[STAT_HEALTH] = MSG_ReadShort();
1720 cl.stats[STAT_AMMO] = MSG_ReadByte();
1721 cl.stats[STAT_SHELLS] = MSG_ReadByte();
1722 cl.stats[STAT_NAILS] = MSG_ReadByte();
1723 cl.stats[STAT_ROCKETS] = MSG_ReadByte();
1724 cl.stats[STAT_CELLS] = MSG_ReadByte();
1725 if (gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE || gamemode == GAME_NEXUIZ)
1726 cl.stats[STAT_ACTIVEWEAPON] = (1<<MSG_ReadByte ());
1728 cl.stats[STAT_ACTIVEWEAPON] = MSG_ReadByte ();
1731 if (bits & SU_VIEWZOOM)
1733 if (cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4)
1734 cl.stats[STAT_VIEWZOOM] = MSG_ReadByte();
1736 cl.stats[STAT_VIEWZOOM] = (unsigned short) MSG_ReadShort();
1739 // viewzoom interpolation
1740 cl.mviewzoom[0] = (float) max(cl.stats[STAT_VIEWZOOM], 2) * (1.0f / 255.0f);
1744 =====================
1746 =====================
1748 void CL_ParseStatic (int large)
1752 if (cl.num_static_entities >= cl.max_static_entities)
1753 Host_Error ("Too many static entities");
1754 ent = &cl.static_entities[cl.num_static_entities++];
1755 CL_ParseBaseline (ent, large);
1757 // copy it to the current state
1758 ent->render.model = cl.model_precache[ent->state_baseline.modelindex];
1759 ent->render.frame = ent->render.frame1 = ent->render.frame2 = ent->state_baseline.frame;
1760 ent->render.framelerp = 0;
1761 // make torchs play out of sync
1762 ent->render.frame1time = ent->render.frame2time = lhrandom(-10, -1);
1763 ent->render.colormap = -1; // no special coloring
1764 ent->render.skinnum = ent->state_baseline.skin;
1765 ent->render.effects = ent->state_baseline.effects;
1766 ent->render.alpha = 1;
1768 //VectorCopy (ent->state_baseline.origin, ent->render.origin);
1769 //VectorCopy (ent->state_baseline.angles, ent->render.angles);
1771 Matrix4x4_CreateFromQuakeEntity(&ent->render.matrix, ent->state_baseline.origin[0], ent->state_baseline.origin[1], ent->state_baseline.origin[2], ent->state_baseline.angles[0], ent->state_baseline.angles[1], ent->state_baseline.angles[2], 1);
1772 CL_UpdateRenderEntity(&ent->render);
1774 // This is definitely a cheesy way to conserve resources...
1775 //if (ent->render.model == NULL)
1776 // cl.num_static_entities--;
1784 void CL_ParseStaticSound (int large)
1787 int sound_num, vol, atten;
1789 MSG_ReadVector(org, cls.protocol);
1791 sound_num = (unsigned short) MSG_ReadShort ();
1793 sound_num = MSG_ReadByte ();
1794 vol = MSG_ReadByte ();
1795 atten = MSG_ReadByte ();
1797 S_StaticSound (cl.sound_precache[sound_num], org, vol/255.0f, atten);
1800 void CL_ParseEffect (void)
1803 int modelindex, startframe, framecount, framerate;
1805 MSG_ReadVector(org, cls.protocol);
1806 modelindex = MSG_ReadByte ();
1807 startframe = MSG_ReadByte ();
1808 framecount = MSG_ReadByte ();
1809 framerate = MSG_ReadByte ();
1811 CL_Effect(org, modelindex, startframe, framecount, framerate);
1814 void CL_ParseEffect2 (void)
1817 int modelindex, startframe, framecount, framerate;
1819 MSG_ReadVector(org, cls.protocol);
1820 modelindex = (unsigned short) MSG_ReadShort ();
1821 startframe = (unsigned short) MSG_ReadShort ();
1822 framecount = MSG_ReadByte ();
1823 framerate = MSG_ReadByte ();
1825 CL_Effect(org, modelindex, startframe, framecount, framerate);
1828 void CL_NewBeam (int ent, vec3_t start, vec3_t end, model_t *m, int lightning)
1833 if (ent >= MAX_EDICTS)
1835 Con_Printf("CL_NewBeam: invalid entity number %i\n", ent);
1839 if (ent >= cl.max_entities)
1840 CL_ExpandEntities(ent);
1842 // override any beam with the same entity
1845 for (i = 0, b = cl.beams;i < cl.max_beams;i++, b++)
1846 if (b->entity == ent)
1848 // if the entity was not found then just replace an unused beam
1849 if (i == cl.max_beams)
1850 for (i = 0, b = cl.beams;i < cl.max_beams;i++, b++)
1853 if (i < cl.max_beams)
1855 cl.num_beams = max(cl.num_beams, i + 1);
1857 b->lightning = lightning;
1859 b->endtime = cl.mtime[0] + 0.2;
1860 VectorCopy (start, b->start);
1861 VectorCopy (end, b->end);
1864 Con_Print("beam list overflow!\n");
1867 void CL_ParseBeam (model_t *m, int lightning)
1872 ent = (unsigned short) MSG_ReadShort ();
1873 MSG_ReadVector(start, cls.protocol);
1874 MSG_ReadVector(end, cls.protocol);
1876 if (ent >= MAX_EDICTS)
1878 Con_Printf("CL_ParseBeam: invalid entity number %i\n", ent);
1882 CL_NewBeam(ent, start, end, m, lightning);
1885 void CL_ParseTempEntity(void)
1893 int colorStart, colorLength, count;
1894 float velspeed, radius;
1895 unsigned char *tempcolor;
1896 matrix4x4_t tempmatrix;
1898 if (cls.protocol == PROTOCOL_QUAKEWORLD)
1900 type = MSG_ReadByte();
1903 case QW_TE_WIZSPIKE:
1904 // spike hitting wall
1905 MSG_ReadVector(pos, cls.protocol);
1906 CL_FindNonSolidLocation(pos, pos, 4);
1907 CL_ParticleEffect(EFFECT_TE_WIZSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
1908 S_StartSound(-1, 0, cl.sfx_wizhit, pos, 1, 1);
1911 case QW_TE_KNIGHTSPIKE:
1912 // spike hitting wall
1913 MSG_ReadVector(pos, cls.protocol);
1914 CL_FindNonSolidLocation(pos, pos, 4);
1915 CL_ParticleEffect(EFFECT_TE_KNIGHTSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
1916 S_StartSound(-1, 0, cl.sfx_knighthit, pos, 1, 1);
1920 // spike hitting wall
1921 MSG_ReadVector(pos, cls.protocol);
1922 CL_FindNonSolidLocation(pos, pos, 4);
1923 CL_ParticleEffect(EFFECT_TE_SPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
1925 S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1);
1930 S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1);
1932 S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1);
1934 S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1);
1937 case QW_TE_SUPERSPIKE:
1938 // super spike hitting wall
1939 MSG_ReadVector(pos, cls.protocol);
1940 CL_FindNonSolidLocation(pos, pos, 4);
1941 CL_ParticleEffect(EFFECT_TE_SUPERSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
1943 S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1);
1948 S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1);
1950 S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1);
1952 S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1);
1956 case QW_TE_EXPLOSION:
1958 MSG_ReadVector(pos, cls.protocol);
1959 CL_FindNonSolidLocation(pos, pos, 10);
1960 CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
1961 S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1);
1962 CL_Effect(pos, cl.qw_modelindex_s_explod, 0, 6, 10);
1965 case QW_TE_TAREXPLOSION:
1966 // tarbaby explosion
1967 MSG_ReadVector(pos, cls.protocol);
1968 CL_FindNonSolidLocation(pos, pos, 10);
1969 CL_ParticleEffect(EFFECT_TE_TAREXPLOSION, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
1970 S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1);
1973 case QW_TE_LIGHTNING1:
1975 CL_ParseBeam(cl.model_bolt, true);
1978 case QW_TE_LIGHTNING2:
1980 CL_ParseBeam(cl.model_bolt2, true);
1983 case QW_TE_LIGHTNING3:
1985 CL_ParseBeam(cl.model_bolt3, false);
1988 case QW_TE_LAVASPLASH:
1989 MSG_ReadVector(pos, cls.protocol);
1990 CL_ParticleEffect(EFFECT_TE_LAVASPLASH, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
1993 case QW_TE_TELEPORT:
1994 MSG_ReadVector(pos, cls.protocol);
1995 CL_ParticleEffect(EFFECT_TE_TELEPORT, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
1999 // bullet hitting wall
2000 radius = MSG_ReadByte();
2001 MSG_ReadVector(pos, cls.protocol);
2002 CL_FindNonSolidLocation(pos, pos, 4);
2003 VectorSet(pos2, pos[0] + radius, pos[1] + radius, pos[2] + radius);
2004 VectorSet(pos, pos[0] - radius, pos[1] - radius, pos[2] - radius);
2005 CL_ParticleEffect(EFFECT_TE_GUNSHOT, radius, pos, pos2, vec3_origin, vec3_origin, NULL, 0);
2009 count = MSG_ReadByte();
2010 MSG_ReadVector(pos, cls.protocol);
2011 CL_FindNonSolidLocation(pos, pos, 4);
2012 CL_ParticleEffect(EFFECT_TE_BLOOD, count, pos, pos, vec3_origin, vec3_origin, NULL, 0);
2015 case QW_TE_LIGHTNINGBLOOD:
2016 MSG_ReadVector(pos, cls.protocol);
2017 CL_FindNonSolidLocation(pos, pos, 4);
2018 CL_ParticleEffect(EFFECT_TE_BLOOD, 2.5, pos, pos, vec3_origin, vec3_origin, NULL, 0);
2022 Host_Error("CL_ParseTempEntity: bad type %d (hex %02X)", type, type);
2027 type = MSG_ReadByte();
2031 // spike hitting wall
2032 MSG_ReadVector(pos, cls.protocol);
2033 CL_FindNonSolidLocation(pos, pos, 4);
2034 CL_ParticleEffect(EFFECT_TE_WIZSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
2035 S_StartSound(-1, 0, cl.sfx_wizhit, pos, 1, 1);
2038 case TE_KNIGHTSPIKE:
2039 // spike hitting wall
2040 MSG_ReadVector(pos, cls.protocol);
2041 CL_FindNonSolidLocation(pos, pos, 4);
2042 CL_ParticleEffect(EFFECT_TE_KNIGHTSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
2043 S_StartSound(-1, 0, cl.sfx_knighthit, pos, 1, 1);
2047 // spike hitting wall
2048 MSG_ReadVector(pos, cls.protocol);
2049 CL_FindNonSolidLocation(pos, pos, 4);
2050 CL_ParticleEffect(EFFECT_TE_SPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
2052 S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1);
2057 S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1);
2059 S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1);
2061 S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1);
2065 // quad spike hitting wall
2066 MSG_ReadVector(pos, cls.protocol);
2067 CL_FindNonSolidLocation(pos, pos, 4);
2068 CL_ParticleEffect(EFFECT_TE_SPIKEQUAD, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
2070 S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1);
2075 S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1);
2077 S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1);
2079 S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1);
2083 // super spike hitting wall
2084 MSG_ReadVector(pos, cls.protocol);
2085 CL_FindNonSolidLocation(pos, pos, 4);
2086 CL_ParticleEffect(EFFECT_TE_SUPERSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
2088 S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1);
2093 S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1);
2095 S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1);
2097 S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1);
2100 case TE_SUPERSPIKEQUAD:
2101 // quad super spike hitting wall
2102 MSG_ReadVector(pos, cls.protocol);
2103 CL_FindNonSolidLocation(pos, pos, 4);
2104 CL_ParticleEffect(EFFECT_TE_SUPERSPIKEQUAD, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
2106 S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1);
2111 S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1);
2113 S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1);
2115 S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1);
2118 // LordHavoc: added for improved blood splatters
2121 MSG_ReadVector(pos, cls.protocol);
2122 CL_FindNonSolidLocation(pos, pos, 4);
2123 dir[0] = MSG_ReadChar();
2124 dir[1] = MSG_ReadChar();
2125 dir[2] = MSG_ReadChar();
2126 count = MSG_ReadByte();
2127 CL_ParticleEffect(EFFECT_TE_BLOOD, count, pos, pos, dir, dir, NULL, 0);
2131 MSG_ReadVector(pos, cls.protocol);
2132 CL_FindNonSolidLocation(pos, pos, 4);
2133 dir[0] = MSG_ReadChar();
2134 dir[1] = MSG_ReadChar();
2135 dir[2] = MSG_ReadChar();
2136 count = MSG_ReadByte();
2137 CL_ParticleEffect(EFFECT_TE_SPARK, count, pos, pos, dir, dir, NULL, 0);
2140 MSG_ReadVector(pos, cls.protocol);
2141 CL_FindNonSolidLocation(pos, pos, 4);
2142 CL_ParticleEffect(EFFECT_TE_PLASMABURN, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
2144 // LordHavoc: added for improved gore
2145 case TE_BLOODSHOWER:
2147 MSG_ReadVector(pos, cls.protocol); // mins
2148 MSG_ReadVector(pos2, cls.protocol); // maxs
2149 velspeed = MSG_ReadCoord(cls.protocol); // speed
2150 count = (unsigned short) MSG_ReadShort(); // number of particles
2151 vel1[0] = -velspeed;
2152 vel1[1] = -velspeed;
2153 vel1[2] = -velspeed;
2157 CL_ParticleEffect(EFFECT_TE_BLOOD, count, pos, pos2, vel1, vel2, NULL, 0);
2160 case TE_PARTICLECUBE:
2161 // general purpose particle effect
2162 MSG_ReadVector(pos, cls.protocol); // mins
2163 MSG_ReadVector(pos2, cls.protocol); // maxs
2164 MSG_ReadVector(dir, cls.protocol); // dir
2165 count = (unsigned short) MSG_ReadShort(); // number of particles
2166 colorStart = MSG_ReadByte(); // color
2167 colorLength = MSG_ReadByte(); // gravity (1 or 0)
2168 velspeed = MSG_ReadCoord(cls.protocol); // randomvel
2169 CL_ParticleCube(pos, pos2, dir, count, colorStart, colorLength != 0, velspeed);
2172 case TE_PARTICLERAIN:
2173 // general purpose particle effect
2174 MSG_ReadVector(pos, cls.protocol); // mins
2175 MSG_ReadVector(pos2, cls.protocol); // maxs
2176 MSG_ReadVector(dir, cls.protocol); // dir
2177 count = (unsigned short) MSG_ReadShort(); // number of particles
2178 colorStart = MSG_ReadByte(); // color
2179 CL_ParticleRain(pos, pos2, dir, count, colorStart, 0);
2182 case TE_PARTICLESNOW:
2183 // general purpose particle effect
2184 MSG_ReadVector(pos, cls.protocol); // mins
2185 MSG_ReadVector(pos2, cls.protocol); // maxs
2186 MSG_ReadVector(dir, cls.protocol); // dir
2187 count = (unsigned short) MSG_ReadShort(); // number of particles
2188 colorStart = MSG_ReadByte(); // color
2189 CL_ParticleRain(pos, pos2, dir, count, colorStart, 1);
2193 // bullet hitting wall
2194 MSG_ReadVector(pos, cls.protocol);
2195 CL_FindNonSolidLocation(pos, pos, 4);
2196 CL_ParticleEffect(EFFECT_TE_GUNSHOT, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
2199 case TE_GUNSHOTQUAD:
2200 // quad bullet hitting wall
2201 MSG_ReadVector(pos, cls.protocol);
2202 CL_FindNonSolidLocation(pos, pos, 4);
2203 CL_ParticleEffect(EFFECT_TE_GUNSHOTQUAD, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
2208 MSG_ReadVector(pos, cls.protocol);
2209 CL_FindNonSolidLocation(pos, pos, 10);
2210 CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
2211 S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1);
2214 case TE_EXPLOSIONQUAD:
2215 // quad rocket explosion
2216 MSG_ReadVector(pos, cls.protocol);
2217 CL_FindNonSolidLocation(pos, pos, 10);
2218 CL_ParticleEffect(EFFECT_TE_EXPLOSIONQUAD, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
2219 S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1);
2223 // Nehahra movie colored lighting explosion
2224 MSG_ReadVector(pos, cls.protocol);
2225 CL_FindNonSolidLocation(pos, pos, 10);
2226 color[0] = MSG_ReadCoord(cls.protocol) * (2.0f / 1.0f);
2227 color[1] = MSG_ReadCoord(cls.protocol) * (2.0f / 1.0f);
2228 color[2] = MSG_ReadCoord(cls.protocol) * (2.0f / 1.0f);
2229 CL_ParticleExplosion(pos);
2230 Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]);
2231 CL_AllocLightFlash(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
2232 S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1);
2235 case TE_EXPLOSIONRGB:
2236 // colored lighting explosion
2237 MSG_ReadVector(pos, cls.protocol);
2238 CL_FindNonSolidLocation(pos, pos, 10);
2239 CL_ParticleExplosion(pos);
2240 color[0] = MSG_ReadByte() * (2.0f / 255.0f);
2241 color[1] = MSG_ReadByte() * (2.0f / 255.0f);
2242 color[2] = MSG_ReadByte() * (2.0f / 255.0f);
2243 Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]);
2244 CL_AllocLightFlash(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
2245 S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1);
2248 case TE_TAREXPLOSION:
2249 // tarbaby explosion
2250 MSG_ReadVector(pos, cls.protocol);
2251 CL_FindNonSolidLocation(pos, pos, 10);
2252 CL_ParticleEffect(EFFECT_TE_TAREXPLOSION, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
2253 S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1);
2257 MSG_ReadVector(pos, cls.protocol);
2258 CL_FindNonSolidLocation(pos, pos, 10);
2259 CL_ParticleEffect(EFFECT_TE_SMALLFLASH, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
2262 case TE_CUSTOMFLASH:
2263 MSG_ReadVector(pos, cls.protocol);
2264 CL_FindNonSolidLocation(pos, pos, 4);
2265 radius = (MSG_ReadByte() + 1) * 8;
2266 velspeed = (MSG_ReadByte() + 1) * (1.0 / 256.0);
2267 color[0] = MSG_ReadByte() * (2.0f / 255.0f);
2268 color[1] = MSG_ReadByte() * (2.0f / 255.0f);
2269 color[2] = MSG_ReadByte() * (2.0f / 255.0f);
2270 Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]);
2271 CL_AllocLightFlash(NULL, &tempmatrix, radius, color[0], color[1], color[2], radius / velspeed, velspeed, 0, -1, true, 1, 0.25, 1, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
2275 MSG_ReadVector(pos, cls.protocol);
2276 MSG_ReadVector(dir, cls.protocol);
2277 count = MSG_ReadByte();
2278 CL_ParticleEffect(EFFECT_TE_FLAMEJET, count, pos, pos, dir, dir, NULL, 0);
2283 CL_ParseBeam(cl.model_bolt, true);
2288 CL_ParseBeam(cl.model_bolt2, true);
2293 CL_ParseBeam(cl.model_bolt3, false);
2298 // grappling hook beam
2299 CL_ParseBeam(cl.model_beam, false);
2303 // LordHavoc: for compatibility with the Nehahra movie...
2304 case TE_LIGHTNING4NEH:
2305 CL_ParseBeam(Mod_ForName(MSG_ReadString(), true, false, false), false);
2309 MSG_ReadVector(pos, cls.protocol);
2310 CL_ParticleEffect(EFFECT_TE_LAVASPLASH, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
2314 MSG_ReadVector(pos, cls.protocol);
2315 CL_ParticleEffect(EFFECT_TE_TELEPORT, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
2319 // color mapped explosion
2320 MSG_ReadVector(pos, cls.protocol);
2321 CL_FindNonSolidLocation(pos, pos, 10);
2322 colorStart = MSG_ReadByte();
2323 colorLength = MSG_ReadByte();
2324 CL_ParticleExplosion2(pos, colorStart, colorLength);
2325 tempcolor = (unsigned char *)&palette_complete[(rand()%colorLength) + colorStart];
2326 color[0] = tempcolor[0] * (2.0f / 255.0f);
2327 color[1] = tempcolor[1] * (2.0f / 255.0f);
2328 color[2] = tempcolor[2] * (2.0f / 255.0f);
2329 Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]);
2330 CL_AllocLightFlash(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
2331 S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1);
2335 MSG_ReadVector(pos, cls.protocol);
2336 MSG_ReadVector(pos2, cls.protocol);
2337 MSG_ReadVector(dir, cls.protocol);
2338 CL_ParticleEffect(EFFECT_TE_TEI_G3, 1, pos, pos2, dir, dir, NULL, 0);
2342 MSG_ReadVector(pos, cls.protocol);
2343 MSG_ReadVector(dir, cls.protocol);
2344 count = MSG_ReadByte();
2345 CL_FindNonSolidLocation(pos, pos, 4);
2346 CL_ParticleEffect(EFFECT_TE_TEI_SMOKE, count, pos, pos, vec3_origin, vec3_origin, NULL, 0);
2349 case TE_TEI_BIGEXPLOSION:
2350 MSG_ReadVector(pos, cls.protocol);
2351 CL_FindNonSolidLocation(pos, pos, 10);
2352 CL_ParticleEffect(EFFECT_TE_TEI_BIGEXPLOSION, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0);
2353 S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1);
2356 case TE_TEI_PLASMAHIT:
2357 MSG_ReadVector(pos, cls.protocol);
2358 MSG_ReadVector(dir, cls.protocol);
2359 count = MSG_ReadByte();
2360 CL_FindNonSolidLocation(pos, pos, 5);
2361 CL_ParticleEffect(EFFECT_TE_TEI_PLASMAHIT, count, pos, pos, vec3_origin, vec3_origin, NULL, 0);
2365 Host_Error("CL_ParseTempEntity: bad type %d (hex %02X)", type, type);
2370 void CL_ParseTrailParticles(void)
2375 entityindex = (unsigned short)MSG_ReadShort();
2376 if (entityindex >= MAX_EDICTS)
2378 if (entityindex >= cl.max_entities)
2379 CL_ExpandEntities(entityindex);
2380 effectindex = (unsigned short)MSG_ReadShort();
2381 MSG_ReadVector(start, cls.protocol);
2382 MSG_ReadVector(end, cls.protocol);
2383 CL_ParticleEffect(effectindex, VectorDistance(start, end), start, end, vec3_origin, vec3_origin, entityindex > 0 ? cl.entities + entityindex : NULL, 0);
2386 void CL_ParsePointParticles(void)
2388 int effectindex, count;
2389 vec3_t origin, velocity;
2390 effectindex = (unsigned short)MSG_ReadShort();
2391 MSG_ReadVector(origin, cls.protocol);
2392 MSG_ReadVector(velocity, cls.protocol);
2393 count = (unsigned short)MSG_ReadShort();
2394 CL_ParticleEffect(effectindex, count, origin, origin, velocity, velocity, NULL, 0);
2397 // look for anything interesting like player IP addresses or ping reports
2398 qboolean CL_ExaminePrintString(const char *text)
2401 if (!strcmp(text, "Client ping times:\n"))
2403 cl.parsingtextmode = CL_PARSETEXTMODE_PING;
2404 for(cl.parsingtextplayerindex = 0; cl.parsingtextplayerindex < cl.maxclients && !cl.scores[cl.parsingtextplayerindex].name[0]; cl.parsingtextplayerindex++)
2406 if (cl.parsingtextplayerindex >= cl.maxclients) // should never happen, since the client itself should be in cl.scores
2408 Con_Printf("ping reply but empty scoreboard?!?\n");
2409 cl.parsingtextmode = CL_PARSETEXTMODE_NONE;
2410 cl.parsingtextexpectingpingforscores = 0;
2412 cl.parsingtextexpectingpingforscores = cl.parsingtextexpectingpingforscores ? 2 : 0;
2413 return !cl.parsingtextexpectingpingforscores;
2415 if (!strncmp(text, "host: ", 9))
2417 // cl.parsingtextexpectingpingforscores = false; // really?
2418 cl.parsingtextmode = CL_PARSETEXTMODE_STATUS;
2419 cl.parsingtextplayerindex = 0;
2422 if (cl.parsingtextmode == CL_PARSETEXTMODE_PING)
2424 // if anything goes wrong, we'll assume this is not a ping report
2425 qboolean expected = cl.parsingtextexpectingpingforscores;
2426 cl.parsingtextexpectingpingforscores = 0;
2427 cl.parsingtextmode = CL_PARSETEXTMODE_NONE;
2431 if ((*t >= '0' && *t <= '9') || *t == '-')
2434 while ((*t >= '0' && *t <= '9') || *t == '-')
2440 if(cl.parsingtextplayerindex < cl.maxclients)
2442 for (charindex = 0;cl.scores[cl.parsingtextplayerindex].name[charindex] == t[charindex];charindex++)
2444 // note: the matching algorithm stops at the end of the player name because some servers append text such as " READY" after the player name in the scoreboard but not in the ping report
2445 //if (cl.scores[cl.parsingtextplayerindex].name[charindex] == 0 && t[charindex] == '\n')
2446 if (t[charindex] == '\n')
2448 cl.scores[cl.parsingtextplayerindex].qw_ping = bound(0, ping, 9999);
2449 for (cl.parsingtextplayerindex++;cl.parsingtextplayerindex < cl.maxclients && !cl.scores[cl.parsingtextplayerindex].name[0];cl.parsingtextplayerindex++)
2451 //if (cl.parsingtextplayerindex < cl.maxclients) // we could still get unconnecteds!
2453 // we parsed a valid ping entry, so expect another to follow
2454 cl.parsingtextmode = CL_PARSETEXTMODE_PING;
2455 cl.parsingtextexpectingpingforscores = expected;
2460 if (!strncmp(t, "unconnected\n", 12))
2463 cl.parsingtextmode = CL_PARSETEXTMODE_PING;
2464 cl.parsingtextexpectingpingforscores = expected;
2468 Con_DPrintf("player names '%s' and '%s' didn't match\n", cl.scores[cl.parsingtextplayerindex].name, t);
2472 if (cl.parsingtextmode == CL_PARSETEXTMODE_STATUS)
2474 if (!strncmp(text, "players: ", 9))
2476 cl.parsingtextmode = CL_PARSETEXTMODE_STATUS_PLAYERID;
2477 cl.parsingtextplayerindex = 0;
2480 else if (!strstr(text, ": "))
2482 cl.parsingtextmode = CL_PARSETEXTMODE_NONE; // status report ended
2486 if (cl.parsingtextmode == CL_PARSETEXTMODE_STATUS_PLAYERID)
2488 // if anything goes wrong, we'll assume this is not a status report
2489 cl.parsingtextmode = CL_PARSETEXTMODE_NONE;
2490 if (text[0] == '#' && text[1] >= '0' && text[1] <= '9')
2493 cl.parsingtextplayerindex = atoi(t);
2494 while (*t >= '0' && *t <= '9')
2498 cl.parsingtextmode = CL_PARSETEXTMODE_STATUS_PLAYERIP;
2503 if (cl.parsingtextmode == CL_PARSETEXTMODE_STATUS_PLAYERIP)
2505 // if anything goes wrong, we'll assume this is not a status report
2506 cl.parsingtextmode = CL_PARSETEXTMODE_NONE;
2512 // botclient is perfectly valid, but we don't care about bots
2513 if (strcmp(t, "botclient\n"))
2515 lhnetaddress_t address;
2516 LHNETADDRESS_FromString(&address, t, 0);
2517 // TODO: log the IP address and player name?
2519 cl.parsingtextmode = CL_PARSETEXTMODE_STATUS_PLAYERID;
2526 static void CL_NetworkTimeReceived(double newtime)
2528 if (cl_nolerp.integer || cls.timedemo || (cl.islocalgame && !sv_fixedframeratesingleplayer.integer))
2529 cl.mtime[1] = cl.mtime[0] = newtime;
2532 cl.mtime[1] = max(cl.mtime[0], newtime - 0.1);
2533 cl.mtime[0] = newtime;
2535 if (cl_nettimesyncmode.integer == 3)
2536 cl.time = cl.mtime[1];
2537 if (cl_nettimesyncmode.integer == 2)
2539 if (cl.time < cl.mtime[1] || cl.time > cl.mtime[0])
2540 cl.time = cl.mtime[1];
2542 else if (cl_nettimesyncmode.integer == 1)
2543 cl.time = cl.mtime[1];
2544 // this packet probably contains a player entity update, so we will need
2545 // to update the prediction
2546 cl.movement_needupdate = true;
2547 // this may get updated later in parsing by svc_clientdata
2548 cl.onground = false;
2549 // if true the cl.viewangles are interpolated from cl.mviewangles[]
2550 // during this frame
2551 // (makes spectating players much smoother and prevents mouse movement from turning)
2552 cl.fixangle[1] = cl.fixangle[0];
2553 cl.fixangle[0] = false;
2554 if (!cls.demoplayback)
2555 VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
2558 #define SHOWNET(x) if(cl_shownet.integer==2)Con_Printf("%3i:%s\n", msg_readcount-1, x);
2561 void CL_VM_Init (void);
2562 qboolean CL_VM_Parse_TempEntity (void);
2563 void CL_VM_Parse_StuffCmd (const char *msg);
2564 void CL_VM_Parse_CenterPrint (const char *msg);
2565 void CSQC_AddPrintText (const char *msg);
2566 void CSQC_ReadEntities (void);
2569 =====================
2570 CL_ParseServerMessage
2571 =====================
2573 int parsingerror = false;
2574 void CL_ParseServerMessage(void)
2578 protocolversion_t protocol;
2579 unsigned char cmdlog[32];
2580 char *cmdlogname[32], *temp;
2581 int cmdindex, cmdcount = 0;
2583 if (cls.demorecording)
2584 CL_WriteDemoMessage ();
2586 cl.last_received_message = realtime;
2588 if (cls.netcon && cls.signon < SIGNONS)
2589 CL_KeepaliveMessage(false);
2592 // if recording demos, copy the message out
2594 if (cl_shownet.integer == 1)
2595 Con_Printf("%f %i\n", realtime, net_message.cursize);
2596 else if (cl_shownet.integer == 2)
2597 Con_Print("------------------\n");
2600 // parse the message
2602 //MSG_BeginReading ();
2604 parsingerror = true;
2606 if (cls.protocol == PROTOCOL_QUAKEWORLD)
2608 CL_NetworkTimeReceived(realtime); // qw has no clock
2610 // slightly kill qw player entities each frame
2611 for (i = 1;i < cl.maxclients;i++)
2612 cl.entities_active[i] = false;
2614 // kill all qw nails
2615 cl.qw_num_nails = 0;
2617 // fade weapon view kick
2618 cl.qw_weaponkick = min(cl.qw_weaponkick + 10 * bound(0, cl.time - cl.oldtime, 0.1), 0);
2623 Host_Error ("CL_ParseServerMessage: Bad QW server message");
2625 cmd = MSG_ReadByte ();
2629 SHOWNET("END OF MESSAGE");
2630 break; // end of message
2633 cmdindex = cmdcount & 31;
2635 cmdlog[cmdindex] = cmd;
2637 SHOWNET(qw_svc_strings[cmd]);
2638 cmdlogname[cmdindex] = qw_svc_strings[cmd];
2639 if (!cmdlogname[cmdindex])
2641 // LordHavoc: fix for bizarre problem in MSVC that I do not understand (if I assign the string pointer directly it ends up storing a NULL pointer)
2643 cmdlogname[cmdindex] = temp;
2651 char description[32*64], temp[64];
2653 strlcpy(description, "packet dump: ", sizeof(description));
2657 count = cmdcount - i;
2661 dpsnprintf(temp, sizeof(temp), "%3i:%s ", cmdlog[i], cmdlogname[i]);
2662 strlcat(description, temp, sizeof(description));
2667 description[strlen(description)-1] = '\n'; // replace the last space with a newline
2668 Con_Print(description);
2669 Host_Error("CL_ParseServerMessage: Illegible server message");
2674 //Con_Printf("qw_svc_nop\n");
2677 case qw_svc_disconnect:
2678 Con_Printf("Server disconnected\n");
2679 if (cls.demonum != -1)
2687 temp = MSG_ReadString();
2688 if (CL_ExaminePrintString(temp)) // look for anything interesting like player IP addresses or ping reports
2691 CSQC_AddPrintText(va("\1%s", temp)); //[515]: csqc
2693 CSQC_AddPrintText(temp);
2697 case qw_svc_centerprint:
2698 CL_VM_Parse_CenterPrint(MSG_ReadString ()); //[515]: csqc
2701 case qw_svc_stufftext:
2702 CL_VM_Parse_StuffCmd(MSG_ReadString ()); //[515]: csqc
2706 // svc_damage protocol is identical to nq
2710 case qw_svc_serverdata:
2711 //Cbuf_Execute(); // make sure any stuffed commands are done
2712 CL_ParseServerInfo();
2713 CL_VM_Init(); //[515]: init csqc
2716 case qw_svc_setangle:
2717 for (i=0 ; i<3 ; i++)
2718 cl.viewangles[i] = MSG_ReadAngle (cls.protocol);
2719 if (!cls.demoplayback)
2721 cl.fixangle[0] = true;
2722 VectorCopy(cl.viewangles, cl.mviewangles[0]);
2723 // disable interpolation if this is new
2724 if (!cl.fixangle[1])
2725 VectorCopy(cl.viewangles, cl.mviewangles[1]);
2729 case qw_svc_lightstyle:
2730 i = MSG_ReadByte ();
2731 if (i >= cl.max_lightstyle)
2733 Con_Printf ("svc_lightstyle >= MAX_LIGHTSTYLES");
2736 strlcpy (cl.lightstyle[i].map, MSG_ReadString(), sizeof (cl.lightstyle[i].map));
2737 cl.lightstyle[i].map[MAX_STYLESTRING - 1] = 0;
2738 cl.lightstyle[i].length = (int)strlen(cl.lightstyle[i].map);
2742 CL_ParseStartSoundPacket(false);
2745 case qw_svc_stopsound:
2746 i = (unsigned short) MSG_ReadShort();
2747 S_StopSound(i>>3, i&7);
2750 case qw_svc_updatefrags:
2752 if (i >= cl.maxclients)
2753 Host_Error("CL_ParseServerMessage: svc_updatefrags >= cl.maxclients");
2754 cl.scores[i].frags = (signed short) MSG_ReadShort();
2757 case qw_svc_updateping:
2759 if (i >= cl.maxclients)
2760 Host_Error("CL_ParseServerMessage: svc_updateping >= cl.maxclients");
2761 cl.scores[i].qw_ping = MSG_ReadShort();
2764 case qw_svc_updatepl:
2766 if (i >= cl.maxclients)
2767 Host_Error("CL_ParseServerMessage: svc_updatepl >= cl.maxclients");
2768 cl.scores[i].qw_packetloss = MSG_ReadByte();
2771 case qw_svc_updateentertime:
2773 if (i >= cl.maxclients)
2774 Host_Error("CL_ParseServerMessage: svc_updateentertime >= cl.maxclients");
2776 cl.scores[i].qw_entertime = cl.time - MSG_ReadFloat();
2779 case qw_svc_spawnbaseline:
2780 i = (unsigned short) MSG_ReadShort();
2781 if (i < 0 || i >= MAX_EDICTS)
2782 Host_Error ("CL_ParseServerMessage: svc_spawnbaseline: invalid entity number %i", i);
2783 if (i >= cl.max_entities)
2784 CL_ExpandEntities(i);
2785 CL_ParseBaseline(cl.entities + i, false);
2787 case qw_svc_spawnstatic:
2788 CL_ParseStatic(false);
2790 case qw_svc_temp_entity:
2791 if(!CL_VM_Parse_TempEntity())
2792 CL_ParseTempEntity ();
2795 case qw_svc_killedmonster:
2796 cl.stats[STAT_MONSTERS]++;
2799 case qw_svc_foundsecret:
2800 cl.stats[STAT_SECRETS]++;
2803 case qw_svc_updatestat:
2804 i = MSG_ReadByte ();
2805 if (i < 0 || i >= MAX_CL_STATS)
2806 Host_Error ("svc_updatestat: %i is invalid", i);
2807 cl.stats[i] = MSG_ReadByte ();
2810 case qw_svc_updatestatlong:
2811 i = MSG_ReadByte ();
2812 if (i < 0 || i >= MAX_CL_STATS)
2813 Host_Error ("svc_updatestatlong: %i is invalid", i);
2814 cl.stats[i] = MSG_ReadLong ();
2817 case qw_svc_spawnstaticsound:
2818 CL_ParseStaticSound (false);
2821 case qw_svc_cdtrack:
2822 cl.cdtrack = cl.looptrack = MSG_ReadByte ();
2823 if ( (cls.demoplayback || cls.demorecording) && (cls.forcetrack != -1) )
2824 CDAudio_Play ((unsigned char)cls.forcetrack, true);
2826 CDAudio_Play ((unsigned char)cl.cdtrack, true);
2829 case qw_svc_intermission:
2830 cl.intermission = 1;
2831 cl.completed_time = cl.time;
2832 MSG_ReadVector(cl.qw_intermission_origin, cls.protocol);
2833 for (i = 0;i < 3;i++)
2834 cl.qw_intermission_angles[i] = MSG_ReadAngle(cls.protocol);
2838 cl.intermission = 2;
2839 cl.completed_time = cl.time;
2840 SCR_CenterPrint(MSG_ReadString ());
2843 case qw_svc_sellscreen:
2844 Cmd_ExecuteString ("help", src_command);
2847 case qw_svc_smallkick:
2848 cl.qw_weaponkick = -2;
2850 case qw_svc_bigkick:
2851 cl.qw_weaponkick = -4;
2854 case qw_svc_muzzleflash:
2855 i = (unsigned short) MSG_ReadShort();
2856 // NOTE: in QW this only worked on clients
2857 if (i < 0 || i >= MAX_EDICTS)
2858 Host_Error("CL_ParseServerMessage: svc_spawnbaseline: invalid entity number %i", i);
2859 if (i >= cl.max_entities)
2860 CL_ExpandEntities(i);
2861 cl.entities[i].persistent.muzzleflash = 1.0f;
2864 case qw_svc_updateuserinfo:
2865 QW_CL_UpdateUserInfo();
2868 case qw_svc_setinfo:
2872 case qw_svc_serverinfo:
2876 case qw_svc_download:
2877 QW_CL_ParseDownload();
2880 case qw_svc_playerinfo:
2881 EntityStateQW_ReadPlayerUpdate();
2888 case qw_svc_chokecount:
2890 // FIXME: apply to netgraph
2891 //for (j = 0;j < i;j++)
2892 // cl.frames[(cls.netcon->qw.incoming_acknowledged-1-j)&QW_UPDATE_MASK].receivedtime = -2;
2895 case qw_svc_modellist:
2896 QW_CL_ParseModelList();
2899 case qw_svc_soundlist:
2900 QW_CL_ParseSoundList();
2903 case qw_svc_packetentities:
2904 EntityFrameQW_CL_ReadFrame(false);
2905 // first update is the final signon stage
2906 if (cls.signon == SIGNONS - 1)
2907 cls.signon = SIGNONS;
2910 case qw_svc_deltapacketentities:
2911 EntityFrameQW_CL_ReadFrame(true);
2912 // first update is the final signon stage
2913 if (cls.signon == SIGNONS - 1)
2914 cls.signon = SIGNONS;
2917 case qw_svc_maxspeed:
2918 cl.qw_movevars_maxspeed = MSG_ReadFloat();
2921 case qw_svc_entgravity:
2922 cl.qw_movevars_entgravity = MSG_ReadFloat();
2925 case qw_svc_setpause:
2926 cl.paused = MSG_ReadByte ();
2931 S_PauseGameSounds (cl.paused);
2936 // fully kill the still slightly dead qw player entities each frame
2937 for (i = 1;i < cl.maxclients;i++)
2938 if (!cl.entities_active[i])
2939 cl.entities[i].state_current.active = false;
2946 Host_Error ("CL_ParseServerMessage: Bad server message");
2948 cmd = MSG_ReadByte ();
2952 SHOWNET("END OF MESSAGE");
2953 break; // end of message
2956 cmdindex = cmdcount & 31;
2958 cmdlog[cmdindex] = cmd;
2960 // if the high bit of the command byte is set, it is a fast update
2963 // LordHavoc: fix for bizarre problem in MSVC that I do not understand (if I assign the string pointer directly it ends up storing a NULL pointer)
2965 cmdlogname[cmdindex] = temp;
2966 SHOWNET("fast update");
2967 if (cls.signon == SIGNONS - 1)
2969 // first update is the final signon stage
2970 cls.signon = SIGNONS;
2973 EntityFrameQuake_ReadEntity (cmd&127);
2977 SHOWNET(svc_strings[cmd]);
2978 cmdlogname[cmdindex] = svc_strings[cmd];
2979 if (!cmdlogname[cmdindex])
2981 // LordHavoc: fix for bizarre problem in MSVC that I do not understand (if I assign the string pointer directly it ends up storing a NULL pointer)
2983 cmdlogname[cmdindex] = temp;
2991 char description[32*64], temp[64];
2993 strlcpy (description, "packet dump: ", sizeof(description));
2997 count = cmdcount - i;
3001 dpsnprintf (temp, sizeof (temp), "%3i:%s ", cmdlog[i], cmdlogname[i]);
3002 strlcat (description, temp, sizeof (description));
3007 description[strlen(description)-1] = '\n'; // replace the last space with a newline
3008 Con_Print(description);
3009 Host_Error ("CL_ParseServerMessage: Illegible server message");
3014 if (cls.signon < SIGNONS)
3015 Con_Print("<-- server to client keepalive\n");
3019 CL_NetworkTimeReceived(MSG_ReadFloat());
3022 case svc_clientdata:
3023 CL_ParseClientdata();
3027 i = MSG_ReadLong ();
3028 protocol = Protocol_EnumForNumber(i);
3029 if (protocol == PROTOCOL_UNKNOWN)
3030 Host_Error("CL_ParseServerMessage: Server is unrecognized protocol number (%i)", i);
3031 // hack for unmarked Nehahra movie demos which had a custom protocol
3032 if (protocol == PROTOCOL_QUAKEDP && cls.demoplayback && demo_nehahra.integer)
3033 protocol = PROTOCOL_NEHAHRAMOVIE;
3034 cls.protocol = protocol;
3037 case svc_disconnect:
3038 Con_Printf ("Server disconnected\n");
3039 if (cls.demonum != -1)
3046 temp = MSG_ReadString();
3047 if (CL_ExaminePrintString(temp)) // look for anything interesting like player IP addresses or ping reports
3048 CSQC_AddPrintText(temp); //[515]: csqc
3051 case svc_centerprint:
3052 CL_VM_Parse_CenterPrint(MSG_ReadString ()); //[515]: csqc
3056 CL_VM_Parse_StuffCmd(MSG_ReadString ()); //[515]: csqc
3063 case svc_serverinfo:
3064 CL_ParseServerInfo ();
3065 CL_VM_Init(); //[515]: init csqc
3069 for (i=0 ; i<3 ; i++)
3070 cl.viewangles[i] = MSG_ReadAngle (cls.protocol);
3071 if (!cls.demoplayback)
3073 cl.fixangle[0] = true;
3074 VectorCopy(cl.viewangles, cl.mviewangles[0]);
3075 // disable interpolation if this is new
3076 if (!cl.fixangle[1])
3077 VectorCopy(cl.viewangles, cl.mviewangles[1]);
3082 cl.viewentity = (unsigned short)MSG_ReadShort ();
3083 if (cl.viewentity >= MAX_EDICTS)
3084 Host_Error("svc_setview >= MAX_EDICTS");
3085 if (cl.viewentity >= cl.max_entities)
3086 CL_ExpandEntities(cl.viewentity);
3087 // LordHavoc: assume first setview recieved is the real player entity
3088 if (!cl.playerentity)
3089 cl.playerentity = cl.viewentity;
3092 case svc_lightstyle:
3093 i = MSG_ReadByte ();
3094 if (i >= cl.max_lightstyle)
3096 Con_Printf ("svc_lightstyle >= MAX_LIGHTSTYLES");
3099 strlcpy (cl.lightstyle[i].map, MSG_ReadString(), sizeof (cl.lightstyle[i].map));
3100 cl.lightstyle[i].map[MAX_STYLESTRING - 1] = 0;
3101 cl.lightstyle[i].length = (int)strlen(cl.lightstyle[i].map);
3105 CL_ParseStartSoundPacket(false);
3109 if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3)
3111 // was svc_sound2 in protocols 1, 2, 3, removed in 4, 5, changed to svc_precache in 6
3112 CL_ParseStartSoundPacket(true);
3116 int i = (unsigned short)MSG_ReadShort();
3117 char *s = MSG_ReadString();
3120 if (i >= 1 && i < MAX_MODELS)
3122 model_t *model = Mod_ForName(s, false, false, i == 1);
3124 Con_Printf("svc_precache: Mod_ForName(\"%s\") failed\n", s);
3125 cl.model_precache[i] = model;
3128 Con_Printf("svc_precache: index %i outside range %i...%i\n", i, 1, MAX_MODELS);
3133 if (i >= 1 && i < MAX_SOUNDS)
3135 sfx_t *sfx = S_PrecacheSound (s, true, false);
3136 if (!sfx && snd_initialized.integer)
3137 Con_Printf("svc_precache: S_PrecacheSound(\"%s\") failed\n", s);
3138 cl.sound_precache[i] = sfx;
3141 Con_Printf("svc_precache: index %i outside range %i...%i\n", i, 1, MAX_SOUNDS);
3147 i = (unsigned short) MSG_ReadShort();
3148 S_StopSound(i>>3, i&7);
3151 case svc_updatename:
3152 i = MSG_ReadByte ();
3153 if (i >= cl.maxclients)
3154 Host_Error ("CL_ParseServerMessage: svc_updatename >= cl.maxclients");
3155 strlcpy (cl.scores[i].name, MSG_ReadString (), sizeof (cl.scores[i].name));
3158 case svc_updatefrags:
3159 i = MSG_ReadByte ();
3160 if (i >= cl.maxclients)
3161 Host_Error ("CL_ParseServerMessage: svc_updatefrags >= cl.maxclients");
3162 cl.scores[i].frags = (signed short) MSG_ReadShort ();
3165 case svc_updatecolors:
3166 i = MSG_ReadByte ();
3167 if (i >= cl.maxclients)
3168 Host_Error ("CL_ParseServerMessage: svc_updatecolors >= cl.maxclients");
3169 cl.scores[i].colors = MSG_ReadByte ();
3173 CL_ParseParticleEffect ();
3184 case svc_spawnbaseline:
3185 i = (unsigned short) MSG_ReadShort ();
3186 if (i < 0 || i >= MAX_EDICTS)
3187 Host_Error ("CL_ParseServerMessage: svc_spawnbaseline: invalid entity number %i", i);
3188 if (i >= cl.max_entities)
3189 CL_ExpandEntities(i);
3190 CL_ParseBaseline (cl.entities + i, false);
3192 case svc_spawnbaseline2:
3193 i = (unsigned short) MSG_ReadShort ();
3194 if (i < 0 || i >= MAX_EDICTS)
3195 Host_Error ("CL_ParseServerMessage: svc_spawnbaseline2: invalid entity number %i", i);
3196 if (i >= cl.max_entities)
3197 CL_ExpandEntities(i);
3198 CL_ParseBaseline (cl.entities + i, true);
3200 case svc_spawnstatic:
3201 CL_ParseStatic (false);
3203 case svc_spawnstatic2:
3204 CL_ParseStatic (true);
3206 case svc_temp_entity:
3207 if(!CL_VM_Parse_TempEntity())
3208 CL_ParseTempEntity ();
3212 cl.paused = MSG_ReadByte ();
3217 S_PauseGameSounds (cl.paused);
3221 i = MSG_ReadByte ();
3222 // LordHavoc: it's rude to kick off the client if they missed the
3223 // reconnect somehow, so allow signon 1 even if at signon 1
3224 if (i <= cls.signon && i != 1)
3225 Host_Error ("Received signon %i when at %i", i, cls.signon);
3230 case svc_killedmonster:
3231 cl.stats[STAT_MONSTERS]++;
3234 case svc_foundsecret:
3235 cl.stats[STAT_SECRETS]++;
3238 case svc_updatestat:
3239 i = MSG_ReadByte ();
3240 if (i < 0 || i >= MAX_CL_STATS)
3241 Host_Error ("svc_updatestat: %i is invalid", i);
3242 cl.stats[i] = MSG_ReadLong ();
3245 case svc_updatestatubyte:
3246 i = MSG_ReadByte ();
3247 if (i < 0 || i >= MAX_CL_STATS)
3248 Host_Error ("svc_updatestat: %i is invalid", i);
3249 cl.stats[i] = MSG_ReadByte ();
3252 case svc_spawnstaticsound:
3253 CL_ParseStaticSound (false);
3256 case svc_spawnstaticsound2:
3257 CL_ParseStaticSound (true);
3261 cl.cdtrack = MSG_ReadByte ();
3262 cl.looptrack = MSG_ReadByte ();
3263 if ( (cls.demoplayback || cls.demorecording) && (cls.forcetrack != -1) )
3264 CDAudio_Play ((unsigned char)cls.forcetrack, true);
3266 CDAudio_Play ((unsigned char)cl.cdtrack, true);
3269 case svc_intermission:
3270 cl.intermission = 1;
3271 cl.completed_time = cl.time;
3275 cl.intermission = 2;
3276 cl.completed_time = cl.time;
3277 SCR_CenterPrint(MSG_ReadString ());
3281 cl.intermission = 3;
3282 cl.completed_time = cl.time;
3283 SCR_CenterPrint(MSG_ReadString ());
3286 case svc_sellscreen:
3287 Cmd_ExecuteString ("help", src_command);
3290 if (gamemode == GAME_TENEBRAE)
3292 // repeating particle effect
3293 MSG_ReadCoord(cls.protocol);
3294 MSG_ReadCoord(cls.protocol);
3295 MSG_ReadCoord(cls.protocol);
3296 MSG_ReadCoord(cls.protocol);
3297 MSG_ReadCoord(cls.protocol);
3298 MSG_ReadCoord(cls.protocol);
3305 SHOWLMP_decodehide();
3308 if (gamemode == GAME_TENEBRAE)
3311 MSG_ReadCoord(cls.protocol);
3312 MSG_ReadCoord(cls.protocol);
3313 MSG_ReadCoord(cls.protocol);
3318 SHOWLMP_decodeshow();
3321 R_SetSkyBox(MSG_ReadString());
3324 if (cls.signon == SIGNONS - 1)
3326 // first update is the final signon stage
3327 cls.signon = SIGNONS;
3330 if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3)
3331 EntityFrame_CL_ReadFrame();
3332 else if (cls.protocol == PROTOCOL_DARKPLACES4)
3333 EntityFrame4_CL_ReadFrame();
3335 EntityFrame5_CL_ReadFrame();
3337 case svc_csqcentities:
3338 CSQC_ReadEntities();
3340 case svc_downloaddata:
3343 case svc_trailparticles:
3344 CL_ParseTrailParticles();
3346 case svc_pointparticles:
3347 CL_ParsePointParticles();
3353 if (cls.signon == SIGNONS)
3354 CL_UpdateItemsAndWeapon();
3356 EntityFrameQuake_ISeeDeadEntities();
3358 parsingerror = false;
3361 void CL_Parse_DumpPacket(void)
3365 Con_Print("Packet dump:\n");
3366 SZ_HexDumpToConsole(&net_message);
3367 parsingerror = false;
3370 void CL_Parse_ErrorCleanUp(void)
3372 CL_StopDownload(0, 0);
3376 void CL_Parse_Init(void)
3378 // LordHavoc: added demo_nehahra cvar
3379 Cvar_RegisterVariable (&demo_nehahra);
3380 if (gamemode == GAME_NEHAHRA)
3381 Cvar_SetValue("demo_nehahra", 1);
3382 Cvar_RegisterVariable(&developer_networkentities);
3384 Cvar_RegisterVariable(&cl_sound_wizardhit);
3385 Cvar_RegisterVariable(&cl_sound_hknighthit);
3386 Cvar_RegisterVariable(&cl_sound_tink1);
3387 Cvar_RegisterVariable(&cl_sound_ric1);
3388 Cvar_RegisterVariable(&cl_sound_ric2);
3389 Cvar_RegisterVariable(&cl_sound_ric3);
3390 Cvar_RegisterVariable(&cl_sound_r_exp3);
3392 Cvar_RegisterVariable(&cl_joinbeforedownloadsfinish);
3394 // server extension cvars set by commands issued from the server during connect
3395 Cvar_RegisterVariable(&cl_serverextension_download);
3397 Cvar_RegisterVariable(&cl_nettimesyncmode);
3399 Cmd_AddCommand("nextul", QW_CL_NextUpload, "sends next fragment of current upload buffer (screenshot for example)");
3400 Cmd_AddCommand("stopul", QW_CL_StopUpload, "aborts current upload (screenshot for example)");
3401 Cmd_AddCommand("skins", QW_CL_Skins_f, "downloads missing qw skins from server");
3402 Cmd_AddCommand("changing", QW_CL_Changing_f, "sent by qw servers to tell client to wait for level change");
3403 Cmd_AddCommand("cl_begindownloads", CL_BeginDownloads_f, "used internally by darkplaces client while connecting (causes loading of models and sounds or triggers downloads for missing ones)");
3404 Cmd_AddCommand("cl_downloadbegin", CL_DownloadBegin_f, "(networking) informs client of download file information, client replies with sv_startsoundload to begin the transfer");
3405 Cmd_AddCommand("stopdownload", CL_StopDownload_f, "terminates a download");
3406 Cmd_AddCommand("cl_downloadfinished", CL_DownloadFinished_f, "signals that a download has finished and provides the client with file size and crc to check its integrity");
3409 void CL_Parse_Shutdown(void)