/***
*
*	Copyright (c) 1996-2002, Valve LLC. All rights reserved.
*	
*	This product contains software technology licensed from Id 
*	Software, Inc. ("Id Technology").  Id Technology (c) 1996 Id Software, Inc. 
*	All Rights Reserved.
*
*   Use, distribution, and modification of this source code and/or resulting
*   object code is restricted to non-commercial enhancements to products from
*   Valve LLC.  All other use, distribution, or modification is prohibited
*   without written permission from Valve LLC.
*
****/
/*

===== plats.cpp ========================================================

  spawn, think, and touch functions for trains, etc

*/

#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "trains.h"
#include "saverestore.h"

static void PlatSpawnInsideTrigger(entvars_t* pevPlatform);

#define SF_PLAT_TOGGLE		0x0001

class CBasePlatTrain : public CBaseToggle
{
public:
	virtual int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
	void KeyValue( KeyValueData* pkvd);
	void Precache( void );

	// This is done to fix spawn flag collisions between this class and a derived class
	virtual BOOL IsTogglePlat( void ) { return ( pev->spawnflags & SF_PLAT_TOGGLE ) ? TRUE : FALSE; }

	virtual int Save( CSave &save );
	virtual int Restore( CRestore &restore );
	static TYPEDESCRIPTION m_SaveData[];

	BYTE m_bMoveSnd;			// sound a plat makes while moving
	BYTE m_bStopSnd;			// sound a plat makes when it stops
	float m_volume;			// Sound volume
};

TYPEDESCRIPTION	CBasePlatTrain::m_SaveData[] =
{
	DEFINE_FIELD( CBasePlatTrain, m_bMoveSnd, FIELD_CHARACTER ),
	DEFINE_FIELD( CBasePlatTrain, m_bStopSnd, FIELD_CHARACTER ),
	DEFINE_FIELD( CBasePlatTrain, m_volume, FIELD_FLOAT ),
};

IMPLEMENT_SAVERESTORE( CBasePlatTrain, CBaseToggle )

void CBasePlatTrain::KeyValue( KeyValueData *pkvd )
{
	if( FStrEq( pkvd->szKeyName, "lip" ) )
	{
		m_flLip = atof( pkvd->szValue );
		pkvd->fHandled = TRUE;
	}
	else if( FStrEq( pkvd->szKeyName, "wait" ) )
	{
		m_flWait = atof( pkvd->szValue );
		pkvd->fHandled = TRUE;
	}
	else if( FStrEq( pkvd->szKeyName, "height" ) )
	{
		m_flHeight = atof( pkvd->szValue );
		pkvd->fHandled = TRUE;
	}
	else if( FStrEq( pkvd->szKeyName, "rotation" ) )
	{
		m_vecFinalAngle.x = atof( pkvd->szValue );
		pkvd->fHandled = TRUE;
	}
	else if( FStrEq( pkvd->szKeyName, "movesnd" ) )
	{
		m_bMoveSnd = atoi( pkvd->szValue );
		pkvd->fHandled = TRUE;
	}
	else if( FStrEq( pkvd->szKeyName, "stopsnd" ) )
	{
		m_bStopSnd = atoi( pkvd->szValue );
		pkvd->fHandled = TRUE;
	}
	else if( FStrEq( pkvd->szKeyName, "volume" ) )
	{
		m_volume = atof( pkvd->szValue );
		pkvd->fHandled = TRUE;
	}
	else
		CBaseToggle::KeyValue( pkvd );
}

#define noiseMoving noise
#define noiseArrived noise1

void CBasePlatTrain::Precache( void )
{
	const char *pszSound;
	BOOL NullSound = FALSE;

	// set the plat's "in-motion" sound
	switch( m_bMoveSnd )
	{
	case 1:
		pszSound = "plats/bigmove1.wav";
		break;
	case 2:
		pszSound = "plats/bigmove2.wav";
		break;
	case 3:
		pszSound = "plats/elevmove1.wav";
		break;
	case 4:
		pszSound = "plats/elevmove2.wav";
		break;
	case 5:
		pszSound = "plats/elevmove3.wav";
		break;
	case 6:
		pszSound = "plats/freightmove1.wav";
		break;
	case 7:
		pszSound = "plats/freightmove2.wav";
		break;
	case 8:
		pszSound = "plats/heavymove1.wav";
		break;
	case 9:
		pszSound = "plats/rackmove1.wav";
		break;
	case 10:
		pszSound = "plats/railmove1.wav";
		break;
	case 11:
		pszSound = "plats/squeekmove1.wav";
		break;
	case 12:
		pszSound = "plats/talkmove1.wav";
		break;
	case 13:
		pszSound = "plats/talkmove2.wav";
		break;
	case 0:
	default:
		pszSound = "common/null.wav";
		NullSound = TRUE;
		break;
	}

	if( !NullSound )
                PRECACHE_SOUND( pszSound );
        pev->noiseMoving = MAKE_STRING( pszSound );
	NullSound = FALSE;

	// set the plat's 'reached destination' stop sound
	switch( m_bStopSnd )
	{
	case 1:
		pszSound = "plats/bigstop1.wav";
		break;
	case 2:
		pszSound = "plats/bigstop2.wav";
		break;
	case 3:
		pszSound = "plats/freightstop1.wav";
		break;
	case 4:
		pszSound = "plats/heavystop2.wav";
		break;
	case 5:
		pszSound = "plats/rackstop1.wav";
		break;
	case 6:
		pszSound = "plats/railstop1.wav";
		break;
	case 7:
		pszSound = "plats/squeekstop1.wav";
		break;
	case 8:
		pszSound = "plats/talkstop1.wav";
		break;
	case 0:
	default:
		pszSound = "common/null.wav";
		NullSound = TRUE;
		break;
	}

	if( !NullSound )
		PRECACHE_SOUND( pszSound );
	pev->noiseArrived = MAKE_STRING( pszSound );
}

//
//====================== PLAT code ====================================================
//

#define noiseMovement noise
#define noiseStopMoving noise1

class CFuncPlat : public CBasePlatTrain
{
public:
	void Spawn( void );
	void Precache( void );
	void Setup( void );

	virtual void Blocked( CBaseEntity *pOther );

	void EXPORT PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );

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

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

LINK_ENTITY_TO_CLASS( func_plat, CFuncPlat )

// UNDONE: Need to save this!!! It needs class & linkage
class CPlatTrigger : public CBaseEntity
{
public:
	virtual int ObjectCaps( void ) { return (CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DONT_SAVE; }
	void SpawnInsideTrigger( CFuncPlat *pPlatform );
	void Touch( CBaseEntity *pOther );
	EHANDLE m_hPlatform;
};

/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER
speed	default 150

Plats are always drawn in the extended position, so they will light correctly.

If the plat is the target of another trigger or button, it will start out disabled in
the extended position until it is trigger, when it will lower and become a normal plat.

If the "height" key is set, that will determine the amount the plat moves, instead of
being implicitly determined by the model's height.

Set "sounds" to one of the following:
1) base fast
2) chain slow
*/

