//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//

#include "cbase.h"
#include "vehicle_base.h"
#include "engine/IEngineSound.h"
#include "in_buttons.h"
#include "soundenvelope.h"
#include "soundent.h"
#include "physics_saverestore.h"
#include "vphysics/constraints.h"
#include "vcollide_parse.h"
#include "ndebugoverlay.h"
#include "npc_vehicledriver.h"
#include "hl2_player.h"
#include "explode.h"
#include "particle_smokegrenade.h"
#include "te_effect_dispatch.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

class CPropCannon;

// Crane bones that have physics followers
static const char *pCannonFollowerBoneNames[] =
{
	"base",
	"arm",
	"platform",
};

#define CANNON_EXTENSION_RATE_MAX	0.01
#define CANNON_TURN_RATE_MAX			1.2

#define MAX_CANNON_FLAT_REACH		1400.0
#define MIN_CANNON_FLAT_REACH		700.0
#define CANNON_EXTENSION_ACCEL		0.006
#define CANNON_EXTENSION_DECEL		0.02
#define CANNON_TURN_ACCEL			0.2
#define CANNON_DECEL					0.5

#define CANNON_SLOWRAISE_TIME		5.0

#define CANNON_PROJECTILE_MODEL "models/props_combine/headcrabcannister01a.mdl"

ConVar g_cannon_reloadtime( "g_cannon_reloadtime", "5", FCVAR_CHEAT | FCVAR_GAMEDLL );
ConVar g_cannon_max_traveltime( "g_cannon_max_traveltime", "10", FCVAR_CHEAT | FCVAR_GAMEDLL );
ConVar g_cannon_debug( "g_cannon_debug", "0", FCVAR_CHEAT | FCVAR_GAMEDLL );

// Turning stats
enum
{
	CANNON_TURNING_NOT,
	CANNON_TURNING_LEFT,
	CANNON_TURNING_RIGHT,
};

//-----------------------------------------------------------------------------
// Purpose: Crane vehicle server
//-----------------------------------------------------------------------------
class CCannonServerVehicle : public CBaseServerVehicle
{
	typedef CBaseServerVehicle BaseClass;
// IServerVehicle
public:
	void	GetVehicleViewPosition( int nRole, Vector *pAbsOrigin, QAngle *pAbsAngles );


protected:
	CPropCannon *GetCannon( void );
};


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
class CPropCannon : public CBaseProp, public IDrivableVehicle
{
	DECLARE_CLASS( CPropCannon, CBaseProp );
public:
	DECLARE_DATADESC();
	DECLARE_SERVERCLASS();

	CPropCannon( void )
	{
		m_ServerVehicle.SetVehicle( this );
	}

	//IDrivableVehicle's Pure Virtuals
	virtual CBaseEntity *GetDriver( void );
	virtual void		ItemPostFrame( CBasePlayer *pPlayer );
	virtual void		SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move );
	virtual void		ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMoveData ) { return; }
	virtual void		FinishMove( CBasePlayer *player, CUserCmd *ucmd, CMoveData *move ) { return; }
	virtual bool		CanEnterVehicle( CBaseEntity *pEntity );
	virtual bool		CanExitVehicle( CBaseEntity *pEntity );
	virtual void		SetVehicleEntryAnim( bool bOn ) { m_bEnterAnimOn = bOn; }
	virtual void		SetVehicleExitAnim( bool bOn, Vector vecEyeExitEndpoint ) { m_bExitAnimOn = bOn; if ( bOn ) m_vecEyeExitEndpoint = vecEyeExitEndpoint; }
	virtual void		EnterVehicle( CBasePlayer *pPlayer );
	virtual bool		AllowBlockedExit( CBasePlayer *pPlayer, int iRole ) { return true; }
	virtual bool		AllowMidairExit( CBasePlayer *pPlayer, int nRole ) { return false; }
	virtual void		PreExitVehicle( CBasePlayer *pPlayer, int iRole ) {}
	virtual void		ExitVehicle( int iRole );

	
	// If this is a vehicle, returns the vehicle interface
	virtual IServerVehicle *GetServerVehicle() { return &m_ServerVehicle; }

	virtual void Precache( void );
	virtual void Spawn( void );
	virtual int	 ObjectCaps( void ) { return BaseClass::ObjectCaps() | FCAP_IMPULSE_USE; };
	virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
	virtual void Think( void );
	
	virtual	bool CreateVPhysics( void );
	virtual void UpdateOnRemove( void );
	virtual void OnRestore( void );

	void	DriveCannon( int iDriverButtons, int iButtonsPressed );
	void	ResetUseKey( CBasePlayer *pPlayer );
	void	InitCannonSpeeds( void );
	void	RunCraneMovement( float flTime );
	void	LaunchProjectile( void );

