/***
*
* Copyright ( c ) 1996 - 2002 , Valve LLC . All rights reserved .
*
* This product contains software technology licensed from Id
* Software , Inc . ( " Id Technology " ) . Id Technology ( c ) 1996 Id Software , Inc .
* All Rights Reserved .
*
* Use , distribution , and modification of this source code and / or resulting
* object code is restricted to non - commercial enhancements to products from
* Valve LLC . All other use , distribution , or modification is prohibited
* without written permission from Valve LLC .
*
* * * */
/*
= = = = = plats . cpp = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
spawn , think , and touch functions for trains , etc
*/
# include "extdll.h"
# include "util.h"
# include "cbase.h"
# include "trains.h"
# include "saverestore.h"
# include "movewith.h"
static void PlatSpawnInsideTrigger ( entvars_t * pevPlatform ) ;
// from mathlib.h
// BUG BUG This is declared in pm_math.cpp, Linux will spit about it
// No idea why windows does not pick that up, in fact
// MSVC will complain it's not delcared if you use extern...
#if 0
# ifdef _WIN32
int nanmask = 255 < < 23 ;
# else
extern int nanmask ;
# endif
# endif
# define nanmask (int)(255<<23)
# define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask)
static float Fix ( float angle )
{
if ( IS_NAN ( angle ) )
{
ALERT ( at_console , " NaN error during Fix! \n " ) ;
return angle ;
}
while ( angle < 0 )
angle + = 360 ;
while ( angle > 360 )
angle - = 360 ;
return angle ;
}
static void FixupAngles ( Vector & v )
{
v . x = Fix ( v . x ) ;
v . y = Fix ( v . y ) ;
v . z = Fix ( v . z ) ;
}
class CFuncTrain ;
//LRC - scripted_trainsequence
class CTrainSequence : public CBaseEntity
{
public :
void Use ( CBaseEntity * pActivator , CBaseEntity * pCaller , USE_TYPE useType , float value ) ;
void EndThink ( void ) ;
void TimeOutThink ( void ) ;
void KeyValue ( KeyValueData * pkvd ) ;
STATE GetState ( void ) { return ( m_pTrain | | m_pTrackTrain ) ? STATE_ON : STATE_OFF ; }
virtual int ObjectCaps ( void ) ;
void StopSequence ( void ) ;
void ArrivalNotify ( void ) ;
virtual int Save ( CSave & save ) ;
virtual int Restore ( CRestore & restore ) ;
static TYPEDESCRIPTION m_SaveData [ ] ;
string_t m_iszEntity ;
string_t m_iszDestination ;
string_t m_iszTerminate ;
int m_iDirection ;
int m_iPostDirection ;
float m_fDuration ;
// at any given time, at most one of these pointers will be set.
CFuncTrain * m_pTrain ;
CFuncTrackTrain * m_pTrackTrain ;
CBaseEntity * m_pDestination ;
} ;
# define SF_PLAT_TOGGLE 0x0001
class CBasePlatTrain : public CBaseToggle
{
public :
virtual int ObjectCaps ( void ) { return CBaseEntity : : ObjectCaps ( ) & ~ FCAP_ACROSS_TRANSITION ; }
void KeyValue ( KeyValueData * pkvd ) ;
void Precache ( void ) ;
// This is done to fix spawn flag collisions between this class and a derived class
virtual BOOL IsTogglePlat ( void ) { return ( pev - > spawnflags & SF_PLAT_TOGGLE ) ? TRUE : FALSE ; }
virtual int Save ( CSave & save ) ;
virtual int Restore ( CRestore & restore ) ;
static TYPEDESCRIPTION m_SaveData [ ] ;
BYTE m_bMoveSnd ; // sound a plat makes while moving
BYTE m_bStopSnd ; // sound a plat makes when it stops
float m_volume ; // Sound volume
} ;
TYPEDESCRIPTION CBasePlatTrain : : m_SaveData [ ] =
{
DEFINE_FIELD ( CBasePlatTrain , m_bMoveSnd , FIELD_CHARACTER ) ,
DEFINE_FIELD ( CBasePlatTrain , m_bStopSnd , FIELD_CHARACTER ) ,
DEFINE_FIELD ( CBasePlatTrain , m_volume , FIELD_FLOAT ) ,
} ;
IMPLEMENT_SAVERESTORE ( CBasePlatTrain , CBaseToggle )
void CBasePlatTrain : : KeyValue ( KeyValueData * pkvd )
{
if ( FStrEq ( pkvd - > szKeyName , " lip " ) )
{
m_flLip = atof ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else if ( FStrEq ( pkvd - > szKeyName , " wait " ) )
{
m_flWait = atof ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else if ( FStrEq ( pkvd - > szKeyName , " height " ) )
{
m_flHeight = atof ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else if ( FStrEq ( pkvd - > szKeyName , " rotation " ) )
{
m_vecFinalAngle . x = atof ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else if ( FStrEq ( pkvd - > szKeyName , " movesnd " ) )
{
m_bMoveSnd = atoi ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else if ( FStrEq ( pkvd - > szKeyName , " stopsnd " ) )
{
m_bStopSnd = atoi ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else if ( FStrEq ( pkvd - > szKeyName , " volume " ) )
{
m_volume = atof ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else
CBaseToggle : : KeyValue ( pkvd ) ;
}
# define noiseMoving noise
# define noiseArrived noise1
void CBasePlatTrain : : Precache ( void )
{
const char * pszSound ;
BOOL NullSound = FALSE ;
// set the plat's "in-motion" sound
switch ( m_bMoveSnd )
{
case 1 :
pszSound = " plats/bigmove1.wav " ;
break ;
case 2 :
pszSound = " plats/bigmove2.wav " ;
break ;
case 3 :
pszSound = " plats/elevmove1.wav " ;
break ;
case 4 :
pszSound = " plats/elevmove2.wav " ;
break ;
case 5 :
pszSound = " plats/elevmove3.wav " ;
break ;
case 6 :
pszSound = " plats/freightmove1.wav " ;
break ;
case 7 :
pszSound = " plats/freightmove2.wav " ;
break ;
case 8 :
pszSound = " plats/heavymove1.wav " ;
break ;
case 9 :
pszSound = " plats/rackmove1.wav " ;
break ;
case 10 :
pszSound = " plats/railmove1.wav " ;
break ;
case 11 :
pszSound = " plats/squeekmove1.wav " ;
break ;
case 12 :
pszSound = " plats/talkmove1.wav " ;
break ;
case 13 :
pszSound = " plats/talkmove2.wav " ;
break ;
case 0 :
default :
pszSound = " common/null.wav " ;
NullSound = TRUE ;
break ;
}
if ( ! NullSound )
PRECACHE_SOUND ( pszSound ) ;
pev - > noiseMoving = MAKE_STRING ( pszSound ) ;
NullSound = FALSE ;
// set the plat's 'reached destination' stop sound
switch ( m_bStopSnd )
{
case 1 :
pszSound = " plats/bigstop1.wav " ;
break ;
case 2 :
pszSound = " plats/bigstop2.wav " ;
break ;
case 3 :
pszSound = " plats/freightstop1.wav " ;
break ;
case 4 :
pszSound = " plats/heavystop2.wav " ;
break ;
case 5 :
pszSound = " plats/rackstop1.wav " ;
break ;
case 6 :
pszSound = " plats/railstop1.wav " ;
break ;
case 7 :
pszSound = " plats/squeekstop1.wav " ;
break ;
case 8 :
pszSound = " plats/talkstop1.wav " ;
break ;
case 0 :
default :
pszSound = " common/null.wav " ;
NullSound = TRUE ;
break ;
}
if ( ! NullSound )
PRECACHE_SOUND ( pszSound ) ;
pev - > noiseArrived = MAKE_STRING ( pszSound ) ;
}
//
//====================== PLAT code ====================================================
//
# define noiseMovement noise
# define noiseStopMoving noise1
class CFuncPlat : public CBasePlatTrain
{
public :
void Spawn ( void ) ;
void Precache ( void ) ;
void Setup ( void ) ;
virtual void Blocked ( CBaseEntity * pOther ) ;
void EXPORT PlatUse ( CBaseEntity * pActivator , CBaseEntity * pCaller , USE_TYPE useType , float value ) ;
void EXPORT CallGoUp ( void ) { GoUp ( ) ; }
void EXPORT CallGoDown ( void ) { GoDown ( ) ; }
void EXPORT CallHitTop ( void ) { HitTop ( ) ; }
void EXPORT CallHitBottom ( void ) { HitBottom ( ) ; }
virtual void GoUp ( void ) ;
virtual void GoDown ( void ) ;
virtual void HitTop ( void ) ;
virtual void HitBottom ( void ) ;
} ;
LINK_ENTITY_TO_CLASS ( func_plat , CFuncPlat )
// UNDONE: Need to save this!!! It needs class & linkage
class CPlatTrigger : public CBaseEntity
{
public :
virtual int ObjectCaps ( void ) { return ( CBaseEntity : : ObjectCaps ( ) & ~ FCAP_ACROSS_TRANSITION ) | FCAP_DONT_SAVE ; }
void SpawnInsideTrigger ( CFuncPlat * pPlatform ) ;
void Touch ( CBaseEntity * pOther ) ;
CFuncPlat * m_pPlatform ;
EHANDLE m_hPlatform ;
} ;
/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER
speed default 150
Plats are always drawn in the extended position , so they will light correctly .
If the plat is the target of another trigger or button , it will start out disabled in
the extended position until it is trigger , when it will lower and become a normal plat .
If the " height " key is set , that will determine the amount the plat moves , instead of
being implicitly determined by the model ' s height .
Set " sounds " to one of the following :
1 ) base fast
2 ) chain slow
*/
void CFuncPlat : : Setup ( void )
{
//pev->noiseMovement = MAKE_STRING( "plats/platmove1.wav" );
//pev->noiseStopMoving = MAKE_STRING( "plats/platstop1.wav" );
if ( m_flTLength = = 0 )
m_flTLength = 80 ;
if ( m_flTWidth = = 0 )
m_flTWidth = 10 ;
pev - > angles = g_vecZero ;
pev - > solid = SOLID_BSP ;
pev - > movetype = MOVETYPE_PUSH ;
UTIL_SetOrigin ( this , pev - > origin ) ; // set size and link into world
UTIL_SetSize ( pev , pev - > mins , pev - > maxs ) ;
SET_MODEL ( ENT ( pev ) , STRING ( pev - > model ) ) ;
// vecPosition1 is the top position, vecPosition2 is the bottom
if ( m_pMoveWith )
m_vecPosition1 = pev - > origin - m_pMoveWith - > pev - > origin ;
else
m_vecPosition1 = pev - > origin ;
m_vecPosition2 = m_vecPosition1 ;
if ( m_flHeight ! = 0 )
m_vecPosition2 . z = m_vecPosition2 . z - m_flHeight ;
else
m_vecPosition2 . z = m_vecPosition2 . z - pev - > size . z + 8 ;
if ( pev - > speed = = 0 )
pev - > speed = 150 ;
if ( m_volume = = 0 )
m_volume = 0.85 ;
}
void CFuncPlat : : Precache ( )
{
CBasePlatTrain : : Precache ( ) ;
//PRECACHE_SOUND( "plats/platmove1.wav" );
//PRECACHE_SOUND( "plats/platstop1.wav" );
if ( ! IsTogglePlat ( ) )
PlatSpawnInsideTrigger ( pev ) ; // the "start moving" trigger
}
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 ( ! FStringNull ( pev - > targetname ) )
{
if ( m_pMoveWith )
UTIL_AssignOrigin ( this , m_vecPosition1 + m_pMoveWith - > pev - > origin ) ;
else
UTIL_AssignOrigin ( this , m_vecPosition1 ) ;
m_toggle_state = TS_AT_TOP ;
SetUse ( & CFuncPlat : : PlatUse ) ;
}
else
{
if ( m_pMoveWith )
UTIL_AssignOrigin ( this , m_vecPosition2 + m_pMoveWith - > pev - > origin ) ;
else
UTIL_AssignOrigin ( this , m_vecPosition2 ) ;
m_toggle_state = TS_AT_BOTTOM ;
}
}
static void PlatSpawnInsideTrigger ( entvars_t * pevPlatform )
{
GetClassPtr ( ( CPlatTrigger * ) NULL ) - > SpawnInsideTrigger ( GetClassPtr ( ( CFuncPlat * ) pevPlatform ) ) ;
}
//
// Create a trigger entity for a platform.
//
void CPlatTrigger : : SpawnInsideTrigger ( CFuncPlat * pPlatform )
{
m_pPlatform = pPlatform ;
m_hPlatform = pPlatform ;
// Create trigger entity, "point" it at the owning platform, give it a touch method
pev - > solid = SOLID_TRIGGER ;
pev - > movetype = MOVETYPE_NONE ;
pev - > origin = pPlatform - > pev - > origin ;
// Establish the trigger field's size
Vector vecTMin = pPlatform - > pev - > mins + Vector ( 25 , 25 , 0 ) ;
Vector vecTMax = pPlatform - > pev - > maxs + Vector ( 25 , 25 , 8 ) ;
vecTMin . z = vecTMax . z - ( pPlatform - > m_vecPosition1 . z - pPlatform - > m_vecPosition2 . z + 8 ) ;
if ( pPlatform - > pev - > size . x < = 50 )
{
vecTMin . x = ( pPlatform - > pev - > mins . x + pPlatform - > pev - > maxs . x ) / 2 ;
vecTMax . x = vecTMin . x + 1 ;
}
if ( pPlatform - > pev - > size . y < = 50 )
{
vecTMin . y = ( pPlatform - > pev - > mins . y + pPlatform - > pev - > maxs . y ) / 2 ;
vecTMax . y = vecTMin . y + 1 ;
}
UTIL_SetSize ( pev , vecTMin , vecTMax ) ;
}
//
// When the platform's trigger field is touched, the platform ???
//
void CPlatTrigger : : Touch ( CBaseEntity * pOther )
{
// Ignore touches by non-players
entvars_t * pevToucher = pOther - > pev ;
if ( ! FClassnameIs ( pevToucher , " player " ) )
return ;
CFuncPlat * pPlatform = ( CFuncPlat * ) ( CBaseEntity * ) m_hPlatform ;
if ( ! pPlatform )
{
// The target platform has been removed, remove myself as well. - Solokiller
UTIL_Remove ( this ) ;
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 - > SetNextThink ( 1 ) ; // delay going down
}
//
// Used by SUB_UseTargets, when a platform is the target of a button.
// Start bringing platform down.
//
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 )
{
SetNextThink ( 0.01 ) ;
SetThink ( & CFuncPlat : : CallGoDown ) ;
}
else if ( m_toggle_state = = TS_AT_BOTTOM )
{
SetNextThink ( 0.01 ) ;
SetThink ( & CFuncPlat : : CallGoUp ) ;
}
}
else
{
SetUse ( NULL ) ;
if ( m_toggle_state = = TS_AT_TOP )
{
SetNextThink ( 0.01 ) ;
SetThink ( & CFuncPlat : : CallGoDown ) ;
}
}
}
//
// Platform is at top, now starts moving down.
//
void CFuncPlat : : GoDown ( void )
{
if ( pev - > noiseMovement )
EMIT_SOUND ( ENT ( pev ) , CHAN_STATIC , STRING ( pev - > noiseMovement ) , m_volume , ATTN_NORM ) ;
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 , pev - > speed ) ;
}
//
// Platform has hit bottom. Stops and waits forever.
//
void CFuncPlat : : HitBottom ( void )
{
if ( pev - > noiseMovement )
STOP_SOUND ( ENT ( pev ) , CHAN_STATIC , STRING ( pev - > noiseMovement ) ) ;
if ( pev - > noiseStopMoving )
EMIT_SOUND ( ENT ( pev ) , CHAN_WEAPON , STRING ( pev - > noiseStopMoving ) , m_volume , ATTN_NORM ) ;
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 )
{
if ( pev - > noiseMovement )
EMIT_SOUND ( ENT ( pev ) , CHAN_STATIC , STRING ( pev - > noiseMovement ) , m_volume , ATTN_NORM ) ;
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 , pev - > speed ) ;
}
//
// Platform has hit top. Pauses, then starts back down again.
//
void CFuncPlat : : HitTop ( void )
{
if ( pev - > noiseMovement )
STOP_SOUND ( ENT ( pev ) , CHAN_STATIC , STRING ( pev - > noiseMovement ) ) ;
if ( pev - > noiseStopMoving )
EMIT_SOUND ( ENT ( pev ) , CHAN_WEAPON , STRING ( pev - > noiseStopMoving ) , m_volume , ATTN_NORM ) ;
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.
SetThink ( & CFuncPlat : : CallGoDown ) ;
SetNextThink ( 3 ) ;
}
}
void CFuncPlat : : Blocked ( CBaseEntity * pOther )
{
ALERT ( at_aiconsole , " %s Blocked by %s \n " , STRING ( pev - > classname ) , STRING ( pOther - > pev - > classname ) ) ;
// Hurt the blocker a little
pOther - > TakeDamage ( pev , pev , 1 , DMG_CRUSH ) ;
if ( pev - > noiseMovement )
STOP_SOUND ( ENT ( pev ) , CHAN_STATIC , STRING ( pev - > noiseMovement ) ) ;
// 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 )
{
SetNextThink ( 0 ) ;
SetThink ( & CFuncPlat : : GoDown ) ;
}
else if ( m_toggle_state = = TS_GOING_DOWN )
{
SetNextThink ( 0 ) ;
SetThink ( & CFuncPlat : : GoUp ) ;
}
}
class CFuncPlatRot : public CFuncPlat
{
public :
void Spawn ( void ) ;
void SetupRotation ( void ) ;
virtual void KeyValue ( KeyValueData * pkvd ) ;
virtual void GoUp ( void ) ;
virtual void GoDown ( void ) ;
virtual void HitTop ( void ) ;
virtual void HitBottom ( void ) ;
void RotMove ( Vector & destAngle , float time ) ;
virtual int Save ( CSave & save ) ;
virtual int Restore ( CRestore & restore ) ;
static TYPEDESCRIPTION m_SaveData [ ] ;
Vector m_end , m_start ;
} ;
LINK_ENTITY_TO_CLASS ( func_platrot , CFuncPlatRot )
TYPEDESCRIPTION CFuncPlatRot : : m_SaveData [ ] =
{
DEFINE_FIELD ( CFuncPlatRot , m_end , FIELD_VECTOR ) ,
DEFINE_FIELD ( CFuncPlatRot , m_start , FIELD_VECTOR ) ,
} ;
IMPLEMENT_SAVERESTORE ( CFuncPlatRot , CFuncPlat ) ;
void CFuncPlatRot : : KeyValue ( KeyValueData * pkvd )
{
if ( FStrEq ( pkvd - > szKeyName , " axes " ) )
{
UTIL_StringToVector ( ( float * ) ( pev - > movedir ) , pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else
CFuncPlat : : KeyValue ( pkvd ) ;
}
void CFuncPlatRot : : SetupRotation ( void )
{
if ( m_vecFinalAngle . x ! = 0 ) // This plat rotates too!
{
CBaseToggle : : AxisDir ( pev ) ;
m_start = pev - > angles ;
m_end = pev - > angles + pev - > movedir * m_vecFinalAngle . x ;
}
else
{
m_start = g_vecZero ;
m_end = g_vecZero ;
}
if ( ! FStringNull ( pev - > targetname ) ) // Start at top
{
UTIL_SetAngles ( this , m_end ) ;
//pev->angles = m_end;
}
}
void CFuncPlatRot : : Spawn ( void )
{
CFuncPlat : : Spawn ( ) ;
SetupRotation ( ) ;
}
void CFuncPlatRot : : GoDown ( void )
{
CFuncPlat : : GoDown ( ) ;
RotMove ( m_start , m_fNextThink - pev - > ltime ) ;
}
//
// Platform has hit bottom. Stops and waits forever.
//
void CFuncPlatRot : : HitBottom ( void )
{
CFuncPlat : : HitBottom ( ) ;
UTIL_SetAvelocity ( this , g_vecZero ) ;
//pev->avelocity = g_vecZero;
UTIL_SetAngles ( this , m_start ) ;
//pev->angles = m_start;
}
//
// Platform is at bottom, now starts moving up
//
void CFuncPlatRot : : GoUp ( void )
{
CFuncPlat : : GoUp ( ) ;
RotMove ( m_end , m_fNextThink - pev - > ltime ) ;
}
//
// Platform has hit top. Pauses, then starts back down again.
//
void CFuncPlatRot : : HitTop ( void )
{
CFuncPlat : : HitTop ( ) ;
UTIL_SetAvelocity ( this , g_vecZero ) ;
//pev->avelocity = g_vecZero;
UTIL_SetAngles ( this , m_end ) ;
//pev->angles = m_end;
}
void CFuncPlatRot : : RotMove ( Vector & destAngle , float time )
{
// set destdelta to the vector needed to move
Vector vecDestDelta = destAngle - pev - > angles ;
// Travel time is so short, we're practically there already; make it so.
if ( time > = 0.1 )
{
UTIL_SetAvelocity ( this , vecDestDelta / time ) ;
//pev->avelocity = vecDestDelta / time;
}
else
{
UTIL_SetAvelocity ( this , vecDestDelta ) ;
//pev->avelocity = vecDestDelta;
SetNextThink ( 1 ) ;
}
}
//
//====================== TRAIN code ==================================================
//
//the others are defined in const.h
// SF_TRAIN_WAIT_RETRIGGER 1
# define SF_TRAIN_SETORIGIN 2
// SF_TRAIN_START_ON 4 // Train is initially moving
// SF_TRAIN_PASSABLE 8 // Train is not solid -- used to make water trains
# define SF_TRAIN_REVERSE 0x800000
class CFuncTrain : public CBasePlatTrain
{
public :
void Spawn ( void ) ;
void Precache ( void ) ;
void PostSpawn ( void ) ;
void OverrideReset ( void ) ;
void Blocked ( CBaseEntity * pOther ) ;
void Use ( CBaseEntity * pActivator , CBaseEntity * pCaller , USE_TYPE useType , float value ) ;
void KeyValue ( KeyValueData * pkvd ) ;
//LRC
void StartSequence ( CTrainSequence * pSequence ) ;
void StopSequence ( ) ;
CTrainSequence * m_pSequence ;
void EXPORT Wait ( void ) ;
void EXPORT Next ( void ) ;
void EXPORT ThinkDoNext ( void ) ;
void EXPORT SoundSetup ( void ) ;
STATE GetState ( void ) { return m_iState ; }
virtual void ThinkCorrection ( void ) ;
virtual int Save ( CSave & save ) ;
virtual int Restore ( CRestore & restore ) ;
static TYPEDESCRIPTION m_SaveData [ ] ;
entvars_t * m_pevCurrentTarget ;
int m_sounds ;
//LRC - now part of CBaseEntity: BOOL m_activated;
STATE m_iState ;
float m_fStoredThink ;
Vector m_vecAvelocity ;
} ;
LINK_ENTITY_TO_CLASS ( func_train , CFuncTrain )
TYPEDESCRIPTION CFuncTrain : : m_SaveData [ ] =
{
DEFINE_FIELD ( CFuncTrain , m_sounds , FIELD_INTEGER ) ,
DEFINE_FIELD ( CFuncTrain , m_pevCurrentTarget , FIELD_EVARS ) ,
//LRC - now part of CBaseEntity: DEFINE_FIELD( CFuncTrain, m_activated, FIELD_BOOLEAN ),
DEFINE_FIELD ( CFuncTrain , m_iState , FIELD_INTEGER ) ,
DEFINE_FIELD ( CFuncTrain , m_fStoredThink , FIELD_TIME ) ,
DEFINE_FIELD ( CFuncTrain , m_pSequence , FIELD_CLASSPTR ) , //LRC
DEFINE_FIELD ( CFuncTrain , m_vecAvelocity , FIELD_VECTOR ) , //LRC
} ;
IMPLEMENT_SAVERESTORE ( CFuncTrain , CBasePlatTrain )
void CFuncTrain : : KeyValue ( KeyValueData * pkvd )
{
if ( FStrEq ( pkvd - > szKeyName , " sounds " ) )
{
m_sounds = atoi ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else
CBasePlatTrain : : KeyValue ( pkvd ) ;
}
void CFuncTrain : : Blocked ( CBaseEntity * pOther )
{
// Keep "movewith" entities in line
UTIL_AssignOrigin ( this , pev - > origin ) ;
if ( gpGlobals - > time < m_flActivateFinished )
return ;
m_flActivateFinished = gpGlobals - > time + 0.5 ;
if ( pev - > dmg )
pOther - > TakeDamage ( pev , pev , pev - > dmg , DMG_CRUSH ) ;
}
void CFuncTrain : : Use ( CBaseEntity * pActivator , CBaseEntity * pCaller , USE_TYPE useType , float value )
{
if ( ShouldToggle ( useType ) )
{
if ( pev - > spawnflags & SF_TRAIN_WAIT_RETRIGGER )
{
// Move toward my target
// ALERT(at_console, "Unset Retrigger (use)\n");
pev - > spawnflags & = ~ SF_TRAIN_WAIT_RETRIGGER ;
Next ( ) ;
}
else
{
// ALERT(at_console, "Set Retrigger (use)\n");
pev - > spawnflags | = SF_TRAIN_WAIT_RETRIGGER ;
// Pop back to last target if it's available
if ( pev - > enemy )
pev - > target = pev - > enemy - > v . targetname ;
DontThink ( ) ;
UTIL_SetVelocity ( this , g_vecZero ) ;
UTIL_SetAvelocity ( this , g_vecZero ) ;
m_iState = STATE_OFF ;
// pev->velocity = g_vecZero;
if ( pev - > noiseMovement )
STOP_SOUND ( edict ( ) , CHAN_STATIC , STRING ( pev - > noiseMovement ) ) ;
if ( pev - > noiseStopMoving )
EMIT_SOUND ( ENT ( pev ) , CHAN_VOICE , STRING ( pev - > noiseStopMoving ) , m_volume , ATTN_NORM ) ;
}
}
}
void CFuncTrain : : Wait ( void )
{
// ALERT(at_console, "Wait t %s, m %s\n", STRING(pev->target), STRING(pev->message));
if ( m_pSequence )
m_pSequence - > ArrivalNotify ( ) ;
// Fire the pass target if there is one
if ( m_pevCurrentTarget - > message )
{
FireTargets ( STRING ( m_pevCurrentTarget - > message ) , this , this , USE_TOGGLE , 0 ) ;
if ( FBitSet ( m_pevCurrentTarget - > spawnflags , SF_CORNER_FIREONCE ) )
m_pevCurrentTarget - > message = 0 ;
}
// need pointer to LAST target.
if ( FBitSet ( m_pevCurrentTarget - > spawnflags , SF_TRAIN_WAIT_RETRIGGER ) | | ( pev - > spawnflags & SF_TRAIN_WAIT_RETRIGGER ) )
{
// if (FBitSet (m_pevCurrentTarget->spawnflags , SF_TRAIN_WAIT_RETRIGGER ))
// ALERT(at_console, "Wait: wait for retrigger from path %s\n", STRING(m_pevCurrentTarget->targetname));
// else
// ALERT(at_console, "Wait: wait for retrigger from train\n");
pev - > spawnflags | = SF_TRAIN_WAIT_RETRIGGER ;
m_iState = STATE_OFF ;
// clear the sound channel.
if ( pev - > noiseMovement )
STOP_SOUND ( edict ( ) , CHAN_STATIC , STRING ( pev - > noiseMovement ) ) ;
if ( pev - > noiseStopMoving )
EMIT_SOUND ( ENT ( pev ) , CHAN_VOICE , STRING ( pev - > noiseStopMoving ) , m_volume , ATTN_NORM ) ;
UTIL_SetVelocity ( this , g_vecZero ) ;
UTIL_SetAvelocity ( this , g_vecZero ) ;
DontThink ( ) ;
return ;
}
// ALERT( at_console, "%f\n", m_flWait );
if ( m_flWait ! = 0 )
{ // -1 wait will wait forever!
m_iState = STATE_OFF ;
UTIL_SetAvelocity ( this , g_vecZero ) ;
UTIL_SetVelocity ( this , g_vecZero ) ;
SetNextThink ( m_flWait ) ;
if ( pev - > noiseMovement )
STOP_SOUND ( edict ( ) , CHAN_STATIC , STRING ( pev - > noiseMovement ) ) ;
if ( pev - > noiseStopMoving )
EMIT_SOUND ( ENT ( pev ) , CHAN_VOICE , STRING ( pev - > noiseStopMoving ) , m_volume , ATTN_NORM ) ;
SetThink ( & CFuncTrain : : Next ) ;
// ALERT(at_console, "Wait: doing Next in %f\n", m_flWait);
}
else
{
// ALERT(at_console, "Wait: doing Next now\n");
Next ( ) ; // do it right now!
}
}
//
// Train next - path corner needs to change to next target
//
void CFuncTrain : : Next ( void )
{
CBaseEntity * pTarg ;
// now find our next target
pTarg = GetNextTarget ( ) ;
if ( ! pTarg )
{
// no destination, just come to a halt
m_iState = STATE_OFF ;
UTIL_SetVelocity ( this , g_vecZero ) ;
UTIL_SetAvelocity ( this , g_vecZero ) ;
if ( pev - > noiseMovement )
STOP_SOUND ( edict ( ) , CHAN_STATIC , STRING ( pev - > noiseMovement ) ) ;
// Play stop sound
if ( pev - > noiseStopMoving )
EMIT_SOUND ( ENT ( pev ) , CHAN_VOICE , STRING ( pev - > noiseStopMoving ) , m_volume , ATTN_NORM ) ;
return ;
}
// Save last target in case we need to find it again
pev - > message = pev - > target ;
// if (m_pevCurrentTarget)
// ALERT(at_console, "Next, pTarg %s, pevTarg %s\n", STRING(pTarg->pev->targetname), STRING(m_pevCurrentTarget->targetname));
// else
// ALERT(at_console, "Next, pTarg %s, pevTarg null\n", STRING(pTarg->pev->targetname));
if ( pev - > spawnflags & SF_TRAIN_REVERSE & & m_pSequence )
{
//LRC - search backwards
CBaseEntity * pSearch = m_pSequence - > m_pDestination ;
while ( pSearch )
{
if ( FStrEq ( STRING ( pSearch - > pev - > target ) , STRING ( pev - > target ) ) )
{
// pSearch leads to the current corner, so it's the next thing we're moving to.
pev - > target = pSearch - > pev - > targetname ;
// ALERT(at_console, "Next, pSearch %s\n", STRING(pSearch->pev->targetname));
break ;
}
pSearch = pSearch - > GetNextTarget ( ) ;
}
}
else
{
pev - > target = pTarg - > pev - > target ;
}
// ALERT(at_console, "Next, new pevtarget %s, new message %s\n", STRING(pev->target), STRING(pev->message));
m_flWait = pTarg - > GetDelay ( ) ;
// don't copy speed from target if it is 0 (uninitialized)
if ( m_pevCurrentTarget )
{
if ( m_pevCurrentTarget - > speed ! = 0 )
{
switch ( ( int ) ( m_pevCurrentTarget - > armortype ) )
{
case PATHSPEED_SET :
pev - > speed = m_pevCurrentTarget - > speed ;
ALERT ( at_aiconsole , " Train %s speed set to %4.2f \n " , STRING ( pev - > targetname ) , pev - > speed ) ;
break ;
case PATHSPEED_ACCEL :
pev - > speed + = m_pevCurrentTarget - > speed ;
ALERT ( at_aiconsole , " Train %s speed accel to %4.2f \n " , STRING ( pev - > targetname ) , pev - > speed ) ;
break ;
case PATHSPEED_TIME :
float distance ;
if ( pev - > spawnflags & SF_TRAIN_SETORIGIN )
distance = ( pev - > origin - pTarg - > pev - > origin ) . Length ( ) ;
else
distance = ( pev - > origin - ( pTarg - > pev - > origin - ( pev - > mins + pev - > maxs ) * 0.5 ) ) . Length ( ) ;
pev - > speed = distance / m_pevCurrentTarget - > speed ;
ALERT ( at_aiconsole , " Train %s speed to %4.2f (timed) \n " , STRING ( pev - > targetname ) , pev - > speed ) ;
break ;
}
}
if ( m_pevCurrentTarget - > spawnflags & SF_CORNER_AVELOCITY )
{
m_vecAvelocity = pTarg - > pev - > avelocity ;
UTIL_SetAvelocity ( this , m_vecAvelocity ) ;
//pev->avelocity = pTarg->pev->avelocity; //LRC
}
if ( m_pevCurrentTarget - > armorvalue )
{
UTIL_SetAngles ( this , m_pevCurrentTarget - > angles ) ;
//pev->angles = m_pevCurrentTarget->angles; //LRC - if we just passed a "turn to face" corner, set angle exactly.
}
}
m_pevCurrentTarget = pTarg - > pev ; // keep track of this since path corners change our target for us.
pev - > enemy = pTarg - > edict ( ) ; //hack
if ( FBitSet ( pTarg - > pev - > spawnflags , SF_CORNER_TELEPORT ) ) //LRC - cosmetic change to use pTarg
{
// Path corner has indicated a teleport to the next corner.
SetBits ( pev - > effects , EF_NOINTERP ) ;
if ( m_pMoveWith )
{
if ( pev - > spawnflags & SF_TRAIN_SETORIGIN )
UTIL_AssignOrigin ( this , pTarg - > pev - > origin - m_pMoveWith - > pev - > origin ) ;
else
UTIL_AssignOrigin ( this , pTarg - > pev - > origin - ( pev - > mins + pev - > maxs ) * 0.5 - m_pMoveWith - > pev - > origin ) ;
}
else
{
if ( pev - > spawnflags & SF_TRAIN_SETORIGIN )
UTIL_AssignOrigin ( this , pTarg - > pev - > origin ) ;
else
UTIL_AssignOrigin ( this , pTarg - > pev - > origin - ( pev - > mins + pev - > maxs ) * 0.5 ) ;
}
if ( pTarg - > pev - > armorvalue ) //LRC - "teleport and turn to face" means you set an angle as you teleport.
{
UTIL_SetAngles ( this , pTarg - > pev - > angles ) ;
//pev->angles = pTarg->pev->angles;
}
Wait ( ) ; // Get on with doing the next path corner.
}
else
{
// Normal linear move.
// CHANGED this from CHAN_VOICE to CHAN_STATIC around OEM beta time because trains should
// use CHAN_STATIC for their movement sounds to prevent sound field problems.
// this is not a hack or temporary fix, this is how things should be. (sjb).
if ( m_iState = = STATE_OFF ) //LRC - don't restart the sound every time we hit a path_corner, it sounds weird
{
if ( pev - > noiseMovement )
STOP_SOUND ( edict ( ) , CHAN_STATIC , STRING ( pev - > noiseMovement ) ) ;
if ( pev - > noiseMovement )
EMIT_SOUND ( ENT ( pev ) , CHAN_STATIC , STRING ( pev - > noiseMovement ) , m_volume , ATTN_NORM ) ;
}
ClearBits ( pev - > effects , EF_NOINTERP ) ;
SetMoveDone ( & CFuncTrain : : Wait ) ;
if ( pTarg - > pev - > armorvalue ) //LRC - "turn to face" the next corner
{
Vector vTemp = pev - > angles ;
FixupAngles ( vTemp ) ;
UTIL_SetAngles ( this , vTemp ) ;
Vector oDelta = pTarg - > pev - > origin - pev - > origin ;
Vector aDelta = pTarg - > pev - > angles - pev - > angles ;
float timeTaken = oDelta . Length ( ) / pev - > speed ;
m_vecAvelocity = aDelta / timeTaken ;
//pev->avelocity = aDelta / timeTaken;
}
UTIL_SetAvelocity ( this , m_vecAvelocity ) ;
m_iState = STATE_ON ;
if ( m_pMoveWith )
{
if ( pev - > spawnflags & SF_TRAIN_SETORIGIN )
LinearMove ( pTarg - > pev - > origin - m_pMoveWith - > pev - > origin , pev - > speed ) ;
else
LinearMove ( pTarg - > pev - > origin - ( pev - > mins + pev - > maxs ) * 0.5 - m_pMoveWith - > pev - > origin , pev - > speed ) ;
}
else
{
if ( pev - > spawnflags & SF_TRAIN_SETORIGIN )
LinearMove ( pTarg - > pev - > origin , pev - > speed ) ;
else
LinearMove ( pTarg - > pev - > origin - ( pev - > mins + pev - > maxs ) * 0.5 , pev - > speed ) ;
}
// ALERT(at_console, "Next: LMove done\n");
// ALERT(at_console, "Next ends, nextthink %f, flags %f\n", pev->nextthink, m_iLFlags);
}
}
//LRC- called by Activate. (but not when a game is loaded.)
void CFuncTrain : : PostSpawn ( void )
{
CBaseEntity * pTarget = UTIL_FindEntityByTargetname ( NULL , STRING ( pev - > target ) ) ;
entvars_t * pevTarg ;
m_iState = STATE_OFF ;
if ( pTarget )
{
pevTarg = pTarget - > pev ;
}
else
{
ALERT ( at_console , " Missing train target \" %s \" \n " , STRING ( pev - > target ) ) ;
return ;
}
pev - > message = pevTarg - > targetname ; //LRC - record the old target so that we can find it again
pev - > target = pevTarg - > target ;
m_pevCurrentTarget = pevTarg ; // keep track of this since path corners change our target for us.
if ( pev - > avelocity ! = g_vecZero )
{
m_vecAvelocity = pev - > avelocity ;
UTIL_SetAvelocity ( this , g_vecZero ) ;
}
if ( pev - > spawnflags & SF_TRAIN_SETORIGIN )
{
m_vecSpawnOffset = m_vecSpawnOffset + pevTarg - > origin - pev - > origin ;
if ( m_pMoveWith )
UTIL_AssignOrigin ( this , pevTarg - > origin - m_pMoveWith - > pev - > origin ) ;
else
UTIL_AssignOrigin ( this , pevTarg - > origin ) ;
}
else
{
m_vecSpawnOffset = m_vecSpawnOffset + ( pevTarg - > origin - ( pev - > mins + pev - > maxs ) * 0.5 ) - pev - > origin ;
if ( m_pMoveWith )
UTIL_AssignOrigin ( this , pevTarg - > origin - ( pev - > mins + pev - > maxs ) * 0.5 - m_pMoveWith - > pev - > origin ) ;
else
UTIL_AssignOrigin ( this , pevTarg - > origin - ( pev - > mins + pev - > maxs ) * 0.5 ) ;
}
if ( FStringNull ( pev - > targetname ) | | pev - > spawnflags & SF_TRAIN_START_ON )
{ // not triggered, so start immediately
SetNextThink ( 1.5 ) ;
// SetThink( Next );
SetThink ( & CFuncTrain : : ThinkDoNext ) ;
}
else
{
// ALERT(at_console, "Set Retrigger (postspawn)\n");
pev - > spawnflags | = SF_TRAIN_WAIT_RETRIGGER ;
}
// ALERT(at_console, "func_train postspawn: origin %f %f %f\n", pev->origin.x, pev->origin.y, pev->origin.z);
}
void CFuncTrain : : ThinkDoNext ( void )
{
SetNextThink ( 0.1 ) ;
// ALERT(at_console, "TDN ");
if ( gpGlobals - > time ! = 1.0 ) // only go on if the game has properly started yet
SetThink ( & CFuncTrain : : Next ) ;
}
//LRC
void CFuncTrain : : StartSequence ( CTrainSequence * pSequence )
{
m_pSequence = pSequence ;
// ALERT(at_console, "Unset Retrigger (startsequence)\n");
pev - > spawnflags & = ~ SF_TRAIN_WAIT_RETRIGGER ;
// m_iState = STATE_ON;
//...
}
//LRC
void CFuncTrain : : StopSequence ( )
{
m_pSequence = NULL ;
// pev->spawnflags &= ~SF_TRAIN_WAIT_RETRIGGER;
pev - > spawnflags & = ~ SF_TRAIN_REVERSE ;
Use ( this , this , USE_OFF , 0 ) ;
//...
}
/*QUAKED func_train (0 .5 .8) ?
Trains are moving platforms that players can ride .
The targets origin specifies the min point of the train at each corner .
The train spawns at the first target it is pointing at .
If the train is the target of a button or trigger , it will not begin moving until activated .
speed default 100
dmg default 2
sounds
1 ) ratchet metal
*/
void CFuncTrain : : Spawn ( void )
{
Precache ( ) ;
if ( pev - > speed = = 0 )
pev - > speed = 100 ;
// if (!(pev->origin == g_vecZero))
// {
// pev->spawnflags |= SF_TRAIN_SETORIGIN;
// m_vecSpawnOffset = pev->origin;
// }
if ( FStringNull ( pev - > target ) )
ALERT ( at_console , " func_train \" %s \" has no target \n " , STRING ( pev - > targetname ) ) ;
if ( pev - > dmg = = 0 )
pev - > dmg = 2 ;
else if ( pev - > dmg = = - 1 ) //LRC- a train that doesn't crush people!
pev - > dmg = 0 ;
pev - > movetype = MOVETYPE_PUSH ;
if ( FBitSet ( pev - > spawnflags , SF_TRACKTRAIN_PASSABLE ) )
pev - > solid = SOLID_NOT ;
else
pev - > solid = SOLID_BSP ;
SET_MODEL ( ENT ( pev ) , STRING ( pev - > model ) ) ;
UTIL_SetSize ( pev , pev - > mins , pev - > maxs ) ;
UTIL_SetOrigin ( this , pev - > origin ) ;
m_iState = STATE_OFF ;
if ( m_volume = = 0 )
m_volume = 0.85 ;
}
//LRC - making movement sounds which continue after a game is loaded.
void CFuncTrain : : SoundSetup ( void )
{
EMIT_SOUND ( ENT ( pev ) , CHAN_STATIC , STRING ( pev - > noiseMovement ) , m_volume , ATTN_NORM ) ;
SetNextThink ( m_fStoredThink - pev - > ltime ) ;
// ALERT(at_console, "SoundSetup: mfNT %f, pevNT %f, stored was %f, time %f", m_fNextThink, pev->nextthink, m_fStoredThink, pev->ltime );
m_fStoredThink = 0 ;
SetThink ( & CFuncTrain : : LinearMoveDone ) ;
}
//LRC
void CFuncTrain : : ThinkCorrection ( void )
{
if ( m_fStoredThink & & pev - > nextthink ! = m_fPevNextThink )
{
// ALERT(at_console, "StoredThink Correction for train \"%s\", %f -> %f\n", STRING(pev->targetname), m_fStoredThink, m_fStoredThink + pev->nextthink - m_fPevNextThink);
m_fStoredThink + = pev - > nextthink - m_fPevNextThink ;
}
CBasePlatTrain : : ThinkCorrection ( ) ;
}
void CFuncTrain : : Precache ( void )
{
CBasePlatTrain : : Precache ( ) ;
//LRC - continue the movement sound after loading a game
if ( m_iState = = STATE_ON & & pev - > noiseMovement )
{
// we can't set up SFX during precache, so get a think to do it.
// Unfortunately, since we're moving, we must be already thinking.
// So we store the current think time, and will restore it after SFX are done.
if ( ! m_fStoredThink )
m_fStoredThink = m_fNextThink ;
SetNextThink ( 0.1 ) ;
// ALERT(at_console, "preparing SoundSetup: stored %f, mfNT %f, pevNT %f, ltime %f", m_fStoredThink, m_fNextThink, pev->nextthink, pev->ltime);
SetThink ( & CFuncTrain : : SoundSetup ) ;
}
#if 0 // obsolete
// otherwise use preset sound
switch ( m_sounds )
{
case 0 :
pev - > noise = 0 ;
pev - > noise1 = 0 ;
break ;
case 1 :
PRECACHE_SOUND ( " plats/train2.wav " ) ;
PRECACHE_SOUND ( " plats/train1.wav " ) ;
pev - > noise = MAKE_STRING ( " plats/train2.wav " ) ;
pev - > noise1 = MAKE_STRING ( " plats/train1.wav " ) ;
break ;
case 2 :
PRECACHE_SOUND ( " plats/platmove1.wav " ) ;
PRECACHE_SOUND ( " plats/platstop1.wav " ) ;
pev - > noise = MAKE_STRING ( " plats/platstop1.wav " ) ;
pev - > noise1 = MAKE_STRING ( " plats/platmove1.wav " ) ;
break ;
}
# endif
}
void CFuncTrain : : OverrideReset ( void )
{
CBaseEntity * pTarg ;
// Are we moving?
if ( m_iState = = STATE_ON ) //pev->velocity != g_vecZero && pev->nextthink != 0 )
{
pev - > target = pev - > message ;
// now find our next target
pTarg = GetNextTarget ( ) ;
if ( ! pTarg )
{
DontThink ( ) ;
UTIL_SetVelocity ( this , g_vecZero ) ;
m_iState = STATE_OFF ;
}
else // Keep moving for 0.1 secs, then find path_corner again and restart
{
SetThink ( & CFuncTrain : : Next ) ;
SetNextThink ( 0.1 ) ;
}
}
}
// ---------------------------------------------------------------------
//
// Track Train
//
// ---------------------------------------------------------------------
void CFuncTrackTrain : : Spawn ( void )
{
if ( pev - > speed = = 0 )
m_speed = 100 ;
else
m_speed = pev - > speed ;
pev - > speed = 0 ;
pev - > velocity = g_vecZero ; // why do they set this stuff? --LRC
m_vecBaseAvel = pev - > avelocity ; //LRC - save it for later
pev - > avelocity = g_vecZero ;
pev - > impulse = m_speed ;
m_dir = 1 ;
if ( FStringNull ( pev - > target ) )
{
if ( FStringNull ( pev - > targetname ) )
ALERT ( at_console , " func_tracktrain with no target \n " ) ;
else
ALERT ( at_console , " func_tracktrain %s has no target \n " , STRING ( pev - > targetname ) ) ;
}
if ( pev - > spawnflags & SF_TRACKTRAIN_PASSABLE )
pev - > solid = SOLID_NOT ;
else
pev - > solid = SOLID_BSP ;
pev - > movetype = MOVETYPE_PUSH ;
SET_MODEL ( ENT ( pev ) , STRING ( pev - > model ) ) ;
UTIL_SetSize ( pev , pev - > mins , pev - > maxs ) ;
UTIL_SetOrigin ( this , pev - > origin ) ;
// ALERT(at_console, "SpawnOrigin %f %f %f\n", pev->origin.x, pev->origin.y, pev->origin.z);
// Cache off placed origin for train controls
pev - > oldorigin = pev - > origin ;
m_controlMins = pev - > mins ;
m_controlMaxs = pev - > maxs ;
m_controlMaxs . z + = 72 ;
// start trains on the next frame, to make sure their targets have had
// a chance to spawn/activate
NextThink ( 0.1 , FALSE ) ;
SetThink ( & CFuncTrackTrain : : Find ) ;
Precache ( ) ;
}
void CFuncTrackTrain : : Precache ( void )
{
if ( m_flVolume = = 0.0 )
m_flVolume = 1.0 ;
switch ( m_sounds )
{
default :
//pev->noise = 0; LRC - allow custom sounds to be set in worldcraft.
break ;
case 1 : pev - > noise = MAKE_STRING ( " plats/ttrain1.wav " ) ; break ;
case 2 : pev - > noise = MAKE_STRING ( " plats/ttrain2.wav " ) ; break ;
case 3 : pev - > noise = MAKE_STRING ( " plats/ttrain3.wav " ) ; break ;
case 4 : pev - > noise = MAKE_STRING ( " plats/ttrain4.wav " ) ; break ;
case 5 : pev - > noise = MAKE_STRING ( " plats/ttrain6.wav " ) ; break ;
case 6 : pev - > noise = MAKE_STRING ( " plats/ttrain7.wav " ) ; break ;
}
if ( FStringNull ( pev - > noise1 ) )
pev - > noise1 = MAKE_STRING ( " plats/ttrain_brake1.wav " ) ;
if ( FStringNull ( pev - > noise2 ) )
pev - > noise2 = MAKE_STRING ( " plats/ttrain_start1.wav " ) ;
if ( pev - > noise )
PRECACHE_SOUND ( STRING ( pev - > noise ) ) ; //LRC
PRECACHE_SOUND ( STRING ( pev - > noise1 ) ) ;
PRECACHE_SOUND ( STRING ( pev - > noise2 ) ) ;
m_usAdjustPitch = PRECACHE_EVENT ( 1 , " events/train.sc " ) ;
}
TYPEDESCRIPTION CFuncTrackTrain : : m_SaveData [ ] =
{
DEFINE_FIELD ( CFuncTrackTrain , m_ppath , FIELD_CLASSPTR ) ,
DEFINE_FIELD ( CFuncTrackTrain , m_length , FIELD_FLOAT ) ,
DEFINE_FIELD ( CFuncTrackTrain , m_height , FIELD_FLOAT ) ,
DEFINE_FIELD ( CFuncTrackTrain , m_speed , FIELD_FLOAT ) ,
DEFINE_FIELD ( CFuncTrackTrain , m_dir , FIELD_FLOAT ) ,
DEFINE_FIELD ( CFuncTrackTrain , m_startSpeed , FIELD_FLOAT ) ,
DEFINE_FIELD ( CFuncTrackTrain , m_controlMins , FIELD_VECTOR ) ,
DEFINE_FIELD ( CFuncTrackTrain , m_controlMaxs , FIELD_VECTOR ) ,
DEFINE_FIELD ( CFuncTrackTrain , m_sounds , FIELD_INTEGER ) ,
DEFINE_FIELD ( CFuncTrackTrain , m_flVolume , FIELD_FLOAT ) ,
DEFINE_FIELD ( CFuncTrackTrain , m_flBank , FIELD_FLOAT ) ,
DEFINE_FIELD ( CFuncTrackTrain , m_oldSpeed , FIELD_FLOAT ) ,
DEFINE_FIELD ( CFuncTrackTrain , m_vecMasterAvel , FIELD_VECTOR ) , //LRC
DEFINE_FIELD ( CFuncTrackTrain , m_vecBaseAvel , FIELD_VECTOR ) , //LRC
DEFINE_FIELD ( CFuncTrackTrain , m_pSequence , FIELD_CLASSPTR ) , //LRC
} ;
IMPLEMENT_SAVERESTORE ( CFuncTrackTrain , CBaseEntity )
LINK_ENTITY_TO_CLASS ( func_tracktrain , CFuncTrackTrain )
void CFuncTrackTrain : : KeyValue ( KeyValueData * pkvd )
{
if ( FStrEq ( pkvd - > szKeyName , " wheels " ) )
{
m_length = atof ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else if ( FStrEq ( pkvd - > szKeyName , " height " ) )
{
m_height = atof ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else if ( FStrEq ( pkvd - > szKeyName , " startspeed " ) )
{
m_startSpeed = atof ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else if ( FStrEq ( pkvd - > szKeyName , " sounds " ) )
{
m_sounds = atoi ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else if ( FStrEq ( pkvd - > szKeyName , " custommovesound " ) )
{
pev - > noise = ALLOC_STRING ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else if ( FStrEq ( pkvd - > szKeyName , " custombrakesound " ) )
{
pev - > noise1 = ALLOC_STRING ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else if ( FStrEq ( pkvd - > szKeyName , " customstartsound " ) )
{
pev - > noise2 = ALLOC_STRING ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else if ( FStrEq ( pkvd - > szKeyName , " volume " ) )
{
m_flVolume = ( float ) atoi ( pkvd - > szValue ) ;
m_flVolume * = 0.1 ;
pkvd - > fHandled = TRUE ;
}
else if ( FStrEq ( pkvd - > szKeyName , " bank " ) )
{
m_flBank = atof ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else
CBaseEntity : : KeyValue ( pkvd ) ;
}
void CFuncTrackTrain : : NextThink ( float thinkTime , BOOL alwaysThink )
{
if ( alwaysThink )
// m_iLFlags |= LF_ALWAYSTHINK;
pev - > flags | = FL_ALWAYSTHINK ;
else
// m_iLFlags &= ~LF_ALWAYSTHINK;
pev - > flags & = ~ FL_ALWAYSTHINK ;
SetNextThink ( thinkTime , TRUE ) ;
}
void CFuncTrackTrain : : Blocked ( CBaseEntity * pOther )
{
entvars_t * pevOther = pOther - > pev ;
// Blocker is on-ground on the train
if ( FBitSet ( pevOther - > flags , FL_ONGROUND ) & & VARS ( pevOther - > groundentity ) = = pev )
{
float deltaSpeed = fabs ( pev - > speed ) ;
if ( deltaSpeed > 50 )
deltaSpeed = 50 ;
if ( ! pevOther - > velocity . z )
pevOther - > velocity . z + = deltaSpeed ;
return ;
}
else
pevOther - > velocity = ( pevOther - > origin - pev - > origin ) . Normalize ( ) * pev - > dmg ;
ALERT ( at_aiconsole , " TRAIN(%s): Blocked by %s (dmg:%.2f) \n " , STRING ( pev - > targetname ) , STRING ( pOther - > pev - > classname ) , pev - > dmg ) ;
if ( pev - > dmg < = 0 )
return ;
// we can't hurt this thing, so we're not concerned with it
pOther - > TakeDamage ( pev , pev , pev - > dmg , DMG_CRUSH ) ;
}
void CFuncTrackTrain : : Use ( CBaseEntity * pActivator , CBaseEntity * pCaller , USE_TYPE useType , float value )
{
// ALERT(at_console, "TRAIN: use\n");
if ( useType ! = USE_SET )
{
if ( ! ShouldToggle ( useType , ( pev - > speed ! = 0 ) ) )
return ;
if ( pev - > speed = = 0 )
{
pev - > speed = m_speed * m_dir ;
PostponeNext ( ) ;
}
else
{
pev - > speed = 0 ;
UTIL_SetVelocity ( this , g_vecZero ) ; //LRC
//pev->velocity = g_vecZero;
if ( ! FBitSet ( pev - > spawnflags , SF_TRACKTRAIN_AVELOCITY ) )
UTIL_SetAvelocity ( this , g_vecZero ) ; //LRC
//pev->avelocity = g_vecZero;
StopSound ( ) ;
SetThink ( NULL ) ;
}
}
else
{
float delta = value ;
delta = ( ( int ) ( pev - > speed * 4 ) / ( int ) m_speed ) * 0.25 + 0.25 * delta ;
if ( delta > 1 )
delta = 1 ;
else if ( delta < - 1 )
delta = - 1 ;
if ( pev - > spawnflags & SF_TRACKTRAIN_FORWARDONLY )
{
if ( delta < 0 )
delta = 0 ;
}
pev - > speed = m_speed * delta ;
if ( pev - > spawnflags & SF_TRACKTRAIN_AVEL_GEARS )
{
UTIL_SetAvelocity ( this , m_vecMasterAvel * delta ) ;
//pev->avelocity = m_vecMasterAvel * delta; //LRC
}
PostponeNext ( ) ;
ALERT ( at_aiconsole , " TRAIN(%s), speed to %.2f \n " , STRING ( pev - > targetname ) , pev - > speed ) ;
}
}
# define TRAIN_STARTPITCH 60
# define TRAIN_MAXPITCH 200
# define TRAIN_MAXSPEED 1000 // approx max speed for sound pitch calculation
void CFuncTrackTrain : : StopSound ( void )
{
// if sound playing, stop it
if ( m_soundPlaying & & pev - > noise )
{
if ( m_sounds ) //LRC - flashy event-based method, for normal sounds.
{
unsigned short us_encode ;
unsigned short us_sound = ( ( unsigned short ) ( m_sounds ) & 0x0007 ) < < 12 ;
us_encode = us_sound ;
PLAYBACK_EVENT_FULL ( FEV_RELIABLE | FEV_UPDATE , edict ( ) , m_usAdjustPitch , 0.0 ,
( float * ) & g_vecZero , ( float * ) & g_vecZero , 0.0 , 0.0 , us_encode , 0 , 1 , 0 ) ;
}
else
{
//LRC - down-to-earth method, for custom sounds.
STOP_SOUND ( ENT ( pev ) , CHAN_STATIC , STRING ( pev - > noise ) ) ;
}
EMIT_SOUND_DYN ( ENT ( pev ) , CHAN_ITEM , STRING ( pev - > noise1 ) , m_flVolume , ATTN_NORM , 0 , 100 ) ;
}
m_soundPlaying = 0 ;
}
// update pitch based on speed, start sound if not playing
// NOTE: when train goes through transition, m_soundPlaying should go to 0,
// which will cause the looped sound to restart.
void CFuncTrackTrain : : UpdateSound ( void )
{
float flpitch ;
if ( ! pev - > noise )
return ;
flpitch = TRAIN_STARTPITCH + ( fabs ( pev - > speed ) * ( TRAIN_MAXPITCH - TRAIN_STARTPITCH ) / TRAIN_MAXSPEED ) ;
if ( ! m_soundPlaying )
{
// play startup sound for train
EMIT_SOUND_DYN ( ENT ( pev ) , CHAN_ITEM , " plats/ttrain_start1.wav " , m_flVolume , ATTN_NORM , 0 , 100 ) ;
EMIT_SOUND_DYN ( ENT ( pev ) , CHAN_STATIC , STRING ( pev - > noise ) , m_flVolume , ATTN_NORM , 0 , ( int ) flpitch ) ;
m_soundPlaying = 1 ;
}
else
{
/*
// update pitch
EMIT_SOUND_DYN ( ENT ( pev ) , CHAN_STATIC , STRING ( pev - > noise ) , m_flVolume , ATTN_NORM , SND_CHANGE_PITCH , ( int ) flpitch ) ;
*/
if ( m_sounds ) //LRC - flashy event-based method, for normal sounds.
{
// volume 0.0 - 1.0 - 6 bits
// m_sounds 3 bits
// flpitch = 6 bits
// 15 bits total
unsigned short us_encode ;
unsigned short us_sound = ( ( unsigned short ) ( m_sounds ) & 0x0007 ) < < 12 ;
unsigned short us_pitch = ( ( unsigned short ) ( flpitch / 10.0 ) & 0x003f ) < < 6 ;
unsigned short us_volume = ( ( unsigned short ) ( m_flVolume * 40.0 ) & 0x003f ) ;
us_encode = us_sound | us_pitch | us_volume ;
PLAYBACK_EVENT_FULL ( FEV_RELIABLE | FEV_UPDATE , edict ( ) , m_usAdjustPitch , 0.0 ,
( float * ) & g_vecZero , ( float * ) & g_vecZero , 0.0 , 0.0 , us_encode , 0 , 0 , 0 ) ;
}
else
{
//LRC - down-to-earth method, for custom sounds.
// update pitch
EMIT_SOUND_DYN ( ENT ( pev ) , CHAN_STATIC , STRING ( pev - > noise ) , m_flVolume , ATTN_NORM , SND_CHANGE_PITCH , ( int ) flpitch ) ;
}
}
}
void CFuncTrackTrain : : PostponeNext ( void )
{
UTIL_DesiredAction ( this ) ;
}
void CFuncTrackTrain : : DesiredAction ( void ) // Next( void )
{
float time = 0.5 ;
// ALERT(at_console, "Next: pos %f %f %f, vel %f %f %f. Child pos %f %f %f, vel %f %f %f\n", pev->origin.x, pev->origin.y, pev->origin.z, pev->velocity.x, pev->velocity.y, pev->velocity.z, m_pChildMoveWith->pev->origin.x, m_pChildMoveWith->pev->origin.y, m_pChildMoveWith->pev->origin.z, m_pChildMoveWith->pev->velocity.x, m_pChildMoveWith->pev->velocity.y, m_pChildMoveWith->pev->velocity.z);
// UTIL_DesiredInfo(this);
// static float stime;
// ALERT(at_console, "TRAIN: think delay = %f\n", gpGlobals->time - stime);
// stime = gpGlobals->time;
if ( ! pev - > speed )
{
// ALERT(at_console, "TRAIN: no speed\n");
UTIL_SetVelocity ( this , g_vecZero ) ;
DontThink ( ) ;
ALERT ( at_aiconsole , " TRAIN(%s): Speed is 0 \n " , STRING ( pev - > targetname ) ) ;
StopSound ( ) ;
return ;
}
//if( !m_ppath )
// m_ppath = UTIL_FindEntityByTargetname( NULL, STRING(pev->target) );
if ( ! m_ppath )
{
// ALERT(at_console, "TRAIN: no path\n");
UTIL_SetVelocity ( this , g_vecZero ) ;
DontThink ( ) ;
ALERT ( at_aiconsole , " TRAIN(%s): Lost path \n " , STRING ( pev - > targetname ) ) ;
StopSound ( ) ;
return ;
}
UpdateSound ( ) ;
Vector nextPos = pev - > origin ;
nextPos . z - = m_height ;
CPathTrack * pnext = m_ppath - > LookAhead ( & nextPos , pev - > speed * 0.1 , 1 ) ;
nextPos . z + = m_height ;
UTIL_SetVelocity ( this , ( nextPos - pev - > origin ) * 10 ) ; //LRC
// Vector vD = (nextPos - pev->origin) * 10;
// ALERT(at_console, "TRAIN: Set vel to (%f %f %f)\n", vD.x, vD.y, vD.z);
//pev->velocity = (nextPos - pev->origin) * 10;
Vector nextFront = pev - > origin ;
nextFront . z - = m_height ;
if ( m_length > 0 )
m_ppath - > LookAhead ( & nextFront , m_length , 0 ) ;
else
m_ppath - > LookAhead ( & nextFront , 100 , 0 ) ;
nextFront . z + = m_height ;
if ( ! FBitSet ( pev - > spawnflags , SF_TRACKTRAIN_AVELOCITY ) ) //LRC
{
Vector delta = nextFront - pev - > origin ;
Vector angles = UTIL_VecToAngles ( delta ) ;
// The train actually points west
angles . y + = 180 ; //LRC, FIXME: add a 'built facing' field.
// !!! All of this crap has to be done to make the angles not wrap around, revisit this.
FixupAngles ( angles ) ;
FixupAngles ( pev - > angles ) ;
if ( ! pnext | | ( delta . x = = 0 & & delta . y = = 0 ) )
angles = pev - > angles ;
float vy , vx , vz ;
if ( ! ( pev - > spawnflags & SF_TRACKTRAIN_NOPITCH ) )
vx = 10 * UTIL_AngleDistance ( angles . x , pev - > angles . x ) ;
else
vx = m_vecBaseAvel . x ;
if ( ! ( pev - > spawnflags & SF_TRACKTRAIN_NOYAW ) ) //LRC
vy = 10 * UTIL_AngleDistance ( angles . y , pev - > angles . y ) ;
else
vy = m_vecBaseAvel . y ;
if ( m_flBank ! = 0 )
{
if ( pev - > avelocity . y < - 5 )
vz = UTIL_AngleDistance ( UTIL_ApproachAngle ( - m_flBank , pev - > angles . z , m_flBank * 2 ) , pev - > angles . z ) ;
else if ( pev - > avelocity . y > 5 )
vz = UTIL_AngleDistance ( UTIL_ApproachAngle ( m_flBank , pev - > angles . z , m_flBank * 2 ) , pev - > angles . z ) ;
else
vz = UTIL_AngleDistance ( UTIL_ApproachAngle ( 0 , pev - > angles . z , m_flBank * 4 ) , pev - > angles . z ) * 4 ;
}
else
{
vz = m_vecBaseAvel . z ;
}
UTIL_SetAvelocity ( this , Vector ( vx , vy , vz ) ) ;
//pev->avelocity.y = vy;
//pev->avelocity.x = vx;
}
if ( pnext )
{
if ( pnext ! = m_ppath )
{
// ALERT(at_console, "TRAIN: new m_ppath %s, was %s. Origin %f %f %f\n", STRING(pnext->pev->targetname), STRING(m_ppath->pev->targetname), pev->origin.x, pev->origin.y, pev->origin.z);
CPathTrack * pFire ;
if ( pev - > speed > = 0 ) // check whether we're going forwards or backwards
pFire = pnext ;
else
pFire = m_ppath ;
m_ppath = pnext ;
// Fire the pass target if there is one
if ( pFire - > pev - > message )
{
FireTargets ( STRING ( pFire - > pev - > message ) , this , this , USE_TOGGLE , 0 ) ;
if ( FBitSet ( pFire - > pev - > spawnflags , SF_PATH_FIREONCE ) )
pFire - > pev - > message = 0 ;
}
if ( pFire - > pev - > spawnflags & SF_PATH_DISABLE_TRAIN )
pev - > spawnflags | = SF_TRACKTRAIN_NOCONTROL ;
//LRC is "match angle" set to "Yes"? If so, set the angle exactly, because we've reached the corner.
if ( pFire - > pev - > armorvalue = = PATHMATCH_YES & & pev - > spawnflags & SF_TRACKTRAIN_AVELOCITY )
{
Vector vTemp = pFire - > pev - > angles ;
vTemp . y - = 180 ; //the train is actually built facing west.
UTIL_SetAngles ( this , vTemp ) ;
//pev->angles = pFire->pev->angles;
//pev->angles.y -= 180; //the train is actually built facing west.
}
float setting = ( ( int ) ( pev - > speed * 4 ) / ( int ) m_speed ) / 4.0 ; //LRC - one of { 1, 0.75, 0.5, 0.25, 0, ... -1 }
//LRC
if ( pFire - > pev - > frags = = PATHTURN_RESET )
{
pev - > spawnflags & = ~ ( SF_TRACKTRAIN_AVEL_GEARS | SF_TRACKTRAIN_AVELOCITY ) ;
}
else if ( pFire - > pev - > spawnflags & SF_PATH_AVELOCITY )
{
if ( pFire - > pev - > frags = = PATHTURN_SET_MASTER )
{
m_vecMasterAvel = pFire - > pev - > avelocity ;
UTIL_SetAvelocity ( this , m_vecMasterAvel * setting ) ;
//pev->avelocity = m_vecMasterAvel * setting;
pev - > spawnflags | = ( SF_TRACKTRAIN_AVEL_GEARS | SF_TRACKTRAIN_AVELOCITY ) ;
}
else if ( pFire - > pev - > frags = = PATHTURN_SET )
{
UTIL_SetAvelocity ( this , pFire - > pev - > avelocity ) ;
//pev->avelocity = pFire->pev->avelocity;
pev - > spawnflags | = SF_TRACKTRAIN_AVELOCITY ;
pev - > spawnflags & = ~ SF_TRACKTRAIN_AVEL_GEARS ;
}
}
CPathTrack * pDest ; //LRC - the path_track we're heading for, after pFire.
if ( pev - > speed > 0 )
pDest = pFire - > GetNext ( ) ;
else
pDest = pFire - > GetPrevious ( ) ;
// ALERT(at_console, "and pDest is %s\n", STRING(pDest->pev->targetname));
//LRC
// don't look at speed from target if it is 0 (uninitialized)
if ( pFire - > pev - > speed ! = 0 )
{
//ALERT( at_console, "TrackTrain setting is %d / %d = %.2f\n", (int)(pev->speed*4), (int)m_speed, setting );
switch ( ( int ) ( pFire - > pev - > armortype ) )
{
case PATHSPEED_SET :
// Don't override speed if under user control
if ( pev - > spawnflags & SF_TRACKTRAIN_NOCONTROL )
pev - > speed = pFire - > pev - > speed ;
ALERT ( at_aiconsole , " TrackTrain %s speed set to %4.2f \n " , STRING ( pev - > targetname ) , pev - > speed ) ;
break ;
case PATHSPEED_SET_MASTER :
m_speed = pFire - > pev - > speed ;
pev - > impulse = m_speed ;
pev - > speed = setting * m_speed ;
ALERT ( at_aiconsole , " TrackTrain %s master speed set to %4.2f \n " , STRING ( pev - > targetname ) , pev - > speed ) ;
break ;
case PATHSPEED_ACCEL :
m_speed + = pFire - > pev - > speed ;
pev - > impulse = m_speed ;
pev - > speed = setting * m_speed ;
ALERT ( at_aiconsole , " TrackTrain %s speed accel to %4.2f \n " , STRING ( pev - > targetname ) , pev - > speed ) ;
break ;
case PATHSPEED_TIME :
float distance = ( pev - > origin - pDest - > pev - > origin ) . Length ( ) ;
//ALERT(at_console, "pFire=%s, distance=%.2f, ospeed=%.2f, nspeed=%.2f\n", STRING(pFire->pev->targetname), distance, pev->speed, distance / pFire->pev->speed);
m_speed = distance / pFire - > pev - > speed ;
pev - > impulse = m_speed ;
pev - > speed = setting * m_speed ;
ALERT ( at_aiconsole , " TrackTrain %s speed to %4.2f (timed) \n " , STRING ( pev - > targetname ) , pev - > speed ) ;
break ;
}
}
//LRC
if ( pDest - > pev - > armorvalue = = PATHMATCH_YES )
{
pev - > spawnflags | = SF_TRACKTRAIN_AVELOCITY | SF_TRACKTRAIN_AVEL_GEARS ;
Vector vTemp = pev - > angles ;
FixupAngles ( vTemp ) ;
UTIL_SetAngles ( this , vTemp ) ;
Vector oDelta = pDest - > pev - > origin - pev - > origin ;
Vector aDelta ;
if ( setting > 0 )
{
aDelta . x = UTIL_AngleDistance ( pDest - > pev - > angles . x , pev - > angles . x ) ;
aDelta . y = UTIL_AngleDistance ( pDest - > pev - > angles . y , pev - > angles . y ) ;
aDelta . z = UTIL_AngleDistance ( pDest - > pev - > angles . z , pev - > angles . z ) ;
}
else
{
aDelta . x = UTIL_AngleDistance ( pev - > angles . x , pDest - > pev - > angles . x ) ;
aDelta . y = UTIL_AngleDistance ( pev - > angles . y , pDest - > pev - > angles . y ) ;
aDelta . z = UTIL_AngleDistance ( pev - > angles . z , pDest - > pev - > angles . z ) ;
}
if ( aDelta . y > 0 ) // the train is actually built facing west.
aDelta . y - = 180 ;
else
aDelta . y + = 180 ;
float timeTaken = oDelta . Length ( ) / m_speed ;
m_vecMasterAvel = aDelta / timeTaken ;
UTIL_SetAvelocity ( this , setting * m_vecMasterAvel ) ;
//pev->avelocity = setting * m_vecMasterAvel;
}
//LRC- FIXME: add support, here, for a Teleport flag.
}
// else
// {
// ALERT(at_console, "TRAIN: same pnext\n");
// }
SetThink ( & CFuncTrackTrain : : PostponeNext ) ;
NextThink ( time , TRUE ) ;
}
else // end of path, stop
{
Vector vecTemp ; //LRC
StopSound ( ) ;
vecTemp = ( nextPos - pev - > origin ) ; //LRC
// ALERT(at_console, "TRAIN: path end\n");
// UTIL_SetVelocity( this, (nextPos - pev->origin) * 10 ); //LRC
// pev->velocity = (nextPos - pev->origin);
if ( ! FBitSet ( pev - > spawnflags , SF_TRACKTRAIN_AVELOCITY ) ) //LRC
UTIL_SetAvelocity ( this , g_vecZero ) ;
//pev->avelocity = g_vecZero;
float distance = vecTemp . Length ( ) ; //LRC
//float distance = pev->velocity.Length();
m_oldSpeed = pev - > speed ;
pev - > speed = 0 ;
// Move to the dead end
// Are we there yet?
if ( distance > 0 )
{
// no, how long to get there?
time = distance / m_oldSpeed ;
UTIL_SetVelocity ( this , vecTemp * ( m_oldSpeed / distance ) ) ; //LRC
//pev->velocity = pev->velocity * (m_oldSpeed / distance);
SetThink ( & CFuncTrackTrain : : DeadEnd ) ;
NextThink ( time , FALSE ) ;
}
else
{
UTIL_SetVelocity ( this , vecTemp ) ; //LRC
DeadEnd ( ) ;
}
}
}
void CFuncTrackTrain : : DeadEnd ( void )
{
// Fire the dead-end target if there is one
CPathTrack * pTrack , * pNext ;
pTrack = m_ppath ;
ALERT ( at_aiconsole , " TRAIN(%s): Dead end " , STRING ( pev - > targetname ) ) ;
// 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 ) ;
}
}
UTIL_SetVelocity ( this , g_vecZero ) ; //LRC
// pev->velocity = g_vecZero;
if ( ! FBitSet ( pev - > spawnflags , SF_TRACKTRAIN_AVELOCITY ) ) //LRC
UTIL_SetAvelocity ( this , g_vecZero ) ;
//pev->avelocity = g_vecZero;
if ( pTrack )
{
ALERT ( at_aiconsole , " at %s \n " , STRING ( pTrack - > pev - > targetname ) ) ;
if ( pTrack - > pev - > netname )
FireTargets ( STRING ( pTrack - > pev - > netname ) , this , this , USE_TOGGLE , 0 ) ;
}
else
ALERT ( at_aiconsole , " \n " ) ;
}
void CFuncTrackTrain : : SetControls ( entvars_t * pevControls )
{
Vector offset = pevControls - > origin - pev - > oldorigin ;
m_controlMins = pevControls - > mins + offset ;
m_controlMaxs = pevControls - > maxs + offset ;
}
BOOL CFuncTrackTrain : : OnControls ( entvars_t * pevTest )
{
Vector offset = pevTest - > origin - pev - > origin ;
if ( pev - > spawnflags & SF_TRACKTRAIN_NOCONTROL )
return FALSE ;
// Transform offset into local coordinates
UTIL_MakeVectors ( pev - > angles ) ;
Vector local ;
local . x = DotProduct ( offset , gpGlobals - > v_forward ) ;
local . y = - DotProduct ( offset , gpGlobals - > v_right ) ;
local . z = DotProduct ( offset , gpGlobals - > v_up ) ;
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 * ) UTIL_FindEntityByTargetname ( NULL , STRING ( pev - > target ) ) ;
if ( ! m_ppath )
return ;
entvars_t * pevTarget = m_ppath - > pev ;
if ( ! FClassnameIs ( pevTarget , " path_track " ) )
{
ALERT ( at_error , " func_track_train must be on a path of path_track \n " ) ;
m_ppath = NULL ;
return ;
}
Vector nextPos = pevTarget - > origin ;
nextPos . z + = m_height ;
Vector look = nextPos ;
look . z - = m_height ;
m_ppath - > LookAhead ( & look , m_length , 0 ) ;
look . z + = m_height ;
Vector vTemp = UTIL_VecToAngles ( look - nextPos ) ;
vTemp . y + = 180 ;
// The train actually points west
//pev->angles.y += 180;
if ( pev - > spawnflags & SF_TRACKTRAIN_NOPITCH )
{
vTemp . x = 0 ;
//pev->angles.x = 0;
}
UTIL_SetAngles ( this , vTemp ) ; //LRC
UTIL_AssignOrigin ( this , nextPos ) ; //LRC
// ALERT(at_console, "Train Find; origin %f %f %f\n", pev->origin.x, pev->origin.y, pev->origin.z);
//UTIL_SetOrigin( this, nextPos );
NextThink ( 0.1 , FALSE ) ;
// NextThink( 8, FALSE ); //LRC - What was this for?!
// SetThink( Next );
SetThink ( & CFuncTrackTrain : : PostponeNext ) ;
pev - > speed = m_startSpeed ;
UpdateSound ( ) ;
}
void CFuncTrackTrain : : NearestPath ( void )
{
CBaseEntity * pTrack = NULL ;
CBaseEntity * pNearest = NULL ;
float dist , closest ;
closest = 1024 ;
while ( ( pTrack = UTIL_FindEntityInSphere ( pTrack , pev - > origin , 1024 ) ) ! = NULL )
{
// filter out non-tracks
if ( ! ( pTrack - > pev - > flags & ( FL_CLIENT | FL_MONSTER ) ) & & FClassnameIs ( pTrack - > pev , " path_track " ) )
{
dist = ( pev - > origin - pTrack - > pev - > origin ) . Length ( ) ;
if ( dist < closest )
{
closest = dist ;
pNearest = pTrack ;
}
}
}
if ( ! pNearest )
{
ALERT ( at_console , " Can't find a nearby track !!! \n " ) ;
SetThink ( NULL ) ;
return ;
}
ALERT ( at_aiconsole , " TRAIN: %s, Nearest track is %s \n " , STRING ( pev - > targetname ) , STRING ( pNearest - > pev - > targetname ) ) ;
// 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 ( ( pev - > origin - pTrack - > pev - > origin ) . Length ( ) < ( pev - > origin - pNearest - > pev - > origin ) . Length ( ) )
pNearest = pTrack ;
}
m_ppath = ( CPathTrack * ) pNearest ;
if ( pev - > speed ! = 0 )
{
NextThink ( 0.1 , FALSE ) ;
SetThink ( & CFuncTrackTrain : : PostponeNext ) ;
}
}
void CFuncTrackTrain : : OverrideReset ( void )
{
NextThink ( 0.1 , FALSE ) ;
SetThink ( & CFuncTrackTrain : : NearestPath ) ;
}
CFuncTrackTrain * CFuncTrackTrain : : Instance ( edict_t * pent )
{
if ( FClassnameIs ( pent , " func_tracktrain " ) )
return ( CFuncTrackTrain * ) GET_PRIVATE ( pent ) ;
return NULL ;
}
//LRC
void CFuncTrackTrain : : StartSequence ( CTrainSequence * pSequence )
{
m_pSequence = pSequence ;
// ALERT(at_console, "Unset Retrigger (startsequence)\n");
pev - > spawnflags & = ~ SF_TRAIN_WAIT_RETRIGGER ;
//...
}
//LRC
void CFuncTrackTrain : : StopSequence ( )
{
DontThink ( ) ;
m_pSequence = NULL ;
//...
}
// This class defines the volume of space that the player must stand in to control the train
class CFuncTrainControls : public CBaseEntity
{
public :
virtual int ObjectCaps ( void ) { return CBaseEntity : : ObjectCaps ( ) & ~ FCAP_ACROSS_TRANSITION ; }
void Spawn ( void ) ;
void EXPORT Find ( void ) ;
} ;
LINK_ENTITY_TO_CLASS ( func_traincontrols , CFuncTrainControls )
void CFuncTrainControls : : Find ( void )
{
CBaseEntity * pTarget = NULL ;
do
{
pTarget = UTIL_FindEntityByTargetname ( pTarget , STRING ( pev - > target ) ) ;
} while ( pTarget & & ! FClassnameIs ( pTarget - > pev , " func_tracktrain " ) ) ;
if ( ! pTarget )
{
ALERT ( at_console , " No train %s \n " , STRING ( pev - > target ) ) ;
return ;
}
CFuncTrackTrain * ptrain = ( CFuncTrackTrain * ) pTarget ;
ptrain - > SetControls ( pev ) ;
UTIL_Remove ( this ) ;
}
void CFuncTrainControls : : Spawn ( void )
{
pev - > solid = SOLID_NOT ;
pev - > movetype = MOVETYPE_NONE ;
SET_MODEL ( ENT ( pev ) , STRING ( pev - > model ) ) ;
UTIL_SetSize ( pev , pev - > mins , pev - > maxs ) ;
UTIL_SetOrigin ( this , pev - > origin ) ;
SetThink ( & CFuncTrainControls : : Find ) ;
SetNextThink ( 0 ) ;
}
// ----------------------------------------------------------------------------
//
// Track changer / Train elevator
//
// ----------------------------------------------------------------------------
# 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
//
// 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.
//
typedef enum { TRAIN_SAFE , TRAIN_BLOCKING , TRAIN_FOLLOWING } TRAIN_CODE ;
class CFuncTrackChange : public CFuncPlatRot
{
public :
void Spawn ( void ) ;
void Precache ( void ) ;
//virtual void Blocked( void );
virtual void EXPORT GoUp ( void ) ;
virtual void EXPORT GoDown ( void ) ;
void KeyValue ( KeyValueData * pkvd ) ;
void Use ( CBaseEntity * pActivator , CBaseEntity * pCaller , USE_TYPE useType , float value ) ;
void EXPORT Find ( void ) ;
TRAIN_CODE EvaluateTrain ( CPathTrack * pcurrent ) ;
void UpdateTrain ( Vector & 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 ; }
virtual int Save ( CSave & save ) ;
virtual int Restore ( CRestore & restore ) ;
static TYPEDESCRIPTION m_SaveData [ ] ;
virtual void OverrideReset ( void ) ;
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 )
TYPEDESCRIPTION CFuncTrackChange : : m_SaveData [ ] =
{
DEFINE_GLOBAL_FIELD ( CFuncTrackChange , m_trackTop , FIELD_CLASSPTR ) ,
DEFINE_GLOBAL_FIELD ( CFuncTrackChange , m_trackBottom , FIELD_CLASSPTR ) ,
DEFINE_GLOBAL_FIELD ( CFuncTrackChange , m_train , FIELD_CLASSPTR ) ,
DEFINE_GLOBAL_FIELD ( CFuncTrackChange , m_trackTopName , FIELD_STRING ) ,
DEFINE_GLOBAL_FIELD ( CFuncTrackChange , m_trackBottomName , FIELD_STRING ) ,
DEFINE_GLOBAL_FIELD ( CFuncTrackChange , m_trainName , FIELD_STRING ) ,
DEFINE_FIELD ( CFuncTrackChange , m_code , FIELD_INTEGER ) ,
DEFINE_FIELD ( CFuncTrackChange , m_targetState , FIELD_INTEGER ) ,
DEFINE_FIELD ( CFuncTrackChange , m_use , FIELD_INTEGER ) ,
} ;
IMPLEMENT_SAVERESTORE ( CFuncTrackChange , CFuncPlatRot )
void CFuncTrackChange : : Spawn ( void )
{
Setup ( ) ;
if ( FBitSet ( pev - > spawnflags , SF_TRACK_DONT_MOVE ) )
m_vecPosition2 . z = pev - > origin . z ;
SetupRotation ( ) ;
if ( FBitSet ( pev - > spawnflags , SF_TRACK_STARTBOTTOM ) )
{
UTIL_SetOrigin ( this , m_vecPosition2 ) ;
m_toggle_state = TS_AT_BOTTOM ;
pev - > angles = m_start ;
m_targetState = TS_AT_TOP ;
}
else
{
UTIL_SetOrigin ( this , m_vecPosition1 ) ;
m_toggle_state = TS_AT_TOP ;
pev - > angles = m_end ;
m_targetState = TS_AT_BOTTOM ;
}
EnableUse ( ) ;
pev - > nextthink = pev - > ltime + 2.0 ;
SetThink ( & CFuncTrackChange : : Find ) ;
Precache ( ) ;
}
void CFuncTrackChange : : Precache ( void )
{
// Can't trigger sound
PRECACHE_SOUND ( " buttons/button11.wav " ) ;
CFuncPlatRot : : Precache ( ) ;
}
// UNDONE: Filter touches before re-evaluating the train.
void CFuncTrackChange : : Touch ( CBaseEntity * pOther )
{
#if 0
TRAIN_CODE code ;
entvars_t * pevToucher = pOther - > pev ;
# endif
}
void CFuncTrackChange : : KeyValue ( KeyValueData * pkvd )
{
if ( FStrEq ( pkvd - > szKeyName , " train " ) )
{
m_trainName = ALLOC_STRING ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else if ( FStrEq ( pkvd - > szKeyName , " toptrack " ) )
{
m_trackTopName = ALLOC_STRING ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else if ( FStrEq ( pkvd - > szKeyName , " bottomtrack " ) )
{
m_trackBottomName = ALLOC_STRING ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else
{
CFuncPlatRot : : KeyValue ( pkvd ) ; // Pass up to base class
}
}
void CFuncTrackChange : : OverrideReset ( void )
{
pev - > nextthink = pev - > ltime + 1.0 ;
SetThink ( & CFuncTrackChange : : Find ) ;
}
void CFuncTrackChange : : Find ( void )
{
// Find track entities
CBaseEntity * pTarget ;
pTarget = UTIL_FindEntityByTargetname ( NULL , STRING ( m_trackTopName ) ) ;
if ( pTarget & & FClassnameIs ( pTarget - > pev , " path_track " ) )
{
m_trackTop = ( CPathTrack * ) pTarget ;
pTarget = UTIL_FindEntityByTargetname ( NULL , STRING ( m_trackBottomName ) ) ;
if ( pTarget & & FClassnameIs ( pTarget - > pev , " path_track " ) )
{
m_trackBottom = ( CPathTrack * ) pTarget ;
pTarget = UTIL_FindEntityByTargetname ( NULL , STRING ( m_trainName ) ) ;
if ( pTarget & & FClassnameIs ( pTarget - > pev , " func_tracktrain " ) )
{
m_train = ( CFuncTrackTrain * ) pTarget ;
Vector center = ( pev - > absmin + pev - > absmax ) * 0.5 ;
m_trackBottom = m_trackBottom - > Nearest ( center ) ;
m_trackTop = m_trackTop - > Nearest ( center ) ;
UpdateAutoTargets ( m_toggle_state ) ;
SetThink ( NULL ) ;
return ;
}
else
ALERT ( at_error , " Can't find train for track change! %s \n " , STRING ( m_trainName ) ) ;
}
else
ALERT ( at_error , " Can't find bottom track for track change! %s \n " , STRING ( m_trackBottomName ) ) ;
}
else
ALERT ( at_error , " Can't find top track for track change! %s \n " , STRING ( m_trackTopName ) ) ;
}
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 - > pev - > speed ! = 0 )
return TRAIN_BLOCKING ;
Vector dist = pev - > origin - m_train - > pev - > origin ;
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 ( Vector & dest )
{
float time ;
Vector vel = pev - > velocity ;
if ( m_pfnThink = = & CFuncTrackChange : : LinearMoveNow )
{
// we're going to do a LinearMoveNow: calculate the velocity it'll have
Vector vecDest ;
if ( m_pMoveWith )
vecDest = m_vecFinalDest + m_pMoveWith - > pev - > origin ;
else
vecDest = m_vecFinalDest ;
Vector vecDestDelta = vecDest - pev - > origin ;
time = vecDestDelta . Length ( ) / m_flLinearMoveSpeed ;
vel = vecDestDelta / time ;
}
else
{
time = ( pev - > nextthink - pev - > ltime ) ;
}
m_train - > pev - > velocity = vel ;
m_train - > pev - > avelocity = pev - > avelocity ;
m_train - > NextThink ( m_train - > pev - > ltime + time , FALSE ) ;
// Attempt at getting the train to rotate properly around the origin of the trackchange
if ( time < = 0 )
{
// ALERT(at_console, "no time, set trainvel %f %f %f\n", m_train->pev->velocity.x, m_train->pev->velocity.y, m_train->pev->velocity.z);
return ;
}
Vector offset = m_train - > pev - > origin - pev - > origin ;
Vector delta = dest - pev - > angles ;
// Transform offset into local coordinates
UTIL_MakeInvVectors ( delta , gpGlobals ) ;
Vector local ;
local . x = DotProduct ( offset , gpGlobals - > v_forward ) ;
local . y = DotProduct ( offset , gpGlobals - > v_right ) ;
local . z = DotProduct ( offset , gpGlobals - > v_up ) ;
local = local - offset ;
m_train - > pev - > velocity = vel + ( local * ( 1.0 / time ) ) ;
// ALERT(at_console, "set trainvel %f %f %f\n", m_train->pev->velocity.x, m_train->pev->velocity.y, m_train->pev->velocity.z);
}
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 ( pev - > spawnflags , SF_TRACK_DONT_MOVE ) )
{
SetMoveDone ( & CFuncTrackChange : : CallHitBottom ) ;
m_toggle_state = TS_GOING_DOWN ;
AngularMove ( m_start , pev - > speed ) ;
}
else
{
CFuncPlat : : GoDown ( ) ;
SetMoveDone ( & CFuncTrackChange : : CallHitBottom ) ;
Vector vecDest ;
if ( m_pMoveWith )
{
vecDest = m_vecFinalDest + m_pMoveWith - > pev - > origin ;
}
else
vecDest = m_vecFinalDest ;
Vector vecDestDelta = vecDest - pev - > origin ;
float flTravelTime = vecDestDelta . Length ( ) / m_flLinearMoveSpeed ;
RotMove ( m_start , flTravelTime ) ;
// RotMove( m_start, pev->nextthink - pev->ltime );
}
// 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 ( pev - > spawnflags , SF_TRACK_DONT_MOVE ) )
{
m_toggle_state = TS_GOING_UP ;
SetMoveDone ( & CFuncTrackChange : : CallHitTop ) ;
AngularMove ( m_end , pev - > speed ) ;
}
else
{
// If ROTMOVE, move & rotate
CFuncPlat : : GoUp ( ) ;
SetMoveDone ( & CFuncTrackChange : : CallHitTop ) ;
RotMove ( m_end , pev - > nextthink - pev - > ltime ) ;
}
// 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 ;
}
}
// Normal track change
void CFuncTrackChange : : UpdateAutoTargets ( int toggleState )
{
if ( ! m_trackTop | | ! m_trackBottom )
return ;
if ( toggleState = = TS_AT_TOP )
ClearBits ( m_trackTop - > pev - > spawnflags , SF_PATH_DISABLED ) ;
else
SetBits ( m_trackTop - > pev - > spawnflags , SF_PATH_DISABLED ) ;
if ( toggleState = = TS_AT_BOTTOM )
ClearBits ( m_trackBottom - > pev - > spawnflags , SF_PATH_DISABLED ) ;
else
SetBits ( m_trackBottom - > pev - > spawnflags , 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
EMIT_SOUND ( ENT ( pev ) , CHAN_VOICE , " buttons/button11.wav " , 1 , ATTN_NORM ) ;
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 )
{
CFuncPlatRot : : HitBottom ( ) ;
if ( m_code = = TRAIN_FOLLOWING )
{
//UpdateTrain();
m_train - > SetTrack ( m_trackBottom ) ;
}
SetThink ( NULL ) ;
pev - > nextthink = - 1 ;
UpdateAutoTargets ( m_toggle_state ) ;
EnableUse ( ) ;
}
//
// Platform has hit bottom. Stops and waits forever.
//
void CFuncTrackChange : : HitTop ( void )
{
CFuncPlatRot : : HitTop ( ) ;
if ( m_code = = TRAIN_FOLLOWING )
{
//UpdateTrain();
m_train - > SetTrack ( m_trackTop ) ;
}
// Don't let the plat go back down
SetThink ( NULL ) ;
pev - > nextthink = - 1 ;
UpdateAutoTargets ( m_toggle_state ) ;
EnableUse ( ) ;
}
class CFuncTrackAuto : public CFuncTrackChange
{
public :
void Use ( CBaseEntity * pActivator , CBaseEntity * pCaller , USE_TYPE useType , float value ) ;
virtual void UpdateAutoTargets ( int toggleState ) ;
} ;
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 )
{
ClearBits ( pTarget - > pev - > spawnflags , SF_PATH_DISABLED ) ;
if ( m_code = = TRAIN_FOLLOWING & & m_train & & m_train - > pev - > speed = = 0 )
m_train - > Use ( this , this , USE_ON , 0 ) ;
}
if ( pNextTarget )
SetBits ( pNextTarget - > pev - > spawnflags , SF_PATH_DISABLED ) ;
}
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 - > pev , " 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 ) ;
}
}
// ----------------------------------------------------------
//
//
// pev->speed is the travel speed
// pev->health is current health
// pev->max_health is the amount to reset to each time it starts
# define FGUNTARGET_START_ON 0x0001
class CGunTarget : public CBaseMonster
{
public :
void Spawn ( void ) ;
void Activate ( void ) ;
void EXPORT Next ( void ) ;
void EXPORT Start ( void ) ;
void EXPORT Wait ( void ) ;
void Stop ( void ) ;
int BloodColor ( void ) { return DONT_BLEED ; }
int Classify ( void ) { return CLASS_MACHINE ; }
int TakeDamage ( entvars_t * pevInflictor , entvars_t * pevAttacker , float flDamage , int bitsDamageType ) ;
void Use ( CBaseEntity * pActivator , CBaseEntity * pCaller , USE_TYPE useType , float value ) ;
Vector BodyTarget ( const Vector & posSrc ) { return pev - > origin ; }
virtual int ObjectCaps ( void ) { return CBaseEntity : : ObjectCaps ( ) & ~ FCAP_ACROSS_TRANSITION ; }
virtual int Save ( CSave & save ) ;
virtual int Restore ( CRestore & restore ) ;
static TYPEDESCRIPTION m_SaveData [ ] ;
private :
BOOL m_on ;
} ;
LINK_ENTITY_TO_CLASS ( func_guntarget , CGunTarget )
TYPEDESCRIPTION CGunTarget : : m_SaveData [ ] =
{
DEFINE_FIELD ( CGunTarget , m_on , FIELD_BOOLEAN ) ,
} ;
IMPLEMENT_SAVERESTORE ( CGunTarget , CBaseMonster )
void CGunTarget : : Spawn ( void )
{
pev - > solid = SOLID_BSP ;
pev - > movetype = MOVETYPE_PUSH ;
UTIL_SetOrigin ( this , pev - > origin ) ;
SET_MODEL ( ENT ( pev ) , STRING ( pev - > model ) ) ;
if ( pev - > speed = = 0 )
pev - > speed = 100 ;
// Don't take damage until "on"
pev - > takedamage = DAMAGE_NO ;
pev - > flags | = FL_MONSTER ;
m_on = FALSE ;
pev - > max_health = pev - > health ;
if ( pev - > spawnflags & FGUNTARGET_START_ON )
{
SetThink ( & CGunTarget : : Start ) ;
SetNextThink ( 0.3 ) ;
}
}
void CGunTarget : : Activate ( void )
{
CBaseEntity * pTarg ;
// now find our next target
pTarg = GetNextTarget ( ) ;
if ( pTarg )
{
m_hTargetEnt = pTarg ;
UTIL_SetOrigin ( this , pTarg - > pev - > origin - ( pev - > mins + pev - > maxs ) * 0.5 ) ;
}
CBaseMonster : : Activate ( ) ;
}
void CGunTarget : : Start ( void )
{
Use ( this , this , USE_ON , 0 ) ;
}
void CGunTarget : : Next ( void )
{
SetThink ( NULL ) ;
m_hTargetEnt = GetNextTarget ( ) ;
CBaseEntity * pTarget = m_hTargetEnt ;
if ( ! pTarget )
{
Stop ( ) ;
return ;
}
SetMoveDone ( & CGunTarget : : Wait ) ;
LinearMove ( pTarget - > pev - > origin - ( pev - > mins + pev - > maxs ) * 0.5 , pev - > speed ) ;
}
void CGunTarget : : Wait ( void )
{
CBaseEntity * pTarget = m_hTargetEnt ;
if ( ! pTarget )
{
Stop ( ) ;
return ;
}
// Fire the pass target if there is one
if ( pTarget - > pev - > message )
{
FireTargets ( STRING ( pTarget - > pev - > message ) , this , this , USE_TOGGLE , 0 ) ;
if ( FBitSet ( pTarget - > pev - > spawnflags , SF_CORNER_FIREONCE ) )
pTarget - > pev - > message = 0 ;
}
m_flWait = pTarget - > GetDelay ( ) ;
pev - > target = pTarget - > pev - > target ;
SetThink ( & CGunTarget : : Next ) ;
if ( m_flWait ! = 0 )
{ // -1 wait will wait forever!
SetNextThink ( m_flWait ) ;
}
else
{
Next ( ) ; // do it RIGHT now!
}
}
void CGunTarget : : Stop ( void )
{
pev - > velocity = g_vecZero ;
DontThink ( ) ;
pev - > takedamage = DAMAGE_NO ;
}
int CGunTarget : : TakeDamage ( entvars_t * pevInflictor , entvars_t * pevAttacker , float flDamage , int bitsDamageType )
{
if ( pev - > health > 0 )
{
pev - > health - = flDamage ;
if ( pev - > health < = 0 )
{
pev - > health = 0 ;
Stop ( ) ;
if ( pev - > message )
FireTargets ( STRING ( pev - > message ) , this , this , USE_TOGGLE , 0 ) ;
}
}
return 0 ;
}
void CGunTarget : : Use ( CBaseEntity * pActivator , CBaseEntity * pCaller , USE_TYPE useType , float value )
{
if ( ! ShouldToggle ( useType , m_on ) )
return ;
if ( m_on )
{
Stop ( ) ;
}
else
{
pev - > takedamage = DAMAGE_AIM ;
m_hTargetEnt = GetNextTarget ( ) ;
if ( m_hTargetEnt = = 0 )
return ;
pev - > health = pev - > max_health ;
Next ( ) ;
}
}
//============================================================================
//LRC - Scripted Train Sequence
//============================================================================
# define DIRECTION_NONE 0
# define DIRECTION_FORWARDS 1
# define DIRECTION_BACKWARDS 2
# define DIRECTION_STOP 3
# define DIRECTION_DESTINATION 4
# define SF_TRAINSEQ_REMOVE 2
# define SF_TRAINSEQ_DIRECT 4
# define SF_TRAINSEQ_DEBUG 8
LINK_ENTITY_TO_CLASS ( scripted_trainsequence , CTrainSequence ) ;
TYPEDESCRIPTION CTrainSequence : : m_SaveData [ ] =
{
DEFINE_FIELD ( CTrainSequence , m_iszEntity , FIELD_STRING ) ,
DEFINE_FIELD ( CTrainSequence , m_iszDestination , FIELD_STRING ) ,
DEFINE_FIELD ( CTrainSequence , m_pDestination , FIELD_CLASSPTR ) ,
DEFINE_FIELD ( CTrainSequence , m_iszTerminate , FIELD_STRING ) ,
DEFINE_FIELD ( CTrainSequence , m_fDuration , FIELD_FLOAT ) ,
DEFINE_FIELD ( CTrainSequence , m_iDirection , FIELD_INTEGER ) ,
DEFINE_FIELD ( CTrainSequence , m_iPostDirection , FIELD_INTEGER ) ,
DEFINE_FIELD ( CTrainSequence , m_pTrain , FIELD_CLASSPTR ) ,
DEFINE_FIELD ( CTrainSequence , m_pTrackTrain , FIELD_CLASSPTR ) ,
} ;
IMPLEMENT_SAVERESTORE ( CTrainSequence , CBaseEntity ) ;
int CTrainSequence : : ObjectCaps ( void )
{
return ( CBaseEntity : : ObjectCaps ( ) & ~ FCAP_ACROSS_TRANSITION ) ;
}
void CTrainSequence : : KeyValue ( KeyValueData * pkvd )
{
if ( FStrEq ( pkvd - > szKeyName , " m_iDirection " ) )
{
m_iDirection = atoi ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else if ( FStrEq ( pkvd - > szKeyName , " m_iPostDirection " ) )
{
m_iPostDirection = atoi ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else if ( FStrEq ( pkvd - > szKeyName , " m_iszEntity " ) )
{
m_iszEntity = ALLOC_STRING ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else if ( FStrEq ( pkvd - > szKeyName , " m_iszDestination " ) )
{
m_iszDestination = ALLOC_STRING ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else if ( FStrEq ( pkvd - > szKeyName , " m_iszTerminate " ) )
{
m_iszTerminate = ALLOC_STRING ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else
CBaseEntity : : KeyValue ( pkvd ) ;
}
void CTrainSequence : : Use ( CBaseEntity * pActivator , CBaseEntity * pCaller , USE_TYPE useType , float value )
{
// ALERT(at_console, "SeqUse\n");
if ( ! ShouldToggle ( useType ) )
{
// ALERT(at_console, "SeqUse, don't toggle\n");
return ;
}
else
{
// ALERT(at_console, "SeqUse ok\n");
}
if ( GetState ( ) = = STATE_OFF )
{
// start the sequence, take control of the train
CBaseEntity * pEnt = UTIL_FindEntityByTargetname ( NULL , STRING ( m_iszEntity ) , pActivator ) ;
if ( pEnt )
{
m_pDestination = UTIL_FindEntityByTargetname ( NULL , STRING ( m_iszDestination ) , pActivator ) ;
if ( pev - > spawnflags & SF_TRAINSEQ_DEBUG )
{
ALERT ( at_console , " trainsequence \" %s \" found train \" %s \" " , STRING ( pev - > targetname ) , STRING ( pEnt - > pev - > targetname ) ) ;
if ( m_pDestination )
ALERT ( at_console , " found destination %s \n " , STRING ( m_pDestination - > pev - > targetname ) ) ;
else
ALERT ( at_console , " missing destination \n " ) ;
}
if ( FStrEq ( STRING ( pEnt - > pev - > classname ) , " func_train " ) )
{
CFuncTrain * pTrain = ( CFuncTrain * ) pEnt ;
// check whether it's being controlled by another sequence
if ( pTrain - > m_pSequence )
{
// ALERT(at_console, "SeqUse: Train sequence already set\n");
return ;
}
// ALERT(at_console, "SeqUse: Train takecontrol\n");
//ok, we can now take control of it.
pTrain - > StartSequence ( this ) ;
m_pTrain = pTrain ;
if ( pev - > spawnflags & SF_TRAINSEQ_DIRECT )
{
pTrain - > pev - > target = m_pDestination - > pev - > targetname ;
pTrain - > Next ( ) ;
}
else
{
int iDir = DIRECTION_NONE ;
switch ( m_iDirection )
{
case DIRECTION_DESTINATION :
if ( m_pDestination )
{
Vector vecFTemp , vecBTemp ;
CBaseEntity * pTrainDest = UTIL_FindEntityByTargetname ( NULL , STRING ( pTrain - > pev - > message ) ) ;
float fForward ;
if ( pTrain - > pev - > spawnflags & SF_TRAIN_SETORIGIN )
fForward = ( pTrainDest - > pev - > origin - pTrain - > pev - > origin ) . Length ( ) ;
else
fForward = ( pTrainDest - > pev - > origin - ( pTrain - > pev - > origin + ( pTrain - > pev - > maxs + pTrain - > pev - > mins ) * 0.5 ) ) . Length ( ) ;
float fBackward = - fForward ; // the further back from the TrainDest entity we are, the shorter the backward distance.
CBaseEntity * pCurForward = pTrainDest ;
CBaseEntity * pCurBackward = m_pDestination ;
vecFTemp = pCurForward - > pev - > origin ;
vecBTemp = pCurBackward - > pev - > origin ;
int loopbreaker = 10 ;
while ( iDir = = DIRECTION_NONE )
{
if ( pCurForward )
{
fForward + = ( pCurForward - > pev - > origin - vecFTemp ) . Length ( ) ;
vecFTemp = pCurForward - > pev - > origin ;
// ALERT(at_console, "SeqUse: Forward %f %s (%p == %p)\n", fForward, STRING(pCurForward->pev->targetname), pCurForward, m_pDestination);
// if we've finished tracing the forward line
if ( pCurForward = = m_pDestination )
{
// if the backward line is longest
if ( fBackward > = fForward | | pCurBackward = = NULL )
iDir = DIRECTION_FORWARDS ;
}
else
{
pCurForward = pCurForward - > GetNextTarget ( ) ;
}
}
if ( pCurBackward )
{
fBackward + = ( pCurBackward - > pev - > origin - vecBTemp ) . Length ( ) ;
vecBTemp = pCurBackward - > pev - > origin ;
// ALERT(at_console, "SeqUse: Backward %f %s (%p == %p)\n", fBackward, STRING(pCurBackward->pev->targetname), pCurBackward, pTrainDest);
// if we've finished tracng the backward line
if ( pCurBackward = = pTrainDest )
{
// if the forward line is shorter
if ( fBackward < fForward | | pCurForward = = NULL )
iDir = DIRECTION_BACKWARDS ;
}
else
{
pCurBackward = pCurBackward - > GetNextTarget ( ) ;
}
}
loopbreaker - - ;
if ( loopbreaker < = 0 )
iDir = DIRECTION_STOP ;
}
}
else
{
iDir = DIRECTION_STOP ;
}
break ;
case DIRECTION_FORWARDS : iDir = DIRECTION_FORWARDS ; break ;
case DIRECTION_BACKWARDS : iDir = DIRECTION_BACKWARDS ; break ;
case DIRECTION_STOP : iDir = DIRECTION_STOP ; break ;
}
// ALERT(at_console, "SeqUse: iDir is %d\n", iDir);
if ( iDir = = DIRECTION_BACKWARDS & & ! ( pTrain - > pev - > spawnflags & SF_TRAIN_REVERSE ) )
{
// ALERT(at_console, "Reversing from \"%s\" \"%s\"\n", STRING(pTrain->pev->target), STRING(pTrain->pev->message));
// change direction
pTrain - > pev - > spawnflags | = SF_TRAIN_REVERSE ;
CBaseEntity * pSearch = m_pDestination ;
while ( pSearch )
{
if ( FStrEq ( STRING ( pSearch - > pev - > target ) , STRING ( pTrain - > pev - > message ) ) )
{
// ALERT(at_console, "SeqUse reverse: pSearch is %s\n", STRING(pSearch->pev->targetname));
CBaseEntity * pTrainTarg = pSearch - > GetNextTarget ( ) ;
if ( pTrainTarg )
pTrain - > pev - > enemy = pTrainTarg - > edict ( ) ;
else
pTrain - > pev - > enemy = NULL ;
pTrain - > pev - > target = pSearch - > pev - > targetname ;
break ;
}
pSearch = pSearch - > GetNextTarget ( ) ;
}
if ( ! pSearch )
{
// this shouldn't happen.
ALERT ( at_error , " Found no path to reach destination! (train has t %s, m %s; dest is %s) \n " , STRING ( pTrain - > pev - > target ) , STRING ( pTrain - > pev - > message ) , STRING ( m_pDestination - > pev - > targetname ) ) ;
return ;
}
pTrain - > m_pevCurrentTarget = NULL ; // we haven't reached the corner, so don't use its settings
// if (pTrain->pev->enemy)
// ALERT(at_console, "SeqUse: pTrain target %s, enemy %s\n", STRING(pTrain->pev->target), STRING(pTrain->pev->enemy->v.targetname));
// else
// ALERT(at_console, "SeqUse: pTrain target %s, no enemy\n", STRING(pTrain->pev->target));
pTrain - > Next ( ) ;
}
else if ( iDir = = DIRECTION_FORWARDS )
{
// ALERT(at_console, "Dir_Forwards targ %s\n", STRING(pTrain->pev->target));
pTrain - > pev - > target = pTrain - > pev - > message ;
pTrain - > Next ( ) ;
}
else if ( iDir = = DIRECTION_STOP )
{
SetNextThink ( 0.1 ) ;
SetThink ( & CTrainSequence : : EndThink ) ;
return ;
}
}
}
else if ( FStrEq ( STRING ( pEnt - > pev - > classname ) , " func_tracktrain " ) )
{
CFuncTrackTrain * pTrackTrain = ( CFuncTrackTrain * ) pEnt ;
// check whether it's being controlled by another sequence
if ( pTrackTrain - > m_pSequence )
return ;
//ok, we can now take control of it.
pTrackTrain - > StartSequence ( this ) ;
m_pTrackTrain = pTrackTrain ;
}
else
{
ALERT ( at_error , " scripted_trainsequence %s can't affect %s \" %s \" : not a train! \n " , STRING ( pev - > targetname ) , STRING ( pEnt - > pev - > classname ) , STRING ( pEnt - > pev - > targetname ) ) ;
return ;
}
}
else // no entity with that name
{
ALERT ( at_error , " Missing train \" %s \" for scripted_trainsequence %s! \n " , STRING ( m_iszEntity ) , STRING ( pev - > targetname ) ) ;
return ;
}
// if we got here, we've set up a sequence successfully.
// do the rest of the setup.
if ( m_fDuration )
{
SetThink ( & CTrainSequence : : TimeOutThink ) ;
SetNextThink ( m_fDuration ) ;
}
// if (m_pTrain)
// ALERT(at_console, "m_pTrain nextthink %f, flags %f\n", STRING(m_pTrain->pev->nextthink), m_pTrain->m_iLFlags);
}
else // prematurely end the sequence
{
//disable the other end conditions
DontThink ( ) ;
// release control of the train
StopSequence ( ) ;
}
}
void CTrainSequence : : ArrivalNotify ( )
{
// ALERT(at_console, "ArrivalNotify\n");
// check whether the current path is our destination,
// and end the sequence if it is.
if ( m_pTrain )
{
if ( m_pTrain - > m_pevCurrentTarget = = m_pDestination - > pev )
{
// we've reached the destination. Stop now.
// ALERT(at_console, "ArrivalNotify %s stop\n", STRING(pev->targetname));
EndThink ( ) ;
}
else
{
// ALERT(at_console, "ArrivalNotify %s continue\n", STRING(pev->targetname));
}
}
else if ( m_pTrackTrain )
{
//...
}
else
{
ALERT ( at_error , " scripted_trainsequence: ArrivalNotify without a train!? \n " ) ;
return ; // this shouldn't happen.
}
}
void CTrainSequence : : EndThink ( )
{
//the sequence has expired. Release control.
StopSequence ( ) ;
FireTargets ( STRING ( pev - > target ) , this , this , USE_TOGGLE , 0 ) ;
}
void CTrainSequence : : TimeOutThink ( )
{
//the sequence has timed out. Release control.
StopSequence ( ) ;
FireTargets ( STRING ( pev - > netname ) , this , this , USE_TOGGLE , 0 ) ;
}
void CTrainSequence : : StopSequence ( )
{
if ( m_pTrain )
{
// ALERT(at_console, "StopSequence called\n");
//stuff...
m_pTrain - > StopSequence ( ) ;
m_pTrain = NULL ;
if ( FBitSet ( pev - > spawnflags , SF_TRAINSEQ_REMOVE ) )
UTIL_Remove ( this ) ;
}
else if ( m_pTrackTrain )
{
//stuff...
}
else
{
ALERT ( at_error , " scripted_trainsequence: StopSequence without a train!? \n " ) ;
return ; // this shouldn't happen.
}
FireTargets ( STRING ( m_iszTerminate ) , this , this , USE_TOGGLE , 0 ) ;
}