void CFuncPlat::Setup( void )
{
	//pev->noiseMovement = MAKE_STRING( "plats/platmove1.wav" );
	//pev->noiseStopMoving = MAKE_STRING( "plats/platstop1.wav" );

	if( m_flTLength == 0 )
		m_flTLength = 80;
	if( m_flTWidth == 0 )
		m_flTWidth = 10;
	
	pev->angles = g_vecZero;

	pev->solid = SOLID_BSP;
	pev->movetype = MOVETYPE_PUSH;

	UTIL_SetOrigin( pev, pev->origin );		// set size and link into world
	UTIL_SetSize( pev, pev->mins, pev->maxs );
	SET_MODEL( ENT( pev), STRING( pev->model ) );

	// vecPosition1 is the top position, vecPosition2 is the bottom
	m_vecPosition1 = pev->origin;
	m_vecPosition2 = pev->origin;
	if( m_flHeight != 0 )
		m_vecPosition2.z = pev->origin.z - m_flHeight;
	else
		m_vecPosition2.z = pev->origin.z - pev->size.z + 8;
	if( pev->speed == 0 )
		pev->speed = 150;

	if( m_volume == 0 )
		m_volume = 0.85;
}

void CFuncPlat::Precache()
{
	CBasePlatTrain::Precache();
	//PRECACHE_SOUND( "plats/platmove1.wav" );
	//PRECACHE_SOUND( "plats/platstop1.wav" );
	if( !IsTogglePlat() )
		PlatSpawnInsideTrigger( pev );		// the "start moving" trigger
}

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( !FStringNull( pev->targetname ) )
	{
		UTIL_SetOrigin( pev, m_vecPosition1 );
		m_toggle_state = TS_AT_TOP;
		SetUse( &CFuncPlat::PlatUse );
	}
	else
	{
		UTIL_SetOrigin( pev, m_vecPosition2 );
		m_toggle_state = TS_AT_BOTTOM;
	}
}

static void PlatSpawnInsideTrigger( entvars_t *pevPlatform )
{
	GetClassPtr( (CPlatTrigger *)NULL )->SpawnInsideTrigger( GetClassPtr( (CFuncPlat *)pevPlatform ) );
}
		
//
// Create a trigger entity for a platform.
//
void CPlatTrigger::SpawnInsideTrigger( CFuncPlat *pPlatform )
{
	m_hPlatform = pPlatform;
	// Create trigger entity, "point" it at the owning platform, give it a touch method
	pev->solid = SOLID_TRIGGER;
	pev->movetype = MOVETYPE_NONE;
	pev->origin = pPlatform->pev->origin;

	// Establish the trigger field's size
	Vector vecTMin = pPlatform->pev->mins + Vector( 25, 25, 0 );
	Vector vecTMax = pPlatform->pev->maxs + Vector( 25, 25, 8 );
	vecTMin.z = vecTMax.z - ( pPlatform->m_vecPosition1.z - pPlatform->m_vecPosition2.z + 8 );
	if( pPlatform->pev->size.x <= 50 )
	{
		vecTMin.x = ( pPlatform->pev->mins.x + pPlatform->pev->maxs.x ) / 2;
		vecTMax.x = vecTMin.x + 1;
	}
	if( pPlatform->pev->size.y <= 50 )
	{
		vecTMin.y = ( pPlatform->pev->mins.y + pPlatform->pev->maxs.y ) / 2;
		vecTMax.y = vecTMin.y + 1;
	}
	UTIL_SetSize( pev, 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;

	CFuncPlat *pPlatform = (CFuncPlat*)(CBaseEntity*)m_hPlatform;

	if( !pPlatform )
	{
		// The target platform has been removed, remove myself as well. - Solokiller
		UTIL_Remove( this );
		return;
	}

	// Ignore touches by corpses
	if( !pOther->IsAlive() )
		return;

	// Make linked platform go up/down.
	if( pPlatform->m_toggle_state == TS_AT_BOTTOM )
		pPlatform->GoUp();
	else if( pPlatform->m_toggle_state == TS_AT_TOP )
		pPlatform->pev->nextthink = pPlatform->pev->ltime + 1;// delay going down
}

//
// Used by SUB_UseTargets, when a platform is the target of a button.
// Start bringing platform down.
//
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 )
{
	if( pev->noiseMovement )
		EMIT_SOUND( ENT( pev ), CHAN_STATIC, STRING( pev->noiseMovement ), m_volume, ATTN_NORM );

	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, pev->speed );
}

//
// Platform has hit bottom. Stops and waits forever.
//
void CFuncPlat::HitBottom( void )
{
	if( pev->noiseMovement )
		STOP_SOUND( ENT( pev ), CHAN_STATIC, STRING( pev->noiseMovement ) );

	if( pev->noiseStopMoving )
		EMIT_SOUND( ENT( pev ), CHAN_WEAPON, STRING( pev->noiseStopMoving ), m_volume, ATTN_NORM );

	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 )
{
	if( pev->noiseMovement )
		EMIT_SOUND( ENT( pev ), CHAN_STATIC, STRING( pev->noiseMovement ), m_volume, ATTN_NORM );

	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, pev->speed);
}

//
// Platform has hit top. Pauses, then starts back down again.
//
void CFuncPlat::HitTop( void )
{
	if( pev->noiseMovement )
		STOP_SOUND( ENT( pev ), CHAN_STATIC, STRING( pev->noiseMovement ) );

	if( pev->noiseStopMoving )
		EMIT_SOUND( ENT( pev ), CHAN_WEAPON, STRING( pev->noiseStopMoving ), m_volume, ATTN_NORM );

	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.
		SetThink( &CFuncPlat::CallGoDown );
		pev->nextthink = pev->ltime + 3;
	}
}

void CFuncPlat::Blocked( CBaseEntity *pOther )
{
	ALERT( at_aiconsole, "%s Blocked by %s\n", STRING( pev->classname ), STRING( pOther->pev->classname ) );
	// Hurt the blocker a little
	pOther->TakeDamage( pev, pev, 1, DMG_CRUSH );

	if( pev->noiseMovement )
		STOP_SOUND( ENT( pev ), CHAN_STATIC, STRING( pev->noiseMovement ) );

	// 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
{
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( Vector &destAngle, float time );
	virtual int Save( CSave &save );
	virtual int Restore( CRestore &restore );
	static TYPEDESCRIPTION m_SaveData[];

	Vector m_end, m_start;
};

LINK_ENTITY_TO_CLASS( func_platrot, CFuncPlatRot )
TYPEDESCRIPTION	CFuncPlatRot::m_SaveData[] =
{
	DEFINE_FIELD( CFuncPlatRot, m_end, FIELD_VECTOR ),
	DEFINE_FIELD( CFuncPlatRot, m_start, FIELD_VECTOR ),
};

IMPLEMENT_SAVERESTORE( CFuncPlatRot, CFuncPlat )

void CFuncPlatRot::SetupRotation( void )
{
	if( m_vecFinalAngle.x != 0 )		// This plat rotates too!
	{
		CBaseToggle::AxisDir( pev );
		m_start	= pev->angles;
		m_end = pev->angles + pev->movedir * m_vecFinalAngle.x;
	}
	else
	{
		m_start = g_vecZero;
		m_end = g_vecZero;
	}
	if( !FStringNull( pev->targetname ) )	// Start at top
	{
		pev->angles = m_end;
	}
}

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

void CFuncPlatRot::GoDown( void )
{
	CFuncPlat::GoDown();
	RotMove( m_start, pev->nextthink - pev->ltime );
}

//
// Platform has hit bottom. Stops and waits forever.
//
void CFuncPlatRot::HitBottom( void )
{
	CFuncPlat::HitBottom();
	pev->avelocity = g_vecZero;
	pev->angles = m_start;
}

//
// Platform is at bottom, now starts moving up
//
void CFuncPlatRot::GoUp( void )
{
	CFuncPlat::GoUp();
	RotMove( m_end, pev->nextthink - pev->ltime );
}

//
// Platform has hit top. Pauses, then starts back down again.
//
void CFuncPlatRot::HitTop( void )
{
	CFuncPlat::HitTop();
	pev->avelocity = g_vecZero;
	pev->angles = m_end;
}

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

	// Travel time is so short, we're practically there already;  so make it so.
	if( time >= 0.1f )
		pev->avelocity = vecDestDelta / time;
	else
	{
		pev->avelocity = vecDestDelta;
		pev->nextthink = pev->ltime + 1;
	}
}

