//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Spawn, think, and touch functions for trains, etc.
//
//=============================================================================//

#include "cbase.h"
#include "ai_basenpc.h"
#include "trains.h"
#include "ndebugoverlay.h"
#include "entitylist.h"
#include "engine/IEngineSound.h"
#include "soundenvelope.h"
#include "physics_npc_solver.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

static void PlatSpawnInsideTrigger(edict_t *pevPlatform);

#define SF_PLAT_TOGGLE				0x0001

class CBasePlatTrain : public CBaseToggle
{
	DECLARE_CLASS( CBasePlatTrain, CBaseToggle );

public:
	~CBasePlatTrain();
	bool KeyValue( const char *szKeyName, const char *szValue );
	void Precache( void );

	// This is done to fix spawn flag collisions between this class and a derived class
	virtual bool IsTogglePlat( void ) { return (m_spawnflags & SF_PLAT_TOGGLE) ? true : false; }

	DECLARE_DATADESC();

	void	PlayMovingSound();
	void	StopMovingSound();

	string_t	m_NoiseMoving;	// sound a plat makes while moving
	string_t	m_NoiseArrived;

	CSoundPatch *m_pMovementSound;
#ifdef HL1_DLL
	int			m_MoveSound;
	int			m_StopSound;
#endif

	float	m_volume;			// Sound volume
	float	m_flTWidth;
	float	m_flTLength;
};

BEGIN_DATADESC( CBasePlatTrain )

	DEFINE_KEYFIELD( m_NoiseMoving, FIELD_SOUNDNAME, "noise1" ),
	DEFINE_KEYFIELD( m_NoiseArrived, FIELD_SOUNDNAME, "noise2" ),

#ifdef HL1_DLL
	DEFINE_KEYFIELD( m_MoveSound, FIELD_INTEGER, "movesnd" ),
	DEFINE_KEYFIELD( m_StopSound, FIELD_INTEGER, "stopsnd" ),

#endif 
	DEFINE_SOUNDPATCH( m_pMovementSound ),

	DEFINE_KEYFIELD( m_volume, FIELD_FLOAT, "volume" ),

	DEFINE_FIELD( m_flTWidth, FIELD_FLOAT ),
	DEFINE_FIELD( m_flTLength, FIELD_FLOAT ),
	DEFINE_KEYFIELD( m_flLip, FIELD_FLOAT, "lip" ),
	DEFINE_KEYFIELD( m_flWait, FIELD_FLOAT, "wait" ),
	DEFINE_KEYFIELD( m_flHeight, FIELD_FLOAT, "height" ),

END_DATADESC()


bool CBasePlatTrain::KeyValue( const char *szKeyName, const char *szValue )
{
	if (FStrEq(szKeyName, "rotation"))
	{
		m_vecFinalAngle.x = atof(szValue);
	}
	else
	{
		return BaseClass::KeyValue( szKeyName, szValue );
	}

	return true;
}


CBasePlatTrain::~CBasePlatTrain()
{
	StopMovingSound();
}

void CBasePlatTrain::PlayMovingSound()
{
	StopMovingSound();
	if(m_NoiseMoving != NULL_STRING )
	{
		CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
		CPASAttenuationFilter filter( this );
		m_pMovementSound = controller.SoundCreate( filter, entindex(), CHAN_STATIC, STRING(m_NoiseMoving), ATTN_NORM );
		
		controller.Play( m_pMovementSound, m_volume, PITCH_NORM );
	}
}

void CBasePlatTrain::StopMovingSound()
{
	if ( m_pMovementSound )
	{
		CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();

		controller.SoundDestroy( m_pMovementSound );
		m_pMovementSound = NULL;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBasePlatTrain::Precache( void )
{
	//Fill in a default value if necessary
	UTIL_ValidateSoundName( m_NoiseMoving, "Plat.DefaultMoving" );
	UTIL_ValidateSoundName( m_NoiseArrived, "Plat.DefaultArrive" );

#ifdef HL1_DLL
// set the plat's "in-motion" sound
	switch (m_MoveSound)
	{
	default:
	case	0:
		m_NoiseMoving = MAKE_STRING( "Plat.DefaultMoving" );
		break;
	case	1:
		m_NoiseMoving = MAKE_STRING("Plat.BigElev1");
		break;
	case	2:
		m_NoiseMoving = MAKE_STRING("Plat.BigElev2");
		break;
	case	3:
		m_NoiseMoving = MAKE_STRING("Plat.TechElev1");
		break;
	case	4:
		m_NoiseMoving = MAKE_STRING("Plat.TechElev2");
		break;
	case	5:
		m_NoiseMoving = MAKE_STRING("Plat.TechElev3");
		break;
	case	6:
		m_NoiseMoving = MAKE_STRING("Plat.FreightElev1");
		break;
	case	7:
		m_NoiseMoving = MAKE_STRING("Plat.FreightElev2");
		break;
	case	8:
		m_NoiseMoving = MAKE_STRING("Plat.HeavyElev");
		break;
	case	9:
		m_NoiseMoving = MAKE_STRING("Plat.RackElev");
		break;
	case	10:
		m_NoiseMoving = MAKE_STRING("Plat.RailElev");
		break;
	case	11:
		m_NoiseMoving = MAKE_STRING("Plat.SqueakElev");
		break;
	case	12:
		m_NoiseMoving = MAKE_STRING("Plat.OddElev1");
		break;
	case	13:
		m_NoiseMoving = MAKE_STRING("Plat.OddElev2");
		break;
	}

// set the plat's 'reached destination' stop sound
	switch (m_StopSound)
	{
	default:
	case	0:
		m_NoiseArrived = MAKE_STRING( "Plat.DefaultArrive" );
		break;
	case	1:
		m_NoiseArrived = MAKE_STRING("Plat.BigElevStop1");
		break;
	case	2:
		m_NoiseArrived = MAKE_STRING("Plat.BigElevStop2");
		break;
	case	3:
		m_NoiseArrived = MAKE_STRING("Plat.FreightElevStop");
		break;
	case	4:
		m_NoiseArrived = MAKE_STRING("Plat.HeavyElevStop");
		break;
	case	5:
		m_NoiseArrived = MAKE_STRING("Plat.RackStop");
		break;
	case	6:
		m_NoiseArrived = MAKE_STRING("Plat.RailStop");
		break;
	case	7:
		m_NoiseArrived = MAKE_STRING("Plat.SqueakStop");
		break;
	case	8:
		m_NoiseArrived = MAKE_STRING("Plat.QuickStop");
		break;
	}

#endif // HL1_DLL

	//Precache them all
	PrecacheScriptSound( (char *) STRING(m_NoiseMoving) );
	PrecacheScriptSound( (char *) STRING(m_NoiseArrived) );

}


class CFuncPlat : public CBasePlatTrain
{
	DECLARE_CLASS( CFuncPlat, CBasePlatTrain );
public:
	void Spawn( void );
	void Precache( void );
	bool CreateVPhysics();
	void Setup( void );

	virtual void Blocked( CBaseEntity *pOther );
	void PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );

	void	CallGoDown( void ) { GoDown(); }
	void	CallHitTop( void  ) { HitTop(); }
	void	CallHitBottom( void ) { HitBottom(); }

	virtual void GoUp( void );
	virtual void GoDown( void );
	virtual void HitTop( void );
	virtual void HitBottom( void );

	void InputToggle(inputdata_t &data);
	void InputGoUp(inputdata_t &data);
	void InputGoDown(inputdata_t &data);

	DECLARE_DATADESC();

private:

	string_t m_sNoise;
};


BEGIN_DATADESC( CFuncPlat )

	DEFINE_FIELD( m_sNoise, FIELD_STRING ),

	// Function Pointers
	DEFINE_FUNCTION( PlatUse ),
	DEFINE_FUNCTION( CallGoDown ),
	DEFINE_FUNCTION( CallHitTop ),
	DEFINE_FUNCTION( CallHitBottom ),

	// Inputs
	DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
	DEFINE_INPUTFUNC( FIELD_VOID, "GoUp", InputGoUp ),
	DEFINE_INPUTFUNC( FIELD_VOID, "GoDown", InputGoDown ),

END_DATADESC()

LINK_ENTITY_TO_CLASS( func_plat, CFuncPlat );

//==================================================
// CPlatTrigger 
//==================================================
class CPlatTrigger : public CBaseEntity
{
	DECLARE_CLASS( CPlatTrigger, CBaseEntity );
public:
	virtual int	ObjectCaps( void ) { return BaseClass::ObjectCaps() | FCAP_DONT_SAVE; }
	void SpawnInsideTrigger( CFuncPlat *pPlatform );
	void Touch( CBaseEntity *pOther );
	CFuncPlat *m_pPlatform;
};

void CFuncPlat::Setup( void )
{
	if (m_flTLength == 0)
	{
		m_flTLength = 80;
	}
	
	if (m_flTWidth == 0)
	{
		m_flTWidth = 10;
	}
	
	SetLocalAngles( vec3_angle );
	SetSolid( SOLID_BSP );
	SetMoveType( MOVETYPE_PUSH );

	// Set size and link into world
	SetModel( STRING( GetModelName() ) );

	m_vecPosition1 = GetLocalOrigin();	//Top
	m_vecPosition2 = GetLocalOrigin();	//Bottom

	if ( m_flHeight != 0 )
	{
		m_vecPosition2.z = GetLocalOrigin().z - m_flHeight;
	}
	else
	{
		// NOTE: This works because the angles were set to vec3_angle above
		m_vecPosition2.z = GetLocalOrigin().z - CollisionProp()->OBBSize().z + 8;
	}

	if (m_flSpeed == 0)
	{
		m_flSpeed = 150;
	}

	if ( m_volume == 0.0f )
	{
		m_volume = 0.85f;
	}
}


void CFuncPlat::Precache( )
{
	BaseClass::Precache();
	
	if ( IsTogglePlat() == false )
	{
		// Create the "start moving" trigger
		PlatSpawnInsideTrigger( edict() );
	}
}


void CFuncPlat::Spawn( )
{
	Setup();
	Precache();

	// If this platform is the target of some button, it starts at the TOP position,
	// and is brought down by that button.  Otherwise, it starts at BOTTOM.
	if ( GetEntityName() != NULL_STRING )
	{
		UTIL_SetOrigin( this, m_vecPosition1);
		m_toggle_state = TS_AT_TOP;
		SetUse( &CFuncPlat::PlatUse );
	}
	else
	{
		UTIL_SetOrigin( this, m_vecPosition2);
		m_toggle_state = TS_AT_BOTTOM;
	}
	CreateVPhysics();
}

bool CFuncPlat::CreateVPhysics()
{
	VPhysicsInitShadow( false, false );
	return true;
}


static void PlatSpawnInsideTrigger(edict_t* pevPlatform)
{
	// old code: //GetClassPtr( (CPlatTrigger *)NULL)->SpawnInsideTrigger( GetClassPtr( (CFuncPlat *)pevPlatform ) );
	CPlatTrigger *plattrig = CREATE_UNSAVED_ENTITY( CPlatTrigger, "plat_trigger" );
	plattrig->SpawnInsideTrigger( (CFuncPlat *)GetContainingEntity( pevPlatform ) );
}
		

//
// Create a trigger entity for a platform.
//
void CPlatTrigger::SpawnInsideTrigger( CFuncPlat *pPlatform )
{
	m_pPlatform = pPlatform;
	// Create trigger entity, "point" it at the owning platform, give it a touch method
	SetSolid( SOLID_BSP );
	AddSolidFlags( FSOLID_TRIGGER );
	SetMoveType( MOVETYPE_NONE );
	SetLocalOrigin( pPlatform->GetLocalOrigin() );

	// Establish the trigger field's size
	CCollisionProperty *pCollision = m_pPlatform->CollisionProp();
	Vector vecTMin = pCollision->OBBMins() + Vector ( 25 , 25 , 0 );
	Vector vecTMax = pCollision->OBBMaxs() + Vector ( 25 , 25 , 8 );
	vecTMin.z = vecTMax.z - ( m_pPlatform->m_vecPosition1.z - m_pPlatform->m_vecPosition2.z + 8 );
	if ( pCollision->OBBSize().x <= 50 )
	{
		vecTMin.x = (pCollision->OBBMins().x + pCollision->OBBMaxs().x) / 2;
		vecTMax.x = vecTMin.x + 1;
	}
	if ( pCollision->OBBSize().y <= 50 )
	{
		vecTMin.y = (pCollision->OBBMins().y + pCollision->OBBMaxs().y) / 2;
		vecTMax.y = vecTMin.y + 1;
	}
	UTIL_SetSize ( this, vecTMin, vecTMax );
}


//
// When the platform's trigger field is touched, the platform ???
//
void CPlatTrigger::Touch( CBaseEntity *pOther )
{
	// Ignore touches by non-players
	if ( !pOther->IsPlayer() )
		return;

	// Ignore touches by corpses
	if (!pOther->IsAlive())
		return;
	
	// Make linked platform go up/down.
	if (m_pPlatform->m_toggle_state == TS_AT_BOTTOM)
		m_pPlatform->GoUp();
	else if (m_pPlatform->m_toggle_state == TS_AT_TOP)
		m_pPlatform->SetMoveDoneTime( 1 );// delay going down
}



//-----------------------------------------------------------------------------
// Purpose: Used when a platform is the target of a button.
//			Start bringing platform down.
// Input  : pActivator - 
//			pCaller - 
//			useType - 
//			value - 
//-----------------------------------------------------------------------------
void CFuncPlat::InputToggle(inputdata_t &data)
{
	if ( IsTogglePlat() )
	{
		if (m_toggle_state == TS_AT_TOP)
			GoDown();
		else if ( m_toggle_state == TS_AT_BOTTOM )
			GoUp();
	}
	else
	{
		SetUse( NULL );

		if (m_toggle_state == TS_AT_TOP)
			GoDown();
	}
}

void CFuncPlat::InputGoUp(inputdata_t &data)
{
	if ( m_toggle_state == TS_AT_BOTTOM )
		GoUp();
}

void CFuncPlat::InputGoDown(inputdata_t &data)
{
	if ( m_toggle_state == TS_AT_TOP )
		GoDown();
}

//-----------------------------------------------------------------------------
// Purpose: Used when a platform is the target of a button.
//			Start bringing platform down.
// Input  : pActivator - 
//			pCaller - 
//			useType - 
//			value - 
//-----------------------------------------------------------------------------
void CFuncPlat::PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	if ( IsTogglePlat() )
	{
		// Top is off, bottom is on
		bool on = (m_toggle_state == TS_AT_BOTTOM) ? true : false;

		if ( !ShouldToggle( useType, on ) )
			return;

		if (m_toggle_state == TS_AT_TOP)
			GoDown();
		else if ( m_toggle_state == TS_AT_BOTTOM )
			GoUp();
	}
	else
	{
		SetUse( NULL );

		if (m_toggle_state == TS_AT_TOP)
			GoDown();
	}
}


