//========= 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 "ai_interactions.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"
#include "mapentities.h"
#include "RagdollBoogie.h"
#include "physics_collisionevent.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");

enum
{
	ROLLER_SKIN_REGULAR = 0,
	ROLLER_SKIN_FRIENDLY,
	ROLLER_SKIN_DETONATE,
};
//-----------------------------------------------------------------------------
// 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_RETURN_TO_PLAYER_DIST			(200*200)

#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,
	SCHED_ROLLERMINE_ALERT_STAND,
	SCHED_ROLLERMINE_NUDGE_TOWARDS_NODES,
	SCHED_ROLLERMINE_PATH_TO_PLAYER,
	SCHED_ROLLERMINE_ROLL_TO_PLAYER,
	SCHED_ROLLERMINE_POWERDOWN,
};

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


// 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 CNPCBaseInteractive<CAI_BaseNPC>, public CDefaultPlayerPickupVPhysics
{
	DECLARE_CLASS( CNPC_RollerMine, CNPCBaseInteractive<CAI_BaseNPC> );
	DECLARE_SERVERCLASS();

public:

	CNPC_RollerMine( void ) { m_bTurnedOn = true; m_bUniformSight = false; }
	~CNPC_RollerMine( void );

	void	Spawn( void );
	bool	CreateVPhysics();
	void	RunAI();
	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, bool bOnlyHateOrFearIfNPC = false );

	int		RangeAttack1Conditions ( float flDot, float flDist );
	int		SelectSchedule( void );
	int		TranslateSchedule( int scheduleType );
	int		GetHackedIdleSchedule( 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();
	float	GetStunDelay();
	void	EmbedOnGroundImpact();
	void	UpdateEfficiency( bool bInPVS )	{ SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL ); SetMoveEfficiency( AIME_NORMAL ); }
	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() 
	{ 
		if( !m_bTurnedOn )
			return CLASS_NONE;

		//About to blow up after being hacked so do damage to the player.
		if ( m_bHackedByAlyx && ( m_flPowerDownDetonateTime > 0.0f && m_flPowerDownDetonateTime <= gpGlobals->curtime ) )
			return CLASS_COMBINE;

		return ( m_bHeld || m_bHackedByAlyx ) ? CLASS_HACKED_ROLLERMINE : 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		InputTurnOn( inputdata_t &inputdata );
	void		InputTurnOff( inputdata_t &inputdata );
	void		InputPowerdown( inputdata_t &inputdata );

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

	virtual unsigned int	PhysicsSolidMaskForEntity( void ) const;

	void		SetRollerSkin( void );

	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; }

	// INPCInteractive Functions
	virtual bool	CanInteractWith( CAI_BaseNPC *pUser ) { return true; }
	virtual	bool	HasBeenInteractedWith()	{ return m_bHackedByAlyx; }
	virtual void	NotifyInteraction( CAI_BaseNPC *pUser );

	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;
	CNetworkVar( bool,	m_bHackedByAlyx );

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

	bool	m_bTurnedOn;
	bool	m_bUniformSight;

	CNetworkVar( bool,	m_bPowerDown );
	float	m_flPowerDownTime;
	float	m_flPowerDownDetonateTime;

	static string_t gm_iszDropshipClassname;
};

string_t CNPC_RollerMine::gm_iszDropshipClassname;

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_FIELD( m_bHackedByAlyx, FIELD_BOOLEAN ),

	DEFINE_FIELD( m_bPowerDown,	FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flPowerDownTime,	FIELD_TIME ),
	DEFINE_FIELD( m_flPowerDownDetonateTime,	FIELD_TIME ),

	DEFINE_PHYSPTR( m_pConstraint ),

	DEFINE_FIELD( m_bTurnedOn, FIELD_BOOLEAN ),
	DEFINE_KEYFIELD( m_bUniformSight, FIELD_BOOLEAN, "uniformsightdist" ),

	DEFINE_INPUTFUNC( FIELD_VOID, "ConstraintBroken", InputConstraintBroken ),
	DEFINE_INPUTFUNC( FIELD_VOID, "RespondToChirp", InputRespondToChirp ),
	DEFINE_INPUTFUNC( FIELD_VOID, "RespondToExplodeChirp", InputRespondToExplodeChirp ),
	DEFINE_INPUTFUNC( FIELD_VOID, "JoltVehicle", InputJoltVehicle ),
	DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ),
	DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ),
	DEFINE_INPUTFUNC( FIELD_VOID, "PowerDown", InputPowerdown ),

	// 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" ),

	DEFINE_BASENPCINTERACTABLE_DATADESC(),