//
//====================== TRAIN code ==================================================
//
class CFuncTrain : public CBasePlatTrain
{
public:
	void Spawn( void );
	void Precache( void );
	void Activate( void );
	void OverrideReset( void );

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

	void EXPORT Wait( void );
	void EXPORT Next( void );
	virtual int Save( CSave &save );
	virtual int Restore( CRestore &restore );
	static TYPEDESCRIPTION m_SaveData[];

	entvars_t *m_pevCurrentTarget;
	int m_sounds;
	BOOL m_activated;
};

LINK_ENTITY_TO_CLASS( func_train, CFuncTrain )

TYPEDESCRIPTION	CFuncTrain::m_SaveData[] =
{
	DEFINE_FIELD( CFuncTrain, m_sounds, FIELD_INTEGER ),
	DEFINE_FIELD( CFuncTrain, m_pevCurrentTarget, FIELD_EVARS ),
	DEFINE_FIELD( CFuncTrain, m_activated, FIELD_BOOLEAN ),
};

IMPLEMENT_SAVERESTORE( CFuncTrain, CBasePlatTrain )

void CFuncTrain::KeyValue( KeyValueData *pkvd )
{
	if( FStrEq( pkvd->szKeyName, "sounds" ) )
	{
		m_sounds = atoi( pkvd->szValue );
		pkvd->fHandled = TRUE;
	}
	else
		CBasePlatTrain::KeyValue( pkvd );
}

void CFuncTrain::Blocked( CBaseEntity *pOther )
{
	if( gpGlobals->time < m_flActivateFinished )
		return;

	m_flActivateFinished = gpGlobals->time + 0.5f;

	pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH );
}

void CFuncTrain::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	if( pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER )
	{
		// Move toward my target
		pev->spawnflags &= ~SF_TRAIN_WAIT_RETRIGGER;
		Next();
	}
	else
	{
		pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER;
		// Pop back to last target if it's available
		if( pev->enemy )
			pev->target = pev->enemy->v.targetname;
		pev->nextthink = 0;
		pev->velocity = g_vecZero;
		if( pev->noiseStopMoving )
			EMIT_SOUND( ENT( pev ), CHAN_VOICE, STRING( pev->noiseStopMoving ), m_volume, ATTN_NORM );
	}
}

void CFuncTrain::Wait( void )
{
	// Fire the pass target if there is one
	if( m_pevCurrentTarget->message )
	{
		FireTargets( STRING( m_pevCurrentTarget->message ), this, this, USE_TOGGLE, 0 );
		if( FBitSet( m_pevCurrentTarget->spawnflags, SF_CORNER_FIREONCE ) )
			m_pevCurrentTarget->message = 0;
	}

	// need pointer to LAST target.
	if( FBitSet( m_pevCurrentTarget->spawnflags, SF_TRAIN_WAIT_RETRIGGER ) || ( pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER ) )
	{
		pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER;

		// clear the sound channel.
		if( pev->noiseMovement )
			STOP_SOUND( edict(), CHAN_STATIC, STRING( pev->noiseMovement ) );
		if( pev->noiseStopMoving )
			EMIT_SOUND( ENT( pev ), CHAN_VOICE, STRING( pev->noiseStopMoving ), m_volume, ATTN_NORM );
		pev->nextthink = 0;
		return;
	}

	// ALERT( at_console, "%f\n", m_flWait );
	if( m_flWait != 0 )
	{
		// -1 wait will wait forever!		
		pev->nextthink = pev->ltime + m_flWait;
		if( pev->noiseMovement )
			STOP_SOUND( edict(), CHAN_STATIC, STRING( pev->noiseMovement ) );
		if( pev->noiseStopMoving )
			EMIT_SOUND( ENT( pev ), CHAN_VOICE, STRING( pev->noiseStopMoving ), m_volume, ATTN_NORM );
		SetThink( &CFuncTrain::Next );
	}
	else
	{
		Next();// do it RIGHT now!
	}
}

//
// Train next - path corner needs to change to next target 
//
void CFuncTrain::Next( void )
{
	CBaseEntity *pTarg;

	// now find our next target
	pTarg = GetNextTarget();

	if( !pTarg )
	{
		if( pev->noiseMovement )
			STOP_SOUND( edict(), CHAN_STATIC, STRING( pev->noiseMovement ) );
		// Play stop sound
		if( pev->noiseStopMoving )
			EMIT_SOUND( ENT( pev ), CHAN_VOICE, STRING( pev->noiseStopMoving ), m_volume, ATTN_NORM );
		return;
	}

	// Save last target in case we need to find it again
	pev->message = pev->target;

	pev->target = pTarg->pev->target;
	m_flWait = pTarg->GetDelay();

	if( m_pevCurrentTarget && m_pevCurrentTarget->speed != 0 )
	{
		// don't copy speed from target if it is 0 (uninitialized)
		pev->speed = m_pevCurrentTarget->speed;
		ALERT( at_aiconsole, "Train %s speed to %4.2f\n", STRING( pev->targetname ), (double)pev->speed );
	}
	m_pevCurrentTarget = pTarg->pev;// keep track of this since path corners change our target for us.

	pev->enemy = pTarg->edict();//hack

	if( FBitSet( m_pevCurrentTarget->spawnflags, SF_CORNER_TELEPORT ) )
	{
		// Path corner has indicated a teleport to the next corner.
		SetBits( pev->effects, EF_NOINTERP );
		UTIL_SetOrigin( pev, pTarg->pev->origin - ( pev->mins + pev->maxs ) * 0.5f );
		Wait(); // Get on with doing the next path corner.
	}
	else
	{
		// Normal linear move.
		
		// CHANGED this from CHAN_VOICE to CHAN_STATIC around OEM beta time because trains should
		// use CHAN_STATIC for their movement sounds to prevent sound field problems.
		// this is not a hack or temporary fix, this is how things should be. (sjb).
		if( pev->noiseMovement )
		{
			STOP_SOUND( edict(), CHAN_STATIC, STRING( pev->noiseMovement ) );
			EMIT_SOUND( ENT( pev ), CHAN_STATIC, STRING( pev->noiseMovement ), m_volume, ATTN_NORM );
		}

		ClearBits( pev->effects, EF_NOINTERP );
		SetMoveDone( &CFuncTrain::Wait );
		LinearMove( pTarg->pev->origin - ( pev->mins + pev->maxs ) * 0.5f, pev->speed );
	}
}

