text: //========= Copyright Valve Corporation, All rights reserved. ============//
  //
  // Purpose: 
  //
  // $NoKeywords: $
  //=============================================================================//
  #include "cbase.h"
  #include "ai_network.h"
  #include "ai_default.h"
  #include "ai_schedule.h"
  #include "ai_hull.h"
  #include "ai_node.h"
  #include "ai_task.h"
  #include "ai_senses.h"
  #include "ai_navigator.h"
  #include "ai_route.h"
  #include "entitylist.h"
  #include "soundenvelope.h"
  #include "gamerules.h"
  #include "ndebugoverlay.h"
  #include "soundflags.h"
  #include "trains.h"
  #include "globalstate.h"
  #include "vehicle_base.h"
  #include "npc_vehicledriver.h"
  
  // memdbgon must be the last include file in a .cpp file!!!
  #include "tier0/memdbgon.h"
  
  #define	DRIVER_DEBUG_PATH				1
  #define	DRIVER_DEBUG_PATH_SPLINE		2
  
  //------------------------------------
  // 
  //------------------------------------
  ConVar g_debug_vehicledriver( "g_debug_vehicledriver", "0", FCVAR_CHEAT );
  
  BEGIN_DATADESC( CNPC_VehicleDriver )
  	DEFINE_KEYFIELD( m_iszVehicleName, FIELD_STRING, "vehicle" ),
  //	DEFINE_FIELD( m_hVehicle,			FIELD_EHANDLE ),
  	// DEFINE_FIELD( m_pVehicleInterface, FIELD_POINTER ),
  	DEFINE_FIELD( m_hVehicleEntity,		FIELD_EHANDLE ),
  	// DEFINE_FIELD( m_Waypoints,		FIELD_???? ),
  	// DEFINE_FIELD( m_pCurrentWaypoint, FIELD_POINTER ),
  	// DEFINE_FIELD( m_pNextWaypoint,	FIELD_POINTER ),
  	DEFINE_FIELD( m_vecDesiredVelocity, FIELD_VECTOR ),
  	DEFINE_FIELD( m_vecDesiredPosition, FIELD_POSITION_VECTOR ),
  	DEFINE_FIELD( m_vecPrevPoint,		FIELD_POSITION_VECTOR ),
  	DEFINE_FIELD( m_vecPrevPrevPoint,	FIELD_POSITION_VECTOR ),
  	DEFINE_FIELD( m_vecPostPoint,		FIELD_POSITION_VECTOR ),
  	DEFINE_FIELD( m_vecPostPostPoint,	FIELD_POSITION_VECTOR ),
  	DEFINE_FIELD( m_flDistanceAlongSpline, FIELD_FLOAT ),
  	DEFINE_KEYFIELD( m_flDriversMaxSpeed, FIELD_FLOAT, "drivermaxspeed" ),
  	DEFINE_KEYFIELD( m_flDriversMinSpeed, FIELD_FLOAT, "driverminspeed" ),
  	DEFINE_FIELD( m_flMaxSpeed,			FIELD_FLOAT ),
  	DEFINE_FIELD( m_flGoalSpeed,		FIELD_FLOAT ),
  	//DEFINE_KEYFIELD( m_flInitialSpeed,	FIELD_FLOAT ),
  	DEFINE_FIELD( m_flSteering,			FIELD_FLOAT ),
  
  	// Inputs
  	DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDriversMaxSpeed", InputSetDriversMaxSpeed ),
  	DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDriversMinSpeed", InputSetDriversMinSpeed ),
  
  	DEFINE_INPUTFUNC( FIELD_VOID, "StartForward", InputStartForward ),
  	DEFINE_INPUTFUNC( FIELD_VOID, "Stop", InputStop ),
  	DEFINE_INPUTFUNC( FIELD_VOID, "StartFiring", InputStartFiring ),
  	DEFINE_INPUTFUNC( FIELD_VOID, "StopFiring", InputStopFiring ),
  	DEFINE_INPUTFUNC( FIELD_STRING, "GotoPathCorner", InputGotoPathCorner ),
  
  END_DATADESC()
  
  LINK_ENTITY_TO_CLASS( npc_vehicledriver, CNPC_VehicleDriver );
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  CNPC_VehicleDriver::CNPC_VehicleDriver( void )
  {
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  CNPC_VehicleDriver::~CNPC_VehicleDriver( void )
  {
  	ClearWaypoints();
  }
  
  //------------------------------------------------------------------------------
  // Purpose :
  //------------------------------------------------------------------------------
  void CNPC_VehicleDriver::Spawn( void )
  {
  	Precache( );
  
  	BaseClass::Spawn();
  
  	CapabilitiesClear();
  	CapabilitiesAdd( bits_CAP_MOVE_GROUND );
  	CapabilitiesAdd( bits_CAP_MOVE_SHOOT );
  
  	SetModel( "models/roller_vehicledriver.mdl" );
  	SetHullType(HULL_LARGE);
  	SetHullSizeNormal();
  	m_iMaxHealth = m_iHealth = 1;
  	m_flFieldOfView	= VIEW_FIELD_FULL;
  
  	SetSolid( SOLID_BBOX );
  	AddSolidFlags( FSOLID_NOT_SOLID );
  	SetMoveType( MOVETYPE_NONE );
  	AddEffects( EF_NODRAW );
  
  	m_lifeState	= LIFE_ALIVE;
  	SetCycle( 0 );
  	ResetSequenceInfo();
  
  	AddFlag( FL_NPC );
  
  	m_flMaxSpeed = 0;
  	m_flGoalSpeed = m_flInitialSpeed;
  
  	m_vecDesiredVelocity = vec3_origin;
  	m_vecPrevPoint = vec3_origin;
  	m_vecPrevPrevPoint = vec3_origin;
  	m_vecPostPoint = vec3_origin;
  	m_vecPostPostPoint = vec3_origin;
  	m_vecDesiredPosition = vec3_origin;
  	m_flSteering = 45;
  	m_flDistanceAlongSpline = 0.2;
  	m_pCurrentWaypoint = m_pNextWaypoint = NULL;
  
  	GetNavigator()->SetPathcornerPathfinding( false );
  
  	NPCInit();
  
  	m_takedamage = DAMAGE_NO;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_VehicleDriver::Precache( void )
  {
  	PrecacheModel( "models/roller_vehicledriver.mdl" );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_VehicleDriver::Activate( void )
  {
  	BaseClass::Activate();
  
  	// Restore doesn't need to do this
  	if ( m_hVehicleEntity )
  		return;
  
  	// Make sure we've got a vehicle
  	if ( m_iszVehicleName == NULL_STRING )
  	{
  		Warning( "npc_vehicledriver %s has no vehicle to drive.\n", STRING(GetEntityName()) );
  		UTIL_Remove( this );
  		return;
  	}
  
  	m_hVehicleEntity = (gEntList.FindEntityByName( NULL, STRING(m_iszVehicleName) ));
  	if ( !m_hVehicleEntity )
  	{
  		Warning( "npc_vehicledriver %s couldn't find his vehicle named %s.\n", STRING(GetEntityName()), STRING(m_iszVehicleName) );
  		UTIL_Remove( this );
  		return;
  	}
  
  	m_pVehicleInterface = m_hVehicleEntity->GetServerVehicle();
  	Assert( m_pVehicleInterface );
  	if ( !m_pVehicleInterface->NPC_CanDrive() )
  	{
  		Warning( "npc_vehicledriver %s doesn't know how to drive vehicle %s.\n", STRING(GetEntityName()), STRING(m_hVehicleEntity->GetEntityName()) );
  		UTIL_Remove( this );
  		return;
  	}
  
  	// We've found our vehicle. Move to it and start following it.
  	SetAbsOrigin( m_hVehicleEntity->WorldSpaceCenter() );
  	m_pVehicleInterface->NPC_SetDriver( this );
  
  	RecalculateSpeeds();
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_VehicleDriver::OnRestore( void )
  {
  	BaseClass::OnRestore();
  	if ( m_hVehicleEntity )
  	{
  		m_pVehicleInterface = m_hVehicleEntity->GetServerVehicle();
  		Assert( m_pVehicleInterface );
  	}
  }
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_VehicleDriver::UpdateOnRemove( void )
  {
  	// Leave our vehicle
  	if ( m_pVehicleInterface )
  	{
  		m_pVehicleInterface->NPC_SetDriver( NULL );
  	}
  
  	BaseClass::UpdateOnRemove();
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_VehicleDriver::PrescheduleThink( void )
  {
  	if ( !m_hVehicleEntity )
  	{
  		m_pVehicleInterface = NULL;
  		UTIL_Remove( this );
  		return;
  	}
  
  	// Keep up with my vehicle
  	SetAbsOrigin( m_hVehicleEntity->WorldSpaceCenter() );
  	SetAbsAngles( m_hVehicleEntity->GetAbsAngles() );
  
  	BaseClass::PrescheduleThink();
  
  	if ( m_NPCState == NPC_STATE_IDLE )
  	{
  		m_pVehicleInterface->NPC_Brake();
  		return;
  	}
  
  	// If we've been picked up by something (dropship probably), abort.
  	if ( m_hVehicleEntity->GetParent() )
  	{
  		SetState( NPC_STATE_IDLE );
  		ClearWaypoints();
  		SetGoalEnt( NULL );
  		return;
  	}
  
  	DriveVehicle();
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  int CNPC_VehicleDriver::SelectSchedule( void )
  {
  	// Vehicle driver hangs in the air inside the vehicle, so we never need to fall to ground
  	ClearCondition( COND_FLOATING_OFF_GROUND );
  
  	if ( HasSpawnFlags(SF_VEHICLEDRIVER_INACTIVE) )
  	{
  		SetState( NPC_STATE_IDLE );
  		return SCHED_VEHICLEDRIVER_INACTIVE;
  	}
  
  	if ( GetGoalEnt() )
  		return SCHED_VEHICLEDRIVER_DRIVE_PATH;
  
  	switch ( m_NPCState )
  	{
  	case NPC_STATE_IDLE:
  		break;
  
  	case NPC_STATE_ALERT:
  		break;
  
  	case NPC_STATE_COMBAT:
  		{
  			if ( HasCondition(COND_NEW_ENEMY) || HasCondition( COND_ENEMY_DEAD ) )
  				return BaseClass::SelectSchedule();
  
  			if ( HasCondition(COND_SEE_ENEMY) )
  			{
  				// we can see the enemy
  				if ( HasCondition(COND_CAN_RANGE_ATTACK2) )
  					return SCHED_RANGE_ATTACK2;
  				if ( HasCondition(COND_CAN_RANGE_ATTACK1) )
  					return SCHED_RANGE_ATTACK1;
  
  				// What to do here? Not necessarily easy to face enemy.
  				//if ( HasCondition(COND_NOT_FACING_ATTACK) )
  					//return SCHED_COMBAT_FACE;
  			}
  
  			// We can see him, but can't shoot him. Just wait and hope he comes closer.
  			return SCHED_VEHICLEDRIVER_COMBAT_WAIT;
  		}
  		break;
  	}
  
  	return BaseClass::SelectSchedule();
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  int	CNPC_VehicleDriver::RangeAttack1Conditions( float flDot, float flDist )
  {
  	// Vehicle not ready to fire again yet?
  	if ( m_pVehicleInterface->Weapon_PrimaryCanFireAt() > gpGlobals->curtime )
  		return 0;
  
  	// Check weapon range
  	float flMinRange, flMaxRange;
  	m_pVehicleInterface->Weapon_PrimaryRanges( &flMinRange, &flMaxRange );
  	if ( flDist < flMinRange )
  		return COND_TOO_CLOSE_TO_ATTACK;
  	if ( flDist > flMaxRange )
  		return COND_TOO_FAR_TO_ATTACK;
  
  	// Don't shoot backwards
  	Vector vecForward;
  	Vector vecToTarget = (GetEnemy()->GetAbsOrigin() - GetAbsOrigin());
  	VectorNormalize(vecToTarget);
  	m_hVehicleEntity->GetVectors( &vecForward, NULL, NULL );
  	float flForwardDot = DotProduct( vecForward, vecToTarget );
  	if ( flForwardDot < 0 && fabs(flDot) < 0.5 )
  		return COND_NOT_FACING_ATTACK;
  
  	return COND_CAN_RANGE_ATTACK1;
  }
  
  //=========================================================
  // RangeAttack2Conditions
  //=========================================================
  int CNPC_VehicleDriver::RangeAttack2Conditions( float flDot, float flDist )
  {
  	// Vehicle not ready to fire again yet?
  	if ( m_pVehicleInterface->Weapon_SecondaryCanFireAt() > gpGlobals->curtime )
  		return 0;
  
  	// Check weapon range
  	float flMinRange, flMaxRange;
  	m_pVehicleInterface->Weapon_SecondaryRanges( &flMinRange, &flMaxRange );
  	if ( flDist < flMinRange )
  		return COND_TOO_CLOSE_TO_ATTACK;
  	if ( flDist > flMaxRange )
  		return COND_TOO_FAR_TO_ATTACK;
  
  	return COND_CAN_RANGE_ATTACK2;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  int CNPC_VehicleDriver::TranslateSchedule( int scheduleType ) 
  {
  	switch ( scheduleType )
  	{
  	case SCHED_COMBAT_FACE:
  		{
  			// Vehicles can't rotate, so don't try and face
  			return TranslateSchedule( SCHED_CHASE_ENEMY );
  		}
  		break;
  
  	case SCHED_ALERT_FACE:
  		{
  			// Vehicles can't rotate, so don't try and face
  			return SCHED_ALERT_STAND;
  		}
  		break;
  
  	case SCHED_CHASE_ENEMY_FAILED:
  	case SCHED_FAIL:
  		{
  			return SCHED_FAIL;
  		}
  		break;
  	}
  
  	return BaseClass::TranslateSchedule(scheduleType);
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Input  : *pTask - 
  //-----------------------------------------------------------------------------
  void CNPC_VehicleDriver::StartTask( const Task_t *pTask )
  {
  	switch( pTask->iTask )
  	{
  	case TASK_RUN_PATH:
  	case TASK_WALK_PATH:
  		TaskComplete();
  		break;
  
  	case TASK_FACE_IDEAL:
  	case TASK_FACE_ENEMY:
  		{
  			// Vehicle ignores face commands, since it can't rotate on the spot.
  			TaskComplete();
  		}
  		break;
  
  	case TASK_VEHICLEDRIVER_GET_PATH:
  		{
  			if ( !GetGoalEnt() )
  			{
  				TaskFail( FAIL_NO_TARGET );
  				return;
  			}
  
  			CheckForTeleport();
  
  			if ( g_debug_vehicledriver.GetInt() & DRIVER_DEBUG_PATH )
  			{
  				NDebugOverlay::Box( GetGoalEnt()->GetAbsOrigin(), -Vector(50,50,50), Vector(50,50,50), 255,255,255, true, 5);
  			}
  
  	  		AI_NavGoal_t goal( GOALTYPE_PATHCORNER, GetGoalEnt()->GetLocalOrigin(), ACT_WALK, AIN_DEF_TOLERANCE, AIN_YAW_TO_DEST);
    			if ( !GetNavigator()->SetGoal( goal ) )
     			{
  				TaskFail( FAIL_NO_ROUTE );
  				return;
     			}
  
  			TaskComplete();
  		}
  		break;
  
  	case TASK_WAIT_FOR_MOVEMENT:
  		{
  			if (GetNavigator()->GetGoalType() == GOALTYPE_NONE)
  			{
  				TaskComplete();
  				GetNavigator()->StopMoving();		// Stop moving
  			}
  			else if (!GetNavigator()->IsGoalActive())
  			{
  				SetIdealActivity( GetStoppedActivity() );
  			}
  			else
  			{
  				// Check validity of goal type
  				ValidateNavGoal();
  			}
  		}
  		break;
  
  	default:
  		BaseClass::StartTask( pTask );
  		break;
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CNPC_VehicleDriver::RunTask( const Task_t *pTask )
  {
  	switch( pTask->iTask )
  	{
  	case TASK_RANGE_ATTACK1:
  		{
  			// Vehicle driver has no animations, so fire a burst at the target
  			CBaseEntity *pEnemy = GetEnemy();
  			if ( pEnemy )
  			{
  				// TODO: Get a bodytarget from the firing point of the gun in the vehicle
  				Vector vecTarget = GetEnemy()->BodyTarget( GetAbsOrigin(), false );
  				m_pVehicleInterface->NPC_AimPrimaryWeapon( vecTarget );
  				m_pVehicleInterface->NPC_PrimaryFire();
  				TaskComplete();
  			}
  			else
  			{
  				TaskFail(FAIL_NO_ENEMY);
  				return;
  			}
  		}
  		break;
  
  	case TASK_RANGE_ATTACK2:
  		{
  			// Vehicle driver has no animations, so fire a burst at the target
  			CBaseEntity *pEnemy = GetEnemy();
  			if ( pEnemy )
  			{
  				// TODO: Get a bodytarget from the firing point of the gun in the vehicle
  				Vector vecTarget = GetEnemy()->BodyTarget( GetAbsOrigin(), false );
  				m_pVehicleInterface->NPC_AimSecondaryWeapon( vecTarget );
  				m_pVehicleInterface->NPC_SecondaryFire();
  				TaskComplete();
  			}
  			else
  			{
  				TaskFail(FAIL_NO_ENEMY);
  				return;
  			}
  		}
  		break;
  
  	case TASK_WAIT_FOR_MOVEMENT:
  		{
  			BaseClass::RunTask( pTask );
  
  			if ( HasCondition(COND_SEE_ENEMY) )
  			{
  				// we can see the enemy
  				if ( HasCondition(COND_CAN_RANGE_ATTACK2) )
  				{
  					ChainRunTask( TASK_RANGE_ATTACK2, pTask->flTaskData );
  				}
  				if ( HasCondition(COND_CAN_RANGE_ATTACK1) )
  				{
  					ChainRunTask( TASK_RANGE_ATTACK1, pTask->flTaskData );
  				}
  			}
  		}
  		break;
  
  	default:
  		BaseClass::RunTask( pTask );
  		break;
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_VehicleDriver::GatherEnemyConditions( CBaseEntity *pEnemy )
  {
  	BaseClass::GatherEnemyConditions(pEnemy);
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Overridden because if the player is a criminal, we hate them.
  //-----------------------------------------------------------------------------
  Disposition_t CNPC_VehicleDriver::IRelationType(CBaseEntity *pTarget)
  {
  	// If it's the player and they are a criminal, we hate them.
  	if ( pTarget && pTarget->Classify() == CLASS_PLAYER)
  	{
  		if (GlobalEntity_GetState("gordon_precriminal") == GLOBAL_ON)
  		{
  			return(D_NU);
  		}
  	}
  #ifdef SecobMod__Enable_Fixed_Multiplayer_AI
  	else if (pTarget && pTarget->Classify() == CLASS_PLAYER_RED)
  	{
  		if (GlobalEntity_GetState("gordon_precriminal") == GLOBAL_ON)
  		{
  			return(D_NU);
  		}
  	}
  	else if (pTarget && pTarget->Classify() == CLASS_PLAYER_BLUE)
  	{
  		if (GlobalEntity_GetState("gordon_precriminal") == GLOBAL_ON)
  		{
  			return(D_NU);
  		}
  	}
  #endif
  
  	return(BaseClass::IRelationType(pTarget));
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  bool CNPC_VehicleDriver::OverrideMove( float flInterval )
  {
  	if ( !m_hVehicleEntity )
  		return true;
  
  	// If we don't have a maxspeed, we've been stopped, so abort early
  	// Or we've been picked up by something (dropship probably).
  	if ( !m_flMaxSpeed || m_hVehicleEntity->GetParent() )
  	{
  		m_pVehicleInterface->NPC_Brake();
  		return true;
  	}
  
  	// -----------------------------------------------------------------
  	// If I have a route, keep it updated and move toward target
  	// ------------------------------------------------------------------
  	if (GetNavigator()->IsGoalActive())
  	{
  		if ( OverridePathMove( flInterval ) )
  			return true;
  	}
  
  	return true;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_VehicleDriver::CalculatePostPoints( void )
  {
  	m_vecPostPoint = m_vecDesiredPosition;
  	m_vecPostPostPoint = m_vecDesiredPosition;
  
  	// If we have a waypoint beyond our current, use it instead.
  	if ( !GetNavigator()->CurWaypointIsGoal() )
  	{
  		AI_Waypoint_t *pCurWaypoint = GetNavigator()->GetPath()->GetCurWaypoint();
  		m_vecPostPoint = pCurWaypoint->GetNext()->GetPos();
  		if ( pCurWaypoint->GetNext()->GetNext() )
  		{
  			m_vecPostPostPoint = pCurWaypoint->GetNext()->GetNext()->GetPos();
  		}
  		else
  		{
  			m_vecPostPostPoint = m_vecPostPoint;
  		}
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Destroy our current waypoints
  //-----------------------------------------------------------------------------
  void CNPC_VehicleDriver::ClearWaypoints( void )
  {
  	m_vecDesiredPosition = vec3_origin;
  	if ( m_pCurrentWaypoint )
  	{
  		delete m_pCurrentWaypoint;
  		m_pCurrentWaypoint = NULL;
  	}
  	if ( m_pNextWaypoint )
  	{
  		delete m_pNextWaypoint;
  		m_pNextWaypoint = NULL;
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: We've hit a waypoint. Handle it, and return true if this is the
  //			end of the path.
  //-----------------------------------------------------------------------------
  bool CNPC_VehicleDriver::WaypointReached( void )
  {
  	// We reached our current waypoint.
  	m_vecPrevPrevPoint = m_vecPrevPoint;
  	m_vecPrevPoint = GetAbsOrigin();
  
  	// If we've got to our goal, we're done here.
  	if ( GetNavigator()->CurWaypointIsGoal() )
  	{
  		// Necessary for InPass outputs to be fired, is a no-op otherwise
  		GetNavigator()->AdvancePath();
  	
  		// Stop pathing
  		ClearWaypoints();
  		TaskComplete();
  		SetGoalEnt( NULL );
  		return true;
  	}
  
  	AI_Waypoint_t *pCurWaypoint = GetNavigator()->GetPath()->GetCurWaypoint();
  	if ( !pCurWaypoint )
  		return false;
  
  	// Check to see if the waypoint wants us to change speed
  	if ( pCurWaypoint->Flags() & bits_WP_TO_PATHCORNER )
  	{
  		CBaseEntity *pEntity = pCurWaypoint->hPathCorner;
  		if ( pEntity )
  		{
  			if ( pEntity->m_flSpeed > 0 )
  			{
  				if ( pEntity->m_flSpeed <= 1.0 )
  				{
  					m_flDriversMaxSpeed = pEntity->m_flSpeed;
  					RecalculateSpeeds();
  				}
  				else
  				{
  					Warning("path_track %s tried to tell the npc_vehicledriver to set speed to %.3f. npc_vehicledriver only accepts values between 0 and 1.\n", STRING(pEntity->GetEntityName()), pEntity->m_flSpeed );
  				}
  			}
  		}
  	}
  
  	// Get the waypoints for the next part of the path
  	GetNavigator()->AdvancePath();
  	if ( !GetNavigator()->GetPath()->GetCurWaypoint() )
  	{
  		ClearWaypoints();
  		TaskComplete();
  		SetGoalEnt( NULL );
  		return true;
  	}
  
  	m_vecDesiredPosition = GetNavigator()->GetCurWaypointPos();	
  	CalculatePostPoints();
  
  	// Move to the next waypoint
  	delete m_pCurrentWaypoint;
  	m_pCurrentWaypoint = m_pNextWaypoint;
  	m_Waypoints[1] = new CVehicleWaypoint( m_vecPrevPoint, m_vecDesiredPosition, m_vecPostPoint, m_vecPostPostPoint );
  	m_pNextWaypoint = m_Waypoints[1];
  
  	// Drop the spline marker back
  	m_flDistanceAlongSpline = MAX( 0, m_flDistanceAlongSpline - 1.0 );
  
  	CheckForTeleport();
  
  	return false;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  bool CNPC_VehicleDriver::OverridePathMove( float flInterval )
  {
  	// Setup our initial path data if we've just started running a path
  	if ( !m_pCurrentWaypoint )
  	{
  		m_vecPrevPoint = GetAbsOrigin();
  		m_vecPrevPrevPoint = GetAbsOrigin();
  		m_vecDesiredPosition = GetNavigator()->GetCurWaypointPos();	
  		CalculatePostPoints();
  	
  		// Init our two waypoints
  		m_Waypoints[0] = new CVehicleWaypoint( m_vecPrevPrevPoint, m_vecPrevPoint, m_vecDesiredPosition, m_vecPostPoint );
  		m_Waypoints[1] = new CVehicleWaypoint( m_vecPrevPoint, m_vecDesiredPosition, m_vecPostPoint, m_vecPostPostPoint );
  		m_pCurrentWaypoint = m_Waypoints[0];
  		m_pNextWaypoint = m_Waypoints[1];
  
  		m_flDistanceAlongSpline = 0.2;
  	}
  
  	// Have we reached our target? See if we've passed the current waypoint's plane.
  	Vector vecAbsMins, vecAbsMaxs;
  	CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs );
  	if ( BoxOnPlaneSide( vecAbsMins, vecAbsMaxs, &m_pCurrentWaypoint->planeWaypoint ) == 3 )
  	{
  		if ( WaypointReached() )
  			return true;
  	}
  
  	// Did we bypass it and reach the next one already?
  	if ( m_pNextWaypoint && BoxOnPlaneSide( vecAbsMins, vecAbsMaxs, &m_pNextWaypoint->planeWaypoint ) == 3 )
  	{
  		if ( WaypointReached() )
  			return true;
  	}
  
  	// We may have just teleported, so check to make sure we have a waypoint
  	if ( !m_pCurrentWaypoint || !m_pNextWaypoint )
  		return false;
  
  	// Figure out which spline we're trucking along
  	CVehicleWaypoint *pCurrentSplineBeingTraversed = m_pCurrentWaypoint;
  	if ( m_flDistanceAlongSpline > 1 )
  	{
  		pCurrentSplineBeingTraversed = m_pNextWaypoint;
  	}
  
  	// Get our current speed, and check it against the length of the spline to know how far to advance our marker
  	AngularImpulse angVel;
  	Vector vecVelocity;
  	IPhysicsObject *pVehiclePhysics = m_hVehicleEntity->VPhysicsGetObject();
  
  	if( !pVehiclePhysics )
  	{
  		// I think my vehicle has been destroyed.
  		return false;
  	}
  
  	pVehiclePhysics->GetVelocity( &vecVelocity, &angVel );
  	float flSpeed = vecVelocity.Length();
  	float flIncTime = gpGlobals->curtime - GetLastThink();
  	float flIncrement = flIncTime * (flSpeed / pCurrentSplineBeingTraversed->GetLength());
  
  	// Now advance our point along the spline
  	m_flDistanceAlongSpline = clamp( m_flDistanceAlongSpline + flIncrement, 0.f, 2.f);
  	if ( m_flDistanceAlongSpline > 1 )
  	{
  		// We crossed the spline boundary
  		pCurrentSplineBeingTraversed = m_pNextWaypoint;
  	}
  
  	Vector vSplinePoint = pCurrentSplineBeingTraversed->GetPointAt( m_flDistanceAlongSpline > 1 ? m_flDistanceAlongSpline-1 : m_flDistanceAlongSpline );
  	Vector vSplineTangent = pCurrentSplineBeingTraversed->GetTangentAt( m_flDistanceAlongSpline > 1 ? m_flDistanceAlongSpline-1 : m_flDistanceAlongSpline );
  
  	// Now that we've got the target spline point & tangent, use it to decide what our desired velocity is.
  	// If we're close to the tangent, just use the tangent. Otherwise, Lerp towards it.
  	Vector vecToDesired = (vSplinePoint - GetAbsOrigin());
  	float flDistToDesired = VectorNormalize( vecToDesired );
  	float flTangentLength = VectorNormalize( vSplineTangent );
  
  	if ( flDistToDesired > (flTangentLength * 0.75) )
  	{
  		m_vecDesiredVelocity = vecToDesired * flTangentLength;
  	}
  	else
  	{
  		VectorLerp( vSplineTangent, vecToDesired * flTangentLength, (flDistToDesired / (flTangentLength * 0.5)), m_vecDesiredVelocity );
  	}
  
  	// Decrease speed according to the turn we're trying to make
  	Vector vecRight;
  	m_hVehicleEntity->GetVectors( NULL, &vecRight, NULL );
  	Vector vecNormVel = m_vecDesiredVelocity;
  	VectorNormalize( vecNormVel );
  	float flDotRight = DotProduct( vecRight, vecNormVel );
  	flSpeed = (1.0 - fabs(flDotRight));
  	// Don't go slower than we've been told to go
  	if ( flSpeed < m_flDriversMinSpeed )
  	{
  		flSpeed = m_flDriversMinSpeed;
  	}
  	m_vecDesiredVelocity = vecNormVel * (flSpeed * m_flMaxSpeed);
  
  	// Bunch o'debug
  	if ( g_debug_vehicledriver.GetInt() & DRIVER_DEBUG_PATH )
  	{
  		NDebugOverlay::Box( m_vecPrevPrevPoint, -Vector(15,15,15), Vector(15,15,15), 192,0,0, true, 0.1);
  		NDebugOverlay::Box( m_vecPrevPoint, -Vector(20,20,20), Vector(20,20,20), 255,0,0, true, 0.1);
  		NDebugOverlay::Box( m_vecPostPoint, -Vector(20,20,20), Vector(20,20,20), 0,192,0, true, 0.1);
  		NDebugOverlay::Box( m_vecPostPostPoint, -Vector(20,20,20), Vector(20,20,20), 0,128,0, true, 0.1);
  		NDebugOverlay::Box( vSplinePoint, -Vector(10,10,10), Vector(10,10,10), 0,0,255, true, 0.1);
  		NDebugOverlay::Line( vSplinePoint, vSplinePoint + (vSplineTangent * 40), 0,0,255, true, 0.1);
  
  		//NDebugOverlay::HorzArrow( pCurrentSplineBeingTraversed->splinePoints[0], pCurrentSplineBeingTraversed->splinePoints[1], 30, 255,255,255,0, false, 0.1f );
  		//NDebugOverlay::HorzArrow( pCurrentSplineBeingTraversed->splinePoints[1], pCurrentSplineBeingTraversed->splinePoints[2], 20, 255,255,255,0, false, 0.1f );
  		//NDebugOverlay::HorzArrow( pCurrentSplineBeingTraversed->splinePoints[2], pCurrentSplineBeingTraversed->splinePoints[3], 10, 255,255,255,0, false, 0.1f );
  
  		// Draw the plane we're checking against for waypoint passing
  		Vector vecPlaneRight;
  		CrossProduct( m_pCurrentWaypoint->planeWaypoint.normal, Vector(0,0,1), vecPlaneRight );
  		Vector vecPlane = m_pCurrentWaypoint->splinePoints[2];
  		NDebugOverlay::Line( vecPlane + (vecPlaneRight * -100), vecPlane + (vecPlaneRight * 100), 255,0,0, true, 0.1);
  
  		// Draw the next plane too
  		CrossProduct( m_pNextWaypoint->planeWaypoint.normal, Vector(0,0,1), vecPlaneRight );
  		vecPlane = m_pNextWaypoint->splinePoints[2];
  		NDebugOverlay::Line( vecPlane + (vecPlaneRight * -100), vecPlane + (vecPlaneRight * 100), 192,0,0, true, 0.1);
  	}
  
  	if ( g_debug_vehicledriver.GetInt() & DRIVER_DEBUG_PATH_SPLINE )
  	{
  		for ( int i = 0; i < 10; i++ )
  		{
  			Vector vecTarget = m_pCurrentWaypoint->GetPointAt( 0.1 * i );
  			Vector vecTangent = m_pCurrentWaypoint->GetTangentAt( 0.1 * i );
  			VectorNormalize(vecTangent);
  			NDebugOverlay::Box( vecTarget, -Vector(10,10,10), Vector(10,10,10), 255,0,0, true, 0.1 );
  			NDebugOverlay::Line( vecTarget, vecTarget + (vecTangent * 10), 255,255,0, true, 0.1);
  		}
  	}
  
  	return true;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: This takes the current place the NPC's trying to get to, figures out
  //			what keys to press to get the vehicle to go there, and then sends
  //			them to the vehicle.
  //-----------------------------------------------------------------------------
  void CNPC_VehicleDriver::DriveVehicle( void )
  {
  	AngularImpulse angVel;
  	Vector vecVelocity;
  	IPhysicsObject *pVehiclePhysics = m_hVehicleEntity->VPhysicsGetObject();
  	if ( !pVehiclePhysics )
  		return;
  	pVehiclePhysics->GetVelocity( &vecVelocity, &angVel );
  	float flSpeed = VectorNormalize( vecVelocity );
  
  	// If we have no target position to drive to, brake to a halt
  	if ( !m_flMaxSpeed || m_vecDesiredPosition == vec3_origin )
  	{
  		if ( flSpeed > 1 )
  		{
  			m_pVehicleInterface->NPC_Brake();
  		}
  		return;
  	}
  
  	if ( g_debug_vehicledriver.GetInt() & DRIVER_DEBUG_PATH )
  	{
  		NDebugOverlay::Box(m_vecDesiredPosition, -Vector(20,20,20), Vector(20,20,20), 0,255,0, true, 0.1);
  		NDebugOverlay::Line(GetAbsOrigin(), GetAbsOrigin() + m_vecDesiredVelocity, 0,255,0, true, 0.1);
  	}
  
  	m_flGoalSpeed = VectorNormalize(m_vecDesiredVelocity);
  
  	// Is our target in front or behind us?
  	Vector vecForward, vecRight;
  	m_hVehicleEntity->GetVectors( &vecForward, &vecRight, NULL );
  	float flDot = DotProduct( vecForward, m_vecDesiredVelocity );
  	bool bBehind = ( flDot < 0 );
  	float flVelDot = DotProduct( vecVelocity, m_vecDesiredVelocity );
  	bool bGoingWrongWay = ( flVelDot < 0 );
  
  	// Figure out whether we should accelerate / decelerate
  	if ( bGoingWrongWay || (flSpeed < m_flGoalSpeed) )
  	{
  		// If it's behind us, go backwards not forwards
  		if ( bBehind )
  		{
  			m_pVehicleInterface->NPC_ThrottleReverse();
  		}
  		else
  		{
  			m_pVehicleInterface->NPC_ThrottleForward();
  		}
  	}
  	else
  	{
  		// Brake if we're go significantly too fast
  		if ( (flSpeed - 200) > m_flGoalSpeed )
  		{
  			m_pVehicleInterface->NPC_Brake();
  		}
  		else
  		{
  			m_pVehicleInterface->NPC_ThrottleCenter();
  		}
  	}
  
  	// Do we need to turn?
  	float flDotRight = DotProduct( vecRight, m_vecDesiredVelocity );
  	if ( bBehind )
  	{
  		// If we're driving backwards, flip our turning
  		flDotRight *= -1;
  	}
  	// Map it to the vehicle's steering
  	flDotRight *= (m_flSteering / 90);
  
  	if ( flDotRight < 0 )
  	{
  		// Turn left
  		m_pVehicleInterface->NPC_TurnLeft( -flDotRight );
  	}
  	else if ( flDotRight > 0 )
  	{
  		// Turn right
  		m_pVehicleInterface->NPC_TurnRight( flDotRight );
  	}
  	else
  	{
  		m_pVehicleInterface->NPC_TurnCenter();
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Check to see if we should teleport to the current path corner
  //-----------------------------------------------------------------------------
  void CNPC_VehicleDriver::CheckForTeleport( void )
  {
  	if ( !GetGoalEnt() )
  		return;
  
  	CPathTrack *pTrack = dynamic_cast<CPathTrack *>( GetGoalEnt() );
  	if ( !pTrack )
  		return;
  
  	// Does it have the teleport flag set?
  	if ( pTrack->HasSpawnFlags( SF_PATH_TELEPORT ) )
  	{
  		IncrementInterpolationFrame();
  
  		// Teleport the vehicle to the pathcorner
  		Vector vecMins, vecMaxs;
  		vecMins = m_hVehicleEntity->CollisionProp()->OBBMins();
  		vecMaxs = m_hVehicleEntity->CollisionProp()->OBBMaxs();
  		Vector vecTarget = pTrack->GetAbsOrigin() - (vecMins + vecMaxs) * 0.5;
  		vecTarget.z += ((vecMaxs.z - vecMins.z) * 0.5) + 8;	// Safety buffer
  
  		// Orient it to face the next point
  		QAngle vecAngles = pTrack->GetAbsAngles();
  		Vector vecToTarget = vec3_origin;
  		if ( pTrack->GetNext() )
  		{
  			vecToTarget = (pTrack->GetNext()->GetAbsOrigin() - pTrack->GetAbsOrigin());
  			VectorNormalize( vecToTarget );
  
  			// Vehicles are rotated 90 degrees
  			VectorAngles( vecToTarget, vecAngles );
  			vecAngles[YAW] -= 90;
  		}
  		m_hVehicleEntity->Teleport( &vecTarget, &vecAngles, &vec3_origin );
  
  		// Teleport the driver
  		SetAbsOrigin( m_hVehicleEntity->WorldSpaceCenter() );
  		SetAbsAngles( m_hVehicleEntity->GetAbsAngles() );
  
  		m_vecPrevPoint = pTrack->GetAbsOrigin();
  
  		// Move to the next waypoint, we've reached this one
  		if ( GetNavigator()->GetPath() )
  		{
  			WaypointReached();
  		}
  
  		// Clear our waypoints, because the next waypoint is certainly invalid now.
  		ClearWaypoints();
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  float CNPC_VehicleDriver::GetDefaultNavGoalTolerance()
  {
  	return 48;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_VehicleDriver::RecalculateSpeeds( void )
  {
  	// Get data from the vehicle
  	const vehicleparams_t *pParams = m_pVehicleInterface->GetVehicleParams();
  	if ( pParams )
  	{
  		m_flMaxSpeed = pParams->engine.maxSpeed * m_flDriversMaxSpeed;
  		m_flSteering = pParams->steering.degreesSlow;
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_VehicleDriver::InputSetDriversMaxSpeed( inputdata_t &inputdata )
  {
  	m_flDriversMaxSpeed = inputdata.value.Float();
  
  	RecalculateSpeeds();
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_VehicleDriver::InputSetDriversMinSpeed( inputdata_t &inputdata )
  {
  	m_flDriversMinSpeed = inputdata.value.Float();
  
  	RecalculateSpeeds();
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_VehicleDriver::InputStartForward( inputdata_t &inputdata )
  {
  	CLEARBITS( m_spawnflags, SF_VEHICLEDRIVER_INACTIVE );
  	if ( m_NPCState == NPC_STATE_IDLE )
  	{
  		SetState( NPC_STATE_ALERT );
  	}
  	SetCondition( COND_PROVOKED );
  
  	RecalculateSpeeds();
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Tell the driver to stop moving
  //-----------------------------------------------------------------------------
  void CNPC_VehicleDriver::InputStop( inputdata_t &inputdata )
  {
  	m_flMaxSpeed = 0;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Tell the driver to start firing at targets
  //-----------------------------------------------------------------------------
  void CNPC_VehicleDriver::InputStartFiring( inputdata_t &inputdata )
  {
  	CLEARBITS( m_spawnflags, SF_VEHICLEDRIVER_INACTIVE );
  	SetCondition( COND_PROVOKED );
  
  	float flMinRange, flMaxRange;
  	// If the vehicle has a weapon, set our capability
  	if ( m_pVehicleInterface->NPC_HasPrimaryWeapon() )
  	{
  		CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 );
  		m_pVehicleInterface->Weapon_PrimaryRanges( &flMinRange, &flMaxRange );
  
  		// Ensure the look distances is long enough
  		if ( m_flDistTooFar < flMaxRange || GetSenses()->GetDistLook() < flMaxRange )
  		{
  			m_flDistTooFar = flMaxRange;
  			SetDistLook( flMaxRange );
  		}
  	}
  
  	if ( m_pVehicleInterface->NPC_HasSecondaryWeapon() )
  	{
  		CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK2 );
  		m_pVehicleInterface->Weapon_SecondaryRanges( &flMinRange, &flMaxRange );
  
  		// Ensure the look distances is long enough
  		if ( m_flDistTooFar < flMaxRange || GetSenses()->GetDistLook() < flMaxRange )
  		{
  			m_flDistTooFar = flMaxRange;
  			SetDistLook( flMaxRange );
  		}
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Tell the driver to stop firing at targets
  //-----------------------------------------------------------------------------
  void CNPC_VehicleDriver::InputStopFiring( inputdata_t &inputdata )
  {
  	// If the vehicle has a weapon, set our capability
  	CapabilitiesRemove( bits_CAP_INNATE_RANGE_ATTACK1 );
  	CapabilitiesRemove( bits_CAP_INNATE_RANGE_ATTACK2 );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CNPC_VehicleDriver::InputGotoPathCorner( inputdata_t &inputdata )
  {
  	string_t iszPathName = inputdata.value.StringID();
  	if ( iszPathName != NULL_STRING )
  	{
  		CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, iszPathName );
  		if ( !pEntity )
  		{
  			Warning("npc_vehicledriver %s couldn't find entity named %s\n", STRING(GetEntityName()), STRING(iszPathName) );
  			return;
  		}
  
  		ClearWaypoints();
  
  		// Drive to the point
  		SetGoalEnt( pEntity );
  		if ( m_NPCState == NPC_STATE_IDLE )
  		{
  			SetState( NPC_STATE_ALERT );
  		}
  		SetCondition( COND_PROVOKED );
  
  		// Force him to start forward
  		InputStartForward( inputdata );
  	}
  }
  
  //-----------------------------------------------------------------------------
  //
  // Schedules
  //
  //-----------------------------------------------------------------------------
  
  AI_BEGIN_CUSTOM_NPC( npc_vehicledriver, CNPC_VehicleDriver )
  
  	//Tasks
  	DECLARE_TASK( TASK_VEHICLEDRIVER_GET_PATH )
  	
  	// Schedules
  	DEFINE_SCHEDULE
  	(
  		SCHED_VEHICLEDRIVER_INACTIVE,
  
  		"	Tasks"
  		"		TASK_WAIT_INDEFINITE	0"
  		""
  		"	Interrupts"
  		"		COND_PROVOKED"
  	)
  
  	DEFINE_SCHEDULE
  	(
  		SCHED_VEHICLEDRIVER_COMBAT_WAIT,
  
  		"	Tasks"
  		"		TASK_WAIT				5"
  		""
  		"	Interrupts"
  		"		COND_NEW_ENEMY"
  		"		COND_LIGHT_DAMAGE"
  		"		COND_HEAVY_DAMAGE"
  		"		COND_PROVOKED"
  		"		COND_CAN_RANGE_ATTACK1"
  		"		COND_CAN_RANGE_ATTACK2"
  	)
  
  	DEFINE_SCHEDULE
  	(
  		SCHED_VEHICLEDRIVER_DRIVE_PATH,
  
  		"	Tasks"
  		"		TASK_VEHICLEDRIVER_GET_PATH		0"
  		"		TASK_WALK_PATH					9999"
  		"		TASK_WAIT_FOR_MOVEMENT			0"
  		"		TASK_WAIT_PVS					0"
  		""
  		"	Interrupts"
  		"		COND_NEW_ENEMY"
  		"		COND_PROVOKED"
  	)
  
  AI_END_CUSTOM_NPC()
  