|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Spawn, think, and touch functions for trains, etc.
//
//=============================================================================//
#include "cbase.h"
#include "ai_basenpc.h"
#include "trains.h"
#include "ndebugoverlay.h"
#include "entitylist.h"
#include "engine/IEngineSound.h"
#include "soundenvelope.h"
#include "physics_npc_solver.h"
#include "vphysics/friction.h"
#include "hierarchy.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
static void PlatSpawnInsideTrigger(edict_t *pevPlatform);
#define SF_PLAT_TOGGLE 0x0001
class CBasePlatTrain : public CBaseToggle { DECLARE_CLASS( CBasePlatTrain, CBaseToggle );
public: ~CBasePlatTrain(); bool KeyValue( const char *szKeyName, const char *szValue ); void Precache( void );
// This is done to fix spawn flag collisions between this class and a derived class
virtual bool IsTogglePlat( void ) { return (m_spawnflags & SF_PLAT_TOGGLE) ? true : false; }
DECLARE_DATADESC();
void PlayMovingSound(); void StopMovingSound();
string_t m_NoiseMoving; // sound a plat makes while moving
string_t m_NoiseArrived;
CSoundPatch *m_pMovementSound; #ifdef HL1_DLL
int m_MoveSound; int m_StopSound; #endif
float m_volume; // Sound volume
float m_flTWidth; float m_flTLength; };
BEGIN_DATADESC( CBasePlatTrain )
DEFINE_KEYFIELD( m_NoiseMoving, FIELD_SOUNDNAME, "noise1" ), DEFINE_KEYFIELD( m_NoiseArrived, FIELD_SOUNDNAME, "noise2" ),
#ifdef HL1_DLL
DEFINE_KEYFIELD( m_MoveSound, FIELD_INTEGER, "movesnd" ), DEFINE_KEYFIELD( m_StopSound, FIELD_INTEGER, "stopsnd" ),
#endif
DEFINE_SOUNDPATCH( m_pMovementSound ),
DEFINE_KEYFIELD( m_volume, FIELD_FLOAT, "volume" ),
DEFINE_FIELD( m_flTWidth, FIELD_FLOAT ), DEFINE_FIELD( m_flTLength, FIELD_FLOAT ), DEFINE_KEYFIELD( m_flLip, FIELD_FLOAT, "lip" ), DEFINE_KEYFIELD( m_flWait, FIELD_FLOAT, "wait" ), DEFINE_KEYFIELD( m_flHeight, FIELD_FLOAT, "height" ),
END_DATADESC()
bool CBasePlatTrain::KeyValue( const char *szKeyName, const char *szValue ) { if (FStrEq(szKeyName, "rotation")) { m_vecFinalAngle.x = atof(szValue); } else { return BaseClass::KeyValue( szKeyName, szValue ); }
return true; }
CBasePlatTrain::~CBasePlatTrain() { StopMovingSound(); }
void CBasePlatTrain::PlayMovingSound() { StopMovingSound(); if(m_NoiseMoving != NULL_STRING ) { CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); CPASAttenuationFilter filter( this ); m_pMovementSound = controller.SoundCreate( filter, entindex(), CHAN_STATIC, STRING(m_NoiseMoving), ATTN_NORM ); controller.Play( m_pMovementSound, m_volume, PITCH_NORM ); } }
void CBasePlatTrain::StopMovingSound() { if ( m_pMovementSound ) { CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundDestroy( m_pMovementSound ); m_pMovementSound = NULL; } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBasePlatTrain::Precache( void ) { //Fill in a default value if necessary
UTIL_ValidateSoundName( m_NoiseMoving, "Plat.DefaultMoving" ); UTIL_ValidateSoundName( m_NoiseArrived, "Plat.DefaultArrive" );
#ifdef HL1_DLL
// set the plat's "in-motion" sound
switch (m_MoveSound) { default: case 0: m_NoiseMoving = MAKE_STRING( "Plat.DefaultMoving" ); break; case 1: m_NoiseMoving = MAKE_STRING("Plat.BigElev1"); break; case 2: m_NoiseMoving = MAKE_STRING("Plat.BigElev2"); break; case 3: m_NoiseMoving = MAKE_STRING("Plat.TechElev1"); break; case 4: m_NoiseMoving = MAKE_STRING("Plat.TechElev2"); break; case 5: m_NoiseMoving = MAKE_STRING("Plat.TechElev3"); break; case 6: m_NoiseMoving = MAKE_STRING("Plat.FreightElev1"); break; case 7: m_NoiseMoving = MAKE_STRING("Plat.FreightElev2"); break; case 8: m_NoiseMoving = MAKE_STRING("Plat.HeavyElev"); break; case 9: m_NoiseMoving = MAKE_STRING("Plat.RackElev"); break; case 10: m_NoiseMoving = MAKE_STRING("Plat.RailElev"); break; case 11: m_NoiseMoving = MAKE_STRING("Plat.SqueakElev"); break; case 12: m_NoiseMoving = MAKE_STRING("Plat.OddElev1"); break; case 13: m_NoiseMoving = MAKE_STRING("Plat.OddElev2"); break; }
// set the plat's 'reached destination' stop sound
switch (m_StopSound) { default: case 0: m_NoiseArrived = MAKE_STRING( "Plat.DefaultArrive" ); break; case 1: m_NoiseArrived = MAKE_STRING("Plat.BigElevStop1"); break; case 2: m_NoiseArrived = MAKE_STRING("Plat.BigElevStop2"); break; case 3: m_NoiseArrived = MAKE_STRING("Plat.FreightElevStop"); break; case 4: m_NoiseArrived = MAKE_STRING("Plat.HeavyElevStop"); break; case 5: m_NoiseArrived = MAKE_STRING("Plat.RackStop"); break; case 6: m_NoiseArrived = MAKE_STRING("Plat.RailStop"); break; case 7: m_NoiseArrived = MAKE_STRING("Plat.SqueakStop"); break; case 8: m_NoiseArrived = MAKE_STRING("Plat.QuickStop"); break; }
#endif // HL1_DLL
//Precache them all
PrecacheScriptSound( (char *) STRING(m_NoiseMoving) ); PrecacheScriptSound( (char *) STRING(m_NoiseArrived) );
}
class CFuncPlat : public CBasePlatTrain { DECLARE_CLASS( CFuncPlat, CBasePlatTrain ); public: void Spawn( void ); void Precache( void ); bool CreateVPhysics(); void Setup( void );
virtual void Blocked( CBaseEntity *pOther ); void PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
void CallGoDown( void ) { GoDown(); } void CallHitTop( void ) { HitTop(); } void CallHitBottom( void ) { HitBottom(); }
virtual void GoUp( void ); virtual void GoDown( void ); virtual void HitTop( void ); virtual void HitBottom( void );
void InputToggle(inputdata_t &data); void InputGoUp(inputdata_t &data); void InputGoDown(inputdata_t &data);
DECLARE_DATADESC();
private:
string_t m_sNoise; };
BEGIN_DATADESC( CFuncPlat )
DEFINE_FIELD( m_sNoise, FIELD_STRING ),
// Function Pointers
DEFINE_FUNCTION( PlatUse ), DEFINE_FUNCTION( CallGoDown ), DEFINE_FUNCTION( CallHitTop ), DEFINE_FUNCTION( CallHitBottom ),
// Inputs
DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), DEFINE_INPUTFUNC( FIELD_VOID, "GoUp", InputGoUp ), DEFINE_INPUTFUNC( FIELD_VOID, "GoDown", InputGoDown ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( func_plat, CFuncPlat );
//==================================================
// CPlatTrigger
//==================================================
class CPlatTrigger : public CBaseEntity { DECLARE_CLASS( CPlatTrigger, CBaseEntity ); public: virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() | FCAP_DONT_SAVE; } void SpawnInsideTrigger( CFuncPlat *pPlatform ); void Touch( CBaseEntity *pOther ); CFuncPlat *m_pPlatform; };
void CFuncPlat::Setup( void ) { if (m_flTLength == 0) { m_flTLength = 80; } if (m_flTWidth == 0) { m_flTWidth = 10; } SetLocalAngles( vec3_angle ); SetSolid( SOLID_BSP ); SetMoveType( MOVETYPE_PUSH );
// Set size and link into world
SetModel( STRING( GetModelName() ) );
m_vecPosition1 = GetLocalOrigin(); //Top
m_vecPosition2 = GetLocalOrigin(); //Bottom
if ( m_flHeight != 0 ) { m_vecPosition2.z = GetLocalOrigin().z - m_flHeight; } else { // NOTE: This works because the angles were set to vec3_angle above
m_vecPosition2.z = GetLocalOrigin().z - CollisionProp()->OBBSize().z + 8; }
if (m_flSpeed == 0) { m_flSpeed = 150; }
if ( m_volume == 0.0f ) { m_volume = 0.85f; } }
void CFuncPlat::Precache( ) { BaseClass::Precache(); if ( IsTogglePlat() == false ) { // Create the "start moving" trigger
PlatSpawnInsideTrigger( edict() ); } }
void CFuncPlat::Spawn( ) { Setup(); Precache();
// If this platform is the target of some button, it starts at the TOP position,
// and is brought down by that button. Otherwise, it starts at BOTTOM.
if ( GetEntityName() != NULL_STRING ) { UTIL_SetOrigin( this, m_vecPosition1); m_toggle_state = TS_AT_TOP; SetUse( &CFuncPlat::PlatUse ); } else { UTIL_SetOrigin( this, m_vecPosition2); m_toggle_state = TS_AT_BOTTOM; } CreateVPhysics(); }
bool CFuncPlat::CreateVPhysics() { VPhysicsInitShadow( false, false ); return true; }
static void PlatSpawnInsideTrigger(edict_t* pevPlatform) { // old code: //GetClassPtr( (CPlatTrigger *)NULL)->SpawnInsideTrigger( GetClassPtr( (CFuncPlat *)pevPlatform ) );
CPlatTrigger *plattrig = CREATE_UNSAVED_ENTITY( CPlatTrigger, "plat_trigger" ); plattrig->SpawnInsideTrigger( (CFuncPlat *)GetContainingEntity( pevPlatform ) ); }
//
// Create a trigger entity for a platform.
//
void CPlatTrigger::SpawnInsideTrigger( CFuncPlat *pPlatform ) { m_pPlatform = pPlatform; // Create trigger entity, "point" it at the owning platform, give it a touch method
SetSolid( SOLID_BSP ); AddSolidFlags( FSOLID_TRIGGER ); SetMoveType( MOVETYPE_NONE ); SetLocalOrigin( pPlatform->GetLocalOrigin() );
// Establish the trigger field's size
CCollisionProperty *pCollision = m_pPlatform->CollisionProp(); Vector vecTMin = pCollision->OBBMins() + Vector ( 25 , 25 , 0 ); Vector vecTMax = pCollision->OBBMaxs() + Vector ( 25 , 25 , 8 ); vecTMin.z = vecTMax.z - ( m_pPlatform->m_vecPosition1.z - m_pPlatform->m_vecPosition2.z + 8 ); if ( pCollision->OBBSize().x <= 50 ) { vecTMin.x = (pCollision->OBBMins().x + pCollision->OBBMaxs().x) / 2; vecTMax.x = vecTMin.x + 1; } if ( pCollision->OBBSize().y <= 50 ) { vecTMin.y = (pCollision->OBBMins().y + pCollision->OBBMaxs().y) / 2; vecTMax.y = vecTMin.y + 1; } UTIL_SetSize ( this, vecTMin, vecTMax ); }
//
// When the platform's trigger field is touched, the platform ???
//
void CPlatTrigger::Touch( CBaseEntity *pOther ) { // Ignore touches by non-players
if ( !pOther->IsPlayer() ) return;
// Ignore touches by corpses
if (!pOther->IsAlive()) return; // Make linked platform go up/down.
if (m_pPlatform->m_toggle_state == TS_AT_BOTTOM) m_pPlatform->GoUp(); else if (m_pPlatform->m_toggle_state == TS_AT_TOP) m_pPlatform->SetMoveDoneTime( 1 );// delay going down
}
//-----------------------------------------------------------------------------
// Purpose: Used when a platform is the target of a button.
// Start bringing platform down.
// Input : pActivator -
// pCaller -
// useType -
// value -
//-----------------------------------------------------------------------------
void CFuncPlat::InputToggle(inputdata_t &data) { if ( IsTogglePlat() ) { if (m_toggle_state == TS_AT_TOP) GoDown(); else if ( m_toggle_state == TS_AT_BOTTOM ) GoUp(); } else { SetUse( NULL );
if (m_toggle_state == TS_AT_TOP) GoDown(); } }
void CFuncPlat::InputGoUp(inputdata_t &data) { if ( m_toggle_state == TS_AT_BOTTOM ) GoUp(); }
void CFuncPlat::InputGoDown(inputdata_t &data) { if ( m_toggle_state == TS_AT_TOP ) GoDown(); }
//-----------------------------------------------------------------------------
// Purpose: Used when a platform is the target of a button.
// Start bringing platform down.
// Input : pActivator -
// pCaller -
// useType -
// value -
//-----------------------------------------------------------------------------
void CFuncPlat::PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { if ( IsTogglePlat() ) { // Top is off, bottom is on
bool on = (m_toggle_state == TS_AT_BOTTOM) ? true : false;
if ( !ShouldToggle( useType, on ) ) return;
if (m_toggle_state == TS_AT_TOP) GoDown(); else if ( m_toggle_state == TS_AT_BOTTOM ) GoUp(); } else { SetUse( NULL );
if (m_toggle_state == TS_AT_TOP) GoDown(); } }
//
// Platform is at top, now starts moving down.
//
void CFuncPlat::GoDown( void ) { PlayMovingSound();
ASSERT(m_toggle_state == TS_AT_TOP || m_toggle_state == TS_GOING_UP); m_toggle_state = TS_GOING_DOWN; SetMoveDone(&CFuncPlat::CallHitBottom); LinearMove(m_vecPosition2, m_flSpeed); }
//
// Platform has hit bottom. Stops and waits forever.
//
void CFuncPlat::HitBottom( void ) { StopMovingSound();
if ( m_NoiseArrived != NULL_STRING ) { CPASAttenuationFilter filter( this );
EmitSound_t ep; ep.m_nChannel = CHAN_WEAPON; ep.m_pSoundName = STRING(m_NoiseArrived); ep.m_flVolume = m_volume; ep.m_SoundLevel = SNDLVL_NORM;
EmitSound( filter, entindex(), ep ); }
ASSERT(m_toggle_state == TS_GOING_DOWN); m_toggle_state = TS_AT_BOTTOM; }
//
// Platform is at bottom, now starts moving up
//
void CFuncPlat::GoUp( void ) { PlayMovingSound(); ASSERT(m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN); m_toggle_state = TS_GOING_UP; SetMoveDone(&CFuncPlat::CallHitTop); LinearMove(m_vecPosition1, m_flSpeed); }
//
// Platform has hit top. Pauses, then starts back down again.
//
void CFuncPlat::HitTop( void ) { StopMovingSound();
if ( m_NoiseArrived != NULL_STRING ) { CPASAttenuationFilter filter( this );
EmitSound_t ep; ep.m_nChannel = CHAN_WEAPON; ep.m_pSoundName = STRING(m_NoiseArrived); ep.m_flVolume = m_volume; ep.m_SoundLevel = SNDLVL_NORM;
EmitSound( filter, entindex(), ep ); } ASSERT(m_toggle_state == TS_GOING_UP); m_toggle_state = TS_AT_TOP;
if ( !IsTogglePlat() ) { // After a delay, the platform will automatically start going down again.
SetMoveDone( &CFuncPlat::CallGoDown ); SetMoveDoneTime( 3 ); } }
//-----------------------------------------------------------------------------
// Purpose: Called when we are blocked.
//-----------------------------------------------------------------------------
void CFuncPlat::Blocked( CBaseEntity *pOther ) { DevMsg( 2, "%s Blocked by %s\n", GetClassname(), pOther->GetClassname() );
// Hurt the blocker a little
pOther->TakeDamage( CTakeDamageInfo( this, this, 1, DMG_CRUSH ) );
if (m_sNoise != NULL_STRING) { StopSound(entindex(), CHAN_STATIC, (char*)STRING(m_sNoise)); } // Send the platform back where it came from
ASSERT(m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN); if (m_toggle_state == TS_GOING_UP) { GoDown(); } else if (m_toggle_state == TS_GOING_DOWN) { GoUp (); } }
class CFuncPlatRot : public CFuncPlat { DECLARE_CLASS( CFuncPlatRot, CFuncPlat ); public: void Spawn( void ); void SetupRotation( void );
virtual void GoUp( void ); virtual void GoDown( void ); virtual void HitTop( void ); virtual void HitBottom( void ); void RotMove( QAngle &destAngle, float time ); DECLARE_DATADESC();
QAngle m_end, m_start; };
LINK_ENTITY_TO_CLASS( func_platrot, CFuncPlatRot );
BEGIN_DATADESC( CFuncPlatRot )
DEFINE_FIELD( m_end, FIELD_VECTOR ), DEFINE_FIELD( m_start, FIELD_VECTOR ),
END_DATADESC()
void CFuncPlatRot::SetupRotation( void ) { if ( m_vecFinalAngle.x != 0 ) // This plat rotates too!
{ CBaseToggle::AxisDir(); m_start = GetLocalAngles(); m_end = GetLocalAngles() + m_vecMoveAng * m_vecFinalAngle.x; } else { m_start = vec3_angle; m_end = vec3_angle; } if ( GetEntityName() != NULL_STRING ) // Start at top
{ SetLocalAngles( m_end ); } }
void CFuncPlatRot::Spawn( void ) { BaseClass::Spawn(); SetupRotation(); }
void CFuncPlatRot::GoDown( void ) { BaseClass::GoDown(); RotMove( m_start, GetMoveDoneTime() ); }
//
// Platform has hit bottom. Stops and waits forever.
//
void CFuncPlatRot::HitBottom( void ) { BaseClass::HitBottom(); SetLocalAngularVelocity( vec3_angle ); SetLocalAngles( m_start ); }
//
// Platform is at bottom, now starts moving up
//
void CFuncPlatRot::GoUp( void ) { BaseClass::GoUp(); RotMove( m_end, GetMoveDoneTime() ); }
//
// Platform has hit top. Pauses, then starts back down again.
//
void CFuncPlatRot::HitTop( void ) { BaseClass::HitTop(); SetLocalAngularVelocity( vec3_angle ); SetLocalAngles( m_end ); }
void CFuncPlatRot::RotMove( QAngle &destAngle, float time ) { // set destdelta to the vector needed to move
QAngle vecDestDelta = destAngle - GetLocalAngles();
// Travel time is so short, we're practically there already; so make it so.
if ( time >= 0.1) SetLocalAngularVelocity( vecDestDelta * (1.0 / time) ); else { SetLocalAngularVelocity( vecDestDelta ); SetMoveDoneTime( 1 ); } }
class CFuncTrain : public CBasePlatTrain { DECLARE_CLASS( CFuncTrain, CBasePlatTrain ); public: void Spawn( void ); void Precache( void ); void Activate( void ); void OnRestore( void );
void SetupTarget( void ); void Blocked( CBaseEntity *pOther ); void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
void Wait( void ); void Next( void );
//Inputs
void InputToggle(inputdata_t &data); void InputStart(inputdata_t &data); void InputStop(inputdata_t &data);
void Start( void ); void Stop( void );
DECLARE_DATADESC();
public: EHANDLE m_hCurrentTarget; bool m_activated; EHANDLE m_hEnemy; float m_flBlockDamage; // Damage to inflict when blocked.
float m_flNextBlockTime; string_t m_iszLastTarget;
};
LINK_ENTITY_TO_CLASS( func_train, CFuncTrain );
BEGIN_DATADESC( CFuncTrain )
DEFINE_FIELD( m_hCurrentTarget, FIELD_EHANDLE ), DEFINE_FIELD( m_activated, FIELD_BOOLEAN ), DEFINE_FIELD( m_hEnemy, FIELD_EHANDLE ), DEFINE_FIELD( m_iszLastTarget, FIELD_STRING ), DEFINE_FIELD( m_flNextBlockTime, FIELD_TIME ),
DEFINE_KEYFIELD( m_flBlockDamage, FIELD_FLOAT, "dmg" ),
// Function Pointers
DEFINE_FUNCTION( Wait ), DEFINE_FUNCTION( Next ),
// Inputs
DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), DEFINE_INPUTFUNC( FIELD_VOID, "Start", InputStart ), DEFINE_INPUTFUNC( FIELD_VOID, "Stop", InputStop ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose: Handles a train being blocked by an entity.
// Input : pOther - What was hit.
//-----------------------------------------------------------------------------
void CFuncTrain::Blocked( CBaseEntity *pOther ) { if ( gpGlobals->curtime < m_flNextBlockTime ) return;
m_flNextBlockTime = gpGlobals->curtime + 0.5; //Inflict damage
pOther->TakeDamage( CTakeDamageInfo( this, this, m_flBlockDamage, DMG_CRUSH ) ); }
void CFuncTrain::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { //If we've been waiting to be retriggered, move to the next destination
if ( m_spawnflags & SF_TRAIN_WAIT_RETRIGGER ) { // Move toward my target
m_spawnflags &= ~SF_TRAIN_WAIT_RETRIGGER; Next(); } else { m_spawnflags |= SF_TRAIN_WAIT_RETRIGGER; // Pop back to last target if it's available
if ( m_hEnemy ) { m_target = m_hEnemy->GetEntityName(); }
SetNextThink( TICK_NEVER_THINK ); SetLocalVelocity( vec3_origin ); if ( m_NoiseArrived != NULL_STRING ) { CPASAttenuationFilter filter( this );
EmitSound_t ep; ep.m_nChannel = CHAN_VOICE; ep.m_pSoundName = STRING(m_NoiseArrived); ep.m_flVolume = m_volume; ep.m_SoundLevel = SNDLVL_NORM;
EmitSound( filter, entindex(), ep ); } } }
void CFuncTrain::Wait( void ) { //If we're moving passed a path track, then trip its output
variant_t emptyVariant; m_hCurrentTarget->AcceptInput( "InPass", this, this, emptyVariant, 0 );
// need pointer to LAST target.
if ( m_hCurrentTarget->HasSpawnFlags( SF_TRAIN_WAIT_RETRIGGER ) || HasSpawnFlags( SF_TRAIN_WAIT_RETRIGGER ) ) { AddSpawnFlags( SF_TRAIN_WAIT_RETRIGGER ); // Clear the sound channel.
StopMovingSound(); if ( m_NoiseArrived != NULL_STRING ) { CPASAttenuationFilter filter( this );
EmitSound_t ep; ep.m_nChannel = CHAN_VOICE; ep.m_pSoundName = STRING(m_NoiseArrived); ep.m_flVolume = m_volume; ep.m_SoundLevel = SNDLVL_NORM;
EmitSound( filter, entindex(), ep ); }
SetMoveDoneTime( -1 ); return; } //NOTENOTE: -1 wait will wait forever
if ( m_flWait != 0 ) { SetMoveDoneTime( m_flWait );
StopMovingSound(); if ( m_NoiseArrived != NULL_STRING ) { CPASAttenuationFilter filter( this );
EmitSound_t ep; ep.m_nChannel = CHAN_VOICE; ep.m_pSoundName = STRING(m_NoiseArrived); ep.m_flVolume = m_volume; ep.m_SoundLevel = SNDLVL_NORM;
EmitSound( filter, entindex(), ep ); }
SetMoveDone( &CFuncTrain::Next ); } else { // Do it right now
Next(); } }
//-----------------------------------------------------------------------------
// Purpose: Advances the train to the next path corner on the path.
//-----------------------------------------------------------------------------
void CFuncTrain::Next( void ) { //Find our next target
CBaseEntity *pTarg = GetNextTarget();
//If none, we're done
if ( pTarg == NULL ) { //Stop the moving sound
StopMovingSound();
// Play stop sound
if ( m_NoiseArrived != NULL_STRING ) { CPASAttenuationFilter filter( this );
EmitSound_t ep; ep.m_nChannel = CHAN_VOICE; ep.m_pSoundName = STRING(m_NoiseArrived); ep.m_flVolume = m_volume; ep.m_SoundLevel = SNDLVL_NORM;
EmitSound( filter, entindex(), ep ); }
return; }
// Save last target in case we need to find it again
m_iszLastTarget = m_target;
m_target = pTarg->m_target; m_flWait = pTarg->GetDelay();
// If our target has a speed, take it
if ( m_hCurrentTarget && m_hCurrentTarget->m_flSpeed != 0 ) { m_flSpeed = m_hCurrentTarget->m_flSpeed; DevMsg( 2, "Train %s speed to %4.2f\n", GetDebugName(), m_flSpeed ); }
// Keep track of this since path corners change our target for us
m_hCurrentTarget = pTarg; m_hEnemy = pTarg;
//Check for teleport
if ( m_hCurrentTarget->HasSpawnFlags( SF_CORNER_TELEPORT ) ) { IncrementInterpolationFrame();
// This is supposed to place the center of the func_train at the target's origin.
// FIXME: This is totally busted! It's using the wrong space for the computation...
UTIL_SetOrigin( this, pTarg->GetLocalOrigin() - CollisionProp()->OBBCenter() ); // Get on with doing the next path corner.
Wait(); } else { // Normal linear move
PlayMovingSound();
SetMoveDone( &CFuncTrain::Wait );
// This is supposed to place the center of the func_train at the target's origin.
// FIXME: This is totally busted! It's using the wrong space for the computation...
LinearMove ( pTarg->GetLocalOrigin() - CollisionProp()->OBBCenter(), m_flSpeed ); } }
//-----------------------------------------------------------------------------
// Purpose: Called after all the entities spawn.
//-----------------------------------------------------------------------------
void CFuncTrain::Activate( void ) { BaseClass::Activate();
// Not yet active, so teleport to first target
if ( m_activated == false ) { SetupTarget();
m_activated = true;
if ( m_hCurrentTarget.Get() == NULL ) return;
// This is supposed to place the center of the func_train at the target's origin.
// FIXME: This is totally busted! It's using the wrong space for the computation...
UTIL_SetOrigin( this, m_hCurrentTarget->GetLocalOrigin() - CollisionProp()->OBBCenter() ); if ( GetSolid() == SOLID_BSP ) { VPhysicsInitShadow( false, false ); }
// Start immediately if not triggered
if ( !GetEntityName() ) { SetMoveDoneTime( 0.1 ); SetMoveDone( &CFuncTrain::Next ); } else { m_spawnflags |= SF_TRAIN_WAIT_RETRIGGER; } } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CFuncTrain::SetupTarget( void ) { // Find our target whenever we don't have one (level transition)
if ( !m_hCurrentTarget ) { CBaseEntity *pTarg = gEntList.FindEntityByName( NULL, m_target );
if ( pTarg == NULL ) { Msg( "Can't find target of train %s\n", STRING(m_target) ); return; } // Keep track of this since path corners change our target for us
m_target = pTarg->m_target; m_hCurrentTarget = pTarg; } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CFuncTrain::Spawn( void ) { Precache(); if ( m_flSpeed == 0 ) { m_flSpeed = 100; } if ( !m_target ) { Warning("FuncTrain '%s' has no target.\n", GetDebugName()); } if ( m_flBlockDamage == 0 ) { m_flBlockDamage = 2; } SetMoveType( MOVETYPE_PUSH ); SetSolid( SOLID_BSP ); SetModel( STRING( GetModelName() ) ); if ( m_spawnflags & SF_TRACKTRAIN_PASSABLE ) { AddSolidFlags( FSOLID_NOT_SOLID ); }
m_activated = false;
if ( m_volume == 0.0f ) { m_volume = 0.85f; } }
void CFuncTrain::Precache( void ) { BaseClass::Precache(); }
void CFuncTrain::OnRestore( void ) { BaseClass::OnRestore();
// Are we moving?
if ( IsMoving() ) { // Continue moving to the same target
m_target = m_iszLastTarget; }
SetupTarget(); }
void CFuncTrain::InputToggle( inputdata_t &data ) { //If we've been waiting to be retriggered, move to the next destination
if( HasSpawnFlags( SF_TRAIN_WAIT_RETRIGGER ) ) { Start(); } else { Stop(); } }
void CFuncTrain::InputStart( inputdata_t &data ) { Start(); }
void CFuncTrain::InputStop( inputdata_t &data ) { Stop(); }
void CFuncTrain::Start( void ) { //start moving
if( HasSpawnFlags( SF_TRAIN_WAIT_RETRIGGER ) ) { // Move toward my target
RemoveSpawnFlags( SF_TRAIN_WAIT_RETRIGGER ); Next(); } }
void CFuncTrain::Stop( void ) { //stop moving
if( !HasSpawnFlags( SF_TRAIN_WAIT_RETRIGGER ) ) { AddSpawnFlags( SF_TRAIN_WAIT_RETRIGGER ); // Pop back to last target if it's available
if ( m_hEnemy ) { m_target = m_hEnemy->GetEntityName(); }
SetNextThink( TICK_NEVER_THINK ); SetAbsVelocity( vec3_origin ); if ( m_NoiseArrived != NULL_STRING ) { CPASAttenuationFilter filter( this );
EmitSound_t ep; ep.m_nChannel = CHAN_VOICE; ep.m_pSoundName = STRING(m_NoiseArrived); ep.m_flVolume = m_volume; ep.m_SoundLevel = SNDLVL_NORM;
EmitSound( filter, entindex(), ep ); }
//Do not teleport to our final move destination
SetMoveDone( NULL ); SetMoveDoneTime( -1 ); } }
BEGIN_DATADESC( CFuncTrackTrain )
DEFINE_KEYFIELD( m_length, FIELD_FLOAT, "wheels" ), DEFINE_KEYFIELD( m_height, FIELD_FLOAT, "height" ), DEFINE_KEYFIELD( m_maxSpeed, FIELD_FLOAT, "startspeed" ), DEFINE_KEYFIELD( m_flBank, FIELD_FLOAT, "bank" ), DEFINE_KEYFIELD( m_flBlockDamage, FIELD_FLOAT, "dmg" ), DEFINE_KEYFIELD( m_iszSoundMove, FIELD_SOUNDNAME, "MoveSound" ), DEFINE_KEYFIELD( m_iszSoundMovePing, FIELD_SOUNDNAME, "MovePingSound" ), DEFINE_KEYFIELD( m_iszSoundStart, FIELD_SOUNDNAME, "StartSound" ), DEFINE_KEYFIELD( m_iszSoundStop, FIELD_SOUNDNAME, "StopSound" ), DEFINE_KEYFIELD( m_nMoveSoundMinPitch, FIELD_INTEGER, "MoveSoundMinPitch" ), DEFINE_KEYFIELD( m_nMoveSoundMaxPitch, FIELD_INTEGER, "MoveSoundMaxPitch" ), DEFINE_KEYFIELD( m_flMoveSoundMinTime, FIELD_FLOAT, "MoveSoundMinTime" ), DEFINE_KEYFIELD( m_flMoveSoundMaxTime, FIELD_FLOAT, "MoveSoundMaxTime" ), DEFINE_FIELD( m_flNextMoveSoundTime, FIELD_TIME ), DEFINE_KEYFIELD( m_eVelocityType, FIELD_INTEGER, "velocitytype" ), DEFINE_KEYFIELD( m_eOrientationType, FIELD_INTEGER, "orientationtype" ),
DEFINE_FIELD( m_ppath, FIELD_CLASSPTR ), DEFINE_FIELD( m_dir, FIELD_FLOAT ), DEFINE_FIELD( m_controlMins, FIELD_VECTOR ), DEFINE_FIELD( m_controlMaxs, FIELD_VECTOR ), DEFINE_FIELD( m_flVolume, FIELD_FLOAT ), DEFINE_FIELD( m_oldSpeed, FIELD_FLOAT ), //DEFINE_FIELD( m_lastBlockPos, FIELD_POSITION_VECTOR ), // temp values for blocking, don't save
//DEFINE_FIELD( m_lastBlockTick, FIELD_INTEGER ),
DEFINE_FIELD( m_bSoundPlaying, FIELD_BOOLEAN ),
DEFINE_KEYFIELD( m_bManualSpeedChanges, FIELD_BOOLEAN, "ManualSpeedChanges" ), DEFINE_KEYFIELD( m_flAccelSpeed, FIELD_FLOAT, "ManualAccelSpeed" ), DEFINE_KEYFIELD( m_flDecelSpeed, FIELD_FLOAT, "ManualDecelSpeed" ),
#ifdef HL1_DLL
DEFINE_FIELD( m_bOnTrackChange, FIELD_BOOLEAN ), #endif
// Inputs
DEFINE_INPUTFUNC( FIELD_VOID, "Stop", InputStop ), DEFINE_INPUTFUNC( FIELD_VOID, "StartForward", InputStartForward ), DEFINE_INPUTFUNC( FIELD_VOID, "StartBackward", InputStartBackward ), DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), DEFINE_INPUTFUNC( FIELD_VOID, "Resume", InputResume ), DEFINE_INPUTFUNC( FIELD_VOID, "Reverse", InputReverse ), DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeed", InputSetSpeed ), DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeedDir", InputSetSpeedDir ), DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeedReal", InputSetSpeedReal ), DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeedDirAccel", InputSetSpeedDirAccel ), DEFINE_INPUTFUNC( FIELD_STRING, "TeleportToPathTrack", InputTeleportToPathTrack ), DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeedForwardModifier", InputSetSpeedForwardModifier ),
// Outputs
DEFINE_OUTPUT( m_OnStart, "OnStart" ), DEFINE_OUTPUT( m_OnNext, "OnNextPoint" ),
// Function Pointers
DEFINE_FUNCTION( Next ), DEFINE_FUNCTION( Find ), DEFINE_FUNCTION( NearestPath ), DEFINE_FUNCTION( DeadEnd ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( func_tracktrain, CFuncTrackTrain );
//-----------------------------------------------------------------------------
// Datatable
//-----------------------------------------------------------------------------
IMPLEMENT_SERVERCLASS_ST( CFuncTrackTrain, DT_FuncTrackTrain ) END_SEND_TABLE()
//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
CFuncTrackTrain::CFuncTrackTrain() { #ifdef _DEBUG
m_controlMins.Init(); m_controlMaxs.Init(); #endif
// These defaults match old func_tracktrains. Changing these defaults would
// require a vmf_tweak of older content to keep it from breaking.
m_eOrientationType = TrainOrientation_AtPathTracks; m_eVelocityType = TrainVelocity_Instantaneous; m_lastBlockPos.Init(); m_lastBlockTick = gpGlobals->tickcount;
m_flSpeedForwardModifier = 1.0f; m_flUnmodifiedDesiredSpeed = 0.0f;
m_bDamageChild = false; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CFuncTrackTrain::DrawDebugTextOverlays( void ) { int nOffset = BaseClass::DrawDebugTextOverlays();
if (m_debugOverlays & OVERLAY_TEXT_BIT) { char tempstr[512]; Q_snprintf( tempstr,sizeof(tempstr), "angles: %g %g %g", (double)GetLocalAngles()[PITCH], (double)GetLocalAngles()[YAW], (double)GetLocalAngles()[ROLL] ); EntityText( nOffset, tempstr, 0 ); nOffset++;
float flCurSpeed = GetLocalVelocity().Length(); Q_snprintf( tempstr,sizeof(tempstr), "current speed (goal): %g (%g)", (double)flCurSpeed, (double)m_flSpeed ); EntityText( nOffset, tempstr, 0 ); nOffset++;
Q_snprintf( tempstr,sizeof(tempstr), "max speed: %g", (double)m_maxSpeed ); EntityText( nOffset, tempstr, 0 ); nOffset++; }
return nOffset; }
void CFuncTrackTrain::DrawDebugGeometryOverlays() { BaseClass::DrawDebugGeometryOverlays(); if (m_debugOverlays & OVERLAY_BBOX_BIT) { NDebugOverlay::Box( GetAbsOrigin(), -Vector(4,4,4),Vector(4,4,4), 255, 0, 255, 0, 0); Vector out; VectorTransform( Vector(m_length,0,0), EntityToWorldTransform(), out ); NDebugOverlay::Box( out, -Vector(4,4,4),Vector(4,4,4), 255, 0, 255, 0, 0); } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CFuncTrackTrain::KeyValue( const char *szKeyName, const char *szValue ) { if (FStrEq(szKeyName, "volume")) { m_flVolume = (float) (atoi(szValue)); m_flVolume *= 0.1f; } else { return BaseClass::KeyValue( szKeyName, szValue ); }
return true; }
//-----------------------------------------------------------------------------
// Purpose: Input handler that stops the train.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::InputStop( inputdata_t &inputdata ) { Stop(); }
//------------------------------------------------------------------------------
// Purpose: Input handler that starts the train moving.
//------------------------------------------------------------------------------
void CFuncTrackTrain::InputResume( inputdata_t &inputdata ) { m_flSpeed = m_oldSpeed; Start(); }
//------------------------------------------------------------------------------
// Purpose: Input handler that reverses the trains current direction of motion.
//------------------------------------------------------------------------------
void CFuncTrackTrain::InputReverse( inputdata_t &inputdata ) { SetDirForward( !IsDirForward() ); SetSpeed( m_flSpeed ); }
//-----------------------------------------------------------------------------
// Purpose: Returns whether we are travelling forward along our path.
//-----------------------------------------------------------------------------
bool CFuncTrackTrain::IsDirForward() { return ( m_dir == 1 ); }
//-----------------------------------------------------------------------------
// Purpose: Sets whether we go forward or backward along our path.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::SetDirForward( bool bForward ) { if ( bForward && ( m_dir != 1 ) ) { // Reverse direction.
if ( m_ppath && m_ppath->GetPrevious() ) { m_ppath = m_ppath->GetPrevious(); }
m_dir = 1; } else if ( !bForward && ( m_dir != -1 ) ) { // Reverse direction.
if ( m_ppath && m_ppath->GetNext() ) { m_ppath = m_ppath->GetNext(); }
m_dir = -1; } }
//------------------------------------------------------------------------------
// Purpose: Input handler that starts the train moving.
//------------------------------------------------------------------------------
void CFuncTrackTrain::InputStartForward( inputdata_t &inputdata ) { SetDirForward( true ); SetSpeed( m_maxSpeed ); }
//------------------------------------------------------------------------------
// Purpose: Input handler that starts the train moving.
//------------------------------------------------------------------------------
void CFuncTrackTrain::InputStartBackward( inputdata_t &inputdata ) { SetDirForward( false ); SetSpeed( m_maxSpeed ); }
//------------------------------------------------------------------------------
// Purpose: Starts the train moving.
//------------------------------------------------------------------------------
void CFuncTrackTrain::Start( void ) { m_OnStart.FireOutput(this,this); Next(); }
//-----------------------------------------------------------------------------
// Purpose: Toggles the train between moving and not moving.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::InputToggle( inputdata_t &inputdata ) { if ( m_flSpeed == 0 ) { SetSpeed( m_maxSpeed ); } else { SetSpeed( 0 ); } }
//-----------------------------------------------------------------------------
// Purpose: Handles player use so players can control the speed of the train.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { // player +USE
if ( useType == USE_SET ) { float delta = value;
delta = ((int)(m_flSpeed * 4) / (int)m_maxSpeed)*0.25 + 0.25 * delta; if ( delta > 1 ) delta = 1; else if ( delta < -0.25 ) delta = -0.25; if ( m_spawnflags & SF_TRACKTRAIN_FORWARDONLY ) { if ( delta < 0 ) delta = 0; } SetDirForward( delta >= 0 ); delta = fabs(delta); SetSpeed( m_maxSpeed * delta ); } }
//-----------------------------------------------------------------------------
// Purpose: Input handler that sets the speed of the train.
// Input : Float speed from 0 to max speed, in units per second.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::InputSetSpeedReal( inputdata_t &inputdata ) { SetSpeed( clamp( inputdata.value.Float(), 0.f, m_maxSpeed ) ); }
//-----------------------------------------------------------------------------
// Purpose: Input handler that sets the speed of the train.
// Input : Float speed scale from 0 to 1.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::InputSetSpeed( inputdata_t &inputdata ) { float flScale = clamp( inputdata.value.Float(), 0.f, 1.f ); SetSpeed( m_maxSpeed * flScale ); }
//-----------------------------------------------------------------------------
// Purpose: Input handler that sets the speed of the train and the direction
// based on the sign of the speed.
// Input : Float speed scale from -1 to 1. Negatives values indicate a reversed
// direction.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::InputSetSpeedDir( inputdata_t &inputdata ) { float newSpeed = inputdata.value.Float(); SetDirForward( newSpeed >= 0 ); newSpeed = fabs(newSpeed); float flScale = clamp( newSpeed, 0.f, 1.f ); SetSpeed( m_maxSpeed * flScale ); }
//-----------------------------------------------------------------------------
// Purpose: Input handler that sets the speed of the train and the direction
// based on the sign of the speed, and accels/decels to that speed
// Input : Float speed scale from -1 to 1. Negatives values indicate a reversed
// direction.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::InputSetSpeedDirAccel( inputdata_t &inputdata ) { SetSpeedDirAccel( inputdata.value.Float() ); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CFuncTrackTrain::SetSpeedDirAccel( float flNewSpeed ) { float newSpeed = flNewSpeed; SetDirForward( newSpeed >= 0 ); newSpeed = fabs( newSpeed ); float flScale = clamp( newSpeed, 0.f, 1.f ); SetSpeed( m_maxSpeed * flScale, true ); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CFuncTrackTrain::InputSetSpeedForwardModifier( inputdata_t &inputdata ) { SetSpeedForwardModifier( inputdata.value.Float() ) ; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CFuncTrackTrain::SetSpeedForwardModifier( float flModifier ) { float flSpeedForwardModifier = flModifier; flSpeedForwardModifier = fabs( flSpeedForwardModifier );
m_flSpeedForwardModifier = clamp( flSpeedForwardModifier, 0.f, 1.f ); SetSpeed( m_flUnmodifiedDesiredSpeed, true ); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CFuncTrackTrain::InputTeleportToPathTrack( inputdata_t &inputdata ) { const char *pszName = inputdata.value.String(); CPathTrack *pTrack = dynamic_cast<CPathTrack*>( gEntList.FindEntityByName( NULL, pszName ) );
if ( pTrack ) { TeleportToPathTrack( pTrack ); m_ppath = pTrack; } }
//-----------------------------------------------------------------------------
// Purpose: Sets the speed of the train to the given value in units per second.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::SetSpeed( float flSpeed, bool bAccel /*= false */ ) { m_bAccelToSpeed = bAccel;
m_flUnmodifiedDesiredSpeed = flSpeed; float flOldSpeed = m_flSpeed;
// are we using a speed forward modifier?
if ( m_flSpeedForwardModifier < 1.0 && m_dir > 0 ) { flSpeed = flSpeed * m_flSpeedForwardModifier; }
if ( m_bAccelToSpeed ) { m_flDesiredSpeed = fabs( flSpeed ) * m_dir; m_flSpeedChangeTime = gpGlobals->curtime;
if ( m_flSpeed == 0 && abs(m_flDesiredSpeed) > 0 ) { m_flSpeed = 0.1; // little push to get us going
}
Start();
return; }
m_flSpeed = fabs( flSpeed ) * m_dir;
if ( m_flSpeed != flOldSpeed) { // Changing speed.
if ( m_flSpeed != 0 ) { if ( flOldSpeed == 0 ) { // Starting to move.
Start(); } else { // Continuing to move.
Next(); } } else { // Stopping.
Stop(); } }
DevMsg( 2, "TRAIN(%s), speed to %.2f\n", GetDebugName(), m_flSpeed ); }
//-----------------------------------------------------------------------------
// Purpose: Stops the train.
//-----------------------------------------------------------------------------
void CFuncTrackTrain::Stop( void ) { SetLocalVelocity( vec3_origin ); SetLocalAngularVelocity( vec3_angle ); m_oldSpeed = m_flSpeed; m_flSpeed = 0; SoundStop(); SetThink(NULL); }
static CBaseEntity *FindPhysicsBlockerForHierarchy( CBaseEntity *pParentEntity ) { CUtlVector<CBaseEntity *> list; GetAllInHierarchy( pParentEntity, list ); CBaseEntity *pPhysicsBlocker = NULL; float maxForce = 0; for ( int i = 0; i < list.Count(); i++ ) { IPhysicsObject *pPhysics = list[i]->VPhysicsGetObject(); if ( pPhysics ) { IPhysicsFrictionSnapshot *pSnapshot = pPhysics->CreateFrictionSnapshot(); while ( pSnapshot->IsValid() ) { IPhysicsObject *pOther = pSnapshot->GetObject(1); CBaseEntity *pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData()); if ( pOtherEntity && 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 ); } }
|