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

#include "cbase.h"
#include "npcevent.h"
#include "ai_basenpc_physicsflyer.h"
#include "weapon_physcannon.h"
#include "hl2_player.h"
#include "npc_scanner.h"
#include "IEffects.h"
#include "explode.h"
#include "ai_route.h"

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

ConVar	g_debug_basescanner( "g_debug_basescanner", "0", FCVAR_CHEAT );

BEGIN_DATADESC( CNPC_BaseScanner )
	DEFINE_EMBEDDED( m_KilledInfo ),
	DEFINE_SOUNDPATCH( m_pEngineSound ),

	DEFINE_FIELD( m_flFlyNoiseBase,			FIELD_FLOAT ),
	DEFINE_FIELD( m_flEngineStallTime,		FIELD_TIME ),
	DEFINE_FIELD( m_fNextFlySoundTime,		FIELD_TIME ),
	DEFINE_FIELD( m_nFlyMode,				FIELD_INTEGER ),

	DEFINE_FIELD( m_vecDiveBombDirection,	FIELD_VECTOR ),
	DEFINE_FIELD( m_flDiveBombRollForce,	FIELD_FLOAT ),

	// Physics Influence
	DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ),
	DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ),

	DEFINE_FIELD( m_flGoalOverrideDistance,	FIELD_FLOAT ),

	DEFINE_FIELD( m_flAttackNearDist,	FIELD_FLOAT ),
	DEFINE_FIELD( m_flAttackFarDist,	FIELD_FLOAT ),
	DEFINE_FIELD( m_flAttackRange,	FIELD_FLOAT ),

	DEFINE_FIELD( m_nPoseTail,				FIELD_INTEGER ),
	DEFINE_FIELD( m_nPoseDynamo,			FIELD_INTEGER ),
	DEFINE_FIELD( m_nPoseFlare,				FIELD_INTEGER ),
	DEFINE_FIELD( m_nPoseFaceVert,			FIELD_INTEGER ),
	DEFINE_FIELD( m_nPoseFaceHoriz,			FIELD_INTEGER ),

	// DEFINE_FIELD( m_bHasSpoken,			FIELD_BOOLEAN ),

	DEFINE_FIELD( m_pSmokeTrail,			FIELD_CLASSPTR ),

	DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDistanceOverride", InputSetDistanceOverride ),
	DEFINE_INPUTFUNC( FIELD_INTEGER, "SetFlightSpeed", InputSetFlightSpeed ),

	DEFINE_THINKFUNC( DiveBombSoundThink ),
END_DATADESC()

ConVar	sk_scanner_dmg_dive( "sk_scanner_dmg_dive","0");

