Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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.
 
 
 
 
 
 

936 lines
22 KiB

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#if defined( REPLAY_ENABLED )
#include "replay/ireplaysystem.h"
#include "replay/ienginereplay.h"
#include "replay/ireplaymanager.h"
#include "replay/replay.h"
#include "replay/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 "vgui/IInput.h"
#include <KeyValues.h>
#include "iinput.h"
#include "iclientmode.h"
#include "ienginevgui.h"
#include "vgui/IInput.h"
#include "mathlib/noise.h"
#ifdef CSTRIKE_DLL
#include "c_cs_player.h"
#endif
ConVar replay_editor_camera_length( "replay_editor_camera_length", "15", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "This is the camera length used to simulate camera shake in the replay editor. The larger this number, the more the actual position will change. It can also be set to negative values." );
//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_MAX 96.0f
#define WALL_OFFSET 6.0f
#define DEFAULT_ROAMING_ACCEL 5.0f
#define DEFAULT_ROAMING_SPEED 3.0f
static Vector WALL_MIN(-WALL_OFFSET,-WALL_OFFSET,-WALL_OFFSET);
static Vector WALL_MAX(WALL_OFFSET,WALL_OFFSET,WALL_OFFSET);
//////////////////////////////////////////////////////////////////////
// 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" );
Reset();
m_nNumSpectators = 0;
m_szTitleText[0] = 0;
}
void C_ReplayCamera::Reset()
{
m_nCameraMode = OBS_MODE_FIXED;
m_iTarget1 = m_iTarget2 = 0;
m_flFOV = 90.0f;
m_flDistance = m_flLastDistance = CHASE_CAM_DISTANCE_MAX;
m_flInertia = 3.0f;
m_flPhi = 0;
m_flTheta = 0;
m_flOffset = 0;
m_bEntityPacketReceived = false;
m_bOverrideView = false;
m_flOldTime = 0.0f;
m_bInputEnabled = true;
m_flRoamingAccel = DEFAULT_ROAMING_ACCEL;
m_flRoamingSpeed = DEFAULT_ROAMING_SPEED;
m_flRoamingFov[0] = m_flRoamingFov[1] = 90.0f;
m_flRoamingRotFilterFactor = 10.0f;
m_flRoamingShakeAmount = 0.0f;
m_flRoamingShakeSpeed = 0.0f;
m_flNoiseSample = 0.0f;
m_flRoamingShakeDir = Lerp( 0.5f, FREE_CAM_SHAKE_DIR_MIN, FREE_CAM_SHAKE_DIR_MAX );
m_vCamOrigin.Init();
m_aCamAngle.Init();
m_aSmoothedRoamingAngles.Init();
m_OverrideViewData.origin.Init();
m_OverrideViewData.angles.Init();
m_OverrideViewData.fov = 90;
m_LastCmd.Reset();
m_vecVelocity.Init();
InitRoamingKeys();
}
void C_ReplayCamera::InitRoamingKeys()
{
m_aMovementButtons[DIR_FWD ] = KEY_W;
m_aMovementButtons[DIR_BACK ] = KEY_S;
m_aMovementButtons[DIR_LEFT ] = KEY_A;
m_aMovementButtons[DIR_RIGHT] = KEY_D;
m_aMovementButtons[DIR_DOWN ] = KEY_X;
m_aMovementButtons[DIR_UP ] = KEY_Z;
}
bool C_ReplayCamera::ShouldUseDefaultRoamingSettings() const
{
return vgui::input()->IsKeyDown( KEY_LSHIFT );
}
void C_ReplayCamera::CalcChaseCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov, float flDelta )
{
bool bManual = true;
Vector targetOrigin1, targetOrigin2, cameraOrigin, forward;
if ( m_iTarget1 == 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_iTarget2 > 0 && (m_iTarget2 != m_iTarget1) && !bManual )
{
target2 = ClientEntityList().GetBaseEntity( m_iTarget2 );
// 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_iTarget2 == 0 || m_iTarget2 == m_iTarget1)
{
// 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 += flDelta * 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.f, 60.f );
SmoothCameraAngle( cameraAngles );
}
else
{
SetCameraAngle( cameraAngles );
}
VectorCopy( cameraOrigin, m_vCamOrigin );
VectorCopy( m_aCamAngle, eyeAngles );
VectorCopy( m_vCamOrigin, eyeOrigin );
fov = m_flFOV;
}
int C_ReplayCamera::GetMode()
{
return m_nCameraMode;
}
C_BaseEntity* C_ReplayCamera::GetPrimaryTarget()
{
if ( m_iTarget1 <= 0 )
return NULL;
C_BaseEntity* target = ClientEntityList().GetEnt( m_iTarget1 );
return target;
}
void C_ReplayCamera::CalcInEyeCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov, float flDelta )
{
C_BasePlayer *pPlayer = UTIL_PlayerByIndex( m_iTarget1 );
if ( !pPlayer )
return;
if ( !pPlayer->IsAlive() )
{
// if dead, show from 3rd person
CalcChaseCamView( eyeOrigin, eyeAngles, fov, flDelta );
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();
}
// This fixes the bug where going from third or first person to free cam defaults to some arbitrary angle,
// because free cam uses engine->GetViewAngles().
engine->SetViewAngles( m_aCamAngle );
}
void C_ReplayCamera::Accelerate( Vector& wishdir, float wishspeed, float accel, float flDelta )
{
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 * flDelta * wishspeed;
// Cap at addspeed
if (accelspeed > addspeed)
accelspeed = addspeed;
// Adjust velocity.
for (int i=0 ; i<3 ; i++)
{
m_vecVelocity[i] += accelspeed * wishdir[i];
}
}
bool C_ReplayCamera::ShouldOverrideView( Vector& origin, QAngle& angles, float& fov )
{
if ( !m_bOverrideView )
return false;
origin = m_OverrideViewData.origin;
angles = m_OverrideViewData.angles;
fov = m_OverrideViewData.fov;
return true;
}
// movement code is a copy of CGameMovement::FullNoClipMove()
void C_ReplayCamera::CalcRoamingView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov, float flDelta)
{
// only if PVS isn't locked by auto-director
if ( !IsPVSLocked() )
{
Vector wishvel;
Vector forward, right, up;
Vector wishdir;
float wishspeed;
float factor = ShouldUseDefaultRoamingSettings() ? DEFAULT_ROAMING_SPEED : m_flRoamingSpeed;
float maxspeed = sv_maxspeed.GetFloat() * factor;
AngleVectors ( m_aCamAngle, &forward, &right, &up ); // Determine movement angles
if ( m_LastCmd.buttons & IN_SPEED )
{
factor /= 2.0f;
}
// Check for movement
float fmove = 0.0f;
float smove = 0.0f;
float vmove = 0.0f;
if ( !enginevgui->IsGameUIVisible() && m_bInputEnabled )
{
// Forward/backward movement
if ( vgui::input()->IsKeyDown( m_aMovementButtons[DIR_FWD] ) )
{
fmove = factor * maxspeed;
}
else if ( vgui::input()->IsKeyDown( m_aMovementButtons[DIR_BACK] ) )
{
fmove = -factor * maxspeed;
}
// Lateral movement
if ( vgui::input()->IsKeyDown( m_aMovementButtons[DIR_LEFT] ) )
{
smove = -factor * maxspeed;
}
else if ( vgui::input()->IsKeyDown( m_aMovementButtons[DIR_RIGHT] ) )
{
smove = factor * maxspeed;
}
// Vertical movement
if ( vgui::input()->IsKeyDown( m_aMovementButtons[DIR_UP] ) )
{
vmove = factor * maxspeed;
}
else if ( vgui::input()->IsKeyDown( m_aMovementButtons[DIR_DOWN] ) )
{
vmove = -factor * maxspeed;
}
}
// Normalize remainder of vectors
VectorNormalize(forward);
VectorNormalize(right);
VectorNormalize(up);
for (int i=0 ; i<3 ; i++) // Determine x and y parts of velocity
wishvel[i] = forward[i]*fmove + right[i]*smove + up[i]*vmove;
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;
}
const float flRoamingAccel = ShouldUseDefaultRoamingSettings() ?
DEFAULT_ROAMING_ACCEL : m_flRoamingAccel;
if ( flRoamingAccel > 0.0 )
{
// Set move velocity
Accelerate ( wishdir, wishspeed, flRoamingAccel, flDelta );
float spd = VectorLength( m_vecVelocity );
if ( CloseEnough( spd, 0.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;
float friction = sv_friction.GetFloat();
// Add the amount to the drop amount.
float drop = control * friction * flDelta;
// 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, flDelta, 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();
}
}
// Smooth the angles
float flPercent = clamp( flDelta * m_flRoamingRotFilterFactor, 0.0f, 1.0f );
m_aSmoothedRoamingAngles = Lerp( flPercent, m_aSmoothedRoamingAngles, m_aCamAngle );
Vector vCameraShakeOffset;
vCameraShakeOffset.Init();
// Add in camera shake
if ( !ShouldUseDefaultRoamingSettings() && m_flRoamingShakeAmount > 0.0f )
{
QAngle angShake( 0.0f, 0.0f, 0.0f );
m_flNoiseSample += m_flRoamingShakeSpeed * flDelta;
float flNoiseX = Lerp( FractalNoise( Vector( m_flNoiseSample, 0.0f, 0.0f ), 1 ), -1.0f, 1.0f );
float flNoiseY = Lerp( FractalNoise( Vector( 0.0f, 1000 + m_flNoiseSample, 0.0f ), 1 ), -1.0f, 1.0f );
// Vertical shake
const float flAmplitudeX = m_flRoamingShakeAmount * ( m_flRoamingShakeDir < 0.0f ? ( 1.0f + m_flRoamingShakeDir ) : 1.0f );
angShake.x = flAmplitudeX * flNoiseX;
// Lateral shake
const float flAmplitudeY = m_flRoamingShakeAmount * ( m_flRoamingShakeDir > 0.0f ? ( 1.0f - m_flRoamingShakeDir ) : 1.0f );
angShake.y = flAmplitudeY * flNoiseY;
// The math below simulates a camera with length "replay_editor_camera_length," so that the camera position bounces around
// as if it were on someone's shoulder. If we were to just use angShake at this point with no translation, we would get a
// camera that looks around but is anchored and doesn't feel quite right. With the code below, the camera will translate,
// but be centered around the same point as when camera shake is off completely, rather than actually offsetting that point
// by the camera length.
// Get the forward vector from the shake transform/angles
Vector vShakeForward;
AngleVectors( angShake, &vShakeForward );
// Calculate an offset, simulating a camera length
Vector vCameraOffset = vShakeForward * replay_editor_camera_length.GetFloat();
// Get the global matrix without any shake
matrix3x4_t mGlobal;
AngleMatrix( m_aSmoothedRoamingAngles, m_vCamOrigin, mGlobal );
// Convert local shake angles and offset to a matrix
matrix3x4_t mShake;
AngleMatrix( angShake, mShake );
// Setup a translation matrix using the offset
matrix3x4_t mOffset;
SetIdentityMatrix( mOffset );
PositionMatrix( vCameraOffset, mOffset );
matrix3x4_t mOffsetInv;
MatrixInvert( mOffset, mOffsetInv );
// The meat
matrix3x4_t mFinal = mGlobal;
MatrixMultiply( mFinal, mOffsetInv, mFinal );
MatrixMultiply( mFinal, mShake, mFinal );
MatrixMultiply( mFinal, mOffset, mFinal );
// Convert back to Vector / QAngle
MatrixAngles( mFinal, eyeAngles, eyeOrigin );
}
else
{
// No shake
eyeOrigin = m_vCamOrigin;
eyeAngles = m_aSmoothedRoamingAngles;
}
fov = m_flRoamingFov[0];
}
void C_ReplayCamera::CalcFixedView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov, float flDelta )
{
eyeOrigin = m_vCamOrigin;
eyeAngles = m_aCamAngle;
fov = m_flFOV;
if ( m_iTarget1 == 0 )
return;
C_BaseEntity * target = ClientEntityList().GetBaseEntity( m_iTarget1 );
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::SmoothFov( float flDelta )
{
m_flRoamingFov[0] = clamp(
Lerp( 7 * flDelta, m_flRoamingFov[0], m_flRoamingFov[1] ),
// Approach( m_flRoamingFov[1], m_flRoamingFov[0], 40 * m_flFrameTime ),
FREE_CAM_FOV_MIN,
FREE_CAM_FOV_MAX
);
}
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::EnableInput( bool bEnable )
{
m_bInputEnabled = bEnable;
}
void C_ReplayCamera::ClearOverrideView()
{
if ( m_bOverrideView )
{
m_vCamOrigin = m_OverrideViewData.origin;
m_aCamAngle = m_aSmoothedRoamingAngles = m_OverrideViewData.angles;
m_flRoamingFov[0] = m_flRoamingFov[1] = m_OverrideViewData.fov;
}
m_bOverrideView = false;
// Set view angles in engine so that CalcRoamingView() won't pop to some stupid angle
engine->SetViewAngles( m_aCamAngle );
}
void C_ReplayCamera::OverrideView( const Vector *pOrigin, const QAngle *pAngles, float flFov )
{
m_bOverrideView = true;
m_OverrideViewData.origin = *pOrigin;
m_OverrideViewData.angles = *pAngles;
m_OverrideViewData.fov = flFov;
}
void C_ReplayCamera::CalcView(Vector &origin, QAngle &angles, float &fov )
{
// NOTE ABOUT CLOCKS: 'realtime' is used, because otherwise we can't move the camera round while
// the game is paused.
// Calculate elapsed time since last call to CalcView()
if ( m_flOldTime == 0.0f )
{
m_flOldTime = gpGlobals->realtime;
}
const float flDelta = gpGlobals->realtime - m_flOldTime;
m_flOldTime = gpGlobals->realtime;
if ( m_bEntityPacketReceived )
{
// try to fixup movment parents
FixupMovmentParents();
m_bEntityPacketReceived = false;
}
// Completely override?
if ( ShouldOverrideView( origin, angles, fov ) )
return;
switch ( m_nCameraMode )
{
case OBS_MODE_ROAMING : CalcRoamingView( origin, angles, fov, flDelta );
break;
case OBS_MODE_FIXED : CalcFixedView( origin, angles, fov, flDelta );
break;
case OBS_MODE_IN_EYE : CalcInEyeCamView( origin, angles, fov, flDelta );
break;
case OBS_MODE_CHASE : CalcChaseCamView( origin, angles, fov, flDelta );
break;
}
// Cache in case we want to access this data later in the frame
m_CachedView.origin = origin;
m_CachedView.angles = angles;
m_CachedView.fov = fov;
}
void C_ReplayCamera::GetCachedView( Vector &origin, QAngle &angles, float &fov )
{
origin = m_CachedView.origin;
angles = m_CachedView.angles;
fov = m_CachedView.fov;
}
void C_ReplayCamera::SetMode(int iMode)
{
if ( m_nCameraMode == iMode )
return;
Assert( iMode > OBS_MODE_NONE && iMode <= LAST_PLAYER_OBSERVERMODE );
m_nCameraMode = iMode;
if ( m_nCameraMode != OBS_MODE_ROAMING && m_nCameraMode != OBS_MODE_CHASE )
{
ClearOverrideView();
}
}
void C_ReplayCamera::SetPrimaryTarget( int nEntity )
{
if ( m_iTarget1 == nEntity )
return;
m_iTarget1 = nEntity;
if ( GetMode() == OBS_MODE_ROAMING )
{
Vector vOrigin;
QAngle aAngles;
float flFov;
CalcChaseCamView( vOrigin, aAngles, flFov, 0.015f );
}
else if ( GetMode() == OBS_MODE_CHASE )
{
C_BaseEntity* target = ClientEntityList().GetEnt( m_iTarget1 );
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_iTarget1 > 0 && m_iTarget1 <= gpGlobals->maxClients )
start = m_iTarget1;
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 ( !g_pEngineClientReplay->IsPlayingReplayDemo() )
return; // not in Replay mode
const char *type = event->GetName();
if ( Q_strcmp( "game_newmap", type ) == 0 )
{
// Do not reset the camera, since we reload the map when "rewinding"
// and want to keep our camera settings intact.
// Reset(); // reset all camera settings
// show spectator UI
if ( !gViewPortInterface )
return;
if ( g_pEngineClientReplay->IsPlayingReplayDemo() )
{
SetMode( OBS_MODE_IN_EYE );
CReplay *pReplay = g_pReplayManager->GetPlayingReplay();
SetPrimaryTarget( ( pReplay && pReplay->m_nPlayerSlot >= 0 ) ? pReplay->m_nPlayerSlot : 0 );
}
else
{
// during live broadcast only show black bars
gViewPortInterface->ShowPanel( PANEL_SPECMENU, true );
}
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;
}
// 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.01f, 1.f);
InterpolateAngles( m_aCamAngle, targetAngle, m_aCamAngle, deltaTime );
}
else
{
m_aCamAngle = targetAngle;
}
m_flLastAngleUpdateTime = gpGlobals->realtime;
}
bool C_ReplayCamera::IsPVSLocked()
{
return false;
}
void C_ReplayCamera::SetAutoDirector( bool bActive )
{
spec_autodirector.SetValue( bActive?1:0 );
}
#endif