+ // send all entities that touch the pvs
+ sv.numsendentities = 0;
+ sv.sendentitiesindex[0] = NULL;
+ memset(sv.sendentitiesindex, 0, prog->num_edicts * sizeof(*sv.sendentitiesindex));
+ for (e = 1, ent = PRVM_NEXT_EDICT(prog->edicts);e < prog->num_edicts;e++, ent = PRVM_NEXT_EDICT(ent))
+ {
+ if (!ent->priv.server->free && SV_PrepareEntityForSending(ent, sv.sendentities + sv.numsendentities, e))
+ {
+ sv.sendentitiesindex[e] = sv.sendentities + sv.numsendentities;
+ sv.numsendentities++;
+ }
+ }
+}
+
+#define MAX_LINEOFSIGHTTRACES 64
+
+qboolean SV_CanSeeBox(int numtraces, vec_t enlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs)
+{
+ float pitchsign;
+ float alpha;
+ float starttransformed[3], endtransformed[3];
+ int blocked = 0;
+ int traceindex;
+ int originalnumtouchedicts;
+ int numtouchedicts = 0;
+ int touchindex;
+ matrix4x4_t matrix, imatrix;
+ dp_model_t *model;
+ prvm_edict_t *touch;
+ prvm_edict_t *touchedicts[MAX_EDICTS];
+ unsigned int modelindex;
+ vec3_t boxmins, boxmaxs;
+ vec3_t clipboxmins, clipboxmaxs;
+ vec3_t endpoints[MAX_LINEOFSIGHTTRACES];
+
+ numtraces = min(numtraces, MAX_LINEOFSIGHTTRACES);
+
+ // expand the box a little
+ boxmins[0] = (enlarge+1) * entboxmins[0] - enlarge * entboxmaxs[0];
+ boxmaxs[0] = (enlarge+1) * entboxmaxs[0] - enlarge * entboxmins[0];
+ boxmins[1] = (enlarge+1) * entboxmins[1] - enlarge * entboxmaxs[1];
+ boxmaxs[1] = (enlarge+1) * entboxmaxs[1] - enlarge * entboxmins[1];
+ boxmins[2] = (enlarge+1) * entboxmins[2] - enlarge * entboxmaxs[2];
+ boxmaxs[2] = (enlarge+1) * entboxmaxs[2] - enlarge * entboxmins[2];
+
+ VectorMAM(0.5f, boxmins, 0.5f, boxmaxs, endpoints[0]);
+ for (traceindex = 1;traceindex < numtraces;traceindex++)
+ VectorSet(endpoints[traceindex], lhrandom(boxmins[0], boxmaxs[0]), lhrandom(boxmins[1], boxmaxs[1]), lhrandom(boxmins[2], boxmaxs[2]));
+
+ // calculate sweep box for the entire swarm of traces
+ VectorCopy(eye, clipboxmins);
+ VectorCopy(eye, clipboxmaxs);
+ for (traceindex = 0;traceindex < numtraces;traceindex++)
+ {
+ clipboxmins[0] = min(clipboxmins[0], endpoints[traceindex][0]);
+ clipboxmins[1] = min(clipboxmins[1], endpoints[traceindex][1]);
+ clipboxmins[2] = min(clipboxmins[2], endpoints[traceindex][2]);
+ clipboxmaxs[0] = max(clipboxmaxs[0], endpoints[traceindex][0]);
+ clipboxmaxs[1] = max(clipboxmaxs[1], endpoints[traceindex][1]);
+ clipboxmaxs[2] = max(clipboxmaxs[2], endpoints[traceindex][2]);
+ }
+
+ // get the list of entities in the sweep box
+ if (sv_cullentities_trace_entityocclusion.integer)
+ numtouchedicts = World_EntitiesInBox(&sv.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts);
+ if (numtouchedicts > MAX_EDICTS)
+ {
+ // this never happens
+ Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS);
+ numtouchedicts = MAX_EDICTS;
+ }
+ // iterate the entities found in the sweep box and filter them
+ originalnumtouchedicts = numtouchedicts;
+ numtouchedicts = 0;
+ for (touchindex = 0;touchindex < originalnumtouchedicts;touchindex++)
+ {
+ touch = touchedicts[touchindex];
+ if (touch->fields.server->solid != SOLID_BSP)
+ continue;
+ modelindex = (unsigned int)touch->fields.server->modelindex;
+ if (!modelindex)
+ continue;
+ if (modelindex >= MAX_MODELS)
+ continue; // error?
+ model = sv.models[(int)touch->fields.server->modelindex];
+ if (!model->brush.TraceLineOfSight)
+ continue;
+ // skip obviously transparent entities
+ alpha = PRVM_EDICTFIELDVALUE(touch, prog->fieldoffsets.alpha)->_float;
+ if (alpha && alpha < 1)
+ continue;
+ if ((int)touch->fields.server->effects & EF_ADDITIVE)
+ continue;
+ touchedicts[numtouchedicts++] = touch;
+ }
+
+ // now that we have a filtered list of "interesting" entities, fire each
+ // ray against all of them, this gives us an early-out case when something
+ // is visible (which it often is)
+
+ for (traceindex = 0;traceindex < numtraces;traceindex++)
+ {
+ // check world occlusion
+ if (sv.worldmodel && sv.worldmodel->brush.TraceLineOfSight)
+ if (!sv.worldmodel->brush.TraceLineOfSight(sv.worldmodel, eye, endpoints[traceindex]))
+ continue;
+ for (touchindex = 0;touchindex < numtouchedicts;touchindex++)
+ {
+ touch = touchedicts[touchindex];
+ modelindex = (unsigned int)touch->fields.server->modelindex;
+ model = sv.models[(int)touch->fields.server->modelindex];
+ // get the entity matrix
+ pitchsign = (model->type == mod_alias) ? -1 : 1;
+ Matrix4x4_CreateFromQuakeEntity(&matrix, touch->fields.server->origin[0], touch->fields.server->origin[1], touch->fields.server->origin[2], pitchsign * touch->fields.server->angles[0], touch->fields.server->angles[1], touch->fields.server->angles[2], 1);
+ Matrix4x4_Invert_Simple(&imatrix, &matrix);
+ // see if the ray hits this entity
+ Matrix4x4_Transform(&imatrix, eye, starttransformed);
+ Matrix4x4_Transform(&imatrix, endpoints[traceindex], endtransformed);
+ if (!model->brush.TraceLineOfSight(model, starttransformed, endtransformed))
+ {
+ blocked++;
+ break;
+ }
+ }
+ // check if the ray was blocked
+ if (touchindex < numtouchedicts)
+ continue;
+ // return if the ray was not blocked
+ return true;
+ }
+
+ // no rays survived
+ return false;
+}
+
+void SV_MarkWriteEntityStateToClient(entity_state_t *s)
+{
+ int isbmodel;
+ dp_model_t *model;
+ prvm_edict_t *ed;
+ if (sv.sententitiesconsideration[s->number] == sv.sententitiesmark)
+ return;
+ sv.sententitiesconsideration[s->number] = sv.sententitiesmark;
+ sv.writeentitiestoclient_stats_totalentities++;
+
+ if (s->customizeentityforclient)
+ {
+ prog->globals.server->self = s->number;
+ prog->globals.server->other = sv.writeentitiestoclient_cliententitynumber;
+ PRVM_ExecuteProgram(s->customizeentityforclient, "customizeentityforclient: NULL function");
+ if(!PRVM_G_FLOAT(OFS_RETURN) || !SV_PrepareEntityForSending(PRVM_EDICT_NUM(s->number), s, s->number))
+ return;
+ }
+
+ // never reject player
+ if (s->number != sv.writeentitiestoclient_cliententitynumber)
+ {
+ // check various rejection conditions
+ if (s->nodrawtoclient == sv.writeentitiestoclient_cliententitynumber)
+ return;
+ if (s->drawonlytoclient && s->drawonlytoclient != sv.writeentitiestoclient_cliententitynumber)
+ return;
+ if (s->effects & EF_NODRAW)
+ return;
+ // LordHavoc: only send entities with a model or important effects
+ if (!s->modelindex && s->specialvisibilityradius == 0)
+ return;
+
+ isbmodel = (model = sv.models[s->modelindex]) != NULL && model->name[0] == '*';
+ // viewmodels don't have visibility checking
+ if (s->viewmodelforclient)
+ {
+ if (s->viewmodelforclient != sv.writeentitiestoclient_cliententitynumber)
+ return;
+ }
+ else if (s->tagentity)
+ {
+ // tag attached entities simply check their parent
+ if (!sv.sendentitiesindex[s->tagentity])
+ return;
+ SV_MarkWriteEntityStateToClient(sv.sendentitiesindex[s->tagentity]);
+ if (sv.sententities[s->tagentity] != sv.sententitiesmark)
+ return;
+ }
+ // always send world submodels in newer protocols because they don't
+ // generate much traffic (in old protocols they hog bandwidth)
+ // but only if sv_cullentities_nevercullbmodels is off
+ else if (!(s->effects & EF_NODEPTHTEST) && (!isbmodel || !sv_cullentities_nevercullbmodels.integer || sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE))
+ {
+ // entity has survived every check so far, check if visible
+ ed = PRVM_EDICT_NUM(s->number);
+
+ // if not touching a visible leaf
+ if (sv_cullentities_pvs.integer && !r_novis.integer && sv.writeentitiestoclient_pvsbytes)
+ {
+ if (ed->priv.server->pvs_numclusters < 0)
+ {
+ // entity too big for clusters list
+ if (sv.worldmodel && sv.worldmodel->brush.BoxTouchingPVS && !sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, sv.writeentitiestoclient_pvs, ed->priv.server->cullmins, ed->priv.server->cullmaxs))
+ {
+ sv.writeentitiestoclient_stats_culled_pvs++;
+ return;
+ }
+ }
+ else
+ {
+ int i;
+ // check cached clusters list
+ for (i = 0;i < ed->priv.server->pvs_numclusters;i++)
+ if (CHECKPVSBIT(sv.writeentitiestoclient_pvs, ed->priv.server->pvs_clusterlist[i]))
+ break;
+ if (i == ed->priv.server->pvs_numclusters)
+ {
+ sv.writeentitiestoclient_stats_culled_pvs++;
+ return;
+ }
+ }
+ }
+
+ // or not seen by random tracelines
+ if (sv_cullentities_trace.integer && !isbmodel && sv.worldmodel->brush.TraceLineOfSight)
+ {
+ int samples =
+ s->number <= svs.maxclients
+ ? sv_cullentities_trace_samples_players.integer
+ :
+ s->specialvisibilityradius
+ ? sv_cullentities_trace_samples_extra.integer
+ : sv_cullentities_trace_samples.integer;
+ float enlarge = sv_cullentities_trace_enlarge.value;
+
+ if(samples > 0)
+ {
+ int eyeindex;
+ for (eyeindex = 0;eyeindex < sv.writeentitiestoclient_numeyes;eyeindex++)
+ if(SV_CanSeeBox(samples, enlarge, sv.writeentitiestoclient_eyes[eyeindex], ed->priv.server->cullmins, ed->priv.server->cullmaxs))
+ break;
+ if(eyeindex < sv.writeentitiestoclient_numeyes)
+ svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[s->number] =
+ realtime + (
+ s->number <= svs.maxclients
+ ? sv_cullentities_trace_delay_players.value
+ : sv_cullentities_trace_delay.value
+ );
+ else if (realtime > svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[s->number])
+ {
+ sv.writeentitiestoclient_stats_culled_trace++;
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ // this just marks it for sending
+ // FIXME: it would be more efficient to send here, but the entity
+ // compressor isn't that flexible
+ sv.writeentitiestoclient_stats_visibleentities++;
+ sv.sententities[s->number] = sv.sententitiesmark;
+}
+
+void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t *msg, int maxsize)
+{
+ qboolean need_empty = false;
+ int i, numsendstates, numcsqcsendstates;
+ entity_state_t *s;
+ prvm_edict_t *camera;
+ qboolean success;
+ vec3_t eye;