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

#include "cbase.h"
#include "ai_default.h"
#include "ai_task.h"
#include "ai_schedule.h"
#include "ai_hull.h"
#include "ai_squadslot.h"
#include "ai_basenpc.h"
#include "ai_navigator.h"
#include "ndebugoverlay.h"
#include "explode.h"
#include "bitstring.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "decals.h"
#include "antlion_dust.h"
#include "ai_memory.h"
#include "ai_squad.h"
#include "ai_senses.h"
#include "beam_shared.h"
#include "iservervehicle.h"
#include "SoundEmitterSystem/isoundemittersystembase.h"
#include "physics_saverestore.h"
#include "vphysics/constraints.h"
#include "vehicle_base.h"
#include "eventqueue.h"
#include "te_effect_dispatch.h"
#include "npc_rollermine.h"
#include "func_break.h"
#include "soundenvelope.h"

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

#define ROLLERMINE_MAX_TORQUE_FACTOR	5
extern short g_sModelIndexWExplosion;

ConVar	sk_rollermine_shock( "sk_rollermine_shock","0");
ConVar	sk_rollermine_stun_delay("sk_rollermine_stun_delay", "1");
ConVar	sk_rollermine_vehicle_intercept( "sk_rollermine_vehicle_intercept","1");

//-----------------------------------------------------------------------------
// CRollerController implementation
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: This class only implements the IMotionEvent-specific behavior
//			It keeps track of the forces so they can be integrated
//-----------------------------------------------------------------------------
class CRollerController : public IMotionEvent
{
	DECLARE_SIMPLE_DATADESC();

public:
	IMotionEvent::simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular );

	AngularImpulse	m_vecAngular;
	Vector			m_vecLinear;

	void Off( void ) { m_fIsStopped = true; }
	void On( void ) { m_fIsStopped = false; }

	bool IsOn( void ) { return !m_fIsStopped; }

private:
	bool	m_fIsStopped;
};

BEGIN_SIMPLE_DATADESC( CRollerController )

	DEFINE_FIELD( m_vecAngular, FIELD_VECTOR ),
	DEFINE_FIELD( m_vecLinear, FIELD_VECTOR ),
	DEFINE_FIELD( m_fIsStopped, FIELD_BOOLEAN ),

END_DATADESC()


//-----------------------------------------------------------------------------
IMotionEvent::simresult_e CRollerController::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
{
	if( m_fIsStopped )
	{
		return SIM_NOTHING;
	}

	linear = m_vecLinear;
	angular = m_vecAngular;
	
	return IMotionEvent::SIM_LOCAL_ACCELERATION;
}
//-----------------------------------------------------------------------------


#define ROLLERMINE_IDLE_SEE_DIST					2048
#define ROLLERMINE_NORMAL_SEE_DIST					2048
#define ROLLERMINE_WAKEUP_DIST						256
#define ROLLERMINE_SEE_VEHICLESONLY_BEYOND_IDLE		300		// See every other than vehicles upto this distance (i.e. old idle see dist)
#define ROLLERMINE_SEE_VEHICLESONLY_BEYOND_NORMAL	800		// See every other than vehicles upto this distance (i.e. old normal see dist)

#define ROLLERMINE_MIN_ATTACK_DIST	1
#define ROLLERMINE_MAX_ATTACK_DIST	4096

#define	ROLLERMINE_OPEN_THRESHOLD	256

#define ROLLERMINE_VEHICLE_OPEN_THRESHOLD	400
#define ROLLERMINE_VEHICLE_HOP_THRESHOLD	300

#define ROLLERMINE_HOP_DELAY				2			// Don't allow hops faster than this

//#define ROLLERMINE_REQUIRED_TO_EXPLODE_VEHICLE		4

#define ROLLERMINE_FEAR_DISTANCE			(300*300)

//=========================================================
// Custom schedules
//=========================================================
enum
{
	SCHED_ROLLERMINE_RANGE_ATTACK1 = LAST_SHARED_SCHEDULE,
	SCHED_ROLLERMINE_CHASE_ENEMY,
	SCHED_ROLLERMINE_BURIED_WAIT,
	SCHED_ROLLERMINE_BURIED_UNBURROW,
	SCHED_ROLLERMINE_FLEE,
};

//=========================================================
// Custom tasks
//=========================================================
enum 
{
	TASK_ROLLERMINE_CHARGE_ENEMY = LAST_SHARED_TASK,
	TASK_ROLLERMINE_BURIED_WAIT,
	TASK_ROLLERMINE_UNBURROW,
	TASK_ROLLERMINE_GET_PATH_TO_FLEE,
};


// This are little 'sound event' flags. Set the flag after you play the
// sound, and the sound will not be allowed to play until the flag is then cleared.
#define ROLLERMINE_SE_CLEAR		0x00000000
#define ROLLERMINE_SE_CHARGE	0x00000001
#define ROLLERMINE_SE_TAUNT		0x00000002
#define ROLLERMINE_SE_SHARPEN	0x00000004
#define ROLLERMINE_SE_TOSSED	0x00000008

enum rollingsoundstate_t { ROLL_SOUND_NOT_READY = 0, ROLL_SOUND_OFF, ROLL_SOUND_CLOSED, ROLL_SOUND_OPEN };

//=========================================================
//=========================================================
class CNPC_RollerMine : public CAI_BaseNPC, public CDefaultPlayerPickupVPhysics
{
	DECLARE_CLASS( CNPC_RollerMine, CAI_BaseNPC );
	DECLARE_SERVERCLASS();

public:

	~CNPC_RollerMine( void );

	void	Spawn( void );
	bool	CreateVPhysics();
	void	StartTask( const Task_t *pTask );
	void	RunTask( const Task_t *pTask );
	void	SpikeTouch( CBaseEntity *pOther );
	void	ShockTouch( CBaseEntity *pOther );
	void	CloseTouch( CBaseEntity *pOther );
	void	EmbedTouch( CBaseEntity *pOther );
	float	GetAttackDamageScale( CBaseEntity *pVictim );
	void	VPhysicsCollision( int index, gamevcollisionevent_t *pEvent );
	void	Precache( void );
	void	OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason );
	void	OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason );
	void	StopLoopingSounds( void );
	void	PrescheduleThink();
	bool	ShouldSavePhysics()	{ return true; }
	void	OnRestore();
	void	Bury( trace_t *tr );
	bool	QuerySeeEntity(CBaseEntity *pSightEnt);

	int		RangeAttack1Conditions ( float flDot, float flDist );
	int		SelectSchedule( void );

	bool	OverrideMove( float flInterval ) { return true; }
	bool	IsValidEnemy( CBaseEntity *pEnemy );
	bool	IsPlayerVehicle( CBaseEntity *pEntity );
	bool	IsShocking() { return gpGlobals->curtime < m_flShockTime ? true : false; }
	void	UpdateRollingSound();
	void	UpdatePingSound();
	void	StopRollingSound();
	void	StopPingSound();
	float	RollingSpeed();
	void	EmbedOnGroundImpact();
	void	DrawDebugGeometryOverlays()
	{
		if (m_debugOverlays & OVERLAY_BBOX_BIT) 
		{
			float dist = GetSenses()->GetDistLook();
			Vector range(dist, dist, 64);
			NDebugOverlay::Box( GetAbsOrigin(), -range, range, 255, 0, 0, 0, 0 );
		}
		BaseClass::DrawDebugGeometryOverlays();
	}
	// UNDONE: Put this in the qc file!
	Vector EyePosition() 
	{
		// This takes advantage of the fact that the system knows
		// that the abs origin is at the center of the rollermine
		// and that the OBB is actually world-aligned despite the
		// fact that SOLID_VPHYSICS is being used
		Vector eye = CollisionProp()->GetCollisionOrigin();
		eye.z += CollisionProp()->OBBMaxs().z;
		return eye;
	}

	int		OnTakeDamage( const CTakeDamageInfo &info );
	void	TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr );

	Class_T	Classify() 
	{ 
		return m_bHeld ? CLASS_PLAYER_ALLY : CLASS_COMBINE; 
	}

	virtual bool ShouldGoToIdleState() 
	{ 
		return gpGlobals->curtime > m_flGoIdleTime ? true : false;
	}

	virtual	void OnStateChange( NPC_STATE OldState, NPC_STATE NewState );

	// Vehicle interception
	bool	EnemyInVehicle( void );
	float	VehicleHeading( CBaseEntity *pVehicle );

	NPC_STATE SelectIdealState();

	// Vehicle sticking
	void		StickToVehicle( CBaseEntity *pOther );
	void		AnnounceArrivalToOthers( CBaseEntity *pOther );
	void		UnstickFromVehicle( void );
	CBaseEntity *GetVehicleStuckTo( void );
	int			CountRollersOnMyVehicle( CUtlVector<CNPC_RollerMine*> *pRollerList );
	void		InputConstraintBroken( inputdata_t &inputdata );
	void		InputRespondToChirp( inputdata_t &inputdata );
	void		InputRespondToExplodeChirp( inputdata_t &inputdata );
	void		InputJoltVehicle( inputdata_t &inputdata );

	void		PreventUnstickUntil( float flTime ) { m_flPreventUnstickUntil = flTime; }

	COutputEvent m_OnPhysGunDrop;
	COutputEvent m_OnPhysGunPickup;

