| /* |
| Copyright (C) 1996-1997 Id Software, Inc. |
| |
| This program is free software; you can redistribute it and/or |
| modify it under the terms of the GNU General Public License |
| as published by the Free Software Foundation; either version 2 |
| of the License, or (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| |
| See the GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program; if not, write to the Free Software |
| Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| |
| */ |
| // cl_parse.c -- parse a message received from the server |
| |
| #include "quakedef.h" |
| |
| char *svc_strings[] = |
| { |
| "svc_bad", |
| "svc_nop", |
| "svc_disconnect", |
| "svc_updatestat", |
| "svc_version", // [long] server version |
| "svc_setview", // [short] entity number |
| "svc_sound", // <see code> |
| "svc_time", // [float] server time |
| "svc_print", // [string] null terminated string |
| "svc_stufftext", // [string] stuffed into client's console buffer |
| // the string should be \n terminated |
| "svc_setangle", // [vec3] set the view angle to this absolute value |
| |
| "svc_serverdata", // [long] version ... |
| "svc_lightstyle", // [byte] [string] |
| "svc_updatename", // [byte] [string] |
| "svc_updatefrags", // [byte] [short] |
| "svc_clientdata", // <shortbits + data> |
| "svc_stopsound", // <see code> |
| "svc_updatecolors", // [byte] [byte] |
| "svc_particle", // [vec3] <variable> |
| "svc_damage", // [byte] impact [byte] blood [vec3] from |
| |
| "svc_spawnstatic", |
| "OBSOLETE svc_spawnbinary", |
| "svc_spawnbaseline", |
| |
| "svc_temp_entity", // <variable> |
| "svc_setpause", |
| "svc_signonnum", |
| "svc_centerprint", |
| "svc_killedmonster", |
| "svc_foundsecret", |
| "svc_spawnstaticsound", |
| "svc_intermission", |
| "svc_finale", |
| |
| "svc_cdtrack", |
| "svc_sellscreen", |
| |
| "svc_smallkick", |
| "svc_bigkick", |
| |
| "svc_updateping", |
| "svc_updateentertime", |
| |
| "svc_updatestatlong", |
| "svc_muzzleflash", |
| "svc_updateuserinfo", |
| "svc_download", |
| "svc_playerinfo", |
| "svc_nails", |
| "svc_choke", |
| "svc_modellist", |
| "svc_soundlist", |
| "svc_packetentities", |
| "svc_deltapacketentities", |
| "svc_maxspeed", |
| "svc_entgravity", |
| |
| "svc_setinfo", |
| "svc_serverinfo", |
| "svc_updatepl", |
| "NEW PROTOCOL", |
| "NEW PROTOCOL", |
| "NEW PROTOCOL", |
| "NEW PROTOCOL", |
| "NEW PROTOCOL", |
| "NEW PROTOCOL", |
| "NEW PROTOCOL", |
| "NEW PROTOCOL", |
| "NEW PROTOCOL", |
| "NEW PROTOCOL", |
| "NEW PROTOCOL", |
| "NEW PROTOCOL", |
| "NEW PROTOCOL" |
| }; |
| |
| int oldparsecountmod; |
| int parsecountmod; |
| double parsecounttime; |
| |
| int cl_spikeindex, cl_playerindex, cl_flagindex; |
| |
| //============================================================================= |
| |
| int packet_latency[NET_TIMINGS]; |
| |
| int CL_CalcNet (void) |
| { |
| int a, i; |
| frame_t *frame; |
| int lost; |
| char st[80]; |
| |
| for (i=cls.netchan.outgoing_sequence-UPDATE_BACKUP+1 |
| ; i <= cls.netchan.outgoing_sequence |
| ; i++) |
| { |
| frame = &cl.frames[i&UPDATE_MASK]; |
| if (frame->receivedtime == -1) |
| packet_latency[i&NET_TIMINGSMASK] = 9999; // dropped |
| else if (frame->receivedtime == -2) |
| packet_latency[i&NET_TIMINGSMASK] = 10000; // choked |
| else if (frame->invalid) |
| packet_latency[i&NET_TIMINGSMASK] = 9998; // invalid delta |
| else |
| packet_latency[i&NET_TIMINGSMASK] = (frame->receivedtime - frame->senttime)*20; |
| } |
| |
| lost = 0; |
| for (a=0 ; a<NET_TIMINGS ; a++) |
| { |
| i = (cls.netchan.outgoing_sequence-a) & NET_TIMINGSMASK; |
| if (packet_latency[i] == 9999) |
| lost++; |
| } |
| return lost * 100 / NET_TIMINGS; |
| } |
| |
| //============================================================================= |
| |
| /* |
| =============== |
| CL_CheckOrDownloadFile |
| |
| Returns true if the file exists, otherwise it attempts |
| to start a download from the server. |
| =============== |
| */ |
| qboolean CL_CheckOrDownloadFile (char *filename) |
| { |
| FILE *f; |
| |
| if (strstr (filename, "..")) |
| { |
| Con_Printf ("Refusing to download a path with ..\n"); |
| return true; |
| } |
| |
| COM_FOpenFile (filename, &f); |
| if (f) |
| { // it exists, no need to download |
| fclose (f); |
| return true; |
| } |
| |
| //ZOID - can't download when recording |
| if (cls.demorecording) { |
| Con_Printf("Unable to download %s in record mode.\n", cls.downloadname); |
| return true; |
| } |
| //ZOID - can't download when playback |
| if (cls.demoplayback) |
| return true; |
| |
| strcpy (cls.downloadname, filename); |
| Con_Printf ("Downloading %s...\n", cls.downloadname); |
| |
| // download to a temp name, and only rename |
| // to the real name when done, so if interrupted |
| // a runt file wont be left |
| COM_StripExtension (cls.downloadname, cls.downloadtempname); |
| strcat (cls.downloadtempname, ".tmp"); |
| |
| MSG_WriteByte (&cls.netchan.message, clc_stringcmd); |
| MSG_WriteString (&cls.netchan.message, va("download %s", cls.downloadname)); |
| |
| cls.downloadnumber++; |
| |
| return false; |
| } |
| |
| /* |
| ================= |
| Model_NextDownload |
| ================= |
| */ |
| void Model_NextDownload (void) |
| { |
| char *s; |
| int i; |
| extern char gamedirfile[]; |
| |
| if (cls.downloadnumber == 0) |
| { |
| Con_Printf ("Checking models...\n"); |
| cls.downloadnumber = 1; |
| } |
| |
| cls.downloadtype = dl_model; |
| for ( |
| ; cl.model_name[cls.downloadnumber][0] |
| ; cls.downloadnumber++) |
| { |
| s = cl.model_name[cls.downloadnumber]; |
| if (s[0] == '*') |
| continue; // inline brush model |
| if (!CL_CheckOrDownloadFile(s)) |
| return; // started a download |
| } |
| |
| for (i=1 ; i<MAX_MODELS ; i++) |
| { |
| if (!cl.model_name[i][0]) |
| break; |
| |
| cl.model_precache[i] = Mod_ForName (cl.model_name[i], false); |
| |
| if (!cl.model_precache[i]) |
| { |
| Con_Printf ("\nThe required model file '%s' could not be found or downloaded.\n\n" |
| , cl.model_name[i]); |
| Con_Printf ("You may need to download or purchase a %s client " |
| "pack in order to play on this server.\n\n", gamedirfile); |
| CL_Disconnect (); |
| return; |
| } |
| } |
| |
| // all done |
| cl.worldmodel = cl.model_precache[1]; |
| R_NewMap (); |
| Hunk_Check (); // make sure nothing is hurt |
| |
| // done with modellist, request first of static signon messages |
| MSG_WriteByte (&cls.netchan.message, clc_stringcmd); |
| // MSG_WriteString (&cls.netchan.message, va("prespawn %i 0 %i", cl.servercount, cl.worldmodel->checksum2)); |
| MSG_WriteString (&cls.netchan.message, va(prespawn_name, cl.servercount, cl.worldmodel->checksum2)); |
| } |
| |
| /* |
| ================= |
| Sound_NextDownload |
| ================= |
| */ |
| void Sound_NextDownload (void) |
| { |
| char *s; |
| int i; |
| |
| if (cls.downloadnumber == 0) |
| { |
| Con_Printf ("Checking sounds...\n"); |
| cls.downloadnumber = 1; |
| } |
| |
| cls.downloadtype = dl_sound; |
| for ( |
| ; cl.sound_name[cls.downloadnumber][0] |
| ; cls.downloadnumber++) |
| { |
| s = cl.sound_name[cls.downloadnumber]; |
| if (!CL_CheckOrDownloadFile(va("sound/%s",s))) |
| return; // started a download |
| } |
| |
| for (i=1 ; i<MAX_SOUNDS ; i++) |
| { |
| if (!cl.sound_name[i][0]) |
| break; |
| cl.sound_precache[i] = S_PrecacheSound (cl.sound_name[i]); |
| } |
| |
| // done with sounds, request models now |
| memset (cl.model_precache, 0, sizeof(cl.model_precache)); |
| cl_playerindex = -1; |
| cl_spikeindex = -1; |
| cl_flagindex = -1; |
| MSG_WriteByte (&cls.netchan.message, clc_stringcmd); |
| // MSG_WriteString (&cls.netchan.message, va("modellist %i 0", cl.servercount)); |
| MSG_WriteString (&cls.netchan.message, va(modellist_name, cl.servercount, 0)); |
| } |
| |
| |
| /* |
| ====================== |
| CL_RequestNextDownload |
| ====================== |
| */ |
| void CL_RequestNextDownload (void) |
| { |
| switch (cls.downloadtype) |
| { |
| case dl_single: |
| break; |
| case dl_skin: |
| Skin_NextDownload (); |
| break; |
| case dl_model: |
| Model_NextDownload (); |
| break; |
| case dl_sound: |
| Sound_NextDownload (); |
| break; |
| case dl_none: |
| default: |
| Con_DPrintf("Unknown download type.\n"); |
| } |
| } |
| |
| /* |
| ===================== |
| CL_ParseDownload |
| |
| A download message has been received from the server |
| ===================== |
| */ |
| void CL_ParseDownload (void) |
| { |
| int size, percent; |
| char name[1024]; |
| int r; |
| |
| |
| // read the data |
| size = MSG_ReadShort (); |
| percent = MSG_ReadByte (); |
| |
| if (cls.demoplayback) { |
| if (size > 0) |
| msg_readcount += size; |
| return; // not in demo playback |
| } |
| |
| if (size == -1) |
| { |
| Con_Printf ("File not found.\n"); |
| if (cls.download) |
| { |
| Con_Printf ("cls.download shouldn't have been set\n"); |
| fclose (cls.download); |
| cls.download = NULL; |
| } |
| CL_RequestNextDownload (); |
| return; |
| } |
| |
| // open the file if not opened yet |
| if (!cls.download) |
| { |
| if (strncmp(cls.downloadtempname,"skins/",6)) |
| sprintf (name, "%s/%s", com_gamedir, cls.downloadtempname); |
| else |
| sprintf (name, "qw/%s", cls.downloadtempname); |
| |
| COM_CreatePath (name); |
| |
| cls.download = fopen (name, "wb"); |
| if (!cls.download) |
| { |
| msg_readcount += size; |
| Con_Printf ("Failed to open %s\n", cls.downloadtempname); |
| CL_RequestNextDownload (); |
| return; |
| } |
| } |
| |
| fwrite (net_message.data + msg_readcount, 1, size, cls.download); |
| msg_readcount += size; |
| |
| if (percent != 100) |
| { |
| // change display routines by zoid |
| // request next block |
| #if 0 |
| Con_Printf ("."); |
| if (10*(percent/10) != cls.downloadpercent) |
| { |
| cls.downloadpercent = 10*(percent/10); |
| Con_Printf ("%i%%", cls.downloadpercent); |
| } |
| #endif |
| cls.downloadpercent = percent; |
| |
| MSG_WriteByte (&cls.netchan.message, clc_stringcmd); |
| SZ_Print (&cls.netchan.message, "nextdl"); |
| } |
| else |
| { |
| char oldn[MAX_OSPATH]; |
| char newn[MAX_OSPATH]; |
| |
| #if 0 |
| Con_Printf ("100%%\n"); |
| #endif |
| |
| fclose (cls.download); |
| |
| // rename the temp file to it's final name |
| if (strcmp(cls.downloadtempname, cls.downloadname)) { |
| if (strncmp(cls.downloadtempname,"skins/",6)) { |
| sprintf (oldn, "%s/%s", com_gamedir, cls.downloadtempname); |
| sprintf (newn, "%s/%s", com_gamedir, cls.downloadname); |
| } else { |
| sprintf (oldn, "qw/%s", cls.downloadtempname); |
| sprintf (newn, "qw/%s", cls.downloadname); |
| } |
| r = rename (oldn, newn); |
| if (r) |
| Con_Printf ("failed to rename.\n"); |
| } |
| |
| cls.download = NULL; |
| cls.downloadpercent = 0; |
| |
| // get another file if needed |
| |
| CL_RequestNextDownload (); |
| } |
| } |
| |
| static byte *upload_data; |
| static int upload_pos; |
| static int upload_size; |
| |
| void CL_NextUpload(void) |
| { |
| byte buffer[1024]; |
| int r; |
| int percent; |
| int size; |
| |
| if (!upload_data) |
| return; |
| |
| r = upload_size - upload_pos; |
| if (r > 768) |
| r = 768; |
| memcpy(buffer, upload_data + upload_pos, r); |
| MSG_WriteByte (&cls.netchan.message, clc_upload); |
| MSG_WriteShort (&cls.netchan.message, r); |
| |
| upload_pos += r; |
| size = upload_size; |
| if (!size) |
| size = 1; |
| percent = upload_pos*100/size; |
| MSG_WriteByte (&cls.netchan.message, percent); |
| SZ_Write (&cls.netchan.message, buffer, r); |
| |
| Con_DPrintf ("UPLOAD: %6d: %d written\n", upload_pos - r, r); |
| |
| if (upload_pos != upload_size) |
| return; |
| |
| Con_Printf ("Upload completed\n"); |
| |
| free(upload_data); |
| upload_data = 0; |
| upload_pos = upload_size = 0; |
| } |
| |
| void CL_StartUpload (byte *data, int size) |
| { |
| if (cls.state < ca_onserver) |
| return; // gotta be connected |
| |
| // override |
| if (upload_data) |
| free(upload_data); |
| |
| Con_DPrintf("Upload starting of %d...\n", size); |
| |
| upload_data = malloc(size); |
| memcpy(upload_data, data, size); |
| upload_size = size; |
| upload_pos = 0; |
| |
| CL_NextUpload(); |
| } |
| |
| qboolean CL_IsUploading(void) |
| { |
| if (upload_data) |
| return true; |
| return false; |
| } |
| |
| void CL_StopUpload(void) |
| { |
| if (upload_data) |
| free(upload_data); |
| upload_data = NULL; |
| } |
| |
| /* |
| ===================================================================== |
| |
| SERVER CONNECTING MESSAGES |
| |
| ===================================================================== |
| */ |
| |
| /* |
| ================== |
| CL_ParseServerData |
| ================== |
| */ |
| void CL_ParseServerData (void) |
| { |
| char *str; |
| FILE *f; |
| char fn[MAX_OSPATH]; |
| qboolean cflag = false; |
| extern char gamedirfile[MAX_OSPATH]; |
| int protover; |
| |
| Con_DPrintf ("Serverdata packet received.\n"); |
| // |
| // wipe the client_state_t struct |
| // |
| CL_ClearState (); |
| |
| // parse protocol version number |
| // allow 2.2 and 2.29 demos to play |
| protover = MSG_ReadLong (); |
| if (protover != PROTOCOL_VERSION && |
| !(cls.demoplayback && (protover == 26 || protover == 27 || protover == 28))) |
| Host_EndGame ("Server returned version %i, not %i\nYou probably need to upgrade.\nCheck http://www.quakeworld.net/", protover, PROTOCOL_VERSION); |
| |
| cl.servercount = MSG_ReadLong (); |
| |
| // game directory |
| str = MSG_ReadString (); |
| |
| if (strcasecmp(gamedirfile, str)) { |
| // save current config |
| Host_WriteConfiguration (); |
| cflag = true; |
| } |
| |
| COM_Gamedir(str); |
| |
| //ZOID--run the autoexec.cfg in the gamedir |
| //if it exists |
| if (cflag) { |
| sprintf(fn, "%s/%s", com_gamedir, "config.cfg"); |
| if ((f = fopen(fn, "r")) != NULL) { |
| fclose(f); |
| Cbuf_AddText ("cl_warncmd 0\n"); |
| Cbuf_AddText("exec config.cfg\n"); |
| Cbuf_AddText("exec frontend.cfg\n"); |
| Cbuf_AddText ("cl_warncmd 1\n"); |
| } |
| } |
| |
| // parse player slot, high bit means spectator |
| cl.playernum = MSG_ReadByte (); |
| if (cl.playernum & 128) |
| { |
| cl.spectator = true; |
| cl.playernum &= ~128; |
| } |
| |
| // get the full level name |
| str = MSG_ReadString (); |
| strncpy (cl.levelname, str, sizeof(cl.levelname)-1); |
| |
| // get the movevars |
| movevars.gravity = MSG_ReadFloat(); |
| movevars.stopspeed = MSG_ReadFloat(); |
| movevars.maxspeed = MSG_ReadFloat(); |
| movevars.spectatormaxspeed = MSG_ReadFloat(); |
| movevars.accelerate = MSG_ReadFloat(); |
| movevars.airaccelerate = MSG_ReadFloat(); |
| movevars.wateraccelerate = MSG_ReadFloat(); |
| movevars.friction = MSG_ReadFloat(); |
| movevars.waterfriction = MSG_ReadFloat(); |
| movevars.entgravity = MSG_ReadFloat(); |
| |
| // seperate the printfs so the server message can have a color |
| 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"); |
| Con_Printf ("%c%s\n", 2, str); |
| |
| // ask for the sound list next |
| memset(cl.sound_name, 0, sizeof(cl.sound_name)); |
| MSG_WriteByte (&cls.netchan.message, clc_stringcmd); |
| // MSG_WriteString (&cls.netchan.message, va("soundlist %i 0", cl.servercount)); |
| MSG_WriteString (&cls.netchan.message, va(soundlist_name, cl.servercount, 0)); |
| |
| // now waiting for downloads, etc |
| cls.state = ca_onserver; |
| } |
| |
| /* |
| ================== |
| CL_ParseSoundlist |
| ================== |
| */ |
| void CL_ParseSoundlist (void) |
| { |
| int numsounds; |
| char *str; |
| int n; |
| |
| // precache sounds |
| // memset (cl.sound_precache, 0, sizeof(cl.sound_precache)); |
| |
| numsounds = MSG_ReadByte(); |
| |
| for (;;) { |
| str = MSG_ReadString (); |
| if (!str[0]) |
| break; |
| numsounds++; |
| if (numsounds == MAX_SOUNDS) |
| Host_EndGame ("Server sent too many sound_precache"); |
| strcpy (cl.sound_name[numsounds], str); |
| } |
| |
| n = MSG_ReadByte(); |
| |
| if (n) { |
| MSG_WriteByte (&cls.netchan.message, clc_stringcmd); |
| // MSG_WriteString (&cls.netchan.message, va("soundlist %i %i", cl.servercount, n)); |
| MSG_WriteString (&cls.netchan.message, va(soundlist_name, cl.servercount, n)); |
| return; |
| } |
| |
| cls.downloadnumber = 0; |
| cls.downloadtype = dl_sound; |
| Sound_NextDownload (); |
| } |
| |
| /* |
| ================== |
| CL_ParseModellist |
| ================== |
| */ |
| void CL_ParseModellist (void) |
| { |
| int nummodels; |
| char *str; |
| int n; |
| |
| // precache models and note certain default indexes |
| nummodels = MSG_ReadByte(); |
| |
| for (;;) |
| { |
| str = MSG_ReadString (); |
| if (!str[0]) |
| break; |
| nummodels++; |
| if (nummodels==MAX_MODELS) |
| Host_EndGame ("Server sent too many model_precache"); |
| strcpy (cl.model_name[nummodels], str); |
| |
| if (!strcmp(cl.model_name[nummodels],"progs/spike.mdl")) |
| cl_spikeindex = nummodels; |
| if (!strcmp(cl.model_name[nummodels],"progs/player.mdl")) |
| cl_playerindex = nummodels; |
| if (!strcmp(cl.model_name[nummodels],"progs/flag.mdl")) |
| cl_flagindex = nummodels; |
| } |
| |
| n = MSG_ReadByte(); |
| |
| if (n) { |
| MSG_WriteByte (&cls.netchan.message, clc_stringcmd); |
| // MSG_WriteString (&cls.netchan.message, va("modellist %i %i", cl.servercount, n)); |
| MSG_WriteString (&cls.netchan.message, va(modellist_name, cl.servercount, n)); |
| return; |
| } |
| |
| cls.downloadnumber = 0; |
| cls.downloadtype = dl_model; |
| Model_NextDownload (); |
| } |
| |
| /* |
| ================== |
| CL_ParseBaseline |
| ================== |
| */ |
| void CL_ParseBaseline (entity_state_t *es) |
| { |
| int i; |
| |
| es->modelindex = MSG_ReadByte (); |
| es->frame = MSG_ReadByte (); |
| es->colormap = MSG_ReadByte(); |
| es->skinnum = MSG_ReadByte(); |
| for (i=0 ; i<3 ; i++) |
| { |
| es->origin[i] = MSG_ReadCoord (); |
| es->angles[i] = MSG_ReadAngle (); |
| } |
| } |
| |
| |
| |
| /* |
| ===================== |
| CL_ParseStatic |
| |
| Static entities are non-interactive world objects |
| like torches |
| ===================== |
| */ |
| void CL_ParseStatic (void) |
| { |
| entity_t *ent; |
| int i; |
| entity_state_t es; |
| |
| CL_ParseBaseline (&es); |
| |
| i = cl.num_statics; |
| if (i >= MAX_STATIC_ENTITIES) |
| Host_EndGame ("Too many static entities"); |
| ent = &cl_static_entities[i]; |
| cl.num_statics++; |
| |
| // copy it to the current state |
| ent->model = cl.model_precache[es.modelindex]; |
| ent->frame = es.frame; |
| ent->colormap = vid.colormap; |
| ent->skinnum = es.skinnum; |
| |
| VectorCopy (es.origin, ent->origin); |
| VectorCopy (es.angles, ent->angles); |
| |
| R_AddEfrags (ent); |
| } |
| |
| /* |
| =================== |
| CL_ParseStaticSound |
| =================== |
| */ |
| void CL_ParseStaticSound (void) |
| { |
| vec3_t org; |
| int sound_num, vol, atten; |
| int i; |
| |
| for (i=0 ; i<3 ; i++) |
| org[i] = MSG_ReadCoord (); |
| sound_num = MSG_ReadByte (); |
| vol = MSG_ReadByte (); |
| atten = MSG_ReadByte (); |
| |
| S_StaticSound (cl.sound_precache[sound_num], org, vol, atten); |
| } |
| |
| |
| |
| /* |
| ===================================================================== |
| |
| ACTION MESSAGES |
| |
| ===================================================================== |
| */ |
| |
| /* |
| ================== |
| CL_ParseStartSoundPacket |
| ================== |
| */ |
| void CL_ParseStartSoundPacket(void) |
| { |
| vec3_t pos; |
| int channel, ent; |
| int sound_num; |
| int volume; |
| float attenuation; |
| int i; |
| |
| channel = MSG_ReadShort(); |
| |
| if (channel & SND_VOLUME) |
| volume = MSG_ReadByte (); |
| else |
| volume = DEFAULT_SOUND_PACKET_VOLUME; |
| |
| if (channel & SND_ATTENUATION) |
| attenuation = MSG_ReadByte () / 64.0; |
| else |
| attenuation = DEFAULT_SOUND_PACKET_ATTENUATION; |
| |
| sound_num = MSG_ReadByte (); |
| |
| for (i=0 ; i<3 ; i++) |
| pos[i] = MSG_ReadCoord (); |
| |
| ent = (channel>>3)&1023; |
| channel &= 7; |
| |
| if (ent > MAX_EDICTS) |
| Host_EndGame ("CL_ParseStartSoundPacket: ent = %i", ent); |
| |
| S_StartSound (ent, channel, cl.sound_precache[sound_num], pos, volume/255.0, attenuation); |
| } |
| |
| |
| /* |
| ================== |
| CL_ParseClientdata |
| |
| Server information pertaining to this client only, sent every frame |
| ================== |
| */ |
| void CL_ParseClientdata (void) |
| { |
| int i; |
| float latency; |
| frame_t *frame; |
| |
| // calculate simulated time of message |
| oldparsecountmod = parsecountmod; |
| |
| i = cls.netchan.incoming_acknowledged; |
| cl.parsecount = i; |
| i &= UPDATE_MASK; |
| parsecountmod = i; |
| frame = &cl.frames[i]; |
| parsecounttime = cl.frames[i].senttime; |
| |
| frame->receivedtime = realtime; |
| |
| // calculate latency |
| latency = frame->receivedtime - frame->senttime; |
| |
| if (latency < 0 || latency > 1.0) |
| { |
| // Con_Printf ("Odd latency: %5.2f\n", latency); |
| } |
| else |
| { |
| // drift the average latency towards the observed latency |
| if (latency < cls.latency) |
| cls.latency = latency; |
| else |
| cls.latency += 0.001; // drift up, so correction are needed |
| } |
| } |
| |
| /* |
| ===================== |
| CL_NewTranslation |
| ===================== |
| */ |
| void CL_NewTranslation (int slot) |
| { |
| #ifdef GLQUAKE |
| if (slot > MAX_CLIENTS) |
| Sys_Error ("CL_NewTranslation: slot > MAX_CLIENTS"); |
| |
| R_TranslatePlayerSkin(slot); |
| #else |
| |
| int i, j; |
| int top, bottom; |
| byte *dest, *source; |
| player_info_t *player; |
| char s[512]; |
| |
| if (slot > MAX_CLIENTS) |
| Sys_Error ("CL_NewTranslation: slot > MAX_CLIENTS"); |
| |
| player = &cl.players[slot]; |
| |
| strcpy(s, Info_ValueForKey(player->userinfo, "skin")); |
| COM_StripExtension(s, s); |
| if (player->skin && !stricmp(s, player->skin->name)) |
| player->skin = NULL; |
| |
| if (player->_topcolor != player->topcolor || |
| player->_bottomcolor != player->bottomcolor || !player->skin) { |
| player->_topcolor = player->topcolor; |
| player->_bottomcolor = player->bottomcolor; |
| |
| dest = player->translations; |
| source = vid.colormap; |
| memcpy (dest, vid.colormap, sizeof(player->translations)); |
| top = player->topcolor; |
| if (top > 13 || top < 0) |
| top = 13; |
| top *= 16; |
| bottom = player->bottomcolor; |
| if (bottom > 13 || bottom < 0) |
| bottom = 13; |
| bottom *= 16; |
| |
| for (i=0 ; i<VID_GRADES ; i++, dest += 256, source+=256) |
| { |
| if (top < 128) // the artists made some backwards ranges. sigh. |
| memcpy (dest + TOP_RANGE, source + top, 16); |
| else |
| for (j=0 ; j<16 ; j++) |
| dest[TOP_RANGE+j] = source[top+15-j]; |
| |
| if (bottom < 128) |
| memcpy (dest + BOTTOM_RANGE, source + bottom, 16); |
| else |
| for (j=0 ; j<16 ; j++) |
| dest[BOTTOM_RANGE+j] = source[bottom+15-j]; |
| } |
| } |
| #endif |
| } |
| |
| /* |
| ============== |
| CL_UpdateUserinfo |
| ============== |
| */ |
| void CL_ProcessUserInfo (int slot, player_info_t *player) |
| { |
| strncpy (player->name, Info_ValueForKey (player->userinfo, "name"), sizeof(player->name)-1); |
| player->topcolor = atoi(Info_ValueForKey (player->userinfo, "topcolor")); |
| player->bottomcolor = atoi(Info_ValueForKey (player->userinfo, "bottomcolor")); |
| if (Info_ValueForKey (player->userinfo, "*spectator")[0]) |
| player->spectator = true; |
| else |
| player->spectator = false; |
| |
| if (cls.state == ca_active) |
| Skin_Find (player); |
| |
| Sbar_Changed (); |
| CL_NewTranslation (slot); |
| } |
| |
| /* |
| ============== |
| CL_UpdateUserinfo |
| ============== |
| */ |
| void CL_UpdateUserinfo (void) |
| { |
| int slot; |
| player_info_t *player; |
| |
| slot = MSG_ReadByte (); |
| if (slot >= MAX_CLIENTS) |
| Host_EndGame ("CL_ParseServerMessage: svc_updateuserinfo > MAX_SCOREBOARD"); |
| |
| player = &cl.players[slot]; |
| player->userid = MSG_ReadLong (); |
| strncpy (player->userinfo, MSG_ReadString(), sizeof(player->userinfo)-1); |
| |
| CL_ProcessUserInfo (slot, player); |
| } |
| |
| /* |
| ============== |
| CL_SetInfo |
| ============== |
| */ |
| void CL_SetInfo (void) |
| { |
| int slot; |
| player_info_t *player; |
| char key[MAX_MSGLEN]; |
| char value[MAX_MSGLEN]; |
| |
| slot = MSG_ReadByte (); |
| if (slot >= MAX_CLIENTS) |
| Host_EndGame ("CL_ParseServerMessage: svc_setinfo > MAX_SCOREBOARD"); |
| |
| player = &cl.players[slot]; |
| |
| strncpy (key, MSG_ReadString(), sizeof(key) - 1); |
| key[sizeof(key) - 1] = 0; |
| strncpy (value, MSG_ReadString(), sizeof(value) - 1); |
| key[sizeof(value) - 1] = 0; |
| |
| Con_DPrintf("SETINFO %s: %s=%s\n", player->name, key, value); |
| |
| Info_SetValueForKey (player->userinfo, key, value, MAX_INFO_STRING); |
| |
| CL_ProcessUserInfo (slot, player); |
| } |
| |
| /* |
| ============== |
| CL_ServerInfo |
| ============== |
| */ |
| void CL_ServerInfo (void) |
| { |
| int slot; |
| player_info_t *player; |
| char key[MAX_MSGLEN]; |
| char value[MAX_MSGLEN]; |
| |
| strncpy (key, MSG_ReadString(), sizeof(key) - 1); |
| key[sizeof(key) - 1] = 0; |
| strncpy (value, MSG_ReadString(), sizeof(value) - 1); |
| key[sizeof(value) - 1] = 0; |
| |
| Con_DPrintf("SERVERINFO: %s=%s\n", key, value); |
| |
| Info_SetValueForKey (cl.serverinfo, key, value, MAX_SERVERINFO_STRING); |
| } |
| |
| /* |
| ===================== |
| CL_SetStat |
| ===================== |
| */ |
| void CL_SetStat (int stat, int value) |
| { |
| int j; |
| if (stat < 0 || stat >= MAX_CL_STATS) |
| Sys_Error ("CL_SetStat: %i is invalid", stat); |
| |
| Sbar_Changed (); |
| |
| if (stat == STAT_ITEMS) |
| { // set flash times |
| Sbar_Changed (); |
| for (j=0 ; j<32 ; j++) |
| if ( (value & (1<<j)) && !(cl.stats[stat] & (1<<j))) |
| cl.item_gettime[j] = cl.time; |
| } |
| |
| cl.stats[stat] = value; |
| } |
| |
| /* |
| ============== |
| CL_MuzzleFlash |
| ============== |
| */ |
| void CL_MuzzleFlash (void) |
| { |
| vec3_t fv, rv, uv; |
| dlight_t *dl; |
| int i; |
| player_state_t *pl; |
| |
| i = MSG_ReadShort (); |
| |
| if ((unsigned)(i-1) >= MAX_CLIENTS) |
| return; |
| |
| #ifdef GLQUAKE |
| // don't draw our own muzzle flash in gl if flashblending |
| if (i-1 == cl.playernum && gl_flashblend.value) |
| return; |
| #endif |
| |
| pl = &cl.frames[parsecountmod].playerstate[i-1]; |
| |
| dl = CL_AllocDlight (i); |
| VectorCopy (pl->origin, dl->origin); |
| AngleVectors (pl->viewangles, fv, rv, uv); |
| |
| VectorMA (dl->origin, 18, fv, dl->origin); |
| dl->radius = 200 + (rand()&31); |
| dl->minlight = 32; |
| dl->die = cl.time + 0.1; |
| dl->color[0] = 0.2; |
| dl->color[1] = 0.1; |
| dl->color[2] = 0.05; |
| dl->color[3] = 0.7; |
| } |
| |
| |
| #define SHOWNET(x) if(cl_shownet.value==2)Con_Printf ("%3i:%s\n", msg_readcount-1, x); |
| /* |
| ===================== |
| CL_ParseServerMessage |
| ===================== |
| */ |
| int received_framecount; |
| void CL_ParseServerMessage (void) |
| { |
| int cmd; |
| char *s; |
| int i, j; |
| |
| received_framecount = host_framecount; |
| cl.last_servermessage = realtime; |
| CL_ClearProjectiles (); |
| |
| // |
| // if recording demos, copy the message out |
| // |
| if (cl_shownet.value == 1) |
| Con_Printf ("%i ",net_message.cursize); |
| else if (cl_shownet.value == 2) |
| Con_Printf ("------------------\n"); |
| |
| |
| CL_ParseClientdata (); |
| |
| // |
| // parse the message |
| // |
| while (1) |
| { |
| if (msg_badread) |
| { |
| Host_EndGame ("CL_ParseServerMessage: Bad server message"); |
| break; |
| } |
| |
| cmd = MSG_ReadByte (); |
| |
| if (cmd == -1) |
| { |
| msg_readcount++; // so the EOM showner has the right value |
| SHOWNET("END OF MESSAGE"); |
| break; |
| } |
| |
| SHOWNET(svc_strings[cmd]); |
| |
| // other commands |
| switch (cmd) |
| { |
| default: |
| Host_EndGame ("CL_ParseServerMessage: Illegible server message"); |
| break; |
| |
| case svc_nop: |
| // Con_Printf ("svc_nop\n"); |
| break; |
| |
| case svc_disconnect: |
| if (cls.state == ca_connected) |
| Host_EndGame ("Server disconnected\n" |
| "Server version may not be compatible"); |
| else |
| Host_EndGame ("Server disconnected"); |
| break; |
| |
| case svc_print: |
| i = MSG_ReadByte (); |
| if (i == PRINT_CHAT) |
| { |
| S_LocalSound ("misc/talk.wav"); |
| con_ormask = 128; |
| } |
| Con_Printf ("%s", MSG_ReadString ()); |
| con_ormask = 0; |
| break; |
| |
| case svc_centerprint: |
| SCR_CenterPrint (MSG_ReadString ()); |
| break; |
| |
| case svc_stufftext: |
| s = MSG_ReadString (); |
| Con_DPrintf ("stufftext: %s\n", s); |
| Cbuf_AddText (s); |
| break; |
| |
| case svc_damage: |
| V_ParseDamage (); |
| break; |
| |
| case svc_serverdata: |
| Cbuf_Execute (); // make sure any stuffed commands are done |
| CL_ParseServerData (); |
| vid.recalc_refdef = true; // leave full screen intermission |
| break; |
| |
| case svc_setangle: |
| for (i=0 ; i<3 ; i++) |
| cl.viewangles[i] = MSG_ReadAngle (); |
| // cl.viewangles[PITCH] = cl.viewangles[ROLL] = 0; |
| break; |
| |
| case svc_lightstyle: |
| i = MSG_ReadByte (); |
| if (i >= MAX_LIGHTSTYLES) |
| Sys_Error ("svc_lightstyle > MAX_LIGHTSTYLES"); |
| Q_strcpy (cl_lightstyle[i].map, MSG_ReadString()); |
| cl_lightstyle[i].length = Q_strlen(cl_lightstyle[i].map); |
| break; |
| |
| case svc_sound: |
| CL_ParseStartSoundPacket(); |
| break; |
| |
| case svc_stopsound: |
| i = MSG_ReadShort(); |
| S_StopSound(i>>3, i&7); |
| break; |
| |
| case svc_updatefrags: |
| Sbar_Changed (); |
| i = MSG_ReadByte (); |
| if (i >= MAX_CLIENTS) |
| Host_EndGame ("CL_ParseServerMessage: svc_updatefrags > MAX_SCOREBOARD"); |
| cl.players[i].frags = MSG_ReadShort (); |
| break; |
| |
| case svc_updateping: |
| i = MSG_ReadByte (); |
| if (i >= MAX_CLIENTS) |
| Host_EndGame ("CL_ParseServerMessage: svc_updateping > MAX_SCOREBOARD"); |
| cl.players[i].ping = MSG_ReadShort (); |
| break; |
| |
| case svc_updatepl: |
| i = MSG_ReadByte (); |
| if (i >= MAX_CLIENTS) |
| Host_EndGame ("CL_ParseServerMessage: svc_updatepl > MAX_SCOREBOARD"); |
| cl.players[i].pl = MSG_ReadByte (); |
| break; |
| |
| case svc_updateentertime: |
| // time is sent over as seconds ago |
| i = MSG_ReadByte (); |
| if (i >= MAX_CLIENTS) |
| Host_EndGame ("CL_ParseServerMessage: svc_updateentertime > MAX_SCOREBOARD"); |
| cl.players[i].entertime = realtime - MSG_ReadFloat (); |
| break; |
| |
| case svc_spawnbaseline: |
| i = MSG_ReadShort (); |
| CL_ParseBaseline (&cl_baselines[i]); |
| break; |
| case svc_spawnstatic: |
| CL_ParseStatic (); |
| break; |
| case svc_temp_entity: |
| CL_ParseTEnt (); |
| break; |
| |
| case svc_killedmonster: |
| cl.stats[STAT_MONSTERS]++; |
| break; |
| |
| case svc_foundsecret: |
| cl.stats[STAT_SECRETS]++; |
| break; |
| |
| case svc_updatestat: |
| i = MSG_ReadByte (); |
| j = MSG_ReadByte (); |
| CL_SetStat (i, j); |
| break; |
| case svc_updatestatlong: |
| i = MSG_ReadByte (); |
| j = MSG_ReadLong (); |
| CL_SetStat (i, j); |
| break; |
| |
| case svc_spawnstaticsound: |
| CL_ParseStaticSound (); |
| break; |
| |
| case svc_cdtrack: |
| cl.cdtrack = MSG_ReadByte (); |
| CDAudio_Play ((byte)cl.cdtrack, true); |
| break; |
| |
| case svc_intermission: |
| cl.intermission = 1; |
| cl.completed_time = realtime; |
| vid.recalc_refdef = true; // go to full screen |
| for (i=0 ; i<3 ; i++) |
| cl.simorg[i] = MSG_ReadCoord (); |
| for (i=0 ; i<3 ; i++) |
| cl.simangles[i] = MSG_ReadAngle (); |
| VectorCopy (vec3_origin, cl.simvel); |
| break; |
| |
| case svc_finale: |
| cl.intermission = 2; |
| cl.completed_time = realtime; |
| vid.recalc_refdef = true; // go to full screen |
| SCR_CenterPrint (MSG_ReadString ()); |
| break; |
| |
| case svc_sellscreen: |
| Cmd_ExecuteString ("help"); |
| break; |
| |
| case svc_smallkick: |
| cl.punchangle = -2; |
| break; |
| case svc_bigkick: |
| cl.punchangle = -4; |
| break; |
| |
| case svc_muzzleflash: |
| CL_MuzzleFlash (); |
| break; |
| |
| case svc_updateuserinfo: |
| CL_UpdateUserinfo (); |
| break; |
| |
| case svc_setinfo: |
| CL_SetInfo (); |
| break; |
| |
| case svc_serverinfo: |
| CL_ServerInfo (); |
| break; |
| |
| case svc_download: |
| CL_ParseDownload (); |
| break; |
| |
| case svc_playerinfo: |
| CL_ParsePlayerinfo (); |
| break; |
| |
| case svc_nails: |
| CL_ParseProjectiles (); |
| break; |
| |
| case svc_chokecount: // some preceding packets were choked |
| i = MSG_ReadByte (); |
| for (j=0 ; j<i ; j++) |
| cl.frames[ (cls.netchan.incoming_acknowledged-1-j)&UPDATE_MASK ].receivedtime = -2; |
| break; |
| |
| case svc_modellist: |
| CL_ParseModellist (); |
| break; |
| |
| case svc_soundlist: |
| CL_ParseSoundlist (); |
| break; |
| |
| case svc_packetentities: |
| CL_ParsePacketEntities (false); |
| break; |
| |
| case svc_deltapacketentities: |
| CL_ParsePacketEntities (true); |
| break; |
| |
| case svc_maxspeed : |
| movevars.maxspeed = MSG_ReadFloat(); |
| break; |
| |
| case svc_entgravity : |
| movevars.entgravity = MSG_ReadFloat(); |
| break; |
| |
| case svc_setpause: |
| cl.paused = MSG_ReadByte (); |
| if (cl.paused) |
| CDAudio_Pause (); |
| else |
| CDAudio_Resume (); |
| break; |
| |
| } |
| } |
| |
| CL_SetSolidEntities (); |
| } |
| |
| |