From 39ed87570bdb2f86969d4be821c94b722dc71179 Mon Sep 17 00:00:00 2001 From: Joe Ludwig Date: Wed, 26 Jun 2013 15:22:04 -0700 Subject: First version of the SOurce SDK 2013 --- mp/src/game/server/trains.cpp | 3378 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3378 insertions(+) create mode 100644 mp/src/game/server/trains.cpp (limited to 'mp/src/game/server/trains.cpp') diff --git a/mp/src/game/server/trains.cpp b/mp/src/game/server/trains.cpp new file mode 100644 index 00000000..c074763c --- /dev/null +++ b/mp/src/game/server/trains.cpp @@ -0,0 +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( 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 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(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 ); + } +} -- cgit v1.2.3