//========= 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