//
// Platform is at top, now starts moving down.
//
void CFuncPlat::GoDown( void )
{
	PlayMovingSound();

	ASSERT(m_toggle_state == TS_AT_TOP || m_toggle_state == TS_GOING_UP);
	m_toggle_state = TS_GOING_DOWN;
	SetMoveDone(&CFuncPlat::CallHitBottom);
	LinearMove(m_vecPosition2, m_flSpeed);
}


//
// Platform has hit bottom.  Stops and waits forever.
//
void CFuncPlat::HitBottom( void )
{
	StopMovingSound();

	if ( m_NoiseArrived != NULL_STRING )
	{
		CPASAttenuationFilter filter( this );

		EmitSound_t ep;
		ep.m_nChannel = CHAN_WEAPON;
		ep.m_pSoundName =  STRING(m_NoiseArrived);
		ep.m_flVolume = m_volume;
		ep.m_SoundLevel = SNDLVL_NORM;

		EmitSound( filter, entindex(), ep );
	}

	ASSERT(m_toggle_state == TS_GOING_DOWN);
	m_toggle_state = TS_AT_BOTTOM;
}


//
// Platform is at bottom, now starts moving up
//
void CFuncPlat::GoUp( void )
{
	PlayMovingSound();
	
	ASSERT(m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN);
	m_toggle_state = TS_GOING_UP;
	SetMoveDone(&CFuncPlat::CallHitTop);
	LinearMove(m_vecPosition1, m_flSpeed);
}


//
// Platform has hit top.  Pauses, then starts back down again.
//
void CFuncPlat::HitTop( void )
{
	StopMovingSound();

	if ( m_NoiseArrived != NULL_STRING )
	{
		CPASAttenuationFilter filter( this );

		EmitSound_t ep;
		ep.m_nChannel = CHAN_WEAPON;
		ep.m_pSoundName =  STRING(m_NoiseArrived);
		ep.m_flVolume = m_volume;
		ep.m_SoundLevel = SNDLVL_NORM;

		EmitSound( filter, entindex(), ep );
	}
	
	ASSERT(m_toggle_state == TS_GOING_UP);
	m_toggle_state = TS_AT_TOP;

	if ( !IsTogglePlat() )
	{
		// After a delay, the platform will automatically start going down again.
		SetMoveDone( &CFuncPlat::CallGoDown );
		SetMoveDoneTime( 3 );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Called when we are blocked.
//-----------------------------------------------------------------------------
void CFuncPlat::Blocked( CBaseEntity *pOther )
{
	DevMsg( 2, "%s Blocked by %s\n", GetClassname(), pOther->GetClassname() );

	// Hurt the blocker a little
	pOther->TakeDamage( CTakeDamageInfo( this, this, 1, DMG_CRUSH ) );

	if (m_sNoise != NULL_STRING)
	{
		StopSound(entindex(), CHAN_STATIC, (char*)STRING(m_sNoise));
	}
	
	// Send the platform back where it came from
	ASSERT(m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN);
	if (m_toggle_state == TS_GOING_UP)
	{
		GoDown();
	}
	else if (m_toggle_state == TS_GOING_DOWN)
	{
		GoUp ();
	}
}


class CFuncPlatRot : public CFuncPlat
{
	DECLARE_CLASS( CFuncPlatRot, CFuncPlat );
public:
	void Spawn( void );
	void SetupRotation( void );

	virtual void	GoUp( void );
	virtual void	GoDown( void );
	virtual void	HitTop( void );
	virtual void	HitBottom( void );
	
	void			RotMove( QAngle &destAngle, float time );
	DECLARE_DATADESC();

	QAngle	m_end, m_start;
};

LINK_ENTITY_TO_CLASS( func_platrot, CFuncPlatRot );

BEGIN_DATADESC( CFuncPlatRot )

	DEFINE_FIELD( m_end, FIELD_VECTOR ),
	DEFINE_FIELD( m_start, FIELD_VECTOR ),

END_DATADESC()


void CFuncPlatRot::SetupRotation( void )
{
	if ( m_vecFinalAngle.x != 0 )		// This plat rotates too!
	{
		CBaseToggle::AxisDir();
		m_start	= GetLocalAngles();
		m_end = GetLocalAngles() + m_vecMoveAng * m_vecFinalAngle.x;
	}
	else
	{
		m_start = vec3_angle;
		m_end = vec3_angle;
	}
	if ( GetEntityName() != NULL_STRING )	// Start at top
	{
		SetLocalAngles( m_end );
	}
}


void CFuncPlatRot::Spawn( void )
{
	BaseClass::Spawn();
	SetupRotation();
}

void CFuncPlatRot::GoDown( void )
{
	BaseClass::GoDown();
	RotMove( m_start, GetMoveDoneTime() );
}


//
// Platform has hit bottom.  Stops and waits forever.
//
void CFuncPlatRot::HitBottom( void )
{
	BaseClass::HitBottom();
	SetLocalAngularVelocity( vec3_angle );
	SetLocalAngles( m_start );
}


//
// Platform is at bottom, now starts moving up
//
void CFuncPlatRot::GoUp( void )
{
	BaseClass::GoUp();
	RotMove( m_end, GetMoveDoneTime() );
}


//
// Platform has hit top.  Pauses, then starts back down again.
//
void CFuncPlatRot::HitTop( void )
{
	BaseClass::HitTop();
	SetLocalAngularVelocity( vec3_angle );
	SetLocalAngles( m_end );
}


void CFuncPlatRot::RotMove( QAngle &destAngle, float time )
{
	// set destdelta to the vector needed to move
	QAngle vecDestDelta = destAngle - GetLocalAngles();

	// Travel time is so short, we're practically there already;  so make it so.
	if ( time >= 0.1)
		SetLocalAngularVelocity( vecDestDelta * (1.0 / time) );
	else
	{
		SetLocalAngularVelocity( vecDestDelta );
		SetMoveDoneTime( 1 );
	}
}


class CFuncTrain : public CBasePlatTrain
{
	DECLARE_CLASS( CFuncTrain, CBasePlatTrain );
public:
	void Spawn( void );
	void Precache( void );
	void Activate( void );
	void OnRestore( void );

	void SetupTarget( void );
	void Blocked( CBaseEntity *pOther );
	void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );

	void Wait( void );
	void Next( void );

	//Inputs
	void InputToggle(inputdata_t &data);
	void InputStart(inputdata_t &data);
	void InputStop(inputdata_t &data);

	void Start( void );
	void Stop( void );

	DECLARE_DATADESC();

public:
	EHANDLE		m_hCurrentTarget;
	
	bool		m_activated;
	EHANDLE		m_hEnemy;
	float		m_flBlockDamage;		// Damage to inflict when blocked.
	float		m_flNextBlockTime;
	string_t m_iszLastTarget;
};

LINK_ENTITY_TO_CLASS( func_train, CFuncTrain );


BEGIN_DATADESC( CFuncTrain )

	DEFINE_FIELD( m_hCurrentTarget, FIELD_EHANDLE ),
	DEFINE_FIELD( m_activated, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_hEnemy, FIELD_EHANDLE ),
	DEFINE_FIELD( m_iszLastTarget, FIELD_STRING ),
	DEFINE_FIELD( m_flNextBlockTime, FIELD_TIME ),

	DEFINE_KEYFIELD( m_flBlockDamage, FIELD_FLOAT, "dmg" ),

	// Function Pointers
	DEFINE_FUNCTION( Wait ),
	DEFINE_FUNCTION( Next ),

	// Inputs
	DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Start",	InputStart ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Stop",	InputStop ),

END_DATADESC()


//-----------------------------------------------------------------------------
// Purpose: Handles a train being blocked by an entity.
// Input  : pOther - What was hit.
//-----------------------------------------------------------------------------
void CFuncTrain::Blocked( CBaseEntity *pOther )
{
	if ( gpGlobals->curtime < m_flNextBlockTime )
		return;

	m_flNextBlockTime = gpGlobals->curtime + 0.5;
	
	//Inflict damage
	pOther->TakeDamage( CTakeDamageInfo( this, this, m_flBlockDamage, DMG_CRUSH ) );
}


void CFuncTrain::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	//If we've been waiting to be retriggered, move to the next destination
	if ( m_spawnflags & SF_TRAIN_WAIT_RETRIGGER )
	{
		// Move toward my target
		m_spawnflags &= ~SF_TRAIN_WAIT_RETRIGGER;
		Next();
	}
	else
	{
		m_spawnflags |= SF_TRAIN_WAIT_RETRIGGER;
		
		// Pop back to last target if it's available
		if ( m_hEnemy )
		{
			m_target = m_hEnemy->GetEntityName();
		}

		SetNextThink( TICK_NEVER_THINK );
		SetLocalVelocity( vec3_origin );
		
		if ( m_NoiseArrived != NULL_STRING )
		{
			CPASAttenuationFilter filter( this );

			EmitSound_t ep;
			ep.m_nChannel = CHAN_VOICE;
			ep.m_pSoundName =  STRING(m_NoiseArrived);
			ep.m_flVolume = m_volume;
			ep.m_SoundLevel = SNDLVL_NORM;

			EmitSound( filter, entindex(), ep );
		}
	}
}