//-----------------------------------------------------------------------------
// Think contexts
//-----------------------------------------------------------------------------
static const char *s_pDiveBombSoundThinkContext = "DiveBombSoundThinkContext";

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CNPC_BaseScanner::CNPC_BaseScanner()
{
#ifdef _DEBUG
	m_vCurrentBanking.Init();
#endif
	m_pEngineSound = NULL;
	m_bHasSpoken = false;

	m_flAttackNearDist = SCANNER_ATTACK_NEAR_DIST;
	m_flAttackFarDist = SCANNER_ATTACK_FAR_DIST;
	m_flAttackRange = SCANNER_ATTACK_RANGE;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::Spawn(void)
{
#ifdef _XBOX
	// Always fade the corpse
	AddSpawnFlags( SF_NPC_FADE_CORPSE );
	AddEffects( EF_NOSHADOW );
#endif // _XBOX

	SetHullType( HULL_TINY_CENTERED );
	SetHullSizeNormal();

	SetSolid( SOLID_BBOX );
	AddSolidFlags( FSOLID_NOT_STANDABLE );

	SetMoveType( MOVETYPE_VPHYSICS );

	m_bloodColor		= DONT_BLEED;
	SetViewOffset( Vector(0, 0, 10) );		// Position of the eyes relative to NPC's origin.
	m_flFieldOfView		= 0.2;
	m_NPCState			= NPC_STATE_NONE;

	SetNavType( NAV_FLY );

	AddFlag( FL_FLY );

	// This entity cannot be dissolved by the combine balls,
	// nor does it get killed by the mega physcannon.
	AddEFlags( EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL );

	m_flGoalOverrideDistance = 0.0f;

	m_nFlyMode = SCANNER_FLY_PATROL;
	AngleVectors( GetLocalAngles(), &m_vCurrentBanking );
	m_fHeadYaw = 0;
	m_pSmokeTrail = NULL;

	SetCurrentVelocity( vec3_origin );

	// Noise modifier
	Vector	bobAmount;
	bobAmount.x = random->RandomFloat( -2.0f, 2.0f );
	bobAmount.y = random->RandomFloat( -2.0f, 2.0f );
	bobAmount.z = random->RandomFloat( 2.0f, 4.0f );
	if ( random->RandomInt( 0, 1 ) )
	{
		bobAmount.z *= -1.0f;
	}
	SetNoiseMod( bobAmount );

	// set flight speed
	m_flSpeed = GetMaxSpeed();

	// --------------------------------------------

	CapabilitiesAdd( bits_CAP_MOVE_FLY | bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_SKIP_NAV_GROUND_CHECK );

	NPCInit();

	m_flFlyNoiseBase = random->RandomFloat( 0, M_PI );

	m_flNextAttack = gpGlobals->curtime;
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::UpdateEfficiency( bool bInPVS )	
{
	SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL ); 
	SetMoveEfficiency( AIME_NORMAL ); 
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
float CNPC_BaseScanner::GetAutoAimRadius()
{ 
	if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE )
	{
		return 24.0f;
	}

	return 12.0f;
}

//-----------------------------------------------------------------------------
// Purpose: Called just before we are deleted.
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::UpdateOnRemove( void )
{
	// Stop combat loops if I'm alive. If I'm dead, the die sound will already have stopped it.
	if ( IsAlive() && m_bHasSpoken )
	{
		SentenceStop();
	}

	BaseClass::UpdateOnRemove();
}

//-----------------------------------------------------------------------------
// Purpose: Gets the appropriate next schedule based on current condition
//			bits.
//-----------------------------------------------------------------------------
int CNPC_BaseScanner::SelectSchedule(void)
{
	// ----------------------------------------------------
	//  If I'm dead, go into a dive bomb
	// ----------------------------------------------------
	if ( m_iHealth <= 0 )
	{
		m_flSpeed = SCANNER_MAX_DIVE_BOMB_SPEED;
		return SCHED_SCANNER_ATTACK_DIVEBOMB;
	}

	// -------------------------------
	// If I'm in a script sequence
	// -------------------------------
	if ( m_NPCState == NPC_STATE_SCRIPT )
		return(BaseClass::SelectSchedule());

	// -------------------------------
	// Flinch
	// -------------------------------
	if ( HasCondition(COND_LIGHT_DAMAGE) || HasCondition(COND_HEAVY_DAMAGE) )
	{
		if ( IsHeldByPhyscannon( ) ) 
			return SCHED_SMALL_FLINCH;

		if ( m_NPCState == NPC_STATE_IDLE )
			return SCHED_SMALL_FLINCH;

		if ( m_NPCState == NPC_STATE_ALERT )
		{
			if ( m_iHealth < ( 3 * m_iMaxHealth / 4 ))
				return SCHED_TAKE_COVER_FROM_ORIGIN;

			if ( SelectWeightedSequence( ACT_SMALL_FLINCH ) != -1 )
				return SCHED_SMALL_FLINCH;
		}
		else
		{
			if ( random->RandomInt( 0, 10 ) < 4 )
				return SCHED_SMALL_FLINCH;
		}
	}

	// I'm being held by the physcannon... struggle!
	if ( IsHeldByPhyscannon( ) ) 
		return SCHED_SCANNER_HELD_BY_PHYSCANNON;

	// ----------------------------------------------------------
	//  If I have an enemy
	// ----------------------------------------------------------
	if ( GetEnemy() != NULL && GetEnemy()->IsAlive() )
	{
		// Patrol if the enemy has vanished
		if ( HasCondition( COND_LOST_ENEMY ) )
			return SCHED_SCANNER_PATROL;

		// Chase via route if we're directly blocked
		if ( HasCondition( COND_SCANNER_FLY_BLOCKED ) )
			return SCHED_SCANNER_CHASE_ENEMY;

		// Attack if it's time
		if ( gpGlobals->curtime >= m_flNextAttack )
		{
			if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
				return SCHED_SCANNER_ATTACK;
		}

		// Otherwise fly in low for attack
		return SCHED_SCANNER_ATTACK_HOVER;
	}

	// Default to patrolling around
	return SCHED_SCANNER_PATROL;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::OnScheduleChange( void )
{
	m_flSpeed = GetMaxSpeed();

	BaseClass::OnScheduleChange();
}

//-----------------------------------------------------------------------------
// Purpose: For innate melee attack
//-----------------------------------------------------------------------------
int CNPC_BaseScanner::MeleeAttack1Conditions( float flDot, float flDist )
{
	if (GetEnemy() == NULL)
	{
		return COND_NONE;
	}

	// Check too far to attack with 2D distance
	float vEnemyDist2D = (GetEnemy()->GetLocalOrigin() - GetLocalOrigin()).Length2D();

	if (m_flNextAttack > gpGlobals->curtime)
	{
		return COND_NONE;
	}
	else if (vEnemyDist2D > m_flAttackRange)
	{
		return COND_TOO_FAR_TO_ATTACK;
	}
	else if (flDot < 0.7)
	{
		return COND_NOT_FACING_ATTACK;
	}
	return COND_CAN_MELEE_ATTACK1;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : eOldState - 
//			eNewState - 
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::OnStateChange( NPC_STATE eOldState, NPC_STATE eNewState )
{
	if (( eNewState == NPC_STATE_ALERT ) || ( eNewState == NPC_STATE_COMBAT ))
	{
		SetPoseParameter(m_nPoseFlare, 1.0f);
	}
	else
	{
		SetPoseParameter(m_nPoseFlare, 0);
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pTask - 
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::StartTask( const Task_t *pTask )
{
	switch (pTask->iTask)
	{
	case TASK_SCANNER_SET_FLY_PATROL:
		{
			// Fly in patrol mode and clear any
			// remaining target entity
			m_nFlyMode = SCANNER_FLY_PATROL;
			TaskComplete();
			break;
		}
	case TASK_SCANNER_SET_FLY_CHASE:
		{
			m_nFlyMode = SCANNER_FLY_CHASE;
			TaskComplete();
			break;
		}
	case TASK_SCANNER_SET_FLY_ATTACK:
		{
			m_nFlyMode = SCANNER_FLY_ATTACK;
			TaskComplete();
			break;
		}

	case TASK_SCANNER_SET_FLY_DIVE:
		{
			// Pick a direction to divebomb.
			if ( GetEnemy() != NULL )
			{
				// Fly towards my enemy
				Vector vEnemyPos = GetEnemyLKP();
				m_vecDiveBombDirection = vEnemyPos - GetLocalOrigin();
			}
			else
			{
				// Pick a random forward and down direction.
				Vector forward;
				GetVectors( &forward, NULL, NULL );
				m_vecDiveBombDirection = forward + Vector( random->RandomFloat( -10, 10 ), random->RandomFloat( -10, 10 ), random->RandomFloat( -20, -10 ) );
			}
			VectorNormalize( m_vecDiveBombDirection );

			// Calculate a roll force.
			m_flDiveBombRollForce = random->RandomFloat( 20.0, 420.0 );
			if ( random->RandomInt( 0, 1 ) )
			{
				m_flDiveBombRollForce *= -1;
			}

			DiveBombSoundThink();

			m_nFlyMode = SCANNER_FLY_DIVE;
			TaskComplete();
			break;
		}

	default:
		BaseClass::StartTask(pTask);
		break;
	}
}

//------------------------------------------------------------------------------
// Purpose: Override to split in two when attacked
//------------------------------------------------------------------------------
int CNPC_BaseScanner::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
	// Start smoking when we're nearly dead
	if ( m_iHealth < ( m_iMaxHealth - ( m_iMaxHealth / 4 ) ) )
	{
		StartSmokeTrail();
	}

	return (BaseClass::OnTakeDamage_Alive( info ));
}

//------------------------------------------------------------------------------
// Purpose: Override to split in two when attacked
//------------------------------------------------------------------------------
int CNPC_BaseScanner::OnTakeDamage_Dying( const CTakeDamageInfo &info )
{
	// do the damage
	m_iHealth -= info.GetDamage();

	if ( m_iHealth < -40 )
	{
		Gib();
		return 1;
	}

	return VPhysicsTakeDamage( info );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr )
{
	if ( info.GetDamageType() & DMG_BULLET)
	{
		g_pEffects->Ricochet(ptr->endpos,ptr->plane.normal);
	}

	BaseClass::TraceAttack( info, vecDir, ptr );
}

//-----------------------------------------------------------------------------
// Take damage from being thrown by a physcannon 
//-----------------------------------------------------------------------------
#define SCANNER_SMASH_SPEED 250.0	// How fast a scanner must slam into something to take full damage
void CNPC_BaseScanner::TakeDamageFromPhyscannon( CBasePlayer *pPlayer )
{
	CTakeDamageInfo info;
	info.SetDamageType( DMG_GENERIC );
	info.SetInflictor( this );
	info.SetAttacker( pPlayer );
	info.SetDamagePosition( GetAbsOrigin() );
	info.SetDamageForce( Vector( 1.0, 1.0, 1.0 ) );

	// Convert velocity into damage.
	Vector vel;
	VPhysicsGetObject()->GetVelocity( &vel, NULL );
	float flSpeed = vel.Length();

	float flFactor = flSpeed / SCANNER_SMASH_SPEED;

	// Clamp. Don't inflict negative damage or massive damage!
	flFactor = clamp( flFactor, 0.0f, 2.0f );
	float flDamage = m_iMaxHealth * flFactor;

#if 0
	Msg("Doing %f damage for %f speed!\n", flDamage, flSpeed );
#endif

	info.SetDamage( flDamage );
	TakeDamage( info );
}


//-----------------------------------------------------------------------------
// Take damage from physics impacts
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::TakeDamageFromPhysicsImpact( int index, gamevcollisionevent_t *pEvent )
{
	CBaseEntity *pHitEntity = pEvent->pEntities[!index];

	// NOTE: Augment the normal impact energy scale here.
	float flDamageScale = PlayerHasMegaPhysCannon() ? 10.0f : 5.0f;

	// Scale by the mapmaker's energyscale
	flDamageScale *= m_impactEnergyScale;

	int damageType = 0;
	float damage = CalculateDefaultPhysicsDamage( index, pEvent, flDamageScale, true, damageType );
	if ( damage == 0 )
		return;

	Vector damagePos;
	pEvent->pInternalData->GetContactPoint( damagePos );
	Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass();
	if ( damageForce == vec3_origin )
	{
		// This can happen if this entity is motion disabled, and can't move.
		// Use the velocity of the entity that hit us instead.
		damageForce = pEvent->postVelocity[!index] * pEvent->pObjects[!index]->GetMass();
	}

	// FIXME: this doesn't pass in who is responsible if some other entity "caused" this collision
	PhysCallbackDamage( this, CTakeDamageInfo( pHitEntity, pHitEntity, damageForce, damagePos, damage, damageType ), *pEvent, index );
}

//-----------------------------------------------------------------------------
// Is the scanner being held?
//-----------------------------------------------------------------------------
bool CNPC_BaseScanner::IsHeldByPhyscannon( )
{
	return VPhysicsGetObject() && (VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD);
}

//------------------------------------------------------------------------------
// Physics impact
//------------------------------------------------------------------------------
#define SCANNER_SMASH_TIME	0.75		// How long after being thrown from a physcannon that a manhack is eligible to die from impact
void CNPC_BaseScanner::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
{
	BaseClass::VPhysicsCollision( index, pEvent );

	// Take no impact damage while being carried.
	if ( IsHeldByPhyscannon( ) )
		return;

	CBasePlayer *pPlayer = HasPhysicsAttacker( SCANNER_SMASH_TIME );
	if( pPlayer )
	{
		TakeDamageFromPhyscannon( pPlayer );
		return;
	}

	// It also can take physics damage from things thrown by the player.
	int otherIndex = !index;
	CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex];
	if ( pHitEntity )
	{
		if ( pHitEntity->HasPhysicsAttacker( 0.5f ) )
		{
			// It can take physics damage from things thrown by the player.
			TakeDamageFromPhysicsImpact( index, pEvent );
		}
		else if ( FClassnameIs( pHitEntity, "prop_combine_ball" ) )
		{
			// It also can take physics damage from a combine ball.
			TakeDamageFromPhysicsImpact( index, pEvent );
		}
	}
}

//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CNPC_BaseScanner::Gib( void )
{
	if ( IsMarkedForDeletion() )
		return;

	// Sparks
	for ( int i = 0; i < 4; i++ )
	{
		Vector sparkPos = GetAbsOrigin();
		sparkPos.x += random->RandomFloat(-12,12);
		sparkPos.y += random->RandomFloat(-12,12);
		sparkPos.z += random->RandomFloat(-12,12);
		g_pEffects->Sparks(sparkPos);
	}

	// Light
	CBroadcastRecipientFilter filter;
	te->DynamicLight( filter, 0.0, &WorldSpaceCenter(), 255, 180, 100, 0, 100, 0.1, 0 );

	// Cover the gib spawn
	ExplosionCreate( WorldSpaceCenter(), GetAbsAngles(), this, 64, 64, false );

	// Turn off any smoke trail
	if ( m_pSmokeTrail )
	{
		m_pSmokeTrail->m_ParticleLifetime = 0;
		UTIL_Remove(m_pSmokeTrail);
		m_pSmokeTrail = NULL;
	}

	// FIXME: This is because we couldn't save/load the CTakeDamageInfo.
	// because it's midnight before the teamwide playtest. Real solution
	// is to add a datadesc to CTakeDamageInfo
	if ( m_KilledInfo.GetInflictor() )
	{
		BaseClass::Event_Killed( m_KilledInfo );
	}

	UTIL_Remove(this);
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pPhysGunUser - 
//			bPunting - 
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
{
	m_hPhysicsAttacker = pPhysGunUser;
	m_flLastPhysicsInfluenceTime = gpGlobals->curtime;

	if ( reason == PUNTED_BY_CANNON )
	{
		// There's about to be a massive change in velocity. 
		// Think immediately to handle changes in m_vCurrentVelocity;
		SetNextThink( gpGlobals->curtime + 0.01f );

		m_flEngineStallTime = gpGlobals->curtime + 2.0f;
		ScannerEmitSound( "DiveBomb" );
	}
	else
	{
		SetCondition( COND_SCANNER_GRABBED_BY_PHYSCANNON );
		ClearCondition( COND_SCANNER_RELEASED_FROM_PHYSCANNON );
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pPhysGunUser - 
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
{
	m_hPhysicsAttacker = pPhysGunUser;
	m_flLastPhysicsInfluenceTime = gpGlobals->curtime;

	ClearCondition( COND_SCANNER_GRABBED_BY_PHYSCANNON );
	SetCondition( COND_SCANNER_RELEASED_FROM_PHYSCANNON );

	if ( Reason == LAUNCHED_BY_CANNON )
	{
		m_flEngineStallTime = gpGlobals->curtime + 2.0f;

		// There's about to be a massive change in velocity. 
		// Think immediately to handle changes in m_vCurrentVelocity;
		SetNextThink( gpGlobals->curtime + 0.01f );
		ScannerEmitSound( "DiveBomb" );
	}
}


//------------------------------------------------------------------------------
// Do we have a physics attacker?
//------------------------------------------------------------------------------
CBasePlayer *CNPC_BaseScanner::HasPhysicsAttacker( float dt )
{
	// If the player is holding me now, or I've been recently thrown
	// then return a pointer to that player
	if ( IsHeldByPhyscannon( ) || (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime) )
	{
		return m_hPhysicsAttacker;
	}
	return NULL;
}


//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CNPC_BaseScanner::StopLoopingSounds(void)
{
	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
	controller.SoundDestroy( m_pEngineSound );
	m_pEngineSound = NULL;

	BaseClass::StopLoopingSounds();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pInflictor - 
//			pAttacker - 
//			flDamage - 
//			bitsDamageType - 
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::Event_Killed( const CTakeDamageInfo &info )
{
	// Copy off the takedamage info that killed me, since we're not going to call
	// up into the base class's Event_Killed() until we gib. (gibbing is ultimate death)
	m_KilledInfo = info;	

	// Interrupt whatever schedule I'm on
	SetCondition(COND_SCHEDULE_DONE);

	// If I have an enemy and I'm up high, do a dive bomb (unless dissolved)
	if ( GetEnemy() != NULL && (info.GetDamageType() & DMG_DISSOLVE) == false )
	{
		Vector vecDelta = GetLocalOrigin() - GetEnemy()->GetLocalOrigin();
		if ( ( vecDelta.z > 120 ) && ( vecDelta.Length() > 360 ) )
		{	
			// If I'm divebombing, don't take any more damage. It will make Event_Killed() be called again.
			// This is especially bad if someone machineguns the divebombing scanner. 
			AttackDivebomb();
			return;
		}
	}

	Gib();
}

//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CNPC_BaseScanner::AttackDivebomb( void )
{
	ScannerEmitSound( "DiveBomb" );

	m_takedamage = DAMAGE_NO;

	StartSmokeTrail();
}

//------------------------------------------------------------------------------
// Purpose: Checks to see if we hit anything while dive bombing.
//------------------------------------------------------------------------------
void CNPC_BaseScanner::AttackDivebombCollide(float flInterval)
{
	//
	// Trace forward to see if I hit anything
	//
	Vector			checkPos = GetAbsOrigin() + (GetCurrentVelocity() * flInterval);
	trace_t			tr;
	CBaseEntity*	pHitEntity = NULL;
	AI_TraceHull( GetAbsOrigin(), checkPos, GetHullMins(), GetHullMaxs(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );

	if (tr.m_pEnt)
	{
		pHitEntity = tr.m_pEnt;

		// Did I hit an entity that isn't another scanner?
		if (pHitEntity && pHitEntity->Classify()!=CLASS_SCANNER)
		{
			if ( !pHitEntity->ClassMatches("item_battery") )
			{
				if ( !pHitEntity->IsWorld() )
				{
					CTakeDamageInfo info( this, this, sk_scanner_dmg_dive.GetFloat(), DMG_CLUB );
					CalculateMeleeDamageForce( &info, (tr.endpos - tr.startpos), tr.endpos );
					pHitEntity->TakeDamage( info );
				}
				Gib();
			}
		}
	}

	if (tr.fraction != 1.0)
	{
		// We've hit something so deflect our velocity based on the surface
		// norm of what we've hit
		if (flInterval > 0)
		{
			float moveLen	= (1.0 - tr.fraction)*(GetAbsOrigin() - checkPos).Length();
			Vector vBounceVel	= moveLen*tr.plane.normal/flInterval;

			// If I'm right over the ground don't push down
			if (vBounceVel.z < 0)
			{
				float floorZ = GetFloorZ(GetAbsOrigin());
				if (fabs(GetAbsOrigin().z - floorZ) < 36.0f)
				{
					vBounceVel.z = 0;
				}
			}
			SetCurrentVelocity( GetCurrentVelocity() + vBounceVel );
		}
		CBaseCombatCharacter* pBCC = ToBaseCombatCharacter( pHitEntity );

		if (pBCC)
		{
			// Spawn some extra blood where we hit
			SpawnBlood(tr.endpos, g_vecAttackDir, pBCC->BloodColor(), sk_scanner_dmg_dive.GetFloat());
		}
		else
		{
			if (!(m_spawnflags	& SF_NPC_GAG))
			{
				// <<TEMP>> need better sound here...
				ScannerEmitSound( "Shoot" );
			}
			// For sparks we must trace a line in the direction of the surface norm
			// that we hit.
			checkPos = GetAbsOrigin() - (tr.plane.normal * 24);

			AI_TraceLine( GetAbsOrigin(), checkPos,MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
			if (tr.fraction != 1.0)
			{
				g_pEffects->Sparks( tr.endpos );

				CBroadcastRecipientFilter filter;
				te->DynamicLight( filter, 0.0,
					&GetAbsOrigin(), 255, 180, 100, 0, 50, 0.1, 0 );
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::PlayFlySound(void)
{
	if ( IsMarkedForDeletion() )
		return;

	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();

	//Setup the sound if we're not already
	if ( m_pEngineSound == NULL )
	{
		// Create the sound
		CPASAttenuationFilter filter( this );

		m_pEngineSound = controller.SoundCreate( filter, entindex(), CHAN_STATIC, GetEngineSound(), ATTN_NORM );

		Assert(m_pEngineSound);

		// Start the engine sound
		controller.Play( m_pEngineSound, 0.0f, 100.0f );
		controller.SoundChangeVolume( m_pEngineSound, 1.0f, 2.0f );
	}

	float	speed	 = GetCurrentVelocity().Length();
	float	flVolume = 0.25f + (0.75f*(speed/GetMaxSpeed()));
	int	iPitch = (int)min( 255, 80 + (20*(speed/GetMaxSpeed())) );

	//Update our pitch and volume based on our speed
	controller.SoundChangePitch( m_pEngineSound, iPitch, 0.1f );
	controller.SoundChangeVolume( m_pEngineSound, flVolume, 0.1f );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::ScannerEmitSound( const char *pszSoundName )
{
	CFmtStr snd;
	snd.sprintf("%s.%s", GetScannerSoundPrefix(), pszSoundName );

	m_bHasSpoken = true;

	EmitSound( snd.Access() );
}

//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CNPC_BaseScanner::SpeakSentence( int sentenceType )
{
	if (sentenceType == SCANNER_SENTENCE_ATTENTION)
	{
		ScannerEmitSound( "Attention" );
	}
	else if (sentenceType == SCANNER_SENTENCE_HANDSUP)
	{
		ScannerEmitSound( "Scan" );
	}
	else if (sentenceType == SCANNER_SENTENCE_PROCEED)
	{
		ScannerEmitSound( "Proceed" );
	}
	else if (sentenceType == SCANNER_SENTENCE_CURIOUS)
	{
		ScannerEmitSound( "Curious" );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::InputSetFlightSpeed(inputdata_t &inputdata)
{
	//FIXME: Currently unsupported

	/*
	m_flFlightSpeed = inputdata.value.Int();
	m_bFlightSpeedOverridden = (m_flFlightSpeed > 0);
	*/
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::StartSmokeTrail( void )
{
	if ( m_pSmokeTrail != NULL )
		return;

	m_pSmokeTrail = SmokeTrail::CreateSmokeTrail();

	if ( m_pSmokeTrail )
	{
		m_pSmokeTrail->m_SpawnRate = 10;
		m_pSmokeTrail->m_ParticleLifetime = 1;
		m_pSmokeTrail->m_StartSize		= 8;
		m_pSmokeTrail->m_EndSize		= 50;
		m_pSmokeTrail->m_SpawnRadius	= 10;
		m_pSmokeTrail->m_MinSpeed		= 15;
		m_pSmokeTrail->m_MaxSpeed		= 25;

		m_pSmokeTrail->m_StartColor.Init( 0.5f, 0.5f, 0.5f );
		m_pSmokeTrail->m_EndColor.Init( 0, 0, 0 );
		m_pSmokeTrail->SetLifetime( 500.0f );
		m_pSmokeTrail->FollowEntity( this );
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::BlendPhyscannonLaunchSpeed()
{
	// Blend out desired velocity when launched by the physcannon
	if (!VPhysicsGetObject())
		return;

	if ( HasPhysicsAttacker( SCANNER_SMASH_TIME ) && !IsHeldByPhyscannon( ) )
	{
		Vector vecCurrentVelocity;
		VPhysicsGetObject()->GetVelocity( &vecCurrentVelocity, NULL );
		float flLerpFactor = (gpGlobals->curtime - m_flLastPhysicsInfluenceTime) / SCANNER_SMASH_TIME;
		flLerpFactor = clamp( flLerpFactor, 0.0f, 1.0f );
		flLerpFactor = SimpleSplineRemapVal( flLerpFactor, 0.0f, 1.0f, 0.0f, 1.0f );
		flLerpFactor *= flLerpFactor;
		VectorLerp( vecCurrentVelocity, m_vCurrentVelocity, flLerpFactor, m_vCurrentVelocity );
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::MoveExecute_Alive(float flInterval)
{
	// Amount of noise to add to flying
	float noiseScale = 3.0f;

	// -------------------------------------------
	//  Avoid obstacles, unless I'm dive bombing
	// -------------------------------------------
	if (m_nFlyMode != SCANNER_FLY_DIVE)
	{
		SetCurrentVelocity( GetCurrentVelocity() + VelocityToAvoidObstacles(flInterval) );
	}
	// If I am dive bombing add more noise to my flying
	else
	{
		AttackDivebombCollide(flInterval);
		noiseScale *= 4;
	}

	IPhysicsObject *pPhysics = VPhysicsGetObject();

	if ( pPhysics && pPhysics->IsAsleep() )
	{
		pPhysics->Wake();
	}

	// Add time-coherent noise to the current velocity so that it never looks bolted in place.
	AddNoiseToVelocity( noiseScale );

	AdjustScannerVelocity();

	float maxSpeed = GetEnemy() ? ( GetMaxSpeed() * 2.0f ) : GetMaxSpeed();
	if ( m_nFlyMode == SCANNER_FLY_DIVE )
	{
		maxSpeed = -1;
	}

	// Limit fall speed
	LimitSpeed( maxSpeed );

	// Blend out desired velocity when launched by the physcannon
	BlendPhyscannonLaunchSpeed();

	// Update what we're looking at
	UpdateHead( flInterval );

	// Control the tail based on our vertical travel
	float tailPerc = clamp( GetCurrentVelocity().z, -150, 250 );
	tailPerc = SimpleSplineRemapVal( tailPerc, -150, 250, -25, 80 );

	SetPoseParameter( m_nPoseTail, tailPerc );

	// Spin the dynamo based upon our speed
	float flCurrentDynamo = GetPoseParameter( m_nPoseDynamo );
	float speed	= GetCurrentVelocity().Length();
	float flDynamoSpeed = (maxSpeed > 0 ? speed / maxSpeed : 1.0) * 60;
	flCurrentDynamo -= flDynamoSpeed;
	if ( flCurrentDynamo < -180.0 )
	{
		flCurrentDynamo += 360.0;
	}
	SetPoseParameter( m_nPoseDynamo, flCurrentDynamo );

	PlayFlySound();
}

//-----------------------------------------------------------------------------
// Purpose: Handles movement towards the last move target.
// Input  : flInterval - 
//-----------------------------------------------------------------------------
bool CNPC_BaseScanner::OverridePathMove( CBaseEntity *pMoveTarget, float flInterval )
{
	// Save our last patrolling direction
	Vector lastPatrolDir = GetNavigator()->GetCurWaypointPos() - GetAbsOrigin();

	// Continue on our path
	if ( ProgressFlyPath( flInterval, pMoveTarget, (MASK_NPCSOLID|CONTENTS_WATER), false, 64 ) == AINPP_COMPLETE )
	{
		if ( IsCurSchedule( SCHED_SCANNER_PATROL ) )
		{
			m_vLastPatrolDir = lastPatrolDir;
			VectorNormalize(m_vLastPatrolDir);
		}

		return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : flInterval - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_BaseScanner::OverrideMove( float flInterval )
{
	// ----------------------------------------------
	//	If dive bombing
	// ----------------------------------------------
	if (m_nFlyMode == SCANNER_FLY_DIVE)
	{
		MoveToDivebomb( flInterval );
	}
	else
	{
		Vector vMoveTargetPos(0,0,0);
		CBaseEntity *pMoveTarget = NULL;

		if ( !GetNavigator()->IsGoalActive() || ( GetNavigator()->GetCurWaypointFlags() | bits_WP_TO_PATHCORNER ) )
		{
			// Select move target 
			if ( GetTarget() != NULL )
			{
				pMoveTarget = GetTarget();
			}
			else if ( GetEnemy() != NULL )
			{
				pMoveTarget = GetEnemy();
			}

			// Select move target position 
			if ( GetEnemy() != NULL )
			{
				vMoveTargetPos = GetEnemy()->GetAbsOrigin();
			}
		}
		else
		{
			vMoveTargetPos = GetNavigator()->GetCurWaypointPos();
		}

		ClearCondition( COND_SCANNER_FLY_CLEAR );
		ClearCondition( COND_SCANNER_FLY_BLOCKED );

		// See if we can fly there directly
		if ( pMoveTarget )
		{
			trace_t tr;
			AI_TraceHull( GetAbsOrigin(), vMoveTargetPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );

			float fTargetDist = (1.0f-tr.fraction)*(GetAbsOrigin() - vMoveTargetPos).Length();

			if ( ( tr.m_pEnt == pMoveTarget ) || ( fTargetDist < 50 ) )
			{
				if ( g_debug_basescanner.GetBool() )
				{
					NDebugOverlay::Line(GetLocalOrigin(), vMoveTargetPos, 0,255,0, true, 0);
					NDebugOverlay::Cross3D(tr.endpos,Vector(-5,-5,-5),Vector(5,5,5),0,255,0,true,0.1);
				}

				SetCondition( COND_SCANNER_FLY_CLEAR );
			}
			else		
			{
				//HANDY DEBUG TOOL	
				if ( g_debug_basescanner.GetBool() )
				{
					NDebugOverlay::Line(GetLocalOrigin(), vMoveTargetPos, 255,0,0, true, 0);
					NDebugOverlay::Cross3D(tr.endpos,Vector(-5,-5,-5),Vector(5,5,5),255,0,0,true,0.1);
				}

				SetCondition( COND_SCANNER_FLY_BLOCKED );
			}
		}

		// If I have a route, keep it updated and move toward target
		if ( GetNavigator()->IsGoalActive() )
		{
			if ( OverridePathMove( pMoveTarget, flInterval ) )
			{
				BlendPhyscannonLaunchSpeed();
				return true;
			}
		}	
		// ----------------------------------------------
		//	If attacking
		// ----------------------------------------------
		else if (m_nFlyMode == SCANNER_FLY_ATTACK)
		{
			MoveToAttack( flInterval );
		}
		// -----------------------------------------------------------------
		// If I don't have a route, just decelerate
		// -----------------------------------------------------------------
		else if (!GetNavigator()->IsGoalActive())
		{
			float	myDecay	 = 9.5;
			Decelerate( flInterval, myDecay);
		}
	}

	MoveExecute_Alive( flInterval );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &goalPos - 
//			&startPos - 
//			idealRange - 
//			idealHeight - 
// Output : Vector
//-----------------------------------------------------------------------------
Vector CNPC_BaseScanner::IdealGoalForMovement( const Vector &goalPos, const Vector &startPos, float idealRange, float idealHeightDiff )
{
	Vector	vMoveDir;

	if ( GetGoalDirection( &vMoveDir ) == false )
	{
		vMoveDir = ( goalPos - startPos );
		vMoveDir.z = 0;
		VectorNormalize( vMoveDir );
	}

	// Move up from the position by the desired amount
	Vector vIdealPos = goalPos + Vector( 0, 0, idealHeightDiff ) + ( vMoveDir * -idealRange );

	// Trace down and make sure we can fit here
	trace_t	tr;
	AI_TraceHull( vIdealPos, vIdealPos - Vector( 0, 0, MinGroundDist() ), GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );

	// Move up otherwise
	if ( tr.fraction < 1.0f )
	{
		vIdealPos.z += ( MinGroundDist() * ( 1.0f - tr.fraction ) );
	}

	//FIXME: We also need to make sure that we fit here at all, and if not, chose a new spot

	// Debug tools
	if ( g_debug_basescanner.GetBool() )
	{
		NDebugOverlay::Cross3D( goalPos, -Vector(8,8,8), Vector(8,8,8), 255, 255, 0, true, 0.1f );
		NDebugOverlay::Cross3D( startPos, -Vector(8,8,8), Vector(8,8,8), 255, 0, 255, true, 0.1f );
		NDebugOverlay::Cross3D( vIdealPos, -Vector(8,8,8), Vector(8,8,8), 255, 255, 255, true, 0.1f );
		NDebugOverlay::Line( startPos, goalPos, 0, 255, 0, true, 0.1f );

		NDebugOverlay::Cross3D( goalPos + ( vMoveDir * -idealRange ), -Vector(8,8,8), Vector(8,8,8), 255, 255, 255, true, 0.1f );
		NDebugOverlay::Line( goalPos, goalPos + ( vMoveDir * -idealRange ), 255, 255, 0, true, 0.1f );
		NDebugOverlay::Line( goalPos + ( vMoveDir * -idealRange ), vIdealPos, 255, 255, 0, true, 0.1f );
	}

	return vIdealPos;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : flInterval - 
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::MoveToAttack(float flInterval)
{
	if (GetEnemy() == NULL)
		return;

	if ( flInterval <= 0 )
		return;

	Vector vTargetPos = GetEnemyLKP();

	//float flDesiredDist = m_flAttackNearDist + ( ( m_flAttackFarDist - m_flAttackNearDist ) / 2 );

	Vector idealPos = IdealGoalForMovement( vTargetPos, GetAbsOrigin(), GetGoalDistance(), m_flAttackNearDist );

	MoveToTarget( flInterval, idealPos );

	//FIXME: Re-implement?

	/*
	// ---------------------------------------------------------
	//  Add evasion if I have taken damage recently
	// ---------------------------------------------------------
	if ((m_flLastDamageTime + SCANNER_EVADE_TIME) > gpGlobals->curtime)
	{
	vFlyDirection = vFlyDirection + VelocityToEvade(GetEnemyCombatCharacterPointer());
	}
	*/
}

//-----------------------------------------------------------------------------
// Purpose: Accelerates toward a given position.
// Input  : flInterval - Time interval over which to move.
//			vecMoveTarget - Position to move toward.
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::MoveToTarget( float flInterval, const Vector &vecMoveTarget )
{
	// Don't move if stalling
	if ( m_flEngineStallTime > gpGlobals->curtime )
		return;

	// Look at our inspection target if we have one
	if ( GetEnemy() != NULL )
	{
		// Otherwise at our enemy
		TurnHeadToTarget( flInterval, GetEnemy()->EyePosition() );
	}
	else
	{
		// Otherwise face our motion direction
		TurnHeadToTarget( flInterval, vecMoveTarget );
	}

	// -------------------------------------
	// Move towards our target
	// -------------------------------------
	float myAccel;
	float myZAccel = 400.0f;
	float myDecay  = 0.15f;

	Vector vecCurrentDir;

	// Get the relationship between my current velocity and the way I want to be going.
	vecCurrentDir = GetCurrentVelocity();
	VectorNormalize( vecCurrentDir );

	Vector targetDir = vecMoveTarget - GetAbsOrigin();
	float flDist = VectorNormalize(targetDir);

	float flDot;
	flDot = DotProduct( targetDir, vecCurrentDir );

	if( flDot > 0.25 )
	{
		// If my target is in front of me, my flight model is a bit more accurate.
		myAccel = 250;
	}
	else
	{
		// Have a harder time correcting my course if I'm currently flying away from my target.
		myAccel = 128;
	}

	if ( myAccel > flDist / flInterval )
	{
		myAccel = flDist / flInterval;
	}

	if ( myZAccel > flDist / flInterval )
	{
		myZAccel = flDist / flInterval;
	}

	MoveInDirection( flInterval, targetDir, myAccel, myZAccel, myDecay );

	// calc relative banking targets
	Vector forward, right, up;
	GetVectors( &forward, &right, &up );

	m_vCurrentBanking.x	= targetDir.x;
	m_vCurrentBanking.z	= 120.0f * DotProduct( right, targetDir );
	m_vCurrentBanking.y	= 0;

	float speedPerc = SimpleSplineRemapVal( GetCurrentVelocity().Length(), 0.0f, GetMaxSpeed(), 0.0f, 1.0f );

	speedPerc = clamp( speedPerc, 0.0f, 1.0f );

	m_vCurrentBanking *= speedPerc;
}

//-----------------------------------------------------------------------------
// Danger sounds. 
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::DiveBombSoundThink()
{
	Vector vecPosition, vecVelocity;
	IPhysicsObject *pPhysicsObject = VPhysicsGetObject();

	if ( pPhysicsObject == NULL )
		return;

	pPhysicsObject->GetPosition( &vecPosition, NULL );
	pPhysicsObject->GetVelocity( &vecVelocity, NULL );

	CBasePlayer *pPlayer = AI_GetSinglePlayer();
	if ( pPlayer )
	{
		Vector vecDelta;
		VectorSubtract( pPlayer->GetAbsOrigin(), vecPosition, vecDelta );
		VectorNormalize( vecDelta );
		if ( DotProduct( vecDelta, vecVelocity ) > 0.5f )
		{
			Vector vecEndPoint;
			VectorMA( vecPosition, 2.0f * TICK_INTERVAL, vecVelocity, vecEndPoint );
			float flDist = CalcDistanceToLineSegment( pPlayer->GetAbsOrigin(), vecPosition, vecEndPoint );
			if ( flDist < 200.0f )
			{
				ScannerEmitSound( "DiveBombFlyby" );
				SetContextThink( &CNPC_BaseScanner::DiveBombSoundThink, gpGlobals->curtime + 0.5f, s_pDiveBombSoundThinkContext );
				return;
			}
		}
	}

	SetContextThink( &CNPC_BaseScanner::DiveBombSoundThink, gpGlobals->curtime + 2.0f * TICK_INTERVAL, s_pDiveBombSoundThinkContext );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : flInterval - 
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::MoveToDivebomb(float flInterval)
{
	float myAccel = 1600;
	float myDecay = 0.05f; // decay current velocity to 10% in 1 second

	// Fly towards my enemy
	Vector vEnemyPos = GetEnemyLKP();
	Vector vFlyDirection  = vEnemyPos - GetLocalOrigin();
	VectorNormalize( vFlyDirection );

	// Set net velocity 
	MoveInDirection( flInterval, m_vecDiveBombDirection, myAccel, myAccel, myDecay);

	// Spin out of control.
	Vector forward;
	VPhysicsGetObject()->LocalToWorldVector( &forward, Vector( 1.0, 0.0, 0.0 ) );
	AngularImpulse torque = forward * m_flDiveBombRollForce;
	VPhysicsGetObject()->ApplyTorqueCenter( torque );

	// BUGBUG: why Y axis and not Z?
	Vector up;
	VPhysicsGetObject()->LocalToWorldVector( &up, Vector( 0.0, 1.0, 0.0 ) );
	VPhysicsGetObject()->ApplyForceCenter( up * 2000 );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CNPC_BaseScanner::IsEnemyPlayerInSuit()
{
	if( GetEnemy() && GetEnemy()->IsPlayer() )
	{
		CHL2_Player *pPlayer = NULL;
		pPlayer = (CHL2_Player *)GetEnemy();

		if( pPlayer && pPlayer->IsSuitEquipped() )
		{
			return true;
		}
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : float
//-----------------------------------------------------------------------------
float CNPC_BaseScanner::GetGoalDistance( void )
{
	if ( m_flGoalOverrideDistance != 0.0f )
		return m_flGoalOverrideDistance;

	switch ( m_nFlyMode )
	{
	case SCANNER_FLY_ATTACK:
		{
			float goalDist = ( m_flAttackNearDist + ( ( m_flAttackFarDist - m_flAttackNearDist ) / 2 ) );
			if( IsEnemyPlayerInSuit() )
			{
				goalDist *= 0.5;
			}
			return goalDist;
		}
		break;
	default:
		break;
	}

	return 128.0f;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &vOut - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_BaseScanner::GetGoalDirection( Vector *vOut )
{
	CBaseEntity *pTarget = GetTarget();

	if ( pTarget == NULL )
		return false;

	if ( FClassnameIs( pTarget, "info_hint_air" ) || FClassnameIs( pTarget, "info_target" ) )
	{
		AngleVectors( pTarget->GetAbsAngles(), vOut );
		return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
Vector CNPC_BaseScanner::VelocityToEvade(CBaseCombatCharacter *pEnemy)
{
	if (pEnemy)
	{
		// -----------------------------------------
		//  Keep out of enemy's shooting position
		// -----------------------------------------
		Vector vEnemyFacing = pEnemy->BodyDirection2D( );
		Vector	vEnemyDir   = pEnemy->EyePosition() - GetLocalOrigin();
		VectorNormalize(vEnemyDir);
		float  fDotPr		= DotProduct(vEnemyFacing,vEnemyDir);

		if (fDotPr < -0.9)
		{
			Vector vDirUp(0,0,1);
			Vector vDir;
			CrossProduct( vEnemyFacing, vDirUp, vDir);

			Vector crossProduct;
			CrossProduct(vEnemyFacing, vEnemyDir, crossProduct);
			if (crossProduct.y < 0)
			{
				vDir = vDir * -1;
			}
			return (vDir);
		}
		else if (fDotPr < -0.85)
		{
			Vector vDirUp(0,0,1);
			Vector vDir;
			CrossProduct( vEnemyFacing, vDirUp, vDir);

			Vector crossProduct;
			CrossProduct(vEnemyFacing, vEnemyDir, crossProduct);
			if (random->RandomInt(0,1))
			{
				vDir = vDir * -1;
			}
			return (vDir);
		}
	}
	return vec3_origin;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CNPC_BaseScanner::DrawDebugTextOverlays(void)
{
	int nOffset = BaseClass::DrawDebugTextOverlays();

	if ( m_debugOverlays & OVERLAY_TEXT_BIT ) 
	{
		Vector vel;
		GetVelocity( &vel, NULL );

		char tempstr[512];
		Q_snprintf( tempstr, sizeof(tempstr), "speed (max): %.2f (%.2f)", vel.Length(), m_flSpeed );
		EntityText( nOffset, tempstr, 0 );
		nOffset++;
	}

	return nOffset;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : float
//-----------------------------------------------------------------------------
float CNPC_BaseScanner::GetHeadTurnRate( void ) 
{ 
	if ( GetEnemy() )
		return 800.0f;

	return 350.0f;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
inline CBaseEntity *CNPC_BaseScanner::EntityToWatch( void )
{
	return ( GetTarget() != NULL ) ? GetTarget() : GetEnemy();	// Okay if NULL
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : flInterval - 
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::UpdateHead( float flInterval )
{
	float yaw = GetPoseParameter( m_nPoseFaceHoriz );
	float pitch = GetPoseParameter( m_nPoseFaceVert );

	CBaseEntity *pTarget = EntityToWatch();

	Vector	vLookPos;

	if ( !HasCondition( COND_IN_PVS ) || GetAttachment( "eyes", vLookPos ) == false )
	{
		vLookPos = EyePosition();
	}

	if ( pTarget != NULL )
	{
		Vector	lookDir = pTarget->EyePosition() - vLookPos;
		VectorNormalize( lookDir );

		if ( DotProduct( lookDir, BodyDirection3D() ) < 0.0f )
		{
			SetPoseParameter( m_nPoseFaceHoriz,	UTIL_Approach( 0, yaw, 10 ) );
			SetPoseParameter( m_nPoseFaceVert, UTIL_Approach( 0, pitch, 10 ) );

			return;
		}

		float facingYaw = VecToYaw( BodyDirection3D() );
		float yawDiff = VecToYaw( lookDir );
		yawDiff = UTIL_AngleDiff( yawDiff, facingYaw + yaw );

		float facingPitch = UTIL_VecToPitch( BodyDirection3D() );
		float pitchDiff = UTIL_VecToPitch( lookDir );
		pitchDiff = UTIL_AngleDiff( pitchDiff, facingPitch + pitch );

		SetPoseParameter( m_nPoseFaceHoriz, UTIL_Approach( yaw + yawDiff, yaw, 50 ) );
		SetPoseParameter( m_nPoseFaceVert, UTIL_Approach( pitch + pitchDiff, pitch, 50 ) );
	}
	else
	{
		SetPoseParameter( m_nPoseFaceHoriz,	UTIL_Approach( 0, yaw, 10 ) );
		SetPoseParameter( m_nPoseFaceVert, UTIL_Approach( 0, pitch, 10 ) );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &linear - 
//			&angular - 
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::ClampMotorForces( Vector &linear, AngularImpulse &angular )
{ 
	// limit reaction forces
	if ( m_nFlyMode != SCANNER_FLY_DIVE )
	{
		linear.x = clamp( linear.x, -500, 500 );
		linear.y = clamp( linear.y, -500, 500 );
		linear.z = clamp( linear.z, -500, 500 );
	}

	// If we're dive bombing, we need to drop faster than normal
	if ( m_nFlyMode != SCANNER_FLY_DIVE )
	{
		// Add in weightlessness
		linear.z += 800;
	}

	angular.z = clamp( angular.z, -GetHeadTurnRate(), GetHeadTurnRate() );
	if ( m_nFlyMode == SCANNER_FLY_DIVE )
	{
		// Disable pitch and roll motors while crashing.
		angular.x = 0;
		angular.y = 0;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::InputSetDistanceOverride( inputdata_t &inputdata )
{
	m_flGoalOverrideDistance = inputdata.value.Float();
}

//-----------------------------------------------------------------------------
// Purpose: Emit sounds specific to the NPC's state.
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::AlertSound(void)
{
	ScannerEmitSound( "Alert" );
}

//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CNPC_BaseScanner::DeathSound( const CTakeDamageInfo &info )
{
	ScannerEmitSound( "Die" );
}

//-----------------------------------------------------------------------------
// Purpose: Overridden so that scanners play battle sounds while fighting.
// Output : Returns TRUE on success, FALSE on failure.
//-----------------------------------------------------------------------------
bool CNPC_BaseScanner::ShouldPlayIdleSound( void )
{
	if ( HasSpawnFlags( SF_NPC_GAG ) )
		return false;

	if ( random->RandomInt( 0, 25 ) != 0 )
		return false;

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: Plays sounds while idle or in combat.
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::IdleSound(void)
{
	if ( m_NPCState == NPC_STATE_COMBAT )
	{
		// dvs: the combat sounds should be related to what is happening, rather than random
		ScannerEmitSound( "Combat" );
	}
	else
	{
		ScannerEmitSound( "Idle" );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Plays a sound when hurt.
//-----------------------------------------------------------------------------
void CNPC_BaseScanner::PainSound( const CTakeDamageInfo &info )
{
	ScannerEmitSound( "Pain" );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float CNPC_BaseScanner::GetMaxSpeed()
{
	return SCANNER_MAX_SPEED;
}

//-----------------------------------------------------------------------------
//
// Schedules
//
//-----------------------------------------------------------------------------

AI_BEGIN_CUSTOM_NPC( npc_basescanner, CNPC_BaseScanner )

	DECLARE_TASK( TASK_SCANNER_SET_FLY_PATROL )
	DECLARE_TASK( TASK_SCANNER_SET_FLY_CHASE )
	DECLARE_TASK( TASK_SCANNER_SET_FLY_ATTACK )
	DECLARE_TASK( TASK_SCANNER_SET_FLY_DIVE )

	DECLARE_CONDITION(COND_SCANNER_FLY_CLEAR)
	DECLARE_CONDITION(COND_SCANNER_FLY_BLOCKED)
	DECLARE_CONDITION(COND_SCANNER_RELEASED_FROM_PHYSCANNON)
	DECLARE_CONDITION(COND_SCANNER_GRABBED_BY_PHYSCANNON)

	//=========================================================
	// > SCHED_SCANNER_PATROL
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_SCANNER_PATROL,

		"	Tasks"
		"		TASK_SCANNER_SET_FLY_PATROL			0"
		"		TASK_SET_TOLERANCE_DISTANCE			32"
		"		TASK_SET_ROUTE_SEARCH_TIME			5"	// Spend 5 seconds trying to build a path if stuck
		"		TASK_GET_PATH_TO_RANDOM_NODE		2000"
		"		TASK_RUN_PATH						0"
		"		TASK_WAIT_FOR_MOVEMENT				0"
		""
		"	Interrupts"
		"		COND_GIVE_WAY"
		"		COND_NEW_ENEMY"
		"		COND_SEE_ENEMY"
		"		COND_SEE_FEAR"
		"		COND_HEAR_COMBAT"
		"		COND_HEAR_DANGER"
		"		COND_HEAR_PLAYER"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
		"		COND_PROVOKED"
		"		COND_SCANNER_GRABBED_BY_PHYSCANNON"
	)

	//=========================================================
	// > SCHED_SCANNER_ATTACK
	//
	//	This task does nothing. Translate it in your derived
	//	class to perform your attack.
	//
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_SCANNER_ATTACK,

		"	Tasks"
		"		TASK_SCANNER_SET_FLY_ATTACK			0"
		"		TASK_SET_ACTIVITY					ACTIVITY:ACT_IDLE"
		"		TASK_WAIT							0.1"
		""
		"	Interrupts"
		"		COND_TOO_FAR_TO_ATTACK"
		"		COND_SCANNER_FLY_BLOCKED"
		"		COND_NEW_ENEMY"
		"		COND_SCANNER_GRABBED_BY_PHYSCANNON"
	)

	//=========================================================
	// > SCHED_SCANNER_ATTACK_HOVER
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_SCANNER_ATTACK_HOVER,

		"	Tasks"
		"		TASK_SCANNER_SET_FLY_ATTACK			0"
		"		TASK_SET_ACTIVITY					ACTIVITY:ACT_IDLE"
		"		TASK_WAIT							0.1"
		""
		"	Interrupts"
		"		COND_TOO_FAR_TO_ATTACK"
		"		COND_SCANNER_FLY_BLOCKED"
		"		COND_NEW_ENEMY"
		"		COND_SCANNER_GRABBED_BY_PHYSCANNON"
	)

	//=========================================================
	// > SCHED_SCANNER_ATTACK_DIVEBOMB
	//
	// Only done when scanner is dead
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_SCANNER_ATTACK_DIVEBOMB,

		"	Tasks"
		"		TASK_SCANNER_SET_FLY_DIVE			0"
		"		TASK_SET_ACTIVITY					ACTIVITY:ACT_IDLE"
		"		TASK_WAIT							10"
		""
		"	Interrupts"
		"		COND_SCANNER_GRABBED_BY_PHYSCANNON"
	)

	//=========================================================
	// > SCHED_SCANNER_CHASE_ENEMY
	//
	//  Different interrupts than normal chase enemy.  
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_SCANNER_CHASE_ENEMY,

		"	Tasks"
		"		 TASK_SCANNER_SET_FLY_CHASE			0"
		"		 TASK_SET_FAIL_SCHEDULE				SCHEDULE:SCHED_SCANNER_PATROL"
		"		 TASK_SET_TOLERANCE_DISTANCE		120"
		"		 TASK_GET_PATH_TO_ENEMY				0"
		"		 TASK_RUN_PATH						0"
		"		 TASK_WAIT_FOR_MOVEMENT				0"
		""
		""
		"	Interrupts"
		"		COND_SCANNER_FLY_CLEAR"
		"		COND_NEW_ENEMY"
		"		COND_ENEMY_DEAD"
		"		COND_LOST_ENEMY"
		"		COND_SCANNER_GRABBED_BY_PHYSCANNON"
	)

	//=========================================================
	// > SCHED_SCANNER_CHASE_TARGET
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_SCANNER_CHASE_TARGET,

		"	Tasks"
		"		 TASK_SCANNER_SET_FLY_CHASE			0"
		"		 TASK_SET_TOLERANCE_DISTANCE		64"
		"		 TASK_GET_PATH_TO_TARGET			0"	//FIXME: This is wrong!
		"		 TASK_RUN_PATH						0"
		"		 TASK_WAIT_FOR_MOVEMENT				0"
		""
		"	Interrupts"
		"		COND_SCANNER_FLY_CLEAR"
		"		COND_NEW_ENEMY"
		"		COND_SCANNER_GRABBED_BY_PHYSCANNON"
	)

	//=========================================================
	// > SCHED_SCANNER_FOLLOW_HOVER
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_SCANNER_FOLLOW_HOVER,

		"	Tasks"
		"		TASK_SET_ACTIVITY					ACTIVITY:ACT_IDLE"
		"		TASK_WAIT							0.1"
		""
		"	Interrupts"
		"		COND_SCANNER_FLY_BLOCKED"
		"		COND_SCANNER_GRABBED_BY_PHYSCANNON"
	)

	//=========================================================
	// > SCHED_SCANNER_HELD_BY_PHYSCANNON
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_SCANNER_HELD_BY_PHYSCANNON,

		"	Tasks"
		"		TASK_SET_ACTIVITY					ACTIVITY:ACT_IDLE"
		"		TASK_WAIT							5.0"
		""
		"	Interrupts"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
		"		COND_SCANNER_RELEASED_FROM_PHYSCANNON"
	)
	
AI_END_CUSTOM_NPC()