void CFuncTrain::Activate( void )
{
	// Not yet active, so teleport to first target
	if( !m_activated )
	{
		m_activated = TRUE;
		entvars_t *pevTarg = VARS( FIND_ENTITY_BY_TARGETNAME( NULL, STRING( pev->target ) ) );

		pev->target = pevTarg->target;
		m_pevCurrentTarget = pevTarg;// keep track of this since path corners change our target for us.

		UTIL_SetOrigin( pev, pevTarg->origin - ( pev->mins + pev->maxs ) * 0.5 );

		if( FStringNull( pev->targetname ) )
		{	// not triggered, so start immediately
			pev->nextthink = pev->ltime + 0.1f;
			SetThink( &CFuncTrain::Next );
		}
		else
			pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER;
	}
}

/*QUAKED func_train (0 .5 .8) ?
Trains are moving platforms that players can ride.
The targets origin specifies the min point of the train at each corner.
The train spawns at the first target it is pointing at.
If the train is the target of a button or trigger, it will not begin moving until activated.
speed	default 100
dmg		default	2
sounds
1) ratchet metal
*/
void CFuncTrain::Spawn( void )
{
	Precache();
	if( pev->speed == 0 )
		pev->speed = 100;

	if( FStringNull(pev->target) )
		ALERT( at_console, "FuncTrain with no target" );

	if( pev->dmg == 0 )
		pev->dmg = 2;

	pev->movetype = MOVETYPE_PUSH;

	if( FBitSet( pev->spawnflags, SF_TRACKTRAIN_PASSABLE ) )
		pev->solid = SOLID_NOT;
	else
		pev->solid = SOLID_BSP;

	SET_MODEL( ENT( pev ), STRING( pev->model ) );
	UTIL_SetSize( pev, pev->mins, pev->maxs );
	UTIL_SetOrigin( pev, pev->origin );

	m_activated = FALSE;

	if( m_volume == 0 )
		m_volume = 0.85f;
}

void CFuncTrain::Precache( void )
{
	CBasePlatTrain::Precache();
#if 0	// obsolete
	// otherwise use preset sound
	switch( m_sounds )
	{
	case 0:
		pev->noise = 0;
		pev->noise1 = 0;
		break;
	case 1:
		PRECACHE_SOUND( "plats/train2.wav" );
		PRECACHE_SOUND( "plats/train1.wav" );
		pev->noise = MAKE_STRING( "plats/train2.wav" );
		pev->noise1 = MAKE_STRING( "plats/train1.wav" );
		break;
	case 2:
		PRECACHE_SOUND( "plats/platmove1.wav" );
		PRECACHE_SOUND( "plats/platstop1.wav" );
		pev->noise = MAKE_STRING( "plats/platstop1.wav" );
		pev->noise1 = MAKE_STRING( "plats/platmove1.wav" );
		break;
	}
#endif
}

void CFuncTrain::OverrideReset( void )
{
	CBaseEntity *pTarg;

	// Are we moving?
	if( pev->velocity != g_vecZero && pev->nextthink != 0 )
	{
		pev->target = pev->message;
		// now find our next target
		pTarg = GetNextTarget();
		if( !pTarg )
		{
			pev->nextthink = 0;
			pev->velocity = g_vecZero;
		}
		else	// Keep moving for 0.1 secs, then find path_corner again and restart
		{
			SetThink( &CFuncTrain::Next );
			pev->nextthink = pev->ltime + 0.1f;
		}
	}
}

// ---------------------------------------------------------------------
//
// Track Train
//
// ---------------------------------------------------------------------

TYPEDESCRIPTION	CFuncTrackTrain::m_SaveData[] = 
{
	DEFINE_FIELD( CFuncTrackTrain, m_ppath, FIELD_CLASSPTR ),
	DEFINE_FIELD( CFuncTrackTrain, m_length, FIELD_FLOAT ),
	DEFINE_FIELD( CFuncTrackTrain, m_height, FIELD_FLOAT ),
	DEFINE_FIELD( CFuncTrackTrain, m_speed, FIELD_FLOAT ),
	DEFINE_FIELD( CFuncTrackTrain, m_dir, FIELD_FLOAT ),
	DEFINE_FIELD( CFuncTrackTrain, m_startSpeed, FIELD_FLOAT ),
	DEFINE_FIELD( CFuncTrackTrain, m_controlMins, FIELD_VECTOR ),
	DEFINE_FIELD( CFuncTrackTrain, m_controlMaxs, FIELD_VECTOR ),
	DEFINE_FIELD( CFuncTrackTrain, m_sounds, FIELD_INTEGER ),
	DEFINE_FIELD( CFuncTrackTrain, m_flVolume, FIELD_FLOAT ),
	DEFINE_FIELD( CFuncTrackTrain, m_flBank, FIELD_FLOAT ),
	DEFINE_FIELD( CFuncTrackTrain, m_oldSpeed, FIELD_FLOAT ),
};

IMPLEMENT_SAVERESTORE( CFuncTrackTrain, CBaseEntity )
LINK_ENTITY_TO_CLASS( func_tracktrain, CFuncTrackTrain )

void CFuncTrackTrain::KeyValue( KeyValueData *pkvd )
{
	if( FStrEq( pkvd->szKeyName, "wheels" ) )
	{
		m_length = atof( pkvd->szValue );
		pkvd->fHandled = TRUE;
	}
	else if( FStrEq( pkvd->szKeyName, "height" ) )
	{
		m_height = atof(pkvd->szValue);
		pkvd->fHandled = TRUE;
	}
	else if( FStrEq( pkvd->szKeyName, "startspeed" ) )
	{
		m_startSpeed = atof(pkvd->szValue);
		pkvd->fHandled = TRUE;
	}
	else if( FStrEq( pkvd->szKeyName, "sounds" ) )
	{
		m_sounds = atoi(pkvd->szValue);
		pkvd->fHandled = TRUE;
	}
	else if( FStrEq( pkvd->szKeyName, "volume" ) )
	{
		m_flVolume = (float)atoi( pkvd->szValue );
		m_flVolume *= 0.1f;
		pkvd->fHandled = TRUE;
	}
	else if( FStrEq( pkvd->szKeyName, "bank" ) )
	{
		m_flBank = atof( pkvd->szValue );
		pkvd->fHandled = TRUE;
	}
	else
		CBaseEntity::KeyValue( pkvd );
}

void CFuncTrackTrain::NextThink( float thinkTime, BOOL alwaysThink )
{
	if( alwaysThink )
		pev->flags |= FL_ALWAYSTHINK;
	else
		pev->flags &= ~FL_ALWAYSTHINK;

	pev->nextthink = thinkTime;
}

void CFuncTrackTrain::Blocked( CBaseEntity *pOther )
{
	entvars_t *pevOther = pOther->pev;

	// Blocker is on-ground on the train
	if( FBitSet( pevOther->flags, FL_ONGROUND ) && VARS( pevOther->groundentity ) == pev )
	{
		float deltaSpeed = fabs( pev->speed );
		if( deltaSpeed > 50 )
			deltaSpeed = 50;
		if( !pevOther->velocity.z )
			pevOther->velocity.z += deltaSpeed;
		return;
	}
	else
		pevOther->velocity = ( pevOther->origin - pev->origin ).Normalize() * pev->dmg;

	ALERT( at_aiconsole, "TRAIN(%s): Blocked by %s (dmg:%.2f)\n", STRING( pev->targetname ), STRING( pOther->pev->classname ), (double)pev->dmg );
	if( pev->dmg <= 0 )
		return;
	// we can't hurt this thing, so we're not concerned with it
	pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH );
}