protected:
	DEFINE_CUSTOM_AI;
	DECLARE_DATADESC();

	bool	BecomePhysical();
	void	WakeNeighbors();
	bool	WakeupMine( CAI_BaseNPC *pNPC );

	void	Open( void );
	void	Close( void );
	void	Explode( void );
	void	PreDetonate( void );
	void	Hop( float height );

	void	ShockTarget( CBaseEntity *pOther );

	bool	IsActive() { return m_flActiveTime > gpGlobals->curtime ? false : true; }
	CSoundPatch					*m_pRollSound;
	CSoundPatch					*m_pPingSound;

	CRollerController			m_RollerController;
	IPhysicsMotionController	*m_pMotionController;

	float	m_flSeeVehiclesOnlyBeyond;
	float	m_flChargeTime;
	float	m_flGoIdleTime;
	float	m_flShockTime;
	float	m_flForwardSpeed;
	int		m_iSoundEventFlags;
	rollingsoundstate_t m_rollingSoundState;

	CNetworkVar( bool, m_bIsOpen );
	CNetworkVar( float, m_flActiveTime );	//If later than the current time, this will force the mine to be active

	bool	m_bHeld;		//Whether or not the player is holding the mine
	EHANDLE m_hVehicleStuckTo;
	float	m_flPreventUnstickUntil;
	float	m_flNextHop;
	bool	m_bStartBuried;
	bool	m_bBuried;
	bool	m_bIsPrimed;
	bool	m_wakeUp;
	bool	m_bEmbedOnGroundImpact;

	// Constraint used to stick us to a vehicle
	IPhysicsConstraint *m_pConstraint;
};


LINK_ENTITY_TO_CLASS( npc_rollermine, CNPC_RollerMine );

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
BEGIN_DATADESC( CNPC_RollerMine )

	DEFINE_SOUNDPATCH( m_pRollSound ),
	DEFINE_SOUNDPATCH( m_pPingSound ),
	DEFINE_EMBEDDED( m_RollerController ),
	DEFINE_PHYSPTR( m_pMotionController ),

	DEFINE_FIELD( m_flSeeVehiclesOnlyBeyond, FIELD_FLOAT ),
	DEFINE_FIELD( m_flActiveTime, FIELD_TIME ),
	DEFINE_FIELD( m_flChargeTime, FIELD_TIME ),
	DEFINE_FIELD( m_flGoIdleTime, FIELD_TIME ),
	DEFINE_FIELD( m_flShockTime, FIELD_TIME ),
	DEFINE_FIELD( m_flForwardSpeed, FIELD_FLOAT ),
	DEFINE_FIELD( m_bIsOpen, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bHeld, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_hVehicleStuckTo, FIELD_EHANDLE ),
	DEFINE_FIELD( m_flPreventUnstickUntil, FIELD_TIME ),
	DEFINE_FIELD( m_flNextHop, FIELD_FLOAT ),
	DEFINE_FIELD( m_bIsPrimed, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_iSoundEventFlags, FIELD_INTEGER ),
	DEFINE_FIELD( m_rollingSoundState, FIELD_INTEGER ),

	DEFINE_KEYFIELD( m_bStartBuried, FIELD_BOOLEAN, "StartBuried" ),
	DEFINE_FIELD( m_bBuried, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_wakeUp, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bEmbedOnGroundImpact, FIELD_BOOLEAN ),

	DEFINE_PHYSPTR( m_pConstraint ),

	DEFINE_INPUTFUNC( FIELD_VOID, "ConstraintBroken", InputConstraintBroken ),
	DEFINE_INPUTFUNC( FIELD_VOID, "RespondToChirp", InputRespondToChirp ),
	DEFINE_INPUTFUNC( FIELD_VOID, "RespondToExplodeChirp", InputRespondToExplodeChirp ),
	DEFINE_INPUTFUNC( FIELD_VOID, "JoltVehicle", InputJoltVehicle ),

	// Function Pointers
	DEFINE_ENTITYFUNC( SpikeTouch ),
	DEFINE_ENTITYFUNC( ShockTouch ),
	DEFINE_ENTITYFUNC( CloseTouch ),
	DEFINE_ENTITYFUNC( EmbedTouch ),
	DEFINE_THINKFUNC( Explode ),
	DEFINE_THINKFUNC( PreDetonate ),

	DEFINE_OUTPUT( m_OnPhysGunDrop, "OnPhysGunDrop" ),
	DEFINE_OUTPUT( m_OnPhysGunPickup, "OnPhysGunPickup" ),

END_DATADESC()

IMPLEMENT_SERVERCLASS_ST( CNPC_RollerMine, DT_RollerMine )
	SendPropInt(SENDINFO(m_bIsOpen), 1, SPROP_UNSIGNED ),
	SendPropFloat(SENDINFO(m_flActiveTime), 0, SPROP_NOSCALE ),
END_SEND_TABLE()

bool NPC_Rollermine_IsRollermine( CBaseEntity *pEntity )
{
	CNPC_RollerMine *pRoller = dynamic_cast<CNPC_RollerMine *>(pEntity);
	return pRoller ? true : false;
}

