From: havoc Date: Sat, 25 Feb 2006 10:11:36 +0000 (+0000) Subject: QW support getting closer X-Git-Tag: xonotic-v0.1.0preview~4284 X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fdarkplaces.git;a=commitdiff_plain;h=3478423d370b83ed35cc123790ab24a1939a9acd QW support getting closer git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@6029 d7cf8633-e32d-0410-b094-e92efae38249 --- diff --git a/cl_main.c b/cl_main.c index 83869387..db35eaca 100644 --- a/cl_main.c +++ b/cl_main.c @@ -674,6 +674,61 @@ void CL_UpdateLights(void) } } +void CL_AddQWCTFFlagModel(entity_t *player, int skin) +{ + float f; + entity_t *flag; + matrix4x4_t flagmatrix; + + // this code taken from QuakeWorld + f = 14; + if (player->render.frame2 >= 29 && player->render.frame2 <= 40) + { + if (player->render.frame2 >= 29 && player->render.frame2 <= 34) + { //axpain + if (player->render.frame2 == 29) f = f + 2; + else if (player->render.frame2 == 30) f = f + 8; + else if (player->render.frame2 == 31) f = f + 12; + else if (player->render.frame2 == 32) f = f + 11; + else if (player->render.frame2 == 33) f = f + 10; + else if (player->render.frame2 == 34) f = f + 4; + } + else if (player->render.frame2 >= 35 && player->render.frame2 <= 40) + { // pain + if (player->render.frame2 == 35) f = f + 2; + else if (player->render.frame2 == 36) f = f + 10; + else if (player->render.frame2 == 37) f = f + 10; + else if (player->render.frame2 == 38) f = f + 8; + else if (player->render.frame2 == 39) f = f + 4; + else if (player->render.frame2 == 40) f = f + 2; + } + } + else if (player->render.frame2 >= 103 && player->render.frame2 <= 118) + { + if (player->render.frame2 >= 103 && player->render.frame2 <= 104) f = f + 6; //nailattack + else if (player->render.frame2 >= 105 && player->render.frame2 <= 106) f = f + 6; //light + else if (player->render.frame2 >= 107 && player->render.frame2 <= 112) f = f + 7; //rocketattack + else if (player->render.frame2 >= 112 && player->render.frame2 <= 118) f = f + 7; //shotattack + } + // end of code taken from QuakeWorld + + flag = CL_NewTempEntity(); + if (!flag) + return; + + flag->render.model = cl.model_precache[cl.qw_modelindex_flag]; + flag->render.skinnum = skin; + flag->render.colormap = -1; // no special coloring + flag->render.alpha = 1; + VectorSet(flag->render.colormod, 1, 1, 1); + // attach the flag to the player matrix + Matrix4x4_CreateFromQuakeEntity(&flagmatrix, -f, -22, 0, 0, 0, -45, 1); + Matrix4x4_Concat(&flag->render.matrix, &flagmatrix, &player->render.matrix); + Matrix4x4_Invert_Simple(&flag->render.inversematrix, &flag->render.matrix); + R_LerpAnimation(&flag->render); + CL_BoundingBoxForEntity(&flag->render); +} + #define MAXVIEWMODELS 32 entity_t *viewmodels[MAXVIEWMODELS]; int numviewmodels; @@ -994,6 +1049,11 @@ void CL_LinkNetworkEntity(entity_t *e) dlightcolor[1] += 0.7f; dlightcolor[2] += 0.3f; } + if (e->render.effects & (EF_FLAG1QW | EF_FLAG2QW)) + { + // these are only set on player entities + CL_AddQWCTFFlagModel(e, (e->render.effects & EF_FLAG2QW) != 0); + } } // muzzleflash fades over time, and is offset a bit if (e->persistent.muzzleflash > 0) @@ -1421,6 +1481,34 @@ void CL_RelinkBeams(void) } } +static void CL_RelinkQWNails(void) +{ + int i; + vec_t *v; + entity_t *ent; + + for (i = 0;i < cl.qw_num_nails;i++) + { + v = cl.qw_nails[i]; + + // if we're drawing effects, get a new temp entity + // (NewTempEntity adds it to the render entities list for us) + if (!(ent = CL_NewTempEntity())) + continue; + + // normal stuff + ent->render.model = cl.model_precache[cl.qw_modelindex_spike]; + ent->render.colormap = -1; // no special coloring + ent->render.alpha = 1; + VectorSet(ent->render.colormod, 1, 1, 1); + + Matrix4x4_CreateFromQuakeEntity(&ent->render.matrix, v[0], v[1], v[2], v[3], v[4], v[5], 1); + Matrix4x4_Invert_Simple(&ent->render.inversematrix, &ent->render.matrix); + R_LerpAnimation(&ent->render); + CL_BoundingBoxForEntity(&ent->render); + } +} + void CL_LerpPlayer(float frac) { int i; @@ -1467,6 +1555,7 @@ void CSQC_RelinkAllEntities (int drawmask) CL_RelinkStaticEntities(); CL_RelinkBeams(); CL_RelinkEffects(); + CL_RelinkQWNails(); } } diff --git a/cl_parse.c b/cl_parse.c index de1dbe44..c0aab1f1 100644 --- a/cl_parse.c +++ b/cl_parse.c @@ -158,6 +158,13 @@ char *qw_svc_strings[128] = cvar_t demo_nehahra = {0, "demo_nehahra", "0", "reads all quake demos as nehahra movie protocol"}; cvar_t developer_networkentities = {0, "developer_networkentities", "0", "prints received entities, value is 0-4 (higher for more info)"}; +static qboolean QW_CL_CheckOrDownloadFile(const char *filename); +static void QW_CL_RequestNextDownload(void); +static void QW_CL_NextUpload(void); +void QW_CL_StartUpload(unsigned char *data, int size); +//static qboolean QW_CL_IsUploading(void); +static void QW_CL_StopUpload(void); + /* ================== CL_ParseStartSoundPacket @@ -172,34 +179,56 @@ void CL_ParseStartSoundPacket(int largesoundindex) int field_mask; float attenuation; - field_mask = MSG_ReadByte(); + if (cls.protocol == PROTOCOL_QUAKEWORLD) + { + channel = MSG_ReadShort(); - if (field_mask & SND_VOLUME) - volume = MSG_ReadByte (); - else - volume = DEFAULT_SOUND_PACKET_VOLUME; + if (channel & (1<<15)) + volume = MSG_ReadByte (); + else + volume = DEFAULT_SOUND_PACKET_VOLUME; - if (field_mask & SND_ATTENUATION) - attenuation = MSG_ReadByte () / 64.0; - else - attenuation = DEFAULT_SOUND_PACKET_ATTENUATION; + if (channel & (1<<14)) + attenuation = MSG_ReadByte () / 64.0; + else + attenuation = DEFAULT_SOUND_PACKET_ATTENUATION; - if (field_mask & SND_LARGEENTITY) - { - ent = (unsigned short) MSG_ReadShort (); - channel = MSG_ReadByte (); + ent = (channel>>3)&1023; + channel &= 7; + + sound_num = MSG_ReadByte (); } else { - channel = (unsigned short) MSG_ReadShort (); - ent = channel >> 3; - channel &= 7; - } + field_mask = MSG_ReadByte(); - if (largesoundindex || field_mask & SND_LARGESOUND) - sound_num = (unsigned short) MSG_ReadShort (); - else - sound_num = MSG_ReadByte (); + if (field_mask & SND_VOLUME) + volume = MSG_ReadByte (); + else + volume = DEFAULT_SOUND_PACKET_VOLUME; + + if (field_mask & SND_ATTENUATION) + attenuation = MSG_ReadByte () / 64.0; + else + attenuation = DEFAULT_SOUND_PACKET_ATTENUATION; + + if (field_mask & SND_LARGEENTITY) + { + ent = (unsigned short) MSG_ReadShort (); + channel = MSG_ReadByte (); + } + else + { + channel = (unsigned short) MSG_ReadShort (); + ent = channel >> 3; + channel &= 7; + } + + if (largesoundindex || field_mask & SND_LARGESOUND) + sound_num = (unsigned short) MSG_ReadShort (); + else + sound_num = MSG_ReadByte (); + } MSG_ReadVector(pos, cls.protocol); @@ -317,6 +346,466 @@ void CL_ParseEntityLump(char *entdata) } } +static qboolean QW_CL_CheckOrDownloadFile(const char *filename) +{ + qfile_t *file; + + // see if the file already exists + file = FS_Open(filename, "rb", true, false); + if (file) + { + FS_Close(file); + return true; + } + + // download messages in a demo would be bad + if (cls.demorecording) + { + Con_Printf("Unable to download \"%s\" when recording.\n", filename); + return true; + } + + // don't try to download when playing a demo + if (!cls.netcon) + return true; + + strlcpy(cls.qw_downloadname, filename, sizeof(cls.qw_downloadname)); + Con_Printf("Downloading %s\n", filename); + + if (!cls.qw_downloadmemory) + { + cls.qw_downloadmemory = NULL; + cls.qw_downloadmemorycursize = 0; + cls.qw_downloadmemorymaxsize = 1024*1024; // start out with a 1MB buffer + } + + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va("download %s", filename)); + + cls.qw_downloadnumber++; + cls.qw_downloadpercent = 0; + + return false; +} + +static void QW_CL_RequestNextDownload(void) +{ + int i; + + // clear name of file that just finished + cls.qw_downloadname[0] = 0; + + switch (cls.qw_downloadtype) + { + case dl_single: + break; + case dl_skin: + // TODO + break; + case dl_model: + if (cls.qw_downloadnumber == 0) + { + Con_Printf("Checking models...\n"); + cls.qw_downloadnumber = 1; + } + + cls.qw_downloadtype = dl_model; + for (;cls.qw_downloadnumber < MAX_MODELS && cl.model_name[cls.qw_downloadnumber][0];cls.qw_downloadnumber++) + { + // skip submodels + if (cl.model_name[cls.qw_downloadnumber][0] == '*') + continue; + if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/spike.mdl")) + cl.qw_modelindex_spike = cls.qw_downloadnumber; + if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/player.mdl")) + cl.qw_modelindex_player = cls.qw_downloadnumber; + if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/flag.mdl")) + cl.qw_modelindex_flag = cls.qw_downloadnumber; + if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/s_explod.spr")) + cl.qw_modelindex_s_explod = cls.qw_downloadnumber; + // check if we need to download the file, and return if so + if (!QW_CL_CheckOrDownloadFile(cl.model_name[cls.qw_downloadnumber])) + return; + } + + // touch all of the precached models that are still loaded so we can free + // anything that isn't needed + Mod_ClearUsed(); + for (i = 1;i < MAX_MODELS && cl.model_name[i][0];i++) + Mod_FindName(cl.model_name[i]); + // precache any models used by the client (this also marks them used) + cl.model_bolt = Mod_ForName("progs/bolt.mdl", false, false, false); + cl.model_bolt2 = Mod_ForName("progs/bolt2.mdl", false, false, false); + cl.model_bolt3 = Mod_ForName("progs/bolt3.mdl", false, false, false); + cl.model_beam = Mod_ForName("progs/beam.mdl", false, false, false); + Mod_PurgeUnused(); + + // now we try to load everything that is new + + // world model + cl.model_precache[1] = Mod_ForName(cl.model_name[1], false, false, true); + if (cl.model_precache[1]->Draw == NULL) + Con_Printf("Map %s could not be found or downloaded\n", cl.model_name[1]); + + // normal models + for (i = 2;i < MAX_MODELS && cl.model_name[i][0];i++) + if ((cl.model_precache[i] = Mod_ForName(cl.model_name[i], false, false, false))->Draw == NULL) + Con_Printf("Model %s could not be found or downloaded\n", cl.model_name[i]); + + // done checking sounds and models, send a prespawn command now + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + // FIXME: calculate the checksum2 this wants + //MSG_WriteString(&cls.netcon->message, va(qw_prespawn_name, cl.qw_servercount, cl.worldmodel->checksum2)); + MSG_WriteString(&cls.netcon->message, va(qw_prespawn_name, cl.qw_servercount, 0)); + + if (cls.qw_downloadmemory) + { + Mem_Free(cls.qw_downloadmemory); + cls.qw_downloadmemory = NULL; + } + break; + case dl_sound: + if (cls.qw_downloadnumber == 0) + { + Con_Printf("Checking sounds...\n"); + cls.qw_downloadnumber = 1; + } + + cls.qw_downloadtype = dl_sound; + for (;cl.sound_name[cls.qw_downloadnumber][0];cls.qw_downloadnumber++) + { + // skip subsounds + if (cl.sound_name[cls.qw_downloadnumber][0] == '*') + continue; + // check if we need to download the file, and return if so + if (!QW_CL_CheckOrDownloadFile(cl.sound_name[cls.qw_downloadnumber])) + return; + } + + // load new sounds and unload old ones + // FIXME: S_ServerSounds does not know about cl.sfx_ sounds + S_ServerSounds(cl.sound_name, cls.qw_downloadnumber); + + // precache any sounds used by the client + cl.sfx_wizhit = S_PrecacheSound("sound/wizard/hit.wav", false, true); + cl.sfx_knighthit = S_PrecacheSound("sound/hknight/hit.wav", false, true); + cl.sfx_tink1 = S_PrecacheSound("sound/weapons/tink1.wav", false, true); + cl.sfx_ric1 = S_PrecacheSound("sound/weapons/ric1.wav", false, true); + cl.sfx_ric2 = S_PrecacheSound("sound/weapons/ric2.wav", false, true); + cl.sfx_ric3 = S_PrecacheSound("sound/weapons/ric3.wav", false, true); + cl.sfx_r_exp3 = S_PrecacheSound("sound/weapons/r_exp3.wav", false, true); + + // sounds + for (i = 1;i < MAX_SOUNDS && cl.sound_name[i][0];i++) + { + // Don't lock the sfx here, S_ServerSounds already did that + cl.sound_precache[i] = S_PrecacheSound(cl.sound_name[i], true, false); + } + + // done with sound downloads, next we check models + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va(qw_modellist_name, cl.qw_servercount, 0)); + break; + case dl_none: + default: + Con_Printf("Unknown download type.\n"); + } +} + +static void QW_CL_ParseDownload(void) +{ + int size = MSG_ReadShort(); + int percent = MSG_ReadByte(); + + // skip the download fragment if playing a demo + if (!cls.netcon) + { + if (size > 0) + msg_readcount += size; + return; + } + + if (size == -1) + { + Con_Printf("File not found.\n"); + QW_CL_RequestNextDownload(); + return; + } + + if (msg_readcount + (unsigned short)size > net_message.cursize) + Host_Error("corrupt download message\n"); + + // make sure the buffer is big enough to include this new fragment + if (!cls.qw_downloadmemory || cls.qw_downloadmemorymaxsize < cls.qw_downloadmemorycursize + size) + { + unsigned char *old; + while (cls.qw_downloadmemorymaxsize < cls.qw_downloadmemorycursize + size) + cls.qw_downloadmemorymaxsize *= 2; + old = cls.qw_downloadmemory; + cls.qw_downloadmemory = Mem_Alloc(cl_mempool, cls.qw_downloadmemorymaxsize); + if (old) + { + memcpy(cls.qw_downloadmemory, old, cls.qw_downloadmemorycursize); + Mem_Free(old); + } + } + + // read the fragment out of the packet + MSG_ReadBytes(size, cls.qw_downloadmemory + cls.qw_downloadmemorycursize); + cls.qw_downloadmemorycursize += size; + + cls.qw_downloadpercent = percent; + + if (percent != 100) + { + // request next fragment + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, "nextdl"); + } + else + { + // finished file + Con_Printf("Downloaded \"%s\"\n", cls.qw_downloadname); + + FS_WriteFile(cls.qw_downloadname, cls.qw_downloadmemory, cls.qw_downloadmemorycursize); + + cls.qw_downloadpercent = 0; + + // start downloading the next file (or join the game) + QW_CL_RequestNextDownload(); + } +} + +static void QW_CL_ParseModelList(void) +{ + int n; + int nummodels = MSG_ReadByte(); + char *str; + + // parse model precache list + for (;;) + { + str = MSG_ReadString(); + if (!str[0]) + break; + nummodels++; + if (nummodels==MAX_MODELS) + Host_Error("Server sent too many model precaches"); + if (strlen(str) >= MAX_QPATH) + Host_Error("Server sent a precache name of %i characters (max %i)", strlen(str), MAX_QPATH - 1); + strlcpy(cl.model_name[nummodels], str, sizeof (cl.model_name[nummodels])); + } + + n = MSG_ReadByte(); + { + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va(qw_modellist_name, cl.qw_servercount, n)); + return; + } + + cls.qw_downloadnumber = 0; + cls.qw_downloadtype = dl_model; + QW_CL_RequestNextDownload(); +} + +static void QW_CL_ParseSoundList(void) +{ + int n; + int numsounds = MSG_ReadByte(); + char *str; + + // parse sound precache list + for (;;) + { + str = MSG_ReadString(); + if (!str[0]) + break; + numsounds++; + if (numsounds==MAX_SOUNDS) + Host_Error("Server sent too many sound precaches"); + if (strlen(str) >= MAX_QPATH) + Host_Error("Server sent a precache name of %i characters (max %i)", strlen(str), MAX_QPATH - 1); + strlcpy(cl.sound_name[numsounds], str, sizeof (cl.sound_name[numsounds])); + } + + n = MSG_ReadByte(); + + if (n) + { + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va(qw_soundlist_name, cl.qw_servercount, n)); + return; + } + + cls.qw_downloadnumber = 0; + cls.qw_downloadtype = dl_sound; + QW_CL_RequestNextDownload(); +} + +void QW_CL_NextUpload(void) +{ + int r, percent, size; + + if (!cls.qw_uploaddata) + return; + + r = cls.qw_uploadsize - cls.qw_uploadpos; + if (r > 768) + r = 768; + size = min(1, cls.qw_uploadsize); + percent = (cls.qw_uploadpos+r)*100/size; + + MSG_WriteByte(&cls.netcon->message, qw_clc_upload); + MSG_WriteShort(&cls.netcon->message, r); + MSG_WriteByte(&cls.netcon->message, percent); + SZ_Write(&cls.netcon->message, cls.qw_uploaddata + cls.qw_uploadpos, r); + + Con_DPrintf("UPLOAD: %6d: %d written\n", cls.qw_uploadpos, r); + + cls.qw_uploadpos += r; + + if (cls.qw_uploadpos < cls.qw_uploadsize) + return; + + Con_Printf("Upload completed\n"); + + QW_CL_StopUpload(); +} + +void QW_CL_StartUpload(unsigned char *data, int size) +{ + // do nothing in demos or if not connected + if (!cls.netcon) + return; + + // abort existing upload if in progress + QW_CL_StopUpload(); + + Con_DPrintf("Starting upload of %d bytes...\n", size); + + cls.qw_uploaddata = Mem_Alloc(cl_mempool, size); + memcpy(cls.qw_uploaddata, data, size); + cls.qw_uploadsize = size; + cls.qw_uploadpos = 0; + + QW_CL_NextUpload(); +} + +#if 0 +qboolean QW_CL_IsUploading(void) +{ + return cls.qw_uploaddata != NULL; +} +#endif + +void QW_CL_StopUpload(void) +{ + if (cls.qw_uploaddata) + Mem_Free(cls.qw_uploaddata); + cls.qw_uploaddata = NULL; + cls.qw_uploadsize = 0; + cls.qw_uploadpos = 0; +} + +static void QW_CL_ProcessUserInfo(int slot) +{ + int topcolor, bottomcolor; + char temp[2048]; + InfoString_GetValue(cl.scores[slot].qw_userinfo, "name", cl.scores[slot].name, sizeof(cl.scores[slot].name)); + InfoString_GetValue(cl.scores[slot].qw_userinfo, "topcolor", temp, sizeof(temp));topcolor = atoi(temp); + InfoString_GetValue(cl.scores[slot].qw_userinfo, "bottomcolor", temp, sizeof(temp));bottomcolor = atoi(temp); + cl.scores[slot].colors = topcolor * 16 + bottomcolor; + InfoString_GetValue(cl.scores[slot].qw_userinfo, "*spectator", temp, sizeof(temp)); + cl.scores[slot].qw_spectator = temp[0] != 0; + InfoString_GetValue(cl.scores[slot].qw_userinfo, "skin", cl.scores[slot].qw_skin, sizeof(cl.scores[slot].qw_skin)); + // LordHavoc: abusing Draw_CachePic for caching skins... + cl.scores[slot].qw_skin_cachepic = Draw_CachePic(cl.scores[slot].qw_skin, true); +} + +static void QW_CL_UpdateUserInfo(void) +{ + int slot; + slot = MSG_ReadByte(); + if (slot >= cl.maxclients) + { + Con_Printf("svc_updateuserinfo >= cl.maxclients\n"); + MSG_ReadLong(); + MSG_ReadString(); + return; + } + cl.scores[slot].qw_userid = MSG_ReadLong(); + strlcpy(cl.scores[slot].qw_userinfo, MSG_ReadString(), sizeof(cl.scores[slot].qw_userinfo)); + + QW_CL_ProcessUserInfo(slot); +} + +static void QW_CL_SetInfo(void) +{ + int slot; + char key[2048]; + char value[2048]; + slot = MSG_ReadByte(); + strlcpy(key, MSG_ReadString(), sizeof(key)); + strlcpy(value, MSG_ReadString(), sizeof(value)); + if (slot >= cl.maxclients) + { + Con_Printf("svc_setinfo >= cl.maxclients\n"); + return; + } + InfoString_SetValue(cl.scores[slot].qw_userinfo, sizeof(cl.scores[slot].qw_userinfo), key, value); + + QW_CL_ProcessUserInfo(slot); +} + +static void QW_CL_ServerInfo(void) +{ + char key[2048]; + char value[2048]; + strlcpy(key, MSG_ReadString(), sizeof(key)); + strlcpy(value, MSG_ReadString(), sizeof(value)); + Con_DPrintf("SERVERINFO: %s=%s\n", key, value); + InfoString_SetValue(cl.qw_serverinfo, sizeof(cl.qw_serverinfo), key, value); +} + +static void QW_CL_ParseNails(void) +{ + int i, j; + int numnails = MSG_ReadByte(); + vec_t *v; + unsigned char bits[6]; + cl.qw_num_nails = 0; + for (i = 0;i < numnails;i++) + { + v = cl.qw_nails[cl.qw_num_nails++]; + for (j = 0;j < 6;j++) + bits[j] = MSG_ReadByte(); + v[0] = ( ( bits[0] + ((bits[1]&15)<<8) ) <<1) - 4096; + v[1] = ( ( (bits[1]>>4) + (bits[2]<<4) ) <<1) - 4096; + v[2] = ( ( bits[3] + ((bits[4]&15)<<8) ) <<1) - 4096; + v[3] = 360*(bits[4]>>4)/16; + v[4] = 360*bits[5]/256; + v[5] = 0; + } +} + +static void QW_CL_UpdateItemsAndWeapon(void) +{ + int j; + // check for important changes + + // set flash times + if (cl.olditems != cl.stats[STAT_ITEMS]) + for (j = 0;j < 32;j++) + if ((cl.stats[STAT_ITEMS] & (1< MAX_SCOREBOARD) + if (protocol == PROTOCOL_QUAKEWORLD) { - Host_Error("Bad maxclients (%u) from server", cl.maxclients); - return; - } - cl.scores = (scoreboard_t *)Mem_Alloc(cl_mempool, cl.maxclients*sizeof(*cl.scores)); + cl.qw_servercount = MSG_ReadLong(); + + str = MSG_ReadString(); + Con_Printf("server gamedir is %s\n", str); +#if 0 + // FIXME: change gamedir if needed! + if (strcasecmp(gamedirfile, str)) + { + Host_SaveConfig_f(); + cflag = 1; + } -// parse gametype - cl.gametype = MSG_ReadByte (); + Com_Gamedir(str); // change gamedir -// parse signon message - str = MSG_ReadString (); - strlcpy (cl.levelname, str, sizeof(cl.levelname)); + if (cflag) + { + // exec the new config stuff + } +#endif -// seperate the printfs so the server message can have a color - if (cls.protocol != PROTOCOL_NEHAHRAMOVIE) // no messages when playing the Nehahra movie + // parse maxclients + cl.maxclients = MSG_ReadByte (); + if (cl.maxclients & 128) + { + cl.qw_spectator = true; + cl.maxclients &= ~128; + } + if (cl.maxclients < 1 || cl.maxclients > MAX_SCOREBOARD) + { + Host_Error("Bad maxclients (%u) from server", cl.maxclients); + return; + } + cl.scores = (scoreboard_t *)Mem_Alloc(cl_mempool, cl.maxclients*sizeof(*cl.scores)); + + cl.gametype = GAME_DEATHMATCH; + + // get the full level name + str = MSG_ReadString (); + strlcpy (cl.levelname, str, sizeof(cl.levelname)); + + // get the movevars + cl.qw_movevars_gravity = MSG_ReadFloat(); + cl.qw_movevars_stopspeed = MSG_ReadFloat(); + cl.qw_movevars_maxspeed = MSG_ReadFloat(); + cl.qw_movevars_spectatormaxspeed = MSG_ReadFloat(); + cl.qw_movevars_accelerate = MSG_ReadFloat(); + cl.qw_movevars_airaccelerate = MSG_ReadFloat(); + cl.qw_movevars_wateraccelerate = MSG_ReadFloat(); + cl.qw_movevars_friction = MSG_ReadFloat(); + cl.qw_movevars_waterfriction = MSG_ReadFloat(); + cl.qw_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\2%s\n", str); - // check memory integrity - Mem_CheckSentinelsGlobal(); + // check memory integrity + Mem_CheckSentinelsGlobal(); - S_StopAllSounds(); - // if server is active, we already began a loading plaque - if (!sv.active) - SCR_BeginLoadingPlaque(); + S_StopAllSounds(); + // if server is active, we already began a loading plaque + if (!sv.active) + SCR_BeginLoadingPlaque(); - // disable until we get textures for it - R_ResetSkyBox(); + // disable until we get textures for it + R_ResetSkyBox(); - memset(cl.csqc_model_precache, 0, sizeof(cl.csqc_model_precache)); //[515]: csqc - memset(cl.model_precache, 0, sizeof(cl.model_precache)); - memset(cl.sound_precache, 0, sizeof(cl.sound_precache)); + memset(cl.csqc_model_precache, 0, sizeof(cl.csqc_model_precache)); //[515]: csqc + memset(cl.model_precache, 0, sizeof(cl.model_precache)); + memset(cl.sound_precache, 0, sizeof(cl.sound_precache)); - // parse model precache list - for (nummodels=1 ; ; nummodels++) - { - str = MSG_ReadString(); - if (!str[0]) - break; - if (nummodels==MAX_MODELS) - Host_Error ("Server sent too many model precaches"); - if (strlen(str) >= MAX_QPATH) - Host_Error ("Server sent a precache name of %i characters (max %i)", strlen(str), MAX_QPATH - 1); - strlcpy (cl.model_name[nummodels], str, sizeof (cl.model_name[nummodels])); + MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); + MSG_WriteString(&cls.netcon->message, va(qw_soundlist_name, cl.qw_servercount, 0)); + + cls.state = ca_connected; + cls.signon = 1; } - // parse sound precache list - for (numsounds=1 ; ; numsounds++) + else { - str = MSG_ReadString(); - if (!str[0]) - break; - if (numsounds==MAX_SOUNDS) - Host_Error("Server sent too many sound precaches"); - if (strlen(str) >= MAX_QPATH) - Host_Error("Server sent a precache name of %i characters (max %i)", strlen(str), MAX_QPATH - 1); - strlcpy (cl.sound_name[numsounds], str, sizeof (cl.sound_name[numsounds])); - } + // parse maxclients + cl.maxclients = MSG_ReadByte (); + if (cl.maxclients < 1 || cl.maxclients > MAX_SCOREBOARD) + { + Host_Error("Bad maxclients (%u) from server", cl.maxclients); + return; + } + cl.scores = (scoreboard_t *)Mem_Alloc(cl_mempool, cl.maxclients*sizeof(*cl.scores)); - // touch all of the precached models that are still loaded so we can free - // anything that isn't needed - Mod_ClearUsed(); - for (i = 1;i < nummodels;i++) - Mod_FindName(cl.model_name[i]); - // precache any models used by the client (this also marks them used) - cl.model_bolt = Mod_ForName("progs/bolt.mdl", false, false, false); - cl.model_bolt2 = Mod_ForName("progs/bolt2.mdl", false, false, false); - cl.model_bolt3 = Mod_ForName("progs/bolt3.mdl", false, false, false); - cl.model_beam = Mod_ForName("progs/beam.mdl", false, false, false); - Mod_PurgeUnused(); - - // do the same for sounds - // FIXME: S_ServerSounds does not know about cl.sfx_ sounds - S_ServerSounds (cl.sound_name, numsounds); - - // precache any sounds used by the client - cl.sfx_wizhit = S_PrecacheSound("sound/wizard/hit.wav", false, true); - cl.sfx_knighthit = S_PrecacheSound("sound/hknight/hit.wav", false, true); - cl.sfx_tink1 = S_PrecacheSound("sound/weapons/tink1.wav", false, true); - cl.sfx_ric1 = S_PrecacheSound("sound/weapons/ric1.wav", false, true); - cl.sfx_ric2 = S_PrecacheSound("sound/weapons/ric2.wav", false, true); - cl.sfx_ric3 = S_PrecacheSound("sound/weapons/ric3.wav", false, true); - cl.sfx_r_exp3 = S_PrecacheSound("sound/weapons/r_exp3.wav", false, true); - - // now we try to load everything that is new - - // world model - CL_KeepaliveMessage (); - cl.model_precache[1] = Mod_ForName(cl.model_name[1], false, false, true); - if (cl.model_precache[1]->Draw == NULL) - Con_Printf("Map %s not found\n", cl.model_name[1]); - - // normal models - for (i=2 ; iDraw == NULL) - Con_Printf("Model %s not found\n", cl.model_name[i]); - } + // parse gametype + cl.gametype = MSG_ReadByte (); - // sounds - for (i=1 ; istate_current.active = true; - ent->render.model = cl.worldmodel = cl.model_precache[1]; - ent->render.scale = 1; // some of the renderer still relies on scale - ent->render.alpha = 1; + // check memory integrity + Mem_CheckSentinelsGlobal(); + + S_StopAllSounds(); + // if server is active, we already began a loading plaque + if (!sv.active) + SCR_BeginLoadingPlaque(); + + // disable until we get textures for it + R_ResetSkyBox(); + + memset(cl.csqc_model_precache, 0, sizeof(cl.csqc_model_precache)); //[515]: csqc + memset(cl.model_precache, 0, sizeof(cl.model_precache)); + memset(cl.sound_precache, 0, sizeof(cl.sound_precache)); + + // parse model precache list + for (nummodels=1 ; ; nummodels++) + { + str = MSG_ReadString(); + if (!str[0]) + break; + if (nummodels==MAX_MODELS) + Host_Error ("Server sent too many model precaches"); + if (strlen(str) >= MAX_QPATH) + Host_Error ("Server sent a precache name of %i characters (max %i)", strlen(str), MAX_QPATH - 1); + strlcpy (cl.model_name[nummodels], str, sizeof (cl.model_name[nummodels])); + } + // parse sound precache list + for (numsounds=1 ; ; numsounds++) + { + str = MSG_ReadString(); + if (!str[0]) + break; + if (numsounds==MAX_SOUNDS) + Host_Error("Server sent too many sound precaches"); + if (strlen(str) >= MAX_QPATH) + Host_Error("Server sent a precache name of %i characters (max %i)", strlen(str), MAX_QPATH - 1); + strlcpy (cl.sound_name[numsounds], str, sizeof (cl.sound_name[numsounds])); + } + + // touch all of the precached models that are still loaded so we can free + // anything that isn't needed + Mod_ClearUsed(); + for (i = 1;i < nummodels;i++) + Mod_FindName(cl.model_name[i]); + // precache any models used by the client (this also marks them used) + cl.model_bolt = Mod_ForName("progs/bolt.mdl", false, false, false); + cl.model_bolt2 = Mod_ForName("progs/bolt2.mdl", false, false, false); + cl.model_bolt3 = Mod_ForName("progs/bolt3.mdl", false, false, false); + cl.model_beam = Mod_ForName("progs/beam.mdl", false, false, false); + Mod_PurgeUnused(); + + // do the same for sounds + // FIXME: S_ServerSounds does not know about cl.sfx_ sounds + S_ServerSounds (cl.sound_name, numsounds); + + // precache any sounds used by the client + cl.sfx_wizhit = S_PrecacheSound("sound/wizard/hit.wav", false, true); + cl.sfx_knighthit = S_PrecacheSound("sound/hknight/hit.wav", false, true); + cl.sfx_tink1 = S_PrecacheSound("sound/weapons/tink1.wav", false, true); + cl.sfx_ric1 = S_PrecacheSound("sound/weapons/ric1.wav", false, true); + cl.sfx_ric2 = S_PrecacheSound("sound/weapons/ric2.wav", false, true); + cl.sfx_ric3 = S_PrecacheSound("sound/weapons/ric3.wav", false, true); + cl.sfx_r_exp3 = S_PrecacheSound("sound/weapons/r_exp3.wav", false, true); + + // now we try to load everything that is new + + // world model + CL_KeepaliveMessage (); + cl.model_precache[1] = Mod_ForName(cl.model_name[1], false, false, true); + if (cl.model_precache[1]->Draw == NULL) + Con_Printf("Map %s not found\n", cl.model_name[1]); + + // normal models + for (i=2 ; iDraw == NULL) + Con_Printf("Model %s not found\n", cl.model_name[i]); + } + + // sounds + for (i=1 ; istate_current.active = true; + ent->render.model = cl.worldmodel = cl.model_precache[1]; + ent->render.scale = 1; // some of the renderer still relies on scale + ent->render.alpha = 1; ent->render.colormap = -1; // no special coloring ent->render.flags = RENDER_SHADOW | RENDER_LIGHT; Matrix4x4_CreateFromQuakeEntity(&ent->render.matrix, 0, 0, 0, 0, 0, 0, 1); @@ -983,414 +1553,578 @@ void CL_ParseTempEntity(void) unsigned char *tempcolor; matrix4x4_t tempmatrix; - type = MSG_ReadByte(); - switch (type) + if (cls.protocol == PROTOCOL_QUAKEWORLD) { - case TE_WIZSPIKE: - // spike hitting wall - MSG_ReadVector(pos, cls.protocol); - CL_FindNonSolidLocation(pos, pos, 4); - Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); - //CL_AllocDlight(NULL, &tempmatrix, 100, 0.12f, 0.50f, 0.12f, 500, 0.2, 0, -1, false, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); - CL_RunParticleEffect(pos, vec3_origin, 20, 30); - S_StartSound(-1, 0, cl.sfx_wizhit, pos, 1, 1); - break; + type = MSG_ReadByte(); + switch (type) + { + case QW_TE_WIZSPIKE: + // spike hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + //CL_AllocDlight(NULL, &tempmatrix, 100, 0.12f, 0.50f, 0.12f, 500, 0.2, 0, -1, false, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + CL_RunParticleEffect(pos, vec3_origin, 20, 30); + S_StartSound(-1, 0, cl.sfx_wizhit, pos, 1, 1); + break; - case TE_KNIGHTSPIKE: - // spike hitting wall - MSG_ReadVector(pos, cls.protocol); - CL_FindNonSolidLocation(pos, pos, 4); - Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); - //CL_AllocDlight(NULL, &tempmatrix, 100, 0.50f, 0.30f, 0.10f, 500, 0.2, 0, -1, false, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); - CL_RunParticleEffect(pos, vec3_origin, 226, 20); - S_StartSound(-1, 0, cl.sfx_knighthit, pos, 1, 1); - break; + case QW_TE_KNIGHTSPIKE: + // spike hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + //CL_AllocDlight(NULL, &tempmatrix, 100, 0.50f, 0.30f, 0.10f, 500, 0.2, 0, -1, false, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + CL_RunParticleEffect(pos, vec3_origin, 226, 20); + S_StartSound(-1, 0, cl.sfx_knighthit, pos, 1, 1); + break; - case TE_SPIKE: - // spike hitting wall - MSG_ReadVector(pos, cls.protocol); - CL_FindNonSolidLocation(pos, pos, 4); - if (cl_particles_quake.integer) - CL_RunParticleEffect(pos, vec3_origin, 0, 10); - else if (cl_particles_bulletimpacts.integer) - { - CL_SparkShower(pos, vec3_origin, 15, 1); - CL_Smoke(pos, vec3_origin, 15); - } - CL_BulletMark(pos); - if (rand() % 5) - S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); - else - { - rnd = rand() & 3; - if (rnd == 1) - S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); - else if (rnd == 2) - S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + case QW_TE_SPIKE: + // spike hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + if (cl_particles_quake.integer) + CL_RunParticleEffect(pos, vec3_origin, 0, 10); + else if (cl_particles_bulletimpacts.integer) + { + CL_SparkShower(pos, vec3_origin, 15, 1, 0); + CL_Smoke(pos, vec3_origin, 15, 0); + } + CL_BulletMark(pos); + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); else - S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); - } - break; - case TE_SPIKEQUAD: - // quad spike hitting wall - MSG_ReadVector(pos, cls.protocol); - CL_FindNonSolidLocation(pos, pos, 4); - if (cl_particles_quake.integer) - CL_RunParticleEffect(pos, vec3_origin, 0, 10); - else if (cl_particles_bulletimpacts.integer) - { - CL_SparkShower(pos, vec3_origin, 15, 1); - CL_Smoke(pos, vec3_origin, 15); - } - CL_BulletMark(pos); - Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); - CL_AllocDlight(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); - if (rand() % 5) - S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); - else - { - rnd = rand() & 3; - if (rnd == 1) - S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); - else if (rnd == 2) - S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + break; + case QW_TE_SUPERSPIKE: + // super spike hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + if (cl_particles_quake.integer) + CL_RunParticleEffect(pos, vec3_origin, 0, 20); + else if (cl_particles_bulletimpacts.integer) + { + CL_SparkShower(pos, vec3_origin, 30, 1, 0); + CL_Smoke(pos, vec3_origin, 30, 0); + } + CL_BulletMark(pos); + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); else - S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); - } - break; - case TE_SUPERSPIKE: - // super spike hitting wall - MSG_ReadVector(pos, cls.protocol); - CL_FindNonSolidLocation(pos, pos, 4); - if (cl_particles_quake.integer) - CL_RunParticleEffect(pos, vec3_origin, 0, 20); - else if (cl_particles_bulletimpacts.integer) - { - CL_SparkShower(pos, vec3_origin, 30, 1); - CL_Smoke(pos, vec3_origin, 30); - } - CL_BulletMark(pos); - if (rand() % 5) - S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); - else - { - rnd = rand() & 3; - if (rnd == 1) - S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); - else if (rnd == 2) - S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + break; + + case QW_TE_EXPLOSION: + // rocket explosion + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleExplosion(pos); + // LordHavoc: boosted color from 1.0, 0.8, 0.4 to 1.25, 1.0, 0.5 + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocDlight(NULL, &tempmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + if (gamemode != GAME_NEXUIZ) + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + CL_Effect(pos, cl.qw_modelindex_s_explod, 0, 6, 10); + break; + + case QW_TE_TAREXPLOSION: + // tarbaby explosion + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_BlobExplosion(pos); + + if (gamemode != GAME_NEXUIZ) + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocDlight(NULL, &tempmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + break; + + case QW_TE_LIGHTNING1: + // lightning bolts + CL_ParseBeam(cl.model_bolt, true); + break; + + case QW_TE_LIGHTNING2: + // lightning bolts + CL_ParseBeam(cl.model_bolt2, true); + break; + + case QW_TE_LIGHTNING3: + // lightning bolts + CL_ParseBeam(cl.model_bolt3, false); + break; + + case QW_TE_LAVASPLASH: + MSG_ReadVector(pos, cls.protocol); + CL_LavaSplash(pos); + break; + + case QW_TE_TELEPORT: + MSG_ReadVector(pos, cls.protocol); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocDlight(NULL, &tempmatrix, 200, 1.0f, 1.0f, 1.0f, 600, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + CL_TeleportSplash(pos); + break; + + case QW_TE_GUNSHOT: + // bullet hitting wall + radius = MSG_ReadByte(); + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + if (cl_particles_quake.integer) + CL_RunParticleEffect(pos, vec3_origin, 0, 20*radius); else - S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); - } - break; - case TE_SUPERSPIKEQUAD: - // quad super spike hitting wall - MSG_ReadVector(pos, cls.protocol); - CL_FindNonSolidLocation(pos, pos, 4); - if (cl_particles_quake.integer) - CL_RunParticleEffect(pos, vec3_origin, 0, 20); - else if (cl_particles_bulletimpacts.integer) - { - CL_SparkShower(pos, vec3_origin, 30, 1); - CL_Smoke(pos, vec3_origin, 30); + { + CL_SparkShower(pos, vec3_origin, 15*radius, 1, radius); + CL_Smoke(pos, vec3_origin, 15*radius, radius); + } + // TODO: scatter bullet marks throughout the sphere? + CL_BulletMark(pos); + break; + + case QW_TE_BLOOD: + count = MSG_ReadByte(); + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_BloodPuff(pos, vec3_origin, 20*count); + break; + + case QW_TE_LIGHTNINGBLOOD: + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + CL_BloodPuff(pos, vec3_origin, 50); + break; + + default: + Host_Error("CL_ParseTempEntity: bad type %d (hex %02X)", type, type); } - CL_BulletMark(pos); - Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); - CL_AllocDlight(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); - if (rand() % 5) - S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); - else + } + else + { + type = MSG_ReadByte(); + switch (type) { - rnd = rand() & 3; - if (rnd == 1) - S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); - else if (rnd == 2) - S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + case TE_WIZSPIKE: + // spike hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + //CL_AllocDlight(NULL, &tempmatrix, 100, 0.12f, 0.50f, 0.12f, 500, 0.2, 0, -1, false, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + CL_RunParticleEffect(pos, vec3_origin, 20, 30); + S_StartSound(-1, 0, cl.sfx_wizhit, pos, 1, 1); + break; + + case TE_KNIGHTSPIKE: + // spike hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + //CL_AllocDlight(NULL, &tempmatrix, 100, 0.50f, 0.30f, 0.10f, 500, 0.2, 0, -1, false, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + CL_RunParticleEffect(pos, vec3_origin, 226, 20); + S_StartSound(-1, 0, cl.sfx_knighthit, pos, 1, 1); + break; + + case TE_SPIKE: + // spike hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + if (cl_particles_quake.integer) + CL_RunParticleEffect(pos, vec3_origin, 0, 10); + else if (cl_particles_bulletimpacts.integer) + { + CL_SparkShower(pos, vec3_origin, 15, 1, 0); + CL_Smoke(pos, vec3_origin, 15, 0); + } + CL_BulletMark(pos); + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); else - S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); - } - break; - // LordHavoc: added for improved blood splatters - case TE_BLOOD: - // blood puff - MSG_ReadVector(pos, cls.protocol); - CL_FindNonSolidLocation(pos, pos, 4); - dir[0] = MSG_ReadChar(); - dir[1] = MSG_ReadChar(); - dir[2] = MSG_ReadChar(); - count = MSG_ReadByte(); - CL_BloodPuff(pos, dir, count); - break; - case TE_SPARK: - // spark shower - MSG_ReadVector(pos, cls.protocol); - CL_FindNonSolidLocation(pos, pos, 4); - dir[0] = MSG_ReadChar(); - dir[1] = MSG_ReadChar(); - dir[2] = MSG_ReadChar(); - count = MSG_ReadByte(); - CL_SparkShower(pos, dir, count, 1); - break; - case TE_PLASMABURN: - MSG_ReadVector(pos, cls.protocol); - CL_FindNonSolidLocation(pos, pos, 4); - Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); - CL_AllocDlight(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); - CL_PlasmaBurn(pos); - break; - // LordHavoc: added for improved gore - case TE_BLOODSHOWER: - // vaporized body - MSG_ReadVector(pos, cls.protocol); // mins - MSG_ReadVector(pos2, cls.protocol); // maxs - velspeed = MSG_ReadCoord(cls.protocol); // speed - count = (unsigned short) MSG_ReadShort(); // number of particles - CL_BloodShower(pos, pos2, velspeed, count); - break; - case TE_PARTICLECUBE: - // general purpose particle effect - MSG_ReadVector(pos, cls.protocol); // mins - MSG_ReadVector(pos2, cls.protocol); // maxs - MSG_ReadVector(dir, cls.protocol); // dir - count = (unsigned short) MSG_ReadShort(); // number of particles - colorStart = MSG_ReadByte(); // color - colorLength = MSG_ReadByte(); // gravity (1 or 0) - velspeed = MSG_ReadCoord(cls.protocol); // randomvel - CL_ParticleCube(pos, pos2, dir, count, colorStart, colorLength, velspeed); - break; + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + break; + case TE_SPIKEQUAD: + // quad spike hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + if (cl_particles_quake.integer) + CL_RunParticleEffect(pos, vec3_origin, 0, 10); + else if (cl_particles_bulletimpacts.integer) + { + CL_SparkShower(pos, vec3_origin, 15, 1, 0); + CL_Smoke(pos, vec3_origin, 15, 0); + } + CL_BulletMark(pos); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocDlight(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + break; + case TE_SUPERSPIKE: + // super spike hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + if (cl_particles_quake.integer) + CL_RunParticleEffect(pos, vec3_origin, 0, 20); + else if (cl_particles_bulletimpacts.integer) + { + CL_SparkShower(pos, vec3_origin, 30, 1, 0); + CL_Smoke(pos, vec3_origin, 30, 0); + } + CL_BulletMark(pos); + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + break; + case TE_SUPERSPIKEQUAD: + // quad super spike hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + if (cl_particles_quake.integer) + CL_RunParticleEffect(pos, vec3_origin, 0, 20); + else if (cl_particles_bulletimpacts.integer) + { + CL_SparkShower(pos, vec3_origin, 30, 1, 0); + CL_Smoke(pos, vec3_origin, 30, 0); + } + CL_BulletMark(pos); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocDlight(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + if (rand() % 5) + S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); + else + { + rnd = rand() & 3; + if (rnd == 1) + S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); + else if (rnd == 2) + S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); + else + S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); + } + break; + // LordHavoc: added for improved blood splatters + case TE_BLOOD: + // blood puff + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + dir[0] = MSG_ReadChar(); + dir[1] = MSG_ReadChar(); + dir[2] = MSG_ReadChar(); + count = MSG_ReadByte(); + CL_BloodPuff(pos, dir, count); + break; + case TE_SPARK: + // spark shower + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + dir[0] = MSG_ReadChar(); + dir[1] = MSG_ReadChar(); + dir[2] = MSG_ReadChar(); + count = MSG_ReadByte(); + CL_SparkShower(pos, dir, count, 1, 0); + break; + case TE_PLASMABURN: + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocDlight(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + CL_PlasmaBurn(pos); + break; + // LordHavoc: added for improved gore + case TE_BLOODSHOWER: + // vaporized body + MSG_ReadVector(pos, cls.protocol); // mins + MSG_ReadVector(pos2, cls.protocol); // maxs + velspeed = MSG_ReadCoord(cls.protocol); // speed + count = (unsigned short) MSG_ReadShort(); // number of particles + CL_BloodShower(pos, pos2, velspeed, count); + break; + case TE_PARTICLECUBE: + // general purpose particle effect + MSG_ReadVector(pos, cls.protocol); // mins + MSG_ReadVector(pos2, cls.protocol); // maxs + MSG_ReadVector(dir, cls.protocol); // dir + count = (unsigned short) MSG_ReadShort(); // number of particles + colorStart = MSG_ReadByte(); // color + colorLength = MSG_ReadByte(); // gravity (1 or 0) + velspeed = MSG_ReadCoord(cls.protocol); // randomvel + CL_ParticleCube(pos, pos2, dir, count, colorStart, colorLength, velspeed); + break; - case TE_PARTICLERAIN: - // general purpose particle effect - MSG_ReadVector(pos, cls.protocol); // mins - MSG_ReadVector(pos2, cls.protocol); // maxs - MSG_ReadVector(dir, cls.protocol); // dir - count = (unsigned short) MSG_ReadShort(); // number of particles - colorStart = MSG_ReadByte(); // color - CL_ParticleRain(pos, pos2, dir, count, colorStart, 0); - break; + case TE_PARTICLERAIN: + // general purpose particle effect + MSG_ReadVector(pos, cls.protocol); // mins + MSG_ReadVector(pos2, cls.protocol); // maxs + MSG_ReadVector(dir, cls.protocol); // dir + count = (unsigned short) MSG_ReadShort(); // number of particles + colorStart = MSG_ReadByte(); // color + CL_ParticleRain(pos, pos2, dir, count, colorStart, 0); + break; - case TE_PARTICLESNOW: - // general purpose particle effect - MSG_ReadVector(pos, cls.protocol); // mins - MSG_ReadVector(pos2, cls.protocol); // maxs - MSG_ReadVector(dir, cls.protocol); // dir - count = (unsigned short) MSG_ReadShort(); // number of particles - colorStart = MSG_ReadByte(); // color - CL_ParticleRain(pos, pos2, dir, count, colorStart, 1); - break; + case TE_PARTICLESNOW: + // general purpose particle effect + MSG_ReadVector(pos, cls.protocol); // mins + MSG_ReadVector(pos2, cls.protocol); // maxs + MSG_ReadVector(dir, cls.protocol); // dir + count = (unsigned short) MSG_ReadShort(); // number of particles + colorStart = MSG_ReadByte(); // color + CL_ParticleRain(pos, pos2, dir, count, colorStart, 1); + break; - case TE_GUNSHOT: - // bullet hitting wall - MSG_ReadVector(pos, cls.protocol); - CL_FindNonSolidLocation(pos, pos, 4); - if (cl_particles_quake.integer) - CL_RunParticleEffect(pos, vec3_origin, 0, 20); - else - { - CL_SparkShower(pos, vec3_origin, 15, 1); - CL_Smoke(pos, vec3_origin, 15); - } - CL_BulletMark(pos); - break; + case TE_GUNSHOT: + // bullet hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + if (cl_particles_quake.integer) + CL_RunParticleEffect(pos, vec3_origin, 0, 20); + else + { + CL_SparkShower(pos, vec3_origin, 15, 1, 0); + CL_Smoke(pos, vec3_origin, 15, 0); + } + CL_BulletMark(pos); + break; - case TE_GUNSHOTQUAD: - // quad bullet hitting wall - MSG_ReadVector(pos, cls.protocol); - CL_FindNonSolidLocation(pos, pos, 4); - if (cl_particles_quake.integer) - CL_RunParticleEffect(pos, vec3_origin, 0, 20); - else - { - CL_SparkShower(pos, vec3_origin, 15, 1); - CL_Smoke(pos, vec3_origin, 15); - } - CL_BulletMark(pos); - Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); - CL_AllocDlight(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); - break; + case TE_GUNSHOTQUAD: + // quad bullet hitting wall + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + if (cl_particles_quake.integer) + CL_RunParticleEffect(pos, vec3_origin, 0, 20); + else + { + CL_SparkShower(pos, vec3_origin, 15, 1, 0); + CL_Smoke(pos, vec3_origin, 15, 0); + } + CL_BulletMark(pos); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocDlight(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + break; - case TE_EXPLOSION: - // rocket explosion - MSG_ReadVector(pos, cls.protocol); - CL_FindNonSolidLocation(pos, pos, 10); - CL_ParticleExplosion(pos); - // LordHavoc: boosted color from 1.0, 0.8, 0.4 to 1.25, 1.0, 0.5 - Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); - CL_AllocDlight(NULL, &tempmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); - if (gamemode != GAME_NEXUIZ) - S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); - break; + case TE_EXPLOSION: + // rocket explosion + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleExplosion(pos); + // LordHavoc: boosted color from 1.0, 0.8, 0.4 to 1.25, 1.0, 0.5 + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocDlight(NULL, &tempmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + if (gamemode != GAME_NEXUIZ) + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; - case TE_EXPLOSIONQUAD: - // quad rocket explosion - MSG_ReadVector(pos, cls.protocol); - CL_FindNonSolidLocation(pos, pos, 10); - CL_ParticleExplosion(pos); - Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); - CL_AllocDlight(NULL, &tempmatrix, 350, 2.5f, 2.0f, 4.0f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); - if (gamemode != GAME_NEXUIZ) - S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); - break; + case TE_EXPLOSIONQUAD: + // quad rocket explosion + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleExplosion(pos); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocDlight(NULL, &tempmatrix, 350, 2.5f, 2.0f, 4.0f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + if (gamemode != GAME_NEXUIZ) + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; - case TE_EXPLOSION3: - // Nehahra movie colored lighting explosion - MSG_ReadVector(pos, cls.protocol); - CL_FindNonSolidLocation(pos, pos, 10); - CL_ParticleExplosion(pos); - Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); - color[0] = MSG_ReadCoord(cls.protocol) * (2.0f / 1.0f); - color[1] = MSG_ReadCoord(cls.protocol) * (2.0f / 1.0f); - color[2] = MSG_ReadCoord(cls.protocol) * (2.0f / 1.0f); - CL_AllocDlight(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); - if (gamemode != GAME_NEXUIZ) - S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); - break; + case TE_EXPLOSION3: + // Nehahra movie colored lighting explosion + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleExplosion(pos); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + color[0] = MSG_ReadCoord(cls.protocol) * (2.0f / 1.0f); + color[1] = MSG_ReadCoord(cls.protocol) * (2.0f / 1.0f); + color[2] = MSG_ReadCoord(cls.protocol) * (2.0f / 1.0f); + CL_AllocDlight(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); + if (gamemode != GAME_NEXUIZ) + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; - case TE_EXPLOSIONRGB: - // colored lighting explosion - MSG_ReadVector(pos, cls.protocol); - CL_FindNonSolidLocation(pos, pos, 10); - CL_ParticleExplosion(pos); - color[0] = MSG_ReadByte() * (2.0f / 255.0f); - color[1] = MSG_ReadByte() * (2.0f / 255.0f); - color[2] = MSG_ReadByte() * (2.0f / 255.0f); - Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); - CL_AllocDlight(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); - if (gamemode != GAME_NEXUIZ) - S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); - break; + case TE_EXPLOSIONRGB: + // colored lighting explosion + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleExplosion(pos); + color[0] = MSG_ReadByte() * (2.0f / 255.0f); + color[1] = MSG_ReadByte() * (2.0f / 255.0f); + color[2] = MSG_ReadByte() * (2.0f / 255.0f); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocDlight(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); + if (gamemode != GAME_NEXUIZ) + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; - case TE_TAREXPLOSION: - // tarbaby explosion - MSG_ReadVector(pos, cls.protocol); - CL_FindNonSolidLocation(pos, pos, 10); - CL_BlobExplosion(pos); + case TE_TAREXPLOSION: + // tarbaby explosion + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_BlobExplosion(pos); - if (gamemode != GAME_NEXUIZ) - S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); - Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); - CL_AllocDlight(NULL, &tempmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); - break; + if (gamemode != GAME_NEXUIZ) + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocDlight(NULL, &tempmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + break; - case TE_SMALLFLASH: - MSG_ReadVector(pos, cls.protocol); - CL_FindNonSolidLocation(pos, pos, 10); - Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); - CL_AllocDlight(NULL, &tempmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); - break; + case TE_SMALLFLASH: + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocDlight(NULL, &tempmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + break; - case TE_CUSTOMFLASH: - MSG_ReadVector(pos, cls.protocol); - CL_FindNonSolidLocation(pos, pos, 4); - radius = (MSG_ReadByte() + 1) * 8; - velspeed = (MSG_ReadByte() + 1) * (1.0 / 256.0); - color[0] = MSG_ReadByte() * (2.0f / 255.0f); - color[1] = MSG_ReadByte() * (2.0f / 255.0f); - color[2] = MSG_ReadByte() * (2.0f / 255.0f); - Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); - CL_AllocDlight(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); - break; + case TE_CUSTOMFLASH: + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 4); + radius = (MSG_ReadByte() + 1) * 8; + velspeed = (MSG_ReadByte() + 1) * (1.0 / 256.0); + color[0] = MSG_ReadByte() * (2.0f / 255.0f); + color[1] = MSG_ReadByte() * (2.0f / 255.0f); + color[2] = MSG_ReadByte() * (2.0f / 255.0f); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocDlight(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); + break; - case TE_FLAMEJET: - MSG_ReadVector(pos, cls.protocol); - MSG_ReadVector(dir, cls.protocol); - count = MSG_ReadByte(); - CL_Flames(pos, dir, count); - break; + case TE_FLAMEJET: + MSG_ReadVector(pos, cls.protocol); + MSG_ReadVector(dir, cls.protocol); + count = MSG_ReadByte(); + CL_Flames(pos, dir, count); + break; - case TE_LIGHTNING1: - // lightning bolts - CL_ParseBeam(cl.model_bolt, true); - break; + case TE_LIGHTNING1: + // lightning bolts + CL_ParseBeam(cl.model_bolt, true); + break; - case TE_LIGHTNING2: - // lightning bolts - CL_ParseBeam(cl.model_bolt2, true); - break; + case TE_LIGHTNING2: + // lightning bolts + CL_ParseBeam(cl.model_bolt2, true); + break; - case TE_LIGHTNING3: - // lightning bolts - CL_ParseBeam(cl.model_bolt3, false); - break; + case TE_LIGHTNING3: + // lightning bolts + CL_ParseBeam(cl.model_bolt3, false); + break; -// PGM 01/21/97 - case TE_BEAM: - // grappling hook beam - CL_ParseBeam(cl.model_beam, false); - break; -// PGM 01/21/97 + // PGM 01/21/97 + case TE_BEAM: + // grappling hook beam + CL_ParseBeam(cl.model_beam, false); + break; + // PGM 01/21/97 -// LordHavoc: for compatibility with the Nehahra movie... - case TE_LIGHTNING4NEH: - CL_ParseBeam(Mod_ForName(MSG_ReadString(), true, false, false), false); - break; + // LordHavoc: for compatibility with the Nehahra movie... + case TE_LIGHTNING4NEH: + CL_ParseBeam(Mod_ForName(MSG_ReadString(), true, false, false), false); + break; - case TE_LAVASPLASH: - MSG_ReadVector(pos, cls.protocol); - CL_LavaSplash(pos); - break; + case TE_LAVASPLASH: + MSG_ReadVector(pos, cls.protocol); + CL_LavaSplash(pos); + break; - case TE_TELEPORT: - MSG_ReadVector(pos, cls.protocol); - Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); - CL_AllocDlight(NULL, &tempmatrix, 200, 1.0f, 1.0f, 1.0f, 600, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); - CL_TeleportSplash(pos); - break; + case TE_TELEPORT: + MSG_ReadVector(pos, cls.protocol); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocDlight(NULL, &tempmatrix, 200, 1.0f, 1.0f, 1.0f, 600, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + CL_TeleportSplash(pos); + break; - case TE_EXPLOSION2: - // color mapped explosion - MSG_ReadVector(pos, cls.protocol); - CL_FindNonSolidLocation(pos, pos, 10); - colorStart = MSG_ReadByte(); - colorLength = MSG_ReadByte(); - CL_ParticleExplosion2(pos, colorStart, colorLength); - tempcolor = (unsigned char *)&palette_complete[(rand()%colorLength) + colorStart]; - color[0] = tempcolor[0] * (2.0f / 255.0f); - color[1] = tempcolor[1] * (2.0f / 255.0f); - color[2] = tempcolor[2] * (2.0f / 255.0f); - Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); - CL_AllocDlight(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); - if (gamemode != GAME_NEXUIZ) - S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); - break; + case TE_EXPLOSION2: + // color mapped explosion + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + colorStart = MSG_ReadByte(); + colorLength = MSG_ReadByte(); + CL_ParticleExplosion2(pos, colorStart, colorLength); + tempcolor = (unsigned char *)&palette_complete[(rand()%colorLength) + colorStart]; + color[0] = tempcolor[0] * (2.0f / 255.0f); + color[1] = tempcolor[1] * (2.0f / 255.0f); + color[2] = tempcolor[2] * (2.0f / 255.0f); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocDlight(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); + if (gamemode != GAME_NEXUIZ) + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; - case TE_TEI_G3: - MSG_ReadVector(pos, cls.protocol); - MSG_ReadVector(pos2, cls.protocol); - MSG_ReadVector(dir, cls.protocol); - CL_BeamParticle(pos, pos2, 8, 1, 1, 1, 1, 1); - break; + case TE_TEI_G3: + MSG_ReadVector(pos, cls.protocol); + MSG_ReadVector(pos2, cls.protocol); + MSG_ReadVector(dir, cls.protocol); + CL_BeamParticle(pos, pos2, 8, 1, 1, 1, 1, 1); + break; - case TE_TEI_SMOKE: - MSG_ReadVector(pos, cls.protocol); - MSG_ReadVector(dir, cls.protocol); - count = MSG_ReadByte(); - CL_FindNonSolidLocation(pos, pos, 4); - CL_Tei_Smoke(pos, dir, count); - break; + case TE_TEI_SMOKE: + MSG_ReadVector(pos, cls.protocol); + MSG_ReadVector(dir, cls.protocol); + count = MSG_ReadByte(); + CL_FindNonSolidLocation(pos, pos, 4); + CL_Tei_Smoke(pos, dir, count); + break; - case TE_TEI_BIGEXPLOSION: - MSG_ReadVector(pos, cls.protocol); - CL_FindNonSolidLocation(pos, pos, 10); - CL_ParticleExplosion(pos); - Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); - CL_AllocDlight(NULL, &tempmatrix, 500, 2.5f, 2.0f, 1.0f, 500, 9999, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); - if (gamemode != GAME_NEXUIZ) - S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); - break; + case TE_TEI_BIGEXPLOSION: + MSG_ReadVector(pos, cls.protocol); + CL_FindNonSolidLocation(pos, pos, 10); + CL_ParticleExplosion(pos); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocDlight(NULL, &tempmatrix, 500, 2.5f, 2.0f, 1.0f, 500, 9999, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + if (gamemode != GAME_NEXUIZ) + S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); + break; - case TE_TEI_PLASMAHIT: - MSG_ReadVector(pos, cls.protocol); - MSG_ReadVector(dir, cls.protocol); - count = MSG_ReadByte(); - CL_FindNonSolidLocation(pos, pos, 5); - CL_Tei_PlasmaHit(pos, dir, count); - Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); - CL_AllocDlight(NULL, &tempmatrix, 500, 0.6, 1.2, 2.0f, 2000, 9999, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); - break; + case TE_TEI_PLASMAHIT: + MSG_ReadVector(pos, cls.protocol); + MSG_ReadVector(dir, cls.protocol); + count = MSG_ReadByte(); + CL_FindNonSolidLocation(pos, pos, 5); + CL_Tei_PlasmaHit(pos, dir, count); + Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); + CL_AllocDlight(NULL, &tempmatrix, 500, 0.6, 1.2, 2.0f, 2000, 9999, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); + break; - default: - Host_Error("CL_ParseTempEntity: bad type %d (hex %02X)", type, type); + default: + Host_Error("CL_ParseTempEntity: bad type %d (hex %02X)", type, type); + } } } @@ -1442,405 +2176,719 @@ void CL_ParseServerMessage(void) parsingerror = true; - while (1) + if (cls.protocol == PROTOCOL_QUAKEWORLD) { - if (msg_badread) - Host_Error ("CL_ParseServerMessage: Bad server message"); - - cmd = MSG_ReadByte (); + cl.mtime[1] = cl.mtime[0]; + cl.mtime[0] = realtime; // qw has no clock - if (cmd == -1) + while (1) { - SHOWNET("END OF MESSAGE"); - break; // end of message - } + if (msg_badread) + Host_Error ("CL_ParseServerMessage: Bad QW server message"); - cmdindex = cmdcount & 31; - cmdcount++; - cmdlog[cmdindex] = cmd; + cmd = MSG_ReadByte (); - // if the high bit of the command byte is set, it is a fast update - if (cmd & 128) - { - // 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) - temp = "entity"; - cmdlogname[cmdindex] = temp; - SHOWNET("fast update"); - if (cls.signon == SIGNONS - 1) + if (cmd == -1) { - // first update is the final signon stage - cls.signon = SIGNONS; - CL_SignonReply (); + SHOWNET("END OF MESSAGE"); + break; // end of message } - EntityFrameQuake_ReadEntity (cmd&127); - continue; - } - SHOWNET(svc_strings[cmd]); - cmdlogname[cmdindex] = svc_strings[cmd]; - if (!cmdlogname[cmdindex]) - { - // 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) - temp = ""; - cmdlogname[cmdindex] = temp; - } + cmdindex = cmdcount & 31; + cmdcount++; + cmdlog[cmdindex] = cmd; - // other commands - switch (cmd) - { - default: + SHOWNET(qw_svc_strings[cmd]); + cmdlogname[cmdindex] = qw_svc_strings[cmd]; + if (!cmdlogname[cmdindex]) { - char description[32*64], temp[64]; - int count; - strcpy (description, "packet dump: "); - i = cmdcount - 32; - if (i < 0) - i = 0; - count = cmdcount - i; - i &= 31; - while(count > 0) + // 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) + temp = ""; + cmdlogname[cmdindex] = temp; + } + + // other commands + switch (cmd) + { + default: { - dpsnprintf (temp, sizeof (temp), "%3i:%s ", cmdlog[i], cmdlogname[i]); - strlcat (description, temp, sizeof (description)); - count--; - i++; + char description[32*64], temp[64]; + int count; + strcpy(description, "packet dump: "); + i = cmdcount - 32; + if (i < 0) + i = 0; + count = cmdcount - i; i &= 31; + while(count > 0) + { + dpsnprintf(temp, sizeof(temp), "%3i:%s ", cmdlog[i], cmdlogname[i]); + strlcat(description, temp, sizeof(description)); + count--; + i++; + i &= 31; + } + description[strlen(description)-1] = '\n'; // replace the last space with a newline + Con_Print(description); + Host_Error("CL_ParseServerMessage: Illegible server message"); } - description[strlen(description)-1] = '\n'; // replace the last space with a newline - Con_Print(description); - Host_Error ("CL_ParseServerMessage: Illegible server message"); - } - break; + break; - case svc_nop: - if (cls.signon < SIGNONS) - Con_Print("<-- server to client keepalive\n"); - break; + case qw_svc_nop: + //Con_Printf("qw_svc_nop\n"); + break; - case svc_time: - cl.mtime[1] = cl.mtime[0]; - cl.mtime[0] = MSG_ReadFloat (); - cl.movement_needupdate = true; - break; + case qw_svc_disconnect: + Con_Printf("Server disconnected\n"); + if (cls.demonum != -1) + CL_NextDemo(); + else + CL_Disconnect(); + break; - case svc_clientdata: - CL_ParseClientdata(); - break; + case qw_svc_print: + i = MSG_ReadByte(); + if (i == 3) // chat + CSQC_AddPrintText(va("\1%s", MSG_ReadString())); //[515]: csqc + else + CSQC_AddPrintText(MSG_ReadString()); + break; - case svc_version: - i = MSG_ReadLong (); - protocol = Protocol_EnumForNumber(i); - if (protocol == PROTOCOL_UNKNOWN) - Host_Error("CL_ParseServerMessage: Server is unrecognized protocol number (%i)", i); - // hack for unmarked Nehahra movie demos which had a custom protocol - if (protocol == PROTOCOL_QUAKEDP && cls.demoplayback && demo_nehahra.integer) - protocol = PROTOCOL_NEHAHRAMOVIE; - cls.protocol = protocol; - break; + case qw_svc_centerprint: + CL_VM_Parse_CenterPrint(MSG_ReadString ()); //[515]: csqc + break; - case svc_disconnect: - Con_Printf ("Server disconnected\n"); - if (cls.demonum != -1) - CL_NextDemo (); - else - CL_Disconnect (); - break; + case qw_svc_stufftext: + CL_VM_Parse_StuffCmd(MSG_ReadString ()); //[515]: csqc + break; - case svc_print: - CSQC_AddPrintText(MSG_ReadString()); //[515]: csqc - break; + case qw_svc_damage: + // svc_damage protocol is identical to nq + V_ParseDamage (); + break; - case svc_centerprint: - CL_VM_Parse_CenterPrint(MSG_ReadString ()); //[515]: csqc - break; + case qw_svc_serverdata: + //Cbuf_Execute(); // make sure any stuffed commands are done + CL_ParseServerInfo(); + CL_VM_Init(); //[515]: init csqc + break; - case svc_stufftext: - CL_VM_Parse_StuffCmd(MSG_ReadString ()); //[515]: csqc - break; + case qw_svc_setangle: + for (i=0 ; i<3 ; i++) + cl.viewangles[i] = MSG_ReadAngle (cls.protocol); + break; - case svc_damage: - V_ParseDamage (); - break; + case qw_svc_lightstyle: + i = MSG_ReadByte (); + if (i >= cl_max_lightstyle) + { + Con_Printf ("svc_lightstyle >= MAX_LIGHTSTYLES"); + break; + } + strlcpy (cl_lightstyle[i].map, MSG_ReadString(), sizeof (cl_lightstyle[i].map)); + cl_lightstyle[i].map[MAX_STYLESTRING - 1] = 0; + cl_lightstyle[i].length = (int)strlen(cl_lightstyle[i].map); + break; - case svc_serverinfo: - CL_ParseServerInfo (); - CL_VM_Init(); //[515]: init csqc - break; + case qw_svc_sound: + CL_ParseStartSoundPacket(false); + break; - case svc_setangle: - for (i=0 ; i<3 ; i++) - cl.viewangles[i] = MSG_ReadAngle (cls.protocol); - break; + case qw_svc_stopsound: + i = (unsigned short) MSG_ReadShort(); + S_StopSound(i>>3, i&7); + break; - case svc_setview: - cl.viewentity = (unsigned short)MSG_ReadShort (); - if (cl.viewentity >= MAX_EDICTS) - Host_Error("svc_setview >= MAX_EDICTS"); - if (cl.viewentity >= cl_max_entities) - CL_ExpandEntities(cl.viewentity); - // LordHavoc: assume first setview recieved is the real player entity - if (!cl.playerentity) - cl.playerentity = cl.viewentity; - break; + case qw_svc_updatefrags: + i = MSG_ReadByte(); + if (i >= cl.maxclients) + Host_Error("CL_ParseServerMessage: svc_updatefrags >= cl.maxclients"); + cl.scores[i].frags = (signed short) MSG_ReadShort(); + break; - case svc_lightstyle: - i = MSG_ReadByte (); - if (i >= cl_max_lightstyle) - { - Con_Printf ("svc_lightstyle >= MAX_LIGHTSTYLES"); + case qw_svc_updateping: + i = MSG_ReadByte(); + if (i >= cl.maxclients) + Host_Error("CL_ParseServerMessage: svc_updateping >= cl.maxclients"); + cl.scores[i].qw_ping = MSG_ReadShort(); + break; + + case qw_svc_updatepl: + i = MSG_ReadByte(); + if (i >= cl.maxclients) + Host_Error("CL_ParseServerMessage: svc_updatepl >= cl.maxclients"); + cl.scores[i].qw_packetloss = MSG_ReadByte(); + break; + + case qw_svc_updateentertime: + i = MSG_ReadByte(); + if (i >= cl.maxclients) + Host_Error("CL_ParseServerMessage: svc_updateentertime >= cl.maxclients"); + // seconds ago + cl.scores[i].qw_entertime = realtime - MSG_ReadFloat(); + break; + + case qw_svc_spawnbaseline: + i = (unsigned short) MSG_ReadShort(); + if (i < 0 || i >= MAX_EDICTS) + Host_Error ("CL_ParseServerMessage: svc_spawnbaseline: invalid entity number %i", i); + if (i >= cl_max_entities) + CL_ExpandEntities(i); + CL_ParseBaseline(cl_entities + i, false); + break; + case qw_svc_spawnstatic: + CL_ParseStatic(false); + break; + case qw_svc_temp_entity: + if(!CL_VM_Parse_TempEntity()) + CL_ParseTempEntity (); + break; + + case qw_svc_killedmonster: + cl.stats[STAT_MONSTERS]++; + break; + + case qw_svc_foundsecret: + cl.stats[STAT_SECRETS]++; + break; + + case qw_svc_updatestat: + i = MSG_ReadByte (); + if (i < 0 || i >= MAX_CL_STATS) + Host_Error ("svc_updatestat: %i is invalid", i); + cl.stats[i] = MSG_ReadByte (); + break; + + case qw_svc_updatestatlong: + i = MSG_ReadByte (); + if (i < 0 || i >= MAX_CL_STATS) + Host_Error ("svc_updatestatlong: %i is invalid", i); + cl.stats[i] = MSG_ReadLong (); + break; + + case qw_svc_spawnstaticsound: + CL_ParseStaticSound (false); + break; + + case qw_svc_cdtrack: + cl.cdtrack = MSG_ReadByte (); + cl.looptrack = MSG_ReadByte (); + if ( (cls.demoplayback || cls.demorecording) && (cls.forcetrack != -1) ) + CDAudio_Play ((unsigned char)cls.forcetrack, true); + else + CDAudio_Play ((unsigned char)cl.cdtrack, true); + break; + + case qw_svc_intermission: + cl.intermission = 1; + cl.completed_time = cl.time; + MSG_ReadVector(cl.qw_intermission_origin, cls.protocol); + for (i = 0;i < 3;i++) + cl.qw_intermission_angles[i] = MSG_ReadAngle(cls.protocol); + break; + + case qw_svc_finale: + cl.intermission = 2; + cl.completed_time = cl.time; + SCR_CenterPrint(MSG_ReadString ()); + break; + + case qw_svc_sellscreen: + Cmd_ExecuteString ("help", src_command); + break; + + case qw_svc_smallkick: + Con_Printf("TODO: qw_svc_smallkick\n"); + break; + case qw_svc_bigkick: + Con_Printf("TODO: qw_svc_bigkick\n"); + break; + + case qw_svc_muzzleflash: + i = (unsigned short) MSG_ReadShort(); + // NOTE: in QW this only worked on clients + if (i < 0 || i >= MAX_EDICTS) + Host_Error("CL_ParseServerMessage: svc_spawnbaseline: invalid entity number %i", i); + if (i >= cl_max_entities) + CL_ExpandEntities(i); + cl_entities[i].persistent.muzzleflash = 1.0f; + break; + + case qw_svc_updateuserinfo: + QW_CL_UpdateUserInfo(); + break; + + case qw_svc_setinfo: + QW_CL_SetInfo(); + break; + + case qw_svc_serverinfo: + QW_CL_ServerInfo(); + break; + + case qw_svc_download: + QW_CL_ParseDownload(); + break; + + case qw_svc_playerinfo: + EntityStateQW_ReadPlayerUpdate(); + break; + + case qw_svc_nails: + QW_CL_ParseNails(); + break; + + case qw_svc_chokecount: + i = MSG_ReadByte(); + // FIXME: apply to netgraph + //for (j = 0;j < i;j++) + // cl.frames[(cls.netcon->qw.incoming_acknowledged-1-j)&QW_UPDATE_MASK].receivedtime = -2; + break; + + case qw_svc_modellist: + QW_CL_ParseModelList(); + break; + + case qw_svc_soundlist: + QW_CL_ParseSoundList(); + break; + + case qw_svc_packetentities: + EntityFrameQW_CL_ReadFrame(false); + // first update is the final signon stage + if (cls.signon == SIGNONS - 1) + cls.signon = SIGNONS; + break; + + case qw_svc_deltapacketentities: + EntityFrameQW_CL_ReadFrame(true); + // first update is the final signon stage + if (cls.signon == SIGNONS - 1) + cls.signon = SIGNONS; + break; + + case qw_svc_maxspeed: + cl.qw_movevars_maxspeed = MSG_ReadFloat(); + break; + + case qw_svc_entgravity: + cl.qw_movevars_entgravity = MSG_ReadFloat(); + break; + + case qw_svc_setpause: + cl.paused = MSG_ReadByte (); + if (cl.paused) + CDAudio_Pause (); + else + CDAudio_Resume (); + S_PauseGameSounds (cl.paused); break; } - strlcpy (cl_lightstyle[i].map, MSG_ReadString(), sizeof (cl_lightstyle[i].map)); - cl_lightstyle[i].map[MAX_STYLESTRING - 1] = 0; - cl_lightstyle[i].length = (int)strlen(cl_lightstyle[i].map); - break; + } + QW_CL_UpdateItemsAndWeapon(); + } + else + { + while (1) + { + if (msg_badread) + Host_Error ("CL_ParseServerMessage: Bad server message"); - case svc_sound: - CL_ParseStartSoundPacket(false); - break; + cmd = MSG_ReadByte (); - case svc_precache: - if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3) + if (cmd == -1) { - // was svc_sound2 in protocols 1, 2, 3, removed in 4, 5, changed to svc_precache in 6 - CL_ParseStartSoundPacket(true); + SHOWNET("END OF MESSAGE"); + break; // end of message } - else + + cmdindex = cmdcount & 31; + cmdcount++; + cmdlog[cmdindex] = cmd; + + // if the high bit of the command byte is set, it is a fast update + if (cmd & 128) + { + // 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) + temp = "entity"; + cmdlogname[cmdindex] = temp; + SHOWNET("fast update"); + if (cls.signon == SIGNONS - 1) + { + // first update is the final signon stage + cls.signon = SIGNONS; + CL_SignonReply (); + } + EntityFrameQuake_ReadEntity (cmd&127); + continue; + } + + SHOWNET(svc_strings[cmd]); + cmdlogname[cmdindex] = svc_strings[cmd]; + if (!cmdlogname[cmdindex]) { - int i = (unsigned short)MSG_ReadShort(); - char *s = MSG_ReadString(); - if (i < 32768) + // 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) + temp = ""; + cmdlogname[cmdindex] = temp; + } + + // other commands + switch (cmd) + { + default: { - if (i >= 1 && i < MAX_MODELS) + char description[32*64], temp[64]; + int count; + strcpy (description, "packet dump: "); + i = cmdcount - 32; + if (i < 0) + i = 0; + count = cmdcount - i; + i &= 31; + while(count > 0) { - model_t *model = Mod_ForName(s, false, false, i == 1); - if (!model) - Con_Printf("svc_precache: Mod_ForName(\"%s\") failed\n", s); - cl.model_precache[i] = model; + dpsnprintf (temp, sizeof (temp), "%3i:%s ", cmdlog[i], cmdlogname[i]); + strlcat (description, temp, sizeof (description)); + count--; + i++; + i &= 31; } - else - Con_Printf("svc_precache: index %i outside range %i...%i\n", i, 1, MAX_MODELS); + description[strlen(description)-1] = '\n'; // replace the last space with a newline + Con_Print(description); + Host_Error ("CL_ParseServerMessage: Illegible server message"); + } + break; + + case svc_nop: + if (cls.signon < SIGNONS) + Con_Print("<-- server to client keepalive\n"); + break; + + case svc_time: + cl.mtime[1] = cl.mtime[0]; + cl.mtime[0] = MSG_ReadFloat (); + cl.movement_needupdate = true; + break; + + case svc_clientdata: + CL_ParseClientdata(); + break; + + case svc_version: + i = MSG_ReadLong (); + protocol = Protocol_EnumForNumber(i); + if (protocol == PROTOCOL_UNKNOWN) + Host_Error("CL_ParseServerMessage: Server is unrecognized protocol number (%i)", i); + // hack for unmarked Nehahra movie demos which had a custom protocol + if (protocol == PROTOCOL_QUAKEDP && cls.demoplayback && demo_nehahra.integer) + protocol = PROTOCOL_NEHAHRAMOVIE; + cls.protocol = protocol; + break; + + case svc_disconnect: + Con_Printf ("Server disconnected\n"); + if (cls.demonum != -1) + CL_NextDemo (); + else + CL_Disconnect (); + break; + + case svc_print: + CSQC_AddPrintText(MSG_ReadString()); //[515]: csqc + break; + + case svc_centerprint: + CL_VM_Parse_CenterPrint(MSG_ReadString ()); //[515]: csqc + break; + + case svc_stufftext: + CL_VM_Parse_StuffCmd(MSG_ReadString ()); //[515]: csqc + break; + + case svc_damage: + V_ParseDamage (); + break; + + case svc_serverinfo: + CL_ParseServerInfo (); + CL_VM_Init(); //[515]: init csqc + break; + + case svc_setangle: + for (i=0 ; i<3 ; i++) + cl.viewangles[i] = MSG_ReadAngle (cls.protocol); + break; + + case svc_setview: + cl.viewentity = (unsigned short)MSG_ReadShort (); + if (cl.viewentity >= MAX_EDICTS) + Host_Error("svc_setview >= MAX_EDICTS"); + if (cl.viewentity >= cl_max_entities) + CL_ExpandEntities(cl.viewentity); + // LordHavoc: assume first setview recieved is the real player entity + if (!cl.playerentity) + cl.playerentity = cl.viewentity; + break; + + case svc_lightstyle: + i = MSG_ReadByte (); + if (i >= cl_max_lightstyle) + { + Con_Printf ("svc_lightstyle >= MAX_LIGHTSTYLES"); + break; + } + strlcpy (cl_lightstyle[i].map, MSG_ReadString(), sizeof (cl_lightstyle[i].map)); + cl_lightstyle[i].map[MAX_STYLESTRING - 1] = 0; + cl_lightstyle[i].length = (int)strlen(cl_lightstyle[i].map); + break; + + case svc_sound: + CL_ParseStartSoundPacket(false); + break; + + case svc_precache: + if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3) + { + // was svc_sound2 in protocols 1, 2, 3, removed in 4, 5, changed to svc_precache in 6 + CL_ParseStartSoundPacket(true); } else { - i -= 32768; - if (i >= 1 && i < MAX_SOUNDS) + int i = (unsigned short)MSG_ReadShort(); + char *s = MSG_ReadString(); + if (i < 32768) { - sfx_t *sfx = S_PrecacheSound (s, true, false); - if (!sfx && snd_initialized.integer) - Con_Printf("svc_precache: S_PrecacheSound(\"%s\") failed\n", s); - cl.sound_precache[i] = sfx; + if (i >= 1 && i < MAX_MODELS) + { + model_t *model = Mod_ForName(s, false, false, i == 1); + if (!model) + Con_Printf("svc_precache: Mod_ForName(\"%s\") failed\n", s); + cl.model_precache[i] = model; + } + else + Con_Printf("svc_precache: index %i outside range %i...%i\n", i, 1, MAX_MODELS); } else - Con_Printf("svc_precache: index %i outside range %i...%i\n", i, 1, MAX_SOUNDS); + { + i -= 32768; + if (i >= 1 && i < MAX_SOUNDS) + { + sfx_t *sfx = S_PrecacheSound (s, true, false); + if (!sfx && snd_initialized.integer) + Con_Printf("svc_precache: S_PrecacheSound(\"%s\") failed\n", s); + cl.sound_precache[i] = sfx; + } + else + Con_Printf("svc_precache: index %i outside range %i...%i\n", i, 1, MAX_SOUNDS); + } } - } - break; + break; - case svc_stopsound: - i = (unsigned short) MSG_ReadShort(); - S_StopSound(i>>3, i&7); - break; + case svc_stopsound: + i = (unsigned short) MSG_ReadShort(); + S_StopSound(i>>3, i&7); + break; - case svc_updatename: - i = MSG_ReadByte (); - if (i >= cl.maxclients) - Host_Error ("CL_ParseServerMessage: svc_updatename >= cl.maxclients"); - strlcpy (cl.scores[i].name, MSG_ReadString (), sizeof (cl.scores[i].name)); - break; + case svc_updatename: + i = MSG_ReadByte (); + if (i >= cl.maxclients) + Host_Error ("CL_ParseServerMessage: svc_updatename >= cl.maxclients"); + strlcpy (cl.scores[i].name, MSG_ReadString (), sizeof (cl.scores[i].name)); + break; - case svc_updatefrags: - i = MSG_ReadByte (); - if (i >= cl.maxclients) - Host_Error ("CL_ParseServerMessage: svc_updatefrags >= cl.maxclients"); - cl.scores[i].frags = (signed short) MSG_ReadShort (); - break; + case svc_updatefrags: + i = MSG_ReadByte (); + if (i >= cl.maxclients) + Host_Error ("CL_ParseServerMessage: svc_updatefrags >= cl.maxclients"); + cl.scores[i].frags = (signed short) MSG_ReadShort (); + break; - case svc_updatecolors: - i = MSG_ReadByte (); - if (i >= cl.maxclients) - Host_Error ("CL_ParseServerMessage: svc_updatecolors >= cl.maxclients"); - cl.scores[i].colors = MSG_ReadByte (); - break; + case svc_updatecolors: + i = MSG_ReadByte (); + if (i >= cl.maxclients) + Host_Error ("CL_ParseServerMessage: svc_updatecolors >= cl.maxclients"); + cl.scores[i].colors = MSG_ReadByte (); + break; - case svc_particle: - CL_ParseParticleEffect (); - break; + case svc_particle: + CL_ParseParticleEffect (); + break; - case svc_effect: - CL_ParseEffect (); - break; + case svc_effect: + CL_ParseEffect (); + break; - case svc_effect2: - CL_ParseEffect2 (); - break; + case svc_effect2: + CL_ParseEffect2 (); + break; - case svc_spawnbaseline: - i = (unsigned short) MSG_ReadShort (); - if (i < 0 || i >= MAX_EDICTS) - Host_Error ("CL_ParseServerMessage: svc_spawnbaseline: invalid entity number %i", i); - if (i >= cl_max_entities) - CL_ExpandEntities(i); - CL_ParseBaseline (cl_entities + i, false); - break; - case svc_spawnbaseline2: - i = (unsigned short) MSG_ReadShort (); - if (i < 0 || i >= MAX_EDICTS) - Host_Error ("CL_ParseServerMessage: svc_spawnbaseline2: invalid entity number %i", i); - if (i >= cl_max_entities) - CL_ExpandEntities(i); - CL_ParseBaseline (cl_entities + i, true); - break; - case svc_spawnstatic: - CL_ParseStatic (false); - break; - case svc_spawnstatic2: - CL_ParseStatic (true); - break; - case svc_temp_entity: - if(!CL_VM_Parse_TempEntity()) - CL_ParseTempEntity (); - break; + case svc_spawnbaseline: + i = (unsigned short) MSG_ReadShort (); + if (i < 0 || i >= MAX_EDICTS) + Host_Error ("CL_ParseServerMessage: svc_spawnbaseline: invalid entity number %i", i); + if (i >= cl_max_entities) + CL_ExpandEntities(i); + CL_ParseBaseline (cl_entities + i, false); + break; + case svc_spawnbaseline2: + i = (unsigned short) MSG_ReadShort (); + if (i < 0 || i >= MAX_EDICTS) + Host_Error ("CL_ParseServerMessage: svc_spawnbaseline2: invalid entity number %i", i); + if (i >= cl_max_entities) + CL_ExpandEntities(i); + CL_ParseBaseline (cl_entities + i, true); + break; + case svc_spawnstatic: + CL_ParseStatic (false); + break; + case svc_spawnstatic2: + CL_ParseStatic (true); + break; + case svc_temp_entity: + if(!CL_VM_Parse_TempEntity()) + CL_ParseTempEntity (); + break; - case svc_setpause: - cl.paused = MSG_ReadByte (); - if (cl.paused) - CDAudio_Pause (); - else - CDAudio_Resume (); - S_PauseGameSounds (cl.paused); - break; + case svc_setpause: + cl.paused = MSG_ReadByte (); + if (cl.paused) + CDAudio_Pause (); + else + CDAudio_Resume (); + S_PauseGameSounds (cl.paused); + break; - case svc_signonnum: - i = MSG_ReadByte (); - // LordHavoc: it's rude to kick off the client if they missed the - // reconnect somehow, so allow signon 1 even if at signon 1 - if (i <= cls.signon && i != 1) - Host_Error ("Received signon %i when at %i", i, cls.signon); - cls.signon = i; - CL_SignonReply (); - break; + case svc_signonnum: + i = MSG_ReadByte (); + // LordHavoc: it's rude to kick off the client if they missed the + // reconnect somehow, so allow signon 1 even if at signon 1 + if (i <= cls.signon && i != 1) + Host_Error ("Received signon %i when at %i", i, cls.signon); + cls.signon = i; + CL_SignonReply (); + break; - case svc_killedmonster: - cl.stats[STAT_MONSTERS]++; - break; + case svc_killedmonster: + cl.stats[STAT_MONSTERS]++; + break; - case svc_foundsecret: - cl.stats[STAT_SECRETS]++; - break; + case svc_foundsecret: + cl.stats[STAT_SECRETS]++; + break; - case svc_updatestat: - i = MSG_ReadByte (); - if (i < 0 || i >= MAX_CL_STATS) - Host_Error ("svc_updatestat: %i is invalid", i); - cl.stats[i] = MSG_ReadLong (); - break; + case svc_updatestat: + i = MSG_ReadByte (); + if (i < 0 || i >= MAX_CL_STATS) + Host_Error ("svc_updatestat: %i is invalid", i); + cl.stats[i] = MSG_ReadLong (); + break; - case svc_updatestatubyte: - i = MSG_ReadByte (); - if (i < 0 || i >= MAX_CL_STATS) - Host_Error ("svc_updatestat: %i is invalid", i); - cl.stats[i] = MSG_ReadByte (); - break; + case svc_updatestatubyte: + i = MSG_ReadByte (); + if (i < 0 || i >= MAX_CL_STATS) + Host_Error ("svc_updatestat: %i is invalid", i); + cl.stats[i] = MSG_ReadByte (); + break; - case svc_spawnstaticsound: - CL_ParseStaticSound (false); - break; + case svc_spawnstaticsound: + CL_ParseStaticSound (false); + break; - case svc_spawnstaticsound2: - CL_ParseStaticSound (true); - break; + case svc_spawnstaticsound2: + CL_ParseStaticSound (true); + break; - case svc_cdtrack: - cl.cdtrack = MSG_ReadByte (); - cl.looptrack = MSG_ReadByte (); - if ( (cls.demoplayback || cls.demorecording) && (cls.forcetrack != -1) ) - CDAudio_Play ((unsigned char)cls.forcetrack, true); - else - CDAudio_Play ((unsigned char)cl.cdtrack, true); - break; + case svc_cdtrack: + cl.cdtrack = MSG_ReadByte (); + cl.looptrack = MSG_ReadByte (); + if ( (cls.demoplayback || cls.demorecording) && (cls.forcetrack != -1) ) + CDAudio_Play ((unsigned char)cls.forcetrack, true); + else + CDAudio_Play ((unsigned char)cl.cdtrack, true); + break; - case svc_intermission: - cl.intermission = 1; - cl.completed_time = cl.time; - break; + case svc_intermission: + cl.intermission = 1; + cl.completed_time = cl.time; + break; - case svc_finale: - cl.intermission = 2; - cl.completed_time = cl.time; - SCR_CenterPrint(MSG_ReadString ()); - break; + case svc_finale: + cl.intermission = 2; + cl.completed_time = cl.time; + SCR_CenterPrint(MSG_ReadString ()); + break; - case svc_cutscene: - cl.intermission = 3; - cl.completed_time = cl.time; - SCR_CenterPrint(MSG_ReadString ()); - break; + case svc_cutscene: + cl.intermission = 3; + cl.completed_time = cl.time; + SCR_CenterPrint(MSG_ReadString ()); + break; - case svc_sellscreen: - Cmd_ExecuteString ("help", src_command); - break; - case svc_hidelmp: - if (gamemode == GAME_TENEBRAE) - { - // repeating particle effect - MSG_ReadCoord(cls.protocol); - MSG_ReadCoord(cls.protocol); - MSG_ReadCoord(cls.protocol); - MSG_ReadCoord(cls.protocol); - MSG_ReadCoord(cls.protocol); - MSG_ReadCoord(cls.protocol); - MSG_ReadByte(); - MSG_ReadLong(); - MSG_ReadLong(); - MSG_ReadString(); - } - else - SHOWLMP_decodehide(); - break; - case svc_showlmp: - if (gamemode == GAME_TENEBRAE) - { - // particle effect - MSG_ReadCoord(cls.protocol); - MSG_ReadCoord(cls.protocol); - MSG_ReadCoord(cls.protocol); - MSG_ReadByte(); - MSG_ReadString(); - } - else - SHOWLMP_decodeshow(); - break; - case svc_skybox: - R_SetSkyBox(MSG_ReadString()); - break; - case svc_cgame: - { - int length; - length = (int) ((unsigned short) MSG_ReadShort()); - for (i = 0;i < length;i++) - cgamenetbuffer[i] = MSG_ReadByte(); - if (!msg_badread) - CL_CGVM_ParseNetwork(cgamenetbuffer, length); - } - break; - case svc_entities: - if (cls.signon == SIGNONS - 1) - { - // first update is the final signon stage - cls.signon = SIGNONS; - CL_SignonReply (); + case svc_sellscreen: + Cmd_ExecuteString ("help", src_command); + break; + case svc_hidelmp: + if (gamemode == GAME_TENEBRAE) + { + // repeating particle effect + MSG_ReadCoord(cls.protocol); + MSG_ReadCoord(cls.protocol); + MSG_ReadCoord(cls.protocol); + MSG_ReadCoord(cls.protocol); + MSG_ReadCoord(cls.protocol); + MSG_ReadCoord(cls.protocol); + MSG_ReadByte(); + MSG_ReadLong(); + MSG_ReadLong(); + MSG_ReadString(); + } + else + SHOWLMP_decodehide(); + break; + case svc_showlmp: + if (gamemode == GAME_TENEBRAE) + { + // particle effect + MSG_ReadCoord(cls.protocol); + MSG_ReadCoord(cls.protocol); + MSG_ReadCoord(cls.protocol); + MSG_ReadByte(); + MSG_ReadString(); + } + else + SHOWLMP_decodeshow(); + break; + case svc_skybox: + R_SetSkyBox(MSG_ReadString()); + break; + case svc_cgame: + { + int length; + length = (int) ((unsigned short) MSG_ReadShort()); + for (i = 0;i < length;i++) + cgamenetbuffer[i] = MSG_ReadByte(); + if (!msg_badread) + CL_CGVM_ParseNetwork(cgamenetbuffer, length); + } + break; + case svc_entities: + if (cls.signon == SIGNONS - 1) + { + // first update is the final signon stage + cls.signon = SIGNONS; + CL_SignonReply (); + } + if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3) + EntityFrame_CL_ReadFrame(); + else if (cls.protocol == PROTOCOL_DARKPLACES4) + EntityFrame4_CL_ReadFrame(); + else + EntityFrame5_CL_ReadFrame(); + break; + case svc_csqcentities: + CSQC_ReadEntities(); + break; } - if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3) - EntityFrame_CL_ReadFrame(); - else if (cls.protocol == PROTOCOL_DARKPLACES4) - EntityFrame4_CL_ReadFrame(); - else - EntityFrame5_CL_ReadFrame(); - break; - case svc_csqcentities: - CSQC_ReadEntities(); - break; } } @@ -1858,6 +2906,17 @@ void CL_Parse_DumpPacket(void) parsingerror = false; } +void CL_Parse_ErrorCleanUp(void) +{ + if (cls.qw_downloadmemory) + { + Mem_Free(cls.qw_downloadmemory); + cls.qw_downloadmemory = NULL; + } + cls.qw_downloadpercent = 0; + QW_CL_StopUpload(); +} + void CL_Parse_Init(void) { // LordHavoc: added demo_nehahra cvar @@ -1865,6 +2924,9 @@ void CL_Parse_Init(void) if (gamemode == GAME_NEHAHRA) Cvar_SetValue("demo_nehahra", 1); Cvar_RegisterVariable(&developer_networkentities); + + Cmd_AddCommand("nextul", QW_CL_NextUpload, "sends next fragment of current upload buffer (screenshot for example)"); + Cmd_AddCommand("stopul", QW_CL_StopUpload, "aborts current upload (screenshot for example)"); } void CL_Parse_Shutdown(void) diff --git a/cl_particles.c b/cl_particles.c index e00b070f..a7ea2146 100644 --- a/cl_particles.c +++ b/cl_particles.c @@ -815,7 +815,7 @@ void CL_RunParticleEffect (vec3_t org, vec3_t dir, int color, int count) CL_SparkShower =============== */ -void CL_SparkShower (vec3_t org, vec3_t dir, int count, vec_t gravityscale) +void CL_SparkShower (vec3_t org, vec3_t dir, int count, vec_t gravityscale, vec_t radius) { int k; @@ -828,12 +828,12 @@ void CL_SparkShower (vec3_t org, vec3_t dir, int count, vec_t gravityscale) while(count--) { k = particlepalette[0x68 + (rand() & 7)]; - particle(particletype + pt_spark, k, k, tex_particle, 0.4f, (1.0f / cl_particles_quality.value) * lhrandom(64, 255), (1.0f / cl_particles_quality.value) * 512, gravityscale, 0, org[0], org[1], org[2], dir[0], dir[1], dir[2] + sv_gravity.value * 0.1, 0, 0, 64); + particle(particletype + pt_spark, k, k, tex_particle, 0.4f, (1.0f / cl_particles_quality.value) * lhrandom(64, 255), (1.0f / cl_particles_quality.value) * 512, gravityscale, 0, org[0], org[1], org[2], dir[0], dir[1], dir[2] + sv_gravity.value * 0.1, 0, radius, 64); } } } -void CL_Smoke (vec3_t org, vec3_t dir, int count) +void CL_Smoke (vec3_t org, vec3_t dir, int count, vec_t radius) { vec3_t org2; int k; @@ -851,7 +851,7 @@ void CL_Smoke (vec3_t org, vec3_t dir, int count) org2[1] = org[1] + 0.125f * lhrandom(-count, count); org2[2] = org[2] + 0.125f * lhrandom(-count, count); trace = CL_TraceBox(org, vec3_origin, vec3_origin, org2, true, NULL, SUPERCONTENTS_SOLID, false); - particle(particletype + pt_smoke, 0x101010, 0x202020, tex_smoke[rand()&7], 3, (1.0f / cl_particles_quality.value) * 255, (1.0f / cl_particles_quality.value) * 1024, 0, 0, trace.endpos[0], trace.endpos[1], trace.endpos[2], 0, 0, 0, 0, 0, 8); + particle(particletype + pt_smoke, 0x101010, 0x202020, tex_smoke[rand()&7], 3, (1.0f / cl_particles_quality.value) * 255, (1.0f / cl_particles_quality.value) * 1024, 0, 0, trace.endpos[0], trace.endpos[1], trace.endpos[2], 0, 0, 0, 0, radius, 8); } } } diff --git a/client.h b/client.h index 58d0dbaf..053365af 100644 --- a/client.h +++ b/client.h @@ -370,13 +370,14 @@ typedef struct scoreboard_s int frags; int colors; // two 4 bit fields // QW fields: - int userid; - char userinfo[MAX_USERINFO_STRING]; - float entertime; - int ping; - int packetloss; - int spectator; - // TODO: QW skin support + int qw_userid; + char qw_userinfo[MAX_USERINFO_STRING]; + float qw_entertime; + int qw_ping; + int qw_packetloss; + int qw_spectator; + char qw_skin[MAX_QPATH]; + cachepic_t *qw_skin_cachepic; // skins are loaded as cachepics } scoreboard_t; typedef struct cshift_s @@ -404,7 +405,7 @@ typedef struct cshift_s #define MAX_DEMOS 8 #define MAX_DEMONAME 16 -typedef enum +typedef enum cactive_e { ca_dedicated, // a dedicated server with no ability to start a client ca_disconnected, // full screen console with no connection @@ -412,6 +413,16 @@ typedef enum } cactive_t; +typedef enum qw_downloadtype_e +{ + dl_none, + dl_single, + dl_skin, + dl_model, + dl_sound +} +qw_downloadtype_t; + // // the client_static_t structure is persistent through an arbitrary number // of server connections @@ -420,9 +431,6 @@ typedef struct client_static_s { cactive_t state; - // value of "qport" cvar at time of connection - int qport; - // demo loop control // -1 = don't play demos int demonum; @@ -469,6 +477,23 @@ typedef struct client_static_s // quakeworld stuff below + // value of "qport" cvar at time of connection + int qw_qport; + + // current file download buffer (only saved when file is completed) + char qw_downloadname[MAX_QPATH]; + unsigned char *qw_downloadmemory; + int qw_downloadmemorycursize; + int qw_downloadmemorymaxsize; + int qw_downloadnumber; + int qw_downloadpercent; + qw_downloadtype_t qw_downloadtype; + + // current file upload buffer (for uploading screenshots to server) + unsigned char *qw_uploaddata; + int qw_uploadsize; + int qw_uploadpos; + // user infostring // this normally contains the following keys in quakeworld: // password spectator name team skin topcolor bottomcolor rate noaim msg *ver *ip @@ -662,15 +687,49 @@ typedef struct client_state_s // [cl.maxclients] scoreboard_t *scores; - // local copy of the server infostring - char serverinfo[MAX_SERVERINFO_STRING]; - // entity database stuff // latest received entity frame numbers #define LATESTFRAMENUMS 3 int latestframenums[LATESTFRAMENUMS]; entityframe_database_t *entitydatabase; entityframe4_database_t *entitydatabase4; + entityframeqw_database_t *entitydatabaseqw; + + // quakeworld stuff + + // local copy of the server infostring + char qw_serverinfo[MAX_SERVERINFO_STRING]; + + // used during connect + int qw_servercount; + + // indicates whether the player is spectating + qboolean qw_spectator; + + // movement parameters for client prediction + float qw_movevars_gravity; + float qw_movevars_stopspeed; + float qw_movevars_maxspeed; // can change during play + float qw_movevars_spectatormaxspeed; + float qw_movevars_accelerate; + float qw_movevars_airaccelerate; + float qw_movevars_wateraccelerate; + float qw_movevars_friction; + float qw_movevars_waterfriction; + float qw_movevars_entgravity; // can change during play + + // models used by qw protocol + int qw_modelindex_spike; + int qw_modelindex_player; + int qw_modelindex_flag; + int qw_modelindex_s_explod; + + vec3_t qw_intermission_origin; + vec3_t qw_intermission_angles; + + // 255 is the most nails the QW protocol could send + int qw_num_nails; + vec_t qw_nails[255][6]; } client_state_t; @@ -768,6 +827,7 @@ extern int cl_num_static_entities; extern int cl_num_temp_entities; extern int cl_num_brushmodel_entities; +extern char qw_emodel_name[], qw_pmodel_name[], qw_prespawn_name[], qw_modellist_name[], qw_soundlist_name[]; extern client_state_t cl; @@ -855,6 +915,8 @@ void CL_Parse_Init(void); void CL_Parse_Shutdown(void); void CL_ParseServerMessage(void); void CL_Parse_DumpPacket(void); +void CL_Parse_ErrorCleanUp(void); +void QW_CL_StartUpload(unsigned char *data, int size); extern cvar_t qport; // @@ -901,8 +963,8 @@ void CL_Particles_Shutdown(void); void CL_ParseParticleEffect (void); void CL_RunParticleEffect (vec3_t org, vec3_t dir, int color, int count); void CL_RocketTrail (vec3_t start, vec3_t end, int type, int color, entity_t *ent); -void CL_SparkShower (vec3_t org, vec3_t dir, int count, vec_t gravityscale); -void CL_Smoke (vec3_t org, vec3_t dir, int count); +void CL_SparkShower (vec3_t org, vec3_t dir, int count, vec_t gravityscale, vec_t radius); +void CL_Smoke (vec3_t org, vec3_t dir, int count, vec_t radius); void CL_BulletMark (vec3_t org); void CL_PlasmaBurn (vec3_t org); void CL_BloodPuff (vec3_t org, vec3_t vel, int count); diff --git a/clvm_cmds.c b/clvm_cmds.c index 435e75a7..67bb45ef 100644 --- a/clvm_cmds.c +++ b/clvm_cmds.c @@ -1222,15 +1222,15 @@ void VM_CL_pointparticles (void) case TE_SPIKEQUAD: case TE_GUNSHOT: case TE_GUNSHOTQUAD: - CL_SparkShower(f, v, 15, 1); - CL_Smoke(f, v, 15); + CL_SparkShower(f, v, 15, 1, 0); + CL_Smoke(f, v, 15, 0); if (cl_particles_bulletimpacts.integer) CL_BulletMark(f); break; case TE_SUPERSPIKE: case TE_SUPERSPIKEQUAD: - CL_SparkShower(f, v, 30, 1); - CL_Smoke(f, v, 30); + CL_SparkShower(f, v, 30, 1, 0); + CL_Smoke(f, v, 30, 0); if (cl_particles_bulletimpacts.integer) CL_BulletMark(f); break; @@ -1263,7 +1263,7 @@ void VM_CL_pointparticles (void) CL_BloodPuff(f, v, n); break; case TE_SPARK: - CL_SparkShower(f, v, n, 1); + CL_SparkShower(f, v, n, 1, 0); break; case TE_FLAMEJET: CL_Flames(f, v, n); @@ -1589,7 +1589,7 @@ void VM_CL_te_spark (void) return; pos = PRVM_G_VECTOR(OFS_PARM0); CL_FindNonSolidLocation(pos, pos2, 4); - CL_SparkShower(pos2, PRVM_G_VECTOR(OFS_PARM1), PRVM_G_FLOAT(OFS_PARM2), 1); + CL_SparkShower(pos2, PRVM_G_VECTOR(OFS_PARM1), PRVM_G_FLOAT(OFS_PARM2), 1, 0); } // #412 void(vector org) te_gunshotquad (DP_QUADEFFECTS1) @@ -1602,8 +1602,8 @@ void VM_CL_te_gunshotquad (void) pos = PRVM_G_VECTOR(OFS_PARM0); CL_FindNonSolidLocation(pos, pos2, 4); - CL_SparkShower(pos2, vec3_origin, 15, 1); - CL_Smoke(pos2, vec3_origin, 15); + CL_SparkShower(pos2, vec3_origin, 15, 1, 0); + CL_Smoke(pos2, vec3_origin, 15, 0); CL_BulletMark(pos2); Matrix4x4_CreateTranslate(&tempmatrix, pos2[0], pos2[1], pos2[2]); CL_AllocDlight(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); @@ -1622,8 +1622,8 @@ void VM_CL_te_spikequad (void) CL_FindNonSolidLocation(pos, pos2, 4); if (cl_particles_bulletimpacts.integer) { - CL_SparkShower(pos2, vec3_origin, 15, 1); - CL_Smoke(pos2, vec3_origin, 15); + CL_SparkShower(pos2, vec3_origin, 15, 1, 0); + CL_Smoke(pos2, vec3_origin, 15, 0); CL_BulletMark(pos2); } Matrix4x4_CreateTranslate(&tempmatrix, pos2[0], pos2[1], pos2[2]); @@ -1651,8 +1651,8 @@ void VM_CL_te_superspikequad (void) CL_FindNonSolidLocation(pos, pos2, 4); if (cl_particles_bulletimpacts.integer) { - CL_SparkShower(pos2, vec3_origin, 30, 1); - CL_Smoke(pos2, vec3_origin, 30); + CL_SparkShower(pos2, vec3_origin, 30, 1, 0); + CL_Smoke(pos2, vec3_origin, 30, 0); CL_BulletMark(pos2); } Matrix4x4_CreateTranslate(&tempmatrix, pos2[0], pos2[1], pos2[2]); @@ -1721,8 +1721,8 @@ void VM_CL_te_gunshot (void) pos = PRVM_G_VECTOR(OFS_PARM0); CL_FindNonSolidLocation(pos, pos2, 4); - CL_SparkShower(pos2, vec3_origin, 15, 1); - CL_Smoke(pos2, vec3_origin, 15); + CL_SparkShower(pos2, vec3_origin, 15, 1, 0); + CL_Smoke(pos2, vec3_origin, 15, 0); CL_BulletMark(pos2); } @@ -1738,8 +1738,8 @@ void VM_CL_te_spike (void) CL_FindNonSolidLocation(pos, pos2, 4); if (cl_particles_bulletimpacts.integer) { - CL_SparkShower(pos2, vec3_origin, 15, 1); - CL_Smoke(pos2, vec3_origin, 15); + CL_SparkShower(pos2, vec3_origin, 15, 1, 0); + CL_Smoke(pos2, vec3_origin, 15, 0); CL_BulletMark(pos2); } if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos2, 1, 1); @@ -1764,8 +1764,8 @@ void VM_CL_te_superspike (void) CL_FindNonSolidLocation(pos, pos2, 4); if (cl_particles_bulletimpacts.integer) { - CL_SparkShower(pos2, vec3_origin, 30, 1); - CL_Smoke(pos2, vec3_origin, 30); + CL_SparkShower(pos2, vec3_origin, 30, 1, 0); + CL_Smoke(pos2, vec3_origin, 30, 0); CL_BulletMark(pos2); } if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos2, 1, 1); diff --git a/host.c b/host.c index 34d1417a..49dcad4c 100644 --- a/host.c +++ b/host.c @@ -144,6 +144,8 @@ void Host_Error (const char *error, ...) CL_Parse_DumpPacket(); + CL_Parse_ErrorCleanUp(); + //PR_Crash(); // print out where the crash happened, if it was caused by QC (and do a cleanup) diff --git a/host_cmd.c b/host_cmd.c index 865ecb02..f10203a1 100644 --- a/host_cmd.c +++ b/host_cmd.c @@ -1957,17 +1957,17 @@ static void MaxPlayers_f(void) // QuakeWorld commands -char emodel_name[] = +char qw_emodel_name[] = { 'e' ^ 0xff, 'm' ^ 0xff, 'o' ^ 0xff, 'd' ^ 0xff, 'e' ^ 0xff, 'l' ^ 0xff, 0 }; -char pmodel_name[] = +char qw_pmodel_name[] = { 'p' ^ 0xff, 'm' ^ 0xff, 'o' ^ 0xff, 'd' ^ 0xff, 'e' ^ 0xff, 'l' ^ 0xff, 0 }; -char prespawn_name[] = +char qw_prespawn_name[] = { 'p'^0xff, 'r'^0xff, 'e'^0xff, 's'^0xff, 'p'^0xff, 'a'^0xff, 'w'^0xff, 'n'^0xff, ' '^0xff, '%'^0xff, 'i'^0xff, ' '^0xff, '0'^0xff, ' '^0xff, '%'^0xff, 'i'^0xff, 0 }; -char modellist_name[] = +char qw_modellist_name[] = { 'm'^0xff, 'o'^0xff, 'd'^0xff, 'e'^0xff, 'l'^0xff, 'l'^0xff, 'i'^0xff, 's'^0xff, 't'^0xff, ' '^0xff, '%'^0xff, 'i'^0xff, ' '^0xff, '%'^0xff, 'i'^0xff, 0 }; -char soundlist_name[] = +char qw_soundlist_name[] = { 's'^0xff, 'o'^0xff, 'u'^0xff, 'n'^0xff, 'd'^0xff, 'l'^0xff, 'i'^0xff, 's'^0xff, 't'^0xff, ' '^0xff, '%'^0xff, 'i'^0xff, ' '^0xff, '%'^0xff, 'i'^0xff, 0 }; @@ -2045,9 +2045,9 @@ void Host_User_f (void) // credit: taken from QuakeWorld { if (!cl.scores[i].name[0]) continue; - if (cl.scores[i].userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1))) + if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1))) { - InfoString_Print(cl.scores[i].userinfo); + InfoString_Print(cl.scores[i].qw_userinfo); return; } } @@ -2073,7 +2073,7 @@ void Host_Users_f (void) // credit: taken from QuakeWorld { if (cl.scores[i].name[0]) { - Con_Printf ("%6i %4i %s\n", cl.scores[i].userid, cl.scores[i].frags, cl.scores[i].name); + Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name); c++; } } @@ -2097,7 +2097,7 @@ void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld return; } - strlcpy (cl.serverinfo, Cmd_Argv(1), sizeof(cl.serverinfo)); + strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo)); } /* @@ -2146,7 +2146,7 @@ void Host_FullInfo_f (void) // credit: taken from QuakeWorld if (*s) s++; - if (!strcasecmp(key, pmodel_name) || !strcasecmp(key, emodel_name)) + if (!strcasecmp(key, qw_pmodel_name) || !strcasecmp(key, qw_emodel_name)) continue; if (key[0] == '*') @@ -2178,7 +2178,7 @@ void Host_SetInfo_f (void) // credit: taken from QuakeWorld Con_Printf ("usage: setinfo [ ]\n"); return; } - if (!strcasecmp(Cmd_Argv(1), pmodel_name) || !strcasecmp(Cmd_Argv(1), emodel_name)) + if (!strcasecmp(Cmd_Argv(1), qw_pmodel_name) || !strcasecmp(Cmd_Argv(1), qw_emodel_name)) return; if (Cmd_Argv(1)[0] == '*') { diff --git a/netconn.c b/netconn.c index 0ce9c745..1904813e 100755 --- a/netconn.c +++ b/netconn.c @@ -466,7 +466,7 @@ int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolvers // client sends qport in every packet if (conn == cls.netcon) { - *((short *)(sendbuffer + 8)) = LittleShort(cls.qport); + *((short *)(sendbuffer + 8)) = LittleShort(cls.qw_qport); packetLen += 2; } if (packetLen + (sendreliable ? conn->sendMessageLength : 0) + data->cursize > (int)sizeof(sendbuffer)) @@ -1075,8 +1075,8 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true); Con_Printf("\"%s\" received, sending QuakeWorld connect request back to %s\n", string, addressstring2); M_Update_Return_Reason("Got QuakeWorld challenge response"); - cls.qport = qport.integer; - NetConn_WriteString(mysocket, va("\377\377\377\377connect 28 %i %i \"%s\"\n", cls.qport, atoi(string + 1), cls.userinfo), peeraddress); + cls.qw_qport = qport.integer; + NetConn_WriteString(mysocket, va("\377\377\377\377connect 28 %i %i \"%s\"\n", cls.qw_qport, atoi(string + 1), cls.userinfo), peeraddress); } if (length == 6 && !memcmp(string, "accept", 6) && cls.connect_trying) { @@ -1239,7 +1239,7 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true); Con_Printf("challenge %s received, sending connect request back to %s\n", string + 1, addressstring2); M_Update_Return_Reason("Got challenge response"); - cls.qport = qport.integer; + cls.qw_qport = qport.integer; InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ip", addressstring2); InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "name", cl_name.string); InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "topcolor", va("%i", (cl_color.integer >> 4) & 15)); @@ -1247,7 +1247,7 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "rate", va("%i", cl_rate.integer)); InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "msg", "1"); InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ver", engineversion); - NetConn_WriteString(mysocket, va("\377\377\377\377connect %i %i %i \"%s\"\n", 28, cls.qport, atoi(string + 1), cls.userinfo), peeraddress); + NetConn_WriteString(mysocket, va("\377\377\377\377connect %i %i %i \"%s\"\n", 28, cls.qw_qport, atoi(string + 1), cls.userinfo), peeraddress); return true; } if (string[0] == 'n') diff --git a/protocol.c b/protocol.c index ad5fe83e..6b104129 100644 --- a/protocol.c +++ b/protocol.c @@ -2339,3 +2339,345 @@ void EntityFrame5_WriteFrame(sizebuf_t *msg, entityframe5_database_t *d, int num MSG_WriteShort(msg, 0x8000); } + + +void EntityStateQW_ReadPlayerUpdate(void) +{ + int slot = MSG_ReadByte(); + int enumber = slot + 1; + int weaponframe; + int msec; + int playerflags; + int bits; + entity_state_t *s; + // look up the entity + entity_t *ent = cl_entities + enumber; + vec3_t viewangles; + vec3_t velocity; + + // slide the current state into the previous + ent->state_previous = ent->state_current; + + // read the update + s = &ent->state_current; + *s = defaultstate; + s->active = true; + playerflags = MSG_ReadShort(); + MSG_ReadVector(s->origin, cls.protocol); + s->frame = MSG_ReadByte(); + if (playerflags & QW_PF_MSEC) + { + // time difference between last update this player sent to the server, + // and last input we sent to the server (this packet is in response to + // our input, so msec is how long ago the last update of this player + // entity occurred, compared to our input being received) + msec = MSG_ReadByte(); + } + else + msec = 0; + if (playerflags & QW_PF_COMMAND) + { + bits = MSG_ReadByte(); + if (bits & QW_CM_ANGLE1) + viewangles[0] = MSG_ReadAngle16i(); + if (bits & QW_CM_ANGLE2) + viewangles[1] = MSG_ReadAngle16i(); + if (bits & QW_CM_ANGLE3) + viewangles[2] = MSG_ReadAngle16i(); + if (bits & QW_CM_FORWARD) + MSG_ReadShort(); + if (bits & QW_CM_SIDE) + MSG_ReadShort(); + if (bits & QW_CM_UP) + MSG_ReadShort(); + if (bits & QW_CM_BUTTONS) + MSG_ReadByte(); + if (bits & QW_CM_IMPULSE) + MSG_ReadByte(); + } + VectorClear(velocity); + if (playerflags & QW_PF_VELOCITY1) + velocity[0] = MSG_ReadShort(); + if (playerflags & QW_PF_VELOCITY2) + velocity[1] = MSG_ReadShort(); + if (playerflags & QW_PF_VELOCITY3) + velocity[2] = MSG_ReadShort(); + if (playerflags & QW_PF_MODEL) + s->modelindex = MSG_ReadByte(); + else + s->modelindex = cl.qw_modelindex_player; + if (playerflags & QW_PF_SKINNUM) + s->skin = MSG_ReadByte(); + if (playerflags & QW_PF_EFFECTS) + s->effects = MSG_ReadByte(); + if (playerflags & QW_PF_WEAPONFRAME) + weaponframe = MSG_ReadByte(); + + // calculate the entity angles from the viewangles + s->angles[0] = viewangles[0] * -0.0333; + s->angles[1] = viewangles[1]; + s->angles[2] = 0; + s->angles[2] = V_CalcRoll(s->angles, velocity)*4; + + // if this is an update on our player, update interpolation state + if (enumber == cl.playerentity) + { + VectorCopy (cl.mpunchangle[0], cl.mpunchangle[1]); + VectorCopy (cl.mpunchvector[0], cl.mpunchvector[1]); + VectorCopy (cl.mvelocity[0], cl.mvelocity[1]); + cl.mviewzoom[1] = cl.mviewzoom[0]; + + cl.idealpitch = 0; + cl.mpunchangle[0][0] = 0; + cl.mpunchangle[0][1] = 0; + cl.mpunchangle[0][2] = 0; + cl.mpunchvector[0][0] = 0; + cl.mpunchvector[0][1] = 0; + cl.mpunchvector[0][2] = 0; + cl.mvelocity[0][0] = 0; + cl.mvelocity[0][1] = 0; + cl.mvelocity[0][2] = 0; + cl.mviewzoom[0] = 1; + + VectorCopy(velocity, cl.mvelocity[0]); + cl.stats[STAT_WEAPONFRAME] = weaponframe; + } + + // set the cl_entities_active flag + cl_entities_active[enumber] = s->active; + // set the update time + s->time = cl.mtime[0] - msec * 0.001; // qw has no clock + // fix the number (it gets wiped occasionally by copying from defaultstate) + s->number = enumber; + // check if we need to update the lerp stuff + if (s->active) + CL_MoveLerpEntityStates(&cl_entities[enumber]); +} + +static void EntityStateQW_ReadEntityUpdate(entity_state_t *s, int bits) +{ + int qweffects = 0; + s->active = true; + s->number = bits & 511; + bits &= ~511; + if (bits & QW_U_MOREBITS) + bits |= MSG_ReadByte(); + + // store the QW_U_SOLID bit here? + + if (bits & QW_U_MODEL) + s->modelindex = MSG_ReadByte(); + if (bits & QW_U_FRAME) + s->frame = MSG_ReadByte(); + if (bits & QW_U_COLORMAP) + s->colormap = MSG_ReadByte(); + if (bits & QW_U_SKIN) + s->skin = MSG_ReadByte(); + if (bits & QW_U_EFFECTS) + { + s->effects = 0; + qweffects = MSG_ReadByte(); + if (qweffects & QW_EF_BRIGHTFIELD) + s->effects |= EF_BRIGHTFIELD; + if (qweffects & QW_EF_MUZZLEFLASH) + s->effects |= EF_MUZZLEFLASH; + if (qweffects & QW_EF_FLAG1) + { + // mimic FTEQW's interpretation of EF_FLAG1 as EF_NODRAW on non-player entities + if (s->number > cl.maxclients) + s->effects |= EF_NODRAW; + else + s->effects |= EF_FLAG1QW; + } + if (qweffects & QW_EF_FLAG2) + { + // mimic FTEQW's interpretation of EF_FLAG2 as EF_ADDITIVE on non-player entities + if (s->number > cl.maxclients) + s->effects |= EF_ADDITIVE; + else + s->effects |= EF_FLAG2QW; + } + if (qweffects & QW_EF_RED) + { + if (qweffects & QW_EF_BLUE) + s->effects |= EF_RED | EF_BLUE; + else + s->effects |= EF_RED; + } + else if (qweffects & QW_EF_BLUE) + s->effects |= EF_BLUE; + else if (qweffects & QW_EF_BRIGHTLIGHT) + s->effects |= EF_BRIGHTLIGHT; + else if (qweffects & QW_EF_DIMLIGHT) + s->effects |= EF_DIMLIGHT; + } + if (bits & QW_U_ORIGIN1) + s->origin[0] = MSG_ReadCoord13i(); + if (bits & QW_U_ANGLE1) + s->angles[0] = MSG_ReadAngle8i(); + if (bits & QW_U_ORIGIN2) + s->origin[1] = MSG_ReadCoord13i(); + if (bits & QW_U_ANGLE2) + s->angles[1] = MSG_ReadAngle8i(); + if (bits & QW_U_ORIGIN3) + s->origin[2] = MSG_ReadCoord13i(); + if (bits & QW_U_ANGLE3) + s->angles[2] = MSG_ReadAngle8i(); + + if (developer_networkentities.integer >= 2) + { + Con_Printf("ReadFields e%i", s->number); + if (bits & QW_U_MODEL) + Con_Printf(" U_MODEL %i", s->modelindex); + if (bits & QW_U_FRAME) + Con_Printf(" U_FRAME %i", s->frame); + if (bits & QW_U_COLORMAP) + Con_Printf(" U_COLORMAP %i", s->colormap); + if (bits & QW_U_SKIN) + Con_Printf(" U_SKIN %i", s->skin); + if (bits & QW_U_EFFECTS) + Con_Printf(" U_EFFECTS %i", qweffects); + if (bits & QW_U_ORIGIN1) + Con_Printf(" U_ORIGIN1 %i", s->origin[0]); + if (bits & QW_U_ANGLE1) + Con_Printf(" U_ANGLE1 %i", s->angles[0]); + if (bits & QW_U_ORIGIN2) + Con_Printf(" U_ORIGIN2 %i", s->origin[1]); + if (bits & QW_U_ANGLE2) + Con_Printf(" U_ANGLE2 %i", s->angles[1]); + if (bits & QW_U_ORIGIN3) + Con_Printf(" U_ORIGIN3 %i", s->origin[2]); + if (bits & QW_U_ANGLE3) + Con_Printf(" U_ANGLE3 %i", s->angles[2]); + if (bits & QW_U_SOLID) + Con_Printf(" U_SOLID"); + Con_Print("\n"); + } +} + +void EntityFrameQW_CL_ReadFrame(qboolean delta) +{ + qboolean invalid = false; + int i, number, oldsnapindex, newsnapindex, oldindex, newindex, oldnum, newnum; + entity_t *ent; + entityframeqw_database_t *d = cl.entitydatabaseqw; + entityframeqw_snapshot_t *oldsnap, *newsnap; + + newsnapindex = cls.netcon->qw.incoming_sequence & QW_UPDATE_MASK; + newsnap = d->snapshot + newsnapindex; + memset(newsnap, 0, sizeof(*newsnap)); + if (delta) + { + oldsnapindex = MSG_ReadByte() & QW_UPDATE_MASK; + oldsnap = d->snapshot + oldsnapindex; + if (cls.netcon->qw.outgoing_sequence - oldsnapindex >= QW_UPDATE_BACKUP-1) + { + Con_DPrintf("delta update too old\n"); + newsnap->invalid = invalid = true; // too old + delta = false; + } + } + else + { + oldsnapindex = -1; + oldsnap = NULL; + } + + // read the number of this frame to echo back in next input packet + for (i = 0;i < LATESTFRAMENUMS-1;i++) + cl.latestframenums[i] = cl.latestframenums[i+1]; + cl.latestframenums[LATESTFRAMENUMS-1] = cls.netcon->qw.incoming_sequence; + if (invalid) + cl.latestframenums[LATESTFRAMENUMS-1] = 0; + + // read entity numbers until we find a 0x0000 + // (which would be an empty update on world entity, but is actually a terminator) + newsnap->num_entities = 0; + oldindex = 0; + for (;;) + { + int word = (unsigned short)MSG_ReadShort(); + if (msg_badread) + return; // just return, the main parser will print an error + newnum = word == 0 ? 512 : (word & 511); + oldnum = delta ? (oldindex >= oldsnap->num_entities ? 9999 : oldsnap->entities[oldindex].number) : 9999; + + // copy unmodified oldsnap entities + while (newnum > oldnum) // delta only + { + if (developer_networkentities.integer >= 2) + Con_Printf("copy %i\n", oldnum); + // copy one of the old entities + if (newsnap->num_entities >= QW_MAX_PACKET_ENTITIES) + Host_Error("EntityFrameQW_CL_ReadFrame: newsnap->num_entities == MAX_PACKETENTITIES"); + newsnap->entities[newsnap->num_entities] = oldsnap->entities[oldindex++]; + newsnap->num_entities++; + oldnum = oldindex >= oldsnap->num_entities ? 9999 : oldsnap->entities[oldindex].number; + } + + if (word == 0) + break; + + if (developer_networkentities.integer >= 2) + { + if (word & QW_U_REMOVE) + Con_Printf("remove %i\n", newnum); + else if (newnum == oldnum) + Con_Printf("delta %i\n", newnum); + else + Con_Printf("baseline %i\n", newnum); + } + + if (word & QW_U_REMOVE) + { + if (newnum != oldnum && !delta && !invalid) + { + cl.latestframenums[LATESTFRAMENUMS-1] = 0; + Con_Printf("WARNING: U_REMOVE %i on full update\n", newnum); + } + } + else + { + if (newsnap->num_entities >= QW_MAX_PACKET_ENTITIES) + Host_Error("EntityFrameQW_CL_ReadFrame: newsnap->num_entities == MAX_PACKETENTITIES"); + newsnap->entities[newsnap->num_entities] = (newnum == oldnum) ? oldsnap->entities[oldindex++] : cl_entities[newnum].state_baseline; + EntityStateQW_ReadEntityUpdate(newsnap->entities + newsnap->num_entities, word); + newsnap->num_entities++; + } + } + + // expand cl_num_entities to include every entity we've seen this game + newnum = newsnap->num_entities ? newsnap->entities[newsnap->num_entities - 1].number : 1; + if (cl_num_entities <= newnum) + { + cl_num_entities = newnum + 1; + if (cl_max_entities < newnum + 1) + CL_ExpandEntities(newnum); + } + + // now update the entities from the snapshot states + number = 1; + for (newindex = 0;;newindex++) + { + newnum = newindex >= newsnap->num_entities ? cl_num_entities : newsnap->entities[newindex].number; + // kill any missing entities + for (;number < newnum;number++) + { + if (cl_entities_active[number]) + { + cl_entities_active[number] = false; + cl_entities[number].state_current.active = false; + } + } + if (number >= cl_num_entities) + break; + // update the entity + ent = &cl_entities[number]; + ent->state_previous = ent->state_current; + ent->state_current = newsnap->entities[newindex]; + CL_MoveLerpEntityStates(ent); + // the entity lives again... + cl_entities_active[number] = true; + number++; + } +} diff --git a/protocol.h b/protocol.h index 20f655d8..e92337b9 100644 --- a/protocol.h +++ b/protocol.h @@ -60,6 +60,8 @@ void Protocol_Names(char *buffer, size_t buffersize); #define EF_SELECTABLE 16384 // LordHavoc: highlights when PRYDON_CLIENTCURSOR mouse is over it #define EF_DOUBLESIDED 32768 //[515]: disable cull face for this entity +#define EF_FLAG1QW 16777216 // internal client use only +#define EF_FLAG2QW 33554432 // internal client use only #define EF_STEP 0x80000000 // internal client use only - present on MOVETYPE_STEP entities, not QC accessible (too many bits) // flags for the pflags field of entities @@ -818,19 +820,19 @@ extern cvar_t developer_networkentities; #define qw_clc_upload 7 // teleport request, spectator only // QUAKEWORLD // playerinfo flags from server -// playerinfo allways sends: playernum, flags, origin[] and framenumber -#define PF_MSEC (1<<0) -#define PF_COMMAND (1<<1) -#define PF_VELOCITY1 (1<<2) -#define PF_VELOCITY2 (1<<3) -#define PF_VELOCITY3 (1<<4) -#define PF_MODEL (1<<5) -#define PF_SKINNUM (1<<6) -#define PF_EFFECTS (1<<7) -#define PF_WEAPONFRAME (1<<8) // only sent for view player -#define PF_DEAD (1<<9) // don't block movement any more -#define PF_GIB (1<<10) // offset the view height differently -#define PF_NOGRAV (1<<11) // don't apply gravity for prediction +// playerinfo always sends: playernum, flags, origin[] and framenumber +#define QW_PF_MSEC (1<<0) +#define QW_PF_COMMAND (1<<1) +#define QW_PF_VELOCITY1 (1<<2) +#define QW_PF_VELOCITY2 (1<<3) +#define QW_PF_VELOCITY3 (1<<4) +#define QW_PF_MODEL (1<<5) +#define QW_PF_SKINNUM (1<<6) +#define QW_PF_EFFECTS (1<<7) +#define QW_PF_WEAPONFRAME (1<<8) // only sent for view player +#define QW_PF_DEAD (1<<9) // don't block movement any more +#define QW_PF_GIB (1<<10) // offset the view height differently +#define QW_PF_NOGRAV (1<<11) // don't apply gravity for prediction // QUAKEWORLD // if the high bit of the client to server byte is set, the low bits are // client move cmd bits @@ -877,6 +879,60 @@ extern cvar_t developer_networkentities; #define QW_TE_TELEPORT 11 #define QW_TE_BLOOD 12 #define QW_TE_LIGHTNINGBLOOD 13 +// QUAKEWORLD +// effect flags +#define QW_EF_BRIGHTFIELD 1 +#define QW_EF_MUZZLEFLASH 2 +#define QW_EF_BRIGHTLIGHT 4 +#define QW_EF_DIMLIGHT 8 +#define QW_EF_FLAG1 16 +#define QW_EF_FLAG2 32 +#define QW_EF_BLUE 64 +#define QW_EF_RED 128 + +#define QW_UPDATE_BACKUP 64 +#define QW_UPDATE_MASK (QW_UPDATE_BACKUP - 1) +#define QW_MAX_PACKET_ENTITIES 64 + +// note: QW stats are directly compatible with NQ +// (but FRAGS, WEAPONFRAME, and VIEWHEIGHT are unused) +// so these defines are not actually used by darkplaces, but kept for reference +#define QW_STAT_HEALTH 0 +//#define QW_STAT_FRAGS 1 +#define QW_STAT_WEAPON 2 +#define QW_STAT_AMMO 3 +#define QW_STAT_ARMOR 4 +//#define QW_STAT_WEAPONFRAME 5 +#define QW_STAT_SHELLS 6 +#define QW_STAT_NAILS 7 +#define QW_STAT_ROCKETS 8 +#define QW_STAT_CELLS 9 +#define QW_STAT_ACTIVEWEAPON 10 +#define QW_STAT_TOTALSECRETS 11 +#define QW_STAT_TOTALMONSTERS 12 +#define QW_STAT_SECRETS 13 // bumped on client side by svc_foundsecret +#define QW_STAT_MONSTERS 14 // bumped by svc_killedmonster +#define QW_STAT_ITEMS 15 +//#define QW_STAT_VIEWHEIGHT 16 + +// build entity data in this, to pass to entity read/write functions +typedef struct entityframeqw_snapshot_s +{ + double time; + qboolean invalid; + int num_entities; + entity_state_t entities[QW_MAX_PACKET_ENTITIES]; +} +entityframeqw_snapshot_t; + +typedef struct entityframeqw_database_s +{ + entityframeqw_snapshot_t snapshot[QW_UPDATE_BACKUP]; +} +entityframeqw_database_t; + +void EntityStateQW_ReadPlayerUpdate(void); +void EntityFrameQW_CL_ReadFrame(qboolean delta); #endif