/* cl_ents.c entity parsing and management 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 */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #include "QF/cvar.h" #include "QF/locs.h" #include "QF/msg.h" #include "QF/render.h" #include "QF/skin.h" #include "QF/sys.h" #include "qw/msg_ucmd.h" #include "qw/bothdefs.h" #include "chase.h" #include "cl_cam.h" #include "cl_ents.h" #include "cl_main.h" #include "cl_parse.h" #include "cl_pred.h" #include "cl_tent.h" #include "compat.h" #include "d_iface.h" #include "host.h" #include "qw/pmove.h" #include "clview.h" entity_t cl_player_ents[MAX_CLIENTS]; entity_t cl_flag_ents[MAX_CLIENTS]; entity_t cl_entities[512]; // FIXME: magic number byte cl_entity_valid[2][512]; void CL_ClearEnts (void) { size_t i; i = qw_entstates.num_frames * qw_entstates.num_entities; memset (qw_entstates.frame[0], 0, i * sizeof (entity_state_t)); memset (cl_entity_valid, 0, sizeof (cl_entity_valid)); for (i = 0; i < sizeof (cl_entities) / sizeof (cl_entities[0]); i++) CL_Init_Entity (&cl_entities[i]); for (i = 0; i < sizeof (cl_flag_ents) / sizeof (cl_flag_ents[0]); i++) CL_Init_Entity (&cl_flag_ents[i]); for (i = 0; i < sizeof (cl_player_ents) / sizeof (cl_player_ents[0]); i++) CL_Init_Entity (&cl_player_ents[i]); } static void CL_NewDlight (int key, vec3_t org, int effects, byte glow_size, byte glow_color) { float radius; dlight_t *dl; static quat_t normal = {0.4, 0.2, 0.05, 0.7}; static quat_t red = {0.5, 0.05, 0.05, 0.7}; static quat_t blue = {0.05, 0.05, 0.5, 0.7}; static quat_t purple = {0.5, 0.05, 0.5, 0.7}; effects &= EF_BLUE | EF_RED | EF_BRIGHTLIGHT | EF_DIMLIGHT; if (!effects) { if (!glow_size) return; } dl = r_funcs->R_AllocDlight (key); if (!dl) return; VectorCopy (org, dl->origin); if (effects & (EF_BLUE | EF_RED | EF_BRIGHTLIGHT | EF_DIMLIGHT)) { radius = 200 + (rand () & 31); if (effects & EF_BRIGHTLIGHT) { radius += 200; dl->origin[2] += 16; } if (effects & EF_DIMLIGHT) if (effects & ~EF_DIMLIGHT) radius -= 100; dl->radius = radius; dl->die = cl.time + 0.1; switch (effects & (EF_RED | EF_BLUE)) { case EF_RED | EF_BLUE: QuatCopy (purple, dl->color); break; case EF_RED: QuatCopy (red, dl->color); break; case EF_BLUE: QuatCopy (blue, dl->color); break; default: QuatCopy (normal, dl->color); break; } } if (glow_size) { dl->radius += glow_size < 128 ? glow_size * 8.0 : (glow_size - 256) * 8.0; dl->die = cl.time + 0.1; if (glow_color) { if (glow_color == 255) { dl->color[0] = dl->color[1] = dl->color[2] = 1.0; } else { byte *tempcolor; tempcolor = (byte *) &d_8to24table[glow_color]; VectorScale (tempcolor, 1 / 255.0, dl->color); } } } } // Hack hack hack static inline int is_dead_body (entity_state_t *s1) { int i = s1->frame; if (s1->modelindex == cl_playerindex && (i == 49 || i == 60 || i == 69 || i == 84 || i == 93 || i == 102)) return 1; return 0; } // Hack hack hack static inline int is_gib (entity_state_t *s1) { if (s1->modelindex == cl_h_playerindex || s1->modelindex == cl_gib1index || s1->modelindex == cl_gib2index || s1->modelindex == cl_gib3index) return 1; return 0; } void CL_TransformEntity (entity_t *ent, const vec3_t angles, qboolean force) { vec3_t ang; vec_t *forward, *left, *up; if (VectorIsZero (angles)) { VectorSet (1, 0, 0, ent->transform + 0); VectorSet (0, 1, 0, ent->transform + 4); VectorSet (0, 0, 1, ent->transform + 8); } else if (force || !VectorCompare (angles, ent->angles)) { forward = ent->transform + 0; left = ent->transform + 4; up = ent->transform + 8; VectorCopy (angles, ang); if (ent->model && ent->model->type == mod_alias) { // stupid quake bug // why, oh, why, do alias models pitch in the opposite direction // to everything else? ang[PITCH] = -ang[PITCH]; } AngleVectors (ang, forward, left, up); VectorNegate (left, left); // AngleVectors is right-handed } VectorCopy (angles, ent->angles); ent->transform[3] = 0; ent->transform[7] = 0; ent->transform[11] = 0; VectorCopy (ent->origin, ent->transform + 12); ent->transform[15] = 1; } static void CL_ModelEffects (entity_t *ent, int num, int glow_color) { dlight_t *dl; model_t *model = ent->model; // add automatic particle trails if (model->flags & EF_ROCKET) { dl = r_funcs->R_AllocDlight (num); if (dl) { VectorCopy (ent->origin, dl->origin); dl->radius = 200.0; dl->die = cl.time + 0.1; //FIXME VectorCopy (r_firecolor->vec, dl->color); VectorSet (0.9, 0.7, 0.0, dl->color); dl->color[3] = 0.7; } r_funcs->particles->R_RocketTrail (ent); } else if (model->flags & EF_GRENADE) r_funcs->particles->R_GrenadeTrail (ent); else if (model->flags & EF_GIB) r_funcs->particles->R_BloodTrail (ent); else if (model->flags & EF_ZOMGIB) r_funcs->particles->R_SlightBloodTrail (ent); else if (model->flags & EF_TRACER) r_funcs->particles->R_WizTrail (ent); else if (model->flags & EF_TRACER2) r_funcs->particles->R_FlameTrail (ent); else if (model->flags & EF_TRACER3) r_funcs->particles->R_VoorTrail (ent); else if (model->flags & EF_GLOWTRAIL) if (r_funcs->particles->R_GlowTrail) r_funcs->particles->R_GlowTrail (ent, glow_color); } static void set_entity_model (entity_t *ent, int modelindex) { ent->model = cl.model_precache[modelindex]; // automatic animation (torches, etc) can be either all together // or randomized if (ent->model) { if (ent->model->synctype == ST_RAND) ent->syncbase = (float) (rand () & 0x7fff) / 0x7fff; else ent->syncbase = 0.0; } } static void CL_LinkPacketEntities (void) { int i, j, forcelink; float frac, f; entity_t *ent; entity_state_t *new, *old; vec3_t delta; frac = 1; for (i = 0; i < 512; i++) { new = &qw_entstates.frame[cl.link_sequence & UPDATE_MASK][i]; old = &qw_entstates.frame[cl.prev_sequence & UPDATE_MASK][i]; ent = &cl_entities[i]; forcelink = cl_entity_valid[0][i] != cl_entity_valid[1][i]; cl_entity_valid[1][i] = cl_entity_valid[0][i]; // if the object wasn't included in the last packet, remove it if (!cl_entity_valid[0][i]) { ent->model = NULL; ent->pose1 = ent->pose2 = -1; if (ent->efrag) r_funcs->R_RemoveEfrags (ent); // just became empty continue; } // spawn light flashes, even ones coming from invisible objects CL_NewDlight (i, new->origin, new->effects, new->glow_size, new->glow_color); // if set to invisible, skip if (!new->modelindex || (cl_deadbodyfilter->int_val && is_dead_body (new)) || (cl_gibfilter->int_val && is_gib (new))) { if (ent->efrag) r_funcs->R_RemoveEfrags (ent); continue; } if (forcelink) *old = *new; if (forcelink || new->modelindex != old->modelindex) { old->modelindex = new->modelindex; set_entity_model (ent, new->modelindex); } ent->frame = new->frame; if (forcelink || new->colormap != old->colormap || new->skinnum != old->skinnum) { old->skinnum = new->skinnum; ent->skinnum = new->skinnum; old->colormap = new->colormap; if (new->colormap && (new->colormap <= MAX_CLIENTS) && cl.players[new->colormap - 1].name && cl.players[new->colormap - 1].name->value[0]) { player_info_t *player = &cl.players[new->colormap - 1]; ent->skin = mod_funcs->Skin_SetSkin (ent->skin, new->colormap, player->skinname->value); ent->skin = mod_funcs->Skin_SetColormap (ent->skin, new->colormap); } else { ent->skin = mod_funcs->Skin_SetColormap (ent->skin, 0); } } ent->scale = new->scale / 16.0; VectorCopy (ent_colormod[new->colormod], ent->colormod); ent->colormod[3] = new->alpha / 255.0; ent->min_light = 0; ent->fullbright = 0; if (new->modelindex == cl_playerindex) { ent->min_light = min (cl.fbskins, cl_fb_players->value); if (ent->min_light >= 1.0) ent->fullbright = 1; } if (forcelink) { ent->pose1 = ent->pose2 = -1; VectorCopy (new->origin, ent->origin); if (!(ent->model->flags & EF_ROTATE)) CL_TransformEntity (ent, new->angles, true); if (i != cl.viewentity || chase_active->int_val) { if (ent->efrag) r_funcs->R_RemoveEfrags (ent); r_funcs->R_AddEfrags (ent); } VectorCopy (ent->origin, ent->old_origin); } else { f = frac; VectorCopy (ent->origin, ent->old_origin); VectorSubtract (new->origin, old->origin, delta); // If the delta is large, assume a teleport and don't lerp if (fabs (delta[0]) > 100 || fabs (delta[1] > 100) || fabs (delta[2]) > 100) { // assume a teleportation, not a motion VectorCopy (new->origin, ent->origin); if (!(ent->model->flags & EF_ROTATE)) CL_TransformEntity (ent, new->angles, true); ent->pose1 = ent->pose2 = -1; } else { vec3_t angles, d; // interpolate the origin and angles VectorMultAdd (old->origin, f, delta, ent->origin); if (!(ent->model->flags & EF_ROTATE)) { VectorSubtract (new->angles, old->angles, d); for (j = 0; j < 3; j++) { if (d[j] > 180) d[j] -= 360; else if (d[j] < -180) d[j] += 360; } VectorMultAdd (old->angles, f, d, angles); CL_TransformEntity (ent, angles, false); } } if (i != cl.viewentity || chase_active->int_val) { if (ent->efrag) { if (!VectorCompare (ent->origin, ent->old_origin)) { r_funcs->R_RemoveEfrags (ent); r_funcs->R_AddEfrags (ent); } } else { r_funcs->R_AddEfrags (ent); } } } if (!ent->efrag) r_funcs->R_AddEfrags (ent); // rotate binary objects locally if (ent->model->flags & EF_ROTATE) { vec3_t angles; angles[PITCH] = 0; angles[YAW] = anglemod (100 * cl.time); angles[ROLL] = 0; CL_TransformEntity (ent, angles, false); } //CL_EntityEffects (i, ent, new); //CL_NewDlight (i, ent->origin, new->effects, 0, 0); if (VectorDistance_fast (old->origin, ent->origin) > (256 * 256)) VectorCopy (ent->origin, old->origin); if (ent->model->flags & ~EF_ROTATE) CL_ModelEffects (ent, -new->number, new->glow_color); } } /* CL_AddFlagModels Called when the CTF flags are set. Flags are effectively temp entities. NOTE: this must be called /after/ the entity has been transformed as it uses the entity's transform matrix to get the frame vectors */ static void CL_AddFlagModels (entity_t *ent, int team, int key) { static float flag_offsets[] = { 16.0, 22.0, 26.0, 25.0, 24.0, 18.0, // 29-34 axpain 16.0, 24.0, 24.0, 22.0, 18.0, 16.0, // 35-40 pain }; float f; entity_t *fent; vec_t *v_forward, *v_left; vec3_t ang; if (cl_flagindex == -1) return; f = 14.0; if (ent->frame >= 29 && ent->frame <= 40) { f = flag_offsets[ent->frame - 29]; } else if (ent->frame >= 103 && ent->frame <= 118) { if (ent->frame <= 106) // 103-104 nailattack f = 20.0; // 105-106 light else // 107-112 rocketattack f = 21.0; // 112-118 shotattack } fent = &cl_flag_ents[key]; fent->model = cl.model_precache[cl_flagindex]; fent->skinnum = team; v_forward = ent->transform + 0; v_left = ent->transform + 4; VectorMultAdd (ent->origin, -f, v_forward, fent->origin); VectorMultAdd (fent->origin, -22, v_left, fent->origin); fent->origin[2] -= 16.0; VectorCopy (ent->angles, ang); ang[2] -= 45.0; CL_TransformEntity (fent, ang, false); r_funcs->R_EnqueueEntity (fent);//FIXME should use efrag (needs smarter // handling //in the player code) } /* CL_LinkPlayers Create visible entities in the correct position for all current players */ static void CL_LinkPlayers (void) { double playertime; int msec, oldphysent, i, j; entity_t *ent; frame_t *frame; player_info_t *info; player_state_t exact; player_state_t *state; qboolean clientplayer; vec3_t org, ang = {0, 0, 0}; playertime = realtime - cls.latency + 0.02; if (playertime > realtime) playertime = realtime; frame = &cl.frames[cl.parsecount & UPDATE_MASK]; for (j = 0, info = cl.players, state = frame->playerstate; j < MAX_CLIENTS; j++, info++, state++) { ent = &cl_player_ents[j]; if (ent->efrag) r_funcs->R_RemoveEfrags (ent); if (state->messagenum != cl.parsecount) continue; // not present this frame if (!info->name || !info->name->value[0]) continue; // spawn light flashes, even ones coming from invisible objects if (j == cl.playernum) { VectorCopy (cl.simorg, org); r_data->player_entity = &cl_player_ents[j]; clientplayer = true; } else { VectorCopy (state->pls.origin, org); clientplayer = false; } if (info->chat && info->chat->value[0] != '0') { dlight_t *dl = r_funcs->R_AllocDlight (j + 1); VectorCopy (org, dl->origin); dl->radius = 100; dl->die = cl.time + 0.1; QuatSet (0.0, 1.0, 0.0, 1.0, dl->color); } else { CL_NewDlight (j + 1, org, state->pls.effects, state->pls.glow_size, state->pls.glow_color); } // Draw player? if (!Cam_DrawPlayer (j)) continue; if (!state->pls.modelindex) continue; // Hack hack hack if (cl_deadbodyfilter->int_val && state->pls.modelindex == cl_playerindex && ((i = state->pls.frame) == 49 || i == 60 || i == 69 || i == 84 || i == 93 || i == 102)) continue; // predict only half the move to minimize overruns msec = 500 * (playertime - state->state_time); if (msec <= 0 || (!cl_predict_players->int_val) || cls.demoplayback2) { VectorCopy (state->pls.origin, ent->origin); } else { // predict players movement state->pls.cmd.msec = msec = min (msec, 255); oldphysent = pmove.numphysent; CL_SetSolidPlayers (j); CL_PredictUsercmd (state, &exact, &state->pls.cmd, clientplayer); pmove.numphysent = oldphysent; VectorCopy (exact.pls.origin, ent->origin); } // angles if (j == cl.playernum) { ang[PITCH] = -cl.viewangles[PITCH] / 3.0; ang[YAW] = cl.viewangles[YAW]; } else { ang[PITCH] = -state->viewangles[PITCH] / 3.0; ang[YAW] = state->viewangles[YAW]; } ang[ROLL] = V_CalcRoll (ang, state->pls.velocity) * 4.0; ent->model = cl.model_precache[state->pls.modelindex]; ent->frame = state->pls.frame; ent->skinnum = state->pls.skinnum; CL_TransformEntity (ent, ang, false); ent->min_light = 0; ent->fullbright = 0; if (state->pls.modelindex == cl_playerindex) { //XXX // use custom skin ent->skin = info->skin; ent->min_light = min (cl.fbskins, cl_fb_players->value); if (ent->min_light >= 1.0) ent->fullbright = 1; } else { // FIXME no team colors on nonstandard player models ent->skin = 0; } // stuff entity in map r_funcs->R_AddEfrags (ent); if (state->pls.effects & EF_FLAG1) CL_AddFlagModels (ent, 0, j); else if (state->pls.effects & EF_FLAG2) CL_AddFlagModels (ent, 1, j); } } /* CL_EmitEntities Builds the visedicts array for cl.time Made up of: clients, packet_entities, nails, and tents */ void CL_EmitEntities (void) { if (cls.state != ca_active) return; if (!cl.validsequence) return; CL_LinkPlayers (); CL_LinkPacketEntities (); CL_UpdateTEnts (); if (cl_draw_locs->int_val) { //FIXME custom ent rendering code would be nice dlight_t *dl; location_t *nearloc; vec3_t trueloc; int i; nearloc = locs_find (cl.simorg); if (nearloc) { dl = r_funcs->R_AllocDlight (4096); if (dl) { VectorCopy (nearloc->loc, dl->origin); dl->radius = 200; dl->die = r_data->realtime + 0.1; dl->color[0] = 0; dl->color[1] = 1; dl->color[2] = 0; dl->color[3] = 0.7; } VectorCopy (nearloc->loc, trueloc); r_funcs->particles->R_Particle_New (pt_smokecloud, part_tex_smoke, trueloc, 2.0, vec3_origin, r_data->realtime + 9.0, 254, 0.25 + qfrandom (0.125), 0.0); for (i = 0; i < 15; i++) r_funcs->particles->R_Particle_NewRandom (pt_fallfade, part_tex_dot, trueloc, 12, 0.7, 96, r_data->realtime + 5.0, 104 + (rand () & 7), 1.0, 0.0); } } } void CL_Ents_Init (void) { r_data->view_model = &cl.viewent; }