+ 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, "team", cl.scores[slot].qw_team, sizeof(cl.scores[slot].qw_team));
+ InfoString_GetValue(cl.scores[slot].qw_userinfo, "skin", cl.scores[slot].qw_skin, sizeof(cl.scores[slot].qw_skin));
+ if (!cl.scores[slot].qw_skin[0])
+ strlcpy(cl.scores[slot].qw_skin, "base", sizeof(cl.scores[slot].qw_skin));
+ // TODO: skin cache
+}
+
+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];
+ char temp[32];
+ 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);
+ InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
+ cl.qw_teamplay = atoi(temp);
+}
+
+static void QW_CL_ParseNails(void)
+{
+ int i, j;
+ int numnails = MSG_ReadByte();
+ vec_t *v;
+ unsigned char bits[6];
+ for (i = 0;i < numnails;i++)
+ {
+ for (j = 0;j < 6;j++)
+ bits[j] = MSG_ReadByte();
+ if (cl.qw_num_nails > 255)
+ continue;
+ v = cl.qw_nails[cl.qw_num_nails++];
+ 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 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<<j)) && !(cl.olditems & (1<<j)))
+ cl.item_gettime[j] = cl.time;
+ cl.olditems = cl.stats[STAT_ITEMS];
+
+ // GAME_NEXUIZ hud needs weapon change time
+ if (cl.activeweapon != cl.stats[STAT_ACTIVEWEAPON])
+ cl.weapontime = cl.time;
+ cl.activeweapon = cl.stats[STAT_ACTIVEWEAPON];
+}
+
+/*
+=====================
+CL_SignonReply
+
+An svc_signonnum has been received, perform a client side setup
+=====================
+*/
+static void CL_SignonReply (void)
+{
+ Con_DPrintf("CL_SignonReply: %i\n", cls.signon);
+
+ switch (cls.signon)
+ {
+ case 1:
+ if (cls.netcon)
+ {
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, "prespawn");
+ }
+ break;
+
+ case 2:
+ if (cls.netcon)
+ {
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, va("name \"%s\"", cl_name.string));
+
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, va("color %i %i", cl_color.integer >> 4, cl_color.integer & 15));
+
+ if (cl_pmodel.integer)
+ {
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, va("pmodel %i", cl_pmodel.integer));
+ }
+ if (*cl_playermodel.string)
+ {
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, va("playermodel %s", cl_playermodel.string));
+ }
+ if (*cl_playerskin.string)
+ {
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, va("playerskin %s", cl_playerskin.string));
+ }
+
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, va("rate %i", cl_rate.integer));
+
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, "spawn");
+ }
+ break;
+
+ case 3:
+ if (cls.netcon)
+ {
+ MSG_WriteByte (&cls.netcon->message, clc_stringcmd);
+ MSG_WriteString (&cls.netcon->message, "begin");
+ }
+ break;
+
+ case 4:
+ Con_ClearNotify();
+ break;
+ }
+}
+
+/*
+==================
+CL_ParseServerInfo
+==================
+*/
+void CL_ParseServerInfo (void)
+{
+ char *str;
+ int i;
+ protocolversion_t protocol;
+ int nummodels, numsounds;
+
+ Con_DPrint("Serverinfo packet received.\n");
+
+ // if server is active, we already began a loading plaque
+ if (!sv.active)
+ SCR_BeginLoadingPlaque();
+
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+
+//
+// wipe the client_state_t struct
+//
+ CL_ClearState ();
+
+// parse protocol version number
+ i = MSG_ReadLong ();
+ protocol = Protocol_EnumForNumber(i);
+ if (protocol == PROTOCOL_UNKNOWN)
+ {
+ Host_Error("CL_ParseServerInfo: Server is unrecognized protocol number (%i)", i);
+ return;
+ }
+ // 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;
+ Con_DPrintf("Server protocol is %s\n", Protocol_NameForEnum(cls.protocol));
+
+ cl.num_entities = 1;
+
+ if (protocol == PROTOCOL_QUAKEWORLD)
+ {
+ 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;
+ }
+
+ Com_Gamedir(str); // change gamedir
+
+ if (cflag)
+ {
+ // exec the new config stuff
+ }
+#endif
+
+ cl.gametype = GAME_DEATHMATCH;
+ cl.maxclients = 32;
+
+ // parse player number
+ i = MSG_ReadByte();
+ cl.qw_spectator = (i & 128) != 0;
+ cl.playerentity = cl.viewentity = (i & 127) + 1;
+ cl.scores = (scoreboard_t *)Mem_Alloc(cls.levelmempool, cl.maxclients*sizeof(*cl.scores));
+
+ // 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();
+
+ if (cls.netcon)
+ {
+ MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
+ MSG_WriteString(&cls.netcon->message, va("soundlist %i %i", cl.qw_servercount, 0));
+ }
+
+ cls.state = ca_connected;
+ cls.signon = 1;
+
+ // note: on QW protocol we can't set up the gameworld until after
+ // downloads finish...
+ // (we don't even know the name of the map yet)
+ }
+ else
+ {
+ // 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(cls.levelmempool, cl.maxclients*sizeof(*cl.scores));
+
+ // parse gametype
+ cl.gametype = MSG_ReadByte ();
+
+ // parse signon message
+ str = MSG_ReadString ();
+ strlcpy (cl.levelname, str, sizeof(cl.levelname));
+
+ // seperate the printfs so the server message can have a color
+ if (cls.protocol != PROTOCOL_NEHAHRAMOVIE) // no messages when playing the Nehahra movie
+ 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();
+
+ // 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(cl_sound_wizardhit.string, false, true);
+ cl.sfx_knighthit = S_PrecacheSound(cl_sound_hknighthit.string, false, true);
+ cl.sfx_tink1 = S_PrecacheSound(cl_sound_tink1.string, false, true);
+ cl.sfx_ric1 = S_PrecacheSound(cl_sound_ric1.string, false, true);
+ cl.sfx_ric2 = S_PrecacheSound(cl_sound_ric2.string, false, true);
+ cl.sfx_ric3 = S_PrecacheSound(cl_sound_ric3.string, false, true);
+ cl.sfx_r_exp3 = S_PrecacheSound(cl_sound_r_exp3.string, 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 ; i<nummodels ; i++)
+ {
+ CL_KeepaliveMessage();
+ if ((cl.model_precache[i] = Mod_ForName(cl.model_name[i], false, false, false))->Draw == NULL)
+ Con_Printf("Model %s not found\n", cl.model_name[i]);
+ }
+
+ // sounds
+ for (i=1 ; i<numsounds ; i++)
+ {
+ CL_KeepaliveMessage();
+ // Don't lock the sfx here, S_ServerSounds already did that
+ cl.sound_precache[i] = S_PrecacheSound (cl.sound_name[i], true, false);
+ }
+
+ // we now have the worldmodel so we can set up the game world
+ cl.entities[0].render.model = cl.worldmodel = cl.model_precache[1];
+ CL_BoundingBoxForEntity(&cl.entities[0].render);
+ R_Modules_NewMap();
+ }
+
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+}
+
+void CL_ValidateState(entity_state_t *s)
+{
+ model_t *model;
+
+ if (!s->active)
+ return;
+
+ if (s->modelindex >= MAX_MODELS && (65536-s->modelindex) >= MAX_MODELS)
+ Host_Error("CL_ValidateState: modelindex (%i) >= MAX_MODELS (%i)\n", s->modelindex, MAX_MODELS);
+
+ // colormap is client index + 1
+ if ((!s->flags & RENDER_COLORMAPPED) && s->colormap > cl.maxclients)
+ {
+ Con_DPrintf("CL_ValidateState: colormap (%i) > cl.maxclients (%i)\n", s->colormap, cl.maxclients);
+ s->colormap = 0;
+ }
+
+ model = cl.model_precache[s->modelindex];
+ if (model && model->type && s->frame >= model->numframes)
+ {
+ Con_DPrintf("CL_ValidateState: no such frame %i in \"%s\" (which has %i frames)\n", s->frame, model->name, model->numframes);
+ s->frame = 0;
+ }
+ if (model && model->type && s->skin > 0 && s->skin >= model->numskins && !(s->lightpflags & PFLAGS_FULLDYNAMIC))
+ {
+ Con_DPrintf("CL_ValidateState: no such skin %i in \"%s\" (which has %i skins)\n", s->skin, model->name, model->numskins);
+ s->skin = 0;
+ }
+}
+
+void CL_MoveLerpEntityStates(entity_t *ent)
+{
+ float odelta[3], adelta[3];
+ CL_ValidateState(&ent->state_current);
+ VectorSubtract(ent->state_current.origin, ent->persistent.neworigin, odelta);
+ VectorSubtract(ent->state_current.angles, ent->persistent.newangles, adelta);
+ if (!ent->state_previous.active || ent->state_previous.modelindex != ent->state_current.modelindex)
+ {
+ // reset all persistent stuff if this is a new entity
+ ent->persistent.lerpdeltatime = 0;
+ ent->persistent.lerpstarttime = cl.mtime[1];
+ VectorCopy(ent->state_current.origin, ent->persistent.oldorigin);
+ VectorCopy(ent->state_current.angles, ent->persistent.oldangles);
+ VectorCopy(ent->state_current.origin, ent->persistent.neworigin);
+ VectorCopy(ent->state_current.angles, ent->persistent.newangles);
+ // reset animation interpolation as well
+ ent->render.frame = ent->render.frame1 = ent->render.frame2 = ent->state_current.frame;
+ ent->render.frame1time = ent->render.frame2time = cl.time;
+ ent->render.framelerp = 1;
+ // reset various persistent stuff
+ ent->persistent.muzzleflash = 0;
+ VectorCopy(ent->state_current.origin, ent->persistent.trail_origin);
+ }
+ else if (cls.timedemo || cl_nolerp.integer || DotProduct(odelta, odelta) > 1000*1000 || (cl.fixangle[0] && !cl.fixangle[1]))
+ {
+ // don't interpolate the move
+ // (the fixangle[] check detects teleports, but not constant fixangles
+ // such as when spectating)
+ ent->persistent.lerpdeltatime = 0;
+ ent->persistent.lerpstarttime = cl.mtime[1];
+ VectorCopy(ent->state_current.origin, ent->persistent.oldorigin);
+ VectorCopy(ent->state_current.angles, ent->persistent.oldangles);
+ VectorCopy(ent->state_current.origin, ent->persistent.neworigin);
+ VectorCopy(ent->state_current.angles, ent->persistent.newangles);
+ }
+ else if (ent->state_current.flags & RENDER_STEP)
+ {
+ // monster interpolation
+ if (DotProduct(odelta, odelta) + DotProduct(adelta, adelta) > 0.01)
+ {
+ ent->persistent.lerpdeltatime = bound(0, cl.mtime[1] - ent->persistent.lerpstarttime, 0.1);
+ ent->persistent.lerpstarttime = cl.mtime[1];
+ VectorCopy(ent->persistent.neworigin, ent->persistent.oldorigin);
+ VectorCopy(ent->persistent.newangles, ent->persistent.oldangles);
+ VectorCopy(ent->state_current.origin, ent->persistent.neworigin);
+ VectorCopy(ent->state_current.angles, ent->persistent.newangles);
+ }
+ }
+ else
+ {
+ // not a monster
+ ent->persistent.lerpstarttime = ent->state_previous.time;
+ // no lerp if it's singleplayer
+ if (cl.islocalgame && !sv_fixedframeratesingleplayer.integer)
+ ent->persistent.lerpdeltatime = 0;
+ else
+ ent->persistent.lerpdeltatime = bound(0, ent->state_current.time - ent->state_previous.time, 0.1);
+ VectorCopy(ent->persistent.neworigin, ent->persistent.oldorigin);
+ VectorCopy(ent->persistent.newangles, ent->persistent.oldangles);
+ VectorCopy(ent->state_current.origin, ent->persistent.neworigin);
+ VectorCopy(ent->state_current.angles, ent->persistent.newangles);
+ }
+}
+
+/*
+==================
+CL_ParseBaseline
+==================
+*/
+void CL_ParseBaseline (entity_t *ent, int large)
+{
+ int i;
+
+ ent->state_baseline = defaultstate;
+ // FIXME: set ent->state_baseline.number?
+ ent->state_baseline.active = true;
+ if (large)
+ {
+ ent->state_baseline.modelindex = (unsigned short) MSG_ReadShort ();
+ ent->state_baseline.frame = (unsigned short) MSG_ReadShort ();
+ }
+ else
+ {
+ ent->state_baseline.modelindex = MSG_ReadByte ();
+ ent->state_baseline.frame = MSG_ReadByte ();
+ }
+ ent->state_baseline.colormap = MSG_ReadByte();
+ ent->state_baseline.skin = MSG_ReadByte();
+ for (i = 0;i < 3;i++)
+ {
+ ent->state_baseline.origin[i] = MSG_ReadCoord(cls.protocol);
+ ent->state_baseline.angles[i] = MSG_ReadAngle(cls.protocol);
+ }
+ CL_ValidateState(&ent->state_baseline);
+ ent->state_previous = ent->state_current = ent->state_baseline;
+}
+
+
+/*
+==================
+CL_ParseClientdata
+
+Server information pertaining to this client only
+==================
+*/
+void CL_ParseClientdata (void)
+{
+ int i, bits;
+
+ 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];
+
+ if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE || cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4 || cls.protocol == PROTOCOL_DARKPLACES5)
+ {
+ cl.stats[STAT_VIEWHEIGHT] = DEFAULT_VIEWHEIGHT;
+ cl.stats[STAT_ITEMS] = 0;
+ cl.stats[STAT_VIEWZOOM] = 255;
+ }
+ 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;
+
+ bits = (unsigned short) MSG_ReadShort ();
+ if (bits & SU_EXTEND1)
+ bits |= (MSG_ReadByte() << 16);
+ if (bits & SU_EXTEND2)
+ bits |= (MSG_ReadByte() << 24);
+
+ if (bits & SU_VIEWHEIGHT)
+ cl.stats[STAT_VIEWHEIGHT] = MSG_ReadChar ();
+
+ if (bits & SU_IDEALPITCH)
+ cl.idealpitch = MSG_ReadChar ();
+
+ for (i = 0;i < 3;i++)
+ {
+ if (bits & (SU_PUNCH1<<i) )
+ {
+ if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE)
+ cl.mpunchangle[0][i] = MSG_ReadChar();
+ else
+ cl.mpunchangle[0][i] = MSG_ReadAngle16i();
+ }
+ if (bits & (SU_PUNCHVEC1<<i))
+ {
+ if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4)
+ cl.mpunchvector[0][i] = MSG_ReadCoord16i();
+ else
+ cl.mpunchvector[0][i] = MSG_ReadCoord32f();
+ }
+ if (bits & (SU_VELOCITY1<<i) )
+ {
+ if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE || cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4)
+ cl.mvelocity[0][i] = MSG_ReadChar()*16;
+ else
+ cl.mvelocity[0][i] = MSG_ReadCoord32f();
+ }
+ }
+
+ // LordHavoc: hipnotic demos don't have this bit set but should
+ if (bits & SU_ITEMS || cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE || cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4 || cls.protocol == PROTOCOL_DARKPLACES5)
+ cl.stats[STAT_ITEMS] = MSG_ReadLong ();
+
+ cl.onground = (bits & SU_ONGROUND) != 0;
+ cl.inwater = (bits & SU_INWATER) != 0;
+
+ if (cls.protocol == PROTOCOL_DARKPLACES5)
+ {
+ cl.stats[STAT_WEAPONFRAME] = (bits & SU_WEAPONFRAME) ? MSG_ReadShort() : 0;
+ cl.stats[STAT_ARMOR] = (bits & SU_ARMOR) ? MSG_ReadShort() : 0;
+ cl.stats[STAT_WEAPON] = (bits & SU_WEAPON) ? MSG_ReadShort() : 0;
+ cl.stats[STAT_HEALTH] = MSG_ReadShort();
+ cl.stats[STAT_AMMO] = MSG_ReadShort();
+ cl.stats[STAT_SHELLS] = MSG_ReadShort();
+ cl.stats[STAT_NAILS] = MSG_ReadShort();
+ cl.stats[STAT_ROCKETS] = MSG_ReadShort();
+ cl.stats[STAT_CELLS] = MSG_ReadShort();
+ cl.stats[STAT_ACTIVEWEAPON] = (unsigned short) MSG_ReadShort ();
+ }
+ else if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE || cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4)
+ {
+ cl.stats[STAT_WEAPONFRAME] = (bits & SU_WEAPONFRAME) ? MSG_ReadByte() : 0;
+ cl.stats[STAT_ARMOR] = (bits & SU_ARMOR) ? MSG_ReadByte() : 0;
+ cl.stats[STAT_WEAPON] = (bits & SU_WEAPON) ? MSG_ReadByte() : 0;
+ cl.stats[STAT_HEALTH] = MSG_ReadShort();
+ cl.stats[STAT_AMMO] = MSG_ReadByte();
+ cl.stats[STAT_SHELLS] = MSG_ReadByte();
+ cl.stats[STAT_NAILS] = MSG_ReadByte();
+ cl.stats[STAT_ROCKETS] = MSG_ReadByte();
+ cl.stats[STAT_CELLS] = MSG_ReadByte();
+ if (gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE || gamemode == GAME_NEXUIZ)
+ cl.stats[STAT_ACTIVEWEAPON] = (1<<MSG_ReadByte ());
+ else
+ cl.stats[STAT_ACTIVEWEAPON] = MSG_ReadByte ();
+ }
+
+ if (bits & SU_VIEWZOOM)
+ {
+ if (cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4)
+ cl.stats[STAT_VIEWZOOM] = MSG_ReadByte();
+ else
+ cl.stats[STAT_VIEWZOOM] = (unsigned short) MSG_ReadShort();
+ }
+
+ // viewzoom interpolation
+ cl.mviewzoom[0] = (float) max(cl.stats[STAT_VIEWZOOM], 2) * (1.0f / 255.0f);
+
+ // force a recalculation of the player prediction
+ cl.movement_replay = true;
+}
+
+/*
+=====================
+CL_ParseStatic
+=====================
+*/
+void CL_ParseStatic (int large)
+{
+ entity_t *ent;
+
+ if (cl.num_static_entities >= cl.max_static_entities)
+ Host_Error ("Too many static entities");
+ ent = &cl.static_entities[cl.num_static_entities++];
+ CL_ParseBaseline (ent, large);
+
+// copy it to the current state
+ ent->render.model = cl.model_precache[ent->state_baseline.modelindex];
+ ent->render.frame = ent->render.frame1 = ent->render.frame2 = ent->state_baseline.frame;
+ ent->render.framelerp = 0;
+ // make torchs play out of sync
+ ent->render.frame1time = ent->render.frame2time = lhrandom(-10, -1);
+ ent->render.colormap = -1; // no special coloring
+ ent->render.skinnum = ent->state_baseline.skin;
+ ent->render.effects = ent->state_baseline.effects;
+ ent->render.alpha = 1;
+ //ent->render.scale = 1;
+
+ //VectorCopy (ent->state_baseline.origin, ent->render.origin);
+ //VectorCopy (ent->state_baseline.angles, ent->render.angles);
+
+ Matrix4x4_CreateFromQuakeEntity(&ent->render.matrix, ent->state_baseline.origin[0], ent->state_baseline.origin[1], ent->state_baseline.origin[2], ent->state_baseline.angles[0], ent->state_baseline.angles[1], ent->state_baseline.angles[2], 1);
+ Matrix4x4_Invert_Simple(&ent->render.inversematrix, &ent->render.matrix);
+ CL_BoundingBoxForEntity(&ent->render);
+
+ // This is definitely cheating...
+ if (ent->render.model == NULL)
+ cl.num_static_entities--;
+}
+
+/*
+===================
+CL_ParseStaticSound
+===================
+*/
+void CL_ParseStaticSound (int large)
+{
+ vec3_t org;
+ int sound_num, vol, atten;
+
+ MSG_ReadVector(org, cls.protocol);
+ if (large)
+ sound_num = (unsigned short) MSG_ReadShort ();
+ else
+ sound_num = MSG_ReadByte ();
+ vol = MSG_ReadByte ();
+ atten = MSG_ReadByte ();
+
+ S_StaticSound (cl.sound_precache[sound_num], org, vol/255.0f, atten);
+}
+
+void CL_ParseEffect (void)
+{
+ vec3_t org;
+ int modelindex, startframe, framecount, framerate;
+
+ MSG_ReadVector(org, cls.protocol);
+ modelindex = MSG_ReadByte ();
+ startframe = MSG_ReadByte ();
+ framecount = MSG_ReadByte ();
+ framerate = MSG_ReadByte ();
+
+ CL_Effect(org, modelindex, startframe, framecount, framerate);
+}
+
+void CL_ParseEffect2 (void)
+{
+ vec3_t org;
+ int modelindex, startframe, framecount, framerate;
+
+ MSG_ReadVector(org, cls.protocol);
+ modelindex = (unsigned short) MSG_ReadShort ();
+ startframe = (unsigned short) MSG_ReadShort ();
+ framecount = MSG_ReadByte ();
+ framerate = MSG_ReadByte ();
+
+ CL_Effect(org, modelindex, startframe, framecount, framerate);
+}
+
+void CL_NewBeam (int ent, vec3_t start, vec3_t end, model_t *m, int lightning)
+{
+ int i;
+ beam_t *b = NULL;
+
+ if (ent >= MAX_EDICTS)
+ {
+ Con_Printf("CL_NewBeam: invalid entity number %i\n", ent);
+ ent = 0;
+ }
+
+ if (ent >= cl.max_entities)
+ CL_ExpandEntities(ent);
+
+ // override any beam with the same entity
+ i = cl.max_beams;
+ if (ent)
+ for (i = 0, b = cl.beams;i < cl.max_beams;i++, b++)
+ if (b->entity == ent)
+ break;
+ // if the entity was not found then just replace an unused beam
+ if (i == cl.max_beams)
+ for (i = 0, b = cl.beams;i < cl.max_beams;i++, b++)
+ if (!b->model)
+ break;
+ if (i < cl.max_beams)
+ {
+ cl.num_beams = max(cl.num_beams, i + 1);
+ b->entity = ent;
+ b->lightning = lightning;
+ b->model = m;
+ b->endtime = cl.mtime[0] + 0.2;
+ VectorCopy (start, b->start);
+ VectorCopy (end, b->end);
+ }
+ else
+ Con_Print("beam list overflow!\n");
+}
+
+void CL_ParseBeam (model_t *m, int lightning)