private:
	
	string_t		m_vehicleScript;

	// Entering / Exiting
	bool				m_bLocked;
	
	CNetworkVar( bool,	m_bEnterAnimOn );
	CNetworkVar( bool,	m_bExitAnimOn );
	CNetworkVector(		m_vecEyeExitEndpoint );

	CNetworkHandle( CBasePlayer, m_hPlayer );
	
	COutputEvent		m_playerOn;
	COutputEvent		m_playerOff;

	int				m_iTurning;
	float			m_flTurn;

	// Crane arm extension / retraction
	bool			m_bExtending;
	float			m_flExtension;
	float			m_flExtensionRate;

	// Speeds
	float			m_flMaxExtensionSpeed;
	float			m_flMaxTurnSpeed;
	float			m_flExtensionAccel;
	float			m_flExtensionDecel;
	float			m_flTurnAccel;
	float			m_flTurnDecel;

	float			m_flFlyTime;
	Vector			m_vCrashPoint;

	float			m_flNextAttackTime;

protected:
	// Contained IServerVehicle
	CCannonServerVehicle		m_ServerVehicle;

	// Contained Bone Follower manager
	CBoneFollowerManager		m_BoneFollowerManager;
};

LINK_ENTITY_TO_CLASS( prop_vehicle_cannon, CPropCannon );

BEGIN_DATADESC( CPropCannon )
	DEFINE_KEYFIELD( m_vehicleScript, FIELD_STRING, "vehiclescript" ),
	DEFINE_OUTPUT( m_playerOn, "PlayerOn" ),
	DEFINE_OUTPUT( m_playerOff, "PlayerOff" ),

	// DEFINE_EMBEDDED( m_BoneFollowerManager ),

	DEFINE_FIELD( m_iTurning, FIELD_INTEGER ),
	DEFINE_FIELD( m_flTurn, FIELD_FLOAT ),

	DEFINE_FIELD( m_bExtending, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flExtension, FIELD_FLOAT ),
	DEFINE_FIELD( m_flExtensionRate, FIELD_FLOAT ),

	DEFINE_FIELD( m_flMaxExtensionSpeed, FIELD_FLOAT ),
	DEFINE_FIELD( m_flMaxTurnSpeed, FIELD_FLOAT ),
	DEFINE_FIELD( m_flExtensionAccel, FIELD_FLOAT ),
	DEFINE_FIELD( m_flExtensionDecel, FIELD_FLOAT ),

	DEFINE_FIELD( m_flTurnAccel, FIELD_FLOAT ),
	DEFINE_FIELD( m_flTurnDecel, FIELD_FLOAT ),

	DEFINE_FIELD( m_flFlyTime, FIELD_TIME ),
	DEFINE_FIELD( m_vCrashPoint, FIELD_POSITION_VECTOR ),

	DEFINE_FIELD( m_flNextAttackTime, FIELD_TIME ),

END_DATADESC()

IMPLEMENT_SERVERCLASS_ST(CPropCannon, DT_PropCannon)
	SendPropEHandle(SENDINFO(m_hPlayer)),
	SendPropBool(SENDINFO(m_bEnterAnimOn)),
	SendPropBool(SENDINFO(m_bExitAnimOn)),
	SendPropVector(SENDINFO(m_vecEyeExitEndpoint), -1, SPROP_COORD),
END_SEND_TABLE();

//------------------------------------------------
// Precache
//------------------------------------------------
void CPropCannon::Precache( void )
{
	BaseClass::Precache();
	m_ServerVehicle.Initialize( STRING( m_vehicleScript ) );

	PrecacheModel( CANNON_PROJECTILE_MODEL );
	
	PrecacheScriptSound( "HeadcrabCanister.LaunchSound" );
	PrecacheScriptSound( "HeadcrabCanister.Explosion" );
	PrecacheScriptSound( "Weapon_Mortar.Incomming" );
}