END_DATADESC()

IMPLEMENT_SERVERCLASS_ST( CNPC_RollerMine, DT_RollerMine )
	SendPropInt(SENDINFO(m_bIsOpen), 1, SPROP_UNSIGNED ),
	SendPropFloat(SENDINFO(m_flActiveTime), 0, SPROP_NOSCALE ),
	SendPropInt(SENDINFO(m_bHackedByAlyx), 1, SPROP_UNSIGNED ),
	SendPropInt(SENDINFO(m_bPowerDown), 1, SPROP_UNSIGNED ),
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, const char *pszTemplate )
{
	CBaseEntity *pEntity = NULL;
	CNPC_RollerMine *pMine = NULL;

	// Use the template, if we have it
	if ( pszTemplate && pszTemplate[0] )
	{
		MapEntity_ParseEntity( pEntity, pszTemplate, NULL );
		pMine = dynamic_cast<CNPC_RollerMine *>(pEntity);
	}
	else
	{
		pMine = (CNPC_RollerMine*)CreateEntityByName("npc_rollermine");
	}

	if ( pMine )
	{
		pMine->SetAbsOrigin( originStart );
		pMine->SetOwnerEntity( pOwner );
		pMine->Spawn();

		if ( !pszTemplate || !pszTemplate[0] )
		{
			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" );
	PrecacheModel( "sprites/rollermine_shock_yellow.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" );

	PrecacheScriptSound( "NPC_RollerMine.Reprogram" );

	PrecacheMaterial( "effects/rollerglow" );

	gm_iszDropshipClassname = AllocPooledString( "npc_combinedropship" ); // For fast string compares.
#ifdef HL2_EPISODIC
	PrecacheScriptSound( "RagdollBoogie.Zap" );
#endif

	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_bPowerDown = false;
	
	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 );

	if( m_bUniformSight )
	{
		m_flSeeVehiclesOnlyBeyond = ROLLERMINE_IDLE_SEE_DIST;
	}
	else
	{
		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;

	m_flPowerDownDetonateTime = 0.0f;
	m_bPowerDown = false;
	m_flPowerDownTime = 0.0f;

	//Set their yaw speed to 0 so the motor doesn't rotate them.
	GetMotor()->SetYawSpeed( 0.0f );
	SetRollerSkin();
}

//-----------------------------------------------------------------------------
// Set the contents types that are solid by default to this NPC
//-----------------------------------------------------------------------------
unsigned int CNPC_RollerMine::PhysicsSolidMaskForEntity( void ) const 
{ 
	if ( HasSpawnFlags( SF_ROLLERMINE_PROP_COLLISION ) )
		return MASK_SOLID;

	return MASK_NPCSOLID;
}

//-----------------------------------------------------------------------------
// 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;

		if( m_bUniformSight )
		{
			m_flSeeVehiclesOnlyBeyond = ROLLERMINE_IDLE_SEE_DIST;
		}
		else
		{
			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 );

		if( m_bUniformSight )
		{
			m_flSeeVehiclesOnlyBeyond = ROLLERMINE_NORMAL_SEE_DIST;
		}
		else
		{
			m_flSeeVehiclesOnlyBeyond = ROLLERMINE_SEE_VEHICLESONLY_BEYOND_NORMAL;
		}

		m_flDistTooFar = ROLLERMINE_NORMAL_SEE_DIST;
	}
	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();
	}
}	

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_RollerMine::RunAI()
{
	if( m_bTurnedOn )
	{
		// Scare combine if hacked by Alyx.
		IPhysicsObject *pPhysicsObject = VPhysicsGetObject();

		Vector vecVelocity;

		if ( pPhysicsObject != NULL )
		{
			pPhysicsObject->GetVelocity( &vecVelocity, NULL );
		}

		if( !m_bHeld && vecVelocity.Length() > 64.0 )
		{
			if( m_bHackedByAlyx )
			{
				// Scare combine
				CSoundEnt::InsertSound( (SOUND_DANGER | SOUND_CONTEXT_COMBINE_ONLY | SOUND_CONTEXT_REACT_TO_SOURCE | SOUND_CONTEXT_DANGER_APPROACH), WorldSpaceCenter() + Vector( 0, 0, 32 ) + vecVelocity * 0.5f, 120.0f, 0.2f, this, SOUNDENT_CHANNEL_REPEATED_DANGER );
			}
			else
			{
				// Scare player allies
				CSoundEnt::InsertSound( (SOUND_DANGER | SOUND_CONTEXT_EXCLUDE_COMBINE | SOUND_CONTEXT_REACT_TO_SOURCE | SOUND_CONTEXT_DANGER_APPROACH), WorldSpaceCenter() + Vector( 0, 0, 32 ) + vecVelocity * 0.5f, 120.0f, 0.2f, this, SOUNDENT_CHANNEL_REPEATED_DANGER );
			}
		}

		BaseClass::RunAI();
	}
}

