862 lines
20 KiB
C++
862 lines
20 KiB
C++
|
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
|
|||
|
//
|
|||
|
// Purpose:
|
|||
|
//
|
|||
|
//=============================================================================//
|
|||
|
|
|||
|
#include "cbase.h"
|
|||
|
|
|||
|
#if defined( REPLAY_ENABLED )
|
|||
|
#include "replaycamera.h"
|
|||
|
#include "cdll_client_int.h"
|
|||
|
#include "util_shared.h"
|
|||
|
#include "prediction.h"
|
|||
|
#include "movevars_shared.h"
|
|||
|
#include "in_buttons.h"
|
|||
|
#include "text_message.h"
|
|||
|
#include "vgui_controls/controls.h"
|
|||
|
#include "vgui/ILocalize.h"
|
|||
|
#include "vguicenterprint.h"
|
|||
|
#include "game/client/iviewport.h"
|
|||
|
#include <KeyValues.h>
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//ConVar spec_autodirector( "spec_autodirector", "1", FCVAR_CLIENTDLL | FCVAR_CLIENTCMD_CAN_EXECUTE, "Auto-director chooses best view modes while spectating" );
|
|||
|
extern ConVar spec_autodirector;
|
|||
|
|
|||
|
// memdbgon must be the last include file in a .cpp file!!!
|
|||
|
#include "tier0/memdbgon.h"
|
|||
|
|
|||
|
#define CHASE_CAM_DISTANCE 96.0f
|
|||
|
#define WALL_OFFSET 6.0f
|
|||
|
|
|||
|
static Vector WALL_MIN(-WALL_OFFSET,-WALL_OFFSET,-WALL_OFFSET);
|
|||
|
static Vector WALL_MAX(WALL_OFFSET,WALL_OFFSET,WALL_OFFSET);
|
|||
|
static const ConVar *replay_transmitall = NULL;
|
|||
|
|
|||
|
//////////////////////////////////////////////////////////////////////
|
|||
|
// Construction/Destruction
|
|||
|
//////////////////////////////////////////////////////////////////////
|
|||
|
|
|||
|
// converts all '\r' characters to '\n', so that the engine can deal with the properly
|
|||
|
// returns a pointer to str
|
|||
|
static wchar_t* ConvertCRtoNL( wchar_t *str )
|
|||
|
{
|
|||
|
for ( wchar_t *ch = str; *ch != 0; ch++ )
|
|||
|
if ( *ch == L'\r' )
|
|||
|
*ch = L'\n';
|
|||
|
return str;
|
|||
|
}
|
|||
|
|
|||
|
static C_ReplayCamera s_ReplayCamera;
|
|||
|
|
|||
|
C_ReplayCamera *ReplayCamera() { return &s_ReplayCamera; }
|
|||
|
|
|||
|
C_ReplayCamera::C_ReplayCamera()
|
|||
|
{
|
|||
|
Reset();
|
|||
|
|
|||
|
m_nNumSpectators = 0;
|
|||
|
m_szTitleText[0] = 0;
|
|||
|
}
|
|||
|
|
|||
|
C_ReplayCamera::~C_ReplayCamera()
|
|||
|
{
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
void C_ReplayCamera::Init()
|
|||
|
{
|
|||
|
ListenForGameEvent( "game_newmap" );
|
|||
|
ListenForGameEvent( "replay_cameraman" );
|
|||
|
ListenForGameEvent( "replay_fixed" );
|
|||
|
ListenForGameEvent( "replay_chase" );
|
|||
|
ListenForGameEvent( "replay_message" );
|
|||
|
ListenForGameEvent( "replay_title" );
|
|||
|
ListenForGameEvent( "replay_status" );
|
|||
|
|
|||
|
Reset();
|
|||
|
|
|||
|
m_nNumSpectators = 0;
|
|||
|
m_szTitleText[0] = 0;
|
|||
|
|
|||
|
// get a handle to the engine convar
|
|||
|
replay_transmitall = cvar->FindVar( "replay_transmitall" );
|
|||
|
}
|
|||
|
|
|||
|
void C_ReplayCamera::Reset()
|
|||
|
{
|
|||
|
m_nCameraMode = OBS_MODE_FIXED;
|
|||
|
m_iCameraMan = 0;
|
|||
|
m_iTraget1 = m_iTraget2 = 0;
|
|||
|
m_flFOV = 90;
|
|||
|
m_flDistance = m_flLastDistance = 96.0f;
|
|||
|
m_flInertia = 3.0f;
|
|||
|
m_flPhi = 0;
|
|||
|
m_flTheta = 0;
|
|||
|
m_flOffset = 0;
|
|||
|
m_bEntityPacketReceived = false;
|
|||
|
|
|||
|
m_vCamOrigin.Init();
|
|||
|
m_aCamAngle.Init();
|
|||
|
|
|||
|
m_LastCmd.Reset();
|
|||
|
m_vecVelocity.Init();
|
|||
|
}
|
|||
|
|
|||
|
void C_ReplayCamera::CalcChaseCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov )
|
|||
|
{
|
|||
|
bool bManual = !spec_autodirector.GetBool(); // chase camera controlled manually
|
|||
|
|
|||
|
Vector targetOrigin1, targetOrigin2, cameraOrigin, forward;
|
|||
|
|
|||
|
if ( m_iTraget1 == 0 )
|
|||
|
return;
|
|||
|
|
|||
|
// get primary target, also translates to ragdoll
|
|||
|
C_BaseEntity *target1 = GetPrimaryTarget();
|
|||
|
|
|||
|
if ( !target1 )
|
|||
|
return;
|
|||
|
|
|||
|
if ( target1->IsAlive() && target1->IsDormant() )
|
|||
|
return;
|
|||
|
|
|||
|
targetOrigin1 = target1->GetRenderOrigin();
|
|||
|
|
|||
|
if ( !target1->IsAlive() )
|
|||
|
{
|
|||
|
targetOrigin1 += VEC_DEAD_VIEWHEIGHT;
|
|||
|
}
|
|||
|
else if ( target1->GetFlags() & FL_DUCKING )
|
|||
|
{
|
|||
|
targetOrigin1 += VEC_DUCK_VIEW;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
targetOrigin1 += VEC_VIEW;
|
|||
|
}
|
|||
|
|
|||
|
// get secondary target if set
|
|||
|
C_BaseEntity *target2 = NULL;
|
|||
|
|
|||
|
if ( m_iTraget2 > 0 && (m_iTraget2 != m_iTraget1) && !bManual )
|
|||
|
{
|
|||
|
target2 = ClientEntityList().GetBaseEntity( m_iTraget2 );
|
|||
|
|
|||
|
// if target is out PVS and not dead, it's not valid
|
|||
|
if ( target2 && target2->IsDormant() && target2->IsAlive() )
|
|||
|
target2 = NULL;
|
|||
|
|
|||
|
if ( target2 )
|
|||
|
{
|
|||
|
targetOrigin2 = target2->GetRenderOrigin();
|
|||
|
|
|||
|
if ( !target2->IsAlive() )
|
|||
|
{
|
|||
|
targetOrigin2 += VEC_DEAD_VIEWHEIGHT;
|
|||
|
}
|
|||
|
else if ( target2->GetFlags() & FL_DUCKING )
|
|||
|
{
|
|||
|
targetOrigin2 += VEC_DUCK_VIEW;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
targetOrigin2 += VEC_VIEW;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// apply angle offset & smoothing
|
|||
|
QAngle angleOffset( m_flPhi, m_flTheta, 0 );
|
|||
|
QAngle cameraAngles = m_aCamAngle;
|
|||
|
|
|||
|
if ( bManual )
|
|||
|
{
|
|||
|
// let spectator choose the view angles
|
|||
|
engine->GetViewAngles( cameraAngles );
|
|||
|
}
|
|||
|
else if ( target2 )
|
|||
|
{
|
|||
|
// look into direction of second target
|
|||
|
forward = targetOrigin2 - targetOrigin1;
|
|||
|
VectorAngles( forward, cameraAngles );
|
|||
|
cameraAngles.z = 0; // no ROLL
|
|||
|
}
|
|||
|
else if ( m_iTraget2 == 0 || m_iTraget2 == m_iTraget1)
|
|||
|
{
|
|||
|
// look into direction where primary target is looking
|
|||
|
cameraAngles = target1->EyeAngles();
|
|||
|
cameraAngles.x = 0; // no PITCH
|
|||
|
cameraAngles.z = 0; // no ROLL
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// target2 is missing, just keep angelsm, reset offset
|
|||
|
angleOffset.Init();
|
|||
|
}
|
|||
|
|
|||
|
if ( !bManual )
|
|||
|
{
|
|||
|
if ( !target1->IsAlive() )
|
|||
|
{
|
|||
|
angleOffset.x = 15;
|
|||
|
}
|
|||
|
|
|||
|
cameraAngles += angleOffset;
|
|||
|
}
|
|||
|
|
|||
|
AngleVectors( cameraAngles, &forward );
|
|||
|
|
|||
|
VectorNormalize( forward );
|
|||
|
|
|||
|
// calc optimal camera position
|
|||
|
VectorMA(targetOrigin1, -m_flDistance, forward, cameraOrigin );
|
|||
|
|
|||
|
targetOrigin1.z += m_flOffset; // add offset
|
|||
|
|
|||
|
// clip against walls
|
|||
|
trace_t trace;
|
|||
|
C_BaseEntity::PushEnableAbsRecomputations( false ); // HACK don't recompute positions while doing RayTrace
|
|||
|
UTIL_TraceHull( targetOrigin1, cameraOrigin, WALL_MIN, WALL_MAX, MASK_SOLID, target1, COLLISION_GROUP_NONE, &trace );
|
|||
|
C_BaseEntity::PopEnableAbsRecomputations();
|
|||
|
|
|||
|
float dist = VectorLength( trace.endpos - targetOrigin1 );
|
|||
|
|
|||
|
// grow distance by 32 unit a second
|
|||
|
m_flLastDistance += gpGlobals->frametime * 32.0f;
|
|||
|
|
|||
|
if ( dist > m_flLastDistance )
|
|||
|
{
|
|||
|
VectorMA(targetOrigin1, -m_flLastDistance, forward, cameraOrigin );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
cameraOrigin = trace.endpos;
|
|||
|
m_flLastDistance = dist;
|
|||
|
}
|
|||
|
|
|||
|
if ( target2 )
|
|||
|
{
|
|||
|
// if we have 2 targets look at point between them
|
|||
|
forward = (targetOrigin1+targetOrigin2)/2 - cameraOrigin;
|
|||
|
QAngle angle;
|
|||
|
VectorAngles( forward, angle );
|
|||
|
cameraAngles.y = angle.y;
|
|||
|
|
|||
|
NormalizeAngles( cameraAngles );
|
|||
|
cameraAngles.x = clamp( cameraAngles.x, -60, 60 );
|
|||
|
|
|||
|
SmoothCameraAngle( cameraAngles );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
SetCameraAngle( cameraAngles );
|
|||
|
}
|
|||
|
|
|||
|
VectorCopy( cameraOrigin, m_vCamOrigin );
|
|||
|
VectorCopy( m_aCamAngle, eyeAngles );
|
|||
|
VectorCopy( m_vCamOrigin, eyeOrigin );
|
|||
|
}
|
|||
|
|
|||
|
int C_ReplayCamera::GetMode()
|
|||
|
{
|
|||
|
if ( m_iCameraMan > 0 )
|
|||
|
{
|
|||
|
C_BasePlayer *pCameraMan = UTIL_PlayerByIndex( m_iCameraMan );
|
|||
|
|
|||
|
if ( pCameraMan )
|
|||
|
return pCameraMan->GetObserverMode();
|
|||
|
}
|
|||
|
|
|||
|
return m_nCameraMode;
|
|||
|
}
|
|||
|
|
|||
|
C_BaseEntity* C_ReplayCamera::GetPrimaryTarget()
|
|||
|
{
|
|||
|
if ( m_iCameraMan > 0 )
|
|||
|
{
|
|||
|
C_BasePlayer *pCameraMan = UTIL_PlayerByIndex( m_iCameraMan );
|
|||
|
|
|||
|
if ( pCameraMan )
|
|||
|
{
|
|||
|
return pCameraMan->GetObserverTarget();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if ( m_iTraget1 <= 0 )
|
|||
|
return NULL;
|
|||
|
|
|||
|
C_BaseEntity* target = ClientEntityList().GetEnt( m_iTraget1 );
|
|||
|
|
|||
|
return target;
|
|||
|
}
|
|||
|
|
|||
|
C_BaseEntity *C_ReplayCamera::GetCameraMan()
|
|||
|
{
|
|||
|
return ClientEntityList().GetEnt( m_iCameraMan );
|
|||
|
}
|
|||
|
|
|||
|
void C_ReplayCamera::CalcInEyeCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov )
|
|||
|
{
|
|||
|
C_BasePlayer *pPlayer = UTIL_PlayerByIndex( m_iTraget1 );
|
|||
|
|
|||
|
if ( !pPlayer )
|
|||
|
return;
|
|||
|
|
|||
|
if ( !pPlayer->IsAlive() )
|
|||
|
{
|
|||
|
// if dead, show from 3rd person
|
|||
|
CalcChaseCamView( eyeOrigin, eyeAngles, fov );
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
m_aCamAngle = pPlayer->EyeAngles();
|
|||
|
m_vCamOrigin = pPlayer->GetAbsOrigin();
|
|||
|
m_flFOV = pPlayer->GetFOV();
|
|||
|
|
|||
|
if ( pPlayer->GetFlags() & FL_DUCKING )
|
|||
|
{
|
|||
|
m_vCamOrigin += VEC_DUCK_VIEW;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
m_vCamOrigin += VEC_VIEW;
|
|||
|
}
|
|||
|
|
|||
|
eyeOrigin = m_vCamOrigin;
|
|||
|
eyeAngles = m_aCamAngle;
|
|||
|
fov = m_flFOV;
|
|||
|
|
|||
|
pPlayer->CalcViewModelView( eyeOrigin, eyeAngles);
|
|||
|
|
|||
|
C_BaseViewModel *pViewModel = pPlayer->GetViewModel( 0 );
|
|||
|
|
|||
|
if ( pViewModel )
|
|||
|
{
|
|||
|
Assert( pViewModel->GetOwner() == pPlayer );
|
|||
|
pViewModel->UpdateVisibility();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void C_ReplayCamera::Accelerate( Vector& wishdir, float wishspeed, float accel )
|
|||
|
{
|
|||
|
float addspeed, accelspeed, currentspeed;
|
|||
|
|
|||
|
// See if we are changing direction a bit
|
|||
|
currentspeed =m_vecVelocity.Dot(wishdir);
|
|||
|
|
|||
|
// Reduce wishspeed by the amount of veer.
|
|||
|
addspeed = wishspeed - currentspeed;
|
|||
|
|
|||
|
// If not going to add any speed, done.
|
|||
|
if (addspeed <= 0)
|
|||
|
return;
|
|||
|
|
|||
|
// Determine amount of acceleration.
|
|||
|
accelspeed = accel * gpGlobals->frametime * wishspeed;
|
|||
|
|
|||
|
// Cap at addspeed
|
|||
|
if (accelspeed > addspeed)
|
|||
|
accelspeed = addspeed;
|
|||
|
|
|||
|
// Adjust velocity.
|
|||
|
for (int i=0 ; i<3 ; i++)
|
|||
|
{
|
|||
|
m_vecVelocity[i] += accelspeed * wishdir[i];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// movement code is a copy of CGameMovement::FullNoClipMove()
|
|||
|
void C_ReplayCamera::CalcRoamingView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov)
|
|||
|
{
|
|||
|
// only if PVS isn't locked by auto-director
|
|||
|
if ( !IsPVSLocked() )
|
|||
|
{
|
|||
|
|
|||
|
Vector wishvel;
|
|||
|
Vector forward, right, up;
|
|||
|
Vector wishdir;
|
|||
|
float wishspeed;
|
|||
|
float factor = sv_specspeed.GetFloat();
|
|||
|
float maxspeed = sv_maxspeed.GetFloat() * factor;
|
|||
|
|
|||
|
AngleVectors ( m_LastCmd.viewangles, &forward, &right, &up); // Determine movement angles
|
|||
|
|
|||
|
if ( m_LastCmd.buttons & IN_SPEED )
|
|||
|
{
|
|||
|
factor /= 2.0f;
|
|||
|
}
|
|||
|
|
|||
|
// Copy movement amounts
|
|||
|
float fmove = m_LastCmd.forwardmove * factor;
|
|||
|
float smove = m_LastCmd.sidemove * factor;
|
|||
|
|
|||
|
VectorNormalize (forward); // Normalize remainder of vectors
|
|||
|
VectorNormalize (right); //
|
|||
|
|
|||
|
for (int i=0 ; i<3 ; i++) // Determine x and y parts of velocity
|
|||
|
wishvel[i] = forward[i]*fmove + right[i]*smove;
|
|||
|
wishvel[2] += m_LastCmd.upmove * factor;
|
|||
|
|
|||
|
VectorCopy (wishvel, wishdir); // Determine magnitude of speed of move
|
|||
|
wishspeed = VectorNormalize(wishdir);
|
|||
|
|
|||
|
//
|
|||
|
// Clamp to server defined max speed
|
|||
|
//
|
|||
|
if (wishspeed > maxspeed )
|
|||
|
{
|
|||
|
VectorScale (wishvel, maxspeed/wishspeed, wishvel);
|
|||
|
wishspeed = maxspeed;
|
|||
|
}
|
|||
|
|
|||
|
if ( sv_specaccelerate.GetFloat() > 0.0 )
|
|||
|
{
|
|||
|
// Set move velocity
|
|||
|
Accelerate ( wishdir, wishspeed, sv_specaccelerate.GetFloat() );
|
|||
|
|
|||
|
float spd = VectorLength( m_vecVelocity );
|
|||
|
if (spd < 1.0f)
|
|||
|
{
|
|||
|
m_vecVelocity.Init();
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Bleed off some speed, but if we have less than the bleed
|
|||
|
// threshold, bleed the threshold amount.
|
|||
|
float control = (spd < maxspeed/4.0) ? maxspeed/4.0 : spd;
|
|||
|
|
|||
|
float friction = sv_friction.GetFloat();
|
|||
|
|
|||
|
// Add the amount to the drop amount.
|
|||
|
float drop = control * friction * gpGlobals->frametime;
|
|||
|
|
|||
|
// scale the velocity
|
|||
|
float newspeed = spd - drop;
|
|||
|
if (newspeed < 0)
|
|||
|
newspeed = 0;
|
|||
|
|
|||
|
// Determine proportion of old speed we are using.
|
|||
|
newspeed /= spd;
|
|||
|
VectorScale( m_vecVelocity, newspeed, m_vecVelocity );
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
VectorCopy( wishvel, m_vecVelocity );
|
|||
|
}
|
|||
|
|
|||
|
// Just move ( don't clip or anything )
|
|||
|
VectorMA( m_vCamOrigin, gpGlobals->frametime, m_vecVelocity, m_vCamOrigin );
|
|||
|
|
|||
|
// get camera angle directly from engine
|
|||
|
engine->GetViewAngles( m_aCamAngle );
|
|||
|
|
|||
|
// Zero out velocity if in noaccel mode
|
|||
|
if ( sv_specaccelerate.GetFloat() < 0.0f )
|
|||
|
{
|
|||
|
m_vecVelocity.Init();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
eyeOrigin = m_vCamOrigin;
|
|||
|
eyeAngles = m_aCamAngle;
|
|||
|
fov = m_flFOV;
|
|||
|
}
|
|||
|
|
|||
|
void C_ReplayCamera::CalcFixedView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov)
|
|||
|
{
|
|||
|
eyeOrigin = m_vCamOrigin;
|
|||
|
eyeAngles = m_aCamAngle;
|
|||
|
fov = m_flFOV;
|
|||
|
|
|||
|
if ( m_iTraget1 == 0 )
|
|||
|
return;
|
|||
|
|
|||
|
C_BaseEntity * target = ClientEntityList().GetBaseEntity( m_iTraget1 );
|
|||
|
|
|||
|
if ( target && target->IsAlive() )
|
|||
|
{
|
|||
|
// if we're chasing a target, change viewangles
|
|||
|
QAngle angle;
|
|||
|
VectorAngles( (target->GetAbsOrigin()+VEC_VIEW) - m_vCamOrigin, angle );
|
|||
|
SmoothCameraAngle( angle );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void C_ReplayCamera::PostEntityPacketReceived()
|
|||
|
{
|
|||
|
m_bEntityPacketReceived = true;
|
|||
|
}
|
|||
|
|
|||
|
void C_ReplayCamera::FixupMovmentParents()
|
|||
|
{
|
|||
|
// Find resource zone
|
|||
|
|
|||
|
for ( ClientEntityHandle_t e = ClientEntityList().FirstHandle();
|
|||
|
e != ClientEntityList().InvalidHandle(); e = ClientEntityList().NextHandle( e ) )
|
|||
|
{
|
|||
|
C_BaseEntity *ent = C_BaseEntity::Instance( e );
|
|||
|
|
|||
|
if ( !ent )
|
|||
|
continue;
|
|||
|
|
|||
|
ent->HierarchyUpdateMoveParent();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void C_ReplayCamera::CalcView(Vector& origin, QAngle& angles, float& fov)
|
|||
|
{
|
|||
|
if ( m_bEntityPacketReceived )
|
|||
|
{
|
|||
|
// try to fixup movment pareents
|
|||
|
FixupMovmentParents();
|
|||
|
m_bEntityPacketReceived = false;
|
|||
|
}
|
|||
|
|
|||
|
if ( m_iCameraMan > 0 )
|
|||
|
{
|
|||
|
C_BasePlayer *pCameraMan = UTIL_PlayerByIndex( m_iCameraMan );
|
|||
|
if ( pCameraMan )
|
|||
|
{
|
|||
|
float zNear,zFar;
|
|||
|
pCameraMan->CalcView( origin, angles, zNear, zFar, fov );
|
|||
|
pCameraMan->CalcViewModelView( origin, angles );
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
switch ( m_nCameraMode )
|
|||
|
{
|
|||
|
case OBS_MODE_ROAMING : CalcRoamingView( origin, angles, fov );
|
|||
|
break;
|
|||
|
|
|||
|
case OBS_MODE_FIXED : CalcFixedView( origin, angles, fov );
|
|||
|
break;
|
|||
|
|
|||
|
case OBS_MODE_IN_EYE : CalcInEyeCamView( origin, angles, fov );
|
|||
|
break;
|
|||
|
|
|||
|
case OBS_MODE_CHASE : CalcChaseCamView( origin, angles, fov );
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void C_ReplayCamera::SetMode(int iMode)
|
|||
|
{
|
|||
|
if ( m_nCameraMode == iMode )
|
|||
|
return;
|
|||
|
|
|||
|
Assert( iMode > OBS_MODE_NONE && iMode <= LAST_PLAYER_OBSERVERMODE );
|
|||
|
|
|||
|
m_nCameraMode = iMode;
|
|||
|
}
|
|||
|
|
|||
|
void C_ReplayCamera::SetPrimaryTarget( int nEntity )
|
|||
|
{
|
|||
|
if ( m_iTraget1 == nEntity )
|
|||
|
return;
|
|||
|
|
|||
|
m_iTraget1 = nEntity;
|
|||
|
|
|||
|
if ( GetMode() == OBS_MODE_ROAMING )
|
|||
|
{
|
|||
|
Vector vOrigin;
|
|||
|
QAngle aAngles;
|
|||
|
float flFov;
|
|||
|
|
|||
|
CalcChaseCamView( vOrigin, aAngles, flFov );
|
|||
|
}
|
|||
|
else if ( GetMode() == OBS_MODE_CHASE )
|
|||
|
{
|
|||
|
C_BaseEntity* target = ClientEntityList().GetEnt( m_iTraget1 );
|
|||
|
if ( target )
|
|||
|
{
|
|||
|
QAngle eyeAngle = target->EyeAngles();
|
|||
|
prediction->SetViewAngles( eyeAngle );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
m_flLastDistance = m_flDistance;
|
|||
|
m_flLastAngleUpdateTime = -1;
|
|||
|
}
|
|||
|
|
|||
|
void C_ReplayCamera::SpecNextPlayer( bool bInverse )
|
|||
|
{
|
|||
|
int start = 1;
|
|||
|
|
|||
|
if ( m_iTraget1 > 0 && m_iTraget1 <= gpGlobals->maxClients )
|
|||
|
start = m_iTraget1;
|
|||
|
|
|||
|
int index = start;
|
|||
|
|
|||
|
while ( true )
|
|||
|
{
|
|||
|
// got next/prev player
|
|||
|
if ( bInverse )
|
|||
|
index--;
|
|||
|
else
|
|||
|
index++;
|
|||
|
|
|||
|
// check bounds
|
|||
|
if ( index < 1 )
|
|||
|
index = gpGlobals->maxClients;
|
|||
|
else if ( index > gpGlobals->maxClients )
|
|||
|
index = 1;
|
|||
|
|
|||
|
if ( index == start )
|
|||
|
break; // couldn't find a new valid player
|
|||
|
|
|||
|
C_BasePlayer *pPlayer = UTIL_PlayerByIndex( index );
|
|||
|
|
|||
|
if ( !pPlayer )
|
|||
|
continue;
|
|||
|
|
|||
|
// only follow living players
|
|||
|
if ( pPlayer->IsObserver() )
|
|||
|
continue;
|
|||
|
|
|||
|
break; // found a new player
|
|||
|
}
|
|||
|
|
|||
|
SetPrimaryTarget( index );
|
|||
|
|
|||
|
// turn off auto director once user tried to change view settings
|
|||
|
SetAutoDirector( false );
|
|||
|
}
|
|||
|
|
|||
|
void C_ReplayCamera::SpecNamedPlayer( const char *szPlayerName )
|
|||
|
{
|
|||
|
for ( int index = 1; index <= gpGlobals->maxClients; ++index )
|
|||
|
{
|
|||
|
C_BasePlayer *pPlayer = UTIL_PlayerByIndex( index );
|
|||
|
|
|||
|
if ( !pPlayer )
|
|||
|
continue;
|
|||
|
|
|||
|
if ( !FStrEq( szPlayerName, pPlayer->GetPlayerName() ) )
|
|||
|
continue;
|
|||
|
|
|||
|
// only follow living players or dedicated spectators
|
|||
|
if ( pPlayer->IsObserver() && pPlayer->GetTeamNumber() != TEAM_SPECTATOR )
|
|||
|
continue;
|
|||
|
|
|||
|
SetPrimaryTarget( index );
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void C_ReplayCamera::FireGameEvent( IGameEvent * event)
|
|||
|
{
|
|||
|
if ( !engine->IsReplay() )
|
|||
|
return; // not in Replay mode
|
|||
|
|
|||
|
const char *type = event->GetName();
|
|||
|
|
|||
|
if ( Q_strcmp( "game_newmap", type ) == 0 )
|
|||
|
{
|
|||
|
Reset(); // reset all camera settings
|
|||
|
|
|||
|
// show spectator UI
|
|||
|
if ( !GetViewPortInterface() )
|
|||
|
return;
|
|||
|
|
|||
|
if ( engine->IsPlayingDemo() )
|
|||
|
{
|
|||
|
// for demo playback show full menu
|
|||
|
GetViewPortInterface()->ShowPanel( PANEL_SPECMENU, true );
|
|||
|
|
|||
|
SetMode( OBS_MODE_ROAMING );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// during live broadcast only show black bars
|
|||
|
GetViewPortInterface()->ShowPanel( PANEL_SPECGUI, true );
|
|||
|
}
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if ( Q_strcmp( "replay_message", type ) == 0 )
|
|||
|
{
|
|||
|
wchar_t outputBuf[1024];
|
|||
|
const char *pszText = event->GetString( "text", "" );
|
|||
|
|
|||
|
char *tmpStr = hudtextmessage->LookupString( pszText );
|
|||
|
const wchar_t *pBuf = g_pVGuiLocalize->Find( tmpStr );
|
|||
|
if ( pBuf )
|
|||
|
{
|
|||
|
// Copy pBuf into szBuf[i].
|
|||
|
int nMaxChars = sizeof( outputBuf ) / sizeof( wchar_t );
|
|||
|
wcsncpy( outputBuf, pBuf, nMaxChars );
|
|||
|
outputBuf[nMaxChars-1] = 0;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
g_pVGuiLocalize->ConvertANSIToUnicode( tmpStr, outputBuf, sizeof(outputBuf) );
|
|||
|
}
|
|||
|
|
|||
|
GetCenterPrint()->Print( ConvertCRtoNL( outputBuf ) );
|
|||
|
return ;
|
|||
|
}
|
|||
|
|
|||
|
if ( Q_strcmp( "replay_title", type ) == 0 )
|
|||
|
{
|
|||
|
Q_strncpy( m_szTitleText, event->GetString( "text", "" ), sizeof(m_szTitleText) );
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if ( Q_strcmp( "replay_status", type ) == 0 )
|
|||
|
{
|
|||
|
int nNumProxies = event->GetInt( "proxies" );
|
|||
|
m_nNumSpectators = event->GetInt( "clients" ) - nNumProxies;
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// after this only auto-director commands follow
|
|||
|
// don't execute them is autodirector is off and PVS is unlocked
|
|||
|
if ( !spec_autodirector.GetBool() && !IsPVSLocked() )
|
|||
|
return;
|
|||
|
|
|||
|
if ( Q_strcmp( "replay_cameraman", type ) == 0 )
|
|||
|
{
|
|||
|
Reset();
|
|||
|
|
|||
|
m_nCameraMode = OBS_MODE_ROAMING;
|
|||
|
m_iCameraMan = event->GetInt( "index" );
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if ( Q_strcmp( "replay_fixed", type ) == 0 )
|
|||
|
{
|
|||
|
m_iCameraMan = 0;
|
|||
|
|
|||
|
m_vCamOrigin.x = event->GetInt( "posx" );
|
|||
|
m_vCamOrigin.y = event->GetInt( "posy" );
|
|||
|
m_vCamOrigin.z = event->GetInt( "posz" );
|
|||
|
|
|||
|
QAngle angle;
|
|||
|
angle.x = event->GetInt( "theta" );
|
|||
|
angle.y = event->GetInt( "phi" );
|
|||
|
angle.z = 0; // no roll yet
|
|||
|
|
|||
|
if ( m_nCameraMode != OBS_MODE_FIXED )
|
|||
|
{
|
|||
|
SetMode( OBS_MODE_FIXED );
|
|||
|
SetCameraAngle( angle );
|
|||
|
m_flFOV = event->GetFloat( "fov", 90 );
|
|||
|
}
|
|||
|
|
|||
|
SetPrimaryTarget( event->GetInt( "target" ) );
|
|||
|
|
|||
|
if ( m_iTraget1 == 0 )
|
|||
|
{
|
|||
|
SetCameraAngle( angle );
|
|||
|
}
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if ( Q_strcmp( "replay_chase", type ) == 0 )
|
|||
|
{
|
|||
|
bool bInEye = event->GetInt( "ineye" );
|
|||
|
|
|||
|
// check if we are already in a player chase mode
|
|||
|
bool bIsInChaseMode = (m_nCameraMode==OBS_MODE_IN_EYE)|| (m_nCameraMode==OBS_MODE_CHASE);
|
|||
|
|
|||
|
// if we are in auto director or not in a valid chase mode, set new mode now
|
|||
|
if ( spec_autodirector.GetBool() || !bIsInChaseMode )
|
|||
|
{
|
|||
|
SetMode( bInEye?OBS_MODE_IN_EYE:OBS_MODE_CHASE );
|
|||
|
}
|
|||
|
|
|||
|
m_iCameraMan = 0;
|
|||
|
|
|||
|
m_iTraget2 = event->GetInt( "target2" );
|
|||
|
m_flDistance = event->GetFloat( "distance", m_flDistance );
|
|||
|
m_flOffset = event->GetFloat( "offset", m_flOffset );
|
|||
|
m_flTheta = event->GetFloat( "theta", m_flTheta );
|
|||
|
m_flPhi = event->GetFloat( "phi", m_flPhi );
|
|||
|
m_flFOV = event->GetFloat( "fov", 90 );
|
|||
|
m_flInertia = event->GetFloat( "inertia", 30.f ) / 10.f;
|
|||
|
|
|||
|
// if inertia is not set use standard value
|
|||
|
if ( m_flInertia <= 0 )
|
|||
|
m_flInertia = 3.0f;
|
|||
|
|
|||
|
SetPrimaryTarget( event->GetInt( "target1" ) );
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// this is a cheap version of FullNoClipMove():
|
|||
|
void C_ReplayCamera::CreateMove( CUserCmd *cmd)
|
|||
|
{
|
|||
|
if ( cmd )
|
|||
|
{
|
|||
|
m_LastCmd = *cmd;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void C_ReplayCamera::SetCameraAngle( QAngle& targetAngle )
|
|||
|
{
|
|||
|
m_aCamAngle = targetAngle;
|
|||
|
NormalizeAngles( m_aCamAngle );
|
|||
|
m_flLastAngleUpdateTime = gpGlobals->realtime;
|
|||
|
}
|
|||
|
|
|||
|
void C_ReplayCamera::SmoothCameraAngle( QAngle& targetAngle )
|
|||
|
{
|
|||
|
if ( m_flLastAngleUpdateTime > 0 )
|
|||
|
{
|
|||
|
float deltaTime = gpGlobals->realtime - m_flLastAngleUpdateTime;
|
|||
|
|
|||
|
deltaTime = clamp( deltaTime*m_flInertia, 0.01, 1);
|
|||
|
|
|||
|
InterpolateAngles( m_aCamAngle, targetAngle, m_aCamAngle, deltaTime );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
m_aCamAngle = targetAngle;
|
|||
|
}
|
|||
|
|
|||
|
m_flLastAngleUpdateTime = gpGlobals->realtime;
|
|||
|
}
|
|||
|
|
|||
|
void C_ReplayCamera::ToggleChaseAsFirstPerson()
|
|||
|
{
|
|||
|
if ( GetMode() == OBS_MODE_CHASE )
|
|||
|
{
|
|||
|
SetMode( OBS_MODE_IN_EYE );
|
|||
|
}
|
|||
|
else if ( GetMode() == OBS_MODE_IN_EYE )
|
|||
|
{
|
|||
|
SetMode( OBS_MODE_CHASE );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
bool C_ReplayCamera::IsPVSLocked()
|
|||
|
{
|
|||
|
if ( replay_transmitall != NULL )
|
|||
|
{
|
|||
|
return !replay_transmitall->GetBool();
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
//old style, assume locked unless we playback a demo
|
|||
|
return !engine->IsPlayingDemo();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void C_ReplayCamera::SetAutoDirector( bool bActive )
|
|||
|
{
|
|||
|
spec_autodirector.SetValue( bActive?1:0 );
|
|||
|
}
|
|||
|
|
|||
|
#endif
|