You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1744 lines
42 KiB
1744 lines
42 KiB
//========= Copyright (c) 1996-2002, Valve LLC, All rights reserved. ============ |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//============================================================================= |
|
|
|
// view/refresh setup functions |
|
|
|
#include "hud.h" |
|
#include "cl_util.h" |
|
#include "cvardef.h" |
|
#include "usercmd.h" |
|
#include "const.h" |
|
|
|
#include "entity_state.h" |
|
#include "cl_entity.h" |
|
#include "ref_params.h" |
|
#include "in_defs.h" // PITCH YAW ROLL |
|
#include "pm_movevars.h" |
|
#include "pm_shared.h" |
|
#include "pm_defs.h" |
|
#include "event_api.h" |
|
#include "pmtrace.h" |
|
#include "screenfade.h" |
|
#include "shake.h" |
|
#include "hltv.h" |
|
|
|
// Spectator Mode |
|
extern "C" |
|
{ |
|
float vecNewViewAngles[3]; |
|
int iHasNewViewAngles; |
|
float vecNewViewOrigin[3]; |
|
int iHasNewViewOrigin; |
|
int iIsSpectator; |
|
} |
|
|
|
#ifndef M_PI |
|
#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h |
|
#endif |
|
|
|
extern "C" |
|
{ |
|
int CL_IsThirdPerson( void ); |
|
void CL_CameraOffset( float *ofs ); |
|
|
|
void DLLEXPORT V_CalcRefdef( struct ref_params_s *pparams ); |
|
|
|
void PM_ParticleLine( float *start, float *end, int pcolor, float life, float vert ); |
|
int PM_GetVisEntInfo( int ent ); |
|
int PM_GetPhysEntInfo( int ent ); |
|
void InterpolateAngles( float * start, float * end, float * output, float frac ); |
|
void NormalizeAngles( float * angles ); |
|
float Distance( const float * v1, const float * v2 ); |
|
float AngleBetweenVectors( const float * v1, const float * v2 ); |
|
|
|
float vJumpOrigin[3]; |
|
float vJumpAngles[3]; |
|
} |
|
|
|
void V_DropPunchAngle( float frametime, float *ev_punchangle ); |
|
void VectorAngles( const float *forward, float *angles ); |
|
|
|
#include "r_studioint.h" |
|
#include "com_model.h" |
|
|
|
extern engine_studio_api_t IEngineStudio; |
|
|
|
float mouse_pos_extern[2]; |
|
extern cvar_t *cam_idealdist; |
|
extern cvar_t *cam_minDist; |
|
|
|
extern cvar_t *cam_contain; |
|
|
|
/* |
|
The view is allowed to move slightly from it's true position for bobbing, |
|
but if it exceeds 8 pixels linear distance (spherical, not box), the list of |
|
entities sent from the server may not include everything in the pvs, especially |
|
when crossing a water boudnary. |
|
*/ |
|
|
|
extern cvar_t *cl_forwardspeed; |
|
extern cvar_t *chase_active; |
|
extern cvar_t *scr_ofsx, *scr_ofsy, *scr_ofsz; |
|
extern cvar_t *cl_vsmoothing; |
|
extern cvar_t *cl_viewbob; |
|
extern Vector dead_viewangles; |
|
|
|
#define CAM_MODE_RELAX 1 |
|
#define CAM_MODE_FOCUS 2 |
|
|
|
vec3_t realOrigin; |
|
float realViewOrg[3]; |
|
float camOffset[3]; |
|
|
|
extern cvar_t *cam_lookahead; |
|
|
|
vec3_t v_origin, v_angles, v_cl_angles, v_sim_org, v_lastAngles; |
|
float v_frametime, v_lastDistance; |
|
float v_cameraRelaxAngle = 5.0f; |
|
float v_cameraFocusAngle = 35.0f; |
|
int v_cameraMode = CAM_MODE_FOCUS; |
|
qboolean v_resetCamera = 1; |
|
|
|
vec3_t g_ev_punchangle; |
|
|
|
cvar_t *scr_ofsx; |
|
cvar_t *scr_ofsy; |
|
cvar_t *scr_ofsz; |
|
|
|
cvar_t *v_centermove; |
|
cvar_t *v_centerspeed; |
|
|
|
cvar_t *cl_bobcycle; |
|
cvar_t *cl_bob; |
|
cvar_t *cl_bobup; |
|
cvar_t *cl_waterdist; |
|
cvar_t *cl_chasedist; |
|
|
|
// These cvars are not registered (so users can't cheat), so set the ->value field directly |
|
// Register these cvars in V_Init() if needed for easy tweaking |
|
cvar_t v_iyaw_cycle = {"v_iyaw_cycle", "2", 0, 2}; |
|
cvar_t v_iroll_cycle = {"v_iroll_cycle", "0.5", 0, 0.5}; |
|
cvar_t v_ipitch_cycle = {"v_ipitch_cycle", "1", 0, 1}; |
|
cvar_t v_iyaw_level = {"v_iyaw_level", "0.3", 0, 0.3}; |
|
cvar_t v_iroll_level = {"v_iroll_level", "0.1", 0, 0.1}; |
|
cvar_t v_ipitch_level = {"v_ipitch_level", "0.3", 0, 0.3}; |
|
|
|
float v_idlescale; // used by TFC for concussion grenade effect |
|
|
|
//============================================================================= |
|
/* |
|
void V_NormalizeAngles( float *angles ) |
|
{ |
|
int i; |
|
|
|
// Normalize angles |
|
for( i = 0; i < 3; i++ ) |
|
{ |
|
if( angles[i] > 180.0 ) |
|
{ |
|
angles[i] -= 360.0; |
|
} |
|
else if( angles[i] < -180.0 ) |
|
{ |
|
angles[i] += 360.0; |
|
} |
|
} |
|
} |
|
|
|
=================== |
|
V_InterpolateAngles |
|
|
|
Interpolate Euler angles. |
|
FIXME: Use Quaternions to avoid discontinuities |
|
Frac is 0.0 to 1.0 ( i.e., should probably be clamped, but doesn't have to be ) |
|
=================== |
|
|
|
void V_InterpolateAngles( float *start, float *end, float *output, float frac ) |
|
{ |
|
int i; |
|
float ang1, ang2; |
|
float d; |
|
|
|
V_NormalizeAngles( start ); |
|
V_NormalizeAngles( end ); |
|
|
|
for( i = 0 ; i < 3 ; i++ ) |
|
{ |
|
ang1 = start[i]; |
|
ang2 = end[i]; |
|
|
|
d = ang2 - ang1; |
|
if( d > 180 ) |
|
{ |
|
d -= 360; |
|
} |
|
else if( d < -180 ) |
|
{ |
|
d += 360; |
|
} |
|
|
|
output[i] = ang1 + d * frac; |
|
} |
|
|
|
V_NormalizeAngles( output ); |
|
} */ |
|
|
|
// Quakeworld bob code, this fixes jitters in the mutliplayer since the clock (pparams->time) isn't quite linear |
|
float V_CalcBob( struct ref_params_s *pparams ) |
|
{ |
|
static double bobtime; |
|
static float bob; |
|
float cycle; |
|
static float lasttime; |
|
vec3_t vel; |
|
|
|
if( pparams->onground == -1 || |
|
pparams->time == lasttime ) |
|
{ |
|
// just use old value |
|
return bob; |
|
} |
|
|
|
lasttime = pparams->time; |
|
|
|
bobtime += pparams->frametime; |
|
cycle = bobtime - (int)( bobtime / cl_bobcycle->value ) * cl_bobcycle->value; |
|
cycle /= cl_bobcycle->value; |
|
|
|
if( cycle < cl_bobup->value ) |
|
{ |
|
cycle = M_PI * cycle / cl_bobup->value; |
|
} |
|
else |
|
{ |
|
cycle = M_PI + M_PI * ( cycle - cl_bobup->value )/( 1.0 - cl_bobup->value ); |
|
} |
|
|
|
// bob is proportional to simulated velocity in the xy plane |
|
// (don't count Z, or jumping messes it up) |
|
VectorCopy( pparams->simvel, vel ); |
|
vel[2] = 0; |
|
|
|
bob = sqrt( vel[0] * vel[0] + vel[1] * vel[1] ) * cl_bob->value; |
|
bob = bob * 0.3 + bob * 0.7 * sin(cycle); |
|
bob = min( bob, 4 ); |
|
bob = max( bob, -7 ); |
|
return bob; |
|
} |
|
|
|
/* |
|
=============== |
|
V_CalcRoll |
|
Used by view and sv_user |
|
=============== |
|
*/ |
|
float V_CalcRoll( vec3_t angles, vec3_t velocity, float rollangle, float rollspeed ) |
|
{ |
|
float sign; |
|
float side; |
|
float value; |
|
vec3_t forward, right, up; |
|
|
|
AngleVectors( angles, forward, right, up ); |
|
|
|
side = DotProduct( velocity, right ); |
|
sign = side < 0 ? -1 : 1; |
|
side = fabs( side ); |
|
|
|
value = rollangle; |
|
if( side < rollspeed ) |
|
{ |
|
side = side * value / rollspeed; |
|
} |
|
else |
|
{ |
|
side = value; |
|
} |
|
return side * sign; |
|
} |
|
|
|
typedef struct pitchdrift_s |
|
{ |
|
float pitchvel; |
|
int nodrift; |
|
float driftmove; |
|
double laststop; |
|
} pitchdrift_t; |
|
|
|
static pitchdrift_t pd; |
|
|
|
/* |
|
=============== |
|
V_DriftPitch |
|
|
|
Moves the client pitch angle towards idealpitch sent by the server. |
|
|
|
If the user is adjusting pitch manually, either with lookup/lookdown, |
|
mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped. |
|
=============== |
|
*/ |
|
|
|
/* |
|
============================================================================== |
|
VIEW RENDERING |
|
============================================================================== |
|
*/ |
|
|
|
/* |
|
================== |
|
V_CalcGunAngle |
|
================== |
|
*/ |
|
void V_CalcGunAngle( struct ref_params_s *pparams ) |
|
{ |
|
cl_entity_t *viewent; |
|
|
|
viewent = gEngfuncs.GetViewModel(); |
|
if( !viewent ) |
|
return; |
|
|
|
viewent->angles[YAW] = pparams->viewangles[YAW] + pparams->crosshairangle[YAW]; |
|
viewent->angles[PITCH] = -pparams->viewangles[PITCH] + pparams->crosshairangle[PITCH] * 0.25; |
|
viewent->angles[ROLL] -= v_idlescale * sin( pparams->time * v_iroll_cycle.value ) * v_iroll_level.value; |
|
|
|
// don't apply all of the v_ipitch to prevent normally unseen parts of viewmodel from coming into view. |
|
viewent->angles[PITCH] -= v_idlescale * sin( pparams->time * v_ipitch_cycle.value ) * ( v_ipitch_level.value * 0.5 ); |
|
viewent->angles[YAW] -= v_idlescale * sin( pparams->time * v_iyaw_cycle.value ) * v_iyaw_level.value; |
|
|
|
VectorCopy( viewent->angles, viewent->curstate.angles ); |
|
VectorCopy( viewent->angles, viewent->latched.prevangles ); |
|
} |
|
|
|
/* |
|
============== |
|
V_AddIdle |
|
|
|
Idle swaying |
|
============== |
|
*/ |
|
void V_AddIdle( struct ref_params_s *pparams ) |
|
{ |
|
pparams->viewangles[ROLL] += v_idlescale * sin( pparams->time * v_iroll_cycle.value ) * v_iroll_level.value; |
|
pparams->viewangles[PITCH] += v_idlescale * sin( pparams->time * v_ipitch_cycle.value ) * v_ipitch_level.value; |
|
pparams->viewangles[YAW] += v_idlescale * sin( pparams->time * v_iyaw_cycle.value ) * v_iyaw_level.value; |
|
} |
|
|
|
/* |
|
============== |
|
V_CalcViewRoll |
|
|
|
Roll is induced by movement and damage |
|
============== |
|
*/ |
|
void V_CalcViewRoll( struct ref_params_s *pparams ) |
|
{ |
|
float side; |
|
cl_entity_t *viewentity; |
|
|
|
viewentity = gEngfuncs.GetEntityByIndex( pparams->viewentity ); |
|
if( !viewentity ) |
|
return; |
|
|
|
side = V_CalcRoll( viewentity->angles, pparams->simvel, pparams->movevars->rollangle, pparams->movevars->rollspeed ); |
|
|
|
pparams->viewangles[ROLL] += side; |
|
|
|
if( pparams->health <= 0 && ( pparams->viewheight[2] != 0 ) ) |
|
{ |
|
// only roll the view if the player is dead and the viewheight[2] is nonzero |
|
// this is so deadcam in multiplayer will work. |
|
pparams->viewangles[ROLL] = 80; // dead view angle |
|
return; |
|
} |
|
} |
|
|
|
/* |
|
================== |
|
V_CalcIntermissionRefdef |
|
|
|
================== |
|
*/ |
|
void V_CalcIntermissionRefdef( struct ref_params_s *pparams ) |
|
{ |
|
cl_entity_t /**ent,*/ *view; |
|
float old; |
|
|
|
// ent is the player model ( visible when out of body ) |
|
//ent = gEngfuncs.GetLocalPlayer(); |
|
|
|
// view is the weapon model (only visible from inside body ) |
|
view = gEngfuncs.GetViewModel(); |
|
|
|
VectorCopy( pparams->simorg, pparams->vieworg ); |
|
VectorCopy( pparams->cl_viewangles, pparams->viewangles ); |
|
|
|
view->model = NULL; |
|
|
|
// allways idle in intermission |
|
old = v_idlescale; |
|
v_idlescale = 1; |
|
|
|
V_AddIdle( pparams ); |
|
|
|
if( gEngfuncs.IsSpectateOnly() ) |
|
{ |
|
// in HLTV we must go to 'intermission' position by ourself |
|
VectorCopy( gHUD.m_Spectator.m_cameraOrigin, pparams->vieworg ); |
|
VectorCopy( gHUD.m_Spectator.m_cameraAngles, pparams->viewangles ); |
|
} |
|
|
|
v_idlescale = old; |
|
|
|
v_cl_angles = pparams->cl_viewangles; |
|
v_origin = pparams->vieworg; |
|
v_angles = pparams->viewangles; |
|
} |
|
|
|
#define ORIGIN_BACKUP 64 |
|
#define ORIGIN_MASK ( ORIGIN_BACKUP - 1 ) |
|
|
|
typedef struct |
|
{ |
|
float Origins[ORIGIN_BACKUP][3]; |
|
float OriginTime[ORIGIN_BACKUP]; |
|
|
|
float Angles[ORIGIN_BACKUP][3]; |
|
float AngleTime[ORIGIN_BACKUP]; |
|
|
|
int CurrentOrigin; |
|
int CurrentAngle; |
|
} viewinterp_t; |
|
|
|
/* |
|
================== |
|
V_CalcRefdef |
|
|
|
================== |
|
*/ |
|
|
|
float offSetFactor = 1; |
|
|
|
void V_CalcNormalRefdef( struct ref_params_s *pparams ) |
|
{ |
|
cl_entity_t *ent, *view; |
|
int i; |
|
vec3_t angles; |
|
float bob, waterOffset; |
|
static viewinterp_t ViewInterp; |
|
|
|
static float oldz = 0; |
|
static float lasttime; |
|
|
|
vec3_t camAngles, camForward, camRight, camUp; |
|
cl_entity_t *pwater; |
|
|
|
if( gEngfuncs.IsSpectateOnly() ) |
|
{ |
|
ent = gEngfuncs.GetEntityByIndex( g_iUser2 ); |
|
} |
|
else |
|
{ |
|
// ent is the player model ( visible when out of body ) |
|
ent = gEngfuncs.GetLocalPlayer(); |
|
} |
|
|
|
// view is the weapon model (only visible from inside body) |
|
view = gEngfuncs.GetViewModel(); |
|
|
|
// transform the view offset by the model's matrix to get the offset from |
|
// model origin for the view |
|
bob = V_CalcBob( pparams ); |
|
|
|
// refresh position |
|
VectorCopy( pparams->simorg, pparams->vieworg ); |
|
//pparams->vieworg[2] += bob ; |
|
VectorAdd( pparams->vieworg, pparams->viewheight, pparams->vieworg ); |
|
|
|
if( pparams->health <= 0 ) |
|
{ |
|
VectorCopy( dead_viewangles, pparams->viewangles ); |
|
} |
|
else |
|
{ |
|
VectorCopy( pparams->cl_viewangles, pparams->viewangles ); |
|
} |
|
|
|
gEngfuncs.V_CalcShake(); |
|
gEngfuncs.V_ApplyShake( pparams->vieworg, pparams->viewangles, 1.0 ); |
|
|
|
// never let view origin sit exactly on a node line, because a water plane can |
|
// dissapear when viewed with the eye exactly on it. |
|
// FIXME, we send origin at 1/128 now, change this? |
|
// the server protocol only specifies to 1/16 pixel, so add 1/32 in each axis |
|
pparams->vieworg[0] += 1.0 / 32; |
|
pparams->vieworg[1] += 1.0 / 32; |
|
pparams->vieworg[2] += 1.0 / 32; |
|
|
|
// Check for problems around water, move the viewer artificially if necessary |
|
// -- this prevents drawing errors in GL due to waves |
|
|
|
waterOffset = 0; |
|
if( pparams->waterlevel >= 2 ) |
|
{ |
|
int contents, waterDist, waterEntity; |
|
vec3_t point; |
|
waterDist = cl_waterdist->value; |
|
|
|
if( pparams->hardware ) |
|
{ |
|
waterEntity = gEngfuncs.PM_WaterEntity( pparams->simorg ); |
|
if( waterEntity >= 0 && waterEntity < pparams->max_entities ) |
|
{ |
|
pwater = gEngfuncs.GetEntityByIndex( waterEntity ); |
|
if( pwater && ( pwater->model != NULL ) ) |
|
{ |
|
waterDist += ( pwater->curstate.scale * 16 ); // Add in wave height |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
waterEntity = 0; // Don't need this in software |
|
} |
|
|
|
VectorCopy( pparams->vieworg, point ); |
|
|
|
// Eyes are above water, make sure we're above the waves |
|
if( pparams->waterlevel == 2 ) |
|
{ |
|
point[2] -= waterDist; |
|
for( i = 0; i < waterDist; i++ ) |
|
{ |
|
contents = gEngfuncs.PM_PointContents( point, NULL ); |
|
if( contents > CONTENTS_WATER ) |
|
break; |
|
point[2] += 1; |
|
} |
|
waterOffset = ( point[2] + waterDist ) - pparams->vieworg[2]; |
|
} |
|
else |
|
{ |
|
// eyes are under water. Make sure we're far enough under |
|
point[2] += waterDist; |
|
|
|
for( i = 0; i < waterDist; i++ ) |
|
{ |
|
contents = gEngfuncs.PM_PointContents( point, NULL ); |
|
if( contents <= CONTENTS_WATER ) |
|
break; |
|
point[2] -= 1; |
|
} |
|
waterOffset = ( point[2] - waterDist ) - pparams->vieworg[2]; |
|
} |
|
} |
|
|
|
pparams->vieworg[2] += waterOffset; |
|
|
|
V_CalcViewRoll( pparams ); |
|
|
|
V_AddIdle( pparams ); |
|
|
|
// offsets |
|
if( pparams->health <= 0 ) |
|
{ |
|
VectorCopy( dead_viewangles, angles ); |
|
} |
|
else |
|
{ |
|
VectorCopy( pparams->cl_viewangles, angles ); |
|
} |
|
|
|
AngleVectors( angles, pparams->forward, pparams->right, pparams->up ); |
|
|
|
// don't allow cheats in multiplayer |
|
if( pparams->maxclients <= 1 ) |
|
{ |
|
for( i = 0; i < 3; i++ ) |
|
{ |
|
pparams->vieworg[i] += scr_ofsx->value*pparams->forward[i] + scr_ofsy->value*pparams->right[i] + scr_ofsz->value*pparams->up[i]; |
|
} |
|
} |
|
|
|
// Treating cam_ofs[2] as the distance |
|
if( CL_IsThirdPerson() ) |
|
{ |
|
vec3_t ofs; |
|
|
|
ofs[0] = ofs[1] = ofs[2] = 0.0; |
|
|
|
CL_CameraOffset( (float *)&ofs ); |
|
|
|
VectorCopy( ofs, camAngles ); |
|
camAngles[ROLL] = 0; |
|
|
|
AngleVectors( camAngles, camForward, camRight, camUp ); |
|
|
|
for( i = 0; i < 3; i++ ) |
|
{ |
|
pparams->vieworg[i] += -ofs[2] * camForward[i]; |
|
} |
|
|
|
//Calculations and setting externs for in_camera.cpp |
|
realOrigin = ent->origin; |
|
|
|
if( cam_contain->value == 1 ) |
|
{ |
|
Vector mousePos = Vector(-mouse_pos_extern[1], -mouse_pos_extern[0], 0.0f); |
|
|
|
// Check if the new camera position is in the wall |
|
Vector startPoint; |
|
|
|
if( cam_idealdist->value + 25 <= cam_minDist->value ) |
|
{ |
|
startPoint = ent->origin + Vector( 0.0f, 0.0f, cam_idealdist->value ); |
|
} |
|
else |
|
{ |
|
// if camera is inside the wall trace from the player's origin |
|
startPoint = ent->origin; |
|
} |
|
|
|
pmtrace_t *camTrace = gEngfuncs.PM_TraceLine( startPoint, startPoint + mousePos * offSetFactor * cam_lookahead->value, 1, 2, -1 ); |
|
|
|
// TODO: fix division |
|
|
|
camOffset[0] = camTrace->endpos.x - startPoint.x; |
|
camOffset[1] = camTrace->endpos.y - startPoint.y; |
|
|
|
realViewOrg[0] = camTrace->endpos.x - startPoint.x; |
|
realViewOrg[1] = camTrace->endpos.y - startPoint.y; |
|
realViewOrg[2] = 0; |
|
} |
|
else |
|
{ |
|
camOffset[0] = -mouse_pos_extern[1] * offSetFactor * cam_lookahead->value; |
|
camOffset[1] = -mouse_pos_extern[0] * offSetFactor * cam_lookahead->value; |
|
|
|
realViewOrg[0] = -mouse_pos_extern[1] * offSetFactor * cam_lookahead->value; |
|
realViewOrg[1] = -mouse_pos_extern[0] * offSetFactor * cam_lookahead->value; |
|
realViewOrg[2] = 0; |
|
} |
|
|
|
pparams->vieworg[0] += camOffset[0]; |
|
pparams->vieworg[1] += camOffset[1]; |
|
} |
|
|
|
// Give gun our viewangles |
|
if( pparams->health <= 0 ) |
|
{ |
|
VectorCopy( dead_viewangles, view->angles ); |
|
} |
|
else |
|
{ |
|
VectorCopy( pparams->cl_viewangles, view->angles ); |
|
} |
|
// set up gun position |
|
V_CalcGunAngle( pparams ); |
|
|
|
// Use predicted origin as view origin. |
|
VectorCopy( pparams->simorg, view->origin ); |
|
view->origin[2] += waterOffset; |
|
VectorAdd( view->origin, pparams->viewheight, view->origin ); |
|
|
|
// Let the viewmodel shake at about 10% of the amplitude |
|
gEngfuncs.V_ApplyShake( view->origin, view->angles, 0.9 ); |
|
|
|
for( i = 0; i < 3; i++ ) |
|
{ |
|
view->origin[i] += bob * 0.4 * pparams->forward[i]; |
|
} |
|
view->origin[2] += bob; |
|
|
|
// throw in a little tilt. |
|
view->angles[YAW] -= bob * 0.5; |
|
view->angles[ROLL] -= bob * 1; |
|
view->angles[PITCH] -= bob * 0.3; |
|
|
|
if( cl_viewbob && cl_viewbob->value ) |
|
VectorCopy( view->angles, view->curstate.angles ); |
|
|
|
// pushing the view origin down off of the same X/Z plane as the ent's origin will give the |
|
// gun a very nice 'shifting' effect when the player looks up/down. If there is a problem |
|
// with view model distortion, this may be a cause. (SJB). |
|
view->origin[2] -= 1; |
|
|
|
// fudge position around to keep amount of weapon visible |
|
// roughly equal with different FOV |
|
if( pparams->viewsize == 110 ) |
|
{ |
|
view->origin[2] += 1; |
|
} |
|
else if( pparams->viewsize == 100 ) |
|
{ |
|
view->origin[2] += 2; |
|
} |
|
else if( pparams->viewsize == 90 ) |
|
{ |
|
view->origin[2] += 1; |
|
} |
|
else if( pparams->viewsize == 80 ) |
|
{ |
|
view->origin[2] += 0.5; |
|
} |
|
|
|
// Add in the punchangle, if any |
|
VectorAdd( pparams->viewangles, pparams->punchangle, pparams->viewangles ); |
|
|
|
// Include client side punch, too |
|
VectorAdd( pparams->viewangles, (float *)&g_ev_punchangle, pparams->viewangles ); |
|
|
|
V_DropPunchAngle( pparams->frametime, (float *)&g_ev_punchangle ); |
|
|
|
// smooth out stair step ups |
|
#if 1 |
|
if( !pparams->smoothing && pparams->onground && pparams->simorg[2] - oldz > 0 ) |
|
{ |
|
float steptime; |
|
|
|
steptime = pparams->time - lasttime; |
|
|
|
if( steptime < 0 ) |
|
//FIXME I_Error( "steptime < 0" ); |
|
steptime = 0; |
|
|
|
oldz += steptime * 150; |
|
if( oldz > pparams->simorg[2] ) |
|
oldz = pparams->simorg[2]; |
|
if( pparams->simorg[2] - oldz > 18 ) |
|
oldz = pparams->simorg[2]- 18; |
|
pparams->vieworg[2] += oldz - pparams->simorg[2]; |
|
view->origin[2] += oldz - pparams->simorg[2]; |
|
} |
|
else |
|
{ |
|
oldz = pparams->simorg[2]; |
|
} |
|
#endif |
|
{ |
|
static float lastorg[3]; |
|
vec3_t delta; |
|
|
|
VectorSubtract( pparams->simorg, lastorg, delta ); |
|
|
|
if( Length( delta ) != 0.0 ) |
|
{ |
|
VectorCopy( pparams->simorg, ViewInterp.Origins[ViewInterp.CurrentOrigin & ORIGIN_MASK] ); |
|
ViewInterp.OriginTime[ViewInterp.CurrentOrigin & ORIGIN_MASK] = pparams->time; |
|
ViewInterp.CurrentOrigin++; |
|
|
|
VectorCopy( pparams->simorg, lastorg ); |
|
} |
|
} |
|
|
|
// Smooth out whole view in multiplayer when on trains, lifts |
|
if( cl_vsmoothing && cl_vsmoothing->value && |
|
( pparams->smoothing && ( pparams->maxclients > 1 ) ) ) |
|
{ |
|
int foundidx; |
|
float t; |
|
|
|
if( cl_vsmoothing->value < 0.0 ) |
|
{ |
|
gEngfuncs.Cvar_SetValue( "cl_vsmoothing", 0.0 ); |
|
} |
|
|
|
t = pparams->time - cl_vsmoothing->value; |
|
|
|
for( i = 1; i < ORIGIN_MASK; i++ ) |
|
{ |
|
foundidx = ViewInterp.CurrentOrigin - 1 - i; |
|
if( ViewInterp.OriginTime[foundidx & ORIGIN_MASK] <= t ) |
|
break; |
|
} |
|
|
|
if( i < ORIGIN_MASK && ViewInterp.OriginTime[foundidx & ORIGIN_MASK] != 0.0 ) |
|
{ |
|
// Interpolate |
|
vec3_t delta; |
|
double frac; |
|
double dt; |
|
vec3_t neworg; |
|
|
|
dt = ViewInterp.OriginTime[( foundidx + 1 ) & ORIGIN_MASK] - ViewInterp.OriginTime[foundidx & ORIGIN_MASK]; |
|
if( dt > 0.0 ) |
|
{ |
|
frac = ( t - ViewInterp.OriginTime[foundidx & ORIGIN_MASK] ) / dt; |
|
frac = min( 1.0, frac ); |
|
VectorSubtract( ViewInterp.Origins[( foundidx + 1 ) & ORIGIN_MASK], ViewInterp.Origins[foundidx & ORIGIN_MASK], delta ); |
|
VectorMA( ViewInterp.Origins[foundidx & ORIGIN_MASK], frac, delta, neworg ); |
|
|
|
// Dont interpolate large changes |
|
if( Length( delta ) < 64 ) |
|
{ |
|
VectorSubtract( neworg, pparams->simorg, delta ); |
|
|
|
VectorAdd( pparams->simorg, delta, pparams->simorg ); |
|
VectorAdd( pparams->vieworg, delta, pparams->vieworg ); |
|
VectorAdd( view->origin, delta, view->origin ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Store off v_angles before munging for third person |
|
v_angles = pparams->viewangles; |
|
v_lastAngles = pparams->viewangles; |
|
//v_cl_angles = pparams->cl_viewangles; // keep old user mouse angles ! |
|
if( CL_IsThirdPerson() ) |
|
{ |
|
VectorCopy( camAngles, pparams->viewangles ); |
|
float pitch = camAngles[0]; |
|
|
|
// Normalize angles |
|
if( pitch > 180 ) |
|
pitch -= 360.0; |
|
else if( pitch < -180 ) |
|
pitch += 360; |
|
|
|
// Player pitch is inverted |
|
pitch /= -3.0; |
|
|
|
// Slam local player's pitch value |
|
// ent->angles[0] = pitch; |
|
// ent->curstate.angles[0] = pitch; |
|
// ent->prevstate.angles[0] = pitch; |
|
// ent->latched.prevangles[0] = pitch; |
|
} |
|
|
|
// override all previous settings if the viewent isn't the client |
|
if( pparams->viewentity > pparams->maxclients ) |
|
{ |
|
cl_entity_t *viewentity; |
|
viewentity = gEngfuncs.GetEntityByIndex( pparams->viewentity ); |
|
if( viewentity ) |
|
{ |
|
VectorCopy( viewentity->origin, pparams->vieworg ); |
|
VectorCopy( viewentity->angles, pparams->viewangles ); |
|
|
|
// Store off overridden viewangles |
|
v_angles = pparams->viewangles; |
|
} |
|
} |
|
|
|
lasttime = pparams->time; |
|
|
|
v_origin = pparams->vieworg; |
|
} |
|
|
|
void V_SmoothInterpolateAngles( float * startAngle, float * endAngle, float * finalAngle, float degreesPerSec ) |
|
{ |
|
float absd, frac, d, threshhold; |
|
|
|
NormalizeAngles( startAngle ); |
|
NormalizeAngles( endAngle ); |
|
|
|
for( int i = 0; i < 3; i++ ) |
|
{ |
|
d = endAngle[i] - startAngle[i]; |
|
|
|
if( d > 180.0f ) |
|
{ |
|
d -= 360.0f; |
|
} |
|
else if( d < -180.0f ) |
|
{ |
|
d += 360.0f; |
|
} |
|
|
|
absd = fabs( d ); |
|
|
|
if( absd > 0.01f ) |
|
{ |
|
frac = degreesPerSec * v_frametime; |
|
|
|
threshhold= degreesPerSec / 4; |
|
|
|
if( absd < threshhold ) |
|
{ |
|
float h = absd / threshhold; |
|
h *= h; |
|
frac *= h; // slow down last degrees |
|
} |
|
|
|
if( frac > absd ) |
|
{ |
|
finalAngle[i] = endAngle[i]; |
|
} |
|
else |
|
{ |
|
if( d > 0 ) |
|
finalAngle[i] = startAngle[i] + frac; |
|
else |
|
finalAngle[i] = startAngle[i] - frac; |
|
} |
|
} |
|
else |
|
{ |
|
finalAngle[i] = endAngle[i]; |
|
} |
|
} |
|
|
|
NormalizeAngles( finalAngle ); |
|
} |
|
|
|
// Get the origin of the Observer based around the target's position and angles |
|
void V_GetChaseOrigin( float * angles, float * origin, float distance, float * returnvec ) |
|
{ |
|
vec3_t vecEnd; |
|
vec3_t forward; |
|
vec3_t vecStart; |
|
pmtrace_t *trace; |
|
int maxLoops = 8; |
|
|
|
int ignoreent = -1; // first, ignore no entity |
|
|
|
cl_entity_t *ent = NULL; |
|
|
|
// Trace back from the target using the player's view angles |
|
AngleVectors( angles, forward, NULL, NULL ); |
|
|
|
VectorScale( forward, -1, forward ); |
|
|
|
VectorCopy( origin, vecStart ); |
|
|
|
VectorMA( vecStart, distance , forward, vecEnd ); |
|
|
|
while( maxLoops > 0 ) |
|
{ |
|
trace = gEngfuncs.PM_TraceLine( vecStart, vecEnd, PM_TRACELINE_PHYSENTSONLY, 2, ignoreent ); |
|
|
|
// WARNING! trace->ent is is the number in physent list not the normal entity number |
|
|
|
if( trace->ent <= 0) |
|
break; // we hit the world or nothing, stop trace |
|
|
|
ent = gEngfuncs.GetEntityByIndex( PM_GetPhysEntInfo( trace->ent ) ); |
|
|
|
if( ent == NULL ) |
|
break; |
|
|
|
// hit non-player solid BSP , stop here |
|
if( ent->curstate.solid == SOLID_BSP && !ent->player ) |
|
break; |
|
|
|
// if close enought to end pos, stop, otherwise continue trace |
|
if( Distance( trace->endpos, vecEnd ) < 1.0f ) |
|
{ |
|
break; |
|
} |
|
else |
|
{ |
|
ignoreent = trace->ent; // ignore last hit entity |
|
VectorCopy( trace->endpos, vecStart ); |
|
} |
|
|
|
maxLoops--; |
|
} |
|
|
|
/* if( ent ) |
|
{ |
|
gEngfuncs.Con_Printf( "Trace loops %i , entity %i, model %s, solid %i\n",(8-maxLoops),ent->curstate.number, ent->model->name , ent->curstate.solid ); |
|
} */ |
|
|
|
VectorMA( trace->endpos, 4, trace->plane.normal, returnvec ); |
|
|
|
v_lastDistance = Distance( trace->endpos, origin ); // real distance without offset |
|
} |
|
|
|
/*void V_GetDeathCam( cl_entity_t * ent1, cl_entity_t * ent2, float * angle, float * origin ) |
|
{ |
|
float newAngle[3]; float newOrigin[3]; |
|
|
|
float distance = 168.0f; |
|
|
|
v_lastDistance += v_frametime * 96.0f; // move unit per seconds back |
|
|
|
if( v_resetCamera ) |
|
v_lastDistance = 64.0f; |
|
|
|
if( distance > v_lastDistance ) |
|
distance = v_lastDistance; |
|
|
|
VectorCopy( ent1->origin, newOrigin ); |
|
|
|
if( ent1->player ) |
|
newOrigin[2] += 17; // head level of living player |
|
|
|
// get new angle towards second target |
|
if( ent2 ) |
|
{ |
|
VectorSubtract( ent2->origin, ent1->origin, newAngle ); |
|
VectorAngles( newAngle, newAngle ); |
|
newAngle[0] = -newAngle[0]; |
|
} |
|
else |
|
{ |
|
// if no second target is given, look down to dead player |
|
newAngle[0] = 90.0f; |
|
newAngle[1] = 0.0f; |
|
newAngle[2] = 0; |
|
} |
|
|
|
// and smooth view |
|
V_SmoothInterpolateAngles( v_lastAngles, newAngle, angle, 120.0f ); |
|
|
|
V_GetChaseOrigin( angle, newOrigin, distance, origin ); |
|
|
|
VectorCopy( angle, v_lastAngles ); |
|
}*/ |
|
|
|
void V_GetSingleTargetCam( cl_entity_t * ent1, float * angle, float * origin ) |
|
{ |
|
float newAngle[3]; float newOrigin[3]; |
|
|
|
int flags = gHUD.m_Spectator.m_iObserverFlags; |
|
|
|
// see is target is a dead player |
|
qboolean deadPlayer = ent1->player && ( ent1->curstate.solid == SOLID_NOT ); |
|
|
|
float dfactor = ( flags & DRC_FLAG_DRAMATIC ) ? -1.0f : 1.0f; |
|
|
|
float distance = 112.0f + ( 16.0f * dfactor ); // get close if dramatic; |
|
|
|
// go away in final scenes or if player just died |
|
if( flags & DRC_FLAG_FINAL ) |
|
distance *= 2.0f; |
|
else if( deadPlayer ) |
|
distance *= 1.5f; |
|
|
|
// let v_lastDistance float smoothly away |
|
v_lastDistance += v_frametime * 32.0f; // move unit per seconds back |
|
|
|
if( distance > v_lastDistance ) |
|
distance = v_lastDistance; |
|
|
|
VectorCopy( ent1->origin, newOrigin ); |
|
|
|
if( ent1->player ) |
|
{ |
|
if( deadPlayer ) |
|
newOrigin[2] += 2; //laying on ground |
|
else |
|
newOrigin[2] += 17; // head level of living player |
|
} |
|
else |
|
newOrigin[2]+= 8; // object, tricky, must be above bomb in CS |
|
|
|
// we have no second target, choose view direction based on |
|
// show front of primary target |
|
VectorCopy( ent1->angles, newAngle ); |
|
|
|
// show dead players from front, normal players back |
|
if( flags & DRC_FLAG_FACEPLAYER ) |
|
newAngle[1] += 180.0f; |
|
|
|
newAngle[0] += 12.5f * dfactor; // lower angle if dramatic |
|
|
|
// if final scene (bomb), show from real high pos |
|
if( flags & DRC_FLAG_FINAL ) |
|
newAngle[0] = 22.5f; |
|
|
|
// choose side of object/player |
|
if( flags & DRC_FLAG_SIDE ) |
|
newAngle[1] += 22.5f; |
|
else |
|
newAngle[1] -= 22.5f; |
|
|
|
V_SmoothInterpolateAngles( v_lastAngles, newAngle, angle, 120.0f ); |
|
|
|
// HACK, if player is dead don't clip against his dead body, can't check this |
|
V_GetChaseOrigin( angle, newOrigin, distance, origin ); |
|
} |
|
|
|
float MaxAngleBetweenAngles( float *a1, float *a2 ) |
|
{ |
|
float d, maxd = 0.0f; |
|
|
|
NormalizeAngles( a1 ); |
|
NormalizeAngles( a2 ); |
|
|
|
for( int i = 0; i < 3; i++ ) |
|
{ |
|
d = a2[i] - a1[i]; |
|
if( d > 180 ) |
|
{ |
|
d -= 360; |
|
} |
|
else if( d < -180 ) |
|
{ |
|
d += 360; |
|
} |
|
|
|
d = fabs( d ); |
|
|
|
if( d > maxd ) |
|
maxd = d; |
|
} |
|
|
|
return maxd; |
|
} |
|
|
|
void V_GetDoubleTargetsCam( cl_entity_t *ent1, cl_entity_t *ent2, float *angle, float *origin ) |
|
{ |
|
float newAngle[3], newOrigin[3], tempVec[3]; |
|
|
|
int flags = gHUD.m_Spectator.m_iObserverFlags; |
|
|
|
float dfactor = ( flags & DRC_FLAG_DRAMATIC ) ? -1.0f : 1.0f; |
|
|
|
float distance = 112.0f + ( 16.0f * dfactor ); // get close if dramatic; |
|
|
|
// go away in final scenes or if player just died |
|
if( flags & DRC_FLAG_FINAL ) |
|
distance *= 2.0f; |
|
|
|
// let v_lastDistance float smoothly away |
|
v_lastDistance += v_frametime * 32.0f; // move unit per seconds back |
|
|
|
if( distance > v_lastDistance ) |
|
distance = v_lastDistance; |
|
|
|
VectorCopy( ent1->origin, newOrigin ); |
|
|
|
if( ent1->player ) |
|
newOrigin[2] += 17; // head level of living player |
|
else |
|
newOrigin[2] += 8; // object, tricky, must be above bomb in CS |
|
|
|
// get new angle towards second target |
|
VectorSubtract( ent2->origin, ent1->origin, newAngle ); |
|
|
|
VectorAngles( newAngle, newAngle ); |
|
newAngle[0] = -newAngle[0]; |
|
|
|
// set angle diffrent in Dramtaic scenes |
|
newAngle[0] += 12.5f * dfactor; // lower angle if dramatic |
|
|
|
if( flags & DRC_FLAG_SIDE ) |
|
newAngle[1] += 22.5f; |
|
else |
|
newAngle[1] -= 22.5f; |
|
|
|
float d = MaxAngleBetweenAngles( v_lastAngles, newAngle ); |
|
|
|
if( ( d < v_cameraFocusAngle) && ( v_cameraMode == CAM_MODE_RELAX ) ) |
|
{ |
|
// difference is to small and we are in relax camera mode, keep viewangles |
|
VectorCopy( v_lastAngles, newAngle ); |
|
} |
|
else if( ( d < v_cameraRelaxAngle ) && ( v_cameraMode == CAM_MODE_FOCUS ) ) |
|
{ |
|
// we catched up with our target, relax again |
|
v_cameraMode = CAM_MODE_RELAX; |
|
} |
|
else |
|
{ |
|
// target move too far away, focus camera again |
|
v_cameraMode = CAM_MODE_FOCUS; |
|
} |
|
|
|
// and smooth view, if not a scene cut |
|
if( v_resetCamera || ( v_cameraMode == CAM_MODE_RELAX ) ) |
|
{ |
|
VectorCopy( newAngle, angle ); |
|
} |
|
else |
|
{ |
|
V_SmoothInterpolateAngles( v_lastAngles, newAngle, angle, 180.0f ); |
|
} |
|
|
|
V_GetChaseOrigin( newAngle, newOrigin, distance, origin ); |
|
|
|
// move position up, if very close at target |
|
if( v_lastDistance < 64.0f ) |
|
origin[2] += 16.0f * ( 1.0f - ( v_lastDistance / 64.0f ) ); |
|
|
|
// calculate angle to second target |
|
VectorSubtract( ent2->origin, origin, tempVec ); |
|
VectorAngles( tempVec, tempVec ); |
|
tempVec[0] = -tempVec[0]; |
|
|
|
/* take middle between two viewangles |
|
InterpolateAngles( newAngle, tempVec, newAngle, 0.5f ); */ |
|
} |
|
|
|
void V_GetDirectedChasePosition(cl_entity_t *ent1, cl_entity_t *ent2,float *angle, float *origin) |
|
{ |
|
if( v_resetCamera ) |
|
{ |
|
v_lastDistance = 4096.0f; |
|
// v_cameraMode = CAM_MODE_FOCUS; |
|
} |
|
|
|
if( ( ent2 == (cl_entity_t*)0xFFFFFFFF ) || ( ent1->player && ( ent1->curstate.solid == SOLID_NOT ) ) ) |
|
{ |
|
// we have no second target or player just died |
|
V_GetSingleTargetCam( ent1, angle, origin ); |
|
} |
|
else if( ent2 ) |
|
{ |
|
// keep both target in view |
|
V_GetDoubleTargetsCam( ent1, ent2, angle, origin ); |
|
} |
|
else |
|
{ |
|
// second target disappeard somehow (dead) |
|
|
|
// keep last good viewangle |
|
float newOrigin[3]; |
|
|
|
int flags = gHUD.m_Spectator.m_iObserverFlags; |
|
|
|
float dfactor = ( flags & DRC_FLAG_DRAMATIC ) ? -1.0f : 1.0f; |
|
|
|
float distance = 112.0f + ( 16.0f * dfactor ); // get close if dramatic; |
|
|
|
// go away in final scenes or if player just died |
|
if( flags & DRC_FLAG_FINAL ) |
|
distance *= 2.0f; |
|
|
|
// let v_lastDistance float smoothly away |
|
v_lastDistance+= v_frametime * 32.0f; // move unit per seconds back |
|
|
|
if( distance > v_lastDistance ) |
|
distance = v_lastDistance; |
|
|
|
VectorCopy( ent1->origin, newOrigin ); |
|
|
|
if( ent1->player ) |
|
newOrigin[2] += 17; // head level of living player |
|
else |
|
newOrigin[2] += 8; // object, tricky, must be above bomb in CS |
|
|
|
V_GetChaseOrigin( angle, newOrigin, distance, origin ); |
|
} |
|
|
|
VectorCopy( angle, v_lastAngles ); |
|
} |
|
|
|
void V_GetChasePos( int target, float *cl_angles, float *origin, float *angles ) |
|
{ |
|
cl_entity_t *ent = NULL; |
|
|
|
if( target ) |
|
{ |
|
ent = gEngfuncs.GetEntityByIndex( target ); |
|
} |
|
|
|
if( !ent ) |
|
{ |
|
// just copy a save in-map position |
|
VectorCopy( vJumpAngles, angles ); |
|
VectorCopy( vJumpOrigin, origin ); |
|
return; |
|
} |
|
|
|
if( gHUD.m_Spectator.m_autoDirector->value ) |
|
{ |
|
if( g_iUser3 ) |
|
V_GetDirectedChasePosition( ent, gEngfuncs.GetEntityByIndex( g_iUser3 ), |
|
angles, origin ); |
|
else |
|
V_GetDirectedChasePosition( ent, (cl_entity_t*)0xFFFFFFFF, |
|
angles, origin ); |
|
} |
|
else |
|
{ |
|
if( cl_angles == NULL ) // no mouse angles given, use entity angles ( locked mode ) |
|
{ |
|
VectorCopy( ent->angles, angles); |
|
angles[0] *= -1; |
|
} |
|
else |
|
VectorCopy( cl_angles, angles ); |
|
|
|
VectorCopy( ent->origin, origin ); |
|
|
|
origin[2] += 28; // DEFAULT_VIEWHEIGHT - some offset |
|
|
|
V_GetChaseOrigin( angles, origin, cl_chasedist->value, origin ); |
|
} |
|
|
|
v_resetCamera = false; |
|
} |
|
|
|
void V_ResetChaseCam() |
|
{ |
|
v_resetCamera = true; |
|
} |
|
|
|
void V_GetInEyePos( int target, float *origin, float *angles ) |
|
{ |
|
if( !target ) |
|
{ |
|
// just copy a save in-map position |
|
VectorCopy ( vJumpAngles, angles ); |
|
VectorCopy ( vJumpOrigin, origin ); |
|
return; |
|
}; |
|
|
|
cl_entity_t *ent = gEngfuncs.GetEntityByIndex( target ); |
|
|
|
if( !ent ) |
|
return; |
|
|
|
VectorCopy( ent->origin, origin ); |
|
VectorCopy( ent->angles, angles ); |
|
|
|
angles[PITCH] *= -3.0f; // see CL_ProcessEntityUpdate() |
|
|
|
if( ent->curstate.solid == SOLID_NOT ) |
|
{ |
|
angles[ROLL] = 80; // dead view angle |
|
origin[2] += -8 ; // PM_DEAD_VIEWHEIGHT |
|
} |
|
else if( ent->curstate.usehull == 1 ) |
|
origin[2] += 12; // VEC_DUCK_VIEW; |
|
else |
|
// exacty eye position can't be caluculated since it depends on |
|
// client values like cl_bobcycle, this offset matches the default values |
|
origin[2] += 28; // DEFAULT_VIEWHEIGHT |
|
} |
|
|
|
void V_GetMapFreePosition( float *cl_angles, float *origin, float *angles ) |
|
{ |
|
vec3_t forward; |
|
vec3_t zScaledTarget; |
|
|
|
VectorCopy( cl_angles, angles ); |
|
|
|
// modify angles since we don't wanna see map's bottom |
|
angles[0] = 51.25f + 38.75f * ( angles[0] / 90.0f ); |
|
|
|
zScaledTarget[0] = gHUD.m_Spectator.m_mapOrigin[0]; |
|
zScaledTarget[1] = gHUD.m_Spectator.m_mapOrigin[1]; |
|
zScaledTarget[2] = gHUD.m_Spectator.m_mapOrigin[2] * ( ( 90.0f - angles[0] ) / 90.0f ); |
|
|
|
AngleVectors( angles, forward, NULL, NULL ); |
|
|
|
VectorNormalize( forward ); |
|
|
|
VectorMA( zScaledTarget, -( 4096.0f / gHUD.m_Spectator.m_mapZoom ), forward, origin ); |
|
} |
|
|
|
void V_GetMapChasePosition( int target, float *cl_angles, float *origin, float *angles ) |
|
{ |
|
vec3_t forward; |
|
|
|
if( target ) |
|
{ |
|
cl_entity_t *ent = gEngfuncs.GetEntityByIndex( target ); |
|
|
|
if( gHUD.m_Spectator.m_autoDirector->value ) |
|
{ |
|
// this is done to get the angles made by director mode |
|
V_GetChasePos( target, cl_angles, origin, angles ); |
|
VectorCopy( ent->origin, origin ); |
|
|
|
// keep fix chase angle horizontal |
|
angles[0] = 45.0f; |
|
} |
|
else |
|
{ |
|
VectorCopy( cl_angles, angles ); |
|
VectorCopy( ent->origin, origin ); |
|
|
|
// modify angles since we don't wanna see map's bottom |
|
angles[0] = 51.25f + 38.75f * ( angles[0] / 90.0f ); |
|
} |
|
} |
|
else |
|
{ |
|
// keep out roaming position, but modify angles |
|
VectorCopy( cl_angles, angles ); |
|
angles[0] = 51.25f + 38.75f * ( angles[0] / 90.0f ); |
|
} |
|
|
|
origin[2] *= ( ( 90.0f - angles[0] ) / 90.0f ); |
|
angles[2] = 0.0f; // don't roll angle (if chased player is dead) |
|
|
|
AngleVectors( angles, forward, NULL, NULL ); |
|
|
|
VectorNormalize( forward ); |
|
|
|
VectorMA( origin, -1536, forward, origin ); |
|
} |
|
|
|
int V_FindViewModelByWeaponModel( int weaponindex ) |
|
{ |
|
static const char *modelmap[][2] = |
|
{ |
|
{ "models/p_crossbow.mdl", "models/v_crossbow.mdl" }, |
|
{ "models/p_crowbar.mdl", "models/v_crowbar.mdl" }, |
|
{ "models/p_egon.mdl", "models/v_egon.mdl" }, |
|
{ "models/p_gauss.mdl", "models/v_gauss.mdl" }, |
|
{ "models/p_9mmhandgun.mdl", "models/v_9mmhandgun.mdl" }, |
|
{ "models/p_grenade.mdl", "models/v_grenade.mdl" }, |
|
{ "models/p_hgun.mdl", "models/v_hgun.mdl" }, |
|
{ "models/p_9mmAR.mdl", "models/v_9mmAR.mdl" }, |
|
{ "models/p_357.mdl", "models/v_357.mdl" }, |
|
{ "models/p_rpg.mdl", "models/v_rpg.mdl" }, |
|
{ "models/p_shotgun.mdl", "models/v_shotgun.mdl" }, |
|
{ "models/p_squeak.mdl", "models/v_squeak.mdl" }, |
|
{ "models/p_tripmine.mdl", "models/v_tripmine.mdl" }, |
|
{ "models/p_satchel_radio.mdl", "models/v_satchel_radio.mdl" }, |
|
{ "models/p_satchel.mdl", "models/v_satchel.mdl" }, |
|
{ NULL, NULL } |
|
}; |
|
|
|
struct model_s *weaponModel = IEngineStudio.GetModelByIndex( weaponindex ); |
|
|
|
if( weaponModel ) |
|
{ |
|
int len = strlen( weaponModel->name ); |
|
int i = 0; |
|
|
|
while( modelmap[i][0] != NULL ) |
|
{ |
|
if( !strnicmp( weaponModel->name, modelmap[i][0], len ) ) |
|
{ |
|
return gEngfuncs.pEventAPI->EV_FindModelIndex( modelmap[i][1] ); |
|
} |
|
i++; |
|
} |
|
|
|
return 0; |
|
} |
|
else |
|
return 0; |
|
} |
|
|
|
/* |
|
================== |
|
V_CalcSpectatorRefdef |
|
|
|
================== |
|
*/ |
|
void V_CalcSpectatorRefdef( struct ref_params_s * pparams ) |
|
{ |
|
static vec3_t velocity( 0.0f, 0.0f, 0.0f ); |
|
|
|
static int lastWeaponModelIndex = 0; |
|
static int lastViewModelIndex = 0; |
|
|
|
cl_entity_t *ent = gEngfuncs.GetEntityByIndex( g_iUser2 ); |
|
|
|
pparams->onlyClientDraw = false; |
|
|
|
// refresh position |
|
VectorCopy( pparams->simorg, v_sim_org ); |
|
|
|
// get old values |
|
VectorCopy( pparams->cl_viewangles, v_cl_angles ); |
|
VectorCopy( pparams->viewangles, v_angles ); |
|
VectorCopy( pparams->vieworg, v_origin ); |
|
|
|
if( ( g_iUser1 == OBS_IN_EYE || gHUD.m_Spectator.m_pip->value == INSET_IN_EYE ) && ent ) |
|
{ |
|
// calculate player velocity |
|
float timeDiff = ent->curstate.msg_time - ent->prevstate.msg_time; |
|
|
|
if( timeDiff > 0 ) |
|
{ |
|
vec3_t distance; |
|
VectorSubtract( ent->prevstate.origin, ent->curstate.origin, distance ); |
|
VectorScale( distance, 1 / timeDiff, distance ); |
|
|
|
velocity[0] = velocity[0] * 0.9f + distance[0] * 0.1f; |
|
velocity[1] = velocity[1] * 0.9f + distance[1] * 0.1f; |
|
velocity[2] = velocity[2] * 0.9f + distance[2] * 0.1f; |
|
|
|
VectorCopy( velocity, pparams->simvel ); |
|
} |
|
|
|
// predict missing client data and set weapon model ( in HLTV mode or inset in eye mode ) |
|
if( gEngfuncs.IsSpectateOnly() ) |
|
{ |
|
V_GetInEyePos( g_iUser2, pparams->simorg, pparams->cl_viewangles ); |
|
|
|
pparams->health = 1; |
|
|
|
cl_entity_t *gunModel = gEngfuncs.GetViewModel(); |
|
|
|
if( lastWeaponModelIndex != ent->curstate.weaponmodel ) |
|
{ |
|
// weapon model changed |
|
lastWeaponModelIndex = ent->curstate.weaponmodel; |
|
lastViewModelIndex = V_FindViewModelByWeaponModel( lastWeaponModelIndex ); |
|
if( lastViewModelIndex ) |
|
{ |
|
gEngfuncs.pfnWeaponAnim( 0, 0 ); // reset weapon animation |
|
} |
|
else |
|
{ |
|
// model not found |
|
gunModel->model = NULL; // disable weapon model |
|
lastWeaponModelIndex = lastViewModelIndex = 0; |
|
} |
|
} |
|
|
|
if( lastViewModelIndex ) |
|
{ |
|
gunModel->model = IEngineStudio.GetModelByIndex( lastViewModelIndex ); |
|
gunModel->curstate.modelindex = lastViewModelIndex; |
|
gunModel->curstate.frame = 0; |
|
gunModel->curstate.colormap = 0; |
|
gunModel->index = g_iUser2; |
|
} |
|
else |
|
{ |
|
gunModel->model = NULL; // disable weaopn model |
|
} |
|
} |
|
else |
|
{ |
|
// only get viewangles from entity |
|
VectorCopy( ent->angles, pparams->cl_viewangles ); |
|
pparams->cl_viewangles[PITCH] *= -3.0f; // see CL_ProcessEntityUpdate() |
|
} |
|
} |
|
|
|
v_frametime = pparams->frametime; |
|
|
|
if( pparams->nextView == 0 ) |
|
{ |
|
// first renderer cycle, full screen |
|
switch( g_iUser1 ) |
|
{ |
|
case OBS_CHASE_LOCKED: |
|
V_GetChasePos( g_iUser2, NULL, v_origin, v_angles ); |
|
break; |
|
case OBS_CHASE_FREE: |
|
V_GetChasePos( g_iUser2, v_cl_angles, v_origin, v_angles ); |
|
break; |
|
case OBS_ROAMING: |
|
VectorCopy( v_cl_angles, v_angles ); |
|
VectorCopy( v_sim_org, v_origin ); |
|
break; |
|
case OBS_IN_EYE: |
|
V_CalcNormalRefdef( pparams ); |
|
break; |
|
case OBS_MAP_FREE: |
|
pparams->onlyClientDraw = true; |
|
V_GetMapFreePosition( v_cl_angles, v_origin, v_angles ); |
|
break; |
|
case OBS_MAP_CHASE: |
|
pparams->onlyClientDraw = true; |
|
V_GetMapChasePosition( g_iUser2, v_cl_angles, v_origin, v_angles ); |
|
break; |
|
} |
|
|
|
if( gHUD.m_Spectator.m_pip->value ) |
|
pparams->nextView = 1; // force a second renderer view |
|
|
|
gHUD.m_Spectator.m_iDrawCycle = 0; |
|
} |
|
else |
|
{ |
|
// second renderer cycle, inset window |
|
// set inset parameters |
|
pparams->viewport[0] = XRES( gHUD.m_Spectator.m_OverviewData.insetWindowX ); // change viewport to inset window |
|
pparams->viewport[1] = YRES( gHUD.m_Spectator.m_OverviewData.insetWindowY ); |
|
pparams->viewport[2] = XRES( gHUD.m_Spectator.m_OverviewData.insetWindowWidth ); |
|
pparams->viewport[3] = YRES( gHUD.m_Spectator.m_OverviewData.insetWindowHeight ); |
|
pparams->nextView = 0; // on further view |
|
|
|
// override some settings in certain modes |
|
switch( (int)gHUD.m_Spectator.m_pip->value ) |
|
{ |
|
case INSET_CHASE_FREE: |
|
V_GetChasePos( g_iUser2, v_cl_angles, v_origin, v_angles ); |
|
break; |
|
case INSET_IN_EYE: |
|
V_CalcNormalRefdef( pparams ); |
|
break; |
|
case INSET_MAP_FREE: |
|
pparams->onlyClientDraw = true; |
|
V_GetMapFreePosition( v_cl_angles, v_origin, v_angles ); |
|
break; |
|
case INSET_MAP_CHASE: |
|
pparams->onlyClientDraw = true; |
|
if( g_iUser1 == OBS_ROAMING ) |
|
V_GetMapChasePosition( 0, v_cl_angles, v_origin, v_angles ); |
|
else |
|
V_GetMapChasePosition( g_iUser2, v_cl_angles, v_origin, v_angles ); |
|
break; |
|
} |
|
|
|
gHUD.m_Spectator.m_iDrawCycle = 1; |
|
} |
|
|
|
// write back new values into pparams |
|
VectorCopy( v_cl_angles, pparams->cl_viewangles ); |
|
VectorCopy( v_angles, pparams->viewangles ) |
|
VectorCopy( v_origin, pparams->vieworg ); |
|
} |
|
|
|
void DLLEXPORT V_CalcRefdef( struct ref_params_s *pparams ) |
|
{ |
|
// intermission / finale rendering |
|
if( pparams->intermission ) |
|
{ |
|
V_CalcIntermissionRefdef( pparams ); |
|
} |
|
else if( pparams->spectator || g_iUser1 ) // g_iUser true if in spectator mode |
|
{ |
|
V_CalcSpectatorRefdef( pparams ); |
|
} |
|
else if( !pparams->paused ) |
|
{ |
|
V_CalcNormalRefdef( pparams ); |
|
} |
|
/* |
|
// Example of how to overlay the whole screen with red at 50 % alpha |
|
#define SF_TEST |
|
#if defined SF_TEST |
|
{ |
|
screenfade_t sf; |
|
gEngfuncs.pfnGetScreenFade( &sf ); |
|
|
|
sf.fader = 255; |
|
sf.fadeg = 0; |
|
sf.fadeb = 0; |
|
sf.fadealpha = 128; |
|
sf.fadeFlags = FFADE_STAYOUT | FFADE_OUT; |
|
|
|
gEngfuncs.pfnSetScreenFade( &sf ); |
|
} |
|
#endif |
|
*/ |
|
} |
|
|
|
/* |
|
============= |
|
V_DropPunchAngle |
|
|
|
============= |
|
*/ |
|
void V_DropPunchAngle( float frametime, float *ev_punchangle ) |
|
{ |
|
float len; |
|
|
|
len = VectorNormalize( ev_punchangle ); |
|
len -= ( 10.0 + len * 0.5 ) * frametime; |
|
len = max( len, 0.0 ); |
|
VectorScale( ev_punchangle, len, ev_punchangle ); |
|
} |
|
|
|
/* |
|
============= |
|
V_PunchAxis |
|
|
|
Client side punch effect |
|
============= |
|
*/ |
|
void V_PunchAxis( int axis, float punch ) |
|
{ |
|
g_ev_punchangle[axis] = punch; |
|
} |
|
|
|
/* |
|
============= |
|
V_Init |
|
============= |
|
*/ |
|
void V_Init( void ) |
|
{ |
|
scr_ofsx = gEngfuncs.pfnRegisterVariable( "scr_ofsx","0", 0 ); |
|
scr_ofsy = gEngfuncs.pfnRegisterVariable( "scr_ofsy","0", 0 ); |
|
scr_ofsz = gEngfuncs.pfnRegisterVariable( "scr_ofsz","0", 0 ); |
|
|
|
v_centermove = gEngfuncs.pfnRegisterVariable( "v_centermove", "0.15", 0 ); |
|
v_centerspeed = gEngfuncs.pfnRegisterVariable( "v_centerspeed","500", 0 ); |
|
|
|
cl_bobcycle = gEngfuncs.pfnRegisterVariable( "cl_bobcycle","0.8", 0 );// best default for my experimental gun wag (sjb) |
|
cl_bob = gEngfuncs.pfnRegisterVariable( "cl_bob","0.01", 0 );// best default for my experimental gun wag (sjb) |
|
cl_bobup = gEngfuncs.pfnRegisterVariable( "cl_bobup","0.5", 0 ); |
|
cl_waterdist = gEngfuncs.pfnRegisterVariable( "cl_waterdist","4", 0 ); |
|
cl_chasedist = gEngfuncs.pfnRegisterVariable( "cl_chasedist","112", 0 ); |
|
} |
|
|
|
//#define TRACE_TEST |
|
#if defined( TRACE_TEST ) |
|
|
|
extern float in_fov; |
|
/* |
|
==================== |
|
CalcFov |
|
==================== |
|
*/ |
|
float CalcFov( float fov_x, float width, float height ) |
|
{ |
|
float a; |
|
float x; |
|
|
|
if( fov_x < 1 || fov_x > 179 ) |
|
fov_x = 90; // error, set to 90 |
|
|
|
x = width / tan( fov_x / 360 * M_PI ); |
|
|
|
a = atan ( height / x ); |
|
|
|
a = a * 360 / M_PI; |
|
|
|
return a; |
|
} |
|
|
|
int hitent = -1; |
|
|
|
void V_Move( int mx, int my ) |
|
{ |
|
float fov; |
|
float fx, fy; |
|
float dx, dy; |
|
float c_x, c_y; |
|
float dX, dY; |
|
vec3_t forward, up, right; |
|
vec3_t newangles; |
|
|
|
vec3_t farpoint; |
|
pmtrace_t tr; |
|
|
|
fov = CalcFov( in_fov, (float)ScreenWidth, (float)ScreenHeight ); |
|
|
|
c_x = (float)ScreenWidth / 2.0; |
|
c_y = (float)ScreenHeight / 2.0; |
|
|
|
dx = (float)mx - c_x; |
|
dy = (float)my - c_y; |
|
|
|
// Proportion we moved in each direction |
|
fx = dx / c_x; |
|
fy = dy / c_y; |
|
|
|
dX = fx * in_fov / 2.0 ; |
|
dY = fy * fov / 2.0; |
|
|
|
newangles = v_angles; |
|
|
|
newangles[YAW] -= dX; |
|
newangles[PITCH] += dY; |
|
|
|
// Now rotate v_forward around that point |
|
AngleVectors( newangles, forward, right, up ); |
|
|
|
farpoint = v_origin + 8192 * forward; |
|
|
|
// Trace |
|
tr = *( gEngfuncs.PM_TraceLine( (float *)&v_origin, (float *)&farpoint, PM_TRACELINE_PHYSENTSONLY, 2 /*point sized hull*/, -1 ) ); |
|
|
|
if( tr.fraction != 1.0 && tr.ent != 0 ) |
|
{ |
|
hitent = PM_GetPhysEntInfo( tr.ent ); |
|
PM_ParticleLine( (float *)&v_origin, (float *)&tr.endpos, 5, 1.0, 0.0 ); |
|
} |
|
else |
|
{ |
|
hitent = -1; |
|
} |
|
} |
|
#endif
|
|
|