void CFuncTrackTrain::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	if( useType != USE_SET )
	{
		if( !ShouldToggle( useType, ( pev->speed != 0 ) ) )
			return;

		if( pev->speed == 0 )
		{
			pev->speed = m_speed * m_dir;
		
			Next();
		}
		else
		{
			pev->speed = 0;
			pev->velocity = g_vecZero;
			pev->avelocity = g_vecZero;
			StopSound();
			SetThink( NULL );
		}
	}
	else
	{
		float delta = value;

		delta = ( (int)( pev->speed * 4 ) / (int)m_speed ) * 0.25f + 0.25f * delta;
		if( delta > 1 )
			delta = 1;
		else if ( delta < -1 )
			delta = -1;
		if( pev->spawnflags & SF_TRACKTRAIN_FORWARDONLY )
		{
			if( delta < 0 )
				delta = 0;
		}
		pev->speed = m_speed * delta;
		Next();	
		ALERT( at_aiconsole, "TRAIN(%s), speed to %.2f\n", STRING( pev->targetname ), (double)pev->speed );
	}
}

static float Fix( float angle )
{
	while( angle < 0 )
		angle += 360;
	while( angle > 360 )
		angle -= 360;

	return angle;
}

static void FixupAngles( Vector &v )
{
	v.x = Fix( v.x );
	v.y = Fix( v.y );
	v.z = Fix( v.z );
}

#define TRAIN_STARTPITCH	60
#define TRAIN_MAXPITCH		200
#define TRAIN_MAXSPEED		1000	// approx max speed for sound pitch calculation

void CFuncTrackTrain::StopSound( void )
{
	// if sound playing, stop it
	if( m_soundPlaying && pev->noise )
	{
		unsigned short us_encode;
		unsigned short us_sound  = ( (unsigned short)( m_sounds ) & 0x0007 ) << 12;

		us_encode = us_sound;

		PLAYBACK_EVENT_FULL( FEV_RELIABLE | FEV_UPDATE, edict(), m_usAdjustPitch, 0.0, 
			g_vecZero, g_vecZero, 0.0, 0.0, us_encode, 0, 1, 0 );
		/*
		STOP_SOUND( ENT( pev ), CHAN_STATIC, STRING( pev->noise ) );
		*/
		EMIT_SOUND_DYN( ENT( pev ), CHAN_ITEM, "plats/ttrain_brake1.wav", m_flVolume, ATTN_NORM, 0, 100 );
	}

	m_soundPlaying = 0;
}

// update pitch based on speed, start sound if not playing
// NOTE: when train goes through transition, m_soundPlaying should go to 0, 
// which will cause the looped sound to restart.
void CFuncTrackTrain::UpdateSound( void )
{
	float flpitch;

	if( !pev->noise )
		return;

	flpitch = TRAIN_STARTPITCH + ( fabs( pev->speed ) * ( TRAIN_MAXPITCH - TRAIN_STARTPITCH ) / TRAIN_MAXSPEED );

	if( !m_soundPlaying )
	{
		// play startup sound for train
		EMIT_SOUND_DYN( ENT( pev ), CHAN_ITEM, "plats/ttrain_start1.wav", m_flVolume, ATTN_NORM, 0, 100 );
		EMIT_SOUND_DYN( ENT( pev ), CHAN_STATIC, STRING( pev->noise ), m_flVolume, ATTN_NORM, 0, (int)flpitch );
		m_soundPlaying = 1;
	} 
	else
	{
/*
		// update pitch
		EMIT_SOUND_DYN( ENT( pev ), CHAN_STATIC, STRING( pev->noise ), m_flVolume, ATTN_NORM, SND_CHANGE_PITCH, (int)flpitch );
*/
		// volume 0.0 - 1.0 - 6 bits
		// m_sounds 3 bits
		// flpitch = 6 bits
		// 15 bits total

		unsigned short us_encode;
		unsigned short us_sound  = ( ( unsigned short )( m_sounds ) & 0x0007 ) << 12;
		unsigned short us_pitch  = ( ( unsigned short )( flpitch / 10.0f ) & 0x003f ) << 6;
		unsigned short us_volume = ( ( unsigned short )( m_flVolume * 40.0f ) & 0x003f );

		us_encode = us_sound | us_pitch | us_volume;

		PLAYBACK_EVENT_FULL( FEV_RELIABLE | FEV_UPDATE, edict(), m_usAdjustPitch, 0.0f,
			g_vecZero, g_vecZero, 0.0f, 0.0f, us_encode, 0, 0, 0 );
	}
}

void CFuncTrackTrain::Next( void )
{
	float time = 0.5;

	if( !pev->speed )
	{
		ALERT( at_aiconsole, "TRAIN(%s): Speed is 0\n", STRING( pev->targetname ) );
		StopSound();
		return;
	}

	//if( !m_ppath )
	//	m_ppath = CPathTrack::Instance( FIND_ENTITY_BY_TARGETNAME( NULL, STRING( pev->target ) ) );
	if( !m_ppath )
	{	
		ALERT( at_aiconsole, "TRAIN(%s): Lost path\n", STRING( pev->targetname ) );
		StopSound();
		return;
	}

	UpdateSound();

	Vector nextPos = pev->origin;

	nextPos.z -= m_height;
	CPathTrack *pnext = m_ppath->LookAhead( &nextPos, pev->speed * 0.1f, 1 );
	nextPos.z += m_height;

	pev->velocity = ( nextPos - pev->origin ) * 10;
	Vector nextFront = pev->origin;

	nextFront.z -= m_height;
	if( m_length > 0 )
		m_ppath->LookAhead( &nextFront, m_length, 0 );
	else
		m_ppath->LookAhead( &nextFront, 100, 0 );
	nextFront.z += m_height;

	Vector delta = nextFront - pev->origin;
	Vector angles = UTIL_VecToAngles( delta );
	// The train actually points west
	angles.y += 180;

	// !!!  All of this crap has to be done to make the angles not wrap around, revisit this.
	FixupAngles( angles );
	FixupAngles( pev->angles );

	if( !pnext || ( delta.x == 0 && delta.y == 0 ) )
		angles = pev->angles;

	float vy, vx;
	if( !( pev->spawnflags & SF_TRACKTRAIN_NOPITCH ) )
		vx = UTIL_AngleDistance( angles.x, pev->angles.x );
	else
		vx = 0;
	vy = UTIL_AngleDistance( angles.y, pev->angles.y );

	pev->avelocity.y = vy * 10;
	pev->avelocity.x = vx * 10;

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

	if( pnext )
	{
		if( pnext != m_ppath )
		{
			CPathTrack *pFire;
			if( pev->speed >= 0 )
				pFire = pnext;
			else 
				pFire = m_ppath;

			m_ppath = pnext;
			// Fire the pass target if there is one
			if( pFire->pev->message )
			{
				FireTargets( STRING( pFire->pev->message ), this, this, USE_TOGGLE, 0 );
				if( FBitSet( pFire->pev->spawnflags, SF_PATH_FIREONCE ) )
					pFire->pev->message = 0;
			}

			if( pFire->pev->spawnflags & SF_PATH_DISABLE_TRAIN )
				pev->spawnflags |= SF_TRACKTRAIN_NOCONTROL;

			// Don't override speed if under user control
			if( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL )
			{
				if( pFire->pev->speed != 0 )
				{
					// don't copy speed from target if it is 0 (uninitialized)
					pev->speed = pFire->pev->speed;
					ALERT( at_aiconsole, "TrackTrain %s speed to %4.2f\n", STRING( pev->targetname ), (double)pev->speed );
				}
			}

		}
		SetThink( &CFuncTrackTrain::Next );
		NextThink( pev->ltime + time, TRUE );
	}
	else // end of path, stop
	{
		StopSound();
		pev->velocity = nextPos - pev->origin;
		pev->avelocity = g_vecZero;
		float distance = pev->velocity.Length();
		m_oldSpeed = pev->speed;

		pev->speed = 0;

		// Move to the dead end

		// Are we there yet?
		if( distance > 0 )
		{
			// no, how long to get there?
			time = distance / m_oldSpeed;
			pev->velocity = pev->velocity * ( m_oldSpeed / distance );
			SetThink( &CFuncTrackTrain::DeadEnd );
			NextThink( pev->ltime + time, FALSE );
		}
		else
		{
			DeadEnd();
		}
	}
}

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

	pTrack = m_ppath;

	ALERT( at_aiconsole, "TRAIN(%s): Dead end ", STRING( pev->targetname ) );
	// 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 );
		}
	}

	pev->velocity = g_vecZero;
	pev->avelocity = g_vecZero;
	if( pTrack )
	{
		ALERT( at_aiconsole, "at %s\n", STRING( pTrack->pev->targetname ) );
		if( pTrack->pev->netname )
			FireTargets( STRING( pTrack->pev->netname ), this, this, USE_TOGGLE, 0 );
	}
	else
		ALERT( at_aiconsole, "\n" );
}