//-----------------------------------------------------------------------------
// 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_bPowerDown )
		return SCHED_ROLLERMINE_POWERDOWN;

	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();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CNPC_RollerMine::GetHackedIdleSchedule( void )
{
	// If we've been hacked, return to the player
	if ( !m_bHackedByAlyx || m_bHeld )
		return SCHED_NONE;

	// Are we near the player?
	CBaseEntity *pPlayer = gEntList.FindEntityByName( NULL, "!player" );
	if ( !pPlayer )
		return SCHED_NONE;

	if ( !HasCondition(COND_SEE_PLAYER) )
		return SCHED_ROLLERMINE_PATH_TO_PLAYER;

	if ( GetAbsOrigin().DistToSqr( pPlayer->GetAbsOrigin() ) > ROLLERMINE_RETURN_TO_PLAYER_DIST )
		return SCHED_ROLLERMINE_ROLL_TO_PLAYER;

	return SCHED_NONE;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_RollerMine::TranslateSchedule( int scheduleType )
{
	switch( scheduleType )
	{
	case SCHED_IDLE_STAND:
		{
			int iSched = GetHackedIdleSchedule();
			if ( iSched != SCHED_NONE )
				return iSched;

			return SCHED_IDLE_STAND;
		}
		break;

	case SCHED_ALERT_STAND:
		{
			int iSched = GetHackedIdleSchedule();
			if ( iSched != SCHED_NONE )
				return iSched;

			return SCHED_ROLLERMINE_ALERT_STAND;
		}
		break;

	case SCHED_ROLLERMINE_RANGE_ATTACK1:
		if( HasCondition(COND_ENEMY_OCCLUDED) )
		{
			// Because of an unfortunate arrangement of cascading failing schedules, the rollermine
			// could end up here with instructions to drive towards the target, although the target is
			// not in sight. Nudge around randomly until we're back on the nodegraph.
			return SCHED_ROLLERMINE_NUDGE_TOWARDS_NODES;
		}
		break;
	}

	return scheduleType;
}


#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, bool bOnlyHateOrFearIfNPC)
{
	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, bOnlyHateOrFearIfNPC);
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  :
// Output :
//-----------------------------------------------------------------------------
void CNPC_RollerMine::StartTask( const Task_t *pTask )
{
	switch( pTask->iTask )
	{
	case TASK_FACE_REASONABLE:
	case TASK_FACE_SAVEPOSITION:
	case TASK_FACE_LASTPOSITION:
	case TASK_FACE_TARGET:
	case TASK_FACE_AWAY_FROM_SAVEPOSITION:
	case TASK_FACE_HINTNODE:
	case TASK_FACE_ENEMY:
	case TASK_FACE_PLAYER:
	case TASK_FACE_PATH:
	case TASK_FACE_IDEAL:
		// This only applies to NPCs that aren't spheres with omnidirectional eyesight.
		TaskComplete();
		break;

	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_WAIT_FOR_MOVEMENT:
		{
			// TASK_RUN_PATH and TASK_WALK_PATH work different on the rollermine and run until movement is done,
			// so movement is already complete when entering this task.
			TaskComplete();
		}
		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:
	case TASK_ROLLERMINE_RETURN_TO_PLAYER:
		{
			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;

	case TASK_ROLLERMINE_NUDGE_TOWARDS_NODES:
		{
			IPhysicsObject *pPhysicsObject = VPhysicsGetObject();

			if( pPhysicsObject )
			{
				// Try a few times to find a direction to shove ourself
				for( int i = 0 ; i < 4 ; i++ )
				{
					int x,y;

					x = random->RandomInt( -1, 1 );
					y = random->RandomInt( -1, 1 );

					Vector vecNudge(x, y, 0.0f);

					trace_t tr;

					// Try to move in a direction with a couple of feet of clearance.
					UTIL_TraceLine( WorldSpaceCenter(), WorldSpaceCenter() + vecNudge * 24.0f, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );

					if( tr.fraction == 1.0 )
					{
						vecNudge *= (pPhysicsObject->GetMass() * 75.0f);
						vecNudge += Vector(0,0,pPhysicsObject->GetMass() * 75.0f);
						pPhysicsObject->ApplyForceCenter( vecNudge );
						break;
					}
				}
			}

			TaskComplete();
		}
		break;

	case TASK_ROLLERMINE_POWERDOWN:
		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_WAIT_FOR_MOVEMENT:
		{
			// TASK_RUN_PATH and TASK_WALK_PATH work different on the rollermine and run until movement is done,
			// so movement is already complete when entering this task.
			TaskComplete();
		}
		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 );
			}

			if( m_bHackedByAlyx )
			{
				// Move faster. 
				m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, m_flForwardSpeed * 2.0f );
			}
			else
			{
				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() )
			{
				CBaseCombatCharacter *pCCEnemy = pEnemy->MyCombatCharacterPointer();
				float flT;

				// Project it's velocity and find our closest point on that line. Do it all in 2d space.
				Vector vecVehicleVelocity = pCCEnemy->GetVehicleEntity()->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;

	case TASK_ROLLERMINE_RETURN_TO_PLAYER:
		{
			if ( ConditionsGathered() && !HasCondition(COND_SEE_PLAYER) )
			{
				TaskFail( FAIL_NO_PLAYER );
				return;
			}

			CBaseEntity *pPlayer = gEntList.FindEntityByName( NULL, "!player" );
			if ( !pPlayer || m_bHeld || m_hVehicleStuckTo )
			{
				TaskFail( FAIL_NO_TARGET );
				return;
			}

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

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

			float flDot;

			IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
			if ( pPhysicsObject == NULL )
			{
				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 * 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 );

			// Once we're near the player, slow & stop
			if ( GetAbsOrigin().DistToSqr( vecTargetPosition ) < (ROLLERMINE_RETURN_TO_PLAYER_DIST*2.0) )
			{
				TaskComplete();
			}
		}
		break;

	case TASK_ROLLERMINE_POWERDOWN:
		{
			if ( m_flPowerDownTime <= gpGlobals->curtime )
			{
				m_flNextHop = gpGlobals->curtime;
				m_flPowerDownTime = gpGlobals->curtime + RandomFloat( 0.3, 0.9 );
				EmitSound( "NPC_RollerMine.Hurt" );

				CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin(), 400, 0.5f, this );

				if ( m_bIsOpen == false )
				{
					Open();
				}
				else
				{
					Close();
				}
			}

			if ( m_flPowerDownDetonateTime <= gpGlobals->curtime )
			{
				SetThink( &CNPC_RollerMine::PreDetonate );
				SetNextThink( gpGlobals->curtime + 0.5f );
			}

			// No TaskComplete() here, because the task will never complete. The rollermine will explode.
		}
		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" );
        SetRollerSkin();

		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 if ( !GetEnemy() || GetEnemy()->Classify() != CLASS_BULLSEYE )		// Don't hop when attacking bullseyes
			{
				Hop( 128 );
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  :
// Output :
//-----------------------------------------------------------------------------
void CNPC_RollerMine::SetRollerSkin( void )
{
	if ( m_bPowerDown == true )
	{
		m_nSkin = (int)ROLLER_SKIN_DETONATE;
	}
	else if ( m_bHackedByAlyx == true )
	{
		m_nSkin = (int)ROLLER_SKIN_FRIENDLY;
	}
	else
	{
		m_nSkin = (int)ROLLER_SKIN_REGULAR;
	}
}


//-----------------------------------------------------------------------------
// 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" );

		SetRollerSkin();

		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 = ToBasePlayer( pVehicle->GetPassenger() );
		if ( pPlayer != NULL )
		{
			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;

	if( m_bHackedByAlyx )
	{
		pBeam = CBeam::BeamCreate( "sprites/rollermine_shock_yellow.vmt", 4 );
	}
	else
	{
		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 && pAnimating->GetModel() )
		{
			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 )
	{
		pAnimating->GetAttachment( startAttach, shockPos );
	}

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

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

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::NotifyInteraction( CAI_BaseNPC *pUser )
{
	// For now, turn green so we can tell who is hacked.
	m_bHackedByAlyx = true; 
	SetRollerSkin();
	GetEnemies()->SetFreeKnowledgeDuration( 30.0f );

	// Play the hax0red sound
	EmitSound( "NPC_RollerMine.Reprogram" );

	// Force the rollermine open here. At very least, this ensures that the 
	// correct, smaller bounding box is recomputed around it.
	Open();
}

//-----------------------------------------------------------------------------
// 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);

	// Ignore anyone that I'm friendly or neutral to.
	if( 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 + GetStunDelay();

	// 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" );
	// Do a shock effect
	ShockTarget( pOther );

	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 );

	if( FClassnameIs( pOther, "npc_combine_s" ) )
	{
		if( pOther->GetHealth() <= (pOther->GetMaxHealth() / 2) ) 
		{
			// Instant special death for a combine soldier who has less than half health.
			Vector vecDamageForce = pOther->WorldSpaceCenter() - WorldSpaceCenter();
			VectorNormalize( vecDamageForce );

			IPhysicsObject *pPhysics = pOther->VPhysicsGetObject();

			if( pPhysics )
			{
				vecDamageForce *= (pPhysics->GetMass() * 200.0f);

				// Slam Z component with some good, reliable upwards velocity.
				vecDamageForce.z = pPhysics->GetMass() * 200.0f;
			}

			pOther->MyCombatCharacterPointer()->BecomeRagdollBoogie( this, vecDamageForce, 5.0f, SF_RAGDOLL_BOOGIE_ELECTRICAL );
			return;
		}
		else
		{
			info.SetDamage( pOther->GetMaxHealth()/2 );
		}
	}

	pOther->TakeDamage( info );

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

//-----------------------------------------------------------------------------
// 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 )
		{
			// clear the flag so we don't queue more than once
			m_bEmbedOnGroundImpact = false;
			// call this when physics is done
			g_PostSimulationQueue.QueueCall( this, &CNPC_RollerMine::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 + GetStunDelay();
}

//-----------------------------------------------------------------------------
// 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: 
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::InputTurnOn( inputdata_t &inputdata )
{
	m_RollerController.On();
	m_bTurnedOn = true;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::InputTurnOff( inputdata_t &inputdata )
{
	m_RollerController.Off();
	m_bTurnedOn = false;
	StopLoopingSounds();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CNPC_RollerMine::InputPowerdown( inputdata_t &inputdata )
{
	m_bPowerDown = true;
	m_flPowerDownTime = gpGlobals->curtime + RandomFloat( 0.1, 0.5 );
	m_flPowerDownDetonateTime = m_flPowerDownTime + RandomFloat( 1.5, 4.0 );

	ClearSchedule( "Received power down input" );
}

//-----------------------------------------------------------------------------
// 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 + GetStunDelay();
		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 + GetStunDelay();
	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 + GetStunDelay();
			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;

	//If we've been hacked and we're blowing up cause we've been shut down then do moderate damage.
	if ( m_bPowerDown == true )
	{
		expDamage = 50;
	}

	// 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;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
float CNPC_RollerMine::GetStunDelay()
{
	if( m_bHackedByAlyx )
	{
		return 0.1f;
	}
	else
	{
		return sk_rollermine_stun_delay.GetFloat();
	}
}

//-----------------------------------------------------------------------------
// 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 )
		{
			// Handle vehicles
			CBaseCombatCharacter *pCCEnemy = pEnemy->MyCombatCharacterPointer();
			if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
			{
				// If we're buried, we only care when they're heading directly towards us
				if ( m_bBuried )
					return ( VehicleHeading( pCCEnemy->GetVehicle()->GetVehicleEnt() ) > DOT_20DEGREE );

				// If we're not buried, chase him as long as he's not heading away from us
				return ( VehicleHeading( pCCEnemy->GetVehicleEntity() ) > 0 );
			}

			return false;
		}

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

		// Don't attack flying things.
		if ( pEnemy->GetMoveType() == MOVETYPE_FLY )
			return false;
	}

	return BaseClass::IsValidEnemy( pEnemy );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CNPC_RollerMine::EnemyInVehicle( void )
{
	// Clearly the enemy is not...
	if ( GetEnemy() == NULL )
		return false;

	// If the target is in a vehicle, let the convar choose
	CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
	if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
		return ( sk_rollermine_vehicle_intercept.GetBool() );

	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 )
	DECLARE_TASK( TASK_ROLLERMINE_NUDGE_TOWARDS_NODES )
	DECLARE_TASK( TASK_ROLLERMINE_RETURN_TO_PLAYER )
	DECLARE_TASK( TASK_ROLLERMINE_POWERDOWN )

	//Schedules

	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"
	)

	DEFINE_SCHEDULE
	(
	SCHED_ROLLERMINE_ALERT_STAND,

	"	Tasks"
	"		TASK_STOP_MOVING			0"
	"		TASK_FACE_REASONABLE		0"
	"		TASK_SET_ACTIVITY			ACTIVITY:ACT_IDLE"
	"		TASK_WAIT					2"
	""
	"	Interrupts"
	"		COND_NEW_ENEMY"
	"		COND_SEE_ENEMY"
	"		COND_SEE_FEAR"
	"		COND_LIGHT_DAMAGE"
	"		COND_HEAVY_DAMAGE"
	"		COND_PROVOKED"
	"		COND_SMELL"
	"		COND_HEAR_COMBAT"		// sound flags
	"		COND_HEAR_WORLD"
	"		COND_HEAR_PLAYER"
	"		COND_HEAR_DANGER"
	"		COND_HEAR_BULLET_IMPACT"
	"		COND_IDLE_INTERRUPT"
	)

	DEFINE_SCHEDULE
	(
	SCHED_ROLLERMINE_NUDGE_TOWARDS_NODES,

	"	Tasks"
	"		TASK_ROLLERMINE_NUDGE_TOWARDS_NODES		0"
	"		TASK_WAIT								1.5"
	""
	"	Interrupts"
	""
	)

	DEFINE_SCHEDULE
	(
		SCHED_ROLLERMINE_PATH_TO_PLAYER,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_ROLLERMINE_ALERT_STAND"
		"		TASK_SET_TOLERANCE_DISTANCE		200"
		"		TASK_GET_PATH_TO_PLAYER			0"
		"		TASK_RUN_PATH					0"
		"		TASK_WAIT_FOR_MOVEMENT			0"
		""
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_SEE_ENEMY"
		"		COND_SEE_FEAR"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
		"		COND_PROVOKED"
		"		COND_SMELL"
		"		COND_HEAR_COMBAT"		// sound flags
		"		COND_HEAR_WORLD"
		"		COND_HEAR_PLAYER"
		"		COND_HEAR_DANGER"
		"		COND_HEAR_BULLET_IMPACT"
		"		COND_IDLE_INTERRUPT"
		"		COND_SEE_PLAYER"
	)

	DEFINE_SCHEDULE
	(
		SCHED_ROLLERMINE_ROLL_TO_PLAYER,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE				SCHEDULE:SCHED_ROLLERMINE_ALERT_STAND"
		"		TASK_SET_TOLERANCE_DISTANCE			200"
		"		TASK_ROLLERMINE_RETURN_TO_PLAYER	0"
		""
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_SEE_ENEMY"
		"		COND_SEE_FEAR"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
		"		COND_PROVOKED"
		"		COND_SMELL"
		"		COND_HEAR_COMBAT"		// sound flags
		"		COND_HEAR_WORLD"
		"		COND_HEAR_PLAYER"
		"		COND_HEAR_DANGER"
		"		COND_HEAR_BULLET_IMPACT"
		"		COND_IDLE_INTERRUPT"
	)

	DEFINE_SCHEDULE
	(
		SCHED_ROLLERMINE_POWERDOWN,

		"	Tasks"
		"		TASK_STOP_MOVING			0"
		"		TASK_SET_ACTIVITY			ACTIVITY:ACT_IDLE"
		"		TASK_ROLLERMINE_POWERDOWN	0"
		""
		"	Interrupts"
		""
	);

AI_END_CUSTOM_NPC()