CBaseEntity *NPC_Rollermine_DropFromPoint( const Vector &originStart, CBaseEntity *pOwner )
{
	CNPC_RollerMine *pMine = (CNPC_RollerMine*)CreateEntityByName("npc_rollermine");

	if ( pMine )
	{
		pMine->SetAbsOrigin( originStart );
		pMine->SetOwnerEntity( pOwner );
		pMine->Spawn();
		pMine->EmbedOnGroundImpact();
	}
	else
	{
		Warning( "NULL Ent in Rollermine Create!\n" );
	}

	return pMine;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CNPC_RollerMine::~CNPC_RollerMine( void )
{
	if ( m_pMotionController != NULL )
	{
		physenv->DestroyMotionController( m_pMotionController );
		m_pMotionController = NULL;
	}

	UnstickFromVehicle();
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  :
// Output :
//-----------------------------------------------------------------------------
void CNPC_RollerMine::Precache( void )
{
	PrecacheModel( "models/roller.mdl" );
	PrecacheModel( "models/roller_spikes.mdl" );

	PrecacheModel( "sprites/bluelight1.vmt" );
	PrecacheModel( "sprites/rollermine_shock.vmt" );

	PrecacheScriptSound( "NPC_RollerMine.Taunt" );
	PrecacheScriptSound( "NPC_RollerMine.OpenSpikes" );
	PrecacheScriptSound( "NPC_RollerMine.Warn" );
	PrecacheScriptSound( "NPC_RollerMine.Shock" );
	PrecacheScriptSound( "NPC_RollerMine.ExplodeChirp" );
	PrecacheScriptSound( "NPC_RollerMine.Chirp" );
	PrecacheScriptSound( "NPC_RollerMine.ChirpRespond" );
	PrecacheScriptSound( "NPC_RollerMine.ExplodeChirpRespond" );
	PrecacheScriptSound( "NPC_RollerMine.JoltVehicle" );
	PrecacheScriptSound( "NPC_RollerMine.Tossed" );
	PrecacheScriptSound( "NPC_RollerMine.Hurt" );

	PrecacheScriptSound( "NPC_RollerMine.Roll" );
	PrecacheScriptSound( "NPC_RollerMine.RollWithSpikes" );
	PrecacheScriptSound( "NPC_RollerMine.Ping" );
	PrecacheScriptSound( "NPC_RollerMine.Held" );

	
	BaseClass::Precache();
}


//-----------------------------------------------------------------------------
// Purpose:
// Input  :
// Output :
//-----------------------------------------------------------------------------
void CNPC_RollerMine::Spawn( void )
{
	Precache();

	SetSolid( SOLID_VPHYSICS );
	AddSolidFlags( FSOLID_FORCE_WORLD_ALIGNED | FSOLID_NOT_STANDABLE );

	BaseClass::Spawn();

	AddEFlags( EFL_NO_DISSOLVE );

	CapabilitiesClear();
	CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_SQUAD );

	m_pRollSound = NULL;

	m_bIsOpen = true;
	Close();
	
	m_flFieldOfView		= -1.0f;
	m_flForwardSpeed	= -1200;
	m_bloodColor		= DONT_BLEED;
	SetHullType(HULL_SMALL_CENTERED);
	SetHullSizeNormal();

	m_flActiveTime		= 0;

	m_bBuried = m_bStartBuried;
	if ( m_bStartBuried )
	{
		trace_t tr;
		Bury( &tr );
	}

	NPCInit();

	m_takedamage = DAMAGE_EVENTS_ONLY;
	SetDistLook( ROLLERMINE_IDLE_SEE_DIST );
	m_flSeeVehiclesOnlyBeyond = ROLLERMINE_SEE_VEHICLESONLY_BEYOND_IDLE;

	//Suppress superfluous warnings from animation system
	m_flGroundSpeed = 20;
	m_NPCState		= NPC_STATE_NONE;

	m_rollingSoundState = ROLL_SOUND_OFF;

	m_pConstraint = NULL;
	m_hVehicleStuckTo = NULL;
	m_flPreventUnstickUntil = 0;
	m_flNextHop = 0;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::Bury( trace_t *tr )
{
	AI_TraceHull( GetAbsOrigin() + Vector(0,0,64), GetAbsOrigin() - Vector( 0, 0, MAX_TRACE_LENGTH ), Vector(-16,-16,-16), Vector(16,16,16), MASK_NPCSOLID, this, GetCollisionGroup(), tr );

	//NDebugOverlay::Box( tr->startpos, Vector(-16,-16,-16), Vector(16,16,16), 255, 0, 0, 64, 10.0 );
	//NDebugOverlay::Box( tr->endpos, Vector(-16,-16,-16), Vector(16,16,16), 0, 255, 0, 64, 10.0 );

	// Move into the ground layer
	Vector buriedPos = tr->endpos - Vector( 0, 0, GetHullHeight() * 0.5 );
	Teleport( &buriedPos, NULL, &vec3_origin );
	SetMoveType( MOVETYPE_NONE );

	SetSchedule( SCHED_ROLLERMINE_BURIED_WAIT );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CNPC_RollerMine::WakeupMine( CAI_BaseNPC *pNPC )
{
	if ( pNPC && pNPC->m_iClassname == m_iClassname && pNPC != this )
	{
		CNPC_RollerMine *pMine = dynamic_cast<CNPC_RollerMine *>(pNPC);
		if ( pMine )
		{
			if ( pMine->m_NPCState == NPC_STATE_IDLE )
			{
				pMine->m_wakeUp = false;
				pMine->SetIdealState( NPC_STATE_ALERT );
				return true;
			}
		}
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::WakeNeighbors()
{
	if ( !m_wakeUp || !IsActive() )
		return;
	m_wakeUp = false;

	if ( m_pSquad )
	{
		AISquadIter_t iter;
		for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) )
		{
			WakeupMine( pSquadMember );
		}
		return;
	}

	CBaseEntity *entityList[64];
	Vector range(ROLLERMINE_WAKEUP_DIST,ROLLERMINE_WAKEUP_DIST,64);
	int boxCount = UTIL_EntitiesInBox( entityList, ARRAYSIZE(entityList), GetAbsOrigin()-range, GetAbsOrigin()+range, FL_NPC );
	//NDebugOverlay::Box( GetAbsOrigin(), -range, range, 255, 0, 0, 64, 10.0 );
	int wakeCount = 0;
	while ( boxCount > 0 )
	{
		boxCount--;
		CAI_BaseNPC *pNPC = entityList[boxCount]->MyNPCPointer();
		if ( WakeupMine( pNPC ) )
		{
			wakeCount++;
			if ( wakeCount >= 2 )
				return;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::OnStateChange( NPC_STATE OldState, NPC_STATE NewState )
{
	if ( NewState == NPC_STATE_IDLE )
	{
		SetDistLook( ROLLERMINE_IDLE_SEE_DIST );
		m_flDistTooFar = ROLLERMINE_IDLE_SEE_DIST;
		m_flSeeVehiclesOnlyBeyond = ROLLERMINE_SEE_VEHICLESONLY_BEYOND_IDLE;
		
		m_RollerController.m_vecAngular = vec3_origin;
		m_wakeUp = true;
	}
	else
	{
		if ( OldState == NPC_STATE_IDLE )
		{
			// wake the neighbors!
			WakeNeighbors();
		}
		SetDistLook( ROLLERMINE_NORMAL_SEE_DIST );
		m_flDistTooFar = ROLLERMINE_NORMAL_SEE_DIST;
		m_flSeeVehiclesOnlyBeyond = ROLLERMINE_SEE_VEHICLESONLY_BEYOND_NORMAL;
	}
	BaseClass::OnStateChange( OldState, NewState );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
NPC_STATE CNPC_RollerMine::SelectIdealState( void )
{
	switch ( m_NPCState )
	{
		case NPC_STATE_COMBAT:
		{
			if ( HasCondition( COND_ENEMY_TOO_FAR ) )
			{
				ClearEnemyMemory();
				SetEnemy( NULL );
				m_flGoIdleTime = gpGlobals->curtime + 10;
				return NPC_STATE_ALERT;
			}
		}
	}

	return BaseClass::SelectIdealState();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_RollerMine::BecomePhysical( void )
{
	VPhysicsDestroyObject();

	RemoveSolidFlags( FSOLID_NOT_SOLID );

	//Setup the physics controller on the roller
	IPhysicsObject *pPhysicsObject = VPhysicsInitNormal( SOLID_VPHYSICS, GetSolidFlags() , false );

	if ( pPhysicsObject == NULL )
		return false;

	m_pMotionController = physenv->CreateMotionController( &m_RollerController );
	m_pMotionController->AttachObject( pPhysicsObject, true );

	SetMoveType( MOVETYPE_VPHYSICS );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::OnRestore()
{
	BaseClass::OnRestore();
	if ( m_pMotionController )
	{
		m_pMotionController->SetEventHandler( &m_RollerController );
	}

	// If we're stuck to a vehicle over a level transition, restart our jolt inputs
	if ( GetVehicleStuckTo() )
	{
		if ( !g_EventQueue.HasEventPending( this, "JoltVehicle" ) )
		{
			g_EventQueue.AddEvent( this, "JoltVehicle", RandomFloat(3,6), NULL, NULL );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CNPC_RollerMine::CreateVPhysics()
{
	if ( m_bBuried )
	{
		VPhysicsInitStatic();
		return true;
	}
	else
	{
		return BecomePhysical();
	}
}	

//-----------------------------------------------------------------------------
// Purpose:
// Input  :
// Output :
//-----------------------------------------------------------------------------
int CNPC_RollerMine::RangeAttack1Conditions( float flDot, float flDist )
{
	if( HasCondition( COND_SEE_ENEMY ) == false )
		return COND_NONE;

	if ( EnemyInVehicle() )
		return COND_CAN_RANGE_ATTACK1;

	if( flDist > ROLLERMINE_MAX_ATTACK_DIST  )
		return COND_TOO_FAR_TO_ATTACK;
	
	if (flDist < ROLLERMINE_MIN_ATTACK_DIST )
		return COND_TOO_CLOSE_TO_ATTACK;

	return COND_CAN_RANGE_ATTACK1;
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  :
// Output :
//-----------------------------------------------------------------------------
int CNPC_RollerMine::SelectSchedule( void )
{
	if ( m_bBuried )
	{
		if ( HasCondition(COND_NEW_ENEMY) || HasCondition(COND_LIGHT_DAMAGE) )
			return SCHED_ROLLERMINE_BURIED_UNBURROW;

		return SCHED_ROLLERMINE_BURIED_WAIT;
	}

	//If we're held, don't try and do anything
	if ( ( m_bHeld ) || !IsActive() || m_hVehicleStuckTo )
		return SCHED_ALERT_STAND;

	// If we can see something we're afraid of, run from it
	if ( HasCondition( COND_SEE_FEAR ) )
		return SCHED_ROLLERMINE_FLEE;

	switch( m_NPCState )
	{
	case NPC_STATE_COMBAT:

		if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
			return SCHED_ROLLERMINE_RANGE_ATTACK1;
		
		return SCHED_ROLLERMINE_CHASE_ENEMY;
		break;

	default:
		break;
	}

	// Rollermines never wait to fall to the ground
	ClearCondition( COND_FLOATING_OFF_GROUND );

	return BaseClass::SelectSchedule();
}

#if 0
#define	ROLLERMINE_DETECTION_RADIUS		350
//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_RollerMine::DetectedEnemyInProximity( void )
{
	CBaseEntity *pEnt = NULL;
	CBaseEntity *pBestEnemy = NULL;
	float		flBestDist = MAX_TRACE_LENGTH;

	while ( ( pEnt = gEntList.FindEntityInSphere( pEnt, GetAbsOrigin(), ROLLERMINE_DETECTION_RADIUS ) ) != NULL )
	{
		if ( IRelationType( pEnt ) != D_HT )
			continue;

		float distance = ( pEnt->GetAbsOrigin() - GetAbsOrigin() ).Length();
		
		if ( distance >= flBestDist )
			continue;

		pBestEnemy = pEnt;
		flBestDist = distance;
	}

	if ( pBestEnemy != NULL )
	{
		SetEnemy( pBestEnemy );
		return true;
	}

	return false;
}
#endif

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pSightEnt - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_RollerMine::QuerySeeEntity(CBaseEntity *pSightEnt)
{
	if ( IRelationType( pSightEnt ) == D_FR )
	{
		// Only see feared objects up close
		float flDist = (WorldSpaceCenter() - pSightEnt->WorldSpaceCenter()).LengthSqr();
		if ( flDist > ROLLERMINE_FEAR_DISTANCE )
			return false;
	}

	return BaseClass::QuerySeeEntity(pSightEnt);
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  :
// Output :
//-----------------------------------------------------------------------------
void CNPC_RollerMine::StartTask( const Task_t *pTask )
{
	switch( pTask->iTask )
	{
	case TASK_ROLLERMINE_UNBURROW:
		
		{
			AddSolidFlags( FSOLID_NOT_SOLID );
			SetMoveType( MOVETYPE_NOCLIP );
			SetAbsVelocity( Vector( 0, 0, 256 ) );
			Open();

			trace_t	tr;
			AI_TraceLine( GetAbsOrigin()+Vector(0,0,1), GetAbsOrigin()-Vector(0,0,64), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );

			if ( tr.fraction < 1.0f )
			{
				UTIL_CreateAntlionDust( tr.endpos + Vector(0,0,24), GetLocalAngles() );		
			}
		}

		return;
		break;

	case TASK_ROLLERMINE_BURIED_WAIT:
		if ( HasCondition( COND_SEE_ENEMY ) )
		{
			TaskComplete();
		}
		break;

	case TASK_STOP_MOVING:

		//Stop turning
		m_RollerController.m_vecAngular = vec3_origin;
		
		TaskComplete();
		return;
		break;

	case TASK_WALK_PATH:
	case TASK_RUN_PATH:
		{
			IPhysicsObject *pPhysicsObject = VPhysicsGetObject();

			if ( pPhysicsObject == NULL )
			{
				assert(0);
				TaskFail("Roller lost internal physics object?");
				return;
			}

			pPhysicsObject->Wake();
		}
		break;

	case TASK_ROLLERMINE_CHARGE_ENEMY:
		{
			IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
			
			if ( pPhysicsObject == NULL )
			{
				assert(0);
				TaskFail("Roller lost internal physics object?");
				return;
			}
			
			pPhysicsObject->Wake();

			m_flChargeTime = gpGlobals->curtime;
		}

		break;

	case TASK_ROLLERMINE_GET_PATH_TO_FLEE:
		{
			// Find the nearest thing we're afraid of, and move away from it.
			float flNearest = ROLLERMINE_FEAR_DISTANCE;
			EHANDLE hNearestEnemy = NULL;
			AIEnemiesIter_t iter;
			for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst( &iter ); pEMemory != NULL; pEMemory = GetEnemies()->GetNext( &iter ) )
			{
				CBaseEntity *pEnemy = pEMemory->hEnemy;
				if ( !pEnemy || !pEnemy->IsAlive() )
					continue;
				if ( IRelationType( pEnemy ) != D_FR )
					continue;			

				float flDist = (WorldSpaceCenter() - pEnemy->WorldSpaceCenter()).LengthSqr();
				if ( flDist < flNearest )
				{
					flNearest = flDist;
					hNearestEnemy = pEnemy;
				}
			}

			if ( !hNearestEnemy )
			{
				TaskFail("Couldn't find nearest feared object.");
				break;
			}

			GetMotor()->SetIdealYawToTarget( hNearestEnemy->WorldSpaceCenter() );
			ChainStartTask( TASK_MOVE_AWAY_PATH, pTask->flTaskData );
		}
		break;

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


//-----------------------------------------------------------------------------
// Purpose:
// Input  :
// Output :
//-----------------------------------------------------------------------------
void CNPC_RollerMine::RunTask( const Task_t *pTask )
{
	switch( pTask->iTask )
	{
	case TASK_ROLLERMINE_UNBURROW:
		{	
			Vector	vCenter = WorldSpaceCenter();

			// Robin: HACK: Bloat the rollermine check to catch the model switch (roller.mdl->roller_spikes.mdl)
			trace_t	tr;
			AI_TraceHull( vCenter, vCenter, Vector(-16,-16,-16), Vector(16,16,16), MASK_NPCSOLID, this, GetCollisionGroup(), &tr );

			if ( tr.fraction == 1 && tr.allsolid != 1 && (tr.startsolid != 1) )
			{
				if ( BecomePhysical() )
				{
					Hop( 256 );
					m_bBuried = false;
					TaskComplete();
					SetIdealState( NPC_STATE_ALERT );
				}
			}
		}

		return;
		break;

	case TASK_ROLLERMINE_BURIED_WAIT:
		if ( HasCondition( COND_SEE_ENEMY ) || HasCondition( COND_LIGHT_DAMAGE ) )
		{
			TaskComplete();
		}
		break;

	case TASK_ROLLERMINE_GET_PATH_TO_FLEE:
		{
			ChainRunTask( TASK_MOVE_AWAY_PATH, pTask->flTaskData );
		}
		break;

	case TASK_RUN_PATH:
	case TASK_WALK_PATH:

		if ( m_bHeld || m_hVehicleStuckTo )
		{
			TaskFail( "Player interrupted by grabbing" );
			break;
		}

		// If we were fleeing, but we've lost sight of the thing scaring us, stop
		if ( IsCurSchedule(SCHED_ROLLERMINE_FLEE) && !HasCondition( COND_SEE_FEAR ) )
		{
			TaskComplete();
			break;
		}

		if ( !GetNavigator()->IsGoalActive() )
		{
			TaskComplete();
			return;
		}

		// Start turning early
		if( (GetLocalOrigin() - GetNavigator()->GetCurWaypointPos() ).Length() <= 64 )
		{
			if( GetNavigator()->CurWaypointIsGoal() )
			{
				// Hit the brakes a bit.
				float yaw = UTIL_VecToYaw( GetNavigator()->GetCurWaypointPos() - GetLocalOrigin() );
				Vector vecRight;
				AngleVectors( QAngle( 0, yaw, 0 ), NULL, &vecRight, NULL );

				m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, -m_flForwardSpeed * 5 );

				TaskComplete();
				return;
			}

			GetNavigator()->AdvancePath();	
		}

		{
			float yaw = UTIL_VecToYaw( GetNavigator()->GetCurWaypointPos() - GetLocalOrigin() );

			Vector vecRight;
			Vector vecToPath; // points at the path
			AngleVectors( QAngle( 0, yaw, 0 ), &vecToPath, &vecRight, NULL );

			// figure out if the roller is turning. If so, cut the throttle a little.
			float flDot;
			Vector vecVelocity;

			IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
			
			if ( pPhysicsObject == NULL )
			{
				assert(0);
				TaskFail("Roller lost internal physics object?");
				return;
			}
			
			pPhysicsObject->GetVelocity( &vecVelocity, NULL );

			VectorNormalize( vecVelocity );

			vecVelocity.z = 0;

			flDot = DotProduct( vecVelocity, vecToPath );

			m_RollerController.m_vecAngular = vec3_origin;

			if( flDot > 0.25 && flDot < 0.7 )
			{
				// Feed a little torque backwards into the axis perpendicular to the velocity.
				// This will help get rid of momentum that would otherwise make us overshoot our goal.
				Vector vecCompensate;

				vecCompensate.x = vecVelocity.y;
				vecCompensate.y = -vecVelocity.x;
				vecCompensate.z = 0;

				m_RollerController.m_vecAngular = WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecCompensate, m_flForwardSpeed * -0.75 );
			}

			m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, m_flForwardSpeed );
		}
		break;

	case TASK_ROLLERMINE_CHARGE_ENEMY:
		{
			if ( !GetEnemy() )
			{
				TaskFail( FAIL_NO_ENEMY );
				break;
			}

			if ( m_bHeld || m_hVehicleStuckTo )
			{
				TaskComplete();
				break;
			}

			CBaseEntity *pEnemy = GetEnemy();
			Vector vecTargetPosition = pEnemy->GetAbsOrigin();

			// If we're chasing a vehicle, try and get ahead of it
			if ( EnemyInVehicle() )
			{
				CBasePlayer *pPlayer = ToBasePlayer( pEnemy );
				float flT;

				// Project it's velocity and find our closest point on that line. Do it all in 2d space.
				Vector vecVehicleVelocity = pPlayer->GetVehicle()->GetVehicleEnt()->GetSmoothedVelocity();
				Vector vecProjected = vecTargetPosition + (vecVehicleVelocity * 1.0);
				Vector2D vecProjected2D( vecProjected.x, vecProjected.y );
				Vector2D vecTargetPosition2D( vecTargetPosition.x, vecTargetPosition.y );
				Vector2D vecOrigin2D( GetAbsOrigin().x, GetAbsOrigin().y );
				Vector2D vecIntercept2D;

				CalcClosestPointOnLine2D( vecOrigin2D, vecTargetPosition2D, vecProjected2D, vecIntercept2D, &flT );
				Vector vecIntercept( vecIntercept2D.x, vecIntercept2D.y, GetAbsOrigin().z );
				
				//NDebugOverlay::Line( vecTargetPosition, vecProjected, 0,255,0, true, 0.1 );

				// If we're ahead of the line somewhere, try to intercept
				if ( flT > 0 )
				{
					// If it's beyond the end of the intercept line, just move towards the end of the line
					if ( flT > 1 )
					{
						vecIntercept.x = vecProjected.x;
						vecIntercept.y = vecProjected.y;
					}

					// If we're closer to the intercept point than to the vehicle, move towards the intercept
					if ( (GetAbsOrigin() - vecTargetPosition).LengthSqr() > (GetAbsOrigin() - vecIntercept).LengthSqr() )
					{
						//NDebugOverlay::Box( vecIntercept, -Vector(20,20,20), Vector(20,20,20), 255,0,0, 0.1, 0.1 );

						// Only use this position if it's clear
						if ( enginetrace->GetPointContents( vecIntercept ) != CONTENTS_SOLID )
						{
							vecTargetPosition = vecIntercept;
						}
					}
				}

				//NDebugOverlay::Box( vecTargetPosition, -Vector(20,20,20), Vector(20,20,20), 255,255,255, 0.1, 0.1 );
			}

			float flTorqueFactor;
			Vector vecToTarget = vecTargetPosition - GetLocalOrigin();
			float yaw = UTIL_VecToYaw( vecToTarget );
			Vector vecRight;

			AngleVectors( QAngle( 0, yaw, 0 ), NULL, &vecRight, NULL );

			//NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin() + (GetEnemy()->GetLocalOrigin() - GetLocalOrigin()), 0,255,0, true, 0.1 );

			float flDot;

			// Figure out whether to continue the charge.
			// (Have I overrun the target?)			
			IPhysicsObject *pPhysicsObject = VPhysicsGetObject();

			if ( pPhysicsObject == NULL )
			{
//				Assert(0);
				TaskFail("Roller lost internal physics object?");
				return;
			}

			Vector vecVelocity;
			pPhysicsObject->GetVelocity( &vecVelocity, NULL );
			VectorNormalize( vecVelocity );

			VectorNormalize( vecToTarget );

			flDot = DotProduct( vecVelocity, vecToTarget );

			// more torque the longer the roller has been going.
			flTorqueFactor = 1 + (gpGlobals->curtime - m_flChargeTime) * 2;

			float flMaxTorque = ROLLERMINE_MAX_TORQUE_FACTOR;
			
			// Friendly rollermines go a little slower
			if ( HasSpawnFlags( SF_ROLLERMINE_FRIENDLY ) )
			{
				flMaxTorque *= 0.75;
			}

			if( flTorqueFactor < 1 )
			{
				flTorqueFactor = 1;
			}
			else if( flTorqueFactor > flMaxTorque)
			{
				flTorqueFactor = flMaxTorque;
			}

			Vector vecCompensate;

			vecCompensate.x = vecVelocity.y;
			vecCompensate.y = -vecVelocity.x;
			vecCompensate.z = 0;
			VectorNormalize( vecCompensate );

			m_RollerController.m_vecAngular = WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecCompensate, m_flForwardSpeed * -0.75 );
			m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, m_flForwardSpeed  * flTorqueFactor );
		
			// Taunt when I get closer
			if( !(m_iSoundEventFlags & ROLLERMINE_SE_TAUNT) && UTIL_DistApprox( GetLocalOrigin(), vecTargetPosition ) <= 400 )
			{
				m_iSoundEventFlags |= ROLLERMINE_SE_TAUNT; // Don't repeat.

				EmitSound( "NPC_RollerMine.Taunt" );
			}

			// Jump earlier when chasing a vehicle
			float flThreshold = ROLLERMINE_OPEN_THRESHOLD;
			if ( EnemyInVehicle() )
			{
				flThreshold = ROLLERMINE_VEHICLE_OPEN_THRESHOLD;
			}

			// Open the spikes if i'm close enough to cut the enemy!!
  			if( ( m_bIsOpen == false ) && ( ( UTIL_DistApprox( GetAbsOrigin(), GetEnemy()->GetAbsOrigin() ) <= flThreshold ) || !IsActive() ) )
			{
				Open();
			}
			else if ( m_bIsOpen )
			{
				float flDistance = UTIL_DistApprox( GetAbsOrigin(), GetEnemy()->GetAbsOrigin() );
				if ( flDistance >= flThreshold )
				{
					// Otherwise close them if the enemy is getting away!
					Close();
				}
				else if ( EnemyInVehicle() && flDistance < ROLLERMINE_VEHICLE_HOP_THRESHOLD )
				{
					// Keep trying to hop when we're ramming a vehicle, so we're visible to the player
					if ( vecVelocity.x != 0 && vecVelocity.y != 0 && flTorqueFactor > 3 && flDot > 0.0 )
					{
						Hop( 300 );
					}
				}
			}

			// If we drive past, close the blades and make a new plan.
			if ( !EnemyInVehicle() )
			{
				if( vecVelocity.x != 0 && vecVelocity.y != 0 )
				{
					if( gpGlobals->curtime - m_flChargeTime > 1.0 && flTorqueFactor > 1 &&  flDot < 0.0 )
					{
						if( m_bIsOpen )
						{
							Close();
						}

						TaskComplete();
					}
				}
			}
		}
		break;

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

//-----------------------------------------------------------------------------
// Purpose:
// Input  :
// Output :
//-----------------------------------------------------------------------------
void CNPC_RollerMine::Open( void )
{
	// Friendly rollers cannot open
	if ( HasSpawnFlags( SF_ROLLERMINE_FRIENDLY ) )
		return;

	if ( m_bIsOpen == false )
	{
		SetModel( "models/roller_spikes.mdl" );
		EmitSound( "NPC_RollerMine.OpenSpikes" );

		SetTouch( &CNPC_RollerMine::ShockTouch );
		m_bIsOpen = true;

		// Don't hop if we're constrained
		if ( !m_pConstraint )
		{
			if ( EnemyInVehicle() )
			{
				Hop( 256 );
			}
			else
			{
				Hop( 128 );
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose:
// Input  :
// Output :
//-----------------------------------------------------------------------------
void CNPC_RollerMine::Close( void )
{
	// Not allowed to close while primed, because we're going to detonate on touch
	if ( m_bIsPrimed )
		return;

	if ( m_bIsOpen && !IsShocking() )
	{
		SetModel( "models/roller.mdl" );

		SetTouch( NULL );
		m_bIsOpen = false;

		m_iSoundEventFlags = ROLLERMINE_SE_CLEAR;
	}
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  :
// Output :
//-----------------------------------------------------------------------------
void CNPC_RollerMine::SpikeTouch( CBaseEntity *pOther )
{
	/*
	if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS) )
		return;

	if ( m_bHeld )
		return;

	if ( pOther->IsPlayer() )
		return;

	if ( pOther->m_takedamage != DAMAGE_YES )
		return;

	// If we just hit a breakable glass object, don't explode. We want to blow through it.
	CBreakable *pBreakable = dynamic_cast<CBreakable*>(pOther);
	if ( pBreakable && pBreakable->GetMaterialType() == matGlass )
		return;

	Explode();
	EmitSound( "NPC_RollerMine.Warn" );
	*/

	//FIXME: Either explode within certain rules, never explode, or just shock the hit victim
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::CloseTouch( CBaseEntity *pOther )
{
	if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS) )
		return;

	if ( IsShocking() )
		return;

	bool bOtherIsDead = ( pOther->MyNPCPointer() && !pOther->MyNPCPointer()->IsAlive() );
	bool bOtherIsNotarget = ( ( pOther->GetFlags() & FL_NOTARGET ) != 0 );

	if ( !bOtherIsDead && !bOtherIsNotarget )
	{
		Disposition_t disp = IRelationType(pOther);

		if ( (disp == D_HT || disp == D_FR) )
		{
			ShockTouch( pOther );
			return;
		}
	}

	Close();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::EmbedTouch( CBaseEntity *pOther )
{
	if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS) )
		return;

	m_bEmbedOnGroundImpact = false;

	// Did we hit the world?
	if ( pOther->entindex() == 0 )
	{
		m_bBuried = true;
		trace_t tr;
		Bury( &tr );

		// Destroy out physics object and become static
		VPhysicsDestroyObject();
		CreateVPhysics();

		// Drop a decal on the ground where we impacted
		UTIL_DecalTrace( &tr, "Rollermine.Crater" );

		// Make some dust
		UTIL_CreateAntlionDust( tr.endpos, GetLocalAngles() );
	}

	// Don't try and embed again
	SetTouch( NULL );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CNPC_RollerMine::IsPlayerVehicle( CBaseEntity *pEntity )
{
	IServerVehicle *pVehicle = pEntity->GetServerVehicle();
	if ( pVehicle )
	{
		CBasePlayer *pPlayer = pVehicle->GetPassenger();
		if ( pPlayer )
		{
			Disposition_t disp = IRelationType(pPlayer);

			if ( disp == D_HT || disp == D_FR )
				return true;
		}
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pVictim - 
// Output : float
//-----------------------------------------------------------------------------
float CNPC_RollerMine::GetAttackDamageScale( CBaseEntity *pVictim )
{
	// If we're friendly, don't damage players or player-friendly NPCs, even with collisions
	if ( HasSpawnFlags( SF_ROLLERMINE_FRIENDLY ) )
	{
		if ( pVictim->IsPlayer() )
			return 0;
		
		if ( pVictim->MyNPCPointer() )
		{
			// If we don't hate the player, we're immune
			CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);
			if ( pPlayer && pVictim->MyNPCPointer()->IRelationType( pPlayer ) != D_HT )
				return 0.0;
		}
	}

	return BaseClass::GetAttackDamageScale( pVictim );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pOther - 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::ShockTarget( CBaseEntity *pOther )
{
	CBeam *pBeam = CBeam::BeamCreate( "sprites/rollermine_shock.vmt", 4 );

	int startAttach = -1;

	CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>(pOther);

	if ( pBeam != NULL )
	{
		pBeam->EntsInit( pOther, this );

		if ( pAnimating )
		{
			startAttach = pAnimating->LookupAttachment("beam_damage" );
			pBeam->SetStartAttachment( startAttach );
		}

		// Change this up a little for first person hits
		if ( pOther->IsPlayer() )
		{
			pBeam->SetEndWidth( 8 );
			pBeam->SetNoise( 4 );
			pBeam->LiveForTime( 0.2f );
		}
		else
		{
			pBeam->SetEndWidth( 16 );
			pBeam->SetNoise( 16 );
			pBeam->LiveForTime( 0.5f );
		}
		
		pBeam->SetEndAttachment( 1 );
		pBeam->SetWidth( 1 );
		pBeam->SetBrightness( 255 );
		pBeam->SetColor( 255, 255, 255 );
		pBeam->RelinkBeam();
	}
	
	Vector shockPos = pOther->WorldSpaceCenter();

	if ( startAttach > 0 && pAnimating )
	{
		QAngle foo;
		pAnimating->GetAttachment( startAttach, shockPos, foo );
	}

	Vector shockDir = ( GetAbsOrigin() - shockPos );
	VectorNormalize( shockDir );

	CPVSFilter filter( shockPos );
	te->GaussExplosion( filter, 0.0f, shockPos, shockDir, 0 );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::ShockTouch( CBaseEntity *pOther )
{
	if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS) )
		return;

	if ( m_bHeld || m_hVehicleStuckTo || gpGlobals->curtime < m_flShockTime )
		return;

	// error?
	Assert( !m_bIsPrimed );

	Disposition_t disp = IRelationType(pOther);
	if ( ( pOther->m_iClassname == m_iClassname ) || (disp != D_HT && disp != D_FR) )
		return;

	IPhysicsObject *pPhysics = VPhysicsGetObject();

	// Calculate a collision force
	Vector impulse = WorldSpaceCenter() - pOther->WorldSpaceCenter();
	impulse.z = 0;
	VectorNormalize( impulse );
	impulse.z = 0.75;
	VectorNormalize( impulse );
	impulse *= 600;

	// Stun the roller
	m_flActiveTime = gpGlobals->curtime + sk_rollermine_stun_delay.GetFloat();

	// If we're a 'friendly' rollermine, just push the player a bit
	if ( HasSpawnFlags( SF_ROLLERMINE_FRIENDLY ) )
	{
		if ( pOther->IsPlayer() )
		{
			Vector vecForce = -impulse * 0.5;
			pOther->ApplyAbsVelocityImpulse( vecForce );
		}
		return;
	}

	// jump up at a 30 degree angle away from the guy we hit
	SetTouch( &CNPC_RollerMine::CloseTouch );
	Vector vel;
	pPhysics->SetVelocity( &impulse, NULL );
	EmitSound( "NPC_RollerMine.Shock" );
	m_flShockTime = gpGlobals->curtime + 1.25;

	// Calculate physics force
	Vector out;
	pOther->CollisionProp()->CalcNearestPoint( WorldSpaceCenter(), &out );

	Vector vecForce = ( -impulse * pPhysics->GetMass() * 10 );
	CTakeDamageInfo	info( this, this, vecForce, out, sk_rollermine_shock.GetFloat(), DMG_SHOCK );
	pOther->TakeDamage( info );

	// Knock players back a bit
	if ( pOther->IsPlayer() )
	{
		vecForce = -impulse;
		pOther->ApplyAbsVelocityImpulse( vecForce );
	}

	// Do a shock effect
	ShockTarget( pOther );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
{
	// Make sure we don't keep hitting the same entity
	int otherIndex = !index;
	CBaseEntity *pOther = pEvent->pEntities[otherIndex];
	if ( pEvent->deltaCollisionTime < 0.5 && (pOther == this) )
		return;

	BaseClass::VPhysicsCollision( index, pEvent );

	// If we've just hit a vehicle, we want to stick to it
	if ( m_bHeld || m_hVehicleStuckTo || !IsPlayerVehicle( pOther ) )
	{
		// Are we supposed to be embedding ourselves?
		if ( m_bEmbedOnGroundImpact )
		{
			EmbedTouch( pOther );
		}
		return;
	}

	StickToVehicle( pOther );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pOther - 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::StickToVehicle( CBaseEntity *pOther )
{
	IPhysicsObject *pOtherPhysics = pOther->VPhysicsGetObject();
	if ( !pOtherPhysics )
		return;

	// Don't stick to the wheels
	if ( pOtherPhysics->GetCallbackFlags() & CALLBACK_IS_VEHICLE_WHEEL )
		return;

	// Destroy our constraint. This can happen if we had our constraint broken
	// and we still haven't cleaned up our constraint.
	UnstickFromVehicle();

	// We've hit the vehicle that the player's in.
	// Stick to it and slow it down.
	m_hVehicleStuckTo = pOther;

	IPhysicsObject *pPhysics = VPhysicsGetObject();

	// Constrain us to the vehicle
	constraint_fixedparams_t fixed;
	fixed.Defaults();
	fixed.InitWithCurrentObjectState( pOtherPhysics, pPhysics );
	fixed.constraint.Defaults();
	fixed.constraint.forceLimit	= ImpulseScale( pPhysics->GetMass(), 200 );
	fixed.constraint.torqueLimit = ImpulseScale( pPhysics->GetMass(), 800 );
	m_pConstraint = physenv->CreateFixedConstraint( pOtherPhysics, pPhysics, NULL, fixed );
	m_pConstraint->SetGameData( (void *)this );

	// Kick the vehicle so the player knows we've arrived
	Vector impulse = pOther->GetAbsOrigin() - GetAbsOrigin();
	VectorNormalize( impulse );
	impulse.z = -0.75;
	VectorNormalize( impulse );
	impulse *= 600;
	Vector vecForce = impulse * pPhysics->GetMass() * 10;
	pOtherPhysics->ApplyForceOffset( vecForce, GetAbsOrigin() );

	// Get the velocity at the point we're sticking to
	Vector vecVelocity;
	pOtherPhysics->GetVelocityAtPoint( GetAbsOrigin(), vecVelocity );
	AngularImpulse angNone( 0.0f, 0.0f, 0.0f );
	pPhysics->SetVelocity( &vecVelocity, &angNone );

	// Make sure we're spiky
	Open();

	AnnounceArrivalToOthers( pOther );

	// Also, jolt the vehicle sometime in the future
	g_EventQueue.AddEvent( this, "JoltVehicle", RandomFloat(3,6), NULL, NULL );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CNPC_RollerMine::CountRollersOnMyVehicle( CUtlVector<CNPC_RollerMine*> *pRollerList )
{
	CBaseEntity *entityList[64];
	Vector range(256,256,256);
	pRollerList->AddToTail( this );
	int boxCount = UTIL_EntitiesInBox( entityList, ARRAYSIZE(entityList), GetAbsOrigin()-range, GetAbsOrigin()+range, FL_NPC );
	for ( int i = 0; i < boxCount; i++ )
	{
		CAI_BaseNPC *pNPC = entityList[i]->MyNPCPointer();
		if ( pNPC && pNPC->m_iClassname == m_iClassname && pNPC != this )
		{
			// Found another rollermine
			CNPC_RollerMine *pMine = dynamic_cast<CNPC_RollerMine*>(pNPC);
			Assert( pMine );

			// Is he stuck to the same vehicle?
			if ( pMine->GetVehicleStuckTo() == GetVehicleStuckTo() )
			{
				pRollerList->AddToTail( pMine );
			}
		}
	}

	return pRollerList->Count();
}

//-----------------------------------------------------------------------------
// Purpose: Tell other rollermines on the vehicle that I've just arrived
// Input  : *pOther - 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::AnnounceArrivalToOthers( CBaseEntity *pOther )
{
	// Now talk to any other rollermines stuck to the same vehicle
	CUtlVector<CNPC_RollerMine*> aRollersOnVehicle;
	int iRollers = CountRollersOnMyVehicle( &aRollersOnVehicle );

	// Stop all rollers on the vehicle falling off due to the force of the arriving one
	for ( int i = 0; i < iRollers; i++ )
	{
		aRollersOnVehicle[i]->PreventUnstickUntil( gpGlobals->curtime + 1 );
	}

	// See if we've got enough rollers on the vehicle to start being mean
	/*
	if ( iRollers >= ROLLERMINE_REQUIRED_TO_EXPLODE_VEHICLE )
	{
		// Alert the others
		EmitSound( "NPC_RollerMine.ExplodeChirp" );

		// Tell everyone to explode shortly
		for ( int i = 0; i < iRollers; i++ )
		{
			variant_t emptyVariant;
			g_EventQueue.AddEvent( aRollersOnVehicle[i], "RespondToExplodeChirp", RandomFloat(2,5), NULL, NULL );
		}
	}
	else 
	{
	*/
	// If there's other rollers on the vehicle, talk to them
	if ( iRollers > 1 )
	{
		// Chirp to the others
		EmitSound( "NPC_RollerMine.Chirp" );

		// Tell the others to respond (skip first slot, because that's me)
		for ( int i = 1; i < iRollers; i++ )
		{
			variant_t emptyVariant;
			g_EventQueue.AddEvent( aRollersOnVehicle[i], "RespondToChirp", RandomFloat(2,3), NULL, NULL );
		}
	}
//	}
}

//-----------------------------------------------------------------------------
// Purpose: Physics system has just told us our constraint has been broken
//-----------------------------------------------------------------------------
void CNPC_RollerMine::InputConstraintBroken( inputdata_t &inputdata )
{
	// Prevent rollermines being dislodged right as they stick
	if ( m_flPreventUnstickUntil > gpGlobals->curtime )
		return;

	// We can't delete it here safely
	UnstickFromVehicle();
	Close();

	// dazed
	m_RollerController.m_vecAngular.Init();
	m_flActiveTime = gpGlobals->curtime + sk_rollermine_stun_delay.GetFloat();
}

//-----------------------------------------------------------------------------
// Purpose: Respond to another rollermine that's chirped at us
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::InputRespondToChirp( inputdata_t &inputdata )
{
	EmitSound( "NPC_RollerMine.ChirpRespond" );
}

//-----------------------------------------------------------------------------
// Purpose: Respond to another rollermine's signal to detonate
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::InputRespondToExplodeChirp( inputdata_t &inputdata )
{
	EmitSound( "NPC_RollerMine.ExplodeChirpRespond" );

	Explode();
}

//-----------------------------------------------------------------------------
// Purpose: Apply a physics force to the vehicle we're in
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::InputJoltVehicle( inputdata_t &inputdata )
{
	Assert( GetVehicleStuckTo() );

	// First, tell all rollers on the vehicle not to fall off
	CUtlVector<CNPC_RollerMine*> aRollersOnVehicle;
	int iRollers = CountRollersOnMyVehicle( &aRollersOnVehicle );
	for ( int i = 0; i < iRollers; i++ )
	{
		aRollersOnVehicle[i]->PreventUnstickUntil( gpGlobals->curtime + 1 );
	}

	// Now smack the vehicle
	Vector impulse = GetVehicleStuckTo()->GetAbsOrigin() - GetAbsOrigin();
	VectorNormalize( impulse );
	// Randomly apply a little vertical lift, to get the wheels off the ground
	impulse.z = RandomFloat( 0.5, 1.0 );
	VectorNormalize( impulse );
	IPhysicsObject *pVehiclePhysics = GetVehicleStuckTo()->VPhysicsGetObject();
	Vector vecForce = impulse * ImpulseScale( pVehiclePhysics->GetMass(), RandomFloat(150,250) );
	pVehiclePhysics->ApplyForceOffset( vecForce, GetAbsOrigin() );

	// Play sounds & effects
	EmitSound( "NPC_RollerMine.JoltVehicle" );

	// UNDONE: Good Zap effects
	/*
	CBeam *pBeam = CBeam::BeamCreate( "sprites/rollermine_shock.vmt", 4 );
	if ( pBeam )
	{
		pBeam->EntsInit( GetVehicleStuckTo(), this );
		CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>( GetVehicleStuckTo() );
		if ( pAnimating )
		{
			int startAttach = pAnimating->LookupAttachment("beam_damage" );
			pBeam->SetStartAttachment( startAttach );
		}
		pBeam->SetEndAttachment( 1 );
		pBeam->SetWidth( 8 );
		pBeam->SetEndWidth( 8 );
		pBeam->SetBrightness( 255 );
		pBeam->SetColor( 255, 255, 255 );
		pBeam->LiveForTime( 0.5f );
		pBeam->RelinkBeam();
		pBeam->SetNoise( 30 );
	}
	*/

	ShockTarget( GetVehicleStuckTo() );

	// Jolt again soon
	g_EventQueue.AddEvent( this, "JoltVehicle", RandomFloat(3,6), NULL, NULL );
}

//-----------------------------------------------------------------------------
// Purpose: If we were stuck to a vehicle, remove ourselves
//-----------------------------------------------------------------------------
void CNPC_RollerMine::UnstickFromVehicle( void )
{
	if ( m_pConstraint )
	{
		physenv->DestroyConstraint( m_pConstraint );
		m_pConstraint = NULL;
	}

	// Cancel any pending jolt events
	g_EventQueue.CancelEventOn( this, "JoltVehicle" );

	m_hVehicleStuckTo = NULL;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CBaseEntity *CNPC_RollerMine::GetVehicleStuckTo( void )
{
	if ( !m_pConstraint )
		return NULL;

	return m_hVehicleStuckTo;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pPhysGunUser - 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
{
	// Are we just being punted?
	if ( reason == PUNTED_BY_CANNON )
	{
		// Be stunned
		m_flActiveTime = gpGlobals->curtime + sk_rollermine_stun_delay.GetFloat();
		return;
	}

	//Stop turning
	m_RollerController.m_vecAngular = vec3_origin;

	UnstickFromVehicle();

	m_OnPhysGunPickup.FireOutput( pPhysGunUser, this );
	m_bHeld = true;
	m_RollerController.Off();
	EmitSound( "NPC_RollerMine.Held" );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pPhysGunUser - 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
{
	m_bHeld = false;
	m_flActiveTime = gpGlobals->curtime + sk_rollermine_stun_delay.GetFloat();
	m_RollerController.On();
	
	// explode on contact if launched from the physgun
	if ( Reason == LAUNCHED_BY_CANNON )
	{
		if ( m_bIsOpen )
		{
			//m_bIsPrimed = true;
			SetTouch( &CNPC_RollerMine::SpikeTouch );
			// enable world/prop touch too
			VPhysicsGetObject()->SetCallbackFlags( VPhysicsGetObject()->GetCallbackFlags() | CALLBACK_GLOBAL_TOUCH|CALLBACK_GLOBAL_TOUCH_STATIC );
		}
		EmitSound( "NPC_RollerMine.Tossed" );
	}

	m_OnPhysGunDrop.FireOutput( pPhysGunUser, this );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &info - 
// Output : float
//-----------------------------------------------------------------------------
int CNPC_RollerMine::OnTakeDamage( const CTakeDamageInfo &info )
{
	if ( !(info.GetDamageType() & DMG_BURN) )
	{
		if ( GetMoveType() == MOVETYPE_VPHYSICS )
		{
			AngularImpulse	angVel;
			angVel.Random( -400.0f, 400.0f );
			VPhysicsGetObject()->AddVelocity( NULL, &angVel );
			m_RollerController.m_vecAngular *= 0.8f;

			VPhysicsTakeDamage( info );
		}
		SetCondition( COND_LIGHT_DAMAGE );
	}

	if ( info.GetDamageType() & (DMG_BURN|DMG_BLAST) )
	{
		if ( info.GetAttacker() && info.GetAttacker()->m_iClassname != m_iClassname )
		{
			SetThink( &CNPC_RollerMine::PreDetonate );
			SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1f, 0.5f ) );
		}
		else
		{
			// dazed
			m_RollerController.m_vecAngular.Init();
			m_flActiveTime = gpGlobals->curtime + sk_rollermine_stun_delay.GetFloat();
			Hop( 300 );
		}
	}

	return 0;
}

//-----------------------------------------------------------------------------
// Purpose: Causes the roller to hop into the air
//-----------------------------------------------------------------------------
void CNPC_RollerMine::Hop( float height )
{
	if ( m_flNextHop > gpGlobals->curtime )
		return;

	if ( GetMoveType() == MOVETYPE_VPHYSICS )
	{
		IPhysicsObject *pPhysObj = VPhysicsGetObject();
		pPhysObj->ApplyForceCenter( Vector(0,0,1) * height * pPhysObj->GetMass() );
		
		AngularImpulse	angVel;
		angVel.Random( -400.0f, 400.0f );
		pPhysObj->AddVelocity( NULL, &angVel );

		m_flNextHop = gpGlobals->curtime + ROLLERMINE_HOP_DELAY;
	}
}

//-----------------------------------------------------------------------------
// Purpose: Makes warning noise before actual explosion occurs
//-----------------------------------------------------------------------------
void CNPC_RollerMine::PreDetonate( void )
{
	Hop( 300 );

	SetTouch( NULL );
	SetThink( &CNPC_RollerMine::Explode );
	SetNextThink( gpGlobals->curtime + 0.5f );

	EmitSound( "NPC_RollerMine.Hurt" );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::Explode( void )
{
	m_takedamage = DAMAGE_NO;

	//FIXME: Hack to make thrown mines more deadly and fun
	float expDamage = m_bIsPrimed ? 100 : 25;

	// Underwater explosion?
	if ( UTIL_PointContents( GetAbsOrigin() ) & MASK_WATER )
	{
		CEffectData	data;
		data.m_vOrigin = WorldSpaceCenter();
		data.m_flMagnitude = expDamage;
		data.m_flScale = 128;
		data.m_fFlags = ( SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE );
		DispatchEffect( "WaterSurfaceExplosion", data );
	}
	else
	{
		ExplosionCreate( WorldSpaceCenter(), GetLocalAngles(), this, expDamage, 128, true );
	}

	CTakeDamageInfo	info( this, this, 1, DMG_GENERIC );
	Event_Killed( info );

	// Remove myself a frame from now to avoid doing it in the middle of running AI
	SetThink( &CNPC_RollerMine::SUB_Remove );
	SetNextThink( gpGlobals->curtime );
}

const float MAX_ROLLING_SPEED = 720;

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float CNPC_RollerMine::RollingSpeed()
{
	IPhysicsObject *pPhysics = VPhysicsGetObject();
	if ( !m_hVehicleStuckTo && !m_bHeld && pPhysics && !pPhysics->IsAsleep() )
	{
		AngularImpulse angVel;
		pPhysics->GetVelocity( NULL, &angVel );
		float rollingSpeed = angVel.Length() - 90;
		rollingSpeed = clamp( rollingSpeed, 1, MAX_ROLLING_SPEED );
		rollingSpeed *= (1/MAX_ROLLING_SPEED);
		return rollingSpeed;
	}
	return 0;
}

//-----------------------------------------------------------------------------
// Purpose: We've been dropped by a dropship. Embed in the ground if we land on it.
//-----------------------------------------------------------------------------
void CNPC_RollerMine::EmbedOnGroundImpact()
{
	m_bEmbedOnGroundImpact = true;

	SetTouch( &CNPC_RollerMine::EmbedTouch );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::PrescheduleThink()
{
	// Are we underwater?
	if ( UTIL_PointContents( GetAbsOrigin() ) & MASK_WATER )
	{
		// As soon as we're far enough underwater, detonate
		Vector vecAboveMe = GetAbsOrigin() + Vector(0,0,64);
		if ( UTIL_PointContents( vecAboveMe ) & MASK_WATER )
		{
			Explode();
			return;
		}
	}

	UpdateRollingSound();
	UpdatePingSound();
	BaseClass::PrescheduleThink();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::UpdateRollingSound()
{
	if ( m_rollingSoundState == ROLL_SOUND_NOT_READY )
		return;

	rollingsoundstate_t soundState = ROLL_SOUND_OFF;
	float rollingSpeed = RollingSpeed();
	if ( rollingSpeed > 0 )
	{
		soundState = m_bIsOpen ? ROLL_SOUND_OPEN : ROLL_SOUND_CLOSED;
	}


	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
	CSoundParameters params;
	switch( soundState )
	{
	case ROLL_SOUND_CLOSED:
		CBaseEntity::GetParametersForSound( "NPC_RollerMine.Roll", params, NULL );
		break;
	case ROLL_SOUND_OPEN:
		CBaseEntity::GetParametersForSound( "NPC_RollerMine.RollWithSpikes", params, NULL );
		break;

	case ROLL_SOUND_OFF:
		// no sound
		break;
	}

	// start the new sound playing if necessary
	if ( m_rollingSoundState != soundState )
	{
		StopRollingSound();

		m_rollingSoundState = soundState;

		if ( m_rollingSoundState == ROLL_SOUND_OFF )
			return;

		CPASAttenuationFilter filter( this );
		m_pRollSound = controller.SoundCreate( filter, entindex(), params.channel, params.soundname, params.soundlevel );
		controller.Play( m_pRollSound, params.volume, params.pitch );
		m_rollingSoundState = soundState;
	}

	if ( m_pRollSound )
	{
		// for tuning
		//DevMsg("SOUND: %s, VOL: %.1f\n", m_rollingSoundState == ROLL_SOUND_CLOSED ? "CLOSED" : "OPEN ", rollingSpeed );
		controller.SoundChangePitch( m_pRollSound, params.pitchlow + (params.pitchhigh - params.pitchlow) * rollingSpeed, 0.1 );
		controller.SoundChangeVolume( m_pRollSound, params.volume * rollingSpeed, 0.1 );
	}
}


void CNPC_RollerMine::StopRollingSound()
{
	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
	controller.SoundDestroy( m_pRollSound );
	m_pRollSound = NULL;
}

void CNPC_RollerMine::UpdatePingSound()
{
	float pingSpeed = 0;
	if ( m_bIsOpen && !IsShocking() && !m_bHeld )
	{
		CBaseEntity *pEnemy = GetEnemy();
		if ( pEnemy )
		{
			pingSpeed = EnemyDistance( pEnemy );
			pingSpeed = clamp( pingSpeed, 1, ROLLERMINE_OPEN_THRESHOLD );
			pingSpeed *= (1.0f/ROLLERMINE_OPEN_THRESHOLD);
		}
	}

	if ( pingSpeed > 0 )
	{
		pingSpeed = 1-pingSpeed;
		CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
		CSoundParameters params;
		CBaseEntity::GetParametersForSound( "NPC_RollerMine.Ping", params, NULL );
		if ( !m_pPingSound )
		{
			CPASAttenuationFilter filter( this );
			m_pPingSound = controller.SoundCreate( filter, entindex(), params.channel, params.soundname, params.soundlevel );
			controller.Play( m_pPingSound, params.volume, 101 );
		}

		controller.SoundChangePitch( m_pPingSound, params.pitchlow + (params.pitchhigh - params.pitchlow) * pingSpeed, 0.1 );
		controller.SoundChangeVolume( m_pPingSound, params.volume, 0.1 );
		//DevMsg("PING: %.1f\n", pingSpeed );

	}
	else
	{
		StopPingSound();
	}
}


void CNPC_RollerMine::StopPingSound()
{
	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
	controller.SoundDestroy( m_pPingSound );
	m_pPingSound = NULL;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::StopLoopingSounds( void )
{
	StopRollingSound();
	StopPingSound();
	BaseClass::StopLoopingSounds();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pEnemy - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_RollerMine::IsValidEnemy( CBaseEntity *pEnemy )
{
	// If the enemy's over the vehicle detection range, and it's not a player in a vehicle, ignore it
	if ( pEnemy )
	{
		float flDistance = GetAbsOrigin().DistTo( pEnemy->GetAbsOrigin() );
		if ( flDistance >= m_flSeeVehiclesOnlyBeyond )
		{
			if ( pEnemy->IsPlayer() )
			{
				CBasePlayer *pPlayer = ToBasePlayer( pEnemy );
				if ( pPlayer->IsInAVehicle() )
				{
					// If we're buried, we only care when they're heading directly towards us.
					if ( m_bBuried )
						return ( VehicleHeading( pPlayer->GetVehicle()->GetVehicleEnt() ) > DOT_20DEGREE );
						
					// If we're not buried, chase him as long as he's not heading away from us
					return ( VehicleHeading( pPlayer->GetVehicle()->GetVehicleEnt() ) > 0 );
				}
			}

			return false;
		}

		// Never pick something I fear
		if ( IRelationType( pEnemy ) == D_FR )
			return false;
	}

	return BaseClass::IsValidEnemy( pEnemy );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CNPC_RollerMine::EnemyInVehicle( void )
{
	CBaseEntity *pEnemy = GetEnemy();
	if ( pEnemy && pEnemy->IsPlayer() && ((CBasePlayer *)pEnemy)->IsInAVehicle() )
		return ( sk_rollermine_vehicle_intercept.GetInt() > 0 );

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float CNPC_RollerMine::VehicleHeading( CBaseEntity *pVehicle )
{
	Vector vecVelocity = pVehicle->GetSmoothedVelocity();
	float flSpeed = VectorNormalize( vecVelocity );
	Vector vecToMine = GetAbsOrigin() - pVehicle->GetAbsOrigin();
	VectorNormalize( vecToMine );

	// If it's not moving, consider it moving towards us, but not directly
	// This will enable already active rollers to chase the vehicle if it's stationary.
	if ( flSpeed < 10 )
		return 0.1;

	return DotProduct( vecVelocity, vecToMine );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &info - 
//			&vecDir - 
//			*ptr - 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr )
{
	if ( info.GetDamageType() & (DMG_BULLET | DMG_CLUB) )
	{
		CTakeDamageInfo newInfo( info );

		// If we're stuck to the car, increase it even more
		if ( GetVehicleStuckTo() )
		{
			newInfo.SetDamageForce( info.GetDamageForce() * 40 );
		}
		else
		{
			newInfo.SetDamageForce( info.GetDamageForce() * 20 );
		}

		BaseClass::TraceAttack( newInfo, vecDir, ptr );
		return;
	}

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

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

AI_BEGIN_CUSTOM_NPC( npc_rollermine, CNPC_RollerMine )

	//Tasks
	DECLARE_TASK( TASK_ROLLERMINE_CHARGE_ENEMY )
	DECLARE_TASK( TASK_ROLLERMINE_BURIED_WAIT )
	DECLARE_TASK( TASK_ROLLERMINE_UNBURROW )
	DECLARE_TASK( TASK_ROLLERMINE_GET_PATH_TO_FLEE )

	//Schedules
	//==================================================
	// SCHED_ANTLION_CHASE_ENEMY_BURROW
	//==================================================

	DEFINE_SCHEDULE
	(
	SCHED_ROLLERMINE_BURIED_WAIT,

		"	Tasks"
		"		TASK_ROLLERMINE_BURIED_WAIT		0"
		"	"
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_LIGHT_DAMAGE"
	)

	DEFINE_SCHEDULE
	(
	SCHED_ROLLERMINE_BURIED_UNBURROW,

		"	Tasks"
		"		TASK_ROLLERMINE_UNBURROW		0"
		"	"
		"	Interrupts"
	)
	
	DEFINE_SCHEDULE
	(
	SCHED_ROLLERMINE_RANGE_ATTACK1,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_CHASE_ENEMY"
		"		TASK_ROLLERMINE_CHARGE_ENEMY	0"
		"	"
		"	Interrupts"
		"		COND_ENEMY_DEAD"
		"		COND_NEW_ENEMY"
		"		COND_ENEMY_OCCLUDED"
		"		COND_ENEMY_TOO_FAR"
	)
	
	DEFINE_SCHEDULE
	(
	SCHED_ROLLERMINE_CHASE_ENEMY,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_ROLLERMINE_RANGE_ATTACK1"
		"		TASK_SET_TOLERANCE_DISTANCE		24"
		"		TASK_GET_PATH_TO_ENEMY			0"
		"		TASK_RUN_PATH					0"
		"		TASK_WAIT_FOR_MOVEMENT			0"
		"	"
		"	Interrupts"
		"		COND_ENEMY_DEAD"
		"		COND_ENEMY_UNREACHABLE"
		"		COND_ENEMY_TOO_FAR"
		"		COND_CAN_RANGE_ATTACK1"
		"		COND_TASK_FAILED"
		"		COND_SEE_FEAR"
	)

	DEFINE_SCHEDULE
	(
	SCHED_ROLLERMINE_FLEE,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE				SCHEDULE:SCHED_IDLE_STAND"
		"		TASK_ROLLERMINE_GET_PATH_TO_FLEE	300"
		"		TASK_RUN_PATH						0"
		"		TASK_STOP_MOVING					0"
		"	"
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_TASK_FAILED"
	)

AI_END_CUSTOM_NPC()
