+ else if(d->framenum < framenum)
+ {
+ // a frame in the past... should never happen
+ Con_Printf("CSQC entity DB encountered a frame from the past when recovering from PL...?\n");
+ }
+ else if(d->framenum == framenum)
+ {
+ // handling the actually lost frame now
+ for(i = 0; i < d->num; ++i)
+ {
+ int sf = d->sendflags[i];
+ int ent = d->entno[i];
+ if(sf < 0) // remove
+ recoversendflags[ent] |= -1; // all bits, including sign
+ else if(sf > 0)
+ recoversendflags[ent] |= sf;
+ }
+ }
+ else
+ {
+ // handling the frames that followed it now
+ for(i = 0; i < d->num; ++i)
+ {
+ int sf = d->sendflags[i];
+ int ent = d->entno[i];
+ if(sf < 0) // remove
+ {
+ recoversendflags[ent] = 0; // no need to update, we got a more recent remove (and will fix it THEN)
+ break; // no flags left to remove...
+ }
+ else if(sf > 0)
+ recoversendflags[ent] &= ~sf; // no need to update these bits, we already got them later
+ }
+ }
+ }
+
+ for(i = 0; i < client->csqcnumedicts; ++i)
+ {
+ if(recoversendflags[i] < 0)
+ {
+ // a remove got lost, then either send a remove or - if it was
+ // recreated later - a FULL update to make totally sure
+ client->csqcentityscope[i] = 1;
+ client->csqcentitysendflags[i] = 0xFFFFFF;
+ }
+ else
+ client->csqcentitysendflags[i] |= recoversendflags[i];
+ }
+}
+static int EntityFrameCSQC_AllocFrame(client_t *client, int framenum)
+{
+ int ringfirst = client->csqcentityframehistory_next; // oldest entry
+ client->csqcentityframehistory_next += 1;
+ client->csqcentityframehistory_next %= NUM_CSQCENTITYDB_FRAMES;
+ client->csqcentityframehistory[ringfirst].framenum = framenum;
+ client->csqcentityframehistory[ringfirst].num = 0;
+ return ringfirst;
+}
+static void EntityFrameCSQC_DeallocFrame(client_t *client, int framenum)
+{
+ int ringfirst = client->csqcentityframehistory_next; // oldest entry
+ int ringlast = (ringfirst + NUM_CSQCENTITYDB_FRAMES - 1) % NUM_CSQCENTITYDB_FRAMES; // most recently added entry
+ if(framenum == client->csqcentityframehistory[ringlast].framenum)
+ {
+ client->csqcentityframehistory[ringlast].framenum = -1;
+ client->csqcentityframehistory[ringlast].num = 0;
+ client->csqcentityframehistory_next = ringlast;
+ }
+ else
+ Con_Printf("Trying to dealloc the wrong entity frame\n");
+}
+
+//[515]: we use only one array per-client for SendEntity feature
+// TODO: add some handling for entity send priorities, to better deal with huge
+// amounts of csqc networked entities
+qboolean EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numnumbers, const unsigned short *numbers, int framenum)
+{
+ int num, number, end, sendflags;
+ qboolean sectionstarted = false;
+ const unsigned short *n;
+ prvm_edict_t *ed;
+ prvm_eval_t *val;
+ client_t *client = svs.clients + sv.writeentitiestoclient_clientnumber;
+ int dbframe = EntityFrameCSQC_AllocFrame(client, framenum);
+ csqcentityframedb_t *db = &client->csqcentityframehistory[dbframe];
+
+ maxsize -= 24; // always fit in an empty svc_entities message (for packet loss detection!)
+
+ // if this server progs is not CSQC-aware, return early
+ if(prog->fieldoffsets.SendEntity < 0 || prog->fieldoffsets.Version < 0)
+ return false;
+
+ // make sure there is enough room to store the svc_csqcentities byte,
+ // the terminator (0x0000) and at least one entity update
+ if (msg->cursize + 32 >= maxsize)
+ return false;
+
+ if (client->csqcnumedicts < prog->num_edicts)
+ client->csqcnumedicts = prog->num_edicts;
+
+ number = 1;
+ for (num = 0, n = numbers;num < numnumbers;num++, n++)
+ {
+ end = *n;
+ for (;number < end;number++)
+ {
+ if (client->csqcentityscope[number])
+ {
+ client->csqcentityscope[number] = 1;
+ client->csqcentitysendflags[number] = 0xFFFFFF;
+ }
+ }
+ ed = prog->edicts + number;
+ val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.SendEntity);
+ if (val->function)
+ client->csqcentityscope[number] = 2;
+ else if (client->csqcentityscope[number])
+ {
+ client->csqcentityscope[number] = 1;
+ client->csqcentitysendflags[number] = 0xFFFFFF;
+ }
+ number++;
+ }
+ end = client->csqcnumedicts;
+ for (;number < end;number++)
+ {
+ if (client->csqcentityscope[number])
+ {
+ client->csqcentityscope[number] = 1;
+ client->csqcentitysendflags[number] = 0xFFFFFF;
+ }
+ }
+
+ /*
+ // mark all scope entities as remove
+ for (number = 1;number < client->csqcnumedicts;number++)
+ if (client->csqcentityscope[number])
+ client->csqcentityscope[number] = 1;
+ // keep visible entities
+ for (i = 0, n = numbers;i < numnumbers;i++, n++)
+ {
+ number = *n;
+ ed = prog->edicts + number;
+ val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.SendEntity);
+ if (val->function)
+ client->csqcentityscope[number] = 2;
+ }
+ */
+
+ // now try to emit the entity updates
+ // (FIXME: prioritize by distance?)
+ end = client->csqcnumedicts;
+ for (number = 1;number < end;number++)
+ {
+ if (!client->csqcentityscope[number])
+ continue;
+ sendflags = client->csqcentitysendflags[number];
+ if (!sendflags)
+ continue;
+ if(db->num >= NUM_CSQCENTITIES_PER_FRAME)
+ break;
+ ed = prog->edicts + number;
+ // entity scope is either update (2) or remove (1)
+ if (client->csqcentityscope[number] == 1)
+ {
+ // write a remove message
+ // first write the message identifier if needed
+ if(!sectionstarted)
+ {
+ sectionstarted = 1;
+ MSG_WriteByte(msg, svc_csqcentities);
+ }
+ // write the remove message
+ {
+ ENTITYSIZEPROFILING_START(msg, number);
+ MSG_WriteShort(msg, (unsigned short)number | 0x8000);
+ client->csqcentityscope[number] = 0;
+ client->csqcentitysendflags[number] = 0xFFFFFF; // resend completely if it becomes active again
+ db->entno[db->num] = number;
+ db->sendflags[db->num] = -1;
+ db->num += 1;
+ client->csqcentityglobalhistory[number] = 1;
+ ENTITYSIZEPROFILING_END(msg, number);
+ }
+ if (msg->cursize + 17 >= maxsize)
+ break;
+ }
+ else
+ {
+ // write an update
+ // save the cursize value in case we overflow and have to rollback
+ int oldcursize = msg->cursize;
+ client->csqcentityscope[number] = 1;
+ val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.SendEntity);
+ if (val->function)
+ {
+ if(!sectionstarted)
+ MSG_WriteByte(msg, svc_csqcentities);
+ {
+ ENTITYSIZEPROFILING_START(msg, number);
+ MSG_WriteShort(msg, number);
+ msg->allowoverflow = true;
+ PRVM_G_INT(OFS_PARM0) = sv.writeentitiestoclient_cliententitynumber;
+ PRVM_G_FLOAT(OFS_PARM1) = sendflags;
+ prog->globals.server->self = number;
+ PRVM_ExecuteProgram(val->function, "Null SendEntity\n");
+ msg->allowoverflow = false;
+ if(PRVM_G_FLOAT(OFS_RETURN) && msg->cursize + 2 <= maxsize)
+ {
+ // an update has been successfully written
+ client->csqcentitysendflags[number] = 0;
+ db->entno[db->num] = number;
+ db->sendflags[db->num] = sendflags;
+ db->num += 1;
+ client->csqcentityglobalhistory[number] = 1;
+ // and take note that we have begun the svc_csqcentities
+ // section of the packet
+ sectionstarted = 1;
+ ENTITYSIZEPROFILING_END(msg, number);
+ if (msg->cursize + 17 >= maxsize)
+ break;
+ continue;
+ }
+ }
+ }
+ // self.SendEntity returned false (or does not exist) or the
+ // update was too big for this packet - rollback the buffer to its
+ // state before the writes occurred, we'll try again next frame
+ msg->cursize = oldcursize;
+ msg->overflowed = false;
+ }
+ }
+ if (sectionstarted)
+ {
+ // write index 0 to end the update (0 is never used by real entities)
+ MSG_WriteShort(msg, 0);
+ }
+
+ if(db->num == 0)
+ // if no single ent got added, remove the frame from the DB again, to allow
+ // for a larger history
+ EntityFrameCSQC_DeallocFrame(client, framenum);
+
+ return sectionstarted;
+}
+
+void Protocol_UpdateClientStats(const int *stats)
+{
+ int i;
+ // update the stats array and set deltabits for any changed stats
+ for (i = 0;i < MAX_CL_STATS;i++)
+ {
+ if (host_client->stats[i] != stats[i])
+ {
+ host_client->statsdeltabits[i >> 3] |= 1 << (i & 7);
+ host_client->stats[i] = stats[i];
+ }
+ }
+}
+
+// only a few stats are within the 32 stat limit of Quake, and most of them
+// are sent every frame in svc_clientdata messages, so we only send the
+// remaining ones here
+static const int sendquakestats[] =
+{
+// quake did not send these secrets/monsters stats in this way, but doing so
+// allows a mod to increase STAT_TOTALMONSTERS during the game, and ensures
+// that STAT_SECRETS and STAT_MONSTERS are always correct (even if a client
+// didn't receive an svc_foundsecret or svc_killedmonster), which may be most
+// valuable if randomly seeking around in a demo
+STAT_TOTALSECRETS, // never changes during game
+STAT_TOTALMONSTERS, // changes in some mods
+STAT_SECRETS, // this makes svc_foundsecret unnecessary
+STAT_MONSTERS, // this makes svc_killedmonster unnecessary
+STAT_VIEWHEIGHT, // sent just for FTEQW clients
+STAT_VIEWZOOM, // this rarely changes
+-1,
+};
+
+void Protocol_WriteStatsReliable(void)
+{
+ int i, j;
+ if (!host_client->netconnection)
+ return;
+ // detect changes in stats and write reliable messages
+ // this only deals with 32 stats because the older protocols which use
+ // this function can only cope with 32 stats,
+ // they also do not support svc_updatestatubyte which was introduced in
+ // DP6 protocol (except for QW)
+ for (j = 0;sendquakestats[j] >= 0;j++)
+ {
+ i = sendquakestats[j];
+ // check if this bit is set
+ if (host_client->statsdeltabits[i >> 3] & (1 << (i & 7)))
+ {
+ host_client->statsdeltabits[i >> 3] -= (1 << (i & 7));
+ // send the stat as a byte if possible
+ if (sv.protocol == PROTOCOL_QUAKEWORLD)
+ {
+ if (host_client->stats[i] >= 0 && host_client->stats[i] < 256)
+ {
+ MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatestat);
+ MSG_WriteByte(&host_client->netconnection->message, i);
+ MSG_WriteByte(&host_client->netconnection->message, host_client->stats[i]);
+ }
+ else
+ {
+ MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatestatlong);
+ MSG_WriteByte(&host_client->netconnection->message, i);
+ MSG_WriteLong(&host_client->netconnection->message, host_client->stats[i]);
+ }
+ }
+ else
+ {
+ // this could make use of svc_updatestatubyte in DP6 and later
+ // protocols but those protocols do not use this function
+ MSG_WriteByte(&host_client->netconnection->message, svc_updatestat);
+ MSG_WriteByte(&host_client->netconnection->message, i);
+ MSG_WriteLong(&host_client->netconnection->message, host_client->stats[i]);
+ }
+ }
+ }
+}
+
+
+qboolean EntityFrameQuake_WriteFrame(sizebuf_t *msg, int maxsize, int numstates, const entity_state_t **states)
+{
+ const entity_state_t *s;
+ entity_state_t baseline;
+ int i, bits;
+ sizebuf_t buf;
+ unsigned char data[128];
+ prvm_eval_t *val;
+ qboolean success = false;
+
+ // prepare the buffer
+ memset(&buf, 0, sizeof(buf));
+ buf.data = data;
+ buf.maxsize = sizeof(data);
+
+ for (i = 0;i < numstates;i++)
+ {
+ ENTITYSIZEPROFILING_START(msg, states[i]->number);
+ s = states[i];
+ val = PRVM_EDICTFIELDVALUE((&prog->edicts[s->number]), prog->fieldoffsets.SendEntity);
+ if(val && val->function)
+ continue;
+
+ // prepare the buffer
+ SZ_Clear(&buf);
+
+// send an update
+ bits = 0;
+ if (s->number >= 256)
+ bits |= U_LONGENTITY;
+ if (s->flags & RENDER_STEP)
+ bits |= U_STEP;
+ if (s->flags & RENDER_VIEWMODEL)
+ bits |= U_VIEWMODEL;
+ if (s->flags & RENDER_GLOWTRAIL)
+ bits |= U_GLOWTRAIL;
+ if (s->flags & RENDER_EXTERIORMODEL)
+ bits |= U_EXTERIORMODEL;
+
+ // LordHavoc: old stuff, but rewritten to have more exact tolerances
+ baseline = prog->edicts[s->number].priv.server->baseline;
+ if (baseline.origin[0] != s->origin[0])
+ bits |= U_ORIGIN1;
+ if (baseline.origin[1] != s->origin[1])
+ bits |= U_ORIGIN2;
+ if (baseline.origin[2] != s->origin[2])
+ bits |= U_ORIGIN3;
+ if (baseline.angles[0] != s->angles[0])
+ bits |= U_ANGLE1;
+ if (baseline.angles[1] != s->angles[1])
+ bits |= U_ANGLE2;
+ if (baseline.angles[2] != s->angles[2])
+ bits |= U_ANGLE3;
+ if (baseline.colormap != s->colormap)
+ bits |= U_COLORMAP;
+ if (baseline.skin != s->skin)
+ bits |= U_SKIN;
+ if (baseline.frame != s->frame)
+ {
+ bits |= U_FRAME;
+ if (s->frame & 0xFF00)
+ bits |= U_FRAME2;
+ }
+ if (baseline.effects != s->effects)
+ {
+ bits |= U_EFFECTS;
+ if (s->effects & 0xFF00)
+ bits |= U_EFFECTS2;
+ }
+ if (baseline.modelindex != s->modelindex)
+ {
+ bits |= U_MODEL;
+ if ((s->modelindex & 0xFF00) && sv.protocol != PROTOCOL_NEHAHRABJP && sv.protocol != PROTOCOL_NEHAHRABJP2 && sv.protocol != PROTOCOL_NEHAHRABJP3)
+ bits |= U_MODEL2;
+ }
+ if (baseline.alpha != s->alpha)
+ bits |= U_ALPHA;
+ if (baseline.scale != s->scale)
+ bits |= U_SCALE;
+ if (baseline.glowsize != s->glowsize)
+ bits |= U_GLOWSIZE;
+ if (baseline.glowcolor != s->glowcolor)
+ bits |= U_GLOWCOLOR;
+ if (!VectorCompare(baseline.colormod, s->colormod))
+ bits |= U_COLORMOD;
+
+ // if extensions are disabled, clear the relevant update flags
+ if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_NEHAHRAMOVIE)
+ bits &= 0x7FFF;
+ if (sv.protocol == PROTOCOL_NEHAHRAMOVIE)
+ if (s->alpha != 255 || s->effects & EF_FULLBRIGHT)
+ bits |= U_EXTEND1;
+
+ // write the message
+ if (bits >= 16777216)
+ bits |= U_EXTEND2;
+ if (bits >= 65536)
+ bits |= U_EXTEND1;
+ if (bits >= 256)
+ bits |= U_MOREBITS;
+ bits |= U_SIGNAL;
+
+ MSG_WriteByte (&buf, bits);
+ if (bits & U_MOREBITS) MSG_WriteByte(&buf, bits>>8);
+ if (sv.protocol != PROTOCOL_NEHAHRAMOVIE)
+ {
+ if (bits & U_EXTEND1) MSG_WriteByte(&buf, bits>>16);
+ if (bits & U_EXTEND2) MSG_WriteByte(&buf, bits>>24);
+ }
+ if (bits & U_LONGENTITY) MSG_WriteShort(&buf, s->number);
+ else MSG_WriteByte(&buf, s->number);
+
+ if (bits & U_MODEL)
+ {
+ if (sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3)
+ MSG_WriteShort(&buf, s->modelindex);
+ else
+ MSG_WriteByte(&buf, s->modelindex);
+ }
+ if (bits & U_FRAME) MSG_WriteByte(&buf, s->frame);
+ if (bits & U_COLORMAP) MSG_WriteByte(&buf, s->colormap);
+ if (bits & U_SKIN) MSG_WriteByte(&buf, s->skin);
+ if (bits & U_EFFECTS) MSG_WriteByte(&buf, s->effects);
+ if (bits & U_ORIGIN1) MSG_WriteCoord(&buf, s->origin[0], sv.protocol);
+ if (bits & U_ANGLE1) MSG_WriteAngle(&buf, s->angles[0], sv.protocol);
+ if (bits & U_ORIGIN2) MSG_WriteCoord(&buf, s->origin[1], sv.protocol);
+ if (bits & U_ANGLE2) MSG_WriteAngle(&buf, s->angles[1], sv.protocol);
+ if (bits & U_ORIGIN3) MSG_WriteCoord(&buf, s->origin[2], sv.protocol);
+ if (bits & U_ANGLE3) MSG_WriteAngle(&buf, s->angles[2], sv.protocol);
+ if (bits & U_ALPHA) MSG_WriteByte(&buf, s->alpha);
+ if (bits & U_SCALE) MSG_WriteByte(&buf, s->scale);
+ if (bits & U_EFFECTS2) MSG_WriteByte(&buf, s->effects >> 8);
+ if (bits & U_GLOWSIZE) MSG_WriteByte(&buf, s->glowsize);
+ if (bits & U_GLOWCOLOR) MSG_WriteByte(&buf, s->glowcolor);
+ if (bits & U_COLORMOD) {int c = ((int)bound(0, s->colormod[0] * (7.0f / 32.0f), 7) << 5) | ((int)bound(0, s->colormod[1] * (7.0f / 32.0f), 7) << 2) | ((int)bound(0, s->colormod[2] * (3.0f / 32.0f), 3) << 0);MSG_WriteByte(&buf, c);}
+ if (bits & U_FRAME2) MSG_WriteByte(&buf, s->frame >> 8);
+ if (bits & U_MODEL2) MSG_WriteByte(&buf, s->modelindex >> 8);
+
+ // the nasty protocol
+ if ((bits & U_EXTEND1) && sv.protocol == PROTOCOL_NEHAHRAMOVIE)
+ {
+ if (s->effects & EF_FULLBRIGHT)
+ {
+ MSG_WriteFloat(&buf, 2); // QSG protocol version
+ MSG_WriteFloat(&buf, s->alpha <= 0 ? 0 : (s->alpha >= 255 ? 1 : s->alpha * (1.0f / 255.0f))); // alpha
+ MSG_WriteFloat(&buf, 1); // fullbright
+ }
+ else
+ {
+ MSG_WriteFloat(&buf, 1); // QSG protocol version
+ MSG_WriteFloat(&buf, s->alpha <= 0 ? 0 : (s->alpha >= 255 ? 1 : s->alpha * (1.0f / 255.0f))); // alpha
+ }
+ }
+
+ // if the commit is full, we're done this frame
+ if (msg->cursize + buf.cursize > maxsize)
+ {
+ // next frame we will continue where we left off
+ break;
+ }
+ // write the message to the packet
+ SZ_Write(msg, buf.data, buf.cursize);
+ success = true;
+ ENTITYSIZEPROFILING_END(msg, s->number);
+ }
+ return success;
+}
+
+int EntityState_DeltaBits(const entity_state_t *o, const entity_state_t *n)
+{
+ unsigned int bits;
+ // if o is not active, delta from default
+ if (o->active != ACTIVE_NETWORK)
+ o = &defaultstate;
+ bits = 0;
+ if (fabs(n->origin[0] - o->origin[0]) > (1.0f / 256.0f))
+ bits |= E_ORIGIN1;
+ if (fabs(n->origin[1] - o->origin[1]) > (1.0f / 256.0f))
+ bits |= E_ORIGIN2;
+ if (fabs(n->origin[2] - o->origin[2]) > (1.0f / 256.0f))
+ bits |= E_ORIGIN3;
+ if ((unsigned char) (n->angles[0] * (256.0f / 360.0f)) != (unsigned char) (o->angles[0] * (256.0f / 360.0f)))
+ bits |= E_ANGLE1;
+ if ((unsigned char) (n->angles[1] * (256.0f / 360.0f)) != (unsigned char) (o->angles[1] * (256.0f / 360.0f)))
+ bits |= E_ANGLE2;
+ if ((unsigned char) (n->angles[2] * (256.0f / 360.0f)) != (unsigned char) (o->angles[2] * (256.0f / 360.0f)))
+ bits |= E_ANGLE3;
+ if ((n->modelindex ^ o->modelindex) & 0x00FF)
+ bits |= E_MODEL1;
+ if ((n->modelindex ^ o->modelindex) & 0xFF00)
+ bits |= E_MODEL2;
+ if ((n->frame ^ o->frame) & 0x00FF)
+ bits |= E_FRAME1;
+ if ((n->frame ^ o->frame) & 0xFF00)
+ bits |= E_FRAME2;
+ if ((n->effects ^ o->effects) & 0x00FF)
+ bits |= E_EFFECTS1;
+ if ((n->effects ^ o->effects) & 0xFF00)
+ bits |= E_EFFECTS2;
+ if (n->colormap != o->colormap)
+ bits |= E_COLORMAP;
+ if (n->skin != o->skin)
+ bits |= E_SKIN;
+ if (n->alpha != o->alpha)
+ bits |= E_ALPHA;
+ if (n->scale != o->scale)
+ bits |= E_SCALE;
+ if (n->glowsize != o->glowsize)
+ bits |= E_GLOWSIZE;
+ if (n->glowcolor != o->glowcolor)
+ bits |= E_GLOWCOLOR;
+ if (n->flags != o->flags)
+ bits |= E_FLAGS;
+ if (n->tagindex != o->tagindex || n->tagentity != o->tagentity)
+ bits |= E_TAGATTACHMENT;
+ if (n->light[0] != o->light[0] || n->light[1] != o->light[1] || n->light[2] != o->light[2] || n->light[3] != o->light[3])
+ bits |= E_LIGHT;
+ if (n->lightstyle != o->lightstyle)
+ bits |= E_LIGHTSTYLE;
+ if (n->lightpflags != o->lightpflags)
+ bits |= E_LIGHTPFLAGS;
+
+ if (bits)
+ {
+ if (bits & 0xFF000000)
+ bits |= 0x00800000;
+ if (bits & 0x00FF0000)
+ bits |= 0x00008000;
+ if (bits & 0x0000FF00)
+ bits |= 0x00000080;
+ }
+ return bits;
+}
+
+void EntityState_WriteExtendBits(sizebuf_t *msg, unsigned int bits)
+{
+ MSG_WriteByte(msg, bits & 0xFF);
+ if (bits & 0x00000080)
+ {
+ MSG_WriteByte(msg, (bits >> 8) & 0xFF);
+ if (bits & 0x00008000)
+ {
+ MSG_WriteByte(msg, (bits >> 16) & 0xFF);
+ if (bits & 0x00800000)
+ MSG_WriteByte(msg, (bits >> 24) & 0xFF);
+ }
+ }
+}
+
+void EntityState_WriteFields(const entity_state_t *ent, sizebuf_t *msg, unsigned int bits)
+{
+ if (sv.protocol == PROTOCOL_DARKPLACES2)
+ {
+ if (bits & E_ORIGIN1)
+ MSG_WriteCoord16i(msg, ent->origin[0]);
+ if (bits & E_ORIGIN2)
+ MSG_WriteCoord16i(msg, ent->origin[1]);
+ if (bits & E_ORIGIN3)
+ MSG_WriteCoord16i(msg, ent->origin[2]);
+ }
+ else
+ {
+ // LordHavoc: have to write flags first, as they can modify protocol
+ if (bits & E_FLAGS)
+ MSG_WriteByte(msg, ent->flags);
+ if (ent->flags & RENDER_LOWPRECISION)
+ {
+ if (bits & E_ORIGIN1)
+ MSG_WriteCoord16i(msg, ent->origin[0]);
+ if (bits & E_ORIGIN2)
+ MSG_WriteCoord16i(msg, ent->origin[1]);
+ if (bits & E_ORIGIN3)
+ MSG_WriteCoord16i(msg, ent->origin[2]);
+ }
+ else
+ {
+ if (bits & E_ORIGIN1)
+ MSG_WriteCoord32f(msg, ent->origin[0]);
+ if (bits & E_ORIGIN2)
+ MSG_WriteCoord32f(msg, ent->origin[1]);
+ if (bits & E_ORIGIN3)
+ MSG_WriteCoord32f(msg, ent->origin[2]);
+ }
+ }
+ if ((sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4) && (ent->flags & RENDER_LOWPRECISION))
+ {
+ if (bits & E_ANGLE1)
+ MSG_WriteAngle8i(msg, ent->angles[0]);
+ if (bits & E_ANGLE2)
+ MSG_WriteAngle8i(msg, ent->angles[1]);
+ if (bits & E_ANGLE3)
+ MSG_WriteAngle8i(msg, ent->angles[2]);
+ }
+ else
+ {
+ if (bits & E_ANGLE1)
+ MSG_WriteAngle16i(msg, ent->angles[0]);
+ if (bits & E_ANGLE2)
+ MSG_WriteAngle16i(msg, ent->angles[1]);
+ if (bits & E_ANGLE3)
+ MSG_WriteAngle16i(msg, ent->angles[2]);
+ }
+ if (bits & E_MODEL1)
+ MSG_WriteByte(msg, ent->modelindex & 0xFF);
+ if (bits & E_MODEL2)
+ MSG_WriteByte(msg, (ent->modelindex >> 8) & 0xFF);
+ if (bits & E_FRAME1)
+ MSG_WriteByte(msg, ent->frame & 0xFF);
+ if (bits & E_FRAME2)
+ MSG_WriteByte(msg, (ent->frame >> 8) & 0xFF);
+ if (bits & E_EFFECTS1)
+ MSG_WriteByte(msg, ent->effects & 0xFF);
+ if (bits & E_EFFECTS2)
+ MSG_WriteByte(msg, (ent->effects >> 8) & 0xFF);
+ if (bits & E_COLORMAP)
+ MSG_WriteByte(msg, ent->colormap);
+ if (bits & E_SKIN)
+ MSG_WriteByte(msg, ent->skin);
+ if (bits & E_ALPHA)
+ MSG_WriteByte(msg, ent->alpha);
+ if (bits & E_SCALE)
+ MSG_WriteByte(msg, ent->scale);
+ if (bits & E_GLOWSIZE)
+ MSG_WriteByte(msg, ent->glowsize);
+ if (bits & E_GLOWCOLOR)
+ MSG_WriteByte(msg, ent->glowcolor);
+ if (sv.protocol == PROTOCOL_DARKPLACES2)
+ if (bits & E_FLAGS)
+ MSG_WriteByte(msg, ent->flags);
+ if (bits & E_TAGATTACHMENT)
+ {
+ MSG_WriteShort(msg, ent->tagentity);
+ MSG_WriteByte(msg, ent->tagindex);
+ }
+ if (bits & E_LIGHT)
+ {
+ MSG_WriteShort(msg, ent->light[0]);
+ MSG_WriteShort(msg, ent->light[1]);
+ MSG_WriteShort(msg, ent->light[2]);
+ MSG_WriteShort(msg, ent->light[3]);
+ }
+ if (bits & E_LIGHTSTYLE)
+ MSG_WriteByte(msg, ent->lightstyle);
+ if (bits & E_LIGHTPFLAGS)
+ MSG_WriteByte(msg, ent->lightpflags);
+}
+
+void EntityState_WriteUpdate(const entity_state_t *ent, sizebuf_t *msg, const entity_state_t *delta)
+{
+ unsigned int bits;
+ ENTITYSIZEPROFILING_START(msg, ent->number);
+ if (ent->active == ACTIVE_NETWORK)
+ {
+ // entity is active, check for changes from the delta
+ if ((bits = EntityState_DeltaBits(delta, ent)))
+ {
+ // write the update number, bits, and fields
+ MSG_WriteShort(msg, ent->number);
+ EntityState_WriteExtendBits(msg, bits);
+ EntityState_WriteFields(ent, msg, bits);
+ }
+ }
+ else
+ {
+ // entity is inactive, check if the delta was active
+ if (delta->active == ACTIVE_NETWORK)
+ {
+ // write the remove number
+ MSG_WriteShort(msg, ent->number | 0x8000);
+ }
+ }
+ ENTITYSIZEPROFILING_END(msg, ent->number);
+}
+
+int EntityState_ReadExtendBits(void)
+{
+ unsigned int bits;
+ bits = MSG_ReadByte();
+ if (bits & 0x00000080)
+ {
+ bits |= MSG_ReadByte() << 8;
+ if (bits & 0x00008000)
+ {
+ bits |= MSG_ReadByte() << 16;
+ if (bits & 0x00800000)
+ bits |= MSG_ReadByte() << 24;
+ }
+ }
+ return bits;
+}
+
+void EntityState_ReadFields(entity_state_t *e, unsigned int bits)
+{
+ if (cls.protocol == PROTOCOL_DARKPLACES2)
+ {
+ if (bits & E_ORIGIN1)
+ e->origin[0] = MSG_ReadCoord16i();
+ if (bits & E_ORIGIN2)
+ e->origin[1] = MSG_ReadCoord16i();
+ if (bits & E_ORIGIN3)
+ e->origin[2] = MSG_ReadCoord16i();
+ }
+ else
+ {
+ if (bits & E_FLAGS)
+ e->flags = MSG_ReadByte();
+ if (e->flags & RENDER_LOWPRECISION)
+ {
+ if (bits & E_ORIGIN1)
+ e->origin[0] = MSG_ReadCoord16i();
+ if (bits & E_ORIGIN2)
+ e->origin[1] = MSG_ReadCoord16i();
+ if (bits & E_ORIGIN3)
+ e->origin[2] = MSG_ReadCoord16i();
+ }
+ else
+ {
+ if (bits & E_ORIGIN1)
+ e->origin[0] = MSG_ReadCoord32f();
+ if (bits & E_ORIGIN2)
+ e->origin[1] = MSG_ReadCoord32f();
+ if (bits & E_ORIGIN3)
+ e->origin[2] = MSG_ReadCoord32f();
+ }
+ }
+ if ((cls.protocol == PROTOCOL_DARKPLACES5 || cls.protocol == PROTOCOL_DARKPLACES6) && !(e->flags & RENDER_LOWPRECISION))
+ {
+ if (bits & E_ANGLE1)
+ e->angles[0] = MSG_ReadAngle16i();
+ if (bits & E_ANGLE2)
+ e->angles[1] = MSG_ReadAngle16i();
+ if (bits & E_ANGLE3)
+ e->angles[2] = MSG_ReadAngle16i();
+ }
+ else
+ {
+ if (bits & E_ANGLE1)
+ e->angles[0] = MSG_ReadAngle8i();
+ if (bits & E_ANGLE2)
+ e->angles[1] = MSG_ReadAngle8i();
+ if (bits & E_ANGLE3)
+ e->angles[2] = MSG_ReadAngle8i();
+ }
+ if (bits & E_MODEL1)
+ e->modelindex = (e->modelindex & 0xFF00) | (unsigned int) MSG_ReadByte();
+ if (bits & E_MODEL2)
+ e->modelindex = (e->modelindex & 0x00FF) | ((unsigned int) MSG_ReadByte() << 8);
+ if (bits & E_FRAME1)
+ e->frame = (e->frame & 0xFF00) | (unsigned int) MSG_ReadByte();
+ if (bits & E_FRAME2)
+ e->frame = (e->frame & 0x00FF) | ((unsigned int) MSG_ReadByte() << 8);
+ if (bits & E_EFFECTS1)
+ e->effects = (e->effects & 0xFF00) | (unsigned int) MSG_ReadByte();
+ if (bits & E_EFFECTS2)
+ e->effects = (e->effects & 0x00FF) | ((unsigned int) MSG_ReadByte() << 8);
+ if (bits & E_COLORMAP)
+ e->colormap = MSG_ReadByte();
+ if (bits & E_SKIN)
+ e->skin = MSG_ReadByte();
+ if (bits & E_ALPHA)
+ e->alpha = MSG_ReadByte();
+ if (bits & E_SCALE)
+ e->scale = MSG_ReadByte();
+ if (bits & E_GLOWSIZE)
+ e->glowsize = MSG_ReadByte();
+ if (bits & E_GLOWCOLOR)
+ e->glowcolor = MSG_ReadByte();
+ if (cls.protocol == PROTOCOL_DARKPLACES2)
+ if (bits & E_FLAGS)
+ e->flags = MSG_ReadByte();
+ if (bits & E_TAGATTACHMENT)
+ {
+ e->tagentity = (unsigned short) MSG_ReadShort();
+ e->tagindex = MSG_ReadByte();
+ }
+ if (bits & E_LIGHT)
+ {
+ e->light[0] = (unsigned short) MSG_ReadShort();
+ e->light[1] = (unsigned short) MSG_ReadShort();
+ e->light[2] = (unsigned short) MSG_ReadShort();
+ e->light[3] = (unsigned short) MSG_ReadShort();
+ }
+ if (bits & E_LIGHTSTYLE)
+ e->lightstyle = MSG_ReadByte();
+ if (bits & E_LIGHTPFLAGS)
+ e->lightpflags = MSG_ReadByte();
+
+ if (developer_networkentities.integer >= 2)
+ {
+ Con_Printf("ReadFields e%i", e->number);
+
+ if (bits & E_ORIGIN1)
+ Con_Printf(" E_ORIGIN1 %f", e->origin[0]);
+ if (bits & E_ORIGIN2)
+ Con_Printf(" E_ORIGIN2 %f", e->origin[1]);
+ if (bits & E_ORIGIN3)
+ Con_Printf(" E_ORIGIN3 %f", e->origin[2]);
+ if (bits & E_ANGLE1)
+ Con_Printf(" E_ANGLE1 %f", e->angles[0]);
+ if (bits & E_ANGLE2)
+ Con_Printf(" E_ANGLE2 %f", e->angles[1]);
+ if (bits & E_ANGLE3)
+ Con_Printf(" E_ANGLE3 %f", e->angles[2]);
+ if (bits & (E_MODEL1 | E_MODEL2))
+ Con_Printf(" E_MODEL %i", e->modelindex);
+
+ if (bits & (E_FRAME1 | E_FRAME2))
+ Con_Printf(" E_FRAME %i", e->frame);
+ if (bits & (E_EFFECTS1 | E_EFFECTS2))
+ Con_Printf(" E_EFFECTS %i", e->effects);
+ if (bits & E_ALPHA)
+ Con_Printf(" E_ALPHA %f", e->alpha / 255.0f);
+ if (bits & E_SCALE)
+ Con_Printf(" E_SCALE %f", e->scale / 16.0f);
+ if (bits & E_COLORMAP)
+ Con_Printf(" E_COLORMAP %i", e->colormap);
+ if (bits & E_SKIN)
+ Con_Printf(" E_SKIN %i", e->skin);
+
+ if (bits & E_GLOWSIZE)
+ Con_Printf(" E_GLOWSIZE %i", e->glowsize * 4);
+ if (bits & E_GLOWCOLOR)
+ Con_Printf(" E_GLOWCOLOR %i", e->glowcolor);
+
+ if (bits & E_LIGHT)
+ Con_Printf(" E_LIGHT %i:%i:%i:%i", e->light[0], e->light[1], e->light[2], e->light[3]);
+ if (bits & E_LIGHTPFLAGS)
+ Con_Printf(" E_LIGHTPFLAGS %i", e->lightpflags);
+
+ if (bits & E_TAGATTACHMENT)
+ Con_Printf(" E_TAGATTACHMENT e%i:%i", e->tagentity, e->tagindex);
+ if (bits & E_LIGHTSTYLE)
+ Con_Printf(" E_LIGHTSTYLE %i", e->lightstyle);
+ Con_Print("\n");
+ }
+}
+
+extern void CL_NewFrameReceived(int num);
+
+// (client and server) allocates a new empty database
+entityframe_database_t *EntityFrame_AllocDatabase(mempool_t *mempool)
+{
+ return (entityframe_database_t *)Mem_Alloc(mempool, sizeof(entityframe_database_t));
+}
+
+// (client and server) frees the database
+void EntityFrame_FreeDatabase(entityframe_database_t *d)
+{
+ Mem_Free(d);
+}
+
+// (server) clears the database to contain no frames (thus delta compression compresses against nothing)
+void EntityFrame_ClearDatabase(entityframe_database_t *d)
+{
+ memset(d, 0, sizeof(*d));
+}
+
+// (server and client) removes frames older than 'frame' from database
+void EntityFrame_AckFrame(entityframe_database_t *d, int frame)
+{
+ int i;
+ d->ackframenum = frame;
+ for (i = 0;i < d->numframes && d->frames[i].framenum < frame;i++);
+ // ignore outdated frame acks (out of order packets)
+ if (i == 0)
+ return;
+ d->numframes -= i;
+ // if some queue is left, slide it down to beginning of array
+ if (d->numframes)
+ memmove(&d->frames[0], &d->frames[i], sizeof(d->frames[0]) * d->numframes);
+}
+
+// (server) clears frame, to prepare for adding entities
+void EntityFrame_Clear(entity_frame_t *f, vec3_t eye, int framenum)
+{
+ f->time = 0;
+ f->framenum = framenum;
+ f->numentities = 0;
+ if (eye == NULL)
+ VectorClear(f->eye);
+ else
+ VectorCopy(eye, f->eye);
+}
+
+// (server and client) reads a frame from the database
+void EntityFrame_FetchFrame(entityframe_database_t *d, int framenum, entity_frame_t *f)
+{
+ int i, n;
+ EntityFrame_Clear(f, NULL, -1);
+ for (i = 0;i < d->numframes && d->frames[i].framenum < framenum;i++);
+ if (i < d->numframes && framenum == d->frames[i].framenum)
+ {
+ f->framenum = framenum;
+ f->numentities = d->frames[i].endentity - d->frames[i].firstentity;
+ n = MAX_ENTITY_DATABASE - (d->frames[i].firstentity % MAX_ENTITY_DATABASE);
+ if (n > f->numentities)
+ n = f->numentities;
+ memcpy(f->entitydata, d->entitydata + d->frames[i].firstentity % MAX_ENTITY_DATABASE, sizeof(*f->entitydata) * n);
+ if (f->numentities > n)
+ memcpy(f->entitydata + n, d->entitydata, sizeof(*f->entitydata) * (f->numentities - n));
+ VectorCopy(d->eye, f->eye);
+ }
+}
+
+// (client) adds a entity_frame to the database, for future reference
+void EntityFrame_AddFrame_Client(entityframe_database_t *d, vec3_t eye, int framenum, int numentities, const entity_state_t *entitydata)
+{
+ int n, e;
+ entity_frameinfo_t *info;
+
+ VectorCopy(eye, d->eye);
+
+ // figure out how many entity slots are used already
+ if (d->numframes)
+ {
+ n = d->frames[d->numframes - 1].endentity - d->frames[0].firstentity;
+ if (n + numentities > MAX_ENTITY_DATABASE || d->numframes >= MAX_ENTITY_HISTORY)
+ {
+ // ran out of room, dump database
+ EntityFrame_ClearDatabase(d);
+ }
+ }
+
+ info = &d->frames[d->numframes];
+ info->framenum = framenum;
+ e = -1000;
+ // make sure we check the newly added frame as well, but we haven't incremented numframes yet
+ for (n = 0;n <= d->numframes;n++)
+ {
+ if (e >= d->frames[n].framenum)
+ {
+ if (e == framenum)
+ Con_Print("EntityFrame_AddFrame: tried to add out of sequence frame to database\n");
+ else
+ Con_Print("EntityFrame_AddFrame: out of sequence frames in database\n");
+ return;
+ }
+ e = d->frames[n].framenum;
+ }
+ // if database still has frames after that...
+ if (d->numframes)
+ info->firstentity = d->frames[d->numframes - 1].endentity;
+ else
+ info->firstentity = 0;
+ info->endentity = info->firstentity + numentities;
+ d->numframes++;
+
+ n = info->firstentity % MAX_ENTITY_DATABASE;
+ e = MAX_ENTITY_DATABASE - n;
+ if (e > numentities)
+ e = numentities;
+ memcpy(d->entitydata + n, entitydata, sizeof(entity_state_t) * e);
+ if (numentities > e)
+ memcpy(d->entitydata, entitydata + e, sizeof(entity_state_t) * (numentities - e));
+}
+
+// (server) adds a entity_frame to the database, for future reference
+void EntityFrame_AddFrame_Server(entityframe_database_t *d, vec3_t eye, int framenum, int numentities, const entity_state_t **entitydata)
+{
+ int n, e;
+ entity_frameinfo_t *info;
+
+ VectorCopy(eye, d->eye);
+
+ // figure out how many entity slots are used already
+ if (d->numframes)
+ {
+ n = d->frames[d->numframes - 1].endentity - d->frames[0].firstentity;
+ if (n + numentities > MAX_ENTITY_DATABASE || d->numframes >= MAX_ENTITY_HISTORY)
+ {
+ // ran out of room, dump database
+ EntityFrame_ClearDatabase(d);
+ }
+ }
+
+ info = &d->frames[d->numframes];
+ info->framenum = framenum;
+ e = -1000;
+ // make sure we check the newly added frame as well, but we haven't incremented numframes yet
+ for (n = 0;n <= d->numframes;n++)
+ {
+ if (e >= d->frames[n].framenum)
+ {
+ if (e == framenum)
+ Con_Print("EntityFrame_AddFrame: tried to add out of sequence frame to database\n");
+ else
+ Con_Print("EntityFrame_AddFrame: out of sequence frames in database\n");
+ return;
+ }
+ e = d->frames[n].framenum;
+ }
+ // if database still has frames after that...
+ if (d->numframes)
+ info->firstentity = d->frames[d->numframes - 1].endentity;
+ else
+ info->firstentity = 0;
+ info->endentity = info->firstentity + numentities;
+ d->numframes++;
+
+ n = info->firstentity % MAX_ENTITY_DATABASE;
+ e = MAX_ENTITY_DATABASE - n;
+ if (e > numentities)
+ e = numentities;
+ memcpy(d->entitydata + n, entitydata, sizeof(entity_state_t) * e);
+ if (numentities > e)
+ memcpy(d->entitydata, entitydata + e, sizeof(entity_state_t) * (numentities - e));
+}
+
+// (server) writes a frame to network stream
+qboolean EntityFrame_WriteFrame(sizebuf_t *msg, int maxsize, entityframe_database_t *d, int numstates, const entity_state_t **states, int viewentnum)
+{
+ int i, onum, number;
+ entity_frame_t *o = &d->deltaframe;
+ const entity_state_t *ent, *delta;
+ vec3_t eye;
+ prvm_eval_t *val;
+
+ d->latestframenum++;
+
+ VectorClear(eye);
+ for (i = 0;i < numstates;i++)
+ {
+ ent = states[i];
+ if (ent->number == viewentnum)
+ {
+ VectorSet(eye, ent->origin[0], ent->origin[1], ent->origin[2] + 22);
+ break;
+ }
+ }
+
+ EntityFrame_AddFrame_Server(d, eye, d->latestframenum, numstates, states);
+
+ EntityFrame_FetchFrame(d, d->ackframenum, o);
+
+ MSG_WriteByte (msg, svc_entities);
+ MSG_WriteLong (msg, o->framenum);
+ MSG_WriteLong (msg, d->latestframenum);
+ MSG_WriteFloat (msg, eye[0]);
+ MSG_WriteFloat (msg, eye[1]);
+ MSG_WriteFloat (msg, eye[2]);
+
+ onum = 0;
+ for (i = 0;i < numstates;i++)
+ {
+ ent = states[i];
+ number = ent->number;
+
+ val = PRVM_EDICTFIELDVALUE((&prog->edicts[number]), prog->fieldoffsets.SendEntity);
+ if(val && val->function)
+ continue;
+ for (;onum < o->numentities && o->entitydata[onum].number < number;onum++)
+ {
+ // write remove message
+ MSG_WriteShort(msg, o->entitydata[onum].number | 0x8000);
+ }
+ if (onum < o->numentities && (o->entitydata[onum].number == number))
+ {
+ // delta from previous frame
+ delta = o->entitydata + onum;
+ // advance to next entity in delta frame
+ onum++;
+ }
+ else
+ {
+ // delta from defaults
+ delta = &defaultstate;
+ }
+ EntityState_WriteUpdate(ent, msg, delta);
+ }
+ for (;onum < o->numentities;onum++)
+ {
+ // write remove message
+ MSG_WriteShort(msg, o->entitydata[onum].number | 0x8000);
+ }
+ MSG_WriteShort(msg, 0xFFFF);
+
+ return true;
+}
+
+// (client) reads a frame from network stream
+void EntityFrame_CL_ReadFrame(void)
+{
+ int i, number, removed;
+ entity_frame_t *f, *delta;
+ entity_state_t *e, *old, *oldend;
+ entity_t *ent;
+ entityframe_database_t *d;
+ if (!cl.entitydatabase)
+ cl.entitydatabase = EntityFrame_AllocDatabase(cls.levelmempool);
+ d = cl.entitydatabase;
+ f = &d->framedata;
+ delta = &d->deltaframe;
+
+ EntityFrame_Clear(f, NULL, -1);
+
+ // read the frame header info
+ f->time = cl.mtime[0];
+ number = MSG_ReadLong();
+ f->framenum = MSG_ReadLong();
+ CL_NewFrameReceived(f->framenum);
+ f->eye[0] = MSG_ReadFloat();
+ f->eye[1] = MSG_ReadFloat();
+ f->eye[2] = MSG_ReadFloat();
+ EntityFrame_AckFrame(d, number);
+ EntityFrame_FetchFrame(d, number, delta);
+ old = delta->entitydata;
+ oldend = old + delta->numentities;
+ // read entities until we hit the magic 0xFFFF end tag
+ while ((number = (unsigned short) MSG_ReadShort()) != 0xFFFF && !msg_badread)
+ {
+ if (msg_badread)
+ Host_Error("EntityFrame_Read: read error");
+ removed = number & 0x8000;
+ number &= 0x7FFF;
+ if (number >= MAX_EDICTS)
+ Host_Error("EntityFrame_Read: number (%i) >= MAX_EDICTS (%i)", number, MAX_EDICTS);
+
+ // seek to entity, while copying any skipped entities (assume unchanged)
+ while (old < oldend && old->number < number)
+ {
+ if (f->numentities >= MAX_ENTITY_DATABASE)
+ Host_Error("EntityFrame_Read: entity list too big");
+ f->entitydata[f->numentities] = *old++;
+ f->entitydata[f->numentities++].time = cl.mtime[0];
+ }
+ if (removed)
+ {
+ if (old < oldend && old->number == number)
+ old++;
+ else
+ Con_Printf("EntityFrame_Read: REMOVE on unused entity %i\n", number);
+ }
+ else
+ {
+ if (f->numentities >= MAX_ENTITY_DATABASE)
+ Host_Error("EntityFrame_Read: entity list too big");
+
+ // reserve this slot
+ e = f->entitydata + f->numentities++;
+
+ if (old < oldend && old->number == number)
+ {
+ // delta from old entity
+ *e = *old++;
+ }
+ else
+ {
+ // delta from defaults
+ *e = defaultstate;
+ }
+
+ if (cl.num_entities <= number)
+ {
+ cl.num_entities = number + 1;
+ if (number >= cl.max_entities)
+ CL_ExpandEntities(number);
+ }
+ cl.entities_active[number] = true;
+ e->active = ACTIVE_NETWORK;
+ e->time = cl.mtime[0];
+ e->number = number;
+ EntityState_ReadFields(e, EntityState_ReadExtendBits());
+ }
+ }
+ while (old < oldend)
+ {
+ if (f->numentities >= MAX_ENTITY_DATABASE)
+ Host_Error("EntityFrame_Read: entity list too big");
+ f->entitydata[f->numentities] = *old++;
+ f->entitydata[f->numentities++].time = cl.mtime[0];
+ }
+ EntityFrame_AddFrame_Client(d, f->eye, f->framenum, f->numentities, f->entitydata);
+
+ memset(cl.entities_active, 0, cl.num_entities * sizeof(unsigned char));
+ number = 1;
+ for (i = 0;i < f->numentities;i++)
+ {
+ for (;number < f->entitydata[i].number && number < cl.num_entities;number++)
+ {
+ if (cl.entities_active[number])
+ {
+ cl.entities_active[number] = false;
+ cl.entities[number].state_current.active = ACTIVE_NOT;
+ }
+ }
+ if (number >= cl.num_entities)
+ break;
+ // update the entity
+ ent = &cl.entities[number];
+ ent->state_previous = ent->state_current;
+ ent->state_current = f->entitydata[i];
+ CL_MoveLerpEntityStates(ent);
+ // the entity lives again...
+ cl.entities_active[number] = true;
+ number++;
+ }
+ for (;number < cl.num_entities;number++)
+ {
+ if (cl.entities_active[number])
+ {
+ cl.entities_active[number] = false;
+ cl.entities[number].state_current.active = ACTIVE_NOT;
+ }
+ }
+}
+
+
+// (client) returns the frame number of the most recent frame recieved
+int EntityFrame_MostRecentlyRecievedFrameNum(entityframe_database_t *d)
+{