source-engine/game/server/hl2/vehicle_cannon.cpp

750 lines
22 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright 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", "3", FCVAR_CHEAT | FCVAR_GAMEDLL );
ConVar g_cannon_max_traveltime( "g_cannon_max_traveltime", "1.5", FCVAR_CHEAT | FCVAR_GAMEDLL );
ConVar g_cannon_debug( "g_cannon_debug", "0", FCVAR_CHEAT | FCVAR_GAMEDLL );
ConVar g_cannon_damageandradius( "g_cannon_damageandradius", "512", 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, float *pFOV = NULL );
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( CBaseCombatCharacter *pPassenger );
virtual bool AllowBlockedExit( CBaseCombatCharacter *pPassenger, int nRole ) { return true; }
virtual bool AllowMidairExit( CBaseCombatCharacter *pPassenger, int nRole ) { return false; }
virtual void PreExitVehicle( CBaseCombatCharacter *pPassenger, int nRole ) {}
virtual void ExitVehicle( int nRole );
virtual string_t GetVehicleScriptName() { return m_vehicleScript; }
virtual bool PassengerShouldReceiveDamage( CTakeDamageInfo &info ) { return false; }
// 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 );
void DriveCannon( int iDriverButtons, int iButtonsPressed );
void ResetUseKey( CBasePlayer *pPlayer );
void InitCannonSpeeds( void );
void RunCraneMovement( float flTime );
void LaunchProjectile( void );
void ProjectileExplosion( 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_hPlayer, FIELD_EHANDLE ),
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:
//-----------------------------------------------------------------------------
CBaseEntity *CPropCannon::GetDriver( void )
{
return m_hPlayer;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPropCannon::EnterVehicle( CBaseCombatCharacter *pPassenger )
{
if ( pPassenger == NULL )
return;
CBasePlayer *pPlayer = ToBasePlayer( pPassenger );
if ( pPlayer != NULL )
{
// Remove any player who may be in the vehicle at the moment
if ( m_hPlayer )
{
ExitVehicle( VEHICLE_ROLE_DRIVER );
}
m_hPlayer = pPlayer;
m_playerOn.FireOutput( pPlayer, this, 0 );
//m_ServerVehicle.SoundStart();
}
else
{
// NPCs not supported yet - jdw
Assert( 0 );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPropCannon::ExitVehicle( int nRole )
{
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 )
{
}
void CPropCannon::ProjectileExplosion( void )
{
ExplosionCreate( m_vCrashPoint, vec3_angle, NULL, 512, 512, false );
// do damage
CTakeDamageInfo info( this, this, g_cannon_damageandradius.GetInt(), DMG_BLAST );
info.SetDamagePosition( m_vCrashPoint );
RadiusDamage( info, m_vCrashPoint, g_cannon_damageandradius.GetInt(), CLASS_NONE, NULL );
}
//-----------------------------------------------------------------------------
// 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(this);
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 )
{
ProjectileExplosion();
}
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 < 100000 )
{
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;
}
iFailSafe++;
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();
if ( bCollided == false )
{
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, float *pFOV /*= NULL*/ )
{
Assert( nRole == VEHICLE_ROLE_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 );
}