mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2025-02-08 16:52:16 +00:00
- add MAX_NET_EDICTS and MAX_NET_EDICTS_MASK defines, which are 512 and 511 respectively - change baselines to access the array directly, rather than through the entity's "data" field - cleanup SV_ReliableSVC_Emit - add entity remapping. the entity number used internally in the server no longer matches the number sent to the client, and it releases the mapping after 10 seconds of inuse, so there's no "512 entity limit" anymore. Still the MAX_EDICTS limit though, which is currently 768, but it can probably be defined to something much higher without any trouble.
613 lines
15 KiB
C
613 lines
15 KiB
C
/*
|
|
sv_ents.c
|
|
|
|
(description)
|
|
|
|
Copyright (C) 1996-1997 Id Software, Inc.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to:
|
|
|
|
Free Software Foundation, Inc.
|
|
59 Temple Place - Suite 330
|
|
Boston, MA 02111-1307, USA
|
|
|
|
*/
|
|
static const char rcsid[] =
|
|
"$Id$";
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
# include <string.h>
|
|
#endif
|
|
#ifdef HAVE_STRINGS_H
|
|
# include <strings.h>
|
|
#endif
|
|
|
|
#include "QF/msg.h"
|
|
#include "QF/net_svc_qw.h"
|
|
#include "QF/sys.h"
|
|
|
|
#include "compat.h"
|
|
#include "server.h"
|
|
#include "sv_progs.h"
|
|
|
|
|
|
/*
|
|
The PVS must include a small area around the client to allow head
|
|
bobbing or other small motion on the client side. Otherwise, a bob
|
|
might cause an entity that should be visible to not show up, especially
|
|
when the bob crosses a waterline.
|
|
*/
|
|
|
|
byte fatpvs[MAX_MAP_LEAFS / 8];
|
|
int fatbytes;
|
|
|
|
|
|
void
|
|
SV_AddToFatPVS (vec3_t org, mnode_t *node)
|
|
{
|
|
byte *pvs;
|
|
int i;
|
|
float d;
|
|
mplane_t *plane;
|
|
|
|
while (1) {
|
|
// if this is a leaf, accumulate the pvs bits
|
|
if (node->contents < 0) {
|
|
if (node->contents != CONTENTS_SOLID) {
|
|
pvs = Mod_LeafPVS ((mleaf_t *) node, sv.worldmodel);
|
|
for (i = 0; i < fatbytes; i++)
|
|
fatpvs[i] |= pvs[i];
|
|
}
|
|
return;
|
|
}
|
|
|
|
plane = node->plane;
|
|
d = DotProduct (org, plane->normal) - plane->dist;
|
|
if (d > 8)
|
|
node = node->children[0];
|
|
else if (d < -8)
|
|
node = node->children[1];
|
|
else { // go down both
|
|
SV_AddToFatPVS (org, node->children[0]);
|
|
node = node->children[1];
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
SV_FatPVS
|
|
|
|
Calculates a PVS that is the inclusive or of all leafs within 8 pixels
|
|
of the given point.
|
|
*/
|
|
byte *
|
|
SV_FatPVS (vec3_t org)
|
|
{
|
|
fatbytes = (sv.worldmodel->numleafs + 31) >> 3;
|
|
memset (fatpvs, 0, fatbytes);
|
|
SV_AddToFatPVS (org, sv.worldmodel->nodes);
|
|
return fatpvs;
|
|
}
|
|
|
|
// nails are plentiful, so there is a special network protocol for them
|
|
#define MAX_NAILS 32
|
|
edict_t *nails[MAX_NAILS];
|
|
int numnails;
|
|
|
|
|
|
qboolean
|
|
SV_AddNailUpdate (edict_t *ent)
|
|
{
|
|
if (SVfloat (ent, modelindex) != sv_nailmodel
|
|
&& SVfloat (ent, modelindex) != sv_supernailmodel) return false;
|
|
if (numnails == MAX_NAILS)
|
|
return true;
|
|
nails[numnails] = ent;
|
|
numnails++;
|
|
return true;
|
|
}
|
|
|
|
void
|
|
SV_EmitNailUpdate (sizebuf_t *msg)
|
|
{
|
|
int i;
|
|
net_svc_nails_t block;
|
|
|
|
if (!numnails)
|
|
return;
|
|
|
|
block.numnails = numnails;
|
|
|
|
for (i = 0; i < numnails; i++) {
|
|
VectorCopy (SVvector (nails[i], origin), block.nails[i].origin);
|
|
VectorCopy (SVvector (nails[i], angles), block.nails[i].angles);
|
|
}
|
|
|
|
NET_SVC_Emit (svc_nails, &block, msg);
|
|
}
|
|
|
|
unsigned int
|
|
SV_EntityState_Diff (entity_state_t *from, entity_state_t *to)
|
|
{
|
|
int i;
|
|
float miss;
|
|
unsigned int bits = 0;
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
miss = to->origin[i] - from->origin[i];
|
|
if (miss < -0.1 || miss > 0.1)
|
|
bits |= U_ORIGIN1 << i;
|
|
}
|
|
|
|
if (to->angles[0] != from->angles[0])
|
|
bits |= U_ANGLE1;
|
|
|
|
if (to->angles[1] != from->angles[1])
|
|
bits |= U_ANGLE2;
|
|
|
|
if (to->angles[2] != from->angles[2])
|
|
bits |= U_ANGLE3;
|
|
|
|
if (to->colormap != from->colormap)
|
|
bits |= U_COLORMAP;
|
|
|
|
if (to->skinnum != from->skinnum)
|
|
bits |= U_SKIN;
|
|
|
|
if (to->frame != from->frame)
|
|
bits |= U_FRAME;
|
|
|
|
if (to->effects != from->effects)
|
|
bits |= U_EFFECTS;
|
|
|
|
if (to->modelindex != from->modelindex)
|
|
bits |= U_MODEL;
|
|
|
|
// LordHavoc: cleaned up Endy's coding style, and added missing effects
|
|
// Ender (QSG - Begin)
|
|
// if (stdver > 1) {
|
|
if (to->alpha != from->alpha)
|
|
bits |= U_ALPHA;
|
|
|
|
if (to->scale != from->scale)
|
|
bits |= U_SCALE;
|
|
|
|
if (to->glow_size != from->glow_size)
|
|
bits |= U_GLOWSIZE;
|
|
|
|
if (to->glow_color != from->glow_color)
|
|
bits |= U_GLOWCOLOR;
|
|
|
|
if (to->colormod != from->colormod)
|
|
bits |= U_COLORMOD;
|
|
// }
|
|
|
|
// if (bits >= 16777216)
|
|
if (bits & U_GROUP_EXTEND2)
|
|
bits |= U_EXTEND2;
|
|
|
|
// if (bits >= 65536)
|
|
if (bits & U_GROUP_EXTEND1)
|
|
bits |= U_EXTEND1;
|
|
// Ender (QSG - End)
|
|
|
|
// if (bits & 511)
|
|
if (bits & U_GROUP_MOREBITS)
|
|
bits |= U_MOREBITS;
|
|
|
|
if (to->flags & U_SOLID)
|
|
bits |= U_SOLID;
|
|
|
|
return bits;
|
|
}
|
|
|
|
/*
|
|
SV_EmitPacketEntities
|
|
|
|
Writes an update of a packet_entities_t to the message.
|
|
*/
|
|
void
|
|
SV_EmitPacketEntities (client_t *client, packet_entities_t *to, sizebuf_t *msg)
|
|
{
|
|
int index;
|
|
entity_state_t *baseline;
|
|
net_svc_packetentities_t block;
|
|
|
|
block.numwords = block.numdeltas = to->num_entities;
|
|
|
|
for (index = 0; index < to->num_entities; index++) {
|
|
baseline = &baselines[to->entities[index].number];
|
|
block.deltas[index] = to->entities[index];
|
|
block.deltas[index].flags =
|
|
SV_EntityState_Diff (baseline, &to->entities[index]);
|
|
|
|
// check if it's a client that doesn't support QSG2
|
|
if (client->stdver <= 1)
|
|
block.deltas[index].flags &= U_VERSION_ID;
|
|
|
|
block.words[index] = to->entities[index].number |
|
|
(block.deltas[index].flags & ~MAX_NET_EDICTS_MASK);
|
|
}
|
|
|
|
block.words[index] = 0;
|
|
NET_SVC_Emit (svc_packetentities, &block, msg);
|
|
}
|
|
|
|
/*
|
|
SV_EmitDeltaPacketEntities
|
|
|
|
Writes a delta update of a packet_entities_t to the message.
|
|
*/
|
|
void
|
|
SV_EmitDeltaPacketEntities (client_t *client, packet_entities_t *to,
|
|
sizebuf_t *msg)
|
|
{
|
|
int newindex, oldindex, newnum, oldnum;
|
|
int word, delta;
|
|
entity_state_t *baseline;
|
|
packet_entities_t *from;
|
|
net_svc_deltapacketentities_t block;
|
|
|
|
// this is the frame that we are going to delta update from
|
|
from = &client->frames[client->delta_sequence & UPDATE_MASK].entities;
|
|
|
|
block.from = client->delta_sequence;
|
|
|
|
// SV_Printf ("---%i to %i ----\n", client->delta_sequence & UPDATE_MASK,
|
|
// client->netchan.outgoing_sequence & UPDATE_MASK);
|
|
for (newindex = 0, oldindex = 0, word = 0, delta = 0;
|
|
newindex < to->num_entities || oldindex < from->num_entities;) {
|
|
newnum = newindex >= to->num_entities ?
|
|
9999 : to->entities[newindex].number;
|
|
oldnum = oldindex >= from->num_entities ?
|
|
9999 : from->entities[oldindex].number;
|
|
|
|
if (newnum == oldnum) { // delta update from old position
|
|
// SV_Printf ("delta %i\n", newnum);
|
|
block.deltas[delta] = to->entities[newindex];
|
|
block.deltas[delta].flags =
|
|
SV_EntityState_Diff (&from->entities[oldindex],
|
|
&to->entities[newindex]);
|
|
|
|
// check if it's a client that doesn't support QSG2
|
|
if (client->stdver <= 1)
|
|
block.deltas[delta].flags &= U_VERSION_ID;
|
|
|
|
if (!block.deltas[delta].flags) { // no updates to send
|
|
oldindex++;
|
|
newindex++;
|
|
continue;
|
|
}
|
|
|
|
block.words[word] = newnum |
|
|
(block.deltas[delta].flags & ~MAX_NET_EDICTS_MASK);
|
|
|
|
oldindex++;
|
|
newindex++;
|
|
word++;
|
|
delta++;
|
|
} else if (newnum < oldnum) { // this is a new entity, send
|
|
// it from the baseline
|
|
baseline = &baselines[newnum];
|
|
// SV_Printf ("baseline %i\n", newnum);
|
|
block.deltas[delta] = to->entities[newindex];
|
|
block.deltas[delta].flags =
|
|
SV_EntityState_Diff (baseline, &to->entities[newindex]);
|
|
|
|
// check if it's a client that doesn't support QSG2
|
|
if (client->stdver <= 1)
|
|
block.deltas[delta].flags &= U_VERSION_ID;
|
|
|
|
block.words[word] = newnum |
|
|
(block.deltas[delta].flags & ~MAX_NET_EDICTS_MASK);
|
|
|
|
newindex++;
|
|
word++;
|
|
delta++;
|
|
} else if (newnum > oldnum) { // the old entity isn't
|
|
// present in the new message
|
|
// SV_Printf ("remove %i\n", oldnum);
|
|
block.words[word] = oldnum | U_REMOVE;
|
|
oldindex++;
|
|
word++;
|
|
}
|
|
}
|
|
|
|
block.words[word] = 0;
|
|
NET_SVC_Emit (svc_deltapacketentities, &block, msg);
|
|
}
|
|
|
|
void
|
|
SV_WritePlayersToClient (client_t *client, edict_t *clent, byte * pvs,
|
|
sizebuf_t *msg)
|
|
{
|
|
int i, j;
|
|
client_t *cl;
|
|
edict_t *ent;
|
|
net_svc_playerinfo_t block;
|
|
|
|
for (j = 0, cl = svs.clients; j < MAX_CLIENTS; j++, cl++) {
|
|
if (cl->state != cs_spawned)
|
|
continue;
|
|
|
|
ent = cl->edict;
|
|
|
|
// ZOID visibility tracking
|
|
if (ent != clent &&
|
|
!(client->spec_track && client->spec_track - 1 == j)) {
|
|
if (cl->spectator)
|
|
continue;
|
|
|
|
// ignore if not touching a PV leaf
|
|
for (i = 0; i < ent->num_leafs; i++)
|
|
if (pvs[ent->leafnums[i] >> 3] & (1 << (ent->leafnums[i] & 7)))
|
|
break;
|
|
if (i == ent->num_leafs)
|
|
continue; // not visible
|
|
}
|
|
|
|
block.flags = PF_MSEC | PF_COMMAND;
|
|
|
|
if (SVfloat (ent, modelindex) != sv_playermodel)
|
|
block.flags |= PF_MODEL;
|
|
for (i = 0; i < 3; i++)
|
|
if (SVvector (ent, velocity)[i])
|
|
block.flags |= PF_VELOCITY1 << i;
|
|
if (SVfloat (ent, effects))
|
|
block.flags |= PF_EFFECTS;
|
|
if (SVfloat (ent, skin))
|
|
block.flags |= PF_SKINNUM;
|
|
if (SVfloat (ent, health) <= 0)
|
|
block.flags |= PF_DEAD;
|
|
if (SVvector (ent, mins)[2] != -24)
|
|
block.flags |= PF_GIB;
|
|
|
|
if (cl->spectator) { // only sent origin and velocity to
|
|
// spectators
|
|
block.flags &= PF_VELOCITY1 | PF_VELOCITY2 | PF_VELOCITY3;
|
|
} else if (ent == clent) { // don't send a lot of data on
|
|
// personal entity
|
|
block.flags &= ~(PF_MSEC | PF_COMMAND);
|
|
if (SVfloat (ent, weaponframe))
|
|
block.flags |= PF_WEAPONFRAME;
|
|
}
|
|
|
|
if (client->spec_track && client->spec_track - 1 == j &&
|
|
SVfloat (ent, weaponframe))
|
|
block.flags |= PF_WEAPONFRAME;
|
|
|
|
block.playernum = j;
|
|
|
|
VectorCopy (SVvector (ent, origin), block.origin);
|
|
block.frame = SVfloat (ent, frame);
|
|
|
|
block.msec = 1000 * (sv.time - cl->localtime);
|
|
if (block.msec > 255)
|
|
block.msec = 255;
|
|
|
|
block.usercmd = cl->lastcmd;
|
|
if (SVfloat (ent, health) <= 0) { // don't show the corpse
|
|
// looking around...
|
|
block.usercmd.angles[0] = 0;
|
|
block.usercmd.angles[1] = SVvector (ent, angles)[1];
|
|
block.usercmd.angles[0] = 0;
|
|
}
|
|
block.usercmd.buttons = 0; // never send buttons
|
|
block.usercmd.impulse = 0; // never send impulses
|
|
|
|
VectorCopy (SVvector (ent, velocity), block.velocity);
|
|
block.modelindex = SVfloat (ent, modelindex);
|
|
block.skinnum = SVfloat (ent, skin);
|
|
block.effects = SVfloat (ent, effects);
|
|
block.weaponframe = SVfloat (ent, weaponframe);
|
|
|
|
NET_SVC_Emit (svc_playerinfo, &block, msg);
|
|
}
|
|
}
|
|
|
|
/*
|
|
SV_WriteEntitiesToClient
|
|
|
|
Encodes the current state of the world as
|
|
a svc_packetentities messages and possibly
|
|
a svc_nails message and
|
|
svc_playerinfo messages
|
|
*/
|
|
void
|
|
SV_WriteEntitiesToClient (client_t *client, sizebuf_t *msg)
|
|
{
|
|
byte *pvs;
|
|
int e, i;
|
|
vec3_t org;
|
|
client_frame_t *frame;
|
|
edict_t *clent, *ent;
|
|
entity_state_t *state;
|
|
packet_entities_t *pack;
|
|
int searchent, netnum;
|
|
|
|
// this is the frame we are creating
|
|
frame = &client->frames[client->netchan.incoming_sequence & UPDATE_MASK];
|
|
|
|
// find the client's PVS
|
|
clent = client->edict;
|
|
VectorAdd (SVvector (clent, origin), SVvector (clent, view_ofs), org);
|
|
pvs = SV_FatPVS (org);
|
|
|
|
// send over the players in the PVS
|
|
SV_WritePlayersToClient (client, clent, pvs, msg);
|
|
|
|
// put other visible entities into either a packet_entities or a nails
|
|
// message
|
|
pack = &frame->entities;
|
|
pack->num_entities = 0;
|
|
|
|
numnails = 0;
|
|
|
|
for (searchent = netnum = MAX_CLIENTS + 1;
|
|
netnum < MAX_NET_EDICTS; netnum++) {
|
|
e = SV_EntMap_LookupByNetwork (&client->entmap, netnum);
|
|
|
|
if (e == ENTMAP_INVALID) {
|
|
// try to find an entity to map here
|
|
for (; searchent < sv.num_edicts; searchent++) {
|
|
ent = EDICT_NUM (&sv_pr_state, searchent);
|
|
if (ent->free)
|
|
continue;
|
|
|
|
// ignore ents without visible models
|
|
if (!SVfloat (ent, modelindex)
|
|
|| !*PR_GetString (&sv_pr_state, SVstring (ent, model)))
|
|
continue;
|
|
|
|
// ignore if not touching a PV leaf
|
|
for (i = 0; i < ent->num_leafs; i++)
|
|
if (pvs[ent->leafnums[i] >> 3] & (1 << (ent->leafnums[i] & 7)))
|
|
break;
|
|
|
|
if (i == ent->num_leafs)
|
|
continue; // not visible
|
|
|
|
if (SV_AddNailUpdate (ent))
|
|
continue; // added to the special update list
|
|
if (SV_EntMap_LookupByInternal (&client->entmap,
|
|
searchent) != ENTMAP_INVALID)
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
|
|
if (searchent == sv.num_edicts)
|
|
continue; // no entity to map
|
|
|
|
SV_EntMap_Add (&client->entmap, searchent, netnum);
|
|
e = searchent;
|
|
}
|
|
|
|
ent = EDICT_NUM (&sv_pr_state, e);
|
|
|
|
if (ent->free)
|
|
continue;
|
|
|
|
// ignore ents without visible models
|
|
if (!SVfloat (ent, modelindex)
|
|
|| !*PR_GetString (&sv_pr_state, SVstring (ent, model)))
|
|
continue;
|
|
|
|
// ignore if not touching a PV leaf
|
|
for (i = 0; i < ent->num_leafs; i++)
|
|
if (pvs[ent->leafnums[i] >> 3] & (1 << (ent->leafnums[i] & 7)))
|
|
break;
|
|
|
|
if (i == ent->num_leafs)
|
|
continue; // not visible
|
|
|
|
if (SV_AddNailUpdate (ent))
|
|
continue; // added to the special update list
|
|
|
|
// add to the packetentities
|
|
if (pack->num_entities == MAX_PACKET_ENTITIES)
|
|
continue; // all full
|
|
|
|
SV_EntMap_Touch (&client->entmap, netnum);
|
|
|
|
state = &pack->entities[pack->num_entities];
|
|
pack->num_entities++;
|
|
|
|
state->number = netnum;
|
|
state->flags = 0;
|
|
VectorCopy (SVvector (ent, origin), state->origin);
|
|
VectorCopy (SVvector (ent, angles), state->angles);
|
|
state->modelindex = SVfloat (ent, modelindex);
|
|
state->frame = SVfloat (ent, frame);
|
|
state->colormap = SVfloat (ent, colormap);
|
|
state->skinnum = SVfloat (ent, skin);
|
|
state->effects = SVfloat (ent, effects);
|
|
|
|
// LordHavoc: cleaned up Endy's coding style, shortened the code,
|
|
// and implemented missing effects
|
|
// Ender: EXTEND (QSG - Begin)
|
|
{
|
|
state->alpha = 255;
|
|
state->scale = 16;
|
|
state->glow_size = 0;
|
|
state->glow_color = 254;
|
|
state->colormod = 255;
|
|
|
|
if (sv_fields.alpha != -1 && SVfloat (ent, alpha))
|
|
state->alpha = bound (0, SVfloat (ent, alpha), 1) * 255.0;
|
|
|
|
if (sv_fields.scale != -1 && SVfloat (ent, scale))
|
|
state->scale = bound (0, SVfloat (ent, scale), 15.9375) * 16.0;
|
|
|
|
if (sv_fields.glow_size != -1 && SVfloat (ent, glow_size))
|
|
state->glow_size = bound (-1024, (int) SVfloat
|
|
(ent, glow_size), 1016) >> 3;
|
|
|
|
if (sv_fields.glow_color != -1 && SVvector (ent, glow_color))
|
|
state->glow_color = (int) SVvector (ent, glow_color);
|
|
|
|
if (sv_fields.colormod != -1
|
|
&& SVvector (ent, colormod)[0]
|
|
&& SVvector (ent, colormod)[1]
|
|
&& SVvector (ent, colormod)[2])
|
|
state->colormod =
|
|
((int) (bound (0, SVvector (ent, colormod)[0], 1) * 7.0)
|
|
<< 5) |
|
|
((int) (bound (0, SVvector (ent, colormod)[1], 1) * 7.0)
|
|
<< 2) |
|
|
(int) (bound (0, SVvector (ent, colormod)[2], 1) * 3.0);
|
|
}
|
|
// Ender: EXTEND (QSG - End)
|
|
}
|
|
|
|
// handle any missing nails
|
|
for (; searchent < sv.num_edicts; searchent++) {
|
|
ent = EDICT_NUM (&sv_pr_state, searchent);
|
|
if (ent->free)
|
|
continue;
|
|
|
|
// ignore ents without visible models
|
|
if (!SVfloat (ent, modelindex)
|
|
|| !*PR_GetString (&sv_pr_state, SVstring (ent, model)))
|
|
continue;
|
|
|
|
// ignore if not touching a PV leaf
|
|
for (i = 0; i < ent->num_leafs; i++)
|
|
if (pvs[ent->leafnums[i] >> 3] & (1 << (ent->leafnums[i] & 7)))
|
|
break;
|
|
|
|
if (i == ent->num_leafs)
|
|
continue; // not visible
|
|
|
|
if (SV_EntMap_LookupByInternal (&client->entmap,
|
|
searchent) != ENTMAP_INVALID)
|
|
continue;
|
|
|
|
SV_AddNailUpdate (ent); // added to the special update list
|
|
}
|
|
|
|
// encode the packet entities as a delta from the
|
|
// last packetentities acknowledged by the client
|
|
if (client->delta_sequence != -1)
|
|
SV_EmitDeltaPacketEntities (client, pack, msg);
|
|
else
|
|
SV_EmitPacketEntities (client, pack, msg);
|
|
|
|
// now add the specialized nail update
|
|
SV_EmitNailUpdate (msg);
|
|
}
|