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.

2239 lines
56 KiB

9 years ago
/***
*
* 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 "game.h"
9 years ago
static void PlatSpawnInsideTrigger(entvars_t* pevPlatform);
#define SF_PLAT_TOGGLE 0x0001
class CBasePlatTrain : public CBaseToggle
{
public:
virtual int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
9 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; }
9 years ago
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
static TYPEDESCRIPTION m_SaveData[];
9 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
9 years ago
};
TYPEDESCRIPTION CBasePlatTrain::m_SaveData[] =
9 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 )
9 years ago
void CBasePlatTrain::KeyValue( KeyValueData *pkvd )
9 years ago
{
if( FStrEq( pkvd->szKeyName, "lip" ) )
9 years ago
{
m_flLip = atof( pkvd->szValue );
9 years ago
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "wait" ) )
9 years ago
{
m_flWait = atof( pkvd->szValue );
9 years ago
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "height" ) )
9 years ago
{
m_flHeight = atof( pkvd->szValue );
9 years ago
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "rotation" ) )
9 years ago
{
m_vecFinalAngle.x = atof( pkvd->szValue );
9 years ago
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "movesnd" ) )
9 years ago
{
m_bMoveSnd = atof( pkvd->szValue );
9 years ago
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "stopsnd" ) )
9 years ago
{
m_bStopSnd = atof( pkvd->szValue );
9 years ago
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "volume" ) )
9 years ago
{
m_volume = atof( pkvd->szValue );
9 years ago
pkvd->fHandled = TRUE;
}
else
CBaseToggle::KeyValue( pkvd );
}
#define noiseMoving noise
#define noiseArrived noise1
void CBasePlatTrain::Precache( void )
{
// set the plat's "in-motion" sound
switch( m_bMoveSnd )
9 years ago
{
case 0:
pev->noiseMoving = MAKE_STRING( "common/null.wav" );
9 years ago
break;
case 1:
PRECACHE_SOUND( "plats/bigmove1.wav" );
pev->noiseMoving = MAKE_STRING( "plats/bigmove1.wav" );
9 years ago
break;
case 2:
PRECACHE_SOUND( "plats/bigmove2.wav" );
pev->noiseMoving = MAKE_STRING( "plats/bigmove2.wav" );
9 years ago
break;
case 3:
PRECACHE_SOUND( "plats/elevmove1.wav" );
pev->noiseMoving = MAKE_STRING( "plats/elevmove1.wav" );
9 years ago
break;
case 4:
PRECACHE_SOUND( "plats/elevmove2.wav" );
pev->noiseMoving = MAKE_STRING( "plats/elevmove2.wav" );
9 years ago
break;
case 5:
PRECACHE_SOUND( "plats/elevmove3.wav" );
pev->noiseMoving = MAKE_STRING( "plats/elevmove3.wav" );
9 years ago
break;
case 6:
PRECACHE_SOUND( "plats/freightmove1.wav" );
pev->noiseMoving = MAKE_STRING( "plats/freightmove1.wav" );
9 years ago
break;
case 7:
PRECACHE_SOUND( "plats/freightmove2.wav" );
pev->noiseMoving = MAKE_STRING( "plats/freightmove2.wav" );
9 years ago
break;
case 8:
PRECACHE_SOUND( "plats/heavymove1.wav" );
pev->noiseMoving = MAKE_STRING( "plats/heavymove1.wav" );
9 years ago
break;
case 9:
PRECACHE_SOUND( "plats/rackmove1.wav" );
pev->noiseMoving = MAKE_STRING( "plats/rackmove1.wav" );
9 years ago
break;
case 10:
PRECACHE_SOUND( "plats/railmove1.wav" );
pev->noiseMoving = MAKE_STRING( "plats/railmove1.wav" );
9 years ago
break;
case 11:
PRECACHE_SOUND( "plats/squeekmove1.wav" );
pev->noiseMoving = MAKE_STRING( "plats/squeekmove1.wav" );
9 years ago
break;
case 12:
PRECACHE_SOUND( "plats/talkmove1.wav" );
pev->noiseMoving = MAKE_STRING( "plats/talkmove1.wav" );
9 years ago
break;
case 13:
PRECACHE_SOUND( "plats/talkmove2.wav" );
pev->noiseMoving = MAKE_STRING( "plats/talkmove2.wav" );
9 years ago
break;
default:
pev->noiseMoving = MAKE_STRING( "common/null.wav" );
9 years ago
break;
}
// set the plat's 'reached destination' stop sound
switch( m_bStopSnd )
9 years ago
{
case 0:
pev->noiseArrived = MAKE_STRING( "common/null.wav" );
9 years ago
break;
case 1:
PRECACHE_SOUND( "plats/bigstop1.wav" );
pev->noiseArrived = MAKE_STRING( "plats/bigstop1.wav" );
9 years ago
break;
case 2:
PRECACHE_SOUND( "plats/bigstop2.wav" );
pev->noiseArrived = MAKE_STRING( "plats/bigstop2.wav" );
9 years ago
break;
case 3:
PRECACHE_SOUND( "plats/freightstop1.wav" );
pev->noiseArrived = MAKE_STRING( "plats/freightstop1.wav" );
9 years ago
break;
case 4:
PRECACHE_SOUND( "plats/heavystop2.wav" );
pev->noiseArrived = MAKE_STRING( "plats/heavystop2.wav" );
9 years ago
break;
case 5:
PRECACHE_SOUND( "plats/rackstop1.wav" );
pev->noiseArrived = MAKE_STRING( "plats/rackstop1.wav" );
9 years ago
break;
case 6:
PRECACHE_SOUND( "plats/railstop1.wav" );
pev->noiseArrived = MAKE_STRING( "plats/railstop1.wav" );
9 years ago
break;
case 7:
PRECACHE_SOUND( "plats/squeekstop1.wav" );
pev->noiseArrived = MAKE_STRING( "plats/squeekstop1.wav" );
9 years ago
break;
case 8:
PRECACHE_SOUND( "plats/talkstop1.wav" );
pev->noiseArrived = MAKE_STRING( "plats/talkstop1.wav" );
9 years ago
break;
default:
pev->noiseArrived = MAKE_STRING( "common/null.wav" );
9 years ago
break;
}
}
//
//====================== 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 CallGoDown( void ) { GoDown(); }
void EXPORT CallHitTop( void ) { HitTop(); }
void EXPORT CallHitBottom( void ) { HitBottom(); }
9 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 )
9 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; }
9 years ago
void SpawnInsideTrigger( CFuncPlat *pPlatform );
void Touch( CBaseEntity *pOther );
CFuncPlat *m_pPlatform;
};
/*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 )
9 years ago
{
//pev->noiseMovement = MAKE_STRING( "plats/platmove1.wav" );
//pev->noiseStopMoving = MAKE_STRING( "plats/platstop1.wav" );
9 years ago
if( m_flTLength == 0 )
9 years ago
m_flTLength = 80;
if( m_flTWidth == 0 )
9 years ago
m_flTWidth = 10;
pev->angles = g_vecZero;
9 years ago
pev->solid = SOLID_BSP;
pev->movetype = MOVETYPE_PUSH;
9 years ago
UTIL_SetOrigin( pev, pev->origin ); // set size and link into world
UTIL_SetSize( pev, pev->mins, pev->maxs );
SET_MODEL( ENT( pev), STRING( pev->model ) );
9 years ago
// vecPosition1 is the top position, vecPosition2 is the bottom
m_vecPosition1 = pev->origin;
m_vecPosition2 = pev->origin;
if( m_flHeight != 0 )
9 years ago
m_vecPosition2.z = pev->origin.z - m_flHeight;
else
m_vecPosition2.z = pev->origin.z - pev->size.z + 8;
if( pev->speed == 0 )
9 years ago
pev->speed = 150;
if( m_volume == 0 )
9 years ago
m_volume = 0.85;
}
void CFuncPlat::Precache()
9 years ago
{
CBasePlatTrain::Precache();
//PRECACHE_SOUND( "plats/platmove1.wav" );
//PRECACHE_SOUND( "plats/platstop1.wav" );
if( !IsTogglePlat() )
9 years ago
PlatSpawnInsideTrigger( pev ); // the "start moving" trigger
}
void CFuncPlat::Spawn()
9 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 ) )
9 years ago
{
UTIL_SetOrigin( pev, m_vecPosition1 );
9 years ago
m_toggle_state = TS_AT_TOP;
SetUse( &CFuncPlat::PlatUse );
}
else
{
UTIL_SetOrigin( pev, m_vecPosition2 );
9 years ago
m_toggle_state = TS_AT_BOTTOM;
}
}
static void PlatSpawnInsideTrigger( entvars_t *pevPlatform )
9 years ago
{
GetClassPtr( (CPlatTrigger *)NULL )->SpawnInsideTrigger( GetClassPtr( (CFuncPlat *)pevPlatform ) );
9 years ago
}
//
// Create a trigger entity for a platform.
//
void CPlatTrigger::SpawnInsideTrigger( CFuncPlat *pPlatform )
9 years ago
{
m_pPlatform = pPlatform;
// Create trigger entity, "point" it at the owning platform, give it a touch method
pev->solid = SOLID_TRIGGER;
pev->movetype = MOVETYPE_NONE;
9 years ago
pev->origin = pPlatform->pev->origin;
// Establish the trigger field's size
Vector vecTMin = m_pPlatform->pev->mins + Vector( 25, 25, 0 );
Vector vecTMax = m_pPlatform->pev->maxs + Vector( 25, 25, 8 );
9 years ago
vecTMin.z = vecTMax.z - ( m_pPlatform->m_vecPosition1.z - m_pPlatform->m_vecPosition2.z + 8 );
if( m_pPlatform->pev->size.x <= 50 )
9 years ago
{
vecTMin.x = ( m_pPlatform->pev->mins.x + m_pPlatform->pev->maxs.x ) / 2;
9 years ago
vecTMax.x = vecTMin.x + 1;
}
if( m_pPlatform->pev->size.y <= 50 )
9 years ago
{
vecTMin.y = ( m_pPlatform->pev->mins.y + m_pPlatform->pev->maxs.y ) / 2;
9 years ago
vecTMax.y = vecTMin.y + 1;
}
UTIL_SetSize( pev, vecTMin, vecTMax );
9 years ago
}
//
// When the platform's trigger field is touched, the platform ???
//
void CPlatTrigger::Touch( CBaseEntity *pOther )
9 years ago
{
// Ignore touches by non-players
entvars_t *pevToucher = pOther->pev;
if( !FClassnameIs( pevToucher, "player" ) )
9 years ago
return;
// Ignore touches by corpses
if( !pOther->IsAlive() )
9 years ago
return;
9 years ago
// Make linked platform go up/down.
if( m_pPlatform->m_toggle_state == TS_AT_BOTTOM )
9 years ago
m_pPlatform->GoUp();
else if( m_pPlatform->m_toggle_state == TS_AT_TOP )
9 years ago
m_pPlatform->pev->nextthink = m_pPlatform->pev->ltime + 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 )
9 years ago
{
if( IsTogglePlat() )
9 years ago
{
// Top is off, bottom is on
BOOL on = ( m_toggle_state == TS_AT_BOTTOM ) ? TRUE : FALSE;
9 years ago
if( !ShouldToggle( useType, on ) )
9 years ago
return;
if( m_toggle_state == TS_AT_TOP )
9 years ago
GoDown();
else if( m_toggle_state == TS_AT_BOTTOM )
9 years ago
GoUp();
}
else
{
SetUse( NULL );
if( m_toggle_state == TS_AT_TOP )
9 years ago
GoDown();
}
}
//
// Platform is at top, now starts moving down.
//
void CFuncPlat::GoDown( void )
9 years ago
{
if( pev->noiseMovement )
EMIT_SOUND( ENT( pev ), CHAN_STATIC, (char*)STRING( pev->noiseMovement ), m_volume, ATTN_NORM );
9 years ago
ASSERT( m_toggle_state == TS_AT_TOP || m_toggle_state == TS_GOING_UP );
9 years ago
m_toggle_state = TS_GOING_DOWN;
SetMoveDone( &CFuncPlat::CallHitBottom );
LinearMove( m_vecPosition2, pev->speed );
9 years ago
}
//
// Platform has hit bottom. Stops and waits forever.
//
void CFuncPlat::HitBottom( void )
9 years ago
{
if( pev->noiseMovement )
STOP_SOUND( ENT( pev ), CHAN_STATIC, (char*)STRING( pev->noiseMovement ) );
9 years ago
if( pev->noiseStopMoving )
EMIT_SOUND( ENT( pev ), CHAN_WEAPON, (char*)STRING( pev->noiseStopMoving ), m_volume, ATTN_NORM );
9 years ago
ASSERT( m_toggle_state == TS_GOING_DOWN );
9 years ago
m_toggle_state = TS_AT_BOTTOM;
}
//
// Platform is at bottom, now starts moving up
//
void CFuncPlat::GoUp( void )
9 years ago
{
if( pev->noiseMovement )
EMIT_SOUND( ENT( pev ), CHAN_STATIC, (char*)STRING( pev->noiseMovement ), m_volume, ATTN_NORM );
ASSERT( m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN );
9 years ago
m_toggle_state = TS_GOING_UP;
SetMoveDone( &CFuncPlat::CallHitTop );
9 years ago
LinearMove(m_vecPosition1, pev->speed);
}
//
// Platform has hit top. Pauses, then starts back down again.
//
void CFuncPlat::HitTop( void )
9 years ago
{
if( pev->noiseMovement )
STOP_SOUND( ENT( pev ), CHAN_STATIC, (char*)STRING( pev->noiseMovement ) );
9 years ago
if( pev->noiseStopMoving )
EMIT_SOUND( ENT( pev ), CHAN_WEAPON, (char*)STRING( pev->noiseStopMoving ), m_volume, ATTN_NORM );
ASSERT( m_toggle_state == TS_GOING_UP );
9 years ago
m_toggle_state = TS_AT_TOP;
if( !IsTogglePlat() )
9 years ago
{
// After a delay, the platform will automatically start going down again.
SetThink( &CFuncPlat::CallGoDown );
pev->nextthink = pev->ltime + 3;
}
}
void CFuncPlat::Blocked( CBaseEntity *pOther )
9 years ago
{
ALERT( at_aiconsole, "%s Blocked by %s\n", STRING( pev->classname ), STRING( pOther->pev->classname ) );
9 years ago
// Hurt the blocker a little
pOther->TakeDamage( pev, pev, 1, DMG_CRUSH );
if( pev->noiseMovement )
STOP_SOUND( ENT( pev ), CHAN_STATIC, (char*)STRING( pev->noiseMovement ) );
9 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 )
9 years ago
GoDown();
else if( m_toggle_state == TS_GOING_DOWN )
9 years ago
GoUp ();
}
class CFuncPlatRot : public CFuncPlat
{
public:
void Spawn( void );
void SetupRotation( void );
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[];
Vector m_end, m_start;
9 years ago
};
LINK_ENTITY_TO_CLASS( func_platrot, CFuncPlatRot )
TYPEDESCRIPTION CFuncPlatRot::m_SaveData[] =
9 years ago
{
DEFINE_FIELD( CFuncPlatRot, m_end, FIELD_VECTOR ),
DEFINE_FIELD( CFuncPlatRot, m_start, FIELD_VECTOR ),
};
IMPLEMENT_SAVERESTORE( CFuncPlatRot, CFuncPlat )
9 years ago
void CFuncPlatRot::SetupRotation( void )
9 years ago
{
if( m_vecFinalAngle.x != 0 ) // This plat rotates too!
9 years ago
{
CBaseToggle::AxisDir( pev );
9 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
9 years ago
{
pev->angles = m_end;
}
}
void CFuncPlatRot::Spawn( void )
9 years ago
{
CFuncPlat::Spawn();
9 years ago
SetupRotation();
}
void CFuncPlatRot::GoDown( void )
9 years ago
{
CFuncPlat::GoDown();
9 years ago
RotMove( m_start, pev->nextthink - pev->ltime );
}
//
// Platform has hit bottom. Stops and waits forever.
//
void CFuncPlatRot::HitBottom( void )
9 years ago
{
CFuncPlat::HitBottom();
9 years ago
pev->avelocity = g_vecZero;
pev->angles = m_start;
}
//
// Platform is at bottom, now starts moving up
//
void CFuncPlatRot::GoUp( void )
9 years ago
{
CFuncPlat::GoUp();
9 years ago
RotMove( m_end, pev->nextthink - pev->ltime );
}
//
// Platform has hit top. Pauses, then starts back down again.
//
void CFuncPlatRot::HitTop( void )
9 years ago
{
CFuncPlat::HitTop();
9 years ago
pev->avelocity = g_vecZero;
pev->angles = m_end;
}
void CFuncPlatRot::RotMove( Vector &destAngle, float time )
9 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; so make it so.
if( time >= 0.1 )
9 years ago
pev->avelocity = vecDestDelta / time;
else
{
pev->avelocity = vecDestDelta;
pev->nextthink = pev->ltime + 1;
}
}
//
//====================== TRAIN code ==================================================
//
class CFuncTrain : public CBasePlatTrain
{
public:
void Spawn( void );
void Precache( void );
void Activate( void );
void OverrideReset( void );
void Blocked( CBaseEntity *pOther );
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
void KeyValue( KeyValueData *pkvd );
void EXPORT Wait( void );
void EXPORT Next( void );
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
static TYPEDESCRIPTION m_SaveData[];
9 years ago
entvars_t *m_pevCurrentTarget;
int m_sounds;
BOOL m_activated;
9 years ago
};
LINK_ENTITY_TO_CLASS( func_train, CFuncTrain )
TYPEDESCRIPTION CFuncTrain::m_SaveData[] =
9 years ago
{
DEFINE_FIELD( CFuncTrain, m_sounds, FIELD_INTEGER ),
DEFINE_FIELD( CFuncTrain, m_pevCurrentTarget, FIELD_EVARS ),
DEFINE_FIELD( CFuncTrain, m_activated, FIELD_BOOLEAN ),
};
IMPLEMENT_SAVERESTORE( CFuncTrain, CBasePlatTrain )
9 years ago
void CFuncTrain::KeyValue( KeyValueData *pkvd )
9 years ago
{
if( FStrEq( pkvd->szKeyName, "sounds" ) )
9 years ago
{
m_sounds = atoi( pkvd->szValue );
9 years ago
pkvd->fHandled = TRUE;
}
else
CBasePlatTrain::KeyValue( pkvd );
}
void CFuncTrain::Blocked( CBaseEntity *pOther )
9 years ago
{
if( gpGlobals->time < m_flActivateFinished )
9 years ago
return;
m_flActivateFinished = gpGlobals->time + 0.5;
pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH );
9 years ago
}
void CFuncTrain::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
9 years ago
{
if( pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER )
9 years ago
{
// Move toward my target
pev->spawnflags &= ~SF_TRAIN_WAIT_RETRIGGER;
Next();
}
else
{
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;
pev->nextthink = 0;
pev->velocity = g_vecZero;
if( pev->noiseStopMoving )
EMIT_SOUND( ENT( pev ), CHAN_VOICE, (char*)STRING( pev->noiseStopMoving ), m_volume, ATTN_NORM );
9 years ago
}
}
void CFuncTrain::Wait( void )
9 years ago
{
// Fire the pass target if there is one
if( m_pevCurrentTarget->message )
9 years ago
{
FireTargets( STRING( m_pevCurrentTarget->message ), this, this, USE_TOGGLE, 0 );
if( FBitSet( m_pevCurrentTarget->spawnflags, SF_CORNER_FIREONCE ) )
9 years ago
m_pevCurrentTarget->message = 0;
}
9 years ago
// need pointer to LAST target.
if( FBitSet( m_pevCurrentTarget->spawnflags, SF_TRAIN_WAIT_RETRIGGER ) || ( pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER ) )
{
9 years ago
pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER;
// clear the sound channel.
if( pev->noiseMovement )
STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING( pev->noiseMovement ) );
if( pev->noiseStopMoving )
EMIT_SOUND( ENT( pev ), CHAN_VOICE, (char*)STRING( pev->noiseStopMoving ), m_volume, ATTN_NORM );
9 years ago
pev->nextthink = 0;
return;
}
// ALERT( at_console, "%f\n", m_flWait );
if( m_flWait != 0 )
{
// -1 wait will wait forever!
9 years ago
pev->nextthink = pev->ltime + m_flWait;
if( pev->noiseMovement )
STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING( pev->noiseMovement ) );
if( pev->noiseStopMoving )
EMIT_SOUND( ENT( pev ), CHAN_VOICE, (char*)STRING( pev->noiseStopMoving ), m_volume, ATTN_NORM );
9 years ago
SetThink( &CFuncTrain::Next );
}
else
{
Next();// do it RIGHT now!
}
}
//
// Train next - path corner needs to change to next target
//
void CFuncTrain::Next( void )
9 years ago
{
CBaseEntity *pTarg;
9 years ago
// now find our next target
pTarg = GetNextTarget();
if( !pTarg )
9 years ago
{
if( pev->noiseMovement )
STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING( pev->noiseMovement ) );
9 years ago
// Play stop sound
if( pev->noiseStopMoving )
EMIT_SOUND( ENT( pev ), CHAN_VOICE, (char*)STRING( pev->noiseStopMoving ), m_volume, ATTN_NORM );
9 years ago
return;
}
// Save last target in case we need to find it again
pev->message = pev->target;
pev->target = pTarg->pev->target;
m_flWait = pTarg->GetDelay();
if ( m_pevCurrentTarget && m_pevCurrentTarget->speed != 0 )
{
// don't copy speed from target if it is 0 (uninitialized)
pev->speed = m_pevCurrentTarget->speed;
9 years ago
ALERT( at_aiconsole, "Train %s speed to %4.2f\n", STRING(pev->targetname), pev->speed );
}
m_pevCurrentTarget = pTarg->pev;// keep track of this since path corners change our target for us.
pev->enemy = pTarg->edict();//hack
9 years ago
if(FBitSet(m_pevCurrentTarget->spawnflags, SF_CORNER_TELEPORT))
{
// Path corner has indicated a teleport to the next corner.
SetBits(pev->effects, EF_NOINTERP);
UTIL_SetOrigin(pev, pTarg->pev->origin - (pev->mins + pev->maxs)* 0.5);
Wait(); // Get on with doing the next path corner.
pev->iuser1++; // Invalidate state for coop
9 years ago
}
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 ( pev->noiseMovement )
STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) );
if ( pev->noiseMovement )
EMIT_SOUND (ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM);
ClearBits(pev->effects, EF_NOINTERP);
SetMoveDone( &CFuncTrain::Wait );
LinearMove (pTarg->pev->origin - (pev->mins + pev->maxs)* 0.5, pev->speed);
}
}
void CFuncTrain::Activate( void )
9 years ago
{
// Not yet active, so teleport to first target
if( !m_activated )
9 years ago
{
m_activated = TRUE;
entvars_t *pevTarg = VARS( FIND_ENTITY_BY_TARGETNAME( NULL, STRING( pev->target ) ) );
9 years ago
pev->target = pevTarg->target;
m_pevCurrentTarget = pevTarg;// keep track of this since path corners change our target for us.
UTIL_SetOrigin( pev, pevTarg->origin - ( pev->mins + pev->maxs ) * 0.5 );
if( FStringNull( pev->targetname ) )
9 years ago
{ // not triggered, so start immediately
pev->nextthink = pev->ltime + 0.1;
SetThink( &CFuncTrain::Next );
}
else
pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER;
}
}
/*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 )
9 years ago
{
Precache();
if( pev->speed == 0 )
9 years ago
pev->speed = 100;
if( FStringNull(pev->target) )
ALERT( at_console, "FuncTrain with no target" );
if( pev->dmg == 0 )
9 years ago
pev->dmg = 2;
9 years ago
pev->movetype = MOVETYPE_PUSH;
if( FBitSet( pev->spawnflags, SF_TRACKTRAIN_PASSABLE ) )
pev->solid = SOLID_NOT;
9 years ago
else
pev->solid = SOLID_BSP;
9 years ago
SET_MODEL( ENT( pev ), STRING( pev->model ) );
UTIL_SetSize( pev, pev->mins, pev->maxs );
UTIL_SetOrigin( pev, pev->origin );
9 years ago
m_activated = FALSE;
if( m_volume == 0 )
9 years ago
m_volume = 0.85;
}
void CFuncTrain::Precache( void )
{
CBasePlatTrain::Precache();
#if 0 // obsolete
9 years ago
// otherwise use preset sound
switch( m_sounds )
9 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" );
9 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" );
9 years ago
break;
}
#endif
}
void CFuncTrain::OverrideReset( void )
{
CBaseEntity *pTarg;
9 years ago
// Are we moving?
if( pev->velocity != g_vecZero && pev->nextthink != 0 )
9 years ago
{
pev->target = pev->message;
// now find our next target
pTarg = GetNextTarget();
if( !pTarg )
9 years ago
{
pev->nextthink = 0;
pev->velocity = g_vecZero;
}
else // Keep moving for 0.1 secs, then find path_corner again and restart
{
SetThink( &CFuncTrain::Next );
pev->nextthink = pev->ltime + 0.1;
}
}
}
// ---------------------------------------------------------------------
//
// Track Train
//
// ---------------------------------------------------------------------
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 ),
};
IMPLEMENT_SAVERESTORE( CFuncTrackTrain, CBaseEntity )
LINK_ENTITY_TO_CLASS( func_tracktrain, CFuncTrackTrain )
9 years ago
void CFuncTrackTrain::KeyValue( KeyValueData *pkvd )
9 years ago
{
if( FStrEq( pkvd->szKeyName, "wheels" ) )
9 years ago
{
m_length = atof( pkvd->szValue );
9 years ago
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "height" ) )
9 years ago
{
m_height = atof(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "startspeed" ) )
9 years ago
{
m_startSpeed = atof(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "sounds" ) )
9 years ago
{
m_sounds = atoi(pkvd->szValue);
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "volume" ) )
9 years ago
{
m_flVolume = (float)atoi( pkvd->szValue );
9 years ago
m_flVolume *= 0.1;
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "bank" ) )
9 years ago
{
m_flBank = atof( pkvd->szValue );
9 years ago
pkvd->fHandled = TRUE;
}
else
CBaseEntity::KeyValue( pkvd );
}
void CFuncTrackTrain::NextThink( float thinkTime, BOOL alwaysThink )
9 years ago
{
if( alwaysThink )
9 years ago
pev->flags |= FL_ALWAYSTHINK;
else
pev->flags &= ~FL_ALWAYSTHINK;
pev->nextthink = thinkTime;
}
void CFuncTrackTrain::Blocked( CBaseEntity *pOther )
9 years ago
{
entvars_t *pevOther = pOther->pev;
9 years ago
// Blocker is on-ground on the train
if( FBitSet( pevOther->flags, FL_ONGROUND ) && VARS( pevOther->groundentity ) == pev )
9 years ago
{
float deltaSpeed = fabs( pev->speed );
if( deltaSpeed > 50 )
9 years ago
deltaSpeed = 50;
if( !pevOther->velocity.z )
9 years ago
pevOther->velocity.z += deltaSpeed;
return;
}
else
pevOther->velocity = ( pevOther->origin - pev->origin ).Normalize() * pev->dmg;
9 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 )
9 years ago
return;
// we can't hurt this thing, so we're not concerned with it
pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH );
9 years ago
}
void CFuncTrackTrain::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
9 years ago
{
if( useType != USE_SET )
9 years ago
{
if( !ShouldToggle( useType, ( pev->speed != 0 ) ) )
9 years ago
return;
if( pev->speed == 0 )
9 years ago
{
pev->speed = m_speed * m_dir;
9 years ago
Next();
}
else
{
pev->speed = 0;
pev->velocity = g_vecZero;
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 )
9 years ago
delta = 1;
else if ( delta < -1 )
delta = -1;
if( pev->spawnflags & SF_TRACKTRAIN_FORWARDONLY )
9 years ago
{
if( delta < 0 )
9 years ago
delta = 0;
}
pev->speed = m_speed * delta;
Next();
ALERT( at_aiconsole, "TRAIN(%s), speed to %.2f\n", STRING( pev->targetname ), pev->speed );
9 years ago
}
}
static float Fix( float angle )
{
while( angle < 0 )
9 years ago
angle += 360;
while( angle > 360 )
9 years ago
angle -= 360;
return angle;
}
static void FixupAngles( Vector &v )
{
v.x = Fix( v.x );
v.y = Fix( v.y );
v.z = Fix( v.z );
}
#define TRAIN_STARTPITCH 60
#define TRAIN_MAXPITCH 200
#define TRAIN_MAXSPEED 1000 // approx max speed for sound pitch calculation
void CFuncTrackTrain::StopSound( void )
9 years ago
{
// if sound playing, stop it
if( m_soundPlaying && pev->noise )
9 years ago
{
unsigned short us_encode;
unsigned short us_sound = ( (unsigned short)( m_sounds ) & 0x0007 ) << 12;
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 );
/*
STOP_SOUND( ENT( pev ), CHAN_STATIC, (char*)STRING( pev->noise ) );
9 years ago
*/
EMIT_SOUND_DYN( ENT( pev ), CHAN_ITEM, "plats/ttrain_brake1.wav", m_flVolume, ATTN_NORM, 0, 100 );
9 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 )
9 years ago
{
float flpitch;
if( !pev->noise )
9 years ago
return;
flpitch = TRAIN_STARTPITCH + ( fabs( pev->speed ) * ( TRAIN_MAXPITCH - TRAIN_STARTPITCH ) / TRAIN_MAXSPEED );
9 years ago
if( !m_soundPlaying )
9 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, (char*)STRING( pev->noise ), m_flVolume, ATTN_NORM, 0, (int)flpitch );
9 years ago
m_soundPlaying = 1;
}
else
{
/*
// update pitch
EMIT_SOUND_DYN( ENT( pev ), CHAN_STATIC, (char*)STRING( pev->noise ), m_flVolume, ATTN_NORM, SND_CHANGE_PITCH, (int)flpitch );
9 years ago
*/
// 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,
9 years ago
(float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, us_encode, 0, 0, 0 );
}
}
void CFuncTrackTrain::Next( void )
9 years ago
{
float time = 0.5;
if( !pev->speed )
9 years ago
{
ALERT( at_aiconsole, "TRAIN(%s): Speed is 0\n", STRING( pev->targetname ) );
9 years ago
StopSound();
return;
}
//if( !m_ppath )
// m_ppath = CPathTrack::Instance( FIND_ENTITY_BY_TARGETNAME( NULL, STRING( pev->target ) ) );
if( !m_ppath )
9 years ago
{
ALERT( at_aiconsole, "TRAIN(%s): Lost path\n", STRING( pev->targetname ) );
9 years ago
StopSound();
return;
}
// prevent train without players going to other map
if( mp_coop.value && pev->globalname && STRING( pev->globalname )[0] )
{
CBaseEntity *pList;
Vector mins = pev->absmin;
Vector maxs = pev->absmax;
maxs.z += 8;
int count = UTIL_EntitiesInBox( &pList, 1, mins, maxs, FL_ONGROUND );
if( !count || !pList->IsPlayer() )
{
pev->velocity = g_vecZero;
NextThink( pev->ltime + time, TRUE );
return;
}
}
9 years ago
UpdateSound();
Vector nextPos = pev->origin;
nextPos.z -= m_height;
CPathTrack *pnext = m_ppath->LookAhead( &nextPos, pev->speed * 0.1, 1 );
nextPos.z += m_height;
pev->velocity = ( nextPos - pev->origin ) * 10;
9 years ago
Vector nextFront = pev->origin;
nextFront.z -= m_height;
if( m_length > 0 )
9 years ago
m_ppath->LookAhead( &nextFront, m_length, 0 );
else
m_ppath->LookAhead( &nextFront, 100, 0 );
nextFront.z += m_height;
Vector delta = nextFront - pev->origin;
Vector angles = UTIL_VecToAngles( delta );
// The train actually points west
angles.y += 180;
// !!! 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 ) )
9 years ago
angles = pev->angles;
float vy, vx;
if( !( pev->spawnflags & SF_TRACKTRAIN_NOPITCH ) )
9 years ago
vx = UTIL_AngleDistance( angles.x, pev->angles.x );
else
vx = 0;
vy = UTIL_AngleDistance( angles.y, pev->angles.y );
pev->avelocity.y = vy * 10;
pev->avelocity.x = vx * 10;
if( m_flBank != 0 )
9 years ago
{
if( pev->avelocity.y < -5 )
pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( -m_flBank, pev->angles.z, m_flBank * 2 ), pev->angles.z );
else if( pev->avelocity.y > 5 )
pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( m_flBank, pev->angles.z, m_flBank * 2 ), pev->angles.z );
9 years ago
else
pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( 0, pev->angles.z, m_flBank * 4 ), pev->angles.z ) * 4;
9 years ago
}
if( pnext )
9 years ago
{
if( pnext != m_ppath )
9 years ago
{
CPathTrack *pFire;
if( pev->speed >= 0 )
9 years ago
pFire = pnext;
else
pFire = m_ppath;
m_ppath = pnext;
// Fire the pass target if there is one
if( pFire->pev->message )
9 years ago
{
FireTargets( STRING( pFire->pev->message ), this, this, USE_TOGGLE, 0 );
if( FBitSet( pFire->pev->spawnflags, SF_PATH_FIREONCE ) )
9 years ago
pFire->pev->message = 0;
}
if( pFire->pev->spawnflags & SF_PATH_DISABLE_TRAIN )
9 years ago
pev->spawnflags |= SF_TRACKTRAIN_NOCONTROL;
9 years ago
// Don't override speed if under user control
if( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL )
9 years ago
{
if( pFire->pev->speed != 0 )
{
// don't copy speed from target if it is 0 (uninitialized)
9 years ago
pev->speed = pFire->pev->speed;
ALERT( at_aiconsole, "TrackTrain %s speed to %4.2f\n", STRING( pev->targetname ), pev->speed );
9 years ago
}
}
}
SetThink( &CFuncTrackTrain::Next );
NextThink( pev->ltime + time, TRUE );
}
else // end of path, stop
9 years ago
{
StopSound();
pev->velocity = nextPos - pev->origin;
9 years ago
pev->avelocity = g_vecZero;
float distance = pev->velocity.Length();
m_oldSpeed = pev->speed;
pev->speed = 0;
9 years ago
// Move to the dead end
9 years ago
// Are we there yet?
if( distance > 0 )
9 years ago
{
// no, how long to get there?
time = distance / m_oldSpeed;
pev->velocity = pev->velocity * ( m_oldSpeed / distance );
9 years ago
SetThink( &CFuncTrackTrain::DeadEnd );
NextThink( pev->ltime + time, FALSE );
}
else
{
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 ) );
9 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 )
9 years ago
{
if( m_oldSpeed < 0 )
9 years ago
{
do
{
pNext = pTrack->ValidPath( pTrack->GetPrevious(), TRUE );
if( pNext )
9 years ago
pTrack = pNext;
} while( pNext );
9 years ago
}
else
{
do
{
pNext = pTrack->ValidPath( pTrack->GetNext(), TRUE );
if( pNext )
9 years ago
pTrack = pNext;
} while( pNext );
9 years ago
}
}
pev->velocity = g_vecZero;
pev->avelocity = g_vecZero;
if( pTrack )
9 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 );
9 years ago
}
else
ALERT( at_aiconsole, "\n" );
}
void CFuncTrackTrain::SetControls( entvars_t *pevControls )
9 years ago
{
Vector offset = pevControls->origin - pev->oldorigin;
m_controlMins = pevControls->mins + offset;
m_controlMaxs = pevControls->maxs + offset;
}
BOOL CFuncTrackTrain::OnControls( entvars_t *pevTest )
9 years ago
{
Vector offset = pevTest->origin - pev->origin;
if( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL )
9 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 &&
9 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 )
9 years ago
{
m_ppath = CPathTrack::Instance(FIND_ENTITY_BY_TARGETNAME( NULL, STRING( pev->target ) ) );
if( !m_ppath )
9 years ago
return;
entvars_t *pevTarget = m_ppath->pev;
if( !FClassnameIs( pevTarget, "path_track" ) )
9 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;
pev->angles = UTIL_VecToAngles( look - nextPos );
// The train actually points west
pev->angles.y += 180;
if( pev->spawnflags & SF_TRACKTRAIN_NOPITCH )
9 years ago
pev->angles.x = 0;
UTIL_SetOrigin( pev, nextPos );
9 years ago
NextThink( pev->ltime + 0.1, FALSE );
SetThink( &CFuncTrackTrain::Next );
pev->speed = m_startSpeed;
UpdateSound();
}
void CFuncTrackTrain::NearestPath( void )
9 years ago
{
CBaseEntity *pTrack = NULL;
CBaseEntity *pNearest = NULL;
float dist, closest;
closest = 1024;
while( ( pTrack = UTIL_FindEntityInSphere( pTrack, pev->origin, 1024 ) ) != NULL )
9 years ago
{
// filter out non-tracks
if( !( pTrack->pev->flags & ( FL_CLIENT | FL_MONSTER ) ) && FClassnameIs( pTrack->pev, "path_track" ) )
9 years ago
{
dist = ( pev->origin - pTrack->pev->origin ).Length();
if( dist < closest )
9 years ago
{
closest = dist;
pNearest = pTrack;
}
}
}
if( !pNearest )
9 years ago
{
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 ) );
9 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 )
9 years ago
{
if( ( pev->origin - pTrack->pev->origin ).Length() < ( pev->origin - pNearest->pev->origin ).Length() )
9 years ago
pNearest = pTrack;
}
m_ppath = (CPathTrack *)pNearest;
if( pev->speed != 0 )
9 years ago
{
NextThink( pev->ltime + 0.1, FALSE );
SetThink( &CFuncTrackTrain::Next );
}
}
void CFuncTrackTrain::OverrideReset( void )
{
NextThink( pev->ltime + 0.1, FALSE );
SetThink( &CFuncTrackTrain::NearestPath );
}
CFuncTrackTrain *CFuncTrackTrain::Instance( edict_t *pent )
{
if( FClassnameIs( pent, "func_tracktrain" ) )
return (CFuncTrackTrain *)GET_PRIVATE( pent );
9 years ago
return NULL;
}
/*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 CFuncTrackTrain::Spawn( void )
9 years ago
{
if( pev->speed == 0 )
9 years ago
m_speed = 100;
else
m_speed = pev->speed;
9 years ago
pev->speed = 0;
pev->velocity = g_vecZero;
pev->avelocity = g_vecZero;
pev->impulse = m_speed;
m_dir = 1;
if( FStringNull( pev->target ) )
9 years ago
ALERT( at_console, "FuncTrain with no target" );
if( pev->spawnflags & SF_TRACKTRAIN_PASSABLE )
9 years ago
pev->solid = SOLID_NOT;
else
pev->solid = SOLID_BSP;
pev->movetype = MOVETYPE_PUSH;
SET_MODEL( ENT( pev ), STRING( pev->model ) );
9 years ago
UTIL_SetSize( pev, pev->mins, pev->maxs );
UTIL_SetOrigin( pev, pev->origin );
// 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
9 years ago
NextThink( pev->ltime + 0.1, FALSE );
SetThink( &CFuncTrackTrain::Find );
Precache();
}
void CFuncTrackTrain::Precache( void )
9 years ago
{
if( m_flVolume == 0.0 )
9 years ago
m_flVolume = 1.0;
switch( m_sounds )
9 years ago
{
default:
// no sound
pev->noise = 0;
break;
case 1:
PRECACHE_SOUND( "plats/ttrain1.wav" );
pev->noise = MAKE_STRING("plats/ttrain1.wav" );
break;
case 2:
PRECACHE_SOUND( "plats/ttrain2.wav" );
pev->noise = MAKE_STRING( "plats/ttrain2.wav" );
break;
case 3:
PRECACHE_SOUND( "plats/ttrain3.wav" );
pev->noise = MAKE_STRING( "plats/ttrain3.wav" );
break;
case 4:
PRECACHE_SOUND( "plats/ttrain4.wav" );
pev->noise = MAKE_STRING( "plats/ttrain4.wav" );
break;
case 5:
PRECACHE_SOUND( "plats/ttrain6.wav" );
pev->noise = MAKE_STRING( "plats/ttrain6.wav" );
break;
case 6:
PRECACHE_SOUND( "plats/ttrain7.wav" );
pev->noise = MAKE_STRING( "plats/ttrain7.wav" );
break;
9 years ago
}
PRECACHE_SOUND( "plats/ttrain_brake1.wav" );
PRECACHE_SOUND( "plats/ttrain_start1.wav" );
9 years ago
m_usAdjustPitch = PRECACHE_EVENT( 1, "events/train.sc" );
}
// 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; }
9 years ago
void Spawn( void );
void EXPORT Find( void );
};
LINK_ENTITY_TO_CLASS( func_traincontrols, CFuncTrainControls )
9 years ago
void CFuncTrainControls::Find( void )
9 years ago
{
edict_t *pTarget = NULL;
do
{
pTarget = FIND_ENTITY_BY_TARGETNAME( pTarget, STRING( pev->target ) );
} while( !FNullEnt(pTarget) && !FClassnameIs( pTarget, "func_tracktrain" ) );
9 years ago
if( FNullEnt( pTarget ) )
9 years ago
{
ALERT( at_console, "No train %s\n", STRING( pev->target ) );
9 years ago
return;
}
CFuncTrackTrain *ptrain = CFuncTrackTrain::Instance( pTarget );
9 years ago
ptrain->SetControls( pev );
UTIL_Remove( this );
}
void CFuncTrainControls::Spawn( void )
9 years ago
{
pev->solid = SOLID_NOT;
pev->movetype = MOVETYPE_NONE;
SET_MODEL( ENT( pev ), STRING( pev->model ) );
9 years ago
UTIL_SetSize( pev, pev->mins, pev->maxs );
UTIL_SetOrigin( pev, pev->origin );
9 years ago
SetThink( &CFuncTrainControls::Find );
pev->nextthink = gpGlobals->time;
}
// ----------------------------------------------------------------------------
//
// Track changer / Train elevator
//
// ----------------------------------------------------------------------------
#define SF_TRACK_ACTIVATETRAIN 0x00000001
9 years ago
#define SF_TRACK_RELINK 0x00000002
#define SF_TRACK_ROTMOVE 0x00000004
#define SF_TRACK_STARTBOTTOM 0x00000008
9 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;
9 years ago
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; }
9 years ago
void DisableUse( void ) { m_use = 0; }
void EnableUse( void ) { m_use = 1; }
int UseEnabled( void ) { return m_use; }
9 years ago
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
static TYPEDESCRIPTION m_SaveData[];
9 years ago
virtual void OverrideReset( void );
9 years ago
CPathTrack *m_trackTop;
CPathTrack *m_trackBottom;
9 years ago
CFuncTrackTrain *m_train;
int m_trackTopName;
int m_trackBottomName;
int m_trainName;
TRAIN_CODE m_code;
int m_targetState;
int m_use;
9 years ago
};
LINK_ENTITY_TO_CLASS( func_trackchange, CFuncTrackChange )
TYPEDESCRIPTION CFuncTrackChange::m_SaveData[] =
9 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 )
9 years ago
void CFuncTrackChange::Spawn( void )
9 years ago
{
Setup();
if( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) )
9 years ago
m_vecPosition2.z = pev->origin.z;
SetupRotation();
if( FBitSet( pev->spawnflags, SF_TRACK_STARTBOTTOM ) )
9 years ago
{
UTIL_SetOrigin( pev, m_vecPosition2 );
9 years ago
m_toggle_state = TS_AT_BOTTOM;
pev->angles = m_start;
m_targetState = TS_AT_TOP;
}
else
{
UTIL_SetOrigin( pev, m_vecPosition1 );
9 years ago
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 )
9 years ago
{
// Can't trigger sound
PRECACHE_SOUND( "buttons/button11.wav" );
9 years ago
CFuncPlatRot::Precache();
}
// UNDONE: Filter touches before re-evaluating the train.
void CFuncTrackChange::Touch( CBaseEntity *pOther )
9 years ago
{
#if 0
TRAIN_CODE code;
entvars_t *pevToucher = pOther->pev;
#endif
}
void CFuncTrackChange::KeyValue( KeyValueData *pkvd )
9 years ago
{
if( FStrEq( pkvd->szKeyName, "train" ) )
9 years ago
{
m_trainName = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "toptrack" ) )
9 years ago
{
m_trackTopName = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "bottomtrack" ) )
9 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;
SetThink( &CFuncTrackChange::Find );
}
void CFuncTrackChange::Find( void )
9 years ago
{
// Find track entities
edict_t *target;
target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING( m_trackTopName ) );
if( !FNullEnt( target ) )
9 years ago
{
m_trackTop = CPathTrack::Instance( target );
target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING( m_trackBottomName ) );
if( !FNullEnt( target ) )
9 years ago
{
m_trackBottom = CPathTrack::Instance( target );
target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING( m_trainName ) );
if( !FNullEnt( target ) )
9 years ago
{
m_train = CFuncTrackTrain::Instance( FIND_ENTITY_BY_TARGETNAME( NULL, STRING( m_trainName ) ) );
if( !m_train )
9 years ago
{
ALERT( at_error, "Can't find train for track change! %s\n", STRING( m_trainName ) );
9 years ago
return;
}
Vector center = ( pev->absmin + pev->absmax ) * 0.5;
9 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 ) );
target = FIND_ENTITY_BY_TARGETNAME( NULL, STRING( m_trainName ) );
9 years ago
}
}
else
ALERT( at_error, "Can't find bottom track for track change! %s\n", STRING( m_trackBottomName ) );
9 years ago
}
else
ALERT( at_error, "Can't find top track for track change! %s\n", STRING( m_trackTopName ) );
9 years ago
}
TRAIN_CODE CFuncTrackChange::EvaluateTrain( CPathTrack *pcurrent )
9 years ago
{
// Go ahead and work, we don't have anything to switch, so just be an elevator
if( !pcurrent || !m_train )
9 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 ) )
9 years ago
{
if( m_train->pev->speed != 0 )
9 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
9 years ago
return TRAIN_FOLLOWING;
else if( length > ( 150 + m_train->m_length ) )
9 years ago
return TRAIN_SAFE;
return TRAIN_BLOCKING;
}
9 years ago
return TRAIN_SAFE;
}
void CFuncTrackChange::UpdateTrain( Vector &dest )
9 years ago
{
float time = ( pev->nextthink - pev->ltime );
9 years ago
m_train->pev->velocity = pev->velocity;
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 )
9 years ago
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 = pev->velocity + ( local * ( 1.0 / time ) );
9 years ago
}
void CFuncTrackChange::GoDown( void )
9 years ago
{
if( m_code == TRAIN_BLOCKING )
9 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 ) )
9 years ago
{
SetMoveDone( &CFuncPlat::CallHitBottom );
m_toggle_state = TS_GOING_DOWN;
AngularMove( m_start, pev->speed );
}
else
{
CFuncPlat::GoDown();
9 years ago
SetMoveDone( &CFuncPlat::CallHitBottom );
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 )
9 years ago
{
UpdateTrain( m_start );
m_train->m_ppath = NULL;
}
}
//
// Platform is at bottom, now starts moving up
//
void CFuncTrackChange::GoUp( void )
9 years ago
{
if( m_code == TRAIN_BLOCKING )
9 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 ) )
9 years ago
{
m_toggle_state = TS_GOING_UP;
SetMoveDone( &CFuncPlat::CallHitTop );
AngularMove( m_end, pev->speed );
}
else
{
// If ROTMOVE, move & rotate
CFuncPlat::GoUp();
9 years ago
SetMoveDone( &CFuncPlat::CallHitTop );
RotMove( m_end, pev->nextthink - pev->ltime );
}
9 years ago
// Otherwise, move first, rotate second
// If the train is moving with the platform, update it
if( m_code == TRAIN_FOLLOWING )
9 years ago
{
UpdateTrain( m_end );
m_train->m_ppath = NULL;
}
}
// Normal track change
void CFuncTrackChange::UpdateAutoTargets( int toggleState )
9 years ago
{
if( !m_trackTop || !m_trackBottom )
9 years ago
return;
if( toggleState == TS_AT_TOP )
9 years ago
ClearBits( m_trackTop->pev->spawnflags, SF_PATH_DISABLED );
else
SetBits( m_trackTop->pev->spawnflags, SF_PATH_DISABLED );
if( toggleState == TS_AT_BOTTOM )
9 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 )
9 years ago
{
if( m_toggle_state != TS_AT_TOP && m_toggle_state != TS_AT_BOTTOM )
9 years ago
return;
// If train is in "safe" area, but not on the elevator, play alarm sound
if( m_toggle_state == TS_AT_TOP )
9 years ago
m_code = EvaluateTrain( m_trackTop );
else if( m_toggle_state == TS_AT_BOTTOM )
9 years ago
m_code = EvaluateTrain( m_trackBottom );
else
m_code = TRAIN_BLOCKING;
if( m_code == TRAIN_BLOCKING )
9 years ago
{
// Play alarm and return
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "buttons/button11.wav", 1, ATTN_NORM );
9 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 )
9 years ago
GoDown();
else
GoUp();
}
//
// Platform has hit bottom. Stops and waits forever.
//
void CFuncTrackChange::HitBottom( void )
9 years ago
{
CFuncPlatRot::HitBottom();
if( m_code == TRAIN_FOLLOWING )
9 years ago
{
//UpdateTrain();
9 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 )
9 years ago
{
CFuncPlatRot::HitTop();
if( m_code == TRAIN_FOLLOWING )
9 years ago
{
//UpdateTrain();
9 years ago
m_train->SetTrack( m_trackTop );
}
9 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 );
9 years ago
virtual void UpdateAutoTargets( int toggleState );
};
LINK_ENTITY_TO_CLASS( func_trackautochange, CFuncTrackAuto )
9 years ago
// Auto track change
void CFuncTrackAuto::UpdateAutoTargets( int toggleState )
9 years ago
{
CPathTrack *pTarget, *pNextTarget;
if( !m_trackTop || !m_trackBottom )
9 years ago
return;
if( m_targetState == TS_AT_TOP )
9 years ago
{
pTarget = m_trackTop->GetNext();
pNextTarget = m_trackBottom->GetNext();
}
else
{
pTarget = m_trackBottom->GetNext();
pNextTarget = m_trackTop->GetNext();
}
if( pTarget )
9 years ago
{
ClearBits( pTarget->pev->spawnflags, SF_PATH_DISABLED );
if( m_code == TRAIN_FOLLOWING && m_train && m_train->pev->speed == 0 )
9 years ago
m_train->Use( this, this, USE_ON, 0 );
}
if( pNextTarget )
9 years ago
SetBits( pNextTarget->pev->spawnflags, SF_PATH_DISABLED );
}
void CFuncTrackAuto::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
9 years ago
{
CPathTrack *pTarget;
if( !UseEnabled() )
9 years ago
return;
if( m_toggle_state == TS_AT_TOP )
9 years ago
pTarget = m_trackTop;
else if( m_toggle_state == TS_AT_BOTTOM )
9 years ago
pTarget = m_trackBottom;
else
pTarget = NULL;
if( FClassnameIs( pActivator->pev, "func_tracktrain" ) )
9 years ago
{
m_code = EvaluateTrain( pTarget );
9 years ago
// Safe to fire?
if( m_code == TRAIN_FOLLOWING && m_toggle_state != m_targetState )
9 years ago
{
DisableUse();
if( m_toggle_state == TS_AT_TOP )
9 years ago
GoDown();
else
GoUp();
}
}
else
{
if( pTarget )
9 years ago
pTarget = pTarget->GetNext();
if( pTarget && m_train->m_ppath != pTarget && ShouldToggle( useType, m_targetState ) )
9 years ago
{
if( m_targetState == TS_AT_TOP )
9 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 );
9 years ago
static TYPEDESCRIPTION m_SaveData[];
9 years ago
private:
BOOL m_on;
9 years ago
};
LINK_ENTITY_TO_CLASS( func_guntarget, CGunTarget )
9 years ago
TYPEDESCRIPTION CGunTarget::m_SaveData[] =
{
DEFINE_FIELD( CGunTarget, m_on, FIELD_BOOLEAN ),
};
IMPLEMENT_SAVERESTORE( CGunTarget, CBaseMonster )
9 years ago
void CGunTarget::Spawn( void )
{
pev->solid = SOLID_BSP;
pev->movetype = MOVETYPE_PUSH;
9 years ago
UTIL_SetOrigin( pev, pev->origin );
SET_MODEL( ENT( pev ), STRING( pev->model ) );
9 years ago
if( pev->speed == 0 )
9 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 )
9 years ago
{
SetThink( &CGunTarget::Start );
pev->nextthink = pev->ltime + 0.3;
}
}
void CGunTarget::Activate( void )
{
CBaseEntity *pTarg;
9 years ago
// now find our next target
pTarg = GetNextTarget();
if( pTarg )
9 years ago
{
m_hTargetEnt = pTarg;
UTIL_SetOrigin( pev, pTarg->pev->origin - ( pev->mins + pev->maxs ) * 0.5 );
9 years ago
}
}
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 )
9 years ago
{
Stop();
return;
}
SetMoveDone( &CGunTarget::Wait );
LinearMove( pTarget->pev->origin - ( pev->mins + pev->maxs ) * 0.5, pev->speed );
9 years ago
}
void CGunTarget::Wait( void )
{
CBaseEntity *pTarget = m_hTargetEnt;
if( !pTarget )
9 years ago
{
Stop();
return;
}
// Fire the pass target if there is one
if( pTarget->pev->message )
9 years ago
{
FireTargets( STRING(pTarget->pev->message), this, this, USE_TOGGLE, 0 );
if( FBitSet( pTarget->pev->spawnflags, SF_CORNER_FIREONCE ) )
9 years ago
pTarget->pev->message = 0;
}
9 years ago
m_flWait = pTarget->GetDelay();
pev->target = pTarget->pev->target;
SetThink( &CGunTarget::Next );
if( m_flWait != 0 )
{
// -1 wait will wait forever!
9 years ago
pev->nextthink = pev->ltime + m_flWait;
}
else
{
Next();// do it RIGHT now!
}
}
void CGunTarget::Stop( void )
{
pev->velocity = g_vecZero;
pev->nextthink = 0;
pev->takedamage = DAMAGE_NO;
}
int CGunTarget::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType )
9 years ago
{
if( pev->health > 0 )
9 years ago
{
pev->health -= flDamage;
if( pev->health <= 0 )
9 years ago
{
pev->health = 0;
Stop();
if( pev->message )
FireTargets( STRING( pev->message ), this, this, USE_TOGGLE, 0 );
9 years ago
}
}
return 0;
}
void CGunTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
if( !ShouldToggle( useType, m_on ) )
9 years ago
return;
if( m_on )
9 years ago
{
Stop();
}
else
{
pev->takedamage = DAMAGE_AIM;
m_hTargetEnt = GetNextTarget();
if( m_hTargetEnt == NULL )
9 years ago
return;
pev->health = pev->max_health;
Next();
}
}