diff options
Diffstat (limited to 'src/cl_ents.c')
-rw-r--r-- | src/cl_ents.c | 1031 |
1 files changed, 1031 insertions, 0 deletions
diff --git a/src/cl_ents.c b/src/cl_ents.c new file mode 100644 index 0000000..33473d9 --- /dev/null +++ b/src/cl_ents.c @@ -0,0 +1,1031 @@ +/* +Copyright (C) 1997-2001 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 the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cl_ents.c -- entity parsing and management + +#include "cl_local.h" + +extern qhandle_t cl_mod_powerscreen; + +/* +========================================================================= + +FRAME PARSING + +========================================================================= +*/ + +/* +================== +CL_SetEntityState + +cl.frame should be set to current frame before calling this function. +================== +*/ +static void CL_SetEntityState( entity_state_t *state ) { + centity_t *ent = &cl_entities[state->number]; + + // if entity is solid, decode mins/maxs and add to the list + if( state->solid && state->number != cl.frame.clientNum + 1 ) { + cl.solidEntities[cl.numSolidEntities++] = ent; + if( state->solid != 31 ) { + int x, zd, zu; + + // encoded bbox + if( cl.esFlags & MSG_ES_LONGSOLID ) { + x = (state->solid & 255); + zd = ((state->solid>>8) & 255); + zu = ((state->solid>>16) & 65535) - 32768; + } else { + x = 8*(state->solid & 31); + zd = 8*((state->solid>>5) & 31); + zu = 8*((state->solid>>10) & 63) - 32; + } + + ent->mins[0] = ent->mins[1] = -x; + ent->maxs[0] = ent->maxs[1] = x; + ent->mins[2] = -zd; + ent->maxs[2] = zu; + } + } + + if( ent->serverframe != cl.oldframe.number ) { + // wasn't in last update, so initialize some things + ent->trailcount = 1024; // for diminishing rocket / grenade trails + + // duplicate the current state so lerping doesn't hurt anything + ent->prev = *state; + + if( state->event == EV_PLAYER_TELEPORT || state->event == EV_OTHER_TELEPORT ) { + // no lerping if teleported + VectorCopy( state->origin, ent->lerp_origin ); + } else { + // old_origin is valid for new entities, + // so use it as starting point for interpolating between + VectorCopy( state->old_origin, ent->prev.origin ); + VectorCopy( state->old_origin, ent->lerp_origin ); + } + } else if( state->modelindex != ent->current.modelindex + || state->modelindex2 != ent->current.modelindex2 + || state->modelindex3 != ent->current.modelindex3 + || state->modelindex4 != ent->current.modelindex4 + || state->event == EV_PLAYER_TELEPORT + || state->event == EV_OTHER_TELEPORT + || abs(state->origin[0] - ent->current.origin[0]) > 512 + || abs(state->origin[1] - ent->current.origin[1]) > 512 + || abs(state->origin[2] - ent->current.origin[2]) > 512 ) + { + // some data changes will force no lerping + ent->trailcount = 1024; // for diminishing rocket / grenade trails + + // duplicate the current state so lerping doesn't hurt anything + ent->prev = *state; + + // no lerping if teleported or morphed + VectorCopy( state->origin, ent->lerp_origin ); + } else { // shuffle the last state to previous + ent->prev = ent->current; + } + + ent->serverframe = cl.frame.number; + ent->current = *state; + + // override position of the player's own entity from playerstate + // needed since the Q2PRO server skips any origin/angles updates + // on it as an additional optimization + if( cls.serverProtocol == PROTOCOL_VERSION_Q2PRO && + state->number == cl.frame.clientNum + 1 ) + { + Com_PlayerToEntityState( &cl.frame.ps, &ent->current ); + } +} + +/* +============== +CL_EntityEvent + +An entity has just been parsed that has an event value +============== +*/ +extern qhandle_t cl_sfx_footsteps[4]; + +static void CL_EntityEvent (int number) { + centity_t *cent = &cl_entities[number]; + + // EF_TELEPORTER acts like an event, but is not cleared each frame + if( cent->current.effects & EF_TELEPORTER ) { + CL_TeleporterParticles( cent->current.origin ); + } + + switch (cent->current.event) { + case EV_ITEM_RESPAWN: + S_StartSound (NULL, number, CHAN_WEAPON, S_RegisterSound("items/respawn1.wav"), 1, ATTN_IDLE, 0); + CL_ItemRespawnParticles (cent->current.origin); + break; + case EV_PLAYER_TELEPORT: + S_StartSound (NULL, number, CHAN_WEAPON, S_RegisterSound("misc/tele1.wav"), 1, ATTN_IDLE, 0); + CL_TeleportParticles (cent->current.origin); + break; + case EV_FOOTSTEP: + if (cl_footsteps->integer) + S_StartSound (NULL, number, CHAN_BODY, cl_sfx_footsteps[rand()&3], 1, ATTN_NORM, 0); + break; + case EV_FALLSHORT: + S_StartSound (NULL, number, CHAN_AUTO, S_RegisterSound ("player/land1.wav"), 1, ATTN_NORM, 0); + break; + case EV_FALL: + S_StartSound (NULL, number, CHAN_AUTO, S_RegisterSound ("*fall2.wav"), 1, ATTN_NORM, 0); + break; + case EV_FALLFAR: + S_StartSound (NULL, number, CHAN_AUTO, S_RegisterSound ("*fall1.wav"), 1, ATTN_NORM, 0); + break; + } +} + +/* +================== +CL_DeltaFrame + +================== +*/ +void CL_DeltaFrame( void ) { + centity_t *ent; + entity_state_t *state; + int i, j; + + // set server time + cl.servertime = ( cl.frame.number - cl.serverdelta ) * cl.frametime; + cl.numSolidEntities = 0; + + // update position of the player's own entity from playerstate + // useful in situations when player entity is invisible, but + // server sends an effect referencing it's origin (such as MZ_LOGIN, etc) + ent = &cl_entities[ cl.frame.clientNum + 1 ]; + Com_PlayerToEntityState( &cl.frame.ps, &ent->current ); + + for( i = 0; i < cl.frame.numEntities; i++ ) { + j = ( cl.frame.firstEntity + i ) & PARSE_ENTITIES_MASK; + state = &cl.entityStates[j]; + + // set current and prev + CL_SetEntityState( state ); + + // fire events + CL_EntityEvent( state->number ); + } + + if( cls.demo.recording ) { + CL_EmitDemoFrame(); + } + + if( !cl.oldframe.ps.stats[STAT_LAYOUTS] && cl.frame.ps.stats[STAT_LAYOUTS] ) { + cl.putaway = qfalse; + } + + if( cl.oldframe.ps.pmove.pm_type != cl.frame.ps.pmove.pm_type ) { + IN_Activate(); + } +} + + +/* +========================================================================== + +INTERPOLATE BETWEEN FRAMES TO GET RENDERING PARMS + +========================================================================== +*/ + +/* +=============== +CL_AddPacketEntities + +=============== +*/ +static void CL_AddPacketEntities( void ) { + entity_t ent; + entity_state_t *s1; + float autorotate; + int i; + int pnum; + centity_t *cent; + int autoanim; + clientinfo_t *ci; + unsigned int effects, renderfx; + + // bonus items rotate at a fixed rate + autorotate = anglemod( cl.time * 0.1f ); + + // brush models can auto animate their frames + autoanim = 2 * cl.time / 1000; + + memset( &ent, 0, sizeof( ent ) ); + + for( pnum = 0; pnum < cl.frame.numEntities; pnum++ ) { + i = ( cl.frame.firstEntity + pnum ) & PARSE_ENTITIES_MASK; + s1 = &cl.entityStates[i]; + + cent = &cl_entities[s1->number]; + + effects = s1->effects; + renderfx = s1->renderfx; + + if( ( effects & EF_GIB ) && !cl_gibs->integer ) { + continue; + } + + // set frame + if (effects & EF_ANIM01) + ent.frame = autoanim & 1; + else if (effects & EF_ANIM23) + ent.frame = 2 + (autoanim & 1); + else if (effects & EF_ANIM_ALL) + ent.frame = autoanim; + else if (effects & EF_ANIM_ALLFAST) + ent.frame = cl.time / 100; + else + ent.frame = s1->frame; + + // quad and pent can do different things on client + if (effects & EF_PENT) { + effects &= ~EF_PENT; + effects |= EF_COLOR_SHELL; + renderfx |= RF_SHELL_RED; + } + + if (effects & EF_QUAD) { + effects &= ~EF_QUAD; + effects |= EF_COLOR_SHELL; + renderfx |= RF_SHELL_BLUE; + } +//====== +// PMM + if (effects & EF_DOUBLE) { + effects &= ~EF_DOUBLE; + effects |= EF_COLOR_SHELL; + renderfx |= RF_SHELL_DOUBLE; + } + + if (effects & EF_HALF_DAMAGE) { + effects &= ~EF_HALF_DAMAGE; + effects |= EF_COLOR_SHELL; + renderfx |= RF_SHELL_HALF_DAM; + } +// pmm +//====== + ent.oldframe = cent->prev.frame; + ent.backlerp = 1.0 - cl.lerpfrac; + + if (renderfx & RF_FRAMELERP) { + // step origin discretely, because the frames + // do the animation properly + VectorCopy (cent->current.origin, ent.origin); + VectorCopy (cent->current.old_origin, ent.oldorigin); // FIXME + } else if( renderfx & RF_BEAM ) { + // interpolate start and end points for beams + LerpVector( cent->prev.origin, cent->current.origin, + cl.lerpfrac, ent.origin ); + LerpVector( cent->prev.old_origin, cent->current.old_origin, + cl.lerpfrac, ent.oldorigin ); + } else if( s1->number == cl.frame.clientNum + 1 ) { + // use predicted origin + VectorCopy( cl.playerEntityOrigin, ent.origin ); + VectorCopy( cl.playerEntityOrigin, ent.oldorigin ); + } else { // interpolate origin + LerpVector( cent->prev.origin, cent->current.origin, + cl.lerpfrac, ent.origin ); + VectorCopy( ent.origin, ent.oldorigin ); + } + + // create a new entity + + // tweak the color of beams + if ( renderfx & RF_BEAM ) + { // the four beam colors are encoded in 32 bits of skinnum (hack) + ent.alpha = 0.30; + ent.skinnum = (s1->skinnum >> ((rand() % 4)*8)) & 0xff; + ent.model = 0; + } + else + { + // set skin + if (s1->modelindex == 255) + { // use custom player skin + ent.skinnum = 0; + ci = &cl.clientinfo[s1->skinnum & 0xff]; + ent.skin = ci->skin; + ent.model = ci->model; + if (!ent.skin || !ent.model) + { + ent.skin = cl.baseclientinfo.skin; + ent.model = cl.baseclientinfo.model; + ci = &cl.baseclientinfo; + } +//============ +//PGM + if (renderfx & RF_USE_DISGUISE) + { + char buffer[MAX_QPATH]; + + Q_concat( buffer, sizeof( buffer ), "players/", ci->model_name, "/disguise.pcx", NULL ); + ent.skin = R_RegisterSkin( buffer ); + } +//PGM +//============ + } + else + { + ent.skinnum = s1->skinnum; + ent.skin = 0; + ent.model = cl.model_draw[s1->modelindex]; + } + } + + // only used for black hole model right now, FIXME: do better + if ((renderfx & RF_TRANSLUCENT) && !(renderfx & RF_BEAM)) + ent.alpha = 0.70; + + // render effects (fullbright, translucent, etc) + if ((effects & EF_COLOR_SHELL)) + ent.flags = 0; // renderfx go on color shell entity + else + ent.flags = renderfx; + + // calculate angles + if (effects & EF_ROTATE) { // some bonus items auto-rotate + ent.angles[0] = 0; + ent.angles[1] = autorotate; + ent.angles[2] = 0; + } + // RAFAEL + else if (effects & EF_SPINNINGLIGHTS) + { + vec3_t forward; + vec3_t start; + + ent.angles[0] = 0; + ent.angles[1] = anglemod(cl.time/2) + s1->angles[1]; + ent.angles[2] = 180; + + AngleVectors (ent.angles, forward, NULL, NULL); + VectorMA (ent.origin, 64, forward, start); + V_AddLight (start, 100, 1, 0, 0); + } else if( s1->number == cl.frame.clientNum + 1 ) { + VectorCopy( cl.playerEntityAngles, ent.angles ); // use predicted angles + } else { // interpolate angles + LerpAngles(cent->prev.angles, cent->current.angles, + cl.lerpfrac, ent.angles); + + // mimic original ref_gl "leaning" bug (uuugly!) + if( s1->modelindex == 255 && cl_rollhack->integer ) { + ent.angles[ROLL] = -ent.angles[ROLL]; + } + } + + if( s1->number == cl.frame.clientNum + 1 ) { + if (effects & EF_FLAG1) + V_AddLight (ent.origin, 225, 1.0, 0.1, 0.1); + else if (effects & EF_FLAG2) + V_AddLight (ent.origin, 225, 0.1, 0.1, 1.0); + else if (effects & EF_TAGTRAIL) //PGM + V_AddLight (ent.origin, 225, 1.0, 1.0, 0.0); //PGM + else if (effects & EF_TRACKERTRAIL) //PGM + V_AddLight (ent.origin, 225, -1.0, -1.0, -1.0); //PGM + + if( !cl.thirdPersonView ) { +#if 0 + ent.flags |= RF_VIEWERMODEL; // only draw from mirrors +#else + goto skip; +#endif + } + } + + // if set to invisible, skip + if (!s1->modelindex) { + goto skip; + } + + if (effects & EF_BFG) + { + ent.flags |= RF_TRANSLUCENT; + ent.alpha = 0.30; + } + + // RAFAEL + if (effects & EF_PLASMA) + { + ent.flags |= RF_TRANSLUCENT; + ent.alpha = 0.6; + } + + if (effects & EF_SPHERETRANS) + { + ent.flags |= RF_TRANSLUCENT; + // PMM - *sigh* yet more EF overloading + if (effects & EF_TRACKERTRAIL) + ent.alpha = 0.6; + else + ent.alpha = 0.3; + } +//pmm + + // add to refresh list + V_AddEntity (&ent); + + // color shells generate a seperate entity for the main model + if (effects & EF_COLOR_SHELL) + { + // PMM - at this point, all of the shells have been handled + // if we're in the rogue pack, set up the custom mixing, otherwise just + // keep going + if(!strcmp(fs_game->string,"rogue")) + { + // all of the solo colors are fine. we need to catch any of the combinations that look bad + // (double & half) and turn them into the appropriate color, and make double/quad something special + if (renderfx & RF_SHELL_HALF_DAM) + { + // ditch the half damage shell if any of red, blue, or double are on + if (renderfx & (RF_SHELL_RED|RF_SHELL_BLUE|RF_SHELL_DOUBLE)) + renderfx &= ~RF_SHELL_HALF_DAM; + } + + if (renderfx & RF_SHELL_DOUBLE) + { + // lose the yellow shell if we have a red, blue, or green shell + if (renderfx & (RF_SHELL_RED|RF_SHELL_BLUE|RF_SHELL_GREEN)) + renderfx &= ~RF_SHELL_DOUBLE; + // if we have a red shell, turn it to purple by adding blue + if (renderfx & RF_SHELL_RED) + renderfx |= RF_SHELL_BLUE; + // if we have a blue shell (and not a red shell), turn it to cyan by adding green + else if (renderfx & RF_SHELL_BLUE) { + // go to green if it's on already, otherwise do cyan (flash green) + if (renderfx & RF_SHELL_GREEN) + renderfx &= ~RF_SHELL_BLUE; + else + renderfx |= RF_SHELL_GREEN; + } + } + } + // pmm + ent.flags = renderfx | RF_TRANSLUCENT; + ent.alpha = 0.30; + V_AddEntity (&ent); + } + + ent.skin = 0; // never use a custom skin on others + ent.skinnum = 0; + ent.flags = 0; + ent.alpha = 0; + + // duplicate for linked models + if (s1->modelindex2) + { + if (s1->modelindex2 == 255) + { // custom weapon + ci = &cl.clientinfo[s1->skinnum & 0xff]; + i = (s1->skinnum >> 8); // 0 is default weapon model + if (i < 0 || i > cl.numWeaponModels - 1) + i = 0; + ent.model = ci->weaponmodel[i]; + if (!ent.model) { + if (i != 0) + ent.model = ci->weaponmodel[0]; + if (!ent.model) + ent.model = cl.baseclientinfo.weaponmodel[0]; + } + } + else + ent.model = cl.model_draw[s1->modelindex2]; + + // PMM - check for the defender sphere shell .. make it translucent + // replaces the previous version which used the high bit on modelindex2 to determine transparency + if (!Q_strcasecmp (cl.configstrings[CS_MODELS+(s1->modelindex2)], "models/items/shell/tris.md2")) + { + ent.alpha = 0.32; + ent.flags = RF_TRANSLUCENT; + } + // pmm + + V_AddEntity (&ent); + + //PGM - make sure these get reset. + ent.flags = 0; + ent.alpha = 0; + //PGM + } + if (s1->modelindex3) + { + ent.model = cl.model_draw[s1->modelindex3]; + V_AddEntity (&ent); + } + if (s1->modelindex4) + { + ent.model = cl.model_draw[s1->modelindex4]; + V_AddEntity (&ent); + } + + if ( effects & EF_POWERSCREEN ) + { + ent.model = cl_mod_powerscreen; + ent.oldframe = 0; + ent.frame = 0; + ent.flags |= (RF_TRANSLUCENT | RF_SHELL_GREEN); + ent.alpha = 0.30; + V_AddEntity (&ent); + } + + // add automatic particle trails + if ( (effects&~EF_ROTATE) ) + { + if (effects & EF_ROCKET) + { + if( !( cl_disable_particles->integer & NOPART_ROCKET_TRAIL ) ) { + CL_RocketTrail( cent->lerp_origin, ent.origin, cent ); + } + V_AddLight (ent.origin, 200, 1, 1, 0); + } + // PGM - Do not reorder EF_BLASTER and EF_HYPERBLASTER. + // EF_BLASTER | EF_TRACKER is a special case for EF_BLASTER2... Cheese! + else if (effects & EF_BLASTER) + { +// CL_BlasterTrail (cent->lerp_origin, ent.origin); +//PGM + if (effects & EF_TRACKER) // lame... problematic? + { + CL_BlasterTrail2 (cent->lerp_origin, ent.origin); + V_AddLight (ent.origin, 200, 0, 1, 0); + } + else + { + CL_BlasterTrail (cent->lerp_origin, ent.origin); + V_AddLight (ent.origin, 200, 1, 1, 0); + } +//PGM + } + else if (effects & EF_HYPERBLASTER) + { + if (effects & EF_TRACKER) // PGM overloaded for blaster2. + V_AddLight (ent.origin, 200, 0, 1, 0); // PGM + else // PGM + V_AddLight (ent.origin, 200, 1, 1, 0); + } + else if (effects & EF_GIB) + { + CL_DiminishingTrail (cent->lerp_origin, ent.origin, cent, effects); + } + else if (effects & EF_GRENADE) + { + if( !( cl_disable_particles->integer & NOPART_GRENADE_TRAIL ) ) { + CL_DiminishingTrail( cent->lerp_origin, ent.origin, cent, effects ); + } + } + else if (effects & EF_FLIES) + { + CL_FlyEffect (cent, ent.origin); + } + else if (effects & EF_BFG) + { + if (effects & EF_ANIM_ALLFAST) { + CL_BfgParticles (&ent); +#if USE_DLIGHTS + i = 200; + } else { + static const int bfg_lightramp[6] = {300, 400, 600, 300, 150, 75}; + + i = s1->frame; clamp( i, 0, 5 ); + i = bfg_lightramp[i]; +#endif + } + V_AddLight (ent.origin, i, 0, 1, 0); + } + // RAFAEL + else if (effects & EF_TRAP) + { + ent.origin[2] += 32; + CL_TrapParticles (&ent); +#if USE_DLIGHTS + i = (rand()%100) + 100; + V_AddLight (ent.origin, i, 1, 0.8, 0.1); +#endif + } + else if (effects & EF_FLAG1) + { + CL_FlagTrail (cent->lerp_origin, ent.origin, 242); + V_AddLight (ent.origin, 225, 1, 0.1, 0.1); + } + else if (effects & EF_FLAG2) + { + CL_FlagTrail (cent->lerp_origin, ent.origin, 115); + V_AddLight (ent.origin, 225, 0.1, 0.1, 1); + } +//====== +//ROGUE + else if (effects & EF_TAGTRAIL) + { + CL_TagTrail (cent->lerp_origin, ent.origin, 220); + V_AddLight (ent.origin, 225, 1.0, 1.0, 0.0); + } + else if (effects & EF_TRACKERTRAIL) + { + if (effects & EF_TRACKER) + { +#if USE_DLIGHTS + float intensity; + + intensity = 50 + (500 * (sin(cl.time/500.0) + 1.0)); + V_AddLight (ent.origin, intensity, -1.0, -1.0, -1.0); +#endif + } + else + { + CL_Tracker_Shell (cent->lerp_origin); + V_AddLight (ent.origin, 155, -1.0, -1.0, -1.0); + } + } + else if (effects & EF_TRACKER) + { + CL_TrackerTrail (cent->lerp_origin, ent.origin, 0); + // FIXME - check out this effect in rendition + V_AddLight (ent.origin, 200, -1, -1, -1); + } +//ROGUE +//====== + // RAFAEL + else if (effects & EF_GREENGIB) + { + CL_DiminishingTrail (cent->lerp_origin, ent.origin, cent, effects); + } + // RAFAEL + else if (effects & EF_IONRIPPER) + { + CL_IonripperTrail (cent->lerp_origin, ent.origin); + V_AddLight (ent.origin, 100, 1, 0.5, 0.5); + } + // RAFAEL + else if (effects & EF_BLUEHYPERBLASTER) + { + V_AddLight (ent.origin, 200, 0, 0, 1); + } + // RAFAEL + else if (effects & EF_PLASMA) + { + if (effects & EF_ANIM_ALLFAST) + { + CL_BlasterTrail (cent->lerp_origin, ent.origin); + } + V_AddLight (ent.origin, 130, 1, 0.5, 0.5); + } + } + +skip: + VectorCopy (ent.origin, cent->lerp_origin); + } +} + +/* +============== +CL_AddViewWeapon +============== +*/ +static void CL_AddViewWeapon( const player_state_t *ps, const player_state_t *ops ) { + entity_t gun; // view model + int i; + + // allow the gun to be completely removed + if( cl_gun->integer < 1 ) { + return; + } + + if( info_hand->integer == 2 ) { + return; + } + + // never draw in third person mode + if( cl.thirdPersonView ) { + return; + } + + memset( &gun, 0, sizeof( gun ) ); + + if( gun_model ) { + gun.model = gun_model; // development tool + } else { + gun.model = cl.model_draw[ps->gunindex]; + } + if( !gun.model ) { + return; + } + + // set up gun position + for( i=0 ; i<3 ; i++ ) { + gun.origin[i] = cl.refdef.vieworg[i] + ops->gunoffset[i] + + cl.lerpfrac * (ps->gunoffset[i] - ops->gunoffset[i]); + gun.angles[i] = cl.refdef.viewangles[i] + LerpAngle( ops->gunangles[i], + ps->gunangles[i], cl.lerpfrac ); + } + + // adjust for high fov + if( cl.frame.ps.fov > 90 ) { + vec_t ofs = ( 90 - cl.frame.ps.fov ) * 0.2f; + VectorMA( gun.origin, ofs, cl.v_forward, gun.origin ); + } + + if( gun_frame ) { + gun.frame = gun_frame; // development tool + gun.oldframe = gun_frame; // development tool + } else { + gun.frame = ps->gunframe; + if( gun.frame == 0 ) { + gun.oldframe = 0; // just changed weapons, don't lerp from old + } else { + gun.oldframe = ops->gunframe; + } + } + + gun.flags = RF_MINLIGHT | RF_DEPTHHACK | RF_WEAPONMODEL; + if( info_hand->integer == 1 ) { + gun.flags |= RF_LEFTHAND; + } + gun.backlerp = 1.0 - cl.lerpfrac; + VectorCopy( gun.origin, gun.oldorigin ); // don't lerp at all + V_AddEntity( &gun ); +} + +/* +=============== +CL_SetupThirdPersionView +=============== +*/ +static void CL_SetupThirdPersionView( void ) { + vec3_t focus; + float fscale, rscale; + float dist, angle, range; + trace_t trace; + static vec3_t mins = { -4, -4, -4 }, maxs = { 4, 4, 4 }; + + VectorMA( cl.refdef.vieworg, 512, cl.v_forward, focus ); + + cl.refdef.vieworg[2] += 8; + + cl.refdef.viewangles[PITCH] *= 0.5f; + AngleVectors( cl.refdef.viewangles, cl.v_forward, cl.v_right, cl.v_up ); + + angle = DEG2RAD( cl_thirdperson_angle->value ); + range = cl_thirdperson_range->value; + fscale = cos( angle ); + rscale = sin( angle ); + VectorMA( cl.refdef.vieworg, -range * fscale, cl.v_forward, cl.refdef.vieworg ); + VectorMA( cl.refdef.vieworg, -range * rscale, cl.v_right, cl.refdef.vieworg ); + + CM_BoxTrace( &trace, cl.playerEntityOrigin, cl.refdef.vieworg, + mins, maxs, cl.bsp->nodes, MASK_SOLID ); + if( trace.fraction != 1.0f ) { + VectorCopy( trace.endpos, cl.refdef.vieworg ); + } + + VectorSubtract( focus, cl.refdef.vieworg, focus ); + dist = sqrt( focus[0] * focus[0] + focus[1] * focus[1] ); + + cl.refdef.viewangles[PITCH] = -180 / M_PI * atan2( focus[2], dist ); + cl.refdef.viewangles[YAW] -= cl_thirdperson_angle->value; + + cl.thirdPersonView = qtrue; +} + +#if USE_SMOOTH_DELTA_ANGLES +static inline float LerpShort( int a2, int a1, float frac ) { + if (a1 - a2 > 32768) + a1 &= 65536; + if (a2 - a1 > 32768) + a1 &= 65536; + return a2 + frac * ( a1 - a2 ); +} +#endif + +/* +=============== +CL_CalcViewValues + +Sets cl.refdef view values and sound spatalization params +=============== +*/ +static void CL_CalcViewValues( void ) { + player_state_t *ps, *ops; + vec3_t viewoffset; + vec3_t kickangles; + float fov, lerp; + centity_t *ent; + + // find states to interpolate between + ps = &cl.frame.ps; + ops = &cl.oldframe.ps; + if( !cl.oldframe.valid || cl.oldframe.number != cl.frame.number - 1 ) { + ops = ps; + } + + // HACK: see if the player entity was teleported this frame + if( abs( ops->pmove.origin[0] - ps->pmove.origin[0] ) > 256*8 || + abs( ops->pmove.origin[1] - ps->pmove.origin[1] ) > 256*8 || + abs( ops->pmove.origin[2] - ps->pmove.origin[2] ) > 256*8 ) + { + ops = ps; // don't interpolate + } + + ent = &cl_entities[cl.frame.clientNum + 1]; + if( ent->serverframe == cl.frame.number && + ( ent->current.event == EV_PLAYER_TELEPORT + || ent->current.event == EV_OTHER_TELEPORT ) ) + { + ops = ps; // don't interpolate + } + + if( ( ops->pmove.pm_flags ^ ps->pmove.pm_flags ) & PMF_TELEPORT_BIT ) { + ops = ps; // don't interpolate + } + + if( cl.oldframe.clientNum != cl.frame.clientNum ) { + ops = ps; // don't interpolate + } + + lerp = cl.lerpfrac; + + // calculate the origin + if( !cls.demo.playback && cl_predict->integer && !( ps->pmove.pm_flags & PMF_NO_PREDICTION ) ) { + // use predicted values + unsigned delta = cls.realtime - cl.predicted_step_time; + float backlerp = lerp - 1.0; + + VectorMA( cl.predicted_origin, backlerp, cl.prediction_error, cl.refdef.vieworg ); + + // smooth out stair climbing + if( cl.predicted_step < 16 ) { + delta <<= 1; // small steps + } + if( delta < 100 ) { + cl.refdef.vieworg[2] -= cl.predicted_step * ( 100 - delta ) * 0.01f; + } + } else { + // just use interpolated values + cl.refdef.vieworg[0] = ops->pmove.origin[0] * 0.125f + + lerp * ( ps->pmove.origin[0] - ops->pmove.origin[0] ) * 0.125f; + cl.refdef.vieworg[1] = ops->pmove.origin[1] * 0.125f + + lerp * ( ps->pmove.origin[1] - ops->pmove.origin[1] ) * 0.125f; + cl.refdef.vieworg[2] = ops->pmove.origin[2] * 0.125f + + lerp * ( ps->pmove.origin[2] - ops->pmove.origin[2] ) * 0.125f; + } + + // if not running a demo or on a locked frame, add the local angle movement + if( cls.demo.playback ) { + if( cls.key_dest == KEY_GAME && Key_IsDown( K_SHIFT ) ) { + VectorCopy( cl.viewangles, cl.refdef.viewangles ); + } else { + LerpAngles( ops->viewangles, ps->viewangles, lerp, + cl.refdef.viewangles ); + } + } else if( ps->pmove.pm_type < PM_DEAD ) { + // use predicted values + VectorCopy( cl.predicted_angles, cl.refdef.viewangles ); + } else if( ops->pmove.pm_type < PM_DEAD && cls.serverProtocol > PROTOCOL_VERSION_DEFAULT ) { + // lerp from predicted angles, since enhanced servers + // do not send viewangles each frame + LerpAngles( cl.predicted_angles, ps->viewangles, lerp, cl.refdef.viewangles ); + } else { + // just use interpolated values + LerpAngles( ops->viewangles, ps->viewangles, lerp, cl.refdef.viewangles ); + } + + LerpVector( ops->viewoffset, ps->viewoffset, lerp, viewoffset ); + +#if USE_SMOOTH_DELTA_ANGLES + cl.delta_angles[0] = LerpShort( ops->pmove.delta_angles[0], ps->pmove.delta_angles[0], lerp ); + cl.delta_angles[1] = LerpShort( ops->pmove.delta_angles[1], ps->pmove.delta_angles[1], lerp ); + cl.delta_angles[2] = LerpShort( ops->pmove.delta_angles[2], ps->pmove.delta_angles[2], lerp ); +#endif + + if( cls.demo.playback && ( info_uf->integer & UF_LOCALFOV ) ) { + fov = info_fov->value; + if( fov < 1 ) { + fov = 90; + } else if( fov > 160 ) { + fov = 160; + } + cl.refdef.fov_x = fov; + } else { + // interpolate field of view + cl.refdef.fov_x = ops->fov + lerp * ( ps->fov - ops->fov ); + } + + // don't interpolate blend color + Vector4Copy( ps->blend, cl.refdef.blend ); + + AngleVectors( cl.refdef.viewangles, cl.v_forward, cl.v_right, cl.v_up ); + + VectorCopy( cl.refdef.vieworg, cl.playerEntityOrigin ); + VectorCopy( cl.refdef.viewangles, cl.playerEntityAngles ); + + if( cl.playerEntityAngles[PITCH] > 180 ) { + cl.playerEntityAngles[PITCH] -= 360; + } + + cl.playerEntityAngles[PITCH] = cl.playerEntityAngles[PITCH] / 3; + + VectorAdd( cl.refdef.vieworg, viewoffset, cl.refdef.vieworg ); + + VectorCopy( cl.refdef.vieworg, listener_origin ); + VectorCopy( cl.v_forward, listener_forward ); + VectorCopy( cl.v_right, listener_right ); + VectorCopy( cl.v_up, listener_up ); + + if( cl_thirdperson->integer && cl.frame.clientNum != CLIENTNUM_NONE ) { + // if dead, set a nice view angle + if( ps->stats[STAT_HEALTH] <= 0 ) { + cl.refdef.viewangles[ROLL] = 0; + cl.refdef.viewangles[PITCH] = 10; + } + CL_SetupThirdPersionView(); + } else { + // add kick angles + if( cl_kickangles->integer ) { + LerpAngles( ops->kick_angles, ps->kick_angles, lerp, kickangles ); + VectorAdd( cl.refdef.viewangles, kickangles, cl.refdef.viewangles ); + } + + // add the weapon + CL_AddViewWeapon( ps, ops ); + + cl.thirdPersonView = qfalse; + } +} + + +/* +=============== +CL_AddEntities + +Emits all entities, particles, and lights to the refresh +=============== +*/ +void CL_AddEntities( void ) { + CL_CalcViewValues(); + CL_AddPacketEntities(); + CL_AddTEnts(); + CL_AddParticles(); +#if USE_DLIGHTS + CL_AddDLights(); +#endif +#if USE_LIGHTSTYLES + CL_AddLightStyles(); +#endif + LOC_AddLocationsToScene(); +} + +/* +=============== +CL_GetEntitySoundOrigin + +Called to get the sound spatialization origin +=============== +*/ +void CL_GetEntitySoundOrigin( int entnum, vec3_t org ) { + centity_t *ent; + mmodel_t *cm; + vec3_t mid; + + if( entnum < 0 || entnum >= MAX_EDICTS ) { + Com_Error( ERR_DROP, "%s: bad entnum: %d", __func__, entnum ); + } + if( !entnum ) { + // should this ever happen? + VectorCopy( listener_origin, org ); + return; + } + ent = &cl_entities[entnum]; + VectorCopy( ent->lerp_origin, org ); + + if( ent->current.solid == 31 ) { + cm = cl.model_clip[ent->current.modelindex]; + if( cm ) { + VectorAvg( cm->mins, cm->maxs, mid ); + VectorAdd( org, mid, org ); + } + } +} + |