+ int number;
+ const char *name;
+}
+protocolversioninfo[] =
+{
+ {0, "UNKNOWN"},
+ {3504, "DP7"},
+ {3503, "DP6"},
+ {3502, "DP5"},
+ {3501, "DP4"},
+ {3500, "DP3"},
+ {97, "DP2"},
+ {96, "DP1"},
+ {15, "QUAKEDP"},
+ {250, "NEHAHRAMOVIE"},
+ {15, "QUAKE"},
+ {28, "QW"},
+ {0, NULL}
+};
+
+protocolversion_t Protocol_EnumForName(const char *s)
+{
+ int i;
+ for (i = 1;protocolversioninfo[i].name;i++)
+ if (!strcasecmp(s, protocolversioninfo[i].name))
+ return (protocolversion_t)i;
+ return PROTOCOL_UNKNOWN;
+}
+
+const char *Protocol_NameForEnum(protocolversion_t p)
+{
+ return protocolversioninfo[p].name;
+}
+
+protocolversion_t Protocol_EnumForNumber(int n)
+{
+ int i;
+ for (i = 1;protocolversioninfo[i].name;i++)
+ if (protocolversioninfo[i].number == n)
+ return (protocolversion_t)i;
+ return PROTOCOL_UNKNOWN;
+}
+
+int Protocol_NumberForEnum(protocolversion_t p)
+{
+ return protocolversioninfo[p].number;
+}
+
+void Protocol_Names(char *buffer, size_t buffersize)
+{
+ int i;
+ if (buffersize < 1)
+ return;
+ buffer[0] = 0;
+ for (i = 1;protocolversioninfo[i].name;i++)
+ {
+ if (i > 1)
+ strlcat(buffer, " ", buffersize);
+ strlcat(buffer, protocolversioninfo[i].name, buffersize);
+ }
+}
+
+void EntityFrameQuake_ReadEntity(int bits)
+{
+ int num;
+ entity_t *ent;
+ entity_state_t s;
+
+ if (bits & U_MOREBITS)
+ bits |= (MSG_ReadByte()<<8);
+ if ((bits & U_EXTEND1) && cls.protocol != PROTOCOL_NEHAHRAMOVIE)
+ {
+ bits |= MSG_ReadByte() << 16;
+ if (bits & U_EXTEND2)
+ bits |= MSG_ReadByte() << 24;
+ }
+
+ if (bits & U_LONGENTITY)
+ num = (unsigned short) MSG_ReadShort ();
+ else
+ num = MSG_ReadByte ();
+
+ if (num >= MAX_EDICTS)
+ Host_Error("EntityFrameQuake_ReadEntity: entity number (%i) >= MAX_EDICTS (%i)", num, MAX_EDICTS);
+ if (num < 1)
+ Host_Error("EntityFrameQuake_ReadEntity: invalid entity number (%i)", num);
+
+ if (cl.num_entities <= num)
+ {
+ cl.num_entities = num + 1;
+ if (num >= cl.max_entities)
+ CL_ExpandEntities(num);
+ }
+
+ ent = cl.entities + num;
+
+ // note: this inherits the 'active' state of the baseline chosen
+ // (state_baseline is always active, state_current may not be active if
+ // the entity was missing in the last frame)
+ if (bits & U_DELTA)
+ s = ent->state_current;
+ else
+ {
+ s = ent->state_baseline;
+ s.active = true;
+ }
+
+ cl.isquakeentity[num] = true;
+ if (cl.lastquakeentity < num)
+ cl.lastquakeentity = num;
+ s.number = num;
+ s.time = cl.mtime[0];
+ s.flags = 0;
+ if (bits & U_MODEL) s.modelindex = (s.modelindex & 0xFF00) | MSG_ReadByte();
+ if (bits & U_FRAME) s.frame = (s.frame & 0xFF00) | MSG_ReadByte();
+ if (bits & U_COLORMAP) s.colormap = MSG_ReadByte();
+ if (bits & U_SKIN) s.skin = MSG_ReadByte();
+ if (bits & U_EFFECTS) s.effects = (s.effects & 0xFF00) | MSG_ReadByte();
+ if (bits & U_ORIGIN1) s.origin[0] = MSG_ReadCoord(cls.protocol);
+ if (bits & U_ANGLE1) s.angles[0] = MSG_ReadAngle(cls.protocol);
+ if (bits & U_ORIGIN2) s.origin[1] = MSG_ReadCoord(cls.protocol);
+ if (bits & U_ANGLE2) s.angles[1] = MSG_ReadAngle(cls.protocol);
+ if (bits & U_ORIGIN3) s.origin[2] = MSG_ReadCoord(cls.protocol);
+ if (bits & U_ANGLE3) s.angles[2] = MSG_ReadAngle(cls.protocol);
+ if (bits & U_STEP) s.flags |= RENDER_STEP;
+ if (bits & U_ALPHA) s.alpha = MSG_ReadByte();
+ if (bits & U_SCALE) s.scale = MSG_ReadByte();
+ if (bits & U_EFFECTS2) s.effects = (s.effects & 0x00FF) | (MSG_ReadByte() << 8);
+ if (bits & U_GLOWSIZE) s.glowsize = MSG_ReadByte();
+ if (bits & U_GLOWCOLOR) s.glowcolor = MSG_ReadByte();
+ if (bits & U_COLORMOD) {int c = MSG_ReadByte();s.colormod[0] = (unsigned char)(((c >> 5) & 7) * (32.0f / 7.0f));s.colormod[1] = (unsigned char)(((c >> 2) & 7) * (32.0f / 7.0f));s.colormod[2] = (unsigned char)((c & 3) * (32.0f / 3.0f));}
+ if (bits & U_GLOWTRAIL) s.flags |= RENDER_GLOWTRAIL;
+ if (bits & U_FRAME2) s.frame = (s.frame & 0x00FF) | (MSG_ReadByte() << 8);
+ if (bits & U_MODEL2) s.modelindex = (s.modelindex & 0x00FF) | (MSG_ReadByte() << 8);
+ if (bits & U_VIEWMODEL) s.flags |= RENDER_VIEWMODEL;
+ if (bits & U_EXTERIORMODEL) s.flags |= RENDER_EXTERIORMODEL;
+
+ // LordHavoc: to allow playback of the Nehahra movie
+ if (cls.protocol == PROTOCOL_NEHAHRAMOVIE && (bits & U_EXTEND1))
+ {
+ // LordHavoc: evil format
+ int i = (int)MSG_ReadFloat();
+ int j = (int)(MSG_ReadFloat() * 255.0f);
+ if (i == 2)
+ {
+ i = (int)MSG_ReadFloat();
+ if (i)
+ s.effects |= EF_FULLBRIGHT;
+ }
+ if (j < 0)
+ s.alpha = 0;
+ else if (j == 0 || j >= 255)
+ s.alpha = 255;
+ else
+ s.alpha = j;
+ }
+
+ ent->state_previous = ent->state_current;
+ ent->state_current = s;
+ if (ent->state_current.active)
+ {
+ CL_MoveLerpEntityStates(ent);
+ cl.entities_active[ent->state_current.number] = true;
+ }
+
+ if (msg_badread)
+ Host_Error("EntityFrameQuake_ReadEntity: read error");
+}
+
+void EntityFrameQuake_ISeeDeadEntities(void)
+{
+ int num, lastentity;
+ if (cl.lastquakeentity == 0)
+ return;
+ lastentity = cl.lastquakeentity;
+ cl.lastquakeentity = 0;
+ for (num = 0;num <= lastentity;num++)
+ {
+ if (cl.isquakeentity[num])
+ {
+ if (cl.entities_active[num] && cl.entities[num].state_current.time == cl.mtime[0])
+ {
+ cl.isquakeentity[num] = true;
+ cl.lastquakeentity = num;
+ }
+ else
+ {
+ cl.isquakeentity[num] = false;
+ cl.entities_active[num] = false;
+ cl.entities[num].state_current = defaultstate;
+ cl.entities[num].state_current.number = num;
+ }
+ }
+ }
+}
+
+// FIXME FIXME FIXME: at this time the CSQC entity writing does not store
+// packet logs and thus if an update is lost it is never repeated, this makes
+// csqc entities useless at the moment.
+
+void EntityFrameCSQC_WriteState (sizebuf_t *msg, int number, qboolean doupdate, qboolean *sectionstarted)
+{
+ int version;
+ prvm_eval_t *val, *val2;
+ version = 0;
+ if (doupdate)
+ {
+ if (msg->cursize + !*sectionstarted + 2 + 1 + 2 > msg->maxsize)
+ return;
+ val2 = PRVM_EDICTFIELDVALUE((&prog->edicts[number]), prog->fieldoffsets.Version);
+ version = (int)val2->_float;
+ // LordHavoc: do some validity checks on self.Version
+ // if self.Version reaches 255, it will soon exceed the byte used to
+ // store an entity version in the client struct, so we need to reset
+ // all the version to 1 and force all the existing clients' version of
+ // it to 255 (which we're not allowing to actually occur)
+ if (version < 0)
+ val2->_float = 0;
+ if (version >= 255)
+ {
+ int i;
+ val2->_float = version = 1;
+ // since we just reset the Version field to 1, it may accidentally
+ // end up being equal to an existing client version now or in the
+ // future, so to fix this situation we have to loop over all
+ // clients and change their versions for this entity to be -1
+ // which never matches, thus causing them to receive the update
+ // soon, as they should
+ for (i = 0;i < svs.maxclients;i++)
+ if (svs.clients[sv.writeentitiestoclient_clientnumber].csqcentityversion[number])
+ svs.clients[sv.writeentitiestoclient_clientnumber].csqcentityversion[number] = 255;
+ }
+ }
+ // if the version already matches, we don't need to do anything as the
+ // latest version has already been sent.
+ if (svs.clients[sv.writeentitiestoclient_clientnumber].csqcentityversion[number] == version)
+ return;
+ if (version)
+ {
+ // if there's no SendEntity function, treat it as a remove
+ val = PRVM_EDICTFIELDVALUE((&prog->edicts[number]), prog->fieldoffsets.SendEntity);
+ if (val->function)
+ {
+ // there is a function to call, save the cursize value incase we
+ // have to do a rollback due to overflow
+ int oldcursize = msg->cursize;
+ if(!*sectionstarted)
+ MSG_WriteByte(msg, svc_csqcentities);
+ MSG_WriteShort(msg, number);
+ ((int *)prog->globals.generic)[OFS_PARM0] = sv.writeentitiestoclient_cliententitynumber;
+ prog->globals.server->self = number;
+ PRVM_ExecuteProgram(val->function, "Null SendEntity\n");
+ if(prog->globals.generic[OFS_RETURN])
+ {
+ if (msg->cursize + 2 > msg->maxsize)
+ {
+ // if the packet no longer has enough room to write the
+ // final index code that ends the message, rollback to the
+ // state before we tried to write anything and then return
+ msg->cursize = oldcursize;
+ msg->overflowed = false;
+ return;
+ }
+ // an update has been successfully written, update the version
+ svs.clients[sv.writeentitiestoclient_clientnumber].csqcentityversion[number] = version;
+ // and take note that we have begun the svc_csqcentities
+ // section of the packet
+ *sectionstarted = 1;
+ return;
+ }
+ else
+ {
+ // rollback the buffer to its state before the writes
+ msg->cursize = oldcursize;
+ // if the function returned FALSE, simply write a remove
+ // this is done by falling through to the remove code below
+ version = 0;
+ }
+ }
+ }
+ // write a remove message if needed
+ // if already removed, do nothing
+ if (!svs.clients[sv.writeentitiestoclient_clientnumber].csqcentityversion[number])
+ return;
+ // if there isn't enough room to write the remove message, just return, as
+ // it will be handled in a later packet
+ if (msg->cursize + !*sectionstarted + 2 + 2 > msg->maxsize)
+ return;
+ // first write the message identifier if needed
+ if(!*sectionstarted)
+ {
+ *sectionstarted = 1;
+ MSG_WriteByte(msg, svc_csqcentities);
+ }
+ // write the remove message
+ MSG_WriteShort(msg, (unsigned short)number | 0x8000);
+ svs.clients[sv.writeentitiestoclient_clientnumber].csqcentityversion[number] = 0;
+}
+
+//[515]: we use only one array per-client for SendEntity feature
+void EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int numstates, const entity_state_t *states)
+{
+ int i, num;
+ qboolean sectionstarted = false;
+ const entity_state_t *n;
+
+ // if this server progs is not CSQC-aware, return early
+ if(prog->fieldoffsets.SendEntity < 0 || prog->fieldoffsets.Version < 0)
+ return;
+ // 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 >= msg->maxsize)
+ return;
+
+ num = 1;
+ for (i = 0, n = states;i < numstates;i++, n++)
+ {
+ // all entities between the previous entity state and this one are dead
+ for (;num < n->number;num++)
+ if(svs.clients[sv.writeentitiestoclient_clientnumber].csqcentityversion[num])
+ EntityFrameCSQC_WriteState(msg, num, false, §ionstarted);
+ // update this entity
+ EntityFrameCSQC_WriteState(msg, num, true, §ionstarted);
+ // advance to next entity so the next iteration doesn't immediately remove it
+ num++;
+ }
+ // all remaining entities are dead
+ for (;num < prog->num_edicts;num++)
+ if(svs.clients[sv.writeentitiestoclient_clientnumber].csqcentityversion[num])
+ EntityFrameCSQC_WriteState(msg, num, false, §ionstarted);
+ if (sectionstarted)
+ {
+ // write index 0 to end the update (0 is never used by real entities)
+ MSG_WriteShort(msg, 0);
+ }
+}
+
+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]);
+ }
+ }
+ }
+}
+
+
+void EntityFrameQuake_WriteFrame(sizebuf_t *msg, 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;
+
+ // prepare the buffer
+ memset(&buf, 0, sizeof(buf));
+ buf.data = data;
+ buf.maxsize = sizeof(data);
+
+ for (i = 0, s = states;i < numstates;i++, s++)
+ {
+ 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)
+ 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) 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 > msg->maxsize)
+ {
+ // next frame we will continue where we left off
+ break;
+ }
+ // write the message to the packet
+ SZ_Write(msg, buf.data, buf.cursize);
+ }