void CFuncTrain::Wait( void )
{
	//If we're moving passed a path track, then trip its output
	variant_t emptyVariant;
	m_hCurrentTarget->AcceptInput( "InPass", this, this, emptyVariant, 0 );

	// need pointer to LAST target.
	if ( m_hCurrentTarget->HasSpawnFlags( SF_TRAIN_WAIT_RETRIGGER ) || HasSpawnFlags( SF_TRAIN_WAIT_RETRIGGER ) )
    {
		AddSpawnFlags( SF_TRAIN_WAIT_RETRIGGER );
        
		// Clear the sound channel.
		StopMovingSound();
		
		if ( m_NoiseArrived != NULL_STRING )
		{
			CPASAttenuationFilter filter( this );

			EmitSound_t ep;
			ep.m_nChannel = CHAN_VOICE;
			ep.m_pSoundName =  STRING(m_NoiseArrived);
			ep.m_flVolume = m_volume;
			ep.m_SoundLevel = SNDLVL_NORM;

			EmitSound( filter, entindex(), ep );
		}

		SetMoveDoneTime( -1 );
        
		return;
    }
    
	//NOTENOTE: -1 wait will wait forever
	if ( m_flWait != 0 )
	{
		SetMoveDoneTime( m_flWait );

		StopMovingSound();
		
		if ( m_NoiseArrived != NULL_STRING )
		{
			CPASAttenuationFilter filter( this );

			EmitSound_t ep;
			ep.m_nChannel = CHAN_VOICE;
			ep.m_pSoundName =  STRING(m_NoiseArrived);
			ep.m_flVolume = m_volume;
			ep.m_SoundLevel = SNDLVL_NORM;

			EmitSound( filter, entindex(), ep );
		}

		SetMoveDone( &CFuncTrain::Next );
	}
	else
	{
		// Do it right now
		Next();
	}
}


//-----------------------------------------------------------------------------
// Purpose: Advances the train to the next path corner on the path.
//-----------------------------------------------------------------------------
void CFuncTrain::Next( void )
{
	//Find our next target
	CBaseEntity	*pTarg = GetNextTarget();

    //If none, we're done
	if ( pTarg == NULL )
	{
		//Stop the moving sound
		StopMovingSound();

		// Play stop sound
		if ( m_NoiseArrived != NULL_STRING )
		{
			CPASAttenuationFilter filter( this );

			EmitSound_t ep;
			ep.m_nChannel = CHAN_VOICE;
			ep.m_pSoundName =  STRING(m_NoiseArrived);
			ep.m_flVolume = m_volume;
			ep.m_SoundLevel = SNDLVL_NORM;

			EmitSound( filter, entindex(), ep );
		}

		return;
	}

	// Save last target in case we need to find it again
	m_iszLastTarget = m_target;

	m_target = pTarg->m_target;
	m_flWait = pTarg->GetDelay();

	// If our target has a speed, take it
	if ( m_hCurrentTarget && m_hCurrentTarget->m_flSpeed != 0 )
	{
        m_flSpeed = m_hCurrentTarget->m_flSpeed;
		DevMsg( 2, "Train %s speed to %4.2f\n", GetDebugName(), m_flSpeed );
	}

	// Keep track of this since path corners change our target for us
	m_hCurrentTarget = pTarg;
    m_hEnemy = pTarg;

	//Check for teleport
	if ( m_hCurrentTarget->HasSpawnFlags( SF_CORNER_TELEPORT ) )
	{
		AddEffects( EF_NOINTERP );

		// This is supposed to place the center of the func_train at the target's origin.
		// FIXME: This is totally busted! It's using the wrong space for the computation...
		UTIL_SetOrigin( this, pTarg->GetLocalOrigin() - CollisionProp()->OBBCenter() );
		
		// Get on with doing the next path corner.
		Wait(); 
	}
	else
	{
		// Normal linear move
		PlayMovingSound();

		RemoveEffects( EF_NOINTERP );
		SetMoveDone( &CFuncTrain::Wait );

		// This is supposed to place the center of the func_train at the target's origin.
		// FIXME: This is totally busted! It's using the wrong space for the computation...
		LinearMove ( pTarg->GetLocalOrigin() - CollisionProp()->OBBCenter(), m_flSpeed );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Called after all the entities spawn.
//-----------------------------------------------------------------------------
void CFuncTrain::Activate( void )
{
	BaseClass::Activate();

	// Not yet active, so teleport to first target
	if ( m_activated == false )
	{
		SetupTarget();

		m_activated = true;

		if ( m_hCurrentTarget.Get() == NULL )
			return;

		// This is supposed to place the center of the func_train at the target's origin.
		// FIXME: This is totally busted! It's using the wrong space for the computation...
		UTIL_SetOrigin( this, m_hCurrentTarget->GetLocalOrigin() - CollisionProp()->OBBCenter() );
		if ( GetSolid() == SOLID_BSP )
		{
			VPhysicsInitShadow( false, false );
		}

		// Start immediately if not triggered
		if ( !GetEntityName() )
		{	
			SetMoveDoneTime( 0.1 );
			SetMoveDone( &CFuncTrain::Next );
		}
		else
		{
			m_spawnflags |= SF_TRAIN_WAIT_RETRIGGER;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CFuncTrain::SetupTarget( void )
{
	// Find our target whenever we don't have one (level transition)
	if ( !m_hCurrentTarget )
	{
		CBaseEntity	*pTarg = gEntList.FindEntityByName( NULL, m_target, NULL );

		if ( pTarg == NULL )
		{
			Msg( "Can't find target of train %s\n", STRING(m_target) );
			return;
		}
		
		// Keep track of this since path corners change our target for us
		m_target = pTarg->m_target;
		m_hCurrentTarget = pTarg;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CFuncTrain::Spawn( void )
{
	Precache();
	
	if ( m_flSpeed == 0 )
	{
		m_flSpeed = 100;
	}
	
	if ( !m_target )
	{
		Warning("FuncTrain with no target\n");
	}
	
	if ( m_flBlockDamage == 0 )
	{
		m_flBlockDamage = 2;
	}
	
	SetMoveType( MOVETYPE_PUSH );
	SetSolid( SOLID_BSP );
	SetModel( STRING( GetModelName() ) );
	if ( m_spawnflags & SF_TRACKTRAIN_PASSABLE )
	{
		AddSolidFlags( FSOLID_NOT_SOLID );
	}

	m_activated = false;

	if ( m_volume == 0.0f )
	{
		m_volume = 0.85f;
	}
}


void CFuncTrain::Precache( void )
{
	BaseClass::Precache();
}


void CFuncTrain::OnRestore( void )
{
	BaseClass::OnRestore();

	// Are we moving?
	if ( IsMoving() )
	{
		// Continue moving to the same target
		m_target = m_iszLastTarget;
	}

	SetupTarget();
}


void CFuncTrain::InputToggle( inputdata_t &data )
{
	//If we've been waiting to be retriggered, move to the next destination
	if( HasSpawnFlags( SF_TRAIN_WAIT_RETRIGGER ) )
	{
		Start();
	}
	else
	{
		Stop();
	}
}


void CFuncTrain::InputStart( inputdata_t &data )
{
	Start();
}


void CFuncTrain::InputStop( inputdata_t &data )
{
	Stop();
}


void CFuncTrain::Start( void )
{
	//start moving
	if( HasSpawnFlags( SF_TRAIN_WAIT_RETRIGGER ) )
	{
		// Move toward my target
		RemoveSpawnFlags( SF_TRAIN_WAIT_RETRIGGER );
		Next();
	}
}


void CFuncTrain::Stop( void )
{
	//stop moving
	if( !HasSpawnFlags( SF_TRAIN_WAIT_RETRIGGER ) )
	{
		AddSpawnFlags( SF_TRAIN_WAIT_RETRIGGER );
		
		// Pop back to last target if it's available
		if ( m_hEnemy )
		{
			m_target = m_hEnemy->GetEntityName();
		}

		SetNextThink( TICK_NEVER_THINK );
		SetAbsVelocity( vec3_origin );
		
		if ( m_NoiseArrived != NULL_STRING )
		{
			CPASAttenuationFilter filter( this );

			EmitSound_t ep;
			ep.m_nChannel = CHAN_VOICE;
			ep.m_pSoundName =  STRING(m_NoiseArrived);
			ep.m_flVolume = m_volume;
			ep.m_SoundLevel = SNDLVL_NORM;

			EmitSound( filter, entindex(), ep );
		}

		//Do not teleport to our final move destination
		SetMoveDone( NULL );
		SetMoveDoneTime( -1 );
	}
}

BEGIN_DATADESC( CFuncTrackTrain )

	DEFINE_KEYFIELD( m_length, FIELD_FLOAT, "wheels" ),
	DEFINE_KEYFIELD( m_height, FIELD_FLOAT, "height" ),
	DEFINE_KEYFIELD( m_maxSpeed, FIELD_FLOAT, "startspeed" ),
	DEFINE_KEYFIELD( m_flBank, FIELD_FLOAT, "bank" ),
	DEFINE_KEYFIELD( m_flBlockDamage, FIELD_FLOAT, "dmg" ),
	DEFINE_KEYFIELD( m_iszSoundMove, FIELD_SOUNDNAME, "MoveSound" ),
	DEFINE_KEYFIELD( m_iszSoundMovePing, FIELD_SOUNDNAME, "MovePingSound" ),
	DEFINE_KEYFIELD( m_iszSoundStart, FIELD_SOUNDNAME, "StartSound" ),
	DEFINE_KEYFIELD( m_iszSoundStop, FIELD_SOUNDNAME, "StopSound" ),
	DEFINE_KEYFIELD( m_nMoveSoundMinPitch, FIELD_INTEGER, "MoveSoundMinPitch" ),
	DEFINE_KEYFIELD( m_nMoveSoundMaxPitch, FIELD_INTEGER, "MoveSoundMaxPitch" ),
	DEFINE_KEYFIELD( m_flMoveSoundMinTime, FIELD_FLOAT, "MoveSoundMinTime" ),
	DEFINE_KEYFIELD( m_flMoveSoundMaxTime, FIELD_FLOAT, "MoveSoundMaxTime" ),
	DEFINE_FIELD( m_flNextMoveSoundTime, FIELD_TIME ),
	DEFINE_KEYFIELD( m_eVelocityType, FIELD_INTEGER, "velocitytype" ),
	DEFINE_KEYFIELD( m_eOrientationType, FIELD_INTEGER, "orientationtype" ),

	DEFINE_FIELD( m_ppath, FIELD_CLASSPTR ),
	DEFINE_FIELD( m_dir, FIELD_FLOAT ),
	DEFINE_FIELD( m_controlMins, FIELD_VECTOR ),
	DEFINE_FIELD( m_controlMaxs, FIELD_VECTOR ),
	DEFINE_FIELD( m_flVolume, FIELD_FLOAT ),
	DEFINE_FIELD( m_oldSpeed, FIELD_FLOAT ),

	DEFINE_FIELD( m_bSoundPlaying, FIELD_BOOLEAN ),

#ifdef HL1_DLL
	DEFINE_FIELD( m_bOnTrackChange, FIELD_BOOLEAN ),
#endif

	// Inputs
	DEFINE_INPUTFUNC( FIELD_VOID, "Stop", InputStop ),
	DEFINE_INPUTFUNC( FIELD_VOID, "StartForward", InputStartForward ),
	DEFINE_INPUTFUNC( FIELD_VOID, "StartBackward", InputStartBackward ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Resume", InputResume ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Reverse", InputReverse ),
	DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeed", InputSetSpeed ),
	DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeedDir", InputSetSpeedDir ),
	DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeedReal", InputSetSpeedReal ),

	// Function Pointers
	DEFINE_FUNCTION( Next ),
	DEFINE_FUNCTION( Find ),
	DEFINE_FUNCTION( NearestPath ),
	DEFINE_FUNCTION( DeadEnd ),

END_DATADESC()

LINK_ENTITY_TO_CLASS( func_tracktrain, CFuncTrackTrain );


//-----------------------------------------------------------------------------
// Datatable
//-----------------------------------------------------------------------------
IMPLEMENT_SERVERCLASS_ST( CFuncTrackTrain, DT_FuncTrackTrain )
END_SEND_TABLE()


//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
CFuncTrackTrain::CFuncTrackTrain()
{
#ifdef _DEBUG
	m_controlMins.Init();
	m_controlMaxs.Init();
#endif

	// These defaults match old func_tracktrains. Changing these defaults would
	// require a vmf_tweak of older content to keep it from breaking.
	m_eOrientationType = TrainOrientation_AtPathTracks;
	m_eVelocityType = TrainVelocity_Instantaneous;
}


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

	if (m_debugOverlays & OVERLAY_TEXT_BIT) 
	{
		char tempstr[512];
		Q_snprintf( tempstr,sizeof(tempstr), "angles: %g %g %g", (double)GetLocalAngles()[PITCH], (double)GetLocalAngles()[YAW], (double)GetLocalAngles()[ROLL] );
		NDebugOverlay::EntityText( entindex(), nOffset, tempstr, 0 );
		nOffset++;

		float flCurSpeed = GetLocalVelocity().Length();
		Q_snprintf( tempstr,sizeof(tempstr), "current speed (goal): %g (%g)", (double)flCurSpeed, (double)m_flSpeed );
		NDebugOverlay::EntityText( entindex(), nOffset, tempstr, 0 );
		nOffset++;

		Q_snprintf( tempstr,sizeof(tempstr), "max speed: %g", (double)m_maxSpeed );
		NDebugOverlay::EntityText( entindex(), nOffset, tempstr, 0 );
		nOffset++;
	}

	return nOffset;
}


void CFuncTrackTrain::DrawDebugGeometryOverlays()
{
	BaseClass::DrawDebugGeometryOverlays();
	if (m_debugOverlays & OVERLAY_BBOX_BIT) 
	{
		NDebugOverlay::Box( GetAbsOrigin(), -Vector(4,4,4),Vector(4,4,4), 255, 0, 255, 0, 0);
		Vector out;
		VectorTransform( Vector(m_length,0,0), EntityToWorldTransform(), out );
		NDebugOverlay::Box( out, -Vector(4,4,4),Vector(4,4,4), 255, 0, 255, 0, 0);
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CFuncTrackTrain::KeyValue( const char *szKeyName, const char *szValue )
{
	if (FStrEq(szKeyName, "volume"))
	{
		m_flVolume = (float) (atoi(szValue));
		m_flVolume *= 0.1f;
	}
	else
	{
		return BaseClass::KeyValue( szKeyName, szValue );
	}

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: Input handler that stops the train.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::InputStop( inputdata_t &inputdata )
{
	Stop();
}


//------------------------------------------------------------------------------
// Purpose: Input handler that starts the train moving.
//------------------------------------------------------------------------------
void CFuncTrackTrain::InputResume( inputdata_t &inputdata )
{
	m_flSpeed = m_oldSpeed;
	Start();
}


//------------------------------------------------------------------------------
// Purpose: Input handler that reverses the trains current direction of motion.
//------------------------------------------------------------------------------
void CFuncTrackTrain::InputReverse( inputdata_t &inputdata )
{
	SetDirForward( !IsDirForward() );
	SetSpeed( m_flSpeed );
}


//-----------------------------------------------------------------------------
// Purpose: Returns whether we are travelling forward along our path.
//-----------------------------------------------------------------------------
bool CFuncTrackTrain::IsDirForward()
{
	return ( m_dir == 1 );
}


//-----------------------------------------------------------------------------
// Purpose: Sets whether we go forward or backward along our path.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::SetDirForward( bool bForward )
{
	if ( bForward && ( m_dir != 1 ) )
	{
		// Reverse direction.
		if ( m_ppath && m_ppath->GetPrevious() )
		{
			m_ppath = m_ppath->GetPrevious();
		}

		m_dir = 1;
	}
	else if ( !bForward && ( m_dir != -1 ) )
	{
		// Reverse direction.
		if ( m_ppath && m_ppath->GetNext() )
		{
			m_ppath = m_ppath->GetNext();
		}

		m_dir = -1;
	}
}


//------------------------------------------------------------------------------
// Purpose: Input handler that starts the train moving.
//------------------------------------------------------------------------------
void CFuncTrackTrain::InputStartForward( inputdata_t &inputdata )
{
	SetDirForward( true );
	SetSpeed( m_maxSpeed );
}


//------------------------------------------------------------------------------
// Purpose: Input handler that starts the train moving.
//------------------------------------------------------------------------------
void CFuncTrackTrain::InputStartBackward( inputdata_t &inputdata )
{
	SetDirForward( false );
	SetSpeed( m_maxSpeed );
}


//------------------------------------------------------------------------------
// Purpose: Starts the train moving.
//------------------------------------------------------------------------------
void CFuncTrackTrain::Start( void )
{
	Next();
}


//-----------------------------------------------------------------------------
// Purpose: Toggles the train between moving and not moving.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::InputToggle( inputdata_t &inputdata )
{
	if ( m_flSpeed == 0 )
	{
		SetSpeed( m_maxSpeed );
	}
	else
	{
		SetSpeed( 0 );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Handles player use so players can control the speed of the train.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	// player +USE
	if ( useType == USE_SET )
	{
		float delta = value;

		delta = ((int)(m_flSpeed * 4) / (int)m_maxSpeed)*0.25 + 0.25 * delta;
		if ( delta > 1 )
			delta = 1;
		else if ( delta < -0.25 )
			delta = -0.25;
		if ( m_spawnflags & SF_TRACKTRAIN_FORWARDONLY )
		{
			if ( delta < 0 )
				delta = 0;
		}
		SetDirForward( delta >= 0 );
		delta = fabs(delta);
		SetSpeed( m_maxSpeed * delta );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Input handler that sets the speed of the train.
// Input  : Float speed from 0 to max speed, in units per second.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::InputSetSpeedReal( inputdata_t &inputdata )
{
	SetSpeed( clamp( inputdata.value.Float(), 0, m_maxSpeed ) );
}


//-----------------------------------------------------------------------------
// Purpose: Input handler that sets the speed of the train.
// Input  : Float speed scale from 0 to 1.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::InputSetSpeed( inputdata_t &inputdata )
{
	float flScale = clamp( inputdata.value.Float(), 0, 1 );
	SetSpeed( m_maxSpeed * flScale );
}


//-----------------------------------------------------------------------------
// Purpose: Input handler that sets the speed of the train and the direction
//			based on the sign of the speed.
// Input  : Float speed scale from -1 to 1. Negatives values indicate a reversed
//			direction.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::InputSetSpeedDir( inputdata_t &inputdata )
{
	float newSpeed = inputdata.value.Float();
	SetDirForward( newSpeed >= 0 );
	newSpeed = fabs(newSpeed);
	float flScale = clamp( newSpeed, 0, 1 );
	SetSpeed( m_maxSpeed * flScale );
}


//-----------------------------------------------------------------------------
// Purpose: Sets the speed of the train to the given value in units per second.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::SetSpeed( float flSpeed )
{
	float flOldSpeed = m_flSpeed;
	m_flSpeed = fabs( flSpeed ) * m_dir;

	if ( m_flSpeed != flOldSpeed)
	{
		// Changing speed.
		if ( m_flSpeed != 0 )
		{
			if ( flOldSpeed == 0 )
			{
				// Starting to move.
				Start();
			}
			else
			{
				// Continuing to move.
				Next();
			}
		}
		else
		{
			// Stopping.
			Stop();
		}
	}

	DevMsg( 2, "TRAIN(%s), speed to %.2f\n", GetDebugName(), m_flSpeed );
}


//-----------------------------------------------------------------------------
// Purpose: Stops the train.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::Stop( void )
{
	SetLocalVelocity( vec3_origin );
	SetLocalAngularVelocity( vec3_angle );
	m_oldSpeed = m_flSpeed;
	m_flSpeed = 0;
	SoundStop();
	SetThink(NULL);
}

#include "vphysics/friction.h"
CBaseEntity *CFuncTrackTrain::FindPhysicsBlocker( IPhysicsObject *pPhysics )
{
	IPhysicsFrictionSnapshot *pSnapshot = pPhysics->CreateFrictionSnapshot();
	CBaseEntity *pBlocker = NULL;
	float maxForce = 0;
	while ( pSnapshot->IsValid() )
	{
		IPhysicsObject *pOther = pSnapshot->GetObject(1);
		CBaseEntity *pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData());
		if ( pOtherEntity->GetMoveType() == MOVETYPE_VPHYSICS )
		{
			Vector normal;
			pSnapshot->GetSurfaceNormal(normal);
			float dot = DotProduct( GetAbsVelocity(), pSnapshot->GetNormalForce() * normal );
			if ( !pBlocker || dot > maxForce )
			{
				pBlocker = pOtherEntity;
				maxForce = dot;
			}
		}
		pSnapshot->NextFrictionData();
	}
	pPhysics->DestroyFrictionSnapshot( pSnapshot );

	return pBlocker;
}

//-----------------------------------------------------------------------------
// Purpose: Called when we are blocked by another entity.
// Input  : pOther - 
//-----------------------------------------------------------------------------
void CFuncTrackTrain::Blocked( CBaseEntity *pOther )
{
	// Blocker is on-ground on the train
	if ( ( pOther->GetFlags() & FL_ONGROUND ) && pOther->GetGroundEntity() == this )
	{
		DevMsg( 1, "TRAIN(%s): Blocked by %s\n", GetDebugName(), pOther->GetClassname() );
		float deltaSpeed = fabs(m_flSpeed);
		if ( deltaSpeed > 50 )
			deltaSpeed = 50;

		Vector vecNewVelocity;
		pOther->GetVelocity( &vecNewVelocity );
		if ( !vecNewVelocity.z )
		{
			pOther->ApplyAbsVelocityImpulse( Vector(0,0,deltaSpeed) );
		}
		return;
	}
	else
	{
		Vector vecNewVelocity;
		vecNewVelocity = pOther->GetAbsOrigin() - GetAbsOrigin();
		VectorNormalize(vecNewVelocity);
		vecNewVelocity *= m_flBlockDamage;
		pOther->SetAbsVelocity( vecNewVelocity );
	}
	if ( HasSpawnFlags(SF_TRACKTRAIN_UNBLOCKABLE_BY_PLAYER) )
	{
		CBaseEntity *pPhysicsBlocker = FindPhysicsBlocker(VPhysicsGetObject());
		if ( pPhysicsBlocker )
		{
			EntityPhysics_CreateSolver( this, pPhysicsBlocker, true, 4.0f );
		}
	}

	DevWarning( 2, "TRAIN(%s): Blocked by %s (dmg:%.2f)\n", GetDebugName(), pOther->GetClassname(), m_flBlockDamage );
	if ( m_flBlockDamage <= 0 )
		return;

	// we can't hurt this thing, so we're not concerned with it
	pOther->TakeDamage( CTakeDamageInfo( this, this, m_flBlockDamage, DMG_CRUSH ) );
}


extern void FixupAngles( QAngle &v );

#define TRAIN_MAXSPEED		1000	// approx max speed for sound pitch calculation


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CFuncTrackTrain::SoundStop( void )
{
	// if sound playing, stop it
	if ( m_bSoundPlaying )
	{
		if ( m_iszSoundMove != NULL_STRING )
		{
			StopSound( entindex(), CHAN_STATIC, STRING( m_iszSoundMove ) );
		}

		if ( m_iszSoundStop != NULL_STRING )
		{
			CPASAttenuationFilter filter( this );

			EmitSound_t ep;
			ep.m_nChannel = CHAN_ITEM;
			ep.m_pSoundName =  STRING(m_iszSoundStop);
			ep.m_flVolume = m_flVolume;
			ep.m_SoundLevel = SNDLVL_NORM;

			EmitSound( filter, entindex(), ep );
		}
	}

	m_bSoundPlaying = false;
}


//-----------------------------------------------------------------------------
// Purpose: Update pitch based on speed, start sound if not playing.
//			NOTE: when train goes through transition, m_bSoundPlaying should become
//			false, which will cause the looped sound to restart.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::SoundUpdate( void )
{
	if ( ( !m_iszSoundMove ) && ( !m_iszSoundStart ) && ( !m_iszSoundMovePing ))
	{
		return;
	}

	float flSpeedRatio = 0;
	if ( HasSpawnFlags( SF_TRACKTRAIN_USE_MAXSPEED_FOR_PITCH ) )
	{
		flSpeedRatio = clamp( fabs( m_flSpeed ) / m_maxSpeed, 0, 1 );
	}
	else
	{
		flSpeedRatio = clamp( fabs( m_flSpeed ) / TRAIN_MAXSPEED, 0, 1 );
	}

	float flpitch = RemapVal( flSpeedRatio, 0, 1, m_nMoveSoundMinPitch, m_nMoveSoundMaxPitch );

	CPASAttenuationFilter filter( this );
	CPASAttenuationFilter filterReliable( this );
	filterReliable.MakeReliable();

	Vector vecWorldSpaceCenter = WorldSpaceCenter();

	if (!m_bSoundPlaying)
	{
		if ( m_iszSoundStart != NULL_STRING )
		{
			EmitSound_t ep;
			ep.m_nChannel = CHAN_ITEM;
			ep.m_pSoundName =  STRING(m_iszSoundStart);
			ep.m_flVolume = m_flVolume;
			ep.m_SoundLevel = SNDLVL_NORM;
			ep.m_pOrigin = &vecWorldSpaceCenter;

			EmitSound( filter, entindex(), ep );
		}

		if ( m_iszSoundMove != NULL_STRING )
		{
			EmitSound_t ep;
			ep.m_nChannel = CHAN_STATIC;
			ep.m_pSoundName =  STRING(m_iszSoundMove);
			ep.m_flVolume = m_flVolume;
			ep.m_SoundLevel = SNDLVL_NORM;
			ep.m_nPitch = (int)flpitch;
			ep.m_pOrigin = &vecWorldSpaceCenter;

			EmitSound( filterReliable, entindex(), ep );
		}

		// We've just started moving. Delay the next move ping sound.
		m_flNextMoveSoundTime = gpGlobals->curtime + RemapVal( flSpeedRatio, 0, 1, m_flMoveSoundMaxTime, m_flMoveSoundMinTime );

		m_bSoundPlaying = true;
	} 
	else
	{
		if ( m_iszSoundMove != NULL_STRING )
		{
			// update pitch
			EmitSound_t ep;
			ep.m_nChannel = CHAN_STATIC;
			ep.m_pSoundName =  STRING(m_iszSoundMove);
			ep.m_flVolume = m_flVolume;
			ep.m_SoundLevel = SNDLVL_NORM;
			ep.m_nPitch = (int)flpitch;
			ep.m_nFlags = SND_CHANGE_PITCH;
			ep.m_pOrigin = &vecWorldSpaceCenter;

			EmitSound( filterReliable, entindex(), ep );
		}

		if ( ( m_iszSoundMovePing != NULL_STRING ) && ( gpGlobals->curtime > m_flNextMoveSoundTime ) )
		{
			EmitSound(STRING(m_iszSoundMovePing));
			m_flNextMoveSoundTime = gpGlobals->curtime + RemapVal( flSpeedRatio, 0, 1, m_flMoveSoundMaxTime, m_flMoveSoundMinTime );
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pNode - 
//-----------------------------------------------------------------------------
void CFuncTrackTrain::ArriveAtNode( CPathTrack *pNode )
{
	// BUGBUG: This is wrong.  We need to fire all targets between the one we've passed and the one
	// we've switched to.
	FirePassInputs( pNode, pNode->GetNext(), true );

	//
	// Disable train controls if this path track says to do so.
	//
	if ( pNode->HasSpawnFlags( SF_PATH_DISABLE_TRAIN ) )
	{
		m_spawnflags |= SF_TRACKTRAIN_NOCONTROL;
	}
	
	//
	// Don't override the train speed if it's under user control.
	//
	if ( m_spawnflags & SF_TRACKTRAIN_NOCONTROL )
	{
		//
		// Don't copy speed from path track if it is 0 (uninitialized).
		//
		if ( pNode->m_flSpeed != 0 )
		{
			SetSpeed( pNode->m_flSpeed );
			DevMsg( 2, "TrackTrain %s arrived at %s, speed to %4.2f\n", GetDebugName(), pNode->GetDebugName(), pNode->m_flSpeed );
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Controls how the train accelerates as it moves along the path.
//-----------------------------------------------------------------------------
TrainVelocityType_t CFuncTrackTrain::GetTrainVelocityType()
{
	return m_eVelocityType;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pnext - 
//-----------------------------------------------------------------------------
void CFuncTrackTrain::UpdateTrainVelocity( CPathTrack *pPrev, CPathTrack *pNext, const Vector &nextPos, float flInterval )
{
	switch ( GetTrainVelocityType() )
	{
		case TrainVelocity_Instantaneous:
		{
			Vector velDesired = nextPos - GetLocalOrigin();
			VectorNormalize( velDesired );
			velDesired *= fabs( m_flSpeed );
			SetLocalVelocity( velDesired );
			break;
		}

		case TrainVelocity_LinearBlend:
		case TrainVelocity_EaseInEaseOut:
		{
			if ( pPrev && pNext )
			{
				// Get the speed to blend from.
				float flPrevSpeed = m_flSpeed;
				if ( pPrev->m_flSpeed != 0 )
				{
					flPrevSpeed = pPrev->m_flSpeed;
				}

				// Get the speed to blend to.
				float flNextSpeed = flPrevSpeed;
				if ( pNext->m_flSpeed != 0 )
				{
					flNextSpeed = pNext->m_flSpeed;
				}

				// If they're different, do the blend.
				if ( flPrevSpeed != flNextSpeed )
				{
					Vector vecSegment = pNext->GetLocalOrigin() - pPrev->GetLocalOrigin();
					float flSegmentLen = vecSegment.Length();
					if ( flSegmentLen )
					{
						Vector vecCurOffset = GetLocalOrigin() - pPrev->GetLocalOrigin();
						float p = vecCurOffset.Length() / flSegmentLen;
						if ( GetTrainVelocityType() == TrainVelocity_EaseInEaseOut )
						{
							p = SimpleSplineRemapVal( p, 0.0f, 1.0f, 0.0f, 1.0f );
						}

						m_flSpeed = m_dir * ( flPrevSpeed * ( 1 - p ) + flNextSpeed * p );
					}
				}
				else
				{
					m_flSpeed = m_dir * flPrevSpeed;
				}
			}

			Vector velDesired = nextPos - GetLocalOrigin();
			VectorNormalize( velDesired );
			velDesired *= fabs( m_flSpeed );
			SetLocalVelocity( velDesired );
			break;
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Controls how the train blends angles as it moves along the path.
//-----------------------------------------------------------------------------
TrainOrientationType_t CFuncTrackTrain::GetTrainOrientationType()
{
#ifdef HL1_DLL
	return TrainOrientation_AtPathTracks;
#else
	return m_eOrientationType;
#endif
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pnext - 
//-----------------------------------------------------------------------------
void CFuncTrackTrain::UpdateTrainOrientation( CPathTrack *pPrev, CPathTrack *pNext, const Vector &nextPos, float flInterval )
{
	// FIXME: old way of doing fixed orienation trains, remove!
	if ( HasSpawnFlags( SF_TRACKTRAIN_FIXED_ORIENTATION ) )
		return;

	// Trains *can* work in local space, but only if all elements of the track share
	// the same move parent as the train.
	Assert( !pPrev || (pPrev->GetMoveParent() == GetMoveParent()) );

	switch ( GetTrainOrientationType() )
	{
		case TrainOrientation_Fixed:
		{
			// Fixed orientation. Do nothing.
			break;
		}

		case TrainOrientation_AtPathTracks:
		{
			UpdateOrientationAtPathTracks( pPrev, pNext, nextPos, flInterval );
			break;
		}

		case TrainOrientation_EaseInEaseOut:
		case TrainOrientation_LinearBlend:
		{
			UpdateOrientationBlend( GetTrainOrientationType(), pPrev, pNext, nextPos, flInterval );
			break;
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Adjusts our angles as we hit each path track. This is for support of
//			trains with wheels that round corners a la HL1 trains.
// FIXME: move into path_track, have the angles come back from LookAhead
//-----------------------------------------------------------------------------
void CFuncTrackTrain::UpdateOrientationAtPathTracks( CPathTrack *pPrev, CPathTrack *pNext, const Vector &nextPos, float flInterval )
{
	if ( !m_ppath )
		return;

	Vector nextFront = GetLocalOrigin();

	nextFront.z -= m_height;
	if ( m_length > 0 )
	{
		m_ppath->LookAhead( nextFront, IsDirForward() ? m_length : -m_length, 0 );
	}
	else
	{
		m_ppath->LookAhead( nextFront, IsDirForward() ? 100 : -100, 0 );
	}
	nextFront.z += m_height;

	Vector vecFaceDir = nextFront - GetLocalOrigin();
	if ( !IsDirForward() )
	{
		vecFaceDir *= -1;
	}
	QAngle angles;
	VectorAngles( vecFaceDir, angles );
	// !!!  All of this crap has to be done to make the angles not wrap around, revisit this.
	FixupAngles( angles );

	QAngle curAngles = GetLocalAngles();
	FixupAngles( curAngles );

	if ( !pPrev || (vecFaceDir.x == 0 && vecFaceDir.y == 0) )
		angles = curAngles;

	DoUpdateOrientation( curAngles, angles, flInterval );
}


//-----------------------------------------------------------------------------
// Purpose: Blends our angles using one of two orientation blending types.
//			ASSUMES that eOrientationType is either LinearBlend or EaseInEaseOut.
//			FIXME: move into path_track, have the angles come back from LookAhead
//-----------------------------------------------------------------------------
void CFuncTrackTrain::UpdateOrientationBlend( TrainOrientationType_t eOrientationType, CPathTrack *pPrev, CPathTrack *pNext, const Vector &nextPos, float flInterval )
{
	// Get the angles to blend from.
	QAngle angPrev = pPrev->GetOrientation( IsDirForward() );
	FixupAngles( angPrev );

	// Get the angles to blend to. 
	QAngle angNext;
	if ( pNext )
	{
		angNext = pNext->GetOrientation( IsDirForward() );
		FixupAngles( angNext );
	}
	else
	{
		// At a dead end, just use the last path track's angles.
		angNext = angPrev;
	}

	if ( m_spawnflags & SF_TRACKTRAIN_NOPITCH )
	{
		angNext[PITCH] = angPrev[PITCH];
	}

	// Calculate our parametric distance along the path segment from 0 to 1.
	float p = 0;
	if ( pPrev && ( angPrev != angNext ) )
	{
		Vector vecSegment = pNext->GetLocalOrigin() - pPrev->GetLocalOrigin();
		float flSegmentLen = vecSegment.Length();
		if ( flSegmentLen )
		{
			Vector vecCurOffset = GetLocalOrigin() - pPrev->GetLocalOrigin();
			p = vecCurOffset.Length() / flSegmentLen;
		}
	}

	if ( eOrientationType == TrainOrientation_EaseInEaseOut )
	{
		p = SimpleSplineRemapVal( p, 0.0f, 1.0f, 0.0f, 1.0f );
	}

	//Msg( "UpdateOrientationFacePathAngles: %s->%s, p=%f, ", pPrev->GetDebugName(), pNext->GetDebugName(), p );

	Quaternion qtPrev;
	Quaternion qtNext;
	
	AngleQuaternion( angPrev, qtPrev );
	AngleQuaternion( angNext, qtNext );

	QAngle angNew = angNext;
	float flAngleDiff = QuaternionAngleDiff( qtPrev, qtNext );
	if ( flAngleDiff )
	{
		Quaternion qtNew;
		QuaternionSlerp( qtPrev, qtNext, p, qtNew );
		QuaternionAngles( qtNew, angNew );
	}

	if ( m_spawnflags & SF_TRACKTRAIN_NOPITCH )
	{
		angNew[PITCH] = angPrev[PITCH];
	}

	DoUpdateOrientation( GetLocalAngles(), angNew, flInterval );
}


//-----------------------------------------------------------------------------
// Purpose: Sets our angular velocity to approach the target angles over the given interval.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::DoUpdateOrientation( const QAngle &curAngles, const QAngle &angles, float flInterval )
{
	float vy, vx;
	if ( !(m_spawnflags & SF_TRACKTRAIN_NOPITCH) )
	{
		vx = UTIL_AngleDistance( angles.x, curAngles.x );
	}
	else
	{
		vx = 0;
	}

	vy = UTIL_AngleDistance( angles.y, curAngles.y );
	
	// HACKHACK: Clamp really small angular deltas to avoid rotating movement on things
	// that are close enough
	if ( fabs(vx) < 0.1 )
	{
		vx = 0;
	}
	if ( fabs(vy) < 0.1 )
	{
		vy = 0;
	}

	if ( flInterval == 0 )
	{
		// Avoid dividing by zero
		flInterval = 0.1;
	}

	QAngle vecAngVel( vx / flInterval, vy / flInterval, GetLocalAngularVelocity().z );

	if ( m_flBank != 0 )
	{
		if ( vecAngVel.y < -5 )
		{
			vecAngVel.z = UTIL_AngleDistance( UTIL_ApproachAngle( -m_flBank, curAngles.z, m_flBank*2 ), curAngles.z);
		}
		else if ( vecAngVel.y > 5 )
		{
			vecAngVel.z = UTIL_AngleDistance( UTIL_ApproachAngle( m_flBank, curAngles.z, m_flBank*2 ), curAngles.z);
		}
		else
		{
			vecAngVel.z = UTIL_AngleDistance( UTIL_ApproachAngle( 0, curAngles.z, m_flBank*4 ), curAngles.z) * 4;
		}
	}
	
	SetLocalAngularVelocity( vecAngVel );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pTeleport - 
//-----------------------------------------------------------------------------
void CFuncTrackTrain::TeleportToPathTrack( CPathTrack *pTeleport )
{
	QAngle angCur = GetLocalAngles();

	Vector nextPos = pTeleport->GetLocalOrigin();
	Vector look = nextPos;
	pTeleport->LookAhead( look, m_length, 0 );

	QAngle nextAngles;
	if ( HasSpawnFlags( SF_TRACKTRAIN_FIXED_ORIENTATION ) || ( look == nextPos ) )
	{
		nextAngles = GetLocalAngles();
	}
	else
	{
		nextAngles = pTeleport->GetOrientation( IsDirForward() );
		if ( HasSpawnFlags( SF_TRACKTRAIN_NOPITCH ) )
		{
			nextAngles[PITCH] = angCur[PITCH];
		}
	}

	Teleport( &pTeleport->GetLocalOrigin(), &nextAngles, NULL );
	SetLocalAngularVelocity( vec3_angle );
}


//-----------------------------------------------------------------------------
// Purpose: Advances the train to the next path corner on the path.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::Next( void )
{
	if ( !m_flSpeed )
	{
		DevMsg( 2, "TRAIN(%s): Speed is 0\n", GetDebugName() );
		SoundStop();
		return;
	}

	if ( !m_ppath )
	{	
		DevMsg( 2, "TRAIN(%s): Lost path\n", GetDebugName() );
		SoundStop();
		m_flSpeed = 0;
		return;
	}

	SoundUpdate();

	//
	// Based on our current position and speed, look ahead along our path and see
	// where we should be in 0.1 seconds.
	//
	Vector nextPos = GetLocalOrigin();
	float flSpeed = m_flSpeed;

	nextPos.z -= m_height;
	CPathTrack *pNextNext = NULL;
	CPathTrack *pNext = m_ppath->LookAhead( nextPos, flSpeed * 0.1, 1, &pNextNext );
	//Assert( pNext != NULL );

	if (m_debugOverlays & OVERLAY_BBOX_BIT) 
	{
		if ( pNext != NULL )
		{
			NDebugOverlay::Line( pNext->GetAbsOrigin(), pNext->GetAbsOrigin() + Vector( 0,0,32), 255, 0, 0, true, 0.1 );
			NDebugOverlay::Box( pNext->GetAbsOrigin(), Vector( -8, -8, -8 ), Vector( 8, 8, 8 ), 255, 0, 0, 0, 0.1 );
		}

		if ( pNextNext != NULL )
		{
			NDebugOverlay::Line( pNextNext->GetAbsOrigin(), pNextNext->GetAbsOrigin() + Vector( 0,0,32), 0, 255, 0, true, 0.1 );
			NDebugOverlay::Box( pNextNext->GetAbsOrigin(), Vector( -8, -8, -8 ), Vector( 8, 8, 8 ), 0, 255, 0, 0, 0.1 );
		}
	}

	nextPos.z += m_height;

	// Trains *can* work in local space, but only if all elements of the track share
	// the same move parent as the train.
	Assert( !pNext || (pNext->GetMoveParent() == GetMoveParent()) );

	if ( pNext )
	{
		UpdateTrainVelocity( pNext, pNextNext, nextPos, gpGlobals->frametime );
		UpdateTrainOrientation( pNext, pNextNext, nextPos, gpGlobals->frametime );

		if ( pNext != m_ppath )
		{
			//
			// We have reached a new path track. Fire its OnPass output.
			//
			m_ppath = pNext;
			ArriveAtNode( pNext );
#ifdef HL1_DLL
			m_bOnTrackChange = false;
#endif

			//
			// See if we should teleport to the next path track.
			//
			CPathTrack *pTeleport = pNext->GetNext();
			if ( ( pTeleport != NULL ) && pTeleport->HasSpawnFlags( SF_PATH_TELEPORT ) )
			{
				TeleportToPathTrack( pTeleport );
			}
		}

		SetThink( &CFuncTrackTrain::Next );
		SetMoveDoneTime( 0.5 );
		SetNextThink( gpGlobals->curtime );
		SetMoveDone( NULL );
	}
	else
	{
		//
		// We've reached the end of the path, stop.
		//
		SoundStop();
		SetLocalVelocity(nextPos - GetLocalOrigin());
		SetLocalAngularVelocity( vec3_angle );
		float distance = GetLocalVelocity().Length();
		m_oldSpeed = m_flSpeed;

		m_flSpeed = 0;
		
		// Move to the dead end
		
		// Are we there yet?
		if ( distance > 0 )
		{
			// no, how long to get there?
			float flTime = distance / fabs( m_oldSpeed );
			SetLocalVelocity( GetLocalVelocity() * (m_oldSpeed / distance) );
			SetMoveDone( &CFuncTrackTrain::DeadEnd );
			SetNextThink( TICK_NEVER_THINK );
			SetMoveDoneTime( flTime );
		}
		else
		{
			DeadEnd();
		}
	}
}


void CFuncTrackTrain::FirePassInputs( CPathTrack *pStart, CPathTrack *pEnd, bool forward )
{
	CPathTrack *pCurrent = pStart;

	// swap if going backward
	if ( !forward )
	{
		pCurrent = pEnd;
		pEnd = pStart;
	}
	variant_t emptyVariant;

	while ( pCurrent && pCurrent != pEnd )
	{
		//Msg("Fired pass on %s\n", STRING(pCurrent->GetEntityName()) );
		pCurrent->AcceptInput( "InPass", this, this, emptyVariant, 0 );
		pCurrent = forward ? pCurrent->GetNext() : pCurrent->GetPrevious();
	}
}


void CFuncTrackTrain::DeadEnd( void )
{
	// Fire the dead-end target if there is one
	CPathTrack *pTrack, *pNext;

	pTrack = m_ppath;

	DevMsg( 2, "TRAIN(%s): Dead end ", GetDebugName() );
	// Find the dead end path node
	// HACKHACK -- This is bugly, but the train can actually stop moving at a different node depending on it's speed
	// so we have to traverse the list to it's end.
	if ( pTrack )
	{
		if ( m_oldSpeed < 0 )
		{
			do
			{
				pNext = pTrack->ValidPath( pTrack->GetPrevious(), true );
				if ( pNext )
					pTrack = pNext;
			} while ( pNext );
		}
		else
		{
			do
			{
				pNext = pTrack->ValidPath( pTrack->GetNext(), true );
				if ( pNext )
					pTrack = pNext;
			} while ( pNext );
		}
	}

	SetLocalVelocity( vec3_origin );
	SetLocalAngularVelocity( vec3_angle );
	if ( pTrack )
	{
		DevMsg( 2, "at %s\n", pTrack->GetDebugName() );
		variant_t emptyVariant;
		pTrack->AcceptInput( "InPass", this, this, emptyVariant, 0 );
	}
	else
	{
		DevMsg( 2, "\n" );
	}
}


void CFuncTrackTrain::SetControls( CBaseEntity *pControls )
{
	Vector offset = pControls->GetLocalOrigin();

	m_controlMins = pControls->WorldAlignMins() + offset;
	m_controlMaxs = pControls->WorldAlignMaxs() + offset;
}


//-----------------------------------------------------------------------------
// Purpose: Returns true if the entity's origin is within the controls region.
//-----------------------------------------------------------------------------
bool CFuncTrackTrain::OnControls( CBaseEntity *pTest )
{
	Vector offset = pTest->GetLocalOrigin() - GetLocalOrigin();

	if ( m_spawnflags & SF_TRACKTRAIN_NOCONTROL )
		return false;

	// Transform offset into local coordinates
	VMatrix tmp = SetupMatrixAngles( GetLocalAngles() );
	// rotate into local space
	Vector local = tmp.VMul3x3Transpose( offset );

	/*
	NDebugOverlay::Box( GetLocalOrigin(), m_controlMins, m_controlMaxs,
				255, 0, 0, 100, 5.0 );

	NDebugOverlay::Box( GetLocalOrigin() + local, Vector(-5,-5,-5), Vector(5,5,5),
				0, 0, 255, 100, 5.0 );
	*/

	if ( local.x >= m_controlMins.x && local.y >= m_controlMins.y && local.z >= m_controlMins.z &&
		 local.x <= m_controlMaxs.x && local.y <= m_controlMaxs.y && local.z <= m_controlMaxs.z )
		 return true;

	return false;
}


void CFuncTrackTrain::Find( void )
{
	m_ppath = (CPathTrack *)gEntList.FindEntityByName( NULL, m_target, NULL );
	if ( !m_ppath )
		return;

	if ( !FClassnameIs( m_ppath, "path_track" ) )
	{
		Warning( "func_track_train must be on a path of path_track\n" );
		Assert(0);
		m_ppath = NULL;
		return;
	}

	Vector nextPos = m_ppath->GetLocalOrigin();
	Vector look = nextPos;
	m_ppath->LookAhead( look, m_length, 0 );
	nextPos.z += m_height;
	look.z += m_height;

	QAngle nextAngles;
	if ( HasSpawnFlags( SF_TRACKTRAIN_FIXED_ORIENTATION ) )
	{
		nextAngles = GetLocalAngles();
	}
	else
	{
		VectorAngles( look - nextPos, nextAngles );
		if ( HasSpawnFlags( SF_TRACKTRAIN_NOPITCH ) )
		{
			nextAngles.x = 0;
		}
	}

	Teleport( &nextPos, &nextAngles, NULL );

	ArriveAtNode( m_ppath );

	if ( m_flSpeed != 0 )
	{
		SetNextThink( gpGlobals->curtime + 0.1f );
		SetThink( &CFuncTrackTrain::Next );
		SoundUpdate();
	}
}


void CFuncTrackTrain::NearestPath( void )
{
	CBaseEntity *pTrack = NULL;
	CBaseEntity *pNearest = NULL;
	float dist, closest;

	closest = 1024;

	for ( CEntitySphereQuery sphere( GetAbsOrigin(), 1024 ); ( pTrack = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
	{
		// filter out non-tracks
		if ( !(pTrack->GetFlags() & (FL_CLIENT|FL_NPC)) && FClassnameIs( pTrack, "path_track" ) )
		{
			dist = (GetAbsOrigin() - pTrack->GetAbsOrigin()).Length();
			if ( dist < closest )
			{
				closest = dist;
				pNearest = pTrack;
			}
		}
	}

	if ( !pNearest )
	{
		Msg( "Can't find a nearby track !!!\n" );
		SetThink(NULL);
		return;
	}

	DevMsg( 2, "TRAIN: %s, Nearest track is %s\n", GetDebugName(), pNearest->GetDebugName() );
	// If I'm closer to the next path_track on this path, then it's my real path
	pTrack = ((CPathTrack *)pNearest)->GetNext();
	if ( pTrack )
	{
		if ( (GetLocalOrigin() - pTrack->GetLocalOrigin()).Length() < (GetLocalOrigin() - pNearest->GetLocalOrigin()).Length() )
			pNearest = pTrack;
	}

	m_ppath = (CPathTrack *)pNearest;

	if ( m_flSpeed != 0 )
	{
		SetMoveDoneTime( 0.1 );
		SetMoveDone( &CFuncTrackTrain::Next );
	}
}

void CFuncTrackTrain::OnRestore( void )
{
	BaseClass::OnRestore();
	if ( !m_ppath 
#ifdef HL1_DLL
		&& !m_bOnTrackChange
#endif	
		)
	{
		NearestPath();
		SetThink( NULL );
	}
}


CFuncTrackTrain *CFuncTrackTrain::Instance( edict_t *pent )
{ 
	CBaseEntity *pEntity = CBaseEntity::Instance( pent );
	if ( FClassnameIs( pEntity, "func_tracktrain" ) )
		return (CFuncTrackTrain *)pEntity;
	return NULL;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CFuncTrackTrain::Spawn( void )
{
	if ( m_maxSpeed == 0 )
	{
		if ( m_flSpeed == 0 )
		{
			m_maxSpeed = 100;
		}
		else
		{
			m_maxSpeed = m_flSpeed;
		}
	}

	if ( m_nMoveSoundMinPitch == 0 )
	{
		m_nMoveSoundMinPitch = 60;
	}

	if ( m_nMoveSoundMaxPitch == 0 )
	{
		m_nMoveSoundMaxPitch = 200;
	}

	SetLocalVelocity(vec3_origin);
	SetLocalAngularVelocity( vec3_angle );

	m_dir = 1;

	if ( !m_target )
		Msg( "FuncTrain with no target" );

	SetModel( STRING( GetModelName() ) );
	SetMoveType( MOVETYPE_PUSH );

#ifdef HL1_DLL
	// BUGBUG: For now, just force this for testing.  Remove if we want to tag all of the trains in the levels
	SetSolid( SOLID_BSP );
#else
	SetSolid( HasSpawnFlags( SF_TRACKTRAIN_HL1TRAIN ) ? SOLID_BSP : SOLID_VPHYSICS );
	//SetSolid( SOLID_VPHYSICS );
#endif

	if ( HasSpawnFlags( SF_TRACKTRAIN_UNBLOCKABLE_BY_PLAYER ) )
	{
		AddFlag( FL_UNBLOCKABLE_BY_PLAYER );
	}
	if ( m_spawnflags & SF_TRACKTRAIN_PASSABLE )
	{
		AddSolidFlags( FSOLID_NOT_SOLID );
	}

	m_controlMins = CollisionProp()->OBBMins();
	m_controlMaxs = CollisionProp()->OBBMaxs();
	m_controlMaxs.z += 72;
// start trains on the next frame, to make sure their targets have had
// a chance to spawn/activate
	SetThink( &CFuncTrackTrain::Find );
	SetNextThink( gpGlobals->curtime );
	Precache();

	CreateVPhysics();
}


bool CFuncTrackTrain::CreateVPhysics( void )
{
	VPhysicsInitShadow( false, false );
	return true;
}


//-----------------------------------------------------------------------------
// Purpose: Precaches the train sounds.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::Precache( void )
{
	if (m_flVolume == 0.0)
	{
		m_flVolume = 1.0;
	}

	if ( m_iszSoundMove != NULL_STRING )
	{
		PrecacheScriptSound( STRING( m_iszSoundMove ) );
	}

	if ( m_iszSoundMovePing != NULL_STRING )
	{
		PrecacheScriptSound( STRING( m_iszSoundMovePing ) );
	}

	if ( m_iszSoundStart != NULL_STRING )
	{
		PrecacheScriptSound( STRING( m_iszSoundStart ) );
	}

	if ( m_iszSoundStop != NULL_STRING )
	{
		PrecacheScriptSound( STRING( m_iszSoundStop ) );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CFuncTrackTrain::UpdateOnRemove()
{
	SoundStop();
	BaseClass::UpdateOnRemove();
}



//-----------------------------------------------------------------------------
// Purpose: Defines the volume of space that the player must stand in to
//			control the train
//-----------------------------------------------------------------------------
class CFuncTrainControls : public CBaseEntity
{
	DECLARE_CLASS( CFuncTrainControls, CBaseEntity );
public:
	void Spawn( void );
	void Find( void );

	DECLARE_DATADESC();
};

BEGIN_DATADESC( CFuncTrainControls )

	// Function Pointers
	DEFINE_FUNCTION( Find ),

END_DATADESC()

LINK_ENTITY_TO_CLASS( func_traincontrols, CFuncTrainControls );


void CFuncTrainControls::Find( void )
{
	CBaseEntity *pTarget = NULL;

	do 
	{
		pTarget = gEntList.FindEntityByName( pTarget, m_target, NULL );
	} while ( pTarget && !FClassnameIs(pTarget, "func_tracktrain") );

	if ( !pTarget )
	{
		Msg( "No train %s\n", STRING(m_target) );
		return;
	}

	CFuncTrackTrain *ptrain = (CFuncTrackTrain*) pTarget;
	ptrain->SetControls( this );
	
	SetThink( NULL );
}


void CFuncTrainControls::Spawn( void )
{
	SetSolid( SOLID_NONE );
	SetMoveType( MOVETYPE_NONE );
	SetModel( STRING( GetModelName() ) );
	AddEffects( EF_NODRAW );

	Assert( GetParent() && "func_traincontrols needs parent to properly align to train" );
	
	SetThink( &CFuncTrainControls::Find );
	SetNextThink( gpGlobals->curtime );
}


#define SF_TRACK_ACTIVATETRAIN		0x00000001
#define SF_TRACK_RELINK				0x00000002
#define SF_TRACK_ROTMOVE			0x00000004
#define SF_TRACK_STARTBOTTOM		0x00000008
#define SF_TRACK_DONT_MOVE			0x00000010


typedef enum { TRAIN_SAFE, TRAIN_BLOCKING, TRAIN_FOLLOWING } TRAIN_CODE;


//-----------------------------------------------------------------------------
// This entity is a rotating/moving platform that will carry a train to a new track.
// It must be larger in X-Y planar area than the train, since it must contain the
// train within these dimensions in order to operate when the train is near it.
//-----------------------------------------------------------------------------
class CFuncTrackChange : public CFuncPlatRot
{
	DECLARE_CLASS( CFuncTrackChange, CFuncPlatRot );
public:
	void Spawn( void );
	void Precache( void );

//	virtual void	Blocked( void );
	virtual void	GoUp( void );
	virtual void	GoDown( void );

	void			Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
	void			Find( void );
	TRAIN_CODE		EvaluateTrain( CPathTrack *pcurrent );
	void			UpdateTrain( QAngle &dest );
	virtual void	HitBottom( void );
	virtual void	HitTop( void );
	void			Touch( CBaseEntity *pOther );
	virtual void	UpdateAutoTargets( int toggleState );
	virtual	bool	IsTogglePlat( void ) { return true; }

	void			DisableUse( void ) { m_use = 0; }
	void			EnableUse( void ) { m_use = 1; }
	int				UseEnabled( void ) { return m_use; }

	DECLARE_DATADESC();

	CPathTrack		*m_trackTop;
	CPathTrack		*m_trackBottom;

	CFuncTrackTrain	*m_train;

	string_t		m_trackTopName;
	string_t		m_trackBottomName;
	string_t		m_trainName;
	TRAIN_CODE		m_code;
	int				m_targetState;
	int				m_use;
};

LINK_ENTITY_TO_CLASS( func_trackchange, CFuncTrackChange );

BEGIN_DATADESC( CFuncTrackChange )

	DEFINE_GLOBAL_FIELD( m_trackTop, FIELD_CLASSPTR ),
	DEFINE_GLOBAL_FIELD( m_trackBottom, FIELD_CLASSPTR ),
	DEFINE_GLOBAL_FIELD( m_train, FIELD_CLASSPTR ),
	DEFINE_GLOBAL_KEYFIELD( m_trackTopName, FIELD_STRING, "toptrack" ),
	DEFINE_GLOBAL_KEYFIELD( m_trackBottomName, FIELD_STRING, "bottomtrack" ),
	DEFINE_GLOBAL_KEYFIELD( m_trainName, FIELD_STRING, "train" ),
	DEFINE_FIELD( m_code, FIELD_INTEGER ),
	DEFINE_FIELD( m_targetState, FIELD_INTEGER ),
	DEFINE_FIELD( m_use, FIELD_INTEGER ),

	// Function Pointers
	DEFINE_FUNCTION( Find ),

END_DATADESC()


void CFuncTrackChange::Spawn( void )
{
	Setup();
	if ( FBitSet( m_spawnflags, SF_TRACK_DONT_MOVE ) )
		m_vecPosition2.z = GetLocalOrigin().z;

	SetupRotation();

	if ( FBitSet( m_spawnflags, SF_TRACK_STARTBOTTOM ) )
	{
		UTIL_SetOrigin( this, m_vecPosition2);
		m_toggle_state = TS_AT_BOTTOM;
		SetLocalAngles( m_start );
		m_targetState = TS_AT_TOP;
	}
	else
	{
		UTIL_SetOrigin( this, m_vecPosition1);
		m_toggle_state = TS_AT_TOP;
		SetLocalAngles( m_end );
		m_targetState = TS_AT_BOTTOM;
	}

	EnableUse();
	SetThink( &CFuncTrackChange::Find );
	SetNextThink( gpGlobals->curtime + 2 );
	Precache();
}


void CFuncTrackChange::Precache( void )
{
	BaseClass::Precache();

	PrecacheScriptSound( "FuncTrackChange.Blocking" );
}


// UNDONE: Filter touches before re-evaluating the train.
void CFuncTrackChange::Touch( CBaseEntity *pOther )
{
}


void CFuncTrackChange::Find( void )
{
	// Find track entities
	CBaseEntity *target;

	target = gEntList.FindEntityByName( NULL, m_trackTopName, NULL );
	if ( target )
	{
		m_trackTop = (CPathTrack*) target;
		target = gEntList.FindEntityByName( NULL, m_trackBottomName, NULL );
		if ( target )
		{
			m_trackBottom = (CPathTrack*) target;
			target = gEntList.FindEntityByName( NULL, m_trainName, NULL );
			if ( target )
			{
				m_train = (CFuncTrackTrain *)gEntList.FindEntityByName( NULL, m_trainName, NULL );
				if ( !m_train )
				{
					Warning( "Can't find train for track change! %s\n", STRING(m_trainName) );
					Assert(0);
					return;
				}
				Vector center = WorldSpaceCenter();
				m_trackBottom = m_trackBottom->Nearest( center );
				m_trackTop = m_trackTop->Nearest( center );
				UpdateAutoTargets( m_toggle_state );
				SetThink( NULL );
				return;
			}
			else
			{
				Warning( "Can't find train for track change! %s\n", STRING(m_trainName) );
				Assert(0);
				target = gEntList.FindEntityByName( NULL, m_trainName, NULL );
			}
		}
		else
		{
			Warning( "Can't find bottom track for track change! %s\n", STRING(m_trackBottomName) );
			Assert(0);
		}
	}
	else
	{
		Warning( "Can't find top track for track change! %s\n", STRING(m_trackTopName) );
		Assert(0);
	}
}


TRAIN_CODE CFuncTrackChange::EvaluateTrain( CPathTrack *pcurrent )
{
	// Go ahead and work, we don't have anything to switch, so just be an elevator
	if ( !pcurrent || !m_train )
		return TRAIN_SAFE;

	if ( m_train->m_ppath == pcurrent || (pcurrent->m_pprevious && m_train->m_ppath == pcurrent->m_pprevious) ||
		 (pcurrent->m_pnext && m_train->m_ppath == pcurrent->m_pnext) )
	{
		if ( m_train->m_flSpeed != 0 )
			return TRAIN_BLOCKING;

		Vector dist = GetLocalOrigin() - m_train->GetLocalOrigin();
		float length = dist.Length2D();
		if ( length < m_train->m_length )		// Empirically determined close distance
			return TRAIN_FOLLOWING;
		else if ( length > (150 + m_train->m_length) )
			return TRAIN_SAFE;

		return TRAIN_BLOCKING;
	}
	
	return TRAIN_SAFE;
}


void CFuncTrackChange::UpdateTrain( QAngle &dest )
{
	float time = GetMoveDoneTime();

	m_train->SetAbsVelocity( GetAbsVelocity() );
	m_train->SetLocalAngularVelocity( GetLocalAngularVelocity() );
	m_train->SetMoveDoneTime( time );

	// Attempt at getting the train to rotate properly around the origin of the trackchange
	if ( time <= 0 )
		return;

	Vector offset = m_train->GetLocalOrigin() - GetLocalOrigin();
	QAngle delta = dest - GetLocalAngles();
	// Transform offset into local coordinates
	Vector forward, right, up;
	AngleVectorsTranspose( delta, &forward, &right, &up );
	Vector local;
	local.x = DotProduct( offset, forward );
	local.y = DotProduct( offset, right );
	local.z = DotProduct( offset, up );

	local = local - offset;
	m_train->SetAbsVelocity( GetAbsVelocity() + (local * (1.0/time)) );
}


void CFuncTrackChange::GoDown( void )
{
	if ( m_code == TRAIN_BLOCKING )
		return;

	// HitBottom may get called during CFuncPlat::GoDown(), so set up for that
	// before you call GoDown()

	UpdateAutoTargets( TS_GOING_DOWN );
	// If ROTMOVE, move & rotate
	if ( FBitSet( m_spawnflags, SF_TRACK_DONT_MOVE ) )
	{
		SetMoveDone( &CFuncTrackChange::CallHitBottom );
		m_toggle_state = TS_GOING_DOWN;
		AngularMove( m_start, m_flSpeed );
	}
	else
	{
		BaseClass::GoDown();
		SetMoveDone( &CFuncTrackChange::CallHitBottom );
		RotMove( m_start, GetMoveDoneTime() );
	}
	// Otherwise, rotate first, move second

	// If the train is moving with the platform, update it
	if ( m_code == TRAIN_FOLLOWING )
	{
		UpdateTrain( m_start );
		m_train->m_ppath = NULL;
#ifdef HL1_DLL
		m_train->m_bOnTrackChange = true;
#endif
	}
}


//
// Platform is at bottom, now starts moving up
//
void CFuncTrackChange::GoUp( void )
{
	if ( m_code == TRAIN_BLOCKING )
		return;

	// HitTop may get called during CFuncPlat::GoUp(), so set up for that
	// before you call GoUp();

	UpdateAutoTargets( TS_GOING_UP );
	if ( FBitSet( m_spawnflags, SF_TRACK_DONT_MOVE ) )
	{
		m_toggle_state = TS_GOING_UP;
		SetMoveDone( &CFuncTrackChange::CallHitTop );
		AngularMove( m_end, m_flSpeed );
	}
	else
	{
		// If ROTMOVE, move & rotate
		BaseClass::GoUp();
		SetMoveDone( &CFuncTrackChange::CallHitTop );
		RotMove( m_end, GetMoveDoneTime() );
	}
	
	// Otherwise, move first, rotate second

	// If the train is moving with the platform, update it
	if ( m_code == TRAIN_FOLLOWING )
	{
		UpdateTrain( m_end );
		m_train->m_ppath = NULL;
	}
}


//-----------------------------------------------------------------------------
// Purpose: Normal track change
// Input  : toggleState - 
//-----------------------------------------------------------------------------
void CFuncTrackChange::UpdateAutoTargets( int toggleState )
{
	if ( !m_trackTop || !m_trackBottom )
		return;

	if ( toggleState == TS_AT_TOP )
	{
		m_trackTop->RemoveSpawnFlags( SF_PATH_DISABLED );
	}
	else
	{
		m_trackTop->AddSpawnFlags( SF_PATH_DISABLED );
	}

	if ( toggleState == TS_AT_BOTTOM )
	{
		m_trackBottom->RemoveSpawnFlags( SF_PATH_DISABLED );
	}
	else
	{
		m_trackBottom->AddSpawnFlags( SF_PATH_DISABLED );
	}
}


void CFuncTrackChange::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	if ( m_toggle_state != TS_AT_TOP && m_toggle_state != TS_AT_BOTTOM )
		return;

	// If train is in "safe" area, but not on the elevator, play alarm sound
	if ( m_toggle_state == TS_AT_TOP )
		m_code = EvaluateTrain( m_trackTop );
	else if ( m_toggle_state == TS_AT_BOTTOM )
		m_code = EvaluateTrain( m_trackBottom );
	else
		m_code = TRAIN_BLOCKING;
	if ( m_code == TRAIN_BLOCKING )
	{
		// Play alarm and return
		EmitSound( "FuncTrackChange.Blocking" );
		return;
	}

	// Otherwise, it's safe to move
	// If at top, go down
	// at bottom, go up

	DisableUse();
	if (m_toggle_state == TS_AT_TOP)
		GoDown();
	else
		GoUp();
}


//
// Platform has hit bottom.  Stops and waits forever.
//
void CFuncTrackChange::HitBottom( void )
{
	BaseClass::HitBottom();
	if ( m_code == TRAIN_FOLLOWING )
	{
//		UpdateTrain();
		m_train->SetTrack( m_trackBottom );
	}
	SetMoveDone( NULL );
	SetMoveDoneTime( -1 );

	UpdateAutoTargets( m_toggle_state );

	EnableUse();
}


//
// Platform has hit bottom.  Stops and waits forever.
//
void CFuncTrackChange::HitTop( void )
{
	BaseClass::HitTop();
	if ( m_code == TRAIN_FOLLOWING )
	{
//		UpdateTrain();
		m_train->SetTrack( m_trackTop );
	}
	
	// Don't let the plat go back down
	SetMoveDone( NULL );
	SetMoveDoneTime( -1 );
	UpdateAutoTargets( m_toggle_state );
	EnableUse();
}


class CFuncTrackAuto : public CFuncTrackChange
{
	DECLARE_CLASS( CFuncTrackAuto, CFuncTrackChange );
public:
	void			Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
	virtual void	UpdateAutoTargets( int toggleState );
	void			TriggerTrackChange( inputdata_t &inputdata );

	DECLARE_DATADESC();
};

BEGIN_DATADESC( CFuncTrackAuto )
	DEFINE_INPUTFUNC( FIELD_VOID, "Trigger", TriggerTrackChange ),
END_DATADESC()

LINK_ENTITY_TO_CLASS( func_trackautochange, CFuncTrackAuto );


// Auto track change
void CFuncTrackAuto::UpdateAutoTargets( int toggleState )
{
	CPathTrack *pTarget, *pNextTarget;

	if ( !m_trackTop || !m_trackBottom )
		return;

	if ( m_targetState == TS_AT_TOP )
	{
		pTarget = m_trackTop->GetNext();
		pNextTarget = m_trackBottom->GetNext();
	}
	else
	{
		pTarget = m_trackBottom->GetNext();
		pNextTarget = m_trackTop->GetNext();
	}
	if ( pTarget )
	{
		pTarget->RemoveSpawnFlags( SF_PATH_DISABLED );
		if ( m_code == TRAIN_FOLLOWING && m_train && m_train->m_flSpeed == 0 )
		{
			m_train->SetSpeed( pTarget->m_flSpeed );
			m_train->Use( this, this, USE_SET, 0 );
		}
	}

	if ( pNextTarget )
	{
		pNextTarget->AddSpawnFlags( SF_PATH_DISABLED );
	}
}


void CFuncTrackAuto::TriggerTrackChange ( inputdata_t &inputdata )
{
	CPathTrack *pTarget;

	if ( !UseEnabled() )
		return;

	if ( m_toggle_state == TS_AT_TOP )
		pTarget = m_trackTop;
	else if ( m_toggle_state == TS_AT_BOTTOM )
		pTarget = m_trackBottom;
	else
		pTarget = NULL;

	if ( inputdata.pActivator && FClassnameIs( inputdata.pActivator, "func_tracktrain" ) )
	{
		m_code = EvaluateTrain( pTarget );
		// Safe to fire?
		if ( m_code == TRAIN_FOLLOWING && m_toggle_state != m_targetState )
		{
			DisableUse();
			if (m_toggle_state == TS_AT_TOP)
				GoDown();
			else
				GoUp();
		}
	}
	else
	{
		if ( pTarget )
			pTarget = pTarget->GetNext();
		if ( pTarget && m_train->m_ppath != pTarget && ShouldToggle( USE_TOGGLE, m_targetState ) )
		{
			if ( m_targetState == TS_AT_TOP )
				m_targetState = TS_AT_BOTTOM;
			else
				m_targetState = TS_AT_TOP;
		}

		UpdateAutoTargets( m_targetState );
	}
}


void CFuncTrackAuto::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	CPathTrack *pTarget;

	if ( !UseEnabled() )
		return;

	if ( m_toggle_state == TS_AT_TOP )
		pTarget = m_trackTop;
	else if ( m_toggle_state == TS_AT_BOTTOM )
		pTarget = m_trackBottom;
	else
		pTarget = NULL;

	if ( FClassnameIs( pActivator, "func_tracktrain" ) )
	{
		m_code = EvaluateTrain( pTarget );
		// Safe to fire?
		if ( m_code == TRAIN_FOLLOWING && m_toggle_state != m_targetState )
		{
			DisableUse();
			if (m_toggle_state == TS_AT_TOP)
				GoDown();
			else
				GoUp();
		}
	}
	else
	{
		if ( pTarget )
			pTarget = pTarget->GetNext();
		if ( pTarget && m_train->m_ppath != pTarget && ShouldToggle( useType, m_targetState ) )
		{
			if ( m_targetState == TS_AT_TOP )
				m_targetState = TS_AT_BOTTOM;
			else
				m_targetState = TS_AT_TOP;
		}

		UpdateAutoTargets( m_targetState );
	}
}