void CFuncTrackTrain::SetControls( entvars_t *pevControls )
{
	Vector offset = pevControls->origin - pev->oldorigin;

	m_controlMins = pevControls->mins + offset;
	m_controlMaxs = pevControls->maxs + offset;
}

BOOL CFuncTrackTrain::OnControls( entvars_t *pevTest )
{
	Vector offset = pevTest->origin - pev->origin;

	if( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL )
		return FALSE;

	// Transform offset into local coordinates
	UTIL_MakeVectors( pev->angles );
	Vector local;
	local.x = DotProduct( offset, gpGlobals->v_forward );
	local.y = -DotProduct( offset, gpGlobals->v_right );
	local.z = DotProduct( offset, gpGlobals->v_up );

	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::Instance( FIND_ENTITY_BY_TARGETNAME( NULL, STRING( pev->target ) ) );
	if( !m_ppath )
		return;

	entvars_t *pevTarget = m_ppath->pev;
	if( !FClassnameIs( pevTarget, "path_track" ) )
	{
		ALERT( at_error, "func_track_train must be on a path of path_track\n" );
		m_ppath = NULL;
		return;
	}

	Vector nextPos = pevTarget->origin;
	nextPos.z += m_height;

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

	pev->angles = UTIL_VecToAngles( look - nextPos );
	// The train actually points west
	pev->angles.y += 180;

	if( pev->spawnflags & SF_TRACKTRAIN_NOPITCH )
		pev->angles.x = 0;
	UTIL_SetOrigin( pev, nextPos );
	NextThink( pev->ltime + 0.1f, FALSE );
	SetThink( &CFuncTrackTrain::Next );
	pev->speed = m_startSpeed;

	UpdateSound();
}

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

	closest = 1024;

	while( ( pTrack = UTIL_FindEntityInSphere( pTrack, pev->origin, 1024 ) ) != NULL )
	{
		// filter out non-tracks
		if( !( pTrack->pev->flags & ( FL_CLIENT | FL_MONSTER ) ) && FClassnameIs( pTrack->pev, "path_track" ) )
		{
			dist = ( pev->origin - pTrack->pev->origin ).Length();
			if( dist < closest )
			{
				closest = dist;
				pNearest = pTrack;
			}
		}
	}

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

	ALERT( at_aiconsole, "TRAIN: %s, Nearest track is %s\n", STRING( pev->targetname ), STRING( pNearest->pev->targetname ) );
	// 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( ( pev->origin - pTrack->pev->origin ).Length() < ( pev->origin - pNearest->pev->origin ).Length() )
			pNearest = pTrack;
	}

	m_ppath = (CPathTrack *)pNearest;

	if( pev->speed != 0 )
	{
		NextThink( pev->ltime + 0.1f, FALSE );
		SetThink( &CFuncTrackTrain::Next );
	}
}

void CFuncTrackTrain::OverrideReset( void )
{
	NextThink( pev->ltime + 0.1f, FALSE );
	SetThink( &CFuncTrackTrain::NearestPath );
}

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

/*QUAKED func_train (0 .5 .8) ?
Trains are moving platforms that players can ride.
The targets origin specifies the min point of the train at each corner.
The train spawns at the first target it is pointing at.
If the train is the target of a button or trigger, it will not begin moving until activated.
speed	default 100
dmg		default	2
sounds
1) ratchet metal
*/

void CFuncTrackTrain::Spawn( void )
{
	if( pev->speed == 0 )
		m_speed = 100;
	else
		m_speed = pev->speed;

	pev->speed = 0;
	pev->velocity = g_vecZero;
	pev->avelocity = g_vecZero;
	pev->impulse = (int)m_speed;

	m_dir = 1;

	if( FStringNull( pev->target ) )
		ALERT( at_console, "FuncTrain with no target" );

	if( pev->spawnflags & SF_TRACKTRAIN_PASSABLE )
		pev->solid = SOLID_NOT;
	else
		pev->solid = SOLID_BSP;
	pev->movetype = MOVETYPE_PUSH;

	SET_MODEL( ENT( pev ), STRING( pev->model ) );

	UTIL_SetSize( pev, pev->mins, pev->maxs );
	UTIL_SetOrigin( pev, pev->origin );

	// Cache off placed origin for train controls
	pev->oldorigin = pev->origin;

	m_controlMins = pev->mins;
	m_controlMaxs = pev->maxs;
	m_controlMaxs.z += 72;

	// start trains on the next frame, to make sure their targets have had
	// a chance to spawn/activate
	NextThink( pev->ltime + 0.1f, FALSE );
	SetThink( &CFuncTrackTrain::Find );
	Precache();
}

void CFuncTrackTrain::Precache( void )
{
	const char *pszSound;

	if( m_flVolume == 0.0f )
		m_flVolume = 1.0f;

	switch( m_sounds )
	{
	default:
		// no sound
		pszSound = NULL;
		break;
	case 1:
		pszSound = "plats/ttrain1.wav";
		break;
	case 2:
		pszSound = "plats/ttrain2.wav";
		break;
	case 3:
		pszSound = "plats/ttrain3.wav";
		break; 
	case 4:
		pszSound = "plats/ttrain4.wav";
		break;
	case 5:
		pszSound = "plats/ttrain6.wav";
		break;
	case 6:
		pszSound = "plats/ttrain7.wav";
		break;
	}

	if( pszSound )
	{
		PRECACHE_SOUND( pszSound );
		pev->noise = MAKE_STRING( pszSound );
	}
	else
		pev->noise = 0;

	PRECACHE_SOUND( "plats/ttrain_brake1.wav" );
	PRECACHE_SOUND( "plats/ttrain_start1.wav" );

	m_usAdjustPitch = PRECACHE_EVENT( 1, "events/train.sc" );
}

// This class defines the volume of space that the player must stand in to control the train
class CFuncTrainControls : public CBaseEntity
{
public:
	virtual int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
	void Spawn( void );
	void EXPORT Find( void );
};

LINK_ENTITY_TO_CLASS( func_traincontrols, CFuncTrainControls )

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

	do 
	{
		pTarget = FIND_ENTITY_BY_TARGETNAME( pTarget, STRING( pev->target ) );
	} while( !FNullEnt(pTarget) && !FClassnameIs( pTarget, "func_tracktrain" ) );

	if( FNullEnt( pTarget ) )
	{
		ALERT( at_console, "No train %s\n", STRING( pev->target ) );
		return;
	}

	CFuncTrackTrain *ptrain = CFuncTrackTrain::Instance( pTarget );
	ptrain->SetControls( pev );
	UTIL_Remove( this );
}

void CFuncTrainControls::Spawn( void )
{
	pev->solid = SOLID_NOT;
	pev->movetype = MOVETYPE_NONE;
	SET_MODEL( ENT( pev ), STRING( pev->model ) );

	UTIL_SetSize( pev, pev->mins, pev->maxs );
	UTIL_SetOrigin( pev, pev->origin );

	SetThink( &CFuncTrainControls::Find );
	pev->nextthink = gpGlobals->time;
}

// ----------------------------------------------------------------------------
//
// Track changer / Train elevator
//
// ----------------------------------------------------------------------------
#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

//
// 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.
//
typedef enum
{
	TRAIN_SAFE,
	TRAIN_BLOCKING,
	TRAIN_FOLLOWING
}TRAIN_CODE;

class CFuncTrackChange : public CFuncPlatRot
{
public:
	void Spawn( void );
	void Precache( void );

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

	void KeyValue( KeyValueData* pkvd );
	void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
	void EXPORT Find( void );
	TRAIN_CODE EvaluateTrain( CPathTrack *pcurrent );
	void UpdateTrain( Vector &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; }

	virtual int Save( CSave &save );
	virtual int Restore( CRestore &restore );
	static TYPEDESCRIPTION m_SaveData[];

	virtual void OverrideReset( void );

	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 )

TYPEDESCRIPTION	CFuncTrackChange::m_SaveData[] =
{
	DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackTop, FIELD_CLASSPTR ),
	DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackBottom, FIELD_CLASSPTR ),
	DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_train, FIELD_CLASSPTR ),
	DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackTopName, FIELD_STRING ),
	DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackBottomName, FIELD_STRING ),
	DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trainName, FIELD_STRING ),
	DEFINE_FIELD( CFuncTrackChange, m_code, FIELD_INTEGER ),
	DEFINE_FIELD( CFuncTrackChange, m_targetState, FIELD_INTEGER ),
	DEFINE_FIELD( CFuncTrackChange, m_use, FIELD_INTEGER ),
};

IMPLEMENT_SAVERESTORE( CFuncTrackChange, CFuncPlatRot )

void CFuncTrackChange::Spawn( void )
{
	Setup();
	if( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) )
		m_vecPosition2.z = pev->origin.z;

	SetupRotation();

	if( FBitSet( pev->spawnflags, SF_TRACK_STARTBOTTOM ) )
	{
		UTIL_SetOrigin( pev, m_vecPosition2 );
		m_toggle_state = TS_AT_BOTTOM;
		pev->angles = m_start;
		m_targetState = TS_AT_TOP;
	}
	else
	{
		UTIL_SetOrigin( pev, m_vecPosition1 );
		m_toggle_state = TS_AT_TOP;
		pev->angles = m_end;
		m_targetState = TS_AT_BOTTOM;
	}

	EnableUse();
	pev->nextthink = pev->ltime + 2.0f;
	SetThink( &CFuncTrackChange::Find );
	Precache();
}

void CFuncTrackChange::Precache( void )
{
	// Can't trigger sound
	PRECACHE_SOUND( "buttons/button11.wav" );

	CFuncPlatRot::Precache();
}

// UNDONE: Filter touches before re-evaluating the train.
void CFuncTrackChange::Touch( CBaseEntity *pOther )
{
#if 0
	TRAIN_CODE code;
	entvars_t *pevToucher = pOther->pev;
#endif
}

void CFuncTrackChange::KeyValue( KeyValueData *pkvd )
{
	if( FStrEq( pkvd->szKeyName, "train" ) )
	{
		m_trainName = ALLOC_STRING( pkvd->szValue );
		pkvd->fHandled = TRUE;
	}
	else if( FStrEq( pkvd->szKeyName, "toptrack" ) )
	{
		m_trackTopName = ALLOC_STRING( pkvd->szValue );
		pkvd->fHandled = TRUE;
	}
	else if( FStrEq( pkvd->szKeyName, "bottomtrack" ) )
	{
		m_trackBottomName = ALLOC_STRING( pkvd->szValue );
		pkvd->fHandled = TRUE;
	}
	else
	{
		CFuncPlatRot::KeyValue( pkvd );		// Pass up to base class
	}
}

void CFuncTrackChange::OverrideReset( void )
{
	pev->nextthink = pev->ltime + 1.0f;
	SetThink( &CFuncTrackChange::Find );
}

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

	target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING( m_trackTopName ) );
	if( !FNullEnt( target ) )
	{
		m_trackTop = CPathTrack::Instance( target );
		target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING( m_trackBottomName ) );
		if( !FNullEnt( target ) )
		{
			m_trackBottom = CPathTrack::Instance( target );
			target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING( m_trainName ) );
			if( !FNullEnt( target ) )
			{
				m_train = CFuncTrackTrain::Instance( FIND_ENTITY_BY_TARGETNAME( NULL, STRING( m_trainName ) ) );
				if( !m_train )
				{
					ALERT( at_error, "Can't find train for track change! %s\n", STRING( m_trainName ) );
					return;
				}
				Vector center = ( pev->absmin + pev->absmax ) * 0.5f;
				m_trackBottom = m_trackBottom->Nearest( center );
				m_trackTop = m_trackTop->Nearest( center );
				UpdateAutoTargets( m_toggle_state );
				SetThink( NULL );
				return;
			}
			else
			{
				ALERT( at_error, "Can't find train for track change! %s\n", STRING( m_trainName ) );
				// target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING( m_trainName ) );
			}
		}
		else
			ALERT( at_error, "Can't find bottom track for track change! %s\n", STRING( m_trackBottomName ) );
	}
	else
		ALERT( at_error, "Can't find top track for track change! %s\n", STRING( m_trackTopName ) );
}

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->pev->speed != 0 )
			return TRAIN_BLOCKING;

		Vector dist = pev->origin - m_train->pev->origin;
		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( Vector &dest )
{
	float time = ( pev->nextthink - pev->ltime );

	m_train->pev->velocity = pev->velocity;
	m_train->pev->avelocity = pev->avelocity;
	m_train->NextThink( m_train->pev->ltime + time, FALSE );

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

	Vector offset = m_train->pev->origin - pev->origin;
	Vector delta = dest - pev->angles;
	// Transform offset into local coordinates
	UTIL_MakeInvVectors( delta, gpGlobals );
	Vector local;
	local.x = DotProduct( offset, gpGlobals->v_forward );
	local.y = DotProduct( offset, gpGlobals->v_right );
	local.z = DotProduct( offset, gpGlobals->v_up );

	local = local - offset;
	m_train->pev->velocity = pev->velocity + ( local * ( 1.0f / 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( pev->spawnflags, SF_TRACK_DONT_MOVE ) )
	{
		SetMoveDone( &CFuncPlat::CallHitBottom );
		m_toggle_state = TS_GOING_DOWN;
		AngularMove( m_start, pev->speed );
	}
	else
	{
		CFuncPlat::GoDown();
		SetMoveDone( &CFuncPlat::CallHitBottom );
		RotMove( m_start, pev->nextthink - pev->ltime );
	}
	// 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;
	}
}