//------------------------------------------------
// Spawn
//------------------------------------------------
void CPropCannon::Spawn( void )
{
	Precache();
	SetModel( STRING( GetModelName() ) );
	SetCollisionGroup( COLLISION_GROUP_VEHICLE );

	BaseClass::Spawn();

	SetSolid( SOLID_BBOX );
	AddSolidFlags( FSOLID_NOT_SOLID );
	SetMoveType( MOVETYPE_NOCLIP );

	m_takedamage = DAMAGE_EVENTS_ONLY;

	m_takedamage = DAMAGE_EVENTS_ONLY;
	m_flTurn = 0;
	m_flExtension = 0;

	m_flFlyTime = 0.0f;
	m_flNextAttackTime = gpGlobals->curtime;

	InitCannonSpeeds();
	
	SetPoseParameter( "armextensionpose", m_flExtension );

	CreateVPhysics();
	SetNextThink( gpGlobals->curtime );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPropCannon::InitCannonSpeeds( void )
{
	m_flMaxExtensionSpeed = CANNON_EXTENSION_RATE_MAX * 2;
	m_flMaxTurnSpeed = CANNON_TURN_RATE_MAX * 2;
	m_flExtensionAccel = CANNON_EXTENSION_ACCEL * 2;
	m_flExtensionDecel = CANNON_EXTENSION_DECEL * 2;
	m_flTurnAccel = CANNON_TURN_ACCEL * 2;
	m_flTurnDecel = CANNON_DECEL * 2;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPropCannon::OnRestore( void )
{
	BaseClass::OnRestore();

	CreateVPhysics();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CBaseEntity *CPropCannon::GetDriver( void ) 
{ 
	return m_hPlayer; 
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPropCannon::EnterVehicle( CBasePlayer *pPlayer )
{
	if ( !pPlayer )
		return;

	// Remove any player who may be in the vehicle at the moment
	if ( m_hPlayer )
	{
		ExitVehicle( VEHICLE_DRIVER );
	}

	m_hPlayer = pPlayer;
	m_playerOn.FireOutput( pPlayer, this, 0 );

	//m_ServerVehicle.SoundStart();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPropCannon::ExitVehicle( int iRole )
{
	CBasePlayer *pPlayer = m_hPlayer;
	if ( !pPlayer )
		return;

	m_hPlayer = NULL;
	ResetUseKey( pPlayer );
	m_playerOff.FireOutput( pPlayer, this, 0 );
	m_bEnterAnimOn = false;

	//m_ServerVehicle.SoundShutdown( 1.0 );
}

//-----------------------------------------------------------------------------
// Purpose: Return true of the player's allowed to enter / exit the vehicle
//-----------------------------------------------------------------------------
bool CPropCannon::CanEnterVehicle( CBaseEntity *pEntity )
{
	// Prevent entering if the vehicle's being driven by an NPC
	if ( GetDriver() && GetDriver() != pEntity )
		return false;
	
	// Prevent entering if the vehicle's locked
	return ( !m_bLocked );
}

//-----------------------------------------------------------------------------
// Purpose: Return true of the player's allowed to enter / exit the vehicle
//-----------------------------------------------------------------------------
bool CPropCannon::CanExitVehicle( CBaseEntity *pEntity )
{
	// Prevent exiting if the vehicle's locked, or rotating
	// Adrian: Check also if I'm currently jumping in or out.
	return ( !m_bLocked && (GetLocalAngularVelocity() == vec3_angle) && m_bExitAnimOn == false && m_bEnterAnimOn == false );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPropCannon::ResetUseKey( CBasePlayer *pPlayer )
{
	pPlayer->m_afButtonPressed &= ~IN_USE;
}

//-----------------------------------------------------------------------------
// Purpose: Pass player movement into the crane's driving system
//-----------------------------------------------------------------------------
void CPropCannon::SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move )
{
	// If the player's entering/exiting the vehicle, prevent movement
	if ( !m_bEnterAnimOn && !m_bExitAnimOn )
	{
		DriveCannon( ucmd->buttons, player->m_afButtonPressed );
	}

	// Run the crane's movement
	RunCraneMovement( gpGlobals->frametime );
}

//-----------------------------------------------------------------------------
// Purpose: Crane rotates around with +left and +right, and extends/retracts 
//			the cable with +forward and +back.
//-----------------------------------------------------------------------------
void CPropCannon::DriveCannon( int iDriverButtons, int iButtonsPressed )
{
	bool bWasExtending = m_bExtending;

	// Handle rotation of the crane
	if ( iDriverButtons & IN_MOVELEFT )
	{
		// Try adding some randomness to make it feel shaky? 
		float flTurnAdd = m_flTurnAccel;
		// If we're turning back on ourselves, use decel speed
		if ( m_flTurn < 0 )
		{
			flTurnAdd = max( flTurnAdd, m_flTurnDecel );
		}

		m_flTurn = UTIL_Approach( m_flMaxTurnSpeed, m_flTurn, flTurnAdd * gpGlobals->frametime );

		m_iTurning = CANNON_TURNING_LEFT;
	}
	else if ( iDriverButtons & IN_MOVERIGHT )
	{
		// Try adding some randomness to make it feel shaky?
		float flTurnAdd = m_flTurnAccel;
		// If we're turning back on ourselves, increase the rate
		if ( m_flTurn > 0 )
		{
			flTurnAdd = max( flTurnAdd, m_flTurnDecel );
		}
		m_flTurn = UTIL_Approach( -m_flMaxTurnSpeed, m_flTurn, flTurnAdd * gpGlobals->frametime );

		m_iTurning = CANNON_TURNING_RIGHT;
	}
	else
	{
		m_flTurn = UTIL_Approach( 0, m_flTurn, m_flTurnDecel * gpGlobals->frametime );
		m_iTurning = CANNON_TURNING_NOT;
	}

	SetLocalAngularVelocity( QAngle(0,m_flTurn * 10,0) );

	// Handle extension / retraction of the arm
	if ( iDriverButtons & IN_FORWARD )
	{
		m_flExtensionRate = UTIL_Approach( m_flMaxExtensionSpeed, m_flExtensionRate, m_flExtensionAccel * gpGlobals->frametime );
		m_bExtending = true;
	}
	else if ( iDriverButtons & IN_BACK )
	{
		m_flExtensionRate = UTIL_Approach( -m_flMaxExtensionSpeed, m_flExtensionRate, m_flExtensionAccel * gpGlobals->frametime );
		m_bExtending = true;
	}
	else
	{
		m_flExtensionRate = UTIL_Approach( 0, m_flExtensionRate, m_flExtensionDecel * gpGlobals->frametime );
		m_bExtending = false;
	}

	//Msg("Turn: %f\nExtensionRate: %f\n", m_flTurn, m_flExtensionRate );

	//If we're holding down an attack button, update our state
	if ( iButtonsPressed & (IN_ATTACK | IN_ATTACK2) )
	{
		if ( m_flNextAttackTime <= gpGlobals->curtime )
		{
			LaunchProjectile();
		}
	}

	float flSpeedPercentage = clamp( fabs(m_flTurn) / m_flMaxTurnSpeed, 0, 1 );
	vbs_sound_update_t params;
	params.Defaults();
	params.bThrottleDown = (m_iTurning != CANNON_TURNING_NOT);
	params.flCurrentSpeedFraction = flSpeedPercentage;
	params.flWorldSpaceSpeed = 0;

	m_ServerVehicle.SoundUpdate( params );

	// Play sounds for arm extension / retraction
	if ( m_bExtending && !bWasExtending )
	{
		m_ServerVehicle.StopSound( VS_ENGINE2_STOP );
		m_ServerVehicle.PlaySound( VS_ENGINE2_START );
	}
	else if ( !m_bExtending && bWasExtending )
	{
		m_ServerVehicle.StopSound( VS_ENGINE2_START );
		m_ServerVehicle.PlaySound( VS_ENGINE2_STOP );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pPlayer - 
//			*pMoveData - 
//-----------------------------------------------------------------------------
void CPropCannon::RunCraneMovement( float flTime )
{
	if ( m_flExtensionRate )
	{
		// Extend / Retract the crane
		m_flExtension = clamp( m_flExtension + (m_flExtensionRate * 10 * flTime), 0, 2 );
		SetPoseParameter( "armextensionpose", m_flExtension );
		StudioFrameAdvance();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *player - 
//-----------------------------------------------------------------------------
void CPropCannon::ItemPostFrame( CBasePlayer *player )
{

}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPropCannon::Think( void )
{
	SetNextThink( gpGlobals->curtime + 0.1 );

	if ( GetDriver() )
	{
		BaseClass::Think();
	}

	if ( GetDriver() )
	{
		BaseClass::Think();
	
		// play enter animation
		StudioFrameAdvance();

		// If the enter or exit animation has finished, tell the server vehicle
		if ( IsSequenceFinished() && (m_bExitAnimOn || m_bEnterAnimOn) )
		{
			if ( m_bEnterAnimOn )
			{
				// Finished entering, display the hint for using the crane
				//UTIL_HudHintText( m_hPlayer, "#Valve_Hint_CraneKeys" );
			}
			
			GetServerVehicle()->HandleEntryExitFinish( m_bExitAnimOn, true );
		}
	}
	else
	{
		// Run the crane's movement
	//	RunCraneMovement( 0.1 );
	}

	// Update follower bones
	m_BoneFollowerManager.UpdateBoneFollowers();

	if ( m_flFlyTime > 0.0f )
	{
		if ( m_flFlyTime - 1.0f <= gpGlobals->curtime && m_flFlyTime - 0.8f > gpGlobals->curtime)
		{
			CPASAttenuationFilter filter( this );
				
			EmitSound_t ep;
			ep.m_nChannel = CHAN_STATIC;
			ep.m_pSoundName = "Weapon_Mortar.Incomming";
			ep.m_flVolume = 255;
			ep.m_SoundLevel = SNDLVL_180dB;
	
			EmitSound( filter, entindex(), ep );
		}

		if ( m_flFlyTime <= gpGlobals->curtime )
		{
			if ( m_vCrashPoint != vec3_origin )
			{
				ExplosionCreate( m_vCrashPoint, vec3_angle, NULL, 512, 512, true );
			}

			CEffectData	data;

			data.m_vOrigin = m_vCrashPoint;
			data.m_flScale = 512;
			DispatchEffect( "ThumperDust", data );

			m_flFlyTime = 0.0f;
		}
	}

}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPropCannon::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	CBasePlayer *pPlayer = ToBasePlayer( pActivator );
	if ( !pPlayer )
		return;

	ResetUseKey( pPlayer );

	GetServerVehicle()->HandlePassengerEntry( pPlayer, (value>0) );
}

void CPropCannon::LaunchProjectile( void )
{
	//ADRIANTODO: Come back to this once we get the right model and remove all the fix ups caused by temp content.

	Vector vTipPos, vTipForward, vTipRight, vUp;

	GetAttachment( "cable_tip", vTipPos, &vTipForward, &vTipRight, &vUp );

	bool bCollided = false;
	bool bInSky = false;
	float gravity = -gpGlobals->frametime * 600;
	Vector vOrigin = vTipPos;
	Vector vVelocity = vTipRight * 2500;

	float flDistance = 0.0f;

	int iFailSafe = 0;
	
	while ( bCollided == false && iFailSafe < 1000 )
	{
		Vector vOldOrigin = vOrigin;
		vOrigin = vOrigin + vVelocity * gpGlobals->frametime;

		flDistance += (vOrigin - vOldOrigin).Length();

		if ( g_cannon_debug.GetBool() == true )
		{
			NDebugOverlay::Line( vOldOrigin, vOrigin, 0, 255, 0, true, 5 );
		}

		trace_t pm;		
		UTIL_TraceLine( vOldOrigin, vOrigin, MASK_SOLID, this, COLLISION_GROUP_NONE, &pm );

		if ( pm.surface.flags & SURF_SKY || pm.allsolid == true ) 
		{
			bInSky = true;
			iFailSafe++;
		}
		else
		{
			bInSky = false;
		}

		if ( pm.fraction != 1.0f && bInSky == false )
		{
			bCollided = true;
			vOrigin = pm.endpos;

			if ( g_cannon_debug.GetBool() == true )
			{
				NDebugOverlay::Box( vOrigin, Vector( 256, 256, 256 ), Vector( -256, -256, -256 ), 255, 0, 0, 0, 5 );
			}
		}
		else
		{
			vVelocity[2] += gravity;
		}
	}
	
	float flTravelTime = flDistance / vVelocity.Length();

	if ( flTravelTime > g_cannon_max_traveltime.GetFloat() )
	{
		flTravelTime = g_cannon_max_traveltime.GetFloat();
		vOrigin = vec3_origin; 
	}

	m_flFlyTime = gpGlobals->curtime + flTravelTime;
	m_vCrashPoint = vOrigin;

	m_flNextAttackTime = gpGlobals->curtime + g_cannon_reloadtime.GetFloat();

	EmitSound( "HeadcrabCanister.LaunchSound" );

	UTIL_ScreenShake( GetDriver()->GetAbsOrigin(), 50.0, 150.0, 1.0, 750, SHAKE_START, true );
}

//========================================================================================================================================
// CRANE VEHICLE SERVER VEHICLE
//========================================================================================================================================
CPropCannon *CCannonServerVehicle::GetCannon( void )
{
	return (CPropCannon*)GetDrivableVehicle();
}


//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CPropCannon::CreateVPhysics( void )
{
	BaseClass::CreateVPhysics();
	m_BoneFollowerManager.InitBoneFollowers( this, ARRAYSIZE(pCannonFollowerBoneNames), pCannonFollowerBoneNames );
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPropCannon::UpdateOnRemove( void )
{
	m_BoneFollowerManager.DestroyBoneFollowers();
	BaseClass::UpdateOnRemove();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CCannonServerVehicle::GetVehicleViewPosition( int nRole, Vector *pAbsOrigin, QAngle *pAbsAngles )
{
	Assert( nRole == VEHICLE_DRIVER );
	CBasePlayer *pPlayer = ToBasePlayer( GetDrivableVehicle()->GetDriver() );
	Assert( pPlayer );

	*pAbsAngles = pPlayer->EyeAngles(); // yuck. this is an in/out parameter.

	float flPitchFactor = 1.0;
	matrix3x4_t vehicleEyePosToWorld;
	Vector vehicleEyeOrigin;
	QAngle vehicleEyeAngles;
	GetCannon()->GetAttachment( "vehicle_driver_eyes", vehicleEyeOrigin, vehicleEyeAngles );
	AngleMatrix( vehicleEyeAngles, vehicleEyePosToWorld );

	// Compute the relative rotation between the unperterbed eye attachment + the eye angles
	matrix3x4_t cameraToWorld;
	AngleMatrix( *pAbsAngles, cameraToWorld );

	matrix3x4_t worldToEyePos;
	MatrixInvert( vehicleEyePosToWorld, worldToEyePos );

	matrix3x4_t vehicleCameraToEyePos;
	ConcatTransforms( worldToEyePos, cameraToWorld, vehicleCameraToEyePos );

	// Now perterb the attachment point
	vehicleEyeAngles.x = RemapAngleRange( PITCH_CURVE_ZERO * flPitchFactor, PITCH_CURVE_LINEAR, vehicleEyeAngles.x );
	vehicleEyeAngles.z = RemapAngleRange( ROLL_CURVE_ZERO * flPitchFactor, ROLL_CURVE_LINEAR, vehicleEyeAngles.z );
	AngleMatrix( vehicleEyeAngles, vehicleEyeOrigin, vehicleEyePosToWorld );

	// Now treat the relative eye angles as being relative to this new, perterbed view position...
	matrix3x4_t newCameraToWorld;
	ConcatTransforms( vehicleEyePosToWorld, vehicleCameraToEyePos, newCameraToWorld );

	// output new view abs angles
	MatrixAngles( newCameraToWorld, *pAbsAngles );

	// UNDONE: *pOrigin would already be correct in single player if the HandleView() on the server ran after vphysics
	MatrixGetColumn( newCameraToWorld, 3, *pAbsOrigin );
}