text: //========= Copyright Valve Corporation, All rights reserved. ============//
  //
  // Purpose: 
  //
  // $NoKeywords: $
  //=============================================================================//
  #include "cbase.h"
  #include "vehicle_apc.h"
  #include "ammodef.h"
  #include "IEffects.h"
  #include "engine/IEngineSound.h"
  #include "weapon_rpg.h"
  #include "in_buttons.h"
  #include "globalstate.h"
  #include "soundent.h"
  #include "ai_basenpc.h"
  #include "ndebugoverlay.h"
  #include "gib.h"
  #include "EntityFlame.h"
  #include "smoke_trail.h"
  #include "explode.h"
  #include "effect_dispatch_data.h"
  #include "te_effect_dispatch.h"
  
  // memdbgon must be the last include file in a .cpp file!!!
  #include "tier0/memdbgon.h"
  
  
  #define ROCKET_ATTACK_RANGE_MAX 5500.0f
  #define ROCKET_ATTACK_RANGE_MIN 1250.0f
  
  #define MACHINE_GUN_ATTACK_RANGE_MAX 1250.0f
  #define MACHINE_GUN_ATTACK_RANGE_MIN 0.0f
  
  #define MACHINE_GUN_MAX_UP_PITCH	30
  #define MACHINE_GUN_MAX_DOWN_PITCH	10
  #define MACHINE_GUN_MAX_LEFT_YAW	30
  #define MACHINE_GUN_MAX_RIGHT_YAW	30
  
  #define MACHINE_GUN_BURST_SIZE		10
  #define MACHINE_GUN_BURST_TIME		0.075f
  #define MACHINE_GUN_BURST_PAUSE_TIME	2.0f
  
  #define ROCKET_SALVO_SIZE				5
  #define ROCKET_DELAY_TIME				1.5
  #define ROCKET_MIN_BURST_PAUSE_TIME		3
  #define ROCKET_MAX_BURST_PAUSE_TIME		4
  #define ROCKET_SPEED					800
  #define DEATH_VOLLEY_ROCKET_COUNT		4
  #define DEATH_VOLLEY_MIN_FIRE_TIME		0.333
  #define DEATH_VOLLEY_MAX_FIRE_TIME		0.166
  
  extern short g_sModelIndexFireball; // Echh...
  
  
  ConVar sk_apc_health( "sk_apc_health", "750" );
  
  
  #define APC_MAX_CHUNKS	3
  static const char *s_pChunkModelName[APC_MAX_CHUNKS] = 
  {
  	"models/gibs/helicopter_brokenpiece_01.mdl",
  	"models/gibs/helicopter_brokenpiece_02.mdl",
  	"models/gibs/helicopter_brokenpiece_03.mdl",
  };
  
  #define APC_MAX_GIBS	6
  static const char *s_pGibModelName[APC_MAX_GIBS] = 
  {
  	"models/combine_apc_destroyed_gib01.mdl",
  	"models/combine_apc_destroyed_gib02.mdl",
  	"models/combine_apc_destroyed_gib03.mdl",
  	"models/combine_apc_destroyed_gib04.mdl",
  	"models/combine_apc_destroyed_gib05.mdl",
  	"models/combine_apc_destroyed_gib06.mdl",
  };
  
  
  LINK_ENTITY_TO_CLASS( prop_vehicle_apc, CPropAPC );
  
  
  BEGIN_DATADESC( CPropAPC )
  
  	DEFINE_FIELD( m_flDangerSoundTime,	FIELD_TIME ),
  	DEFINE_FIELD( m_flHandbrakeTime,	FIELD_TIME ),
  	DEFINE_FIELD( m_bInitialHandbrake,	FIELD_BOOLEAN ),
  	DEFINE_FIELD( m_nSmokeTrailCount,	FIELD_INTEGER ),
  	DEFINE_FIELD( m_flMachineGunTime,		FIELD_TIME ),
  	DEFINE_FIELD( m_iMachineGunBurstLeft,	FIELD_INTEGER ),
  //	DEFINE_FIELD( m_nMachineGunMuzzleAttachment,	FIELD_INTEGER ),
  //	DEFINE_FIELD( m_nMachineGunBaseAttachment,	FIELD_INTEGER ),
  //	DEFINE_FIELD( m_vecBarrelPos,		FIELD_VECTOR ),
  	DEFINE_FIELD( m_bInFiringCone,		FIELD_BOOLEAN ),
  //	DEFINE_FIELD( m_hLaserDot,			FIELD_EHANDLE ),
  	DEFINE_FIELD( m_hRocketTarget,			FIELD_EHANDLE ),
  	DEFINE_FIELD( m_iRocketSalvoLeft,	FIELD_INTEGER ),
  	DEFINE_FIELD( m_flRocketTime,		FIELD_TIME ),
  //	DEFINE_FIELD( m_nRocketAttachment,	FIELD_INTEGER ),
  	DEFINE_FIELD( m_nRocketSide,		FIELD_INTEGER ),
  	DEFINE_FIELD( m_hSpecificRocketTarget, FIELD_EHANDLE ),
  	DEFINE_KEYFIELD( m_strMissileHint,	FIELD_STRING, "missilehint" ),
  
  	DEFINE_INPUTFUNC( FIELD_VOID, "Destroy", InputDestroy ),
  	DEFINE_INPUTFUNC( FIELD_STRING, "FireMissileAt", InputFireMissileAt ),
  
  	DEFINE_OUTPUT( m_OnDeath,				"OnDeath" ),
  	DEFINE_OUTPUT( m_OnFiredMissile,		"OnFiredMissile" ),
  	DEFINE_OUTPUT( m_OnDamaged,				"OnDamaged" ),
  	DEFINE_OUTPUT( m_OnDamagedByPlayer,		"OnDamagedByPlayer" ),
  
  END_DATADESC()
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CPropAPC::Precache( void )
  {
  	BaseClass::Precache();
  
  	int i;
  	for ( i = 0; i < APC_MAX_CHUNKS; ++i )
  	{
  		PrecacheModel( s_pChunkModelName[i] );
  	}
  
  	for ( i = 0; i < APC_MAX_GIBS; ++i )
  	{
  		PrecacheModel( s_pGibModelName[i] );
  	}
  
  	PrecacheScriptSound( "Weapon_AR2.Single" );
  	PrecacheScriptSound( "PropAPC.FireRocket" );
  	PrecacheScriptSound( "combine.door_lock" );
  }
  
  
  //------------------------------------------------
  // Spawn
  //------------------------------------------------
  void CPropAPC::Spawn( void )
  {
  	BaseClass::Spawn();
  	SetBlocksLOS( true );
  	m_iHealth = m_iMaxHealth = sk_apc_health.GetFloat();
  	SetCycle( 0 );
  	m_iMachineGunBurstLeft = MACHINE_GUN_BURST_SIZE;
  	m_iRocketSalvoLeft = ROCKET_SALVO_SIZE;
  	m_nRocketSide = 0;
  	m_lifeState = LIFE_ALIVE;
  	m_bInFiringCone = false;
  
  	m_flHandbrakeTime = gpGlobals->curtime + 0.1;
  	m_bInitialHandbrake = false;
  
  	// Reset the gun to a default pose.
  	SetPoseParameter( "vehicle_weapon_pitch", 0 );
  	SetPoseParameter( "vehicle_weapon_yaw", 90 );
  
  	CreateAPCLaserDot();
  
  	if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE )
  	{
  		AddFlag( FL_AIMTARGET );
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Create a laser
  //-----------------------------------------------------------------------------
  void CPropAPC::CreateAPCLaserDot( void )
  {
  	// Create a laser if we don't have one
  	//if ( m_hLaserDot == NULL )
  	//{
  		//m_hLaserDot = CreateLaserDot( GetAbsOrigin(), this, false );
  	//}
  }
  
  //-----------------------------------------------------------------------------
  //-----------------------------------------------------------------------------
  bool CPropAPC::ShouldAttractAutoAim( CBaseEntity *pAimingEnt )
  {
  	if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE && pAimingEnt->IsPlayer() && GetDriver() )
  	{
  		return true;
  	}
  
  	return BaseClass::ShouldAttractAutoAim( pAimingEnt );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CPropAPC::Activate()
  {
  	BaseClass::Activate();
  
  	m_nRocketAttachment = LookupAttachment( "cannon_muzzle" );
  	m_nMachineGunMuzzleAttachment = LookupAttachment( "muzzle" );
  	m_nMachineGunBaseAttachment = LookupAttachment( "gun_base" );
  
  	// NOTE: gun_ref must have the same position as gun_base, but rotates with the gun
  	int nMachineGunRefAttachment = LookupAttachment( "gun_def" );
  
  	Vector vecWorldBarrelPos;
  	matrix3x4_t matRefToWorld;
  	GetAttachment( m_nMachineGunMuzzleAttachment, vecWorldBarrelPos );
  	GetAttachment( nMachineGunRefAttachment, matRefToWorld );
  	VectorITransform( vecWorldBarrelPos, matRefToWorld, m_vecBarrelPos );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CPropAPC::UpdateOnRemove( void )
  {
  	if ( m_hLaserDot )
  	{
  		UTIL_Remove( m_hLaserDot );
  		m_hLaserDot = NULL;
  	}
  	BaseClass::UpdateOnRemove();
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CPropAPC::CreateServerVehicle( void )
  {
  	// Create our armed server vehicle
  	m_pServerVehicle = new CAPCFourWheelServerVehicle();
  	m_pServerVehicle->SetVehicle( this );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Input  : *pMoveData - 
  //-----------------------------------------------------------------------------
  Class_T	CPropAPC::ClassifyPassenger( CBaseCombatCharacter *pPassenger, Class_T defaultClassification )
  { 
  	return CLASS_COMBINE;	
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Damage events as modified for the passenger of the APC, not the APC itself
  //-----------------------------------------------------------------------------
  float CPropAPC::PassengerDamageModifier( const CTakeDamageInfo &info ) 
  { 
  	CTakeDamageInfo DmgInfo = info;
  
  	// bullets, slashing and headbutts don't hurt us in the apc, neither do rockets
  	if( (DmgInfo.GetDamageType() & DMG_BULLET) || (DmgInfo.GetDamageType() & DMG_SLASH) ||
  		(DmgInfo.GetDamageType() & DMG_CLUB) || (DmgInfo.GetDamageType() & DMG_BLAST) )
  		return (0);
  
  	// Accept everything else by default
  	return 1.0; 
  }
  
  
  //-----------------------------------------------------------------------------
  // position of eyes
  //-----------------------------------------------------------------------------
  Vector CPropAPC::EyePosition( )
  {
  	Vector vecEyePosition;
  	CollisionProp()->NormalizedToWorldSpace( Vector( 0.5, 0.5, 1.0 ), &vecEyePosition );
  	return vecEyePosition;
  }
  
  //-----------------------------------------------------------------------------
  //-----------------------------------------------------------------------------
  Vector CPropAPC::BodyTarget( const Vector &posSrc, bool bNoisy ) 
  {
  	if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE )
  	{
  		return WorldSpaceCenter();
  	}
  
  	return BaseClass::BodyTarget( posSrc, bNoisy );
  }
  	
  //-----------------------------------------------------------------------------
  // Add a smoke trail since we've taken more damage
  //-----------------------------------------------------------------------------
  void CPropAPC::AddSmokeTrail( const Vector &vecPos )
  {
  	// Start this trail out with a bang!
  	ExplosionCreate( vecPos, vec3_angle, this, 1000, 500.0f, SF_ENVEXPLOSION_NODAMAGE | 
  		SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE | 
  		SF_ENVEXPLOSION_NOFIREBALLSMOKE, 0 );
  	UTIL_ScreenShake( vecPos, 25.0, 150.0, 1.0, 750.0f, SHAKE_START );
  
  	if ( m_nSmokeTrailCount == MAX_SMOKE_TRAILS )
  		return;
  
  	SmokeTrail *pSmokeTrail =  SmokeTrail::CreateSmokeTrail();
  	if( !pSmokeTrail )
  		return;
  
  	// See if there's an attachment for this smoke trail
  	char buf[32];
  	Q_snprintf( buf, 32, "damage%d", m_nSmokeTrailCount );
  	int nAttachment = LookupAttachment( buf );
  
  	++m_nSmokeTrailCount;
  
  	pSmokeTrail->m_SpawnRate = 4;
  	pSmokeTrail->m_ParticleLifetime = 5.0f;
  	pSmokeTrail->m_StartColor.Init( 0.7f, 0.7f, 0.7f );
  	pSmokeTrail->m_EndColor.Init( 0.6, 0.6, 0.6 );
  	pSmokeTrail->m_StartSize = 32;
  	pSmokeTrail->m_EndSize = 64;
  	pSmokeTrail->m_SpawnRadius = 4;
  	pSmokeTrail->m_Opacity = 0.5f;
  	pSmokeTrail->m_MinSpeed = 16;
  	pSmokeTrail->m_MaxSpeed = 16;
  	pSmokeTrail->m_MinDirectedSpeed	= 16.0f;
  	pSmokeTrail->m_MaxDirectedSpeed	= 16.0f;
  	pSmokeTrail->SetLifetime( 5 );
  	pSmokeTrail->SetParent( this, nAttachment );
  
  	Vector vecForward( 0, 0, 1 );
  	QAngle angles;
  	VectorAngles( vecForward, angles );
  
  	if ( nAttachment == 0 )
  	{
  		pSmokeTrail->SetAbsOrigin( vecPos );
  		pSmokeTrail->SetAbsAngles( angles );
  	}
  	else
  	{
  		pSmokeTrail->SetLocalOrigin( vec3_origin );
  		pSmokeTrail->SetLocalAngles( angles );
  	}
  
  	pSmokeTrail->SetMoveType( MOVETYPE_NONE );
  }
  
  
  //------------------------------------------------------------------------------
  // Pow!
  //------------------------------------------------------------------------------
  void CPropAPC::ExplodeAndThrowChunk( const Vector &vecExplosionPos )
  {
  	ExplosionCreate( vecExplosionPos, vec3_angle, this, 1000, 500.0f, 
  		SF_ENVEXPLOSION_NODAMAGE | SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS	|
  		SF_ENVEXPLOSION_NOSMOKE  | SF_ENVEXPLOSION_NOFIREBALLSMOKE, 0 );
  	UTIL_ScreenShake( vecExplosionPos, 25.0, 150.0, 1.0, 750.0f, SHAKE_START );
  
  	// Drop a flaming, smoking chunk.
  	CGib *pChunk = CREATE_ENTITY( CGib, "gib" );
  	pChunk->Spawn( "models/gibs/hgibs.mdl" );
  	pChunk->SetBloodColor( DONT_BLEED );
  
  	QAngle vecSpawnAngles;
  	vecSpawnAngles.Random( -90, 90 );
  	pChunk->SetAbsOrigin( vecExplosionPos );
  	pChunk->SetAbsAngles( vecSpawnAngles );
  
  	int nGib = random->RandomInt( 0, APC_MAX_CHUNKS - 1 );
  	pChunk->Spawn( s_pChunkModelName[nGib] );
  	pChunk->SetOwnerEntity( this );
  	pChunk->m_lifeTime = random->RandomFloat( 6.0f, 8.0f );
  	pChunk->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
  	IPhysicsObject *pPhysicsObject = pChunk->VPhysicsInitNormal( SOLID_VPHYSICS, pChunk->GetSolidFlags(), false );
  	
  	// Set the velocity
  	if ( pPhysicsObject )
  	{
  		pPhysicsObject->EnableMotion( true );
  		Vector vecVelocity;
  
  		QAngle angles;
  		angles.x = random->RandomFloat( -40, 0 );
  		angles.y = random->RandomFloat( 0, 360 );
  		angles.z = 0.0f;
  		AngleVectors( angles, &vecVelocity );
  		
  		vecVelocity *= random->RandomFloat( 300, 900 );
  		vecVelocity += GetAbsVelocity();
  
  		AngularImpulse angImpulse;
  		angImpulse = RandomAngularImpulse( -180, 180 );
  
  		pChunk->SetAbsVelocity( vecVelocity );
  		pPhysicsObject->SetVelocity(&vecVelocity, &angImpulse );
  	}
  
  	CEntityFlame *pFlame = CEntityFlame::Create( pChunk, false );
  	if ( pFlame != NULL )
  	{
  		pFlame->SetLifetime( pChunk->m_lifeTime );
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Should we trigger a damage effect?
  //-----------------------------------------------------------------------------
  inline bool CPropAPC::ShouldTriggerDamageEffect( int nPrevHealth, int nEffectCount ) const
  {
  	int nPrevRange = (int)( ((float)nPrevHealth / (float)GetMaxHealth()) * nEffectCount );
  	int nRange = (int)( ((float)GetHealth() / (float)GetMaxHealth()) * nEffectCount );
  	return ( nRange != nPrevRange );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CPropAPC::Event_Killed( const CTakeDamageInfo &info )
  {
  	m_OnDeath.FireOutput( info.GetAttacker(), this );
  
  	Vector vecAbsMins, vecAbsMaxs;
  	CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs );
  
  	Vector vecNormalizedMins, vecNormalizedMaxs;
  	CollisionProp()->WorldToNormalizedSpace( vecAbsMins, &vecNormalizedMins );
  	CollisionProp()->WorldToNormalizedSpace( vecAbsMaxs, &vecNormalizedMaxs );
  
  	Vector vecAbsPoint;
  	CPASFilter filter( GetAbsOrigin() );
  	for (int i = 0; i < 5; i++)
  	{
  		CollisionProp()->RandomPointInBounds( vecNormalizedMins, vecNormalizedMaxs, &vecAbsPoint );
  		te->Explosion( filter, random->RandomFloat( 0.0, 1.0 ),	&vecAbsPoint, 
  			g_sModelIndexFireball, random->RandomInt( 4, 10 ), 
  			random->RandomInt( 8, 15 ), 
  			( i < 2 ) ? TE_EXPLFLAG_NODLIGHTS : TE_EXPLFLAG_NOPARTICLES | TE_EXPLFLAG_NOFIREBALLSMOKE | TE_EXPLFLAG_NODLIGHTS,
  			100, 0 );
  	}
  
  	// TODO: make the gibs spawn in sync with the delayed explosions
  	int nGibs = random->RandomInt( 1, 4 );
  	for ( int i = 0; i < nGibs; i++)
  	{
  		// Throw a flaming, smoking chunk.
  		CGib *pChunk = CREATE_ENTITY( CGib, "gib" );
  		pChunk->Spawn( "models/gibs/hgibs.mdl" );
  		pChunk->SetBloodColor( DONT_BLEED );
  
  		QAngle vecSpawnAngles;
  		vecSpawnAngles.Random( -90, 90 );
  		pChunk->SetAbsOrigin( vecAbsPoint );
  		pChunk->SetAbsAngles( vecSpawnAngles );
  
  		int nGib = random->RandomInt( 0, APC_MAX_CHUNKS - 1 );
  		pChunk->Spawn( s_pChunkModelName[nGib] );
  		pChunk->SetOwnerEntity( this );
  		pChunk->m_lifeTime = random->RandomFloat( 6.0f, 8.0f );
  		pChunk->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
  		IPhysicsObject *pPhysicsObject = pChunk->VPhysicsInitNormal( SOLID_VPHYSICS, pChunk->GetSolidFlags(), false );
  		
  		// Set the velocity
  		if ( pPhysicsObject )
  		{
  			pPhysicsObject->EnableMotion( true );
  			Vector vecVelocity;
  
  			QAngle angles;
  			angles.x = random->RandomFloat( -20, 20 );
  			angles.y = random->RandomFloat( 0, 360 );
  			angles.z = 0.0f;
  			AngleVectors( angles, &vecVelocity );
  			
  			vecVelocity *= random->RandomFloat( 300, 900 );
  			vecVelocity += GetAbsVelocity();
  
  			AngularImpulse angImpulse;
  			angImpulse = RandomAngularImpulse( -180, 180 );
  
  			pChunk->SetAbsVelocity( vecVelocity );
  			pPhysicsObject->SetVelocity(&vecVelocity, &angImpulse );
  		}
  
  		CEntityFlame *pFlame = CEntityFlame::Create( pChunk, false );
  		if ( pFlame != NULL )
  		{
  			pFlame->SetLifetime( pChunk->m_lifeTime );
  		}
  	}
  
  	UTIL_ScreenShake( vecAbsPoint, 25.0, 150.0, 1.0, 750.0f, SHAKE_START );
  
  	if( hl2_episodic.GetBool() )
  	{
  		// EP1 perf hit
  		Ignite( 6, false );
  	}
  	else
  	{
  		Ignite( 60, false );
  	}
  
  	m_lifeState = LIFE_DYING;
  
  	// Spawn a lesser amount if the player is close
  	m_iRocketSalvoLeft = DEATH_VOLLEY_ROCKET_COUNT;
  	m_flRocketTime = gpGlobals->curtime;
  }
  
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Blows it up!
  //-----------------------------------------------------------------------------
  void CPropAPC::InputDestroy( inputdata_t &inputdata )
  {
  	CTakeDamageInfo info( this, this, m_iHealth, DMG_BLAST );
  	info.SetDamagePosition( WorldSpaceCenter() );
  	info.SetDamageForce( Vector( 0, 0, 1 ) );
  	TakeDamage( info );
  }
  
  
  //-----------------------------------------------------------------------------
  // Aim the next rocket at a specific target
  //-----------------------------------------------------------------------------
  void CPropAPC::InputFireMissileAt( inputdata_t &inputdata )
  {
  	string_t strMissileTarget = MAKE_STRING( inputdata.value.String() );
  	CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, strMissileTarget, NULL, inputdata.pActivator, inputdata.pCaller );
  	if ( pTarget == NULL )
  	{
  		DevWarning( "%s: Could not find target '%s'!\n", GetClassname(), STRING( strMissileTarget ) );
  		return;
  	}
  
  	m_hSpecificRocketTarget = pTarget;
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  int CPropAPC::OnTakeDamage( const CTakeDamageInfo &info )
  {
  	if ( m_iHealth == 0 )
  		return 0;
  
  	m_OnDamaged.FireOutput( info.GetAttacker(), this );
  
  	if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() )
  	{
  		m_OnDamagedByPlayer.FireOutput( info.GetAttacker(), this );
  	}
  
  	CTakeDamageInfo dmgInfo = info;
  	if ( dmgInfo.GetDamageType() & (DMG_BLAST | DMG_AIRBOAT) )
  	{
  		int nPrevHealth = GetHealth();
  
  		m_iHealth -= dmgInfo.GetDamage();
  		if ( m_iHealth <= 0 )
  		{
  			m_iHealth = 0;
  			Event_Killed( dmgInfo );
  			return 0;
  		}
  
  		// Chain
  //		BaseClass::OnTakeDamage( dmgInfo );
  
  		// Spawn damage effects
  		if ( nPrevHealth != GetHealth() )
  		{
  			if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_SMOKE_TRAILS ) )
  			{
  				AddSmokeTrail( dmgInfo.GetDamagePosition() );
  			}
  
  			if ( ShouldTriggerDamageEffect( nPrevHealth, MAX_EXPLOSIONS ) )
  			{
  				ExplodeAndThrowChunk( dmgInfo.GetDamagePosition() );
  			}
  		}
  	}
  	return 1;
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Input  : *pMoveData - 
  //-----------------------------------------------------------------------------
  void CPropAPC::ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMoveData )
  {
  	BaseClass::ProcessMovement( pPlayer, pMoveData );
  
  	if ( m_flDangerSoundTime > gpGlobals->curtime )
  		return;
  
  	QAngle vehicleAngles = GetLocalAngles();
  	Vector vecStart = GetAbsOrigin();
  	Vector vecDir;
  
  	GetVectors( &vecDir, NULL, NULL );
  
  	// Make danger sounds ahead of the APC
  	trace_t	tr;
  	Vector	vecSpot, vecLeftDir, vecRightDir;
  
  	// lay down sound path
  	vecSpot = vecStart + vecDir * 600;
  	CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, 400, 0.1, this );
  
  	// put sounds a bit to left and right but slightly closer to APC to make a "cone" of sound 
  	// in front of it
  	QAngle leftAngles = vehicleAngles;
  	leftAngles[YAW] += 20;
  	VehicleAngleVectors( leftAngles, &vecLeftDir, NULL, NULL );
  	vecSpot = vecStart + vecLeftDir * 400;
  	UTIL_TraceLine( vecStart, vecSpot, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  	CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, 400, 0.1, this );
  
  	QAngle rightAngles = vehicleAngles;
  	rightAngles[YAW] -= 20;
  	VehicleAngleVectors( rightAngles, &vecRightDir, NULL, NULL );
  	vecSpot = vecStart + vecRightDir * 400;
  	UTIL_TraceLine( vecStart, vecSpot, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  	CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, 400, 0.1, this);
  
  	m_flDangerSoundTime = gpGlobals->curtime + 0.3;
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CPropAPC::Think( void )
  {
  	BaseClass::Think();
  
  	SetNextThink( gpGlobals->curtime );
  
  	if ( !m_bInitialHandbrake )	// after initial timer expires, set the handbrake
  	{
  		m_bInitialHandbrake = true;
  		m_VehiclePhysics.SetHandbrake( true );
  		m_VehiclePhysics.Think();
  	}
  
  	StudioFrameAdvance();
  
  	if ( IsSequenceFinished() )
  	{
  		int iSequence = SelectWeightedSequence( ACT_IDLE );
  		if ( iSequence > ACTIVITY_NOT_AVAILABLE )
  		{
  			SetCycle( 0 );
  			m_flAnimTime = gpGlobals->curtime;
  			ResetSequence( iSequence );
  			ResetClientsideFrame();
  		}
  	}
  
  	if (m_debugOverlays & OVERLAY_NPC_KILL_BIT)
  	{
  		CTakeDamageInfo info( this, this, m_iHealth, DMG_BLAST );
  		info.SetDamagePosition( WorldSpaceCenter() );
  		info.SetDamageForce( Vector( 0, 0, 1 ) );
  		TakeDamage( info );
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Aims the secondary weapon at a target 
  //-----------------------------------------------------------------------------
  void CPropAPC::AimSecondaryWeaponAt( CBaseEntity *pTarget )
  {
  	m_hRocketTarget = pTarget;
  
  	// Update the rocket target
  	//CreateAPCLaserDot();
  
  	if ( m_hRocketTarget )
  	{
  		m_hLaserDot->SetAbsOrigin( m_hRocketTarget->BodyTarget( WorldSpaceCenter(), false ) );
  	}
  	//SetLaserDotTarget( m_hLaserDot, m_hRocketTarget );
  	//EnableLaserDot( m_hLaserDot, m_hRocketTarget != NULL );
  }
  
  	
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CPropAPC::DriveVehicle( float flFrameTime, CUserCmd *ucmd, int iButtonsDown, int iButtonsReleased )
  {
  	switch( m_lifeState )
  	{
  	case LIFE_ALIVE:
  		{
  			int iButtons = ucmd->buttons;
  			if ( iButtons & IN_ATTACK )
  			{
  				FireMachineGun();
  			}
  			else if ( iButtons & IN_ATTACK2 )
  			{
  				FireRocket();
  			}
  		}
  		break;
  
  	case LIFE_DYING:
  		FireDying( );
  		break;
  
  	case LIFE_DEAD:
  		return;
  	}
  
  	BaseClass::DriveVehicle( flFrameTime, ucmd, iButtonsDown, iButtonsReleased );
  }
  
  void CPropAPC::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  {
  	BaseClass::Use( pActivator, pCaller, useType, value );
  
  	if ( pActivator->IsPlayer() )
  	{
  		 EmitSound ( "combine.door_lock" );
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Primary gun 
  //-----------------------------------------------------------------------------
  void CPropAPC::AimPrimaryWeapon( const Vector &vecWorldTarget ) 
  {
  	EntityMatrix parentMatrix;
  	parentMatrix.InitFromEntity( this, m_nMachineGunBaseAttachment );
  	Vector target = parentMatrix.WorldToLocal( vecWorldTarget ); 
  
  	float quadTarget = target.LengthSqr();
  	float quadTargetXY = target.x*target.x + target.y*target.y;
  
  	// Target is too close!  Can't aim at it
  	if ( quadTarget > m_vecBarrelPos.LengthSqr() )
  	{
  		// We're trying to aim the offset barrel at an arbitrary point.
  		// To calculate this, I think of the target as being on a sphere with 
  		// it's center at the origin of the gun.
  		// The rotation we need is the opposite of the rotation that moves the target 
  		// along the surface of that sphere to intersect with the gun's shooting direction
  		// To calculate that rotation, we simply calculate the intersection of the ray 
  		// coming out of the barrel with the target sphere (that's the new target position)
  		// and use atan2() to get angles
  
  		// angles from target pos to center
  		float targetToCenterYaw = atan2( target.y, target.x );
  		float centerToGunYaw = atan2( m_vecBarrelPos.y, sqrt( quadTarget - (m_vecBarrelPos.y*m_vecBarrelPos.y) ) );
  
  		float targetToCenterPitch = atan2( target.z, sqrt( quadTargetXY ) );
  		float centerToGunPitch = atan2( -m_vecBarrelPos.z, sqrt( quadTarget - (m_vecBarrelPos.z*m_vecBarrelPos.z) ) );
  
  		QAngle angles;
  		angles.Init( -RAD2DEG(targetToCenterPitch+centerToGunPitch), RAD2DEG( targetToCenterYaw + centerToGunYaw ), 0 );
  
  		SetPoseParameter( "vehicle_weapon_yaw", angles.y );
  		SetPoseParameter( "vehicle_weapon_pitch", angles.x );
  		StudioFrameAdvance();
  
  		float curPitch = GetPoseParameter( "vehicle_weapon_pitch" );
  		float curYaw = GetPoseParameter( "vehicle_weapon_yaw" );
  		m_bInFiringCone = (fabs(curPitch - angles.x) < 1e-3) && (fabs(curYaw - angles.y) < 1e-3);
  	}
  	else
  	{
  		m_bInFiringCone = false;
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  const char *CPropAPC::GetTracerType( void ) 
  {
  	return "HelicopterTracer"; 
  }
  
  
  //-----------------------------------------------------------------------------
  // Allows the shooter to change the impact effect of his bullets
  //-----------------------------------------------------------------------------
  void CPropAPC::DoImpactEffect( trace_t &tr, int nDamageType )
  {
  	UTIL_ImpactTrace( &tr, nDamageType, "HelicopterImpact" );
  } 
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CPropAPC::DoMuzzleFlash( void )
  {
  	CEffectData data;
  	data.m_nEntIndex = entindex();
  	data.m_nAttachmentIndex = m_nMachineGunMuzzleAttachment;
  	data.m_flScale = 1.0f;
  	DispatchEffect( "ChopperMuzzleFlash", data );
  
  	BaseClass::DoMuzzleFlash();
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CPropAPC::FireMachineGun( void )
  {
  	if ( m_flMachineGunTime > gpGlobals->curtime )
  		return;
  
  	// If we're still firing the salvo, fire quickly
  	m_iMachineGunBurstLeft--;
  	if ( m_iMachineGunBurstLeft > 0 )
  	{
  		m_flMachineGunTime = gpGlobals->curtime + MACHINE_GUN_BURST_TIME;
  	}
  	else
  	{
  		// Reload the salvo
  		m_iMachineGunBurstLeft = MACHINE_GUN_BURST_SIZE;
  		m_flMachineGunTime = gpGlobals->curtime + MACHINE_GUN_BURST_PAUSE_TIME;
  	}
  
  	Vector vecMachineGunShootPos;
  	Vector vecMachineGunDir;
  	GetAttachment( m_nMachineGunMuzzleAttachment, vecMachineGunShootPos, &vecMachineGunDir );
  	
  	// Fire the round
  	int	bulletType = GetAmmoDef()->Index("AR2");
  	FireBullets( 1, vecMachineGunShootPos, vecMachineGunDir, VECTOR_CONE_8DEGREES, MAX_TRACE_LENGTH, bulletType, 1 );
  	DoMuzzleFlash();
  
  	EmitSound( "Weapon_AR2.Single" );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CPropAPC::GetRocketShootPosition( Vector *pPosition )
  {
  	GetAttachment( m_nRocketAttachment, *pPosition );
  }
  
  
  //-----------------------------------------------------------------------------
  // Create a corpse 
  //-----------------------------------------------------------------------------
  void CPropAPC::CreateCorpse( )
  {
  	m_lifeState = LIFE_DEAD;
  
  	for ( int i = 0; i < APC_MAX_GIBS; ++i )
  	{
  		CPhysicsProp *pGib = assert_cast<CPhysicsProp*>(CreateEntityByName( "prop_physics" ));
  		pGib->SetAbsOrigin( GetAbsOrigin() );
  		pGib->SetAbsAngles( GetAbsAngles() );
  		pGib->SetAbsVelocity( GetAbsVelocity() );
  		pGib->SetModel( s_pGibModelName[i] );
  		pGib->Spawn();
  		pGib->SetMoveType( MOVETYPE_VPHYSICS );
  
  		float flMass = pGib->GetMass();
  		if ( flMass < 200 )
  		{
  			Vector vecVelocity;
  			pGib->GetMassCenter( &vecVelocity );
  			vecVelocity -= WorldSpaceCenter();
  			vecVelocity.z = fabs(vecVelocity.z);
  			VectorNormalize( vecVelocity );
  
  			// Apply a force that would make a 100kg mass travel 150 - 300 m/s
  			float flRandomVel = random->RandomFloat( 150, 300 );
  			vecVelocity *= (100 * flRandomVel) / flMass;
  			vecVelocity.z += 100.0f;
  			AngularImpulse angImpulse = RandomAngularImpulse( -500, 500 );
  			
  			IPhysicsObject *pObj = pGib->VPhysicsGetObject();
  			if ( pObj != NULL )
  			{
  				pObj->AddVelocity( &vecVelocity, &angImpulse );
  			}
  			pGib->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
  		}	
  		if( hl2_episodic.GetBool() )
  		{
  			// EP1 perf hit
  			pGib->Ignite( 6, false );
  		}
  		else
  		{
  			pGib->Ignite( 60, false );
  		}
  	}
  
  	AddSolidFlags( FSOLID_NOT_SOLID );
  	AddEffects( EF_NODRAW );
  	UTIL_Remove( this );
  }
  
  
  //-----------------------------------------------------------------------------
  // Death volley 
  //-----------------------------------------------------------------------------
  void CPropAPC::FireDying( )
  {
  	if ( m_flRocketTime > gpGlobals->curtime )
  		return;
  
  	Vector vecRocketOrigin;
  	GetRocketShootPosition(	&vecRocketOrigin );
  
  	Vector vecDir;
  	vecDir.Random( -1.0f, 1.0f );
  	if ( vecDir.z < 0.0f )
  	{
  		vecDir.z *= -1.0f;
  	}
  
  	VectorNormalize( vecDir );
  
  	Vector vecVelocity;
  	VectorMultiply( vecDir, ROCKET_SPEED * random->RandomFloat( 0.75f, 1.25f ), vecVelocity );
  
  	QAngle angles;
  	VectorAngles( vecDir, angles );
  
  	//CAPCMissile *pRocket = (CAPCMissile *) CAPCMissile::Create( vecRocketOrigin, angles, vecVelocity, this );
  //	float flDeathTime = random->RandomFloat( 0.3f, 0.5f );
  	if ( random->RandomFloat( 0.0f, 1.0f ) < 0.3f )
  	{
  		//pRocket->ExplodeDelay( flDeathTime );
  	}
  	else
  	{
  		//pRocket->AugerDelay( flDeathTime );
  	}
  
  	// Make erratic firing
  	m_flRocketTime = gpGlobals->curtime + random->RandomFloat( DEATH_VOLLEY_MIN_FIRE_TIME, DEATH_VOLLEY_MAX_FIRE_TIME );
  	if ( --m_iRocketSalvoLeft <= 0 )
  	{
  		CreateCorpse();
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CPropAPC::FireRocket( void )
  {
  	if ( m_flRocketTime > gpGlobals->curtime )
  		return;
  
  	// If we're still firing the salvo, fire quickly
  	m_iRocketSalvoLeft--;
  	if ( m_iRocketSalvoLeft > 0 )
  	{
  		m_flRocketTime = gpGlobals->curtime + ROCKET_DELAY_TIME;
  	}
  	else
  	{
  		// Reload the salvo
  		m_iRocketSalvoLeft = ROCKET_SALVO_SIZE;
  		m_flRocketTime = gpGlobals->curtime + random->RandomFloat( ROCKET_MIN_BURST_PAUSE_TIME, ROCKET_MAX_BURST_PAUSE_TIME );
  	}
  
  	Vector vecRocketOrigin;
  	GetRocketShootPosition(	&vecRocketOrigin );
  
  	static float s_pSide[] = { 0.966, 0.866, 0.5, -0.5, -0.866, -0.966 };
  
  	Vector forward;
  	GetVectors( &forward, NULL, NULL );
  
  	Vector vecDir;
  	CrossProduct( Vector( 0, 0, 1 ), forward, vecDir );
  	vecDir.z = 1.0f;
  	vecDir.x *= s_pSide[m_nRocketSide];
  	vecDir.y *= s_pSide[m_nRocketSide];
  	if ( ++m_nRocketSide >= 6 )
  	{
  		m_nRocketSide = 0;
  	}
  
  	VectorNormalize( vecDir );
  
  	Vector vecVelocity;
  	VectorMultiply( vecDir, ROCKET_SPEED, vecVelocity );
  
  	QAngle angles;
  	VectorAngles( vecDir, angles );
  
  //	CAPCMissile *pRocket = (CAPCMissile *)CAPCMissile::Create( vecRocketOrigin, angles, vecVelocity, this );
  	//pRocket->IgniteDelay();
  
  	if ( m_hSpecificRocketTarget )
  	{
  		//pRocket->AimAtSpecificTarget( m_hSpecificRocketTarget );
  		m_hSpecificRocketTarget = NULL;
  	}
  	else if ( m_strMissileHint != NULL_STRING )
  	{
  		//pRocket->SetGuidanceHint( STRING( m_strMissileHint ) );
  	}
  
  	EmitSound( "PropAPC.FireRocket" );
  	m_OnFiredMissile.FireOutput( this, this );
  }
  									 
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  float CPropAPC::MaxAttackRange() const
  {
  	return ROCKET_ATTACK_RANGE_MAX;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CPropAPC::OnRestore( void )
  {
  	IServerVehicle *pServerVehicle = GetServerVehicle();
  	if ( pServerVehicle != NULL )
  	{
  		// Restore the passenger information we're holding on to
  		pServerVehicle->RestorePassengerInfo();
  	}
  }
  
  //========================================================================================================================================
  // APC FOUR WHEEL PHYSICS VEHICLE SERVER VEHICLE
  //========================================================================================================================================
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CAPCFourWheelServerVehicle::NPC_AimPrimaryWeapon( Vector vecTarget )
  {
  	CPropAPC *pAPC = ((CPropAPC*)m_pVehicle);
  	pAPC->AimPrimaryWeapon( vecTarget );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CAPCFourWheelServerVehicle::NPC_AimSecondaryWeapon( Vector vecTarget )
  {
  	// Add some random noise
  //	Vector vecOffset = vecTarget + RandomVector( -128, 128 );
  //	((CPropAPC*)m_pVehicle)->AimSecondaryWeaponAt( vecOffset );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CAPCFourWheelServerVehicle::Weapon_PrimaryRanges( float *flMinRange, float *flMaxRange )
  {
  	*flMinRange = MACHINE_GUN_ATTACK_RANGE_MIN;
  	*flMaxRange = MACHINE_GUN_ATTACK_RANGE_MAX;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CAPCFourWheelServerVehicle::Weapon_SecondaryRanges( float *flMinRange, float *flMaxRange )
  {
  	*flMinRange = ROCKET_ATTACK_RANGE_MIN;
  	*flMaxRange = ROCKET_ATTACK_RANGE_MAX;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Return the time at which this vehicle's primary weapon can fire again
  //-----------------------------------------------------------------------------
  float CAPCFourWheelServerVehicle::Weapon_PrimaryCanFireAt( void )
  {
  	return ((CPropAPC*)m_pVehicle)->PrimaryWeaponFireTime();
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Return the time at which this vehicle's secondary weapon can fire again
  //-----------------------------------------------------------------------------
  float CAPCFourWheelServerVehicle::Weapon_SecondaryCanFireAt( void )
  {
  	return ((CPropAPC*)m_pVehicle)->SecondaryWeaponFireTime();
  }
  
  