You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3185 lines
79 KiB
3185 lines
79 KiB
//========= Copyright © 1996-2005, 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; |
|
|
|
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" ), |
|
|
|
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" ); |
|
|
|
//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 ) ) |
|
{ |
|
AddEffects( EF_NOINTERP ); |
|
|
|
// 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(); |
|
|
|
RemoveEffects( EF_NOINTERP ); |
|
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" ), |
|
|
|
// 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 ), |
|
|
|
// 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; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// 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, 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, 1 ); |
|
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, 1 ); |
|
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 ) |
|
{ |
|
float newSpeed = inputdata.value.Float(); |
|
SetDirForward( newSpeed >= 0 ); |
|
newSpeed = fabs(newSpeed); |
|
float flScale = clamp( newSpeed, 0, 1 ); |
|
SetSpeed( m_maxSpeed * flScale, true ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// 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; |
|
|
|
float flOldSpeed = m_flSpeed; |
|
|
|
if ( m_bAccelToSpeed ) |
|
{ |
|
m_flDesiredSpeed = fabs( flSpeed ) * m_dir; |
|
m_flSpeedChangeTime = gpGlobals->curtime; |
|
|
|
if ( m_flSpeed == 0 && abs(m_flDesiredSpeed) > 0 ) |
|
{ |
|
m_flSpeed = 0.1; // little push to get us going |
|
} |
|
|
|
Start(); |
|
|
|
return; |
|
} |
|
|
|
m_flSpeed = fabs( flSpeed ) * m_dir; |
|
|
|
if ( m_flSpeed != flOldSpeed) |
|
{ |
|
// Changing speed. |
|
if ( m_flSpeed != 0 ) |
|
{ |
|
if ( flOldSpeed == 0 ) |
|
{ |
|
// Starting to move. |
|
Start(); |
|
} |
|
else |
|
{ |
|
// Continuing to move. |
|
Next(); |
|
} |
|
} |
|
else |
|
{ |
|
// Stopping. |
|
Stop(); |
|
} |
|
} |
|
|
|
DevMsg( 2, "TRAIN(%s), speed to %.2f\n", GetDebugName(), m_flSpeed ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Stops the train. |
|
//----------------------------------------------------------------------------- |
|
void CFuncTrackTrain::Stop( void ) |
|
{ |
|
SetLocalVelocity( vec3_origin ); |
|
SetLocalAngularVelocity( vec3_angle ); |
|
m_oldSpeed = m_flSpeed; |
|
m_flSpeed = 0; |
|
SoundStop(); |
|
SetThink(NULL); |
|
} |
|
|
|
static CBaseEntity *FindPhysicsBlockerForHierarchy( CBaseEntity *pParentEntity ) |
|
{ |
|
CUtlVector<CBaseEntity *> list; |
|
GetAllInHierarchy( pParentEntity, list ); |
|
CBaseEntity *pPhysicsBlocker = NULL; |
|
float maxForce = 0; |
|
for ( int i = 0; i < list.Count(); i++ ) |
|
{ |
|
IPhysicsObject *pPhysics = list[i]->VPhysicsGetObject(); |
|
if ( pPhysics ) |
|
{ |
|
IPhysicsFrictionSnapshot *pSnapshot = pPhysics->CreateFrictionSnapshot(); |
|
while ( pSnapshot->IsValid() ) |
|
{ |
|
IPhysicsObject *pOther = pSnapshot->GetObject(1); |
|
CBaseEntity *pOtherEntity = static_cast<CBaseEntity *>(pOther->GetGameData()); |
|
if ( pOtherEntity->GetMoveType() == MOVETYPE_VPHYSICS ) |
|
{ |
|
Vector normal; |
|
pSnapshot->GetSurfaceNormal(normal); |
|
float dot = DotProduct( pParentEntity->GetAbsVelocity(), pSnapshot->GetNormalForce() * normal ); |
|
if ( !pPhysicsBlocker || dot > maxForce ) |
|
{ |
|
pPhysicsBlocker = pOtherEntity; |
|
maxForce = dot; |
|
} |
|
} |
|
pSnapshot->NextFrictionData(); |
|
} |
|
pPhysics->DestroyFrictionSnapshot( pSnapshot ); |
|
} |
|
} |
|
return pPhysicsBlocker; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called when we are blocked by another entity. |
|
// Input : pOther - |
|
//----------------------------------------------------------------------------- |
|
void CFuncTrackTrain::Blocked( CBaseEntity *pOther ) |
|
{ |
|
// Blocker is on-ground on the train |
|
if ( ( pOther->GetFlags() & FL_ONGROUND ) && pOther->GetGroundEntity() == this ) |
|
{ |
|
DevMsg( 1, "TRAIN(%s): Blocked by %s\n", GetDebugName(), pOther->GetClassname() ); |
|
float deltaSpeed = fabs(m_flSpeed); |
|
if ( deltaSpeed > 50 ) |
|
deltaSpeed = 50; |
|
|
|
Vector vecNewVelocity; |
|
pOther->GetVelocity( &vecNewVelocity ); |
|
if ( !vecNewVelocity.z ) |
|
{ |
|
pOther->ApplyAbsVelocityImpulse( Vector(0,0,deltaSpeed) ); |
|
} |
|
return; |
|
} |
|
else |
|
{ |
|
Vector vecNewVelocity; |
|
vecNewVelocity = pOther->GetAbsOrigin() - GetAbsOrigin(); |
|
VectorNormalize(vecNewVelocity); |
|
vecNewVelocity *= m_flBlockDamage; |
|
pOther->SetAbsVelocity( vecNewVelocity ); |
|
} |
|
if ( HasSpawnFlags(SF_TRACKTRAIN_UNBLOCKABLE_BY_PLAYER) ) |
|
{ |
|
CBaseEntity *pPhysicsBlocker = FindPhysicsBlockerForHierarchy(this); |
|
if ( pPhysicsBlocker ) |
|
{ |
|
// This code keeps track of how long this train has been blocked |
|
// The heuristic here is to keep instantaneous blocks from invoking the somewhat |
|
// heavy-handed solver (which will disable collisions until we're clear) in cases |
|
// where physics can solve it easily enough. |
|
int ticksBlocked = gpGlobals->tickcount - m_lastBlockTick; |
|
float dist = 0.0f; |
|
// wait at least 10 ticks and make sure the train isn't actually moving before really blocking |
|
const int MIN_BLOCKED_TICKS = 10; |
|
if ( ticksBlocked > MIN_BLOCKED_TICKS ) |
|
{ |
|
dist = (GetAbsOrigin() - m_lastBlockPos).Length(); |
|
// must have moved at least 10% of normal velocity over the blocking interval, or we're being blocked |
|
float minLength = GetAbsVelocity().Length() * TICK_INTERVAL * MIN_BLOCKED_TICKS * 0.10f; |
|
if ( dist < minLength ) |
|
{ |
|
// been stuck for more than one tick without moving much? |
|
// yes, disable collisions with the physics object most likely to be blocking us |
|
EntityPhysics_CreateSolver( this, pPhysicsBlocker, true, 4.0f ); |
|
} |
|
} |
|
// first time blocking or moved too far since last block, reset |
|
if ( dist > 1.0f || m_lastBlockTick < 0 ) |
|
{ |
|
m_lastBlockPos = GetAbsOrigin(); |
|
m_lastBlockTick = gpGlobals->tickcount; |
|
} |
|
} |
|
// unblockable shouldn't damage the player in this case |
|
if ( pOther->IsPlayer() ) |
|
return; |
|
} |
|
|
|
DevWarning( 2, "TRAIN(%s): Blocked by %s (dmg:%.2f)\n", GetDebugName(), pOther->GetClassname(), m_flBlockDamage ); |
|
if ( m_flBlockDamage <= 0 ) |
|
return; |
|
|
|
// we can't hurt this thing, so we're not concerned with it |
|
pOther->TakeDamage( CTakeDamageInfo( this, this, m_flBlockDamage, DMG_CRUSH ) ); |
|
} |
|
|
|
|
|
extern void FixupAngles( QAngle &v ); |
|
|
|
#define TRAIN_MAXSPEED 1000 // approx max speed for sound pitch calculation |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTrackTrain::SoundStop( void ) |
|
{ |
|
// if sound playing, stop it |
|
if ( m_bSoundPlaying ) |
|
{ |
|
if ( m_iszSoundMove != NULL_STRING ) |
|
{ |
|
StopSound( entindex(), CHAN_STATIC, STRING( m_iszSoundMove ) ); |
|
} |
|
|
|
if ( m_iszSoundStop != NULL_STRING ) |
|
{ |
|
CPASAttenuationFilter filter( this ); |
|
|
|
EmitSound_t ep; |
|
ep.m_nChannel = CHAN_ITEM; |
|
ep.m_pSoundName = STRING(m_iszSoundStop); |
|
ep.m_flVolume = m_flVolume; |
|
ep.m_SoundLevel = SNDLVL_NORM; |
|
|
|
EmitSound( filter, entindex(), ep ); |
|
} |
|
} |
|
|
|
m_bSoundPlaying = false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Update pitch based on speed, start sound if not playing. |
|
// NOTE: when train goes through transition, m_bSoundPlaying should become |
|
// false, which will cause the looped sound to restart. |
|
//----------------------------------------------------------------------------- |
|
void CFuncTrackTrain::SoundUpdate( void ) |
|
{ |
|
if ( ( !m_iszSoundMove ) && ( !m_iszSoundStart ) && ( !m_iszSoundMovePing )) |
|
{ |
|
return; |
|
} |
|
|
|
// In multiplayer, only update the sound once a second |
|
if ( g_pGameRules->IsMultiplayer() && m_bSoundPlaying ) |
|
{ |
|
if ( m_flNextMPSoundTime > gpGlobals->curtime ) |
|
return; |
|
|
|
m_flNextMPSoundTime = gpGlobals->curtime + 1.0; |
|
} |
|
|
|
float flSpeedRatio = 0; |
|
if ( HasSpawnFlags( SF_TRACKTRAIN_USE_MAXSPEED_FOR_PITCH ) ) |
|
{ |
|
flSpeedRatio = clamp( fabs( m_flSpeed ) / m_maxSpeed, 0, 1 ); |
|
} |
|
else |
|
{ |
|
flSpeedRatio = clamp( fabs( m_flSpeed ) / TRAIN_MAXSPEED, 0, 1 ); |
|
} |
|
|
|
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() |
|
{ |
|
return m_eOrientationType; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// 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 ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// 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 ); |
|
|
|
// |
|
// 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" ) |
|
&& !FClassnameIs( m_ppath, "env_portal_path_track" ) |
|
|
|
) |
|
{ |
|
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 ) |
|
{ |
|
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 ); |
|
|
|
SetSolid( HasSpawnFlags( SF_TRACKTRAIN_HL1TRAIN ) ? SOLID_BSP : SOLID_VPHYSICS ); |
|
//SetSolid( SOLID_VPHYSICS ); |
|
|
|
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(); |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// 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; |
|
} |
|
} |
|
|
|
|
|
// |
|
// 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 ); |
|
} |
|
}
|
|
|