/** * @brief Decides which entities are going to be visible to the client, and * copies off the player state and area_bits. */ void Sv_BuildClientFrame(sv_client_t *client) { vec3_t org, off; g_entity_t *cent = client->entity; if (!cent->client) { return; // not in game yet } // this is the frame we are creating sv_frame_t *frame = &client->frames[sv.frame_num & PACKET_MASK]; frame->sent_time = quetoo.ticks; // timestamp for ping calculation // grab the current player_state_t frame->ps = cent->client->ps; // find the client's PVS const pm_state_t *pm = ¢->client->ps.pm_state; UnpackVector(pm->view_offset, off); VectorAdd(pm->origin, off, org); const int32_t leaf = Cm_PointLeafnum(org, 0); const int32_t area = Cm_LeafArea(leaf); // calculate the visible areas frame->area_bytes = Cm_WriteAreaBits(area, frame->area_bits); // resolve the visibility data byte pvs[MAX_BSP_LEAFS >> 3], phs[MAX_BSP_LEAFS >> 3]; Sv_ClientVisibility(org, pvs, phs); // build up the list of relevant entities frame->num_entities = 0; frame->entity_state = svs.next_entity_state; for (uint16_t e = 1; e < svs.game->num_entities; e++) { g_entity_t *ent = ENTITY_FOR_NUM(e); // ignore entities that are local to the server if (ent->sv_flags & SVF_NO_CLIENT) { continue; } // ignore entities without visible presence unless they have an effect if (!ent->s.event && !ent->s.effects && !ent->s.trail && !ent->s.model1 && !ent->s.sound) { continue; } // ignore entities not in PVS / PHS if (ent != cent) { const sv_entity_t *sent = &sv.entities[e]; // by first checking area if (!Cm_AreasConnected(area, sent->areas[0])) { if (!sent->areas[1] || !Cm_AreasConnected(area, sent->areas[1])) { continue; } } const byte *vis = ent->s.sound || ent->s.event ? phs : pvs; if (sent->num_clusters == -1) { // use top_node if (!Cm_HeadnodeVisible(sent->top_node, vis)) { continue; } } else { // or check individual leafs int32_t i; for (i = 0; i < sent->num_clusters; i++) { const int32_t c = sent->clusters[i]; if (vis[c >> 3] & (1 << (c & 7))) { break; } } if (i == sent->num_clusters) { continue; // not visible } } } // copy it to the circular entity_state_t array entity_state_t *s = &svs.entity_states[svs.next_entity_state % svs.num_entity_states]; if (ent->s.number != e) { Com_Warn("Fixing entity number: %d -> %d\n", ent->s.number, e); ent->s.number = e; } *s = ent->s; // don't mark our own missiles as solid for prediction if (ent->owner == client->entity) { s->solid = SOLID_NOT; } svs.next_entity_state++; frame->num_entities++; } }
/* * Sv_LinkEdict * * Called whenever an entity changes origin, mins, maxs, or solid to add it to * the clipping hull. */ void Sv_LinkEdict(g_edict_t *ent) { sv_area_node_t *node; int leafs[MAX_TOTAL_ENT_LEAFS]; int clusters[MAX_TOTAL_ENT_LEAFS]; int num_leafs; int i, j, k; int area; int top_node; if (ent == svs.game->edicts) // never bother with the world return; if (ent->area.prev) // unlink from its previous area Sv_UnlinkEdict(ent); if (!ent->in_use) // and if its free, we're done return; // set the size VectorSubtract(ent->maxs, ent->mins, ent->size); // encode the size into the entity_state for client prediction if (ent->solid == SOLID_BOX) { // assume that x/y are equal and symetric i = ent->maxs[0] / 8; if (i < 1) i = 1; if (i > 31) i = 31; // z is not symmetric j = (-ent->mins[2]) / 8; if (j < 1) j = 1; if (j > 31) j = 31; // and z maxs can be negative... k = (ent->maxs[2] + 32) / 8; if (k < 1) k = 1; if (k > 63) k = 63; ent->s.solid = (k << 10) | (j << 5) | i; } else if (ent->solid == SOLID_BSP) { ent->s.solid = 31; // a solid_bbox will never create this value } else ent->s.solid = 0; // set the absolute bounding box if (ent->solid == SOLID_BSP && (ent->s.angles[0] || ent->s.angles[1] || ent->s.angles[2])) { // expand for rotation float max, v; int i; max = 0; for (i = 0; i < 3; i++) { v = fabsf(ent->mins[i]); if (v > max) max = v; v = fabsf(ent->maxs[i]); if (v > max) max = v; } for (i = 0; i < 3; i++) { ent->abs_mins[i] = ent->s.origin[i] - max; ent->abs_maxs[i] = ent->s.origin[i] + max; } } else { // normal VectorAdd(ent->s.origin, ent->mins, ent->abs_mins); VectorAdd(ent->s.origin, ent->maxs, ent->abs_maxs); } // because movement is clipped an epsilon away from an actual edge, // we must fully check even when bounding boxes don't quite touch ent->abs_mins[0] -= 1; ent->abs_mins[1] -= 1; ent->abs_mins[2] -= 1; ent->abs_maxs[0] += 1; ent->abs_maxs[1] += 1; ent->abs_maxs[2] += 1; // link to PVS leafs ent->num_clusters = 0; ent->area_num = 0; ent->area_num2 = 0; // get all leafs, including solids num_leafs = Cm_BoxLeafnums(ent->abs_mins, ent->abs_maxs, leafs, MAX_TOTAL_ENT_LEAFS, &top_node); // set areas for (i = 0; i < num_leafs; i++) { clusters[i] = Cm_LeafCluster(leafs[i]); area = Cm_LeafArea(leafs[i]); if (area) { // doors may legally occupy two areas, // but nothing should ever need more than that if (ent->area_num && ent->area_num != area) { if (ent->area_num2 && ent->area_num2 != area && sv.state == SV_LOADING) { Com_Debug("Object touching 3 areas at %f %f %f\n", ent->abs_mins[0], ent->abs_mins[1], ent->abs_mins[2]); } ent->area_num2 = area; } else ent->area_num = area; } } if (num_leafs >= MAX_TOTAL_ENT_LEAFS) { // assume we missed some leafs, and mark by head_node ent->num_clusters = -1; ent->head_node = top_node; } else { ent->num_clusters = 0; for (i = 0; i < num_leafs; i++) { if (clusters[i] == -1) continue; // not a visible leaf for (j = 0; j < i; j++) if (clusters[j] == clusters[i]) break; if (j == i) { if (ent->num_clusters == MAX_ENT_CLUSTERS) { // assume we missed some leafs, and mark by head_node ent->num_clusters = -1; ent->head_node = top_node; break; } ent->cluster_nums[ent->num_clusters++] = clusters[i]; } } } // if first time, make sure old_origin is valid if (!ent->link_count) { VectorCopy(ent->s.origin, ent->s.old_origin); } ent->link_count++; if (ent->solid == SOLID_NOT) return; // find the first node that the ent's box crosses node = sv_world.area_nodes; while (true) { if (node->axis == -1) break; if (ent->abs_mins[node->axis] > node->dist) node = node->children[0]; else if (ent->abs_maxs[node->axis] < node->dist) node = node->children[1]; else break; // crosses the node } // link it in if (ent->solid == SOLID_TRIGGER) Sv_InsertLink(&ent->area, &node->trigger_edicts); else Sv_InsertLink(&ent->area, &node->solid_edicts); }
/** * @brief Called whenever an entity changes origin, mins, maxs, or solid to add it to * the clipping hull. */ void Sv_LinkEntity(g_entity_t *ent) { int32_t leafs[MAX_ENT_LEAFS]; int32_t clusters[MAX_ENT_LEAFS]; size_t i, j; int32_t top_node; if (ent == svs.game->entities) { // never bother with the world return; } // remove it from its current sector Sv_UnlinkEntity(ent); if (!ent->in_use) { // and if its free, we're done return; } // set the size VectorSubtract(ent->maxs, ent->mins, ent->size); // encode the size into the entity state for client prediction ent->s.solid = ent->solid; switch (ent->s.solid) { case SOLID_TRIGGER: case SOLID_PROJECTILE: case SOLID_DEAD: case SOLID_BOX: PackBounds(ent->mins, ent->maxs, &ent->s.bounds); break; default: PackBounds(vec3_origin, vec3_origin, &ent->s.bounds); break; } // set the absolute bounding box; ensure it is symmetrical Cm_EntityBounds(ent->solid, ent->s.origin, ent->s.angles, ent->mins, ent->maxs, ent->abs_mins, ent->abs_maxs); sv_entity_t *sent = &sv.entities[NUM_FOR_ENTITY(ent)]; // link to PVS leafs sent->num_clusters = 0; sent->areas[0] = sent->areas[1] = 0; // get all leafs, including solids const size_t len = Cm_BoxLeafnums(ent->abs_mins, ent->abs_maxs, leafs, lengthof(leafs), &top_node, 0); // set areas, allowing entities (doors) to occupy up to two for (i = 0; i < len; i++) { clusters[i] = Cm_LeafCluster(leafs[i]); const int32_t area = Cm_LeafArea(leafs[i]); if (area) { if (sent->areas[0] && sent->areas[0] != area) { if (sent->areas[1] && sent->areas[1] != area && sv.state == SV_LOADING) { Com_Warn("Object touching 3 areas at %s\n", vtos(ent->abs_mins)); } sent->areas[1] = area; } else { sent->areas[0] = area; } } } if (len == MAX_ENT_LEAFS) { // use top_node sent->num_clusters = -1; sent->top_node = top_node; } else { sent->num_clusters = 0; for (i = 0; i < len; i++) { if (clusters[i] == -1) { continue; // not a visible leaf } for (j = 0; j < i; j++) if (clusters[j] == clusters[i]) { break; } if (j == i) { if (sent->num_clusters == MAX_ENT_CLUSTERS) { // use top_node Com_Debug(DEBUG_SERVER, "%s exceeds MAX_ENT_CLUSTERS\n", etos(ent)); sent->num_clusters = -1; sent->top_node = top_node; break; } sent->clusters[sent->num_clusters++] = clusters[i]; } } } if (ent->solid == SOLID_NOT) { return; } // find the first sector that the ent's box crosses sv_sector_t *sector = sv_world.sectors; while (true) { if (sector->axis == -1) { break; } if (ent->abs_mins[sector->axis] > sector->dist) { sector = sector->children[0]; } else if (ent->abs_maxs[sector->axis] < sector->dist) { sector = sector->children[1]; } else { break; // crosses the node } } // add it to the sector sent->sector = sector; sector->entities = g_list_prepend(sector->entities, ent); // and update its clipping matrices const vec_t *angles = ent->solid == SOLID_BSP ? ent->s.angles : vec3_origin; Matrix4x4_CreateFromEntity(&sent->matrix, ent->s.origin, angles, 1.0); Matrix4x4_Invert_Simple(&sent->inverse_matrix, &sent->matrix); }