aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/tanktrain.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/tanktrain.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/tanktrain.cpp')
-rw-r--r--mp/src/game/server/tanktrain.cpp1070
1 files changed, 535 insertions, 535 deletions
diff --git a/mp/src/game/server/tanktrain.cpp b/mp/src/game/server/tanktrain.cpp
index 2b359b06..62a5acd6 100644
--- a/mp/src/game/server/tanktrain.cpp
+++ b/mp/src/game/server/tanktrain.cpp
@@ -1,535 +1,535 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-// $NoKeywords: $
-//=============================================================================//
-
-#include "cbase.h"
-#include "trains.h"
-#include "entitylist.h"
-#include "soundenvelope.h"
-#include "engine/IEngineSound.h"
-
-// memdbgon must be the last include file in a .cpp file!!!
-#include "tier0/memdbgon.h"
-
-extern short g_sModelIndexFireball;
-#define SPRITE_FIREBALL "sprites/zerogxplode.vmt"
-#define SPRITE_SMOKE "sprites/steam1.vmt"
-
-void UTIL_RemoveHierarchy( CBaseEntity *pDead )
-{
- if ( !pDead )
- return;
-
- if ( pDead->edict() )
- {
- CBaseEntity *pChild = pDead->FirstMoveChild();
- while ( pChild )
- {
- CBaseEntity *pEntity = pChild;
- pChild = pChild->NextMovePeer();
-
- UTIL_RemoveHierarchy( pEntity );
- }
- }
- UTIL_Remove( pDead );
-}
-
-class CFuncTankTrain : public CFuncTrackTrain
-{
-public:
- DECLARE_CLASS( CFuncTankTrain, CFuncTrackTrain );
-
- void Spawn( void );
-
- // Filter out damage messages that don't contain blast damage (impervious to other forms of attack)
- int OnTakeDamage( const CTakeDamageInfo &info );
- void Event_Killed( const CTakeDamageInfo &info );
- void Blocked( CBaseEntity *pOther )
- {
- // FIxme, set speed to zero?
- }
- DECLARE_DATADESC();
-
-private:
-
- COutputEvent m_OnDeath;
-};
-
-LINK_ENTITY_TO_CLASS( func_tanktrain, CFuncTankTrain );
-
-BEGIN_DATADESC( CFuncTankTrain )
-
- // Outputs
- DEFINE_OUTPUT(m_OnDeath, "OnDeath"),
-
-END_DATADESC()
-
-
-void CFuncTankTrain::Spawn( void )
-{
- m_takedamage = true;
- BaseClass::Spawn();
-}
-
-// Filter out damage messages that don't contain blast damage (impervious to other forms of attack)
-int CFuncTankTrain::OnTakeDamage( const CTakeDamageInfo &info )
-{
- if ( ! (info.GetDamageType() & DMG_BLAST) )
- return 0;
-
- return BaseClass::OnTakeDamage( info );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Called when the train is killed.
-// Input : pInflictor - What killed us.
-// pAttacker - Who killed us.
-// flDamage - The damage that the killing blow inflicted.
-// bitsDamageType - Bitfield of damage types that were inflicted.
-//-----------------------------------------------------------------------------
-void CFuncTankTrain::Event_Killed( const CTakeDamageInfo &info )
-{
- m_takedamage = DAMAGE_NO;
- m_lifeState = LIFE_DEAD;
-
- m_OnDeath.FireOutput( info.GetInflictor(), this );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Changes the target entity for a func_tank or tanktrain_ai
-//-----------------------------------------------------------------------------
-class CTankTargetChange : public CPointEntity
-{
-public:
- DECLARE_CLASS( CTankTargetChange, CPointEntity );
-
- void Precache( void );
- void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
-
- DECLARE_DATADESC();
-
-private:
- variant_t m_newTarget;
- string_t m_newTargetName;
-};
-
-LINK_ENTITY_TO_CLASS( tanktrain_aitarget, CTankTargetChange );
-
-BEGIN_DATADESC( CTankTargetChange )
-
- // DEFINE_FIELD( m_newTarget, variant_t ),
- DEFINE_KEYFIELD( m_newTargetName, FIELD_STRING, "newtarget" ),
-
-END_DATADESC()
-
-
-void CTankTargetChange::Precache( void )
-{
- BaseClass::Precache();
-
- // This needs to be in Precache so save/load works
- m_newTarget.SetString( m_newTargetName );
-}
-
-void CTankTargetChange::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
-{
- CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_target, NULL, pActivator, pCaller );
-
- // UNDONE: This should use more of the event system
- while ( pTarget )
- {
- // Change the target over
- pTarget->AcceptInput( "TargetEntity", this, this, m_newTarget, 0 );
- pTarget = gEntList.FindEntityByName( pTarget, m_target, NULL, pActivator, pCaller );
- }
-}
-
-
-// UNDONE: Should be just a logical entity, but we act as another static sound channel for the train
-class CTankTrainAI : public CPointEntity
-{
-public:
- DECLARE_CLASS( CTankTrainAI, CPointEntity );
-
- virtual ~CTankTrainAI( void );
-
- void Precache( void );
- void Spawn( void );
- void Activate( void );
- void Think( void );
-
- int SoundEnginePitch( void );
- void SoundEngineStart( void );
- void SoundEngineStop( void );
- void SoundShutdown( void );
-
- CBaseEntity *FindTarget( string_t target, CBaseEntity *pActivator );
-
- DECLARE_DATADESC();
-
- // INPUTS
- void InputTargetEntity( inputdata_t &inputdata );
-
-private:
- CHandle<CFuncTrackTrain> m_hTrain;
- EHANDLE m_hTargetEntity;
- int m_soundPlaying;
-
- CSoundPatch *m_soundTreads;
- CSoundPatch *m_soundEngine;
-
- string_t m_startSoundName;
- string_t m_engineSoundName;
- string_t m_movementSoundName;
- string_t m_targetEntityName;
-};
-
-LINK_ENTITY_TO_CLASS( tanktrain_ai, CTankTrainAI );
-
-BEGIN_DATADESC( CTankTrainAI )
-
- DEFINE_FIELD( m_hTrain, FIELD_EHANDLE),
- DEFINE_FIELD( m_hTargetEntity, FIELD_EHANDLE),
- DEFINE_FIELD( m_soundPlaying, FIELD_INTEGER),
- DEFINE_SOUNDPATCH( m_soundTreads ),
- DEFINE_SOUNDPATCH( m_soundEngine ),
-
- DEFINE_KEYFIELD( m_startSoundName, FIELD_STRING, "startsound" ),
- DEFINE_KEYFIELD( m_engineSoundName, FIELD_STRING, "enginesound" ),
- DEFINE_KEYFIELD( m_movementSoundName, FIELD_STRING, "movementsound" ),
- DEFINE_FIELD( m_targetEntityName, FIELD_STRING),
-
- // Inputs
- DEFINE_INPUTFUNC( FIELD_STRING, "TargetEntity", InputTargetEntity ),
-
-END_DATADESC()
-
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Input handler for setting the target entity by name.
-//-----------------------------------------------------------------------------
-void CTankTrainAI::InputTargetEntity( inputdata_t &inputdata )
-{
- m_targetEntityName = inputdata.value.StringID();
- m_hTargetEntity = FindTarget( m_targetEntityName, inputdata.pActivator );
- SetNextThink( gpGlobals->curtime );
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Finds the first entity in the entity list with the given name.
-// Input : target - String ID of the entity to find.
-// pActivator - The activating entity if this is called from an input
-// or Use handler, NULL otherwise.
-//-----------------------------------------------------------------------------
-CBaseEntity *CTankTrainAI::FindTarget( string_t target, CBaseEntity *pActivator )
-{
- return gEntList.FindEntityGeneric( NULL, STRING( target ), this, pActivator );
-}
-
-
-CTankTrainAI::~CTankTrainAI( void )
-{
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
-
- if ( m_soundTreads )
- {
- controller.SoundDestroy( m_soundTreads );
- }
-
- if ( m_soundEngine )
- {
- controller.SoundDestroy( m_soundEngine );
- }
-}
-
-void CTankTrainAI::Precache( void )
-{
- PrecacheScriptSound( STRING( m_startSoundName ) );
- PrecacheScriptSound( STRING( m_engineSoundName ) );
- PrecacheScriptSound( STRING( m_movementSoundName ) );
-}
-
-int CTankTrainAI::SoundEnginePitch( void )
-{
- CFuncTrackTrain *pTrain = m_hTrain;
-
- // we know this isn't NULL here
- if ( pTrain->GetMaxSpeed() )
- {
- return 90 + (fabs(pTrain->GetCurrentSpeed()) * (20) / pTrain->GetMaxSpeed());
- }
- return 100;
-}
-
-
-void CTankTrainAI::SoundEngineStart( void )
-{
- CFuncTrackTrain *pTrain = m_hTrain;
-
- SoundEngineStop();
- // play startup sound for train
- if ( m_startSoundName != NULL_STRING )
- {
- CPASAttenuationFilter filter( pTrain );
-
- EmitSound_t ep;
- ep.m_nChannel = CHAN_ITEM;
- ep.m_pSoundName = STRING(m_startSoundName);
- ep.m_flVolume = 1.0f;
- ep.m_SoundLevel = SNDLVL_NORM;
-
- EmitSound( filter, pTrain->entindex(), ep );
- }
-
- // play the looping sounds using the envelope controller
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
-
- if ( m_soundTreads )
- {
- controller.Play( m_soundTreads, 1.0, 100 );
- }
-
- if ( m_soundEngine )
- {
- controller.Play( m_soundEngine, 0.5, 90 );
- controller.CommandClear( m_soundEngine );
- controller.CommandAdd( m_soundEngine, 0, SOUNDCTRL_CHANGE_PITCH, 1.5, random->RandomInt(130, 145) );
- controller.CommandAdd( m_soundEngine, 1.5, SOUNDCTRL_CHANGE_PITCH, 2, random->RandomInt(105, 115) );
- }
-
- m_soundPlaying = true;
-}
-
-
-void CTankTrainAI::SoundEngineStop( void )
-{
- if ( !m_soundPlaying )
- return;
-
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
-
- if ( m_soundTreads )
- {
- controller.SoundFadeOut( m_soundTreads, 0.25 );
- }
-
- if ( m_soundEngine )
- {
- controller.CommandClear( m_soundEngine );
- controller.SoundChangePitch( m_soundEngine, 70, 3.0 );
- }
- m_soundPlaying = false;
-}
-
-
-void CTankTrainAI::SoundShutdown( void )
-{
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
- if ( m_soundTreads )
- {
- controller.Shutdown( m_soundTreads );
- }
-
- if ( m_soundEngine )
- {
- controller.Shutdown( m_soundEngine );
- }
- m_soundPlaying = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Set up think and AI
-//-----------------------------------------------------------------------------
-void CTankTrainAI::Spawn( void )
-{
- Precache();
- m_soundPlaying = false;
- m_hTargetEntity = NULL;
-}
-
-void CTankTrainAI::Activate( void )
-{
- BaseClass::Activate();
-
- CBaseEntity *pTarget = NULL;
-
- CFuncTrackTrain *pTrain = NULL;
-
- if ( m_target != NULL_STRING )
- {
- do
- {
- pTarget = gEntList.FindEntityByName( pTarget, m_target );
- pTrain = dynamic_cast<CFuncTrackTrain *>(pTarget);
- } while (!pTrain && pTarget);
- }
-
- m_hTrain = pTrain;
-
- if ( pTrain )
- {
- SetNextThink( gpGlobals->curtime + 0.5f );
- CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
-
- if ( m_movementSoundName != NULL_STRING )
- {
- CPASAttenuationFilter filter( this, ATTN_NORM * 0.5 );
- m_soundTreads = controller.SoundCreate( filter, pTrain->entindex(), CHAN_STATIC, STRING(m_movementSoundName), ATTN_NORM*0.5 );
- }
- if ( m_engineSoundName != NULL_STRING )
- {
- CPASAttenuationFilter filter( this );
- m_soundEngine = controller.SoundCreate( filter, pTrain->entindex(), CHAN_STATIC, STRING(m_engineSoundName), ATTN_NORM );
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Dumb linear serach of the path
-// Input : *pStart - starting path node
-// &startPosition - starting position
-// &destination - position to move close to
-// Output : int move direction 1 = forward, -1 = reverse, 0 = stop
-//-----------------------------------------------------------------------------
-int PathFindDirection( CPathTrack *pStart, const Vector &startPosition, const Vector &destination )
-{
- if ( !pStart )
- return 0; // no path, don't move
-
- CPathTrack *pPath = pStart->m_pnext;
- CPathTrack *pNearest = pStart;
-
- float nearestDist = (pNearest->GetLocalOrigin() - destination).LengthSqr();
- float length = 0;
- float nearestForward = 0, nearestReverse = 0;
-
- do
- {
- float dist = (pPath->GetLocalOrigin() - destination).LengthSqr();
-
- // This is closer than our current estimate
- if ( dist < nearestDist )
- {
- nearestDist = dist;
- pNearest = pPath;
- nearestForward = length; // current path length forward
- nearestReverse = 0; // count until we hit the start again
- }
- CPathTrack *pNext = pPath->m_pnext;
- if ( pNext )
- {
- // UNDONE: Cache delta in path?
- float delta = (pNext->GetLocalOrigin() - pPath->GetLocalOrigin()).LengthSqr();
- length += delta;
- // add to current reverse estimate
- nearestReverse += delta;
- pPath = pNext;
- }
- else
- {
- // not a looping path
- // traverse back to other end of the path
- int fail = 0;
- while ( pPath->m_pprevious )
- {
- fail++;
- // HACKHACK: Don't infinite loop
- if ( fail > 256 )
- break;
- pPath = pPath->m_pprevious;
- }
- // don't take the reverse path to old node
- nearestReverse = nearestForward + 1;
- // dont' take forward path to new node (if we find one)
- length = (float)COORD_EXTENT * (float)COORD_EXTENT; // HACKHACK: Max quad length
- }
-
- } while ( pPath != pStart );
-
- // UNDONE: Fix this fudge factor
- // if you are already at the path, or <100 units away, don't move
- if ( pNearest == pStart || (pNearest->GetLocalOrigin() - startPosition).LengthSqr() < 100 )
- return 0;
-
- if ( nearestForward <= nearestReverse )
- return 1;
-
- return -1;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Find a point on my path near to the target and move toward it
-//-----------------------------------------------------------------------------
-void CTankTrainAI::Think( void )
-{
- CFuncTrackTrain *pTrain = m_hTrain;
-
- if ( !pTrain || pTrain->m_lifeState != LIFE_ALIVE )
- {
- SoundShutdown();
- if ( pTrain )
- UTIL_RemoveHierarchy( pTrain );
- UTIL_Remove( this );
- return;
- }
-
- int desired = 0;
- CBaseEntity *pTarget = m_hTargetEntity;
- if ( pTarget )
- {
- desired = PathFindDirection( pTrain->m_ppath, pTrain->GetLocalOrigin(), pTarget->GetLocalOrigin() );
- }
-
- // If the train wants to stop, figure out throttle
- // otherwise, just throttle in the indicated direction and let the train logic
- // clip the speed
- if ( !desired )
- {
- if ( pTrain->m_flSpeed > 0 )
- {
- desired = -1;
- }
- else if ( pTrain->m_flSpeed < 0 )
- {
- desired = 1;
- }
- }
-
- // UNDONE: Align the think time with arrival, and bump this up to a few seconds
- SetNextThink( gpGlobals->curtime + 0.5f );
-
- if ( desired != 0 )
- {
- int wasMoving = (pTrain->m_flSpeed == 0) ? false : true;
- // chaser wants train to move, send message
- pTrain->SetSpeed( desired );
- int isMoving = (pTrain->m_flSpeed == 0) ? false : true;
-
- if ( !isMoving && wasMoving )
- {
- SoundEngineStop();
- }
- else if ( isMoving )
- {
- if ( !wasMoving )
- {
- SoundEngineStart();
- }
- }
- }
- else
- {
- SoundEngineStop();
- // UNDONE: Align the think time with arrival, and bump this up to a few seconds
- SetNextThink( gpGlobals->curtime + 1.0f );
- }
-}
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "trains.h"
+#include "entitylist.h"
+#include "soundenvelope.h"
+#include "engine/IEngineSound.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+extern short g_sModelIndexFireball;
+#define SPRITE_FIREBALL "sprites/zerogxplode.vmt"
+#define SPRITE_SMOKE "sprites/steam1.vmt"
+
+void UTIL_RemoveHierarchy( CBaseEntity *pDead )
+{
+ if ( !pDead )
+ return;
+
+ if ( pDead->edict() )
+ {
+ CBaseEntity *pChild = pDead->FirstMoveChild();
+ while ( pChild )
+ {
+ CBaseEntity *pEntity = pChild;
+ pChild = pChild->NextMovePeer();
+
+ UTIL_RemoveHierarchy( pEntity );
+ }
+ }
+ UTIL_Remove( pDead );
+}
+
+class CFuncTankTrain : public CFuncTrackTrain
+{
+public:
+ DECLARE_CLASS( CFuncTankTrain, CFuncTrackTrain );
+
+ void Spawn( void );
+
+ // Filter out damage messages that don't contain blast damage (impervious to other forms of attack)
+ int OnTakeDamage( const CTakeDamageInfo &info );
+ void Event_Killed( const CTakeDamageInfo &info );
+ void Blocked( CBaseEntity *pOther )
+ {
+ // FIxme, set speed to zero?
+ }
+ DECLARE_DATADESC();
+
+private:
+
+ COutputEvent m_OnDeath;
+};
+
+LINK_ENTITY_TO_CLASS( func_tanktrain, CFuncTankTrain );
+
+BEGIN_DATADESC( CFuncTankTrain )
+
+ // Outputs
+ DEFINE_OUTPUT(m_OnDeath, "OnDeath"),
+
+END_DATADESC()
+
+
+void CFuncTankTrain::Spawn( void )
+{
+ m_takedamage = true;
+ BaseClass::Spawn();
+}
+
+// Filter out damage messages that don't contain blast damage (impervious to other forms of attack)
+int CFuncTankTrain::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ if ( ! (info.GetDamageType() & DMG_BLAST) )
+ return 0;
+
+ return BaseClass::OnTakeDamage( info );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when the train is killed.
+// Input : pInflictor - What killed us.
+// pAttacker - Who killed us.
+// flDamage - The damage that the killing blow inflicted.
+// bitsDamageType - Bitfield of damage types that were inflicted.
+//-----------------------------------------------------------------------------
+void CFuncTankTrain::Event_Killed( const CTakeDamageInfo &info )
+{
+ m_takedamage = DAMAGE_NO;
+ m_lifeState = LIFE_DEAD;
+
+ m_OnDeath.FireOutput( info.GetInflictor(), this );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Changes the target entity for a func_tank or tanktrain_ai
+//-----------------------------------------------------------------------------
+class CTankTargetChange : public CPointEntity
+{
+public:
+ DECLARE_CLASS( CTankTargetChange, CPointEntity );
+
+ void Precache( void );
+ void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+
+ DECLARE_DATADESC();
+
+private:
+ variant_t m_newTarget;
+ string_t m_newTargetName;
+};
+
+LINK_ENTITY_TO_CLASS( tanktrain_aitarget, CTankTargetChange );
+
+BEGIN_DATADESC( CTankTargetChange )
+
+ // DEFINE_FIELD( m_newTarget, variant_t ),
+ DEFINE_KEYFIELD( m_newTargetName, FIELD_STRING, "newtarget" ),
+
+END_DATADESC()
+
+
+void CTankTargetChange::Precache( void )
+{
+ BaseClass::Precache();
+
+ // This needs to be in Precache so save/load works
+ m_newTarget.SetString( m_newTargetName );
+}
+
+void CTankTargetChange::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_target, NULL, pActivator, pCaller );
+
+ // UNDONE: This should use more of the event system
+ while ( pTarget )
+ {
+ // Change the target over
+ pTarget->AcceptInput( "TargetEntity", this, this, m_newTarget, 0 );
+ pTarget = gEntList.FindEntityByName( pTarget, m_target, NULL, pActivator, pCaller );
+ }
+}
+
+
+// UNDONE: Should be just a logical entity, but we act as another static sound channel for the train
+class CTankTrainAI : public CPointEntity
+{
+public:
+ DECLARE_CLASS( CTankTrainAI, CPointEntity );
+
+ virtual ~CTankTrainAI( void );
+
+ void Precache( void );
+ void Spawn( void );
+ void Activate( void );
+ void Think( void );
+
+ int SoundEnginePitch( void );
+ void SoundEngineStart( void );
+ void SoundEngineStop( void );
+ void SoundShutdown( void );
+
+ CBaseEntity *FindTarget( string_t target, CBaseEntity *pActivator );
+
+ DECLARE_DATADESC();
+
+ // INPUTS
+ void InputTargetEntity( inputdata_t &inputdata );
+
+private:
+ CHandle<CFuncTrackTrain> m_hTrain;
+ EHANDLE m_hTargetEntity;
+ int m_soundPlaying;
+
+ CSoundPatch *m_soundTreads;
+ CSoundPatch *m_soundEngine;
+
+ string_t m_startSoundName;
+ string_t m_engineSoundName;
+ string_t m_movementSoundName;
+ string_t m_targetEntityName;
+};
+
+LINK_ENTITY_TO_CLASS( tanktrain_ai, CTankTrainAI );
+
+BEGIN_DATADESC( CTankTrainAI )
+
+ DEFINE_FIELD( m_hTrain, FIELD_EHANDLE),
+ DEFINE_FIELD( m_hTargetEntity, FIELD_EHANDLE),
+ DEFINE_FIELD( m_soundPlaying, FIELD_INTEGER),
+ DEFINE_SOUNDPATCH( m_soundTreads ),
+ DEFINE_SOUNDPATCH( m_soundEngine ),
+
+ DEFINE_KEYFIELD( m_startSoundName, FIELD_STRING, "startsound" ),
+ DEFINE_KEYFIELD( m_engineSoundName, FIELD_STRING, "enginesound" ),
+ DEFINE_KEYFIELD( m_movementSoundName, FIELD_STRING, "movementsound" ),
+ DEFINE_FIELD( m_targetEntityName, FIELD_STRING),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_STRING, "TargetEntity", InputTargetEntity ),
+
+END_DATADESC()
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler for setting the target entity by name.
+//-----------------------------------------------------------------------------
+void CTankTrainAI::InputTargetEntity( inputdata_t &inputdata )
+{
+ m_targetEntityName = inputdata.value.StringID();
+ m_hTargetEntity = FindTarget( m_targetEntityName, inputdata.pActivator );
+ SetNextThink( gpGlobals->curtime );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Finds the first entity in the entity list with the given name.
+// Input : target - String ID of the entity to find.
+// pActivator - The activating entity if this is called from an input
+// or Use handler, NULL otherwise.
+//-----------------------------------------------------------------------------
+CBaseEntity *CTankTrainAI::FindTarget( string_t target, CBaseEntity *pActivator )
+{
+ return gEntList.FindEntityGeneric( NULL, STRING( target ), this, pActivator );
+}
+
+
+CTankTrainAI::~CTankTrainAI( void )
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ if ( m_soundTreads )
+ {
+ controller.SoundDestroy( m_soundTreads );
+ }
+
+ if ( m_soundEngine )
+ {
+ controller.SoundDestroy( m_soundEngine );
+ }
+}
+
+void CTankTrainAI::Precache( void )
+{
+ PrecacheScriptSound( STRING( m_startSoundName ) );
+ PrecacheScriptSound( STRING( m_engineSoundName ) );
+ PrecacheScriptSound( STRING( m_movementSoundName ) );
+}
+
+int CTankTrainAI::SoundEnginePitch( void )
+{
+ CFuncTrackTrain *pTrain = m_hTrain;
+
+ // we know this isn't NULL here
+ if ( pTrain->GetMaxSpeed() )
+ {
+ return 90 + (fabs(pTrain->GetCurrentSpeed()) * (20) / pTrain->GetMaxSpeed());
+ }
+ return 100;
+}
+
+
+void CTankTrainAI::SoundEngineStart( void )
+{
+ CFuncTrackTrain *pTrain = m_hTrain;
+
+ SoundEngineStop();
+ // play startup sound for train
+ if ( m_startSoundName != NULL_STRING )
+ {
+ CPASAttenuationFilter filter( pTrain );
+
+ EmitSound_t ep;
+ ep.m_nChannel = CHAN_ITEM;
+ ep.m_pSoundName = STRING(m_startSoundName);
+ ep.m_flVolume = 1.0f;
+ ep.m_SoundLevel = SNDLVL_NORM;
+
+ EmitSound( filter, pTrain->entindex(), ep );
+ }
+
+ // play the looping sounds using the envelope controller
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ if ( m_soundTreads )
+ {
+ controller.Play( m_soundTreads, 1.0, 100 );
+ }
+
+ if ( m_soundEngine )
+ {
+ controller.Play( m_soundEngine, 0.5, 90 );
+ controller.CommandClear( m_soundEngine );
+ controller.CommandAdd( m_soundEngine, 0, SOUNDCTRL_CHANGE_PITCH, 1.5, random->RandomInt(130, 145) );
+ controller.CommandAdd( m_soundEngine, 1.5, SOUNDCTRL_CHANGE_PITCH, 2, random->RandomInt(105, 115) );
+ }
+
+ m_soundPlaying = true;
+}
+
+
+void CTankTrainAI::SoundEngineStop( void )
+{
+ if ( !m_soundPlaying )
+ return;
+
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ if ( m_soundTreads )
+ {
+ controller.SoundFadeOut( m_soundTreads, 0.25 );
+ }
+
+ if ( m_soundEngine )
+ {
+ controller.CommandClear( m_soundEngine );
+ controller.SoundChangePitch( m_soundEngine, 70, 3.0 );
+ }
+ m_soundPlaying = false;
+}
+
+
+void CTankTrainAI::SoundShutdown( void )
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ if ( m_soundTreads )
+ {
+ controller.Shutdown( m_soundTreads );
+ }
+
+ if ( m_soundEngine )
+ {
+ controller.Shutdown( m_soundEngine );
+ }
+ m_soundPlaying = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set up think and AI
+//-----------------------------------------------------------------------------
+void CTankTrainAI::Spawn( void )
+{
+ Precache();
+ m_soundPlaying = false;
+ m_hTargetEntity = NULL;
+}
+
+void CTankTrainAI::Activate( void )
+{
+ BaseClass::Activate();
+
+ CBaseEntity *pTarget = NULL;
+
+ CFuncTrackTrain *pTrain = NULL;
+
+ if ( m_target != NULL_STRING )
+ {
+ do
+ {
+ pTarget = gEntList.FindEntityByName( pTarget, m_target );
+ pTrain = dynamic_cast<CFuncTrackTrain *>(pTarget);
+ } while (!pTrain && pTarget);
+ }
+
+ m_hTrain = pTrain;
+
+ if ( pTrain )
+ {
+ SetNextThink( gpGlobals->curtime + 0.5f );
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ if ( m_movementSoundName != NULL_STRING )
+ {
+ CPASAttenuationFilter filter( this, ATTN_NORM * 0.5 );
+ m_soundTreads = controller.SoundCreate( filter, pTrain->entindex(), CHAN_STATIC, STRING(m_movementSoundName), ATTN_NORM*0.5 );
+ }
+ if ( m_engineSoundName != NULL_STRING )
+ {
+ CPASAttenuationFilter filter( this );
+ m_soundEngine = controller.SoundCreate( filter, pTrain->entindex(), CHAN_STATIC, STRING(m_engineSoundName), ATTN_NORM );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Dumb linear serach of the path
+// Input : *pStart - starting path node
+// &startPosition - starting position
+// &destination - position to move close to
+// Output : int move direction 1 = forward, -1 = reverse, 0 = stop
+//-----------------------------------------------------------------------------
+int PathFindDirection( CPathTrack *pStart, const Vector &startPosition, const Vector &destination )
+{
+ if ( !pStart )
+ return 0; // no path, don't move
+
+ CPathTrack *pPath = pStart->m_pnext;
+ CPathTrack *pNearest = pStart;
+
+ float nearestDist = (pNearest->GetLocalOrigin() - destination).LengthSqr();
+ float length = 0;
+ float nearestForward = 0, nearestReverse = 0;
+
+ do
+ {
+ float dist = (pPath->GetLocalOrigin() - destination).LengthSqr();
+
+ // This is closer than our current estimate
+ if ( dist < nearestDist )
+ {
+ nearestDist = dist;
+ pNearest = pPath;
+ nearestForward = length; // current path length forward
+ nearestReverse = 0; // count until we hit the start again
+ }
+ CPathTrack *pNext = pPath->m_pnext;
+ if ( pNext )
+ {
+ // UNDONE: Cache delta in path?
+ float delta = (pNext->GetLocalOrigin() - pPath->GetLocalOrigin()).LengthSqr();
+ length += delta;
+ // add to current reverse estimate
+ nearestReverse += delta;
+ pPath = pNext;
+ }
+ else
+ {
+ // not a looping path
+ // traverse back to other end of the path
+ int fail = 0;
+ while ( pPath->m_pprevious )
+ {
+ fail++;
+ // HACKHACK: Don't infinite loop
+ if ( fail > 256 )
+ break;
+ pPath = pPath->m_pprevious;
+ }
+ // don't take the reverse path to old node
+ nearestReverse = nearestForward + 1;
+ // dont' take forward path to new node (if we find one)
+ length = (float)COORD_EXTENT * (float)COORD_EXTENT; // HACKHACK: Max quad length
+ }
+
+ } while ( pPath != pStart );
+
+ // UNDONE: Fix this fudge factor
+ // if you are already at the path, or <100 units away, don't move
+ if ( pNearest == pStart || (pNearest->GetLocalOrigin() - startPosition).LengthSqr() < 100 )
+ return 0;
+
+ if ( nearestForward <= nearestReverse )
+ return 1;
+
+ return -1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Find a point on my path near to the target and move toward it
+//-----------------------------------------------------------------------------
+void CTankTrainAI::Think( void )
+{
+ CFuncTrackTrain *pTrain = m_hTrain;
+
+ if ( !pTrain || pTrain->m_lifeState != LIFE_ALIVE )
+ {
+ SoundShutdown();
+ if ( pTrain )
+ UTIL_RemoveHierarchy( pTrain );
+ UTIL_Remove( this );
+ return;
+ }
+
+ int desired = 0;
+ CBaseEntity *pTarget = m_hTargetEntity;
+ if ( pTarget )
+ {
+ desired = PathFindDirection( pTrain->m_ppath, pTrain->GetLocalOrigin(), pTarget->GetLocalOrigin() );
+ }
+
+ // If the train wants to stop, figure out throttle
+ // otherwise, just throttle in the indicated direction and let the train logic
+ // clip the speed
+ if ( !desired )
+ {
+ if ( pTrain->m_flSpeed > 0 )
+ {
+ desired = -1;
+ }
+ else if ( pTrain->m_flSpeed < 0 )
+ {
+ desired = 1;
+ }
+ }
+
+ // UNDONE: Align the think time with arrival, and bump this up to a few seconds
+ SetNextThink( gpGlobals->curtime + 0.5f );
+
+ if ( desired != 0 )
+ {
+ int wasMoving = (pTrain->m_flSpeed == 0) ? false : true;
+ // chaser wants train to move, send message
+ pTrain->SetSpeed( desired );
+ int isMoving = (pTrain->m_flSpeed == 0) ? false : true;
+
+ if ( !isMoving && wasMoving )
+ {
+ SoundEngineStop();
+ }
+ else if ( isMoving )
+ {
+ if ( !wasMoving )
+ {
+ SoundEngineStart();
+ }
+ }
+ }
+ else
+ {
+ SoundEngineStop();
+ // UNDONE: Align the think time with arrival, and bump this up to a few seconds
+ SetNextThink( gpGlobals->curtime + 1.0f );
+ }
+}