Portable Half-Life SDK. GoldSource and Xash3D. Crossplatform.
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.

3215 lines
85 KiB

7 years ago
/***
*
9 years ago
* Copyright (c) 1996-2002, Valve LLC. All rights reserved.
7 years ago
*
* 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");
7 years ago
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; }
7 years ago
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; }
7 years ago
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
static TYPEDESCRIPTION m_SaveData[];
7 years ago
BYTE m_bMoveSnd; // sound a plat makes while moving
BYTE m_bStopSnd; // sound a plat makes when it stops
float m_volume; // Sound volume
7 years ago
};
TYPEDESCRIPTION CBasePlatTrain::m_SaveData[] =
7 years ago
{
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 )
7 years ago
void CBasePlatTrain::KeyValue( KeyValueData *pkvd )
7 years ago
{
if( FStrEq( pkvd->szKeyName, "lip" ) )
7 years ago
{
m_flLip = atof( pkvd->szValue );
7 years ago
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "wait" ) )
7 years ago
{
m_flWait = atof( pkvd->szValue );
7 years ago
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "height" ) )
7 years ago
{
m_flHeight = atof( pkvd->szValue );
7 years ago
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "rotation" ) )
7 years ago
{
m_vecFinalAngle.x = atof( pkvd->szValue );
7 years ago
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "movesnd" ) )
7 years ago
{
m_bMoveSnd = atoi( pkvd->szValue );
7 years ago
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "stopsnd" ) )
7 years ago
{
m_bStopSnd = atoi( pkvd->szValue );
7 years ago
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "custommovesnd" ) )
{
pev->noise = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "customstopsnd" ) )
{
pev->noise1 = ALLOC_STRING(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "volume" ) )
7 years ago
{
m_volume = atof( pkvd->szValue );
7 years ago
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 )
7 years ago
{
case 1:
pszSound = "plats/bigmove1.wav";
9 years ago
break;
case 2:
pszSound = "plats/bigmove2.wav";
9 years ago
break;
case 3:
pszSound = "plats/elevmove1.wav";
9 years ago
break;
case 4:
pszSound = "plats/elevmove2.wav";
9 years ago
break;
case 5:
pszSound = "plats/elevmove3.wav";
9 years ago
break;
case 6:
pszSound = "plats/freightmove1.wav";
9 years ago
break;
case 7:
pszSound = "plats/freightmove2.wav";
9 years ago
break;
case 8:
pszSound = "plats/heavymove1.wav";
9 years ago
break;
case 9:
pszSound = "plats/rackmove1.wav";
9 years ago
break;
case 10:
pszSound = "plats/railmove1.wav";
9 years ago
break;
case 11:
pszSound = "plats/squeekmove1.wav";
9 years ago
break;
case 12:
pszSound = "plats/talkmove1.wav";
9 years ago
break;
case 13:
pszSound = "plats/talkmove2.wav";
9 years ago
break;
case 0:
9 years ago
default:
pszSound = "common/null.wav";
NullSound = TRUE;
9 years ago
break;
7 years ago
}
if( !NullSound )
PRECACHE_SOUND( pszSound );
pev->noiseMoving = MAKE_STRING( pszSound );
NullSound = FALSE;
// set the plat's 'reached destination' stop sound
switch( m_bStopSnd )
7 years ago
{
case 1:
pszSound = "plats/bigstop1.wav";
9 years ago
break;
case 2:
pszSound = "plats/bigstop2.wav";
9 years ago
break;
case 3:
pszSound = "plats/freightstop1.wav";
9 years ago
break;
case 4:
pszSound = "plats/heavystop2.wav";
9 years ago
break;
case 5:
pszSound = "plats/rackstop1.wav";
9 years ago
break;
case 6:
pszSound = "plats/railstop1.wav";
9 years ago
break;
case 7:
pszSound = "plats/squeekstop1.wav";
9 years ago
break;
case 8:
pszSound = "plats/talkstop1.wav";
9 years ago
break;
case 0:
9 years ago
default:
pszSound = "common/null.wav";
NullSound = TRUE;
9 years ago
break;
7 years ago
}
if( !NullSound )
PRECACHE_SOUND( pszSound );
pev->noiseArrived = MAKE_STRING( pszSound );
7 years ago
}
//
//====================== 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(); }
7 years ago
virtual void GoUp( void );
virtual void GoDown( void );
virtual void HitTop( void );
virtual void HitBottom( void );
};
LINK_ENTITY_TO_CLASS( func_plat, CFuncPlat )
7 years ago
// 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; }
7 years ago
void SpawnInsideTrigger( CFuncPlat *pPlatform );
void Touch( CBaseEntity *pOther );
CFuncPlat *m_pPlatform;
7 years ago
EHANDLE m_hPlatform;
7 years ago
};
/*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 )
7 years ago
{
//pev->noiseMovement = MAKE_STRING( "plats/platmove1.wav" );
//pev->noiseStopMoving = MAKE_STRING( "plats/platstop1.wav" );
7 years ago
if( m_flTLength == 0 )
7 years ago
m_flTLength = 80;
if( m_flTWidth == 0 )
7 years ago
m_flTWidth = 10;
pev->angles = g_vecZero;
7 years ago
pev->solid = SOLID_BSP;
pev->movetype = MOVETYPE_PUSH;
7 years ago
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 ) );
7 years ago
// vecPosition1 is the top position, vecPosition2 is the bottom
if (m_pMoveWith)
m_vecPosition1 = pev->origin - m_pMoveWith->pev->origin;
else
9 years ago
m_vecPosition1 = pev->origin;
7 years ago
m_vecPosition2 = m_vecPosition1;
if( m_flHeight != 0 )
7 years ago
m_vecPosition2.z = m_vecPosition2.z - m_flHeight;
else
m_vecPosition2.z = m_vecPosition2.z - pev->size.z + 8;
if( pev->speed == 0 )
7 years ago
pev->speed = 150;
if( m_volume == 0 )
7 years ago
m_volume = 0.85;
}
void CFuncPlat::Precache()
7 years ago
{
CBasePlatTrain::Precache();
//PRECACHE_SOUND( "plats/platmove1.wav" );
//PRECACHE_SOUND( "plats/platstop1.wav" );
if( !IsTogglePlat() )
7 years ago
PlatSpawnInsideTrigger( pev ); // the "start moving" trigger
}
void CFuncPlat::Spawn()
7 years ago
{
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 ) )
7 years ago
{
if (m_pMoveWith)
UTIL_AssignOrigin (this, m_vecPosition1 + m_pMoveWith->pev->origin);
else
UTIL_AssignOrigin (this, m_vecPosition1);
m_toggle_state = TS_AT_TOP;
9 years ago
SetUse( &CFuncPlat::PlatUse );
7 years ago
}
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 )
7 years ago
{
GetClassPtr( (CPlatTrigger *)NULL )->SpawnInsideTrigger( GetClassPtr( (CFuncPlat *)pevPlatform ) );
7 years ago
}
//
// Create a trigger entity for a platform.
//
void CPlatTrigger::SpawnInsideTrigger( CFuncPlat *pPlatform )
7 years ago
{
m_pPlatform = pPlatform;
m_hPlatform = pPlatform;
7 years ago
// Create trigger entity, "point" it at the owning platform, give it a touch method
pev->solid = SOLID_TRIGGER;
pev->movetype = MOVETYPE_NONE;
7 years ago
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 )
7 years ago
{
vecTMin.x = ( pPlatform->pev->mins.x + pPlatform->pev->maxs.x ) / 2;
7 years ago
vecTMax.x = vecTMin.x + 1;
}
if( pPlatform->pev->size.y <= 50 )
7 years ago
{
vecTMin.y = ( pPlatform->pev->mins.y + pPlatform->pev->maxs.y ) / 2;
7 years ago
vecTMax.y = vecTMin.y + 1;
}
UTIL_SetSize( pev, vecTMin, vecTMax );
7 years ago
}
//
// When the platform's trigger field is touched, the platform ???
//
void CPlatTrigger::Touch( CBaseEntity *pOther )
7 years ago
{
// Ignore touches by non-players
entvars_t *pevToucher = pOther->pev;
if( !FClassnameIs( pevToucher, "player" ) )
9 years ago
return;
CFuncPlat *pPlatform = (CFuncPlat*)(CBaseEntity*)m_hPlatform;
7 years ago
if( !pPlatform )
{
// The target platform has been removed, remove myself as well. - Solokiller
UTIL_Remove( this );
7 years ago
return;
}
7 years ago
// Ignore touches by corpses
if( !pOther->IsAlive() )
7 years ago
return;
7 years ago
// 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 )
7 years ago
{
if( IsTogglePlat() )
7 years ago
{
// Top is off, bottom is on
BOOL on = ( m_toggle_state == TS_AT_BOTTOM ) ? TRUE : FALSE;
7 years ago
if( !ShouldToggle( useType, on ) )
7 years ago
return;
if( m_toggle_state == TS_AT_TOP )
7 years ago
{
SetNextThink( 0.01 );
SetThink(&CFuncPlat :: CallGoDown );
}
else if( m_toggle_state == TS_AT_BOTTOM )
7 years ago
{
SetNextThink( 0.01 );
SetThink(&CFuncPlat :: CallGoUp );
}
}
else
{
SetUse( NULL );
if( m_toggle_state == TS_AT_TOP )
7 years ago
{
SetNextThink( 0.01 );
SetThink(&CFuncPlat :: CallGoDown );
}
}
}
//
// Platform is at top, now starts moving down.
//
void CFuncPlat::GoDown( void )
7 years ago
{
if( pev->noiseMovement )
EMIT_SOUND( ENT( pev ), CHAN_STATIC, STRING( pev->noiseMovement ), m_volume, ATTN_NORM );
7 years ago
ASSERT( m_toggle_state == TS_AT_TOP || m_toggle_state == TS_GOING_UP );
7 years ago
m_toggle_state = TS_GOING_DOWN;
SetMoveDone( &CFuncPlat::CallHitBottom );
LinearMove( m_vecPosition2, pev->speed );
7 years ago
}
//
// Platform has hit bottom. Stops and waits forever.
7 years ago
//
void CFuncPlat::HitBottom( void )
7 years ago
{
if( pev->noiseMovement )
STOP_SOUND( ENT( pev ), CHAN_STATIC, STRING( pev->noiseMovement ) );
7 years ago
if( pev->noiseStopMoving )
EMIT_SOUND( ENT( pev ), CHAN_WEAPON, STRING( pev->noiseStopMoving ), m_volume, ATTN_NORM );
7 years ago
ASSERT( m_toggle_state == TS_GOING_DOWN );
7 years ago
m_toggle_state = TS_AT_BOTTOM;
}
//
// Platform is at bottom, now starts moving up
//
void CFuncPlat::GoUp( void )
7 years ago
{
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 );
7 years ago
m_toggle_state = TS_GOING_UP;
SetMoveDone( &CFuncPlat::CallHitTop );
7 years ago
LinearMove(m_vecPosition1, pev->speed);
}
//
// Platform has hit top. Pauses, then starts back down again.
7 years ago
//
void CFuncPlat::HitTop( void )
7 years ago
{
if( pev->noiseMovement )
STOP_SOUND( ENT( pev ), CHAN_STATIC, STRING( pev->noiseMovement ) );
7 years ago
if( pev->noiseStopMoving )
EMIT_SOUND( ENT( pev ), CHAN_WEAPON, STRING( pev->noiseStopMoving ), m_volume, ATTN_NORM );
ASSERT( m_toggle_state == TS_GOING_UP );
7 years ago
m_toggle_state = TS_AT_TOP;
if( !IsTogglePlat() )
7 years ago
{
// After a delay, the platform will automatically start going down again.
9 years ago
SetThink( &CFuncPlat::CallGoDown );
7 years ago
SetNextThink( 3 );
}
}
void CFuncPlat::Blocked( CBaseEntity *pOther )
7 years ago
{
ALERT( at_aiconsole, "%s Blocked by %s\n", STRING( pev->classname ), STRING( pOther->pev->classname ) );
7 years ago
// Hurt the blocker a little
pOther->TakeDamage( pev, pev, 1, DMG_CRUSH );
if( pev->noiseMovement )
STOP_SOUND( ENT( pev ), CHAN_STATIC, STRING( pev->noiseMovement ) );
7 years ago
// 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 )
7 years ago
{
SetNextThink( 0 );
SetThink(&CFuncPlat :: GoDown );
}
else if( m_toggle_state == TS_GOING_DOWN )
7 years ago
{
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 );
9 years ago
void RotMove( Vector &destAngle, float time );
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
static TYPEDESCRIPTION m_SaveData[];
7 years ago
Vector m_end, m_start;
7 years ago
};
LINK_ENTITY_TO_CLASS( func_platrot, CFuncPlatRot )
TYPEDESCRIPTION CFuncPlatRot::m_SaveData[] =
7 years ago
{
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 )
7 years ago
{
if( m_vecFinalAngle.x != 0 ) // This plat rotates too!
7 years ago
{
CBaseToggle::AxisDir( pev );
7 years ago
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
7 years ago
{
UTIL_SetAngles(this, m_end);
//pev->angles = m_end;
}
}
void CFuncPlatRot::Spawn( void )
7 years ago
{
CFuncPlat::Spawn();
7 years ago
SetupRotation();
}
void CFuncPlatRot::GoDown( void )
7 years ago
{
CFuncPlat::GoDown();
7 years ago
RotMove( m_start, m_fNextThink - pev->ltime );
}
//
// Platform has hit bottom. Stops and waits forever.
7 years ago
//
void CFuncPlatRot::HitBottom( void )
7 years ago
{
CFuncPlat::HitBottom();
7 years ago
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 )
7 years ago
{
CFuncPlat::GoUp();
7 years ago
RotMove( m_end, m_fNextThink - pev->ltime );
}
//
// Platform has hit top. Pauses, then starts back down again.
7 years ago
//
void CFuncPlatRot::HitTop( void )
7 years ago
{
CFuncPlat::HitTop();
7 years ago
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 )
7 years ago
{
// 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 )
7 years ago
{
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[];
7 years ago
entvars_t *m_pevCurrentTarget;
int m_sounds;
7 years ago
//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[] =
7 years ago
{
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 )
7 years ago
void CFuncTrain::KeyValue( KeyValueData *pkvd )
7 years ago
{
if( FStrEq( pkvd->szKeyName, "sounds" ) )
7 years ago
{
m_sounds = atoi( pkvd->szValue );
7 years ago
pkvd->fHandled = TRUE;
}
else
CBasePlatTrain::KeyValue( pkvd );
}
void CFuncTrain::Blocked( CBaseEntity *pOther )
7 years ago
{
// Keep "movewith" entities in line
UTIL_AssignOrigin(this, pev->origin);
if( gpGlobals->time < m_flActivateFinished )
7 years ago
return;
m_flActivateFinished = gpGlobals->time + 0.5;
if (pev->dmg)
pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH );
7 years ago
}
void CFuncTrain::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
7 years ago
{
if ( ShouldToggle( useType ) )
{
if( pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER )
9 years ago
{
// Move toward my target
7 years ago
// ALERT(at_console, "Unset Retrigger (use)\n");
9 years ago
pev->spawnflags &= ~SF_TRAIN_WAIT_RETRIGGER;
Next();
}
else
{
7 years ago
// ALERT(at_console, "Set Retrigger (use)\n");
9 years ago
pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER;
// Pop back to last target if it's available
if( pev->enemy )
9 years ago
pev->target = pev->enemy->v.targetname;
7 years ago
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) );
7 years ago
if( pev->noiseStopMoving )
EMIT_SOUND (ENT(pev), CHAN_VOICE, STRING(pev->noiseStopMoving), m_volume, ATTN_NORM);
7 years ago
}
}
}
void CFuncTrain::Wait( void )
7 years ago
{
// 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 )
7 years ago
{
FireTargets( STRING( m_pevCurrentTarget->message ), this, this, USE_TOGGLE, 0 );
if( FBitSet( m_pevCurrentTarget->spawnflags, SF_CORNER_FIREONCE ) )
7 years ago
m_pevCurrentTarget->message = 0;
}
// need pointer to LAST target.
if( FBitSet( m_pevCurrentTarget->spawnflags, SF_TRAIN_WAIT_RETRIGGER ) || ( pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER ) )
{
7 years ago
// 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);
7 years ago
UTIL_SetVelocity(this, g_vecZero);
UTIL_SetAvelocity(this, g_vecZero);
DontThink();
return;
}
// ALERT( at_console, "%f\n", m_flWait );
if( m_flWait != 0 )
7 years ago
{// -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 );
9 years ago
SetThink( &CFuncTrain::Next );
7 years ago
// 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 )
7 years ago
{
CBaseEntity *pTarg;
9 years ago
7 years ago
// now find our next target
pTarg = GetNextTarget();
if( !pTarg )
7 years ago
{
// 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 ) );
7 years ago
// Play stop sound
if( pev->noiseStopMoving )
EMIT_SOUND( ENT( pev ), CHAN_VOICE, STRING( pev->noiseStopMoving ), m_volume, ATTN_NORM );
7 years ago
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
{
9 years ago
pev->target = pTarg->pev->target;
7 years ago
}
// 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))
{
7 years ago
case PATHSPEED_SET:
pev->speed = m_pevCurrentTarget->speed;
7 years ago
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
7 years ago
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 );
7 years ago
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.
9 years ago
7 years ago
// 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);
7 years ago
}
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 );
9 years ago
}
7 years ago
// 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)
9 years ago
{
7 years ago
pevTarg = pTarget->pev;
}
else
{
ALERT(at_console, "Missing train target \"%s\"\n", STRING(pev->target));
7 years ago
return;
}
pev->message = pevTarg->targetname; //LRC - record the old target so that we can find it again
9 years ago
pev->target = pevTarg->target;
m_pevCurrentTarget = pevTarg;// keep track of this since path corners change our target for us.
7 years ago
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)
9 years ago
{ // not triggered, so start immediately
7 years ago
SetNextThink( 1.5 );
// SetThink( Next );
SetThink(&CFuncTrain :: ThinkDoNext );
9 years ago
}
else
7 years ago
{
// ALERT(at_console, "Set Retrigger (postspawn)\n");
9 years ago
pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER;
7 years ago
}
// 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 )
7 years ago
{
Precache();
if( pev->speed == 0 )
7 years ago
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 )
7 years ago
pev->dmg = 2;
else if (pev->dmg == -1) //LRC- a train that doesn't crush people!
pev->dmg = 0;
7 years ago
pev->movetype = MOVETYPE_PUSH;
if( FBitSet( pev->spawnflags, SF_TRACKTRAIN_PASSABLE ) )
pev->solid = SOLID_NOT;
7 years ago
else
pev->solid = SOLID_BSP;
7 years ago
SET_MODEL( ENT( pev ), STRING( pev->model ) );
UTIL_SetSize( pev, pev->mins, pev->maxs );
7 years ago
UTIL_SetOrigin(this, pev->origin);
m_iState = STATE_OFF;
if( m_volume == 0 )
7 years ago
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);
7 years ago
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();
}
9 years ago
void CFuncTrain::Precache( void )
7 years ago
{
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
7 years ago
// otherwise use preset sound
switch( m_sounds )
7 years ago
{
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" );
7 years ago
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" );
7 years ago
break;
}
#endif
}
void CFuncTrain::OverrideReset( void )
{
CBaseEntity *pTarg;
7 years ago
// 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 )
7 years ago
{
DontThink();
UTIL_SetVelocity(this, g_vecZero);
m_iState = STATE_OFF;
}
else // Keep moving for 0.1 secs, then find path_corner again and restart
{
9 years ago
SetThink( &CFuncTrain::Next );
7 years ago
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" );
7 years ago
else
ALERT( at_console, "func_tracktrain %s has no target\n", STRING(pev->targetname));
7 years ago
}
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));
7 years ago
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 )
7 years ago
void CFuncTrackTrain::KeyValue( KeyValueData *pkvd )
7 years ago
{
if( FStrEq( pkvd->szKeyName, "wheels" ) )
7 years ago
{
m_length = atof( pkvd->szValue );
7 years ago
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "height" ) )
7 years ago
{
m_height = atof(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "startspeed" ) )
7 years ago
{
m_startSpeed = atof(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "sounds" ) )
7 years ago
{
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" ) )
7 years ago
{
m_flVolume = (float)atoi( pkvd->szValue );
7 years ago
m_flVolume *= 0.1;
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "bank" ) )
7 years ago
{
m_flBank = atof( pkvd->szValue );
7 years ago
pkvd->fHandled = TRUE;
}
else
CBaseEntity::KeyValue( pkvd );
}
void CFuncTrackTrain::NextThink( float thinkTime, BOOL alwaysThink )
7 years ago
{
if( alwaysThink )
7 years ago
// 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 )
7 years ago
{
entvars_t *pevOther = pOther->pev;
7 years ago
// Blocker is on-ground on the train
if( FBitSet( pevOther->flags, FL_ONGROUND ) && VARS( pevOther->groundentity ) == pev )
7 years ago
{
float deltaSpeed = fabs( pev->speed );
if( deltaSpeed > 50 )
7 years ago
deltaSpeed = 50;
if( !pevOther->velocity.z )
7 years ago
pevOther->velocity.z += deltaSpeed;
return;
}
else
pevOther->velocity = ( pevOther->origin - pev->origin ).Normalize() * pev->dmg;
7 years ago
ALERT( at_aiconsole, "TRAIN(%s): Blocked by %s (dmg:%.2f)\n", STRING( pev->targetname ), STRING( pOther->pev->classname ), pev->dmg );
if( pev->dmg <= 0 )
7 years ago
return;
// we can't hurt this thing, so we're not concerned with it
pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH );
7 years ago
}
void CFuncTrackTrain::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
7 years ago
{
// ALERT(at_console, "TRAIN: use\n");
7 years ago
if( useType != USE_SET )
7 years ago
{
if( !ShouldToggle( useType, ( pev->speed != 0 ) ) )
7 years ago
return;
if( pev->speed == 0 )
7 years ago
{
pev->speed = m_speed * m_dir;
7 years ago
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 )
7 years ago
delta = 1;
else if ( delta < -1 )
delta = -1;
if( pev->spawnflags & SF_TRACKTRAIN_FORWARDONLY )
7 years ago
{
if( delta < 0 )
7 years ago
delta = 0;
}
pev->speed = m_speed * delta;
if ( pev->spawnflags & SF_TRACKTRAIN_AVEL_GEARS )
9 years ago
{
7 years ago
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 );
9 years ago
}
7 years ago
}
#define TRAIN_STARTPITCH 60
#define TRAIN_MAXPITCH 200
#define TRAIN_MAXSPEED 1000 // approx max speed for sound pitch calculation
void CFuncTrackTrain::StopSound( void )
7 years ago
{
// if sound playing, stop it
if( m_soundPlaying && pev->noise )
7 years ago
{
if (m_sounds) //LRC - flashy event-based method, for normal sounds.
{
9 years ago
unsigned short us_encode;
unsigned short us_sound = ( (unsigned short)( m_sounds ) & 0x0007 ) << 12;
7 years ago
9 years ago
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 );
7 years ago
}
else
{
//LRC - down-to-earth method, for custom sounds.
STOP_SOUND(ENT(pev), CHAN_STATIC, STRING(pev->noise));
7 years ago
}
EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, STRING(pev->noise1), m_flVolume, ATTN_NORM, 0, 100);
7 years ago
}
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 )
7 years ago
{
float flpitch;
if( !pev->noise )
7 years ago
return;
flpitch = TRAIN_STARTPITCH + ( fabs( pev->speed ) * ( TRAIN_MAXPITCH - TRAIN_STARTPITCH ) / TRAIN_MAXSPEED );
if( !m_soundPlaying )
7 years ago
{
// 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 );
7 years ago
m_soundPlaying = 1;
}
else
{
9 years ago
/*
// update pitch
EMIT_SOUND_DYN( ENT( pev ), CHAN_STATIC, STRING( pev->noise ), m_flVolume, ATTN_NORM, SND_CHANGE_PITCH, (int)flpitch );
9 years ago
*/
7 years ago
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,
7 years ago
(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);
7 years ago
}
}
}
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 )
7 years ago
{
// 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 ) );
7 years ago
StopSound();
return;
}
//if( !m_ppath )
7 years ago
// m_ppath = UTIL_FindEntityByTargetname( NULL, STRING(pev->target) );
if( !m_ppath )
7 years ago
{
// ALERT(at_console, "TRAIN: no path\n");
7 years ago
UTIL_SetVelocity(this, g_vecZero);
DontThink();
ALERT( at_aiconsole, "TRAIN(%s): Lost path\n", STRING( pev->targetname ) );
7 years ago
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);
7 years ago
//pev->velocity = (nextPos - pev->origin) * 10;
Vector nextFront = pev->origin;
nextFront.z -= m_height;
if( m_length > 0 )
7 years ago
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
{
9 years ago
Vector delta = nextFront - pev->origin;
Vector angles = UTIL_VecToAngles( delta );
// The train actually points west
7 years ago
angles.y += 180; //LRC, FIXME: add a 'built facing' field.
9 years ago
// !!! All of this crap has to be done to make the angles not wrap around, revisit this.
FixupAngles( angles );
FixupAngles( pev->angles );
7 years ago
if( !pnext || ( delta.x == 0 && delta.y == 0 ) )
9 years ago
angles = pev->angles;
7 years ago
float vy, vx, vz;
if( !( pev->spawnflags & SF_TRACKTRAIN_NOPITCH ) )
7 years ago
vx = 10*UTIL_AngleDistance( angles.x, pev->angles.x );
9 years ago
else
7 years ago
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 )
9 years ago
{
if( pev->avelocity.y < -5 )
7 years ago
vz = UTIL_AngleDistance( UTIL_ApproachAngle( -m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z);
else if( pev->avelocity.y > 5 )
7 years ago
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 )
7 years ago
{
if( pnext != m_ppath )
7 years ago
{
// 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);
7 years ago
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 )
7 years ago
{
FireTargets( STRING( pFire->pev->message ), this, this, USE_TOGGLE, 0 );
if( FBitSet( pFire->pev->spawnflags, SF_PATH_FIREONCE ) )
7 years ago
pFire->pev->message = 0;
}
if( pFire->pev->spawnflags & SF_PATH_DISABLE_TRAIN )
7 years ago
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 )
{
7 years ago
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));
7 years ago
//LRC
// don't look at speed from target if it is 0 (uninitialized)
if( pFire->pev->speed != 0 )
{
7 years ago
//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);
7 years ago
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;
}
}
9 years ago
7 years ago
//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);
}
7 years ago
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");
7 years ago
// }
SetThink(&CFuncTrackTrain :: PostponeNext );
NextThink( time, TRUE );
}
else // end of path, stop
7 years ago
{
Vector vecTemp; //LRC
StopSound();
vecTemp = (nextPos - pev->origin); //LRC
// ALERT(at_console, "TRAIN: path end\n");
7 years ago
// 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;
7 years ago
// Move to the dead end
7 years ago
// Are we there yet?
if( distance > 0 )
7 years ago
{
// 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);
9 years ago
SetThink( &CFuncTrackTrain::DeadEnd );
7 years ago
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 ) );
7 years ago
// 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 )
7 years ago
{
if( m_oldSpeed < 0 )
7 years ago
{
do
{
pNext = pTrack->ValidPath( pTrack->GetPrevious(), TRUE );
if( pNext )
7 years ago
pTrack = pNext;
} while( pNext );
7 years ago
}
else
{
do
{
pNext = pTrack->ValidPath( pTrack->GetNext(), TRUE );
if( pNext )
7 years ago
pTrack = pNext;
} while( pNext );
7 years ago
}
}
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 )
7 years ago
{
ALERT( at_aiconsole, "at %s\n", STRING( pTrack->pev->targetname ) );
if( pTrack->pev->netname )
FireTargets( STRING( pTrack->pev->netname ), this, this, USE_TOGGLE, 0 );
7 years ago
}
else
ALERT( at_aiconsole, "\n" );
}
void CFuncTrackTrain::SetControls( entvars_t *pevControls )
7 years ago
{
Vector offset = pevControls->origin - pev->oldorigin;
m_controlMins = pevControls->mins + offset;
m_controlMaxs = pevControls->maxs + offset;
}
BOOL CFuncTrackTrain::OnControls( entvars_t *pevTest )
7 years ago
{
Vector offset = pevTest->origin - pev->origin;
if( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL )
7 years ago
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 &&
7 years ago
local.x <= m_controlMaxs.x && local.y <= m_controlMaxs.y && local.z <= m_controlMaxs.z )
return TRUE;
return FALSE;
}
void CFuncTrackTrain::Find( void )
7 years ago
{
m_ppath = (CPathTrack*)UTIL_FindEntityByTargetname( NULL, STRING(pev->target) );
if( !m_ppath )
7 years ago
return;
entvars_t *pevTarget = m_ppath->pev;
if( !FClassnameIs( pevTarget, "path_track" ) )
7 years ago
{
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 )
7 years ago
{
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 )
7 years ago
{
CBaseEntity *pTrack = NULL;
CBaseEntity *pNearest = NULL;
float dist, closest;
closest = 1024;
while( ( pTrack = UTIL_FindEntityInSphere( pTrack, pev->origin, 1024 ) ) != NULL )
7 years ago
{
// filter out non-tracks
if( !( pTrack->pev->flags & ( FL_CLIENT | FL_MONSTER ) ) && FClassnameIs( pTrack->pev, "path_track" ) )
7 years ago
{
dist = ( pev->origin - pTrack->pev->origin ).Length();
if( dist < closest )
7 years ago
{
closest = dist;
pNearest = pTrack;
}
}
}
if( !pNearest )
7 years ago
{
9 years ago
ALERT( at_console, "Can't find a nearby track !!!\n" );
SetThink( NULL );
7 years ago
return;
}
ALERT( at_aiconsole, "TRAIN: %s, Nearest track is %s\n", STRING( pev->targetname ), STRING( pNearest->pev->targetname ) );
7 years ago
// If I'm closer to the next path_track on this path, then it's my real path
pTrack = ( (CPathTrack *)pNearest )->GetNext();
if( pTrack )
7 years ago
{
if( ( pev->origin - pTrack->pev->origin ).Length() < ( pev->origin - pNearest->pev->origin ).Length() )
7 years ago
pNearest = pTrack;
}
m_ppath = (CPathTrack *)pNearest;
if( pev->speed != 0 )
7 years ago
{
NextThink( 0.1, FALSE );
SetThink(&CFuncTrackTrain :: PostponeNext );
}
}
void CFuncTrackTrain::OverrideReset( void )
{
NextThink( 0.1, FALSE );
9 years ago
SetThink( &CFuncTrackTrain::NearestPath );
7 years ago
}
CFuncTrackTrain *CFuncTrackTrain::Instance( edict_t *pent )
{
if( FClassnameIs( pent, "func_tracktrain" ) )
return (CFuncTrackTrain *)GET_PRIVATE( pent );
7 years ago
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; }
7 years ago
void Spawn( void );
void EXPORT Find( void );
};
LINK_ENTITY_TO_CLASS( func_traincontrols, CFuncTrainControls )
7 years ago
void CFuncTrainControls::Find( void )
7 years ago
{
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 ) );
7 years ago
return;
}
CFuncTrackTrain *ptrain = (CFuncTrackTrain*)pTarget;
ptrain->SetControls( pev );
UTIL_Remove( this );
}
void CFuncTrainControls::Spawn( void )
7 years ago
{
pev->solid = SOLID_NOT;
pev->movetype = MOVETYPE_NONE;
SET_MODEL( ENT( pev ), STRING( pev->model ) );
7 years ago
UTIL_SetSize( pev, pev->mins, pev->maxs );
UTIL_SetOrigin( this, pev->origin );
9 years ago
SetThink( &CFuncTrainControls::Find );
7 years ago
SetNextThink( 0 );
}
// ----------------------------------------------------------------------------
//
// Track changer / Train elevator
//
// ----------------------------------------------------------------------------
#define SF_TRACK_ACTIVATETRAIN 0x00000001
7 years ago
#define SF_TRACK_RELINK 0x00000002
#define SF_TRACK_ROTMOVE 0x00000004
#define SF_TRACK_STARTBOTTOM 0x00000008
7 years ago
#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 );
7 years ago
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; }
7 years ago
void DisableUse( void ) { m_use = 0; }
void EnableUse( void ) { m_use = 1; }
int UseEnabled( void ) { return m_use; }
7 years ago
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
static TYPEDESCRIPTION m_SaveData[];
7 years ago
virtual void OverrideReset( void );
7 years ago
CPathTrack *m_trackTop;
CPathTrack *m_trackBottom;
7 years ago
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;
7 years ago
};
LINK_ENTITY_TO_CLASS( func_trackchange, CFuncTrackChange )
TYPEDESCRIPTION CFuncTrackChange::m_SaveData[] =
7 years ago
{
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 )
7 years ago
void CFuncTrackChange::Spawn( void )
7 years ago
{
Setup();
if( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) )
7 years ago
m_vecPosition2.z = pev->origin.z;
SetupRotation();
if( FBitSet( pev->spawnflags, SF_TRACK_STARTBOTTOM ) )
7 years ago
{
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;
9 years ago
SetThink( &CFuncTrackChange::Find );
7 years ago
Precache();
}
void CFuncTrackChange::Precache( void )
7 years ago
{
// Can't trigger sound
PRECACHE_SOUND( "buttons/button11.wav" );
7 years ago
CFuncPlatRot::Precache();
}
// UNDONE: Filter touches before re-evaluating the train.
void CFuncTrackChange::Touch( CBaseEntity *pOther )
7 years ago
{
#if 0
TRAIN_CODE code;
entvars_t *pevToucher = pOther->pev;
#endif
}
void CFuncTrackChange::KeyValue( KeyValueData *pkvd )
7 years ago
{
if( FStrEq( pkvd->szKeyName, "train" ) )
7 years ago
{
m_trainName = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "toptrack" ) )
7 years ago
{
m_trackTopName = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "bottomtrack" ) )
7 years ago
{
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;
9 years ago
SetThink( &CFuncTrackChange::Find );
7 years ago
}
void CFuncTrackChange::Find( void )
7 years ago
{
// 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;
7 years ago
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 ) );
7 years ago
}
else
ALERT( at_error, "Can't find bottom track for track change! %s\n", STRING( m_trackBottomName ) );
7 years ago
}
else
ALERT( at_error, "Can't find top track for track change! %s\n", STRING( m_trackTopName ) );
7 years ago
}
TRAIN_CODE CFuncTrackChange::EvaluateTrain( CPathTrack *pcurrent )
7 years ago
{
// Go ahead and work, we don't have anything to switch, so just be an elevator
if( !pcurrent || !m_train )
7 years ago
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 ) )
7 years ago
{
if( m_train->pev->speed != 0 )
7 years ago
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
7 years ago
return TRAIN_FOLLOWING;
else if( length > ( 150 + m_train->m_length ) )
7 years ago
return TRAIN_SAFE;
return TRAIN_BLOCKING;
}
7 years ago
return TRAIN_SAFE;
}
void CFuncTrackChange::UpdateTrain( Vector &dest )
7 years ago
{
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 )
7 years ago
{
// 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 )
7 years ago
{
if( m_code == TRAIN_BLOCKING )
7 years ago
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 ) )
7 years ago
{
SetMoveDone(&CFuncTrackChange :: CallHitBottom );
m_toggle_state = TS_GOING_DOWN;
AngularMove( m_start, pev->speed );
}
else
{
CFuncPlat::GoDown();
7 years ago
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 )
7 years ago
{
UpdateTrain( m_start );
m_train->m_ppath = NULL;
}
}
//
// Platform is at bottom, now starts moving up
//
void CFuncTrackChange::GoUp( void )
7 years ago
{
if( m_code == TRAIN_BLOCKING )
7 years ago
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 ) )
7 years ago
{
m_toggle_state = TS_GOING_UP;
SetMoveDone(&CFuncTrackChange :: CallHitTop );
AngularMove( m_end, pev->speed );
}
else
{
// If ROTMOVE, move & rotate
CFuncPlat::GoUp();
7 years ago
SetMoveDone(&CFuncTrackChange :: CallHitTop );
RotMove( m_end, pev->nextthink - pev->ltime );
}
7 years ago
// Otherwise, move first, rotate second
// If the train is moving with the platform, update it
if( m_code == TRAIN_FOLLOWING )
7 years ago
{
UpdateTrain( m_end );
m_train->m_ppath = NULL;
}
}
// Normal track change
void CFuncTrackChange::UpdateAutoTargets( int toggleState )
7 years ago
{
if( !m_trackTop || !m_trackBottom )
7 years ago
return;
if( toggleState == TS_AT_TOP )
7 years ago
ClearBits( m_trackTop->pev->spawnflags, SF_PATH_DISABLED );
else
SetBits( m_trackTop->pev->spawnflags, SF_PATH_DISABLED );
if( toggleState == TS_AT_BOTTOM )
7 years ago
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 )
7 years ago
{
if( m_toggle_state != TS_AT_TOP && m_toggle_state != TS_AT_BOTTOM )
7 years ago
return;
// If train is in "safe" area, but not on the elevator, play alarm sound
if( m_toggle_state == TS_AT_TOP )
7 years ago
m_code = EvaluateTrain( m_trackTop );
else if( m_toggle_state == TS_AT_BOTTOM )
7 years ago
m_code = EvaluateTrain( m_trackBottom );
else
m_code = TRAIN_BLOCKING;
if( m_code == TRAIN_BLOCKING )
7 years ago
{
// Play alarm and return
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "buttons/button11.wav", 1, ATTN_NORM );
7 years ago
return;
}
// Otherwise, it's safe to move
// If at top, go down
// at bottom, go up
DisableUse();
if( m_toggle_state == TS_AT_TOP )
7 years ago
GoDown();
else
GoUp();
}
//
// Platform has hit bottom. Stops and waits forever.
//
void CFuncTrackChange::HitBottom( void )
7 years ago
{
CFuncPlatRot::HitBottom();
if( m_code == TRAIN_FOLLOWING )
7 years ago
{
//UpdateTrain();
7 years ago
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 )
7 years ago
{
CFuncPlatRot::HitTop();
if( m_code == TRAIN_FOLLOWING )
7 years ago
{
//UpdateTrain();
7 years ago
m_train->SetTrack( m_trackTop );
}
7 years ago
// 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 );
7 years ago
virtual void UpdateAutoTargets( int toggleState );
};
LINK_ENTITY_TO_CLASS( func_trackautochange, CFuncTrackAuto )
7 years ago
// Auto track change
void CFuncTrackAuto::UpdateAutoTargets( int toggleState )
7 years ago
{
CPathTrack *pTarget, *pNextTarget;
if( !m_trackTop || !m_trackBottom )
7 years ago
return;
if( m_targetState == TS_AT_TOP )
7 years ago
{
pTarget = m_trackTop->GetNext();
pNextTarget = m_trackBottom->GetNext();
}
else
{
pTarget = m_trackBottom->GetNext();
pNextTarget = m_trackTop->GetNext();
}
if( pTarget )
7 years ago
{
ClearBits( pTarget->pev->spawnflags, SF_PATH_DISABLED );
if( m_code == TRAIN_FOLLOWING && m_train && m_train->pev->speed == 0 )
7 years ago
m_train->Use( this, this, USE_ON, 0 );
}
if( pNextTarget )
7 years ago
SetBits( pNextTarget->pev->spawnflags, SF_PATH_DISABLED );
}
void CFuncTrackAuto::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
7 years ago
{
CPathTrack *pTarget;
if( !UseEnabled() )
7 years ago
return;
if( m_toggle_state == TS_AT_TOP )
7 years ago
pTarget = m_trackTop;
else if( m_toggle_state == TS_AT_BOTTOM )
7 years ago
pTarget = m_trackBottom;
else
pTarget = NULL;
if( FClassnameIs( pActivator->pev, "func_tracktrain" ) )
7 years ago
{
m_code = EvaluateTrain( pTarget );
7 years ago
// Safe to fire?
if( m_code == TRAIN_FOLLOWING && m_toggle_state != m_targetState )
7 years ago
{
DisableUse();
if( m_toggle_state == TS_AT_TOP )
7 years ago
GoDown();
else
GoUp();
}
}
else
{
if( pTarget )
7 years ago
pTarget = pTarget->GetNext();
if( pTarget && m_train->m_ppath != pTarget && ShouldToggle( useType, m_targetState ) )
7 years ago
{
if( m_targetState == TS_AT_TOP )
7 years ago
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; }
9 years ago
virtual int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
7 years ago
static TYPEDESCRIPTION m_SaveData[];
7 years ago
private:
BOOL m_on;
7 years ago
};
LINK_ENTITY_TO_CLASS( func_guntarget, CGunTarget )
7 years ago
TYPEDESCRIPTION CGunTarget::m_SaveData[] =
{
DEFINE_FIELD( CGunTarget, m_on, FIELD_BOOLEAN ),
};
IMPLEMENT_SAVERESTORE( CGunTarget, CBaseMonster )
7 years ago
void CGunTarget::Spawn( void )
{
pev->solid = SOLID_BSP;
pev->movetype = MOVETYPE_PUSH;
7 years ago
UTIL_SetOrigin(this, pev->origin);
SET_MODEL( ENT( pev ), STRING( pev->model ) );
7 years ago
if( pev->speed == 0 )
7 years ago
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 )
7 years ago
{
9 years ago
SetThink( &CGunTarget::Start );
7 years ago
SetNextThink( 0.3 );
}
}
void CGunTarget::Activate( void )
{
CBaseEntity *pTarg;
7 years ago
// now find our next target
pTarg = GetNextTarget();
if( pTarg )
7 years ago
{
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 )
7 years ago
{
Stop();
return;
}
9 years ago
SetMoveDone( &CGunTarget::Wait );
LinearMove( pTarget->pev->origin - ( pev->mins + pev->maxs ) * 0.5, pev->speed );
7 years ago
}
void CGunTarget::Wait( void )
{
CBaseEntity *pTarget = m_hTargetEnt;
if( !pTarget )
7 years ago
{
Stop();
return;
}
// Fire the pass target if there is one
if( pTarget->pev->message )
7 years ago
{
FireTargets( STRING(pTarget->pev->message), this, this, USE_TOGGLE, 0 );
if( FBitSet( pTarget->pev->spawnflags, SF_CORNER_FIREONCE ) )
7 years ago
pTarget->pev->message = 0;
}
7 years ago
m_flWait = pTarget->GetDelay();
pev->target = pTarget->pev->target;
9 years ago
SetThink( &CGunTarget::Next );
if( m_flWait != 0 )
7 years ago
{// -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 )
7 years ago
{
if( pev->health > 0 )
7 years ago
{
pev->health -= flDamage;
if( pev->health <= 0 )
7 years ago
{
pev->health = 0;
Stop();
if( pev->message )
FireTargets( STRING( pev->message ), this, this, USE_TOGGLE, 0 );
7 years ago
}
}
return 0;
}
void CGunTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
if( !ShouldToggle( useType, m_on ) )
7 years ago
return;
if( m_on )
7 years ago
{
Stop();
}
else
{
pev->takedamage = DAMAGE_AIM;
m_hTargetEnt = GetNextTarget();
if( m_hTargetEnt == 0 )
7 years ago
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);
}