diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/trains.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/trains.cpp')
| -rw-r--r-- | mp/src/game/server/trains.cpp | 6756 |
1 files changed, 3378 insertions, 3378 deletions
diff --git a/mp/src/game/server/trains.cpp b/mp/src/game/server/trains.cpp index c074763c..639523b9 100644 --- a/mp/src/game/server/trains.cpp +++ b/mp/src/game/server/trains.cpp @@ -1,3378 +1,3378 @@ -//========= Copyright 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"
-#include "vphysics/friction.h"
-#include "hierarchy.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 ) )
- {
- IncrementInterpolationFrame();
-
- // 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();
-
- 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 );
-
- 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 '%s' has no target.\n", GetDebugName());
- }
-
- 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_lastBlockPos, FIELD_POSITION_VECTOR ), // temp values for blocking, don't save
- //DEFINE_FIELD( m_lastBlockTick, FIELD_INTEGER ),
-
- DEFINE_FIELD( m_bSoundPlaying, FIELD_BOOLEAN ),
-
- DEFINE_KEYFIELD( m_bManualSpeedChanges, FIELD_BOOLEAN, "ManualSpeedChanges" ),
- DEFINE_KEYFIELD( m_flAccelSpeed, FIELD_FLOAT, "ManualAccelSpeed" ),
- DEFINE_KEYFIELD( m_flDecelSpeed, FIELD_FLOAT, "ManualDecelSpeed" ),
-
-#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 ),
- DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeedDirAccel", InputSetSpeedDirAccel ),
- DEFINE_INPUTFUNC( FIELD_STRING, "TeleportToPathTrack", InputTeleportToPathTrack ),
- DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeedForwardModifier", InputSetSpeedForwardModifier ),
-
- // Outputs
- DEFINE_OUTPUT( m_OnStart, "OnStart" ),
- DEFINE_OUTPUT( m_OnNext, "OnNextPoint" ),
-
- // 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;
- m_lastBlockPos.Init();
- m_lastBlockTick = gpGlobals->tickcount;
-
- m_flSpeedForwardModifier = 1.0f;
- m_flUnmodifiedDesiredSpeed = 0.0f;
-
- m_bDamageChild = false;
-}
-
-
-//-----------------------------------------------------------------------------
-// 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] );
- EntityText( nOffset, tempstr, 0 );
- nOffset++;
-
- float flCurSpeed = GetLocalVelocity().Length();
- Q_snprintf( tempstr,sizeof(tempstr), "current speed (goal): %g (%g)", (double)flCurSpeed, (double)m_flSpeed );
- EntityText( nOffset, tempstr, 0 );
- nOffset++;
-
- Q_snprintf( tempstr,sizeof(tempstr), "max speed: %g", (double)m_maxSpeed );
- EntityText( 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 )
-{
- m_OnStart.FireOutput(this,this);
- 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.f, 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.f, 1.f );
- 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.f, 1.f );
- SetSpeed( m_maxSpeed * flScale );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Input handler that sets the speed of the train and the direction
-// based on the sign of the speed, and accels/decels to that speed
-// Input : Float speed scale from -1 to 1. Negatives values indicate a reversed
-// direction.
-//-----------------------------------------------------------------------------
-void CFuncTrackTrain::InputSetSpeedDirAccel( inputdata_t &inputdata )
-{
- SetSpeedDirAccel( inputdata.value.Float() );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CFuncTrackTrain::SetSpeedDirAccel( float flNewSpeed )
-{
- float newSpeed = flNewSpeed;
- SetDirForward( newSpeed >= 0 );
- newSpeed = fabs( newSpeed );
- float flScale = clamp( newSpeed, 0.f, 1.f );
- SetSpeed( m_maxSpeed * flScale, true );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CFuncTrackTrain::InputSetSpeedForwardModifier( inputdata_t &inputdata )
-{
- SetSpeedForwardModifier( inputdata.value.Float() ) ;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CFuncTrackTrain::SetSpeedForwardModifier( float flModifier )
-{
- float flSpeedForwardModifier = flModifier;
- flSpeedForwardModifier = fabs( flSpeedForwardModifier );
-
- m_flSpeedForwardModifier = clamp( flSpeedForwardModifier, 0.f, 1.f );
- SetSpeed( m_flUnmodifiedDesiredSpeed, true );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void CFuncTrackTrain::InputTeleportToPathTrack( inputdata_t &inputdata )
-{
- const char *pszName = inputdata.value.String();
- CPathTrack *pTrack = dynamic_cast<CPathTrack*>( gEntList.FindEntityByName( NULL, pszName ) );
-
- if ( pTrack )
- {
- TeleportToPathTrack( pTrack );
- m_ppath = pTrack;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Sets the speed of the train to the given value in units per second.
-//-----------------------------------------------------------------------------
-void CFuncTrackTrain::SetSpeed( float flSpeed, bool bAccel /*= false */ )
-{
- m_bAccelToSpeed = bAccel;
-
- m_flUnmodifiedDesiredSpeed = flSpeed;
- float flOldSpeed = m_flSpeed;
-
- // are we using a speed forward modifier?
- if ( m_flSpeedForwardModifier < 1.0 && m_dir > 0 )
- {
- flSpeed = flSpeed * m_flSpeedForwardModifier;
- }
-
- if ( m_bAccelToSpeed )
- {
- m_flDesiredSpeed = fabs( flSpeed ) * m_dir;
- m_flSpeedChangeTime = gpGlobals->curtime;
-
- if ( m_flSpeed == 0 && abs(m_flDesiredSpeed) > 0 )
- {
- m_flSpeed = 0.1; // little push to get us going
- }
-
- Start();
-
- return;
- }
-
- 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);
-}
-
-static CBaseEntity *FindPhysicsBlockerForHierarchy( CBaseEntity *pParentEntity )
-{
- CUtlVector<CBaseEntity *> list;
- GetAllInHierarchy( pParentEntity, list );
- CBaseEntity *pPhysicsBlocker = NULL;
- float maxForce = 0;
- for ( int i = 0; i < list.Count(); i++ )
- {
- IPhysicsObject *pPhysics = list[i]->VPhysicsGetObject();
- if ( pPhysics )
- {
- IPhysicsFrictionSnapshot *pSnapshot = pPhysics->CreateFrictionSnapshot();
- 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( pParentEntity->GetAbsVelocity(), pSnapshot->GetNormalForce() * normal );
- if ( !pPhysicsBlocker || dot > maxForce )
- {
- pPhysicsBlocker = pOtherEntity;
- maxForce = dot;
- }
- }
- pSnapshot->NextFrictionData();
- }
- pPhysics->DestroyFrictionSnapshot( pSnapshot );
- }
- }
- return pPhysicsBlocker;
-}
-
-//-----------------------------------------------------------------------------
-// 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 = FindPhysicsBlockerForHierarchy(this);
- if ( pPhysicsBlocker )
- {
- // This code keeps track of how long this train has been blocked
- // The heuristic here is to keep instantaneous blocks from invoking the somewhat
- // heavy-handed solver (which will disable collisions until we're clear) in cases
- // where physics can solve it easily enough.
- int ticksBlocked = gpGlobals->tickcount - m_lastBlockTick;
- float dist = 0.0f;
- // wait at least 10 ticks and make sure the train isn't actually moving before really blocking
- const int MIN_BLOCKED_TICKS = 10;
- if ( ticksBlocked > MIN_BLOCKED_TICKS )
- {
- dist = (GetAbsOrigin() - m_lastBlockPos).Length();
- // must have moved at least 10% of normal velocity over the blocking interval, or we're being blocked
- float minLength = GetAbsVelocity().Length() * TICK_INTERVAL * MIN_BLOCKED_TICKS * 0.10f;
- if ( dist < minLength )
- {
- // been stuck for more than one tick without moving much?
- // yes, disable collisions with the physics object most likely to be blocking us
- EntityPhysics_CreateSolver( this, pPhysicsBlocker, true, 4.0f );
- }
- }
- // first time blocking or moved too far since last block, reset
- if ( dist > 1.0f || m_lastBlockTick < 0 )
- {
- m_lastBlockPos = GetAbsOrigin();
- m_lastBlockTick = gpGlobals->tickcount;
- }
- }
- // unblockable shouldn't damage the player in this case
- if ( pOther->IsPlayer() )
- return;
- }
-
- 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;
- }
-
- // In multiplayer, only update the sound once a second
- if ( g_pGameRules->IsMultiplayer() && m_bSoundPlaying )
- {
- if ( m_flNextMPSoundTime > gpGlobals->curtime )
- return;
-
- m_flNextMPSoundTime = gpGlobals->curtime + 1.0;
- }
-
- float flSpeedRatio = 0;
- if ( HasSpawnFlags( SF_TRACKTRAIN_USE_MAXSPEED_FOR_PITCH ) )
- {
- flSpeedRatio = clamp( fabs( m_flSpeed ) / m_maxSpeed, 0.f, 1.f );
- }
- else
- {
- flSpeedRatio = clamp( fabs( m_flSpeed ) / TRAIN_MAXSPEED, 0.f, 1.f );
- }
-
- 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;
-
- // In multiplayer, don't make this reliable
- if ( g_pGameRules->IsMultiplayer() )
- {
- EmitSound( filter, entindex(), ep );
- }
- else
- {
- 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 ( m_bAccelToSpeed )
- {
- float flPrevSpeed = m_flSpeed;
- float flNextSpeed = m_flDesiredSpeed;
-
- if ( flPrevSpeed != flNextSpeed )
- {
- float flSpeedChangeTime = ( abs(flNextSpeed) > abs(flPrevSpeed) ) ? m_flAccelSpeed : m_flDecelSpeed;
- m_flSpeed = UTIL_Approach( m_flDesiredSpeed, m_flSpeed, flSpeedChangeTime * gpGlobals->frametime );
- }
- }
- else 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();
-
- CPathTrack *pNextNode = NULL;
-
- nextFront.z -= m_height;
- if ( m_length > 0 )
- {
- m_ppath->LookAhead( nextFront, IsDirForward() ? m_length : -m_length, 0, &pNextNode );
- }
- else
- {
- m_ppath->LookAhead( nextFront, IsDirForward() ? 100 : -100, 0, &pNextNode );
- }
- 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 );
-
- // Wrapped with this bool so we don't affect old trains
- if ( m_bManualSpeedChanges )
- {
- if ( pNextNode && pNextNode->GetOrientationType() == TrackOrientation_FacePathAngles )
- {
- angles = pNextNode->GetOrientation( IsDirForward() );
- }
- }
-
- 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 );
-
- variant_t emptyVariant;
- pTeleport->AcceptInput( "InTeleport", this, this, emptyVariant, 0 );
-}
-
-
-//-----------------------------------------------------------------------------
-// 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 we're moving towards a dead end, but our desired speed goes in the opposite direction
- // this fixes us from stalling
- if ( m_bManualSpeedChanges && ( ( flSpeed < 0 ) != ( m_flDesiredSpeed < 0 ) ) )
- {
- if ( !pNext )
- pNext = m_ppath;
- }
-
- if (m_debugOverlays & OVERLAY_BBOX_BIT)
- {
- if ( pNext != NULL )
- {
- NDebugOverlay::Line( GetAbsOrigin(), pNext->GetAbsOrigin(), 255, 0, 0, true, 0.1 );
- 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( GetAbsOrigin(), pNextNext->GetAbsOrigin(), 0, 255, 0, true, 0.1 );
- 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 );
- }
- }
-
- m_OnNext.FireOutput( pNext, this );
-
- 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 );
- if ( !m_ppath )
- return;
-
- if ( !FClassnameIs( m_ppath, "path_track" )
-#ifndef PORTAL //env_portal_path_track is a child of path_track and would like to get found
- && !FClassnameIs( m_ppath, "env_portal_path_track" )
-#endif //#ifndef PORTAL
- )
- {
- 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("FuncTrackTrain '%s' has no target.\n", GetDebugName());
- }
-
- 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();
-}
-
-void CFuncTrackTrain::MoveDone()
-{
- m_lastBlockPos.Init();
- m_lastBlockTick = -1;
- BaseClass::MoveDone();
-}
-
-int CFuncTrackTrain::OnTakeDamage( const CTakeDamageInfo &info )
-{
- if ( m_bDamageChild )
- {
- if ( FirstMoveChild() )
- {
- FirstMoveChild()->TakeDamage( info );
- }
-
- return 0;
- }
- else
- {
- return BaseClass::OnTakeDamage( info );
- }
-}
-
-
-
-//-----------------------------------------------------------------------------
-// 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 );
- } 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 );
- if ( target )
- {
- m_trackTop = (CPathTrack*) target;
- target = gEntList.FindEntityByName( NULL, m_trackBottomName );
- if ( target )
- {
- m_trackBottom = (CPathTrack*) target;
- target = gEntList.FindEntityByName( NULL, m_trainName );
- if ( target )
- {
- m_train = (CFuncTrackTrain *)gEntList.FindEntityByName( NULL, m_trainName );
- 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 );
- }
- }
- 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 );
- }
-}
+//========= Copyright 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" +#include "vphysics/friction.h" +#include "hierarchy.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 ) ) + { + IncrementInterpolationFrame(); + + // 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(); + + 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 ); + + 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 '%s' has no target.\n", GetDebugName()); + } + + 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_lastBlockPos, FIELD_POSITION_VECTOR ), // temp values for blocking, don't save + //DEFINE_FIELD( m_lastBlockTick, FIELD_INTEGER ), + + DEFINE_FIELD( m_bSoundPlaying, FIELD_BOOLEAN ), + + DEFINE_KEYFIELD( m_bManualSpeedChanges, FIELD_BOOLEAN, "ManualSpeedChanges" ), + DEFINE_KEYFIELD( m_flAccelSpeed, FIELD_FLOAT, "ManualAccelSpeed" ), + DEFINE_KEYFIELD( m_flDecelSpeed, FIELD_FLOAT, "ManualDecelSpeed" ), + +#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 ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeedDirAccel", InputSetSpeedDirAccel ), + DEFINE_INPUTFUNC( FIELD_STRING, "TeleportToPathTrack", InputTeleportToPathTrack ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeedForwardModifier", InputSetSpeedForwardModifier ), + + // Outputs + DEFINE_OUTPUT( m_OnStart, "OnStart" ), + DEFINE_OUTPUT( m_OnNext, "OnNextPoint" ), + + // 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; + m_lastBlockPos.Init(); + m_lastBlockTick = gpGlobals->tickcount; + + m_flSpeedForwardModifier = 1.0f; + m_flUnmodifiedDesiredSpeed = 0.0f; + + m_bDamageChild = false; +} + + +//----------------------------------------------------------------------------- +// 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] ); + EntityText( nOffset, tempstr, 0 ); + nOffset++; + + float flCurSpeed = GetLocalVelocity().Length(); + Q_snprintf( tempstr,sizeof(tempstr), "current speed (goal): %g (%g)", (double)flCurSpeed, (double)m_flSpeed ); + EntityText( nOffset, tempstr, 0 ); + nOffset++; + + Q_snprintf( tempstr,sizeof(tempstr), "max speed: %g", (double)m_maxSpeed ); + EntityText( 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 ) +{ + m_OnStart.FireOutput(this,this); + 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.f, 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.f, 1.f ); + 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.f, 1.f ); + SetSpeed( m_maxSpeed * flScale ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler that sets the speed of the train and the direction +// based on the sign of the speed, and accels/decels to that speed +// Input : Float speed scale from -1 to 1. Negatives values indicate a reversed +// direction. +//----------------------------------------------------------------------------- +void CFuncTrackTrain::InputSetSpeedDirAccel( inputdata_t &inputdata ) +{ + SetSpeedDirAccel( inputdata.value.Float() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTrackTrain::SetSpeedDirAccel( float flNewSpeed ) +{ + float newSpeed = flNewSpeed; + SetDirForward( newSpeed >= 0 ); + newSpeed = fabs( newSpeed ); + float flScale = clamp( newSpeed, 0.f, 1.f ); + SetSpeed( m_maxSpeed * flScale, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTrackTrain::InputSetSpeedForwardModifier( inputdata_t &inputdata ) +{ + SetSpeedForwardModifier( inputdata.value.Float() ) ; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTrackTrain::SetSpeedForwardModifier( float flModifier ) +{ + float flSpeedForwardModifier = flModifier; + flSpeedForwardModifier = fabs( flSpeedForwardModifier ); + + m_flSpeedForwardModifier = clamp( flSpeedForwardModifier, 0.f, 1.f ); + SetSpeed( m_flUnmodifiedDesiredSpeed, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTrackTrain::InputTeleportToPathTrack( inputdata_t &inputdata ) +{ + const char *pszName = inputdata.value.String(); + CPathTrack *pTrack = dynamic_cast<CPathTrack*>( gEntList.FindEntityByName( NULL, pszName ) ); + + if ( pTrack ) + { + TeleportToPathTrack( pTrack ); + m_ppath = pTrack; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the speed of the train to the given value in units per second. +//----------------------------------------------------------------------------- +void CFuncTrackTrain::SetSpeed( float flSpeed, bool bAccel /*= false */ ) +{ + m_bAccelToSpeed = bAccel; + + m_flUnmodifiedDesiredSpeed = flSpeed; + float flOldSpeed = m_flSpeed; + + // are we using a speed forward modifier? + if ( m_flSpeedForwardModifier < 1.0 && m_dir > 0 ) + { + flSpeed = flSpeed * m_flSpeedForwardModifier; + } + + if ( m_bAccelToSpeed ) + { + m_flDesiredSpeed = fabs( flSpeed ) * m_dir; + m_flSpeedChangeTime = gpGlobals->curtime; + + if ( m_flSpeed == 0 && abs(m_flDesiredSpeed) > 0 ) + { + m_flSpeed = 0.1; // little push to get us going + } + + Start(); + + return; + } + + 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); +} + +static CBaseEntity *FindPhysicsBlockerForHierarchy( CBaseEntity *pParentEntity ) +{ + CUtlVector<CBaseEntity *> list; + GetAllInHierarchy( pParentEntity, list ); + CBaseEntity *pPhysicsBlocker = NULL; + float maxForce = 0; + for ( int i = 0; i < list.Count(); i++ ) + { + IPhysicsObject *pPhysics = list[i]->VPhysicsGetObject(); + if ( pPhysics ) + { + IPhysicsFrictionSnapshot *pSnapshot = pPhysics->CreateFrictionSnapshot(); + 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( pParentEntity->GetAbsVelocity(), pSnapshot->GetNormalForce() * normal ); + if ( !pPhysicsBlocker || dot > maxForce ) + { + pPhysicsBlocker = pOtherEntity; + maxForce = dot; + } + } + pSnapshot->NextFrictionData(); + } + pPhysics->DestroyFrictionSnapshot( pSnapshot ); + } + } + return pPhysicsBlocker; +} + +//----------------------------------------------------------------------------- +// 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 = FindPhysicsBlockerForHierarchy(this); + if ( pPhysicsBlocker ) + { + // This code keeps track of how long this train has been blocked + // The heuristic here is to keep instantaneous blocks from invoking the somewhat + // heavy-handed solver (which will disable collisions until we're clear) in cases + // where physics can solve it easily enough. + int ticksBlocked = gpGlobals->tickcount - m_lastBlockTick; + float dist = 0.0f; + // wait at least 10 ticks and make sure the train isn't actually moving before really blocking + const int MIN_BLOCKED_TICKS = 10; + if ( ticksBlocked > MIN_BLOCKED_TICKS ) + { + dist = (GetAbsOrigin() - m_lastBlockPos).Length(); + // must have moved at least 10% of normal velocity over the blocking interval, or we're being blocked + float minLength = GetAbsVelocity().Length() * TICK_INTERVAL * MIN_BLOCKED_TICKS * 0.10f; + if ( dist < minLength ) + { + // been stuck for more than one tick without moving much? + // yes, disable collisions with the physics object most likely to be blocking us + EntityPhysics_CreateSolver( this, pPhysicsBlocker, true, 4.0f ); + } + } + // first time blocking or moved too far since last block, reset + if ( dist > 1.0f || m_lastBlockTick < 0 ) + { + m_lastBlockPos = GetAbsOrigin(); + m_lastBlockTick = gpGlobals->tickcount; + } + } + // unblockable shouldn't damage the player in this case + if ( pOther->IsPlayer() ) + return; + } + + 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; + } + + // In multiplayer, only update the sound once a second + if ( g_pGameRules->IsMultiplayer() && m_bSoundPlaying ) + { + if ( m_flNextMPSoundTime > gpGlobals->curtime ) + return; + + m_flNextMPSoundTime = gpGlobals->curtime + 1.0; + } + + float flSpeedRatio = 0; + if ( HasSpawnFlags( SF_TRACKTRAIN_USE_MAXSPEED_FOR_PITCH ) ) + { + flSpeedRatio = clamp( fabs( m_flSpeed ) / m_maxSpeed, 0.f, 1.f ); + } + else + { + flSpeedRatio = clamp( fabs( m_flSpeed ) / TRAIN_MAXSPEED, 0.f, 1.f ); + } + + 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; + + // In multiplayer, don't make this reliable + if ( g_pGameRules->IsMultiplayer() ) + { + EmitSound( filter, entindex(), ep ); + } + else + { + 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 ( m_bAccelToSpeed ) + { + float flPrevSpeed = m_flSpeed; + float flNextSpeed = m_flDesiredSpeed; + + if ( flPrevSpeed != flNextSpeed ) + { + float flSpeedChangeTime = ( abs(flNextSpeed) > abs(flPrevSpeed) ) ? m_flAccelSpeed : m_flDecelSpeed; + m_flSpeed = UTIL_Approach( m_flDesiredSpeed, m_flSpeed, flSpeedChangeTime * gpGlobals->frametime ); + } + } + else 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(); + + CPathTrack *pNextNode = NULL; + + nextFront.z -= m_height; + if ( m_length > 0 ) + { + m_ppath->LookAhead( nextFront, IsDirForward() ? m_length : -m_length, 0, &pNextNode ); + } + else + { + m_ppath->LookAhead( nextFront, IsDirForward() ? 100 : -100, 0, &pNextNode ); + } + 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 ); + + // Wrapped with this bool so we don't affect old trains + if ( m_bManualSpeedChanges ) + { + if ( pNextNode && pNextNode->GetOrientationType() == TrackOrientation_FacePathAngles ) + { + angles = pNextNode->GetOrientation( IsDirForward() ); + } + } + + 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 ); + + variant_t emptyVariant; + pTeleport->AcceptInput( "InTeleport", this, this, emptyVariant, 0 ); +} + + +//----------------------------------------------------------------------------- +// 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 we're moving towards a dead end, but our desired speed goes in the opposite direction + // this fixes us from stalling + if ( m_bManualSpeedChanges && ( ( flSpeed < 0 ) != ( m_flDesiredSpeed < 0 ) ) ) + { + if ( !pNext ) + pNext = m_ppath; + } + + if (m_debugOverlays & OVERLAY_BBOX_BIT) + { + if ( pNext != NULL ) + { + NDebugOverlay::Line( GetAbsOrigin(), pNext->GetAbsOrigin(), 255, 0, 0, true, 0.1 ); + 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( GetAbsOrigin(), pNextNext->GetAbsOrigin(), 0, 255, 0, true, 0.1 ); + 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 ); + } + } + + m_OnNext.FireOutput( pNext, this ); + + 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 ); + if ( !m_ppath ) + return; + + if ( !FClassnameIs( m_ppath, "path_track" ) +#ifndef PORTAL //env_portal_path_track is a child of path_track and would like to get found + && !FClassnameIs( m_ppath, "env_portal_path_track" ) +#endif //#ifndef PORTAL + ) + { + 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("FuncTrackTrain '%s' has no target.\n", GetDebugName()); + } + + 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(); +} + +void CFuncTrackTrain::MoveDone() +{ + m_lastBlockPos.Init(); + m_lastBlockTick = -1; + BaseClass::MoveDone(); +} + +int CFuncTrackTrain::OnTakeDamage( const CTakeDamageInfo &info ) +{ + if ( m_bDamageChild ) + { + if ( FirstMoveChild() ) + { + FirstMoveChild()->TakeDamage( info ); + } + + return 0; + } + else + { + return BaseClass::OnTakeDamage( info ); + } +} + + + +//----------------------------------------------------------------------------- +// 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 ); + } 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 ); + if ( target ) + { + m_trackTop = (CPathTrack*) target; + target = gEntList.FindEntityByName( NULL, m_trackBottomName ); + if ( target ) + { + m_trackBottom = (CPathTrack*) target; + target = gEntList.FindEntityByName( NULL, m_trainName ); + if ( target ) + { + m_train = (CFuncTrackTrain *)gEntList.FindEntityByName( NULL, m_trainName ); + 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 ); + } + } + 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 ); + } +} |