//
// 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( pev->spawnflags, SF_TRACK_DONT_MOVE ) )
	{
		m_toggle_state = TS_GOING_UP;
		SetMoveDone( &CFuncPlat::CallHitTop );
		AngularMove( m_end, pev->speed );
	}
	else
	{
		// If ROTMOVE, move & rotate
		CFuncPlat::GoUp();
		SetMoveDone( &CFuncPlat::CallHitTop );
		RotMove( m_end, pev->nextthink - pev->ltime );
	}

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

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

	if( toggleState == TS_AT_TOP )
		ClearBits( m_trackTop->pev->spawnflags, SF_PATH_DISABLED );
	else
		SetBits( m_trackTop->pev->spawnflags, SF_PATH_DISABLED );

	if( toggleState == TS_AT_BOTTOM )
		ClearBits( m_trackBottom->pev->spawnflags, SF_PATH_DISABLED );
	else
		SetBits( m_trackBottom->pev->spawnflags, 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
		EMIT_SOUND( ENT( pev ), CHAN_VOICE, "buttons/button11.wav", 1, ATTN_NORM );
		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 )
{
	CFuncPlatRot::HitBottom();
	if( m_code == TRAIN_FOLLOWING )
	{
		//UpdateTrain();
		m_train->SetTrack( m_trackBottom );
	}
	SetThink( NULL );
	pev->nextthink = -1;

	UpdateAutoTargets( m_toggle_state );

	EnableUse();
}

//
// Platform has hit bottom.  Stops and waits forever.
//
void CFuncTrackChange::HitTop( void )
{
	CFuncPlatRot::HitTop();
	if( m_code == TRAIN_FOLLOWING )
	{
		//UpdateTrain();
		m_train->SetTrack( m_trackTop );
	}

	// Don't let the plat go back down
	SetThink( NULL );
	pev->nextthink = -1;
	UpdateAutoTargets( m_toggle_state );
	EnableUse();
}

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

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 )
	{
		ClearBits( pTarget->pev->spawnflags, SF_PATH_DISABLED );
		if( m_code == TRAIN_FOLLOWING && m_train && m_train->pev->speed == 0 )
			m_train->Use( this, this, USE_ON, 0 );
	}

	if( pNextTarget )
		SetBits( pNextTarget->pev->spawnflags, SF_PATH_DISABLED );
}

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->pev, "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 );
	}
}

// ----------------------------------------------------------
//
//
// pev->speed is the travel speed
// pev->health is current health
// pev->max_health is the amount to reset to each time it starts

#define FGUNTARGET_START_ON			0x0001

class CGunTarget : public CBaseMonster
{
public:
	void		Spawn( void );
	void		Activate( void );
	void EXPORT	Next( void );
	void EXPORT	Start( void );
	void EXPORT	Wait( void );
	void		Stop( void );

	int		BloodColor( void ) { return DONT_BLEED; }
	int		Classify( void ) { return CLASS_MACHINE; }
	int		TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType );
	void		Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
	Vector		BodyTarget( const Vector &posSrc ) { return pev->origin; }

	virtual int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
	virtual int Save( CSave &save );
	virtual int Restore( CRestore &restore );

	static TYPEDESCRIPTION m_SaveData[];

private:
	BOOL m_on;
};

LINK_ENTITY_TO_CLASS( func_guntarget, CGunTarget )

TYPEDESCRIPTION	CGunTarget::m_SaveData[] = 
{
	DEFINE_FIELD( CGunTarget, m_on, FIELD_BOOLEAN ),
};

IMPLEMENT_SAVERESTORE( CGunTarget, CBaseMonster )

void CGunTarget::Spawn( void )
{
	pev->solid = SOLID_BSP;
	pev->movetype = MOVETYPE_PUSH;

	UTIL_SetOrigin( pev, pev->origin );
	SET_MODEL( ENT( pev ), STRING( pev->model ) );

	if( pev->speed == 0 )
		pev->speed = 100;

	// Don't take damage until "on"
	pev->takedamage = DAMAGE_NO;
	pev->flags |= FL_MONSTER;

	m_on = FALSE;
	pev->max_health = pev->health;

	if( pev->spawnflags & FGUNTARGET_START_ON )
	{
		SetThink( &CGunTarget::Start );
		pev->nextthink = pev->ltime + 0.3f;
	}
}

void CGunTarget::Activate( void )
{
	CBaseEntity *pTarg;

	// now find our next target
	pTarg = GetNextTarget();
	if( pTarg )
	{
		m_hTargetEnt = pTarg;
		UTIL_SetOrigin( pev, pTarg->pev->origin - ( pev->mins + pev->maxs ) * 0.5f );
	}
}

void CGunTarget::Start( void )
{
	Use( this, this, USE_ON, 0 );
}

void CGunTarget::Next( void )
{
	SetThink( NULL );

	m_hTargetEnt = GetNextTarget();
	CBaseEntity *pTarget = m_hTargetEnt;

	if( !pTarget )
	{
		Stop();
		return;
	}
	SetMoveDone( &CGunTarget::Wait );
	LinearMove( pTarget->pev->origin - ( pev->mins + pev->maxs ) * 0.5f, pev->speed );
}

void CGunTarget::Wait( void )
{
	CBaseEntity *pTarget = m_hTargetEnt;

	if( !pTarget )
	{
		Stop();
		return;
	}

	// Fire the pass target if there is one
	if( pTarget->pev->message )
	{
		FireTargets( STRING(pTarget->pev->message), this, this, USE_TOGGLE, 0 );
		if( FBitSet( pTarget->pev->spawnflags, SF_CORNER_FIREONCE ) )
			pTarget->pev->message = 0;
	}

	m_flWait = pTarget->GetDelay();

	pev->target = pTarget->pev->target;
	SetThink( &CGunTarget::Next );
	if( m_flWait != 0 )
	{
		// -1 wait will wait forever!		
		pev->nextthink = pev->ltime + m_flWait;
	}
	else
	{
		Next();// do it RIGHT now!
	}
}

void CGunTarget::Stop( void )
{
	pev->velocity = g_vecZero;
	pev->nextthink = 0;
	pev->takedamage = DAMAGE_NO;
}

int CGunTarget::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType )
{
	if( pev->health > 0 )
	{
		pev->health -= flDamage;
		if( pev->health <= 0 )
		{
			pev->health = 0;
			Stop();
			if( pev->message )
				FireTargets( STRING( pev->message ), this, this, USE_TOGGLE, 0 );
		}
	}
	return 0;
}

void CGunTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	if( !ShouldToggle( useType, m_on ) )
		return;

	if( m_on )
	{
		Stop();
	}
	else
	{
		pev->takedamage = DAMAGE_AIM;
		m_hTargetEnt = GetNextTarget();
		if( m_hTargetEnt == 0 )
			return;
		pev->health = pev->max_health;
		Next();
	}
}
