1074 lines
29 KiB
C++
1074 lines
29 KiB
C++
//=======================================================================
|
|
// Copyright (C) XashXT Group 2006
|
|
//=======================================================================
|
|
|
|
#include "extdll.h"
|
|
#include "utils.h"
|
|
#include "sfx.h"
|
|
#include "cbase.h"
|
|
#include "basebeams.h"
|
|
#include "baseweapon.h"
|
|
#include "monsters.h"
|
|
#include "defaults.h"
|
|
#include "player.h"
|
|
|
|
// Tracktrain spawn flags
|
|
#define SF_TRACKTRAIN_NOPITCH 0x0001
|
|
#define SF_TRACKTRAIN_NOCONTROL 0x0002
|
|
#define SF_TRACKTRAIN_FORWARDONLY 0x0004
|
|
#define SF_TRACKTRAIN_PASSABLE 0x0008
|
|
#define SF_TRACKTRAIN_NOYAW 0x0010 //LRC
|
|
#define SF_TRACKTRAIN_AVELOCITY 0x800000 //LRC - avelocity has been set manually, don't turn.
|
|
#define SF_TRACKTRAIN_AVEL_GEARS 0x400000 //LRC - avelocity should be scaled up/down when the train changes gear.
|
|
|
|
// Spawnflag for CPathTrack
|
|
#define SF_PATH_DISABLED 0x00000001
|
|
#define SF_PATH_FIREONCE 0x00000002
|
|
#define SF_PATH_ALTREVERSE 0x00000004
|
|
#define SF_PATH_DISABLE_TRAIN 0x00000008
|
|
#define SF_PATH_ALTERNATE 0x00008000
|
|
#define SF_PATH_AVELOCITY 0x00080000 //LRC
|
|
|
|
//LRC - values in 'armortype'
|
|
#define PATHSPEED_SET 0
|
|
#define PATHSPEED_ACCEL 1
|
|
#define PATHSPEED_TIME 2
|
|
#define PATHSPEED_SET_MASTER 3
|
|
|
|
|
|
//#define PATH_SPARKLE_DEBUG 1 // This makes a particle effect around path_track entities for debugging
|
|
class CPathTrack : public CPointEntity
|
|
{
|
|
public:
|
|
void Spawn( void );
|
|
void Activate( void );
|
|
void KeyValue( KeyValueData* pkvd);
|
|
|
|
void SetPrevious( CPathTrack *pprevious );
|
|
void Link( void );
|
|
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
|
|
|
|
CBaseEntity *ValidPath( CBaseEntity *ppath, int testFlag ); // Returns ppath if enabled, NULL otherwise
|
|
void Project( CBaseEntity *pstart, CBaseEntity *pend, Vector *origin, float dist );
|
|
|
|
static CPathTrack *Instance( edict_t *pent );
|
|
|
|
CBaseEntity *LookAhead( Vector *origin, float dist, int move );
|
|
CBaseEntity *Nearest( Vector origin ); //notused
|
|
|
|
CBaseEntity *GetNext( void );
|
|
CBaseEntity *GetPrev( void );
|
|
|
|
virtual int Save( CSave &save );
|
|
virtual int Restore( CRestore &restore );
|
|
|
|
static TYPEDESCRIPTION m_SaveData[];
|
|
#if PATH_SPARKLE_DEBUG
|
|
void EXPORT Sparkle(void);
|
|
#endif
|
|
|
|
float m_length;
|
|
string_t m_altName;
|
|
CPathTrack *m_pnext;
|
|
CPathTrack *m_pprevious;
|
|
CPathTrack *m_paltpath;
|
|
};
|
|
|
|
class CFuncTrackTrain : public CBaseMover
|
|
{
|
|
public:
|
|
void Spawn( void );
|
|
void Precache( void );
|
|
|
|
void Blocked( CBaseEntity *pOther );
|
|
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
|
|
void KeyValue( KeyValueData* pkvd );
|
|
|
|
void EXPORT DesiredAction( void ); //LRC - used to be called Next!
|
|
void PostActivate( void );
|
|
void ClearPointers( void );
|
|
|
|
// void EXPORT Next( void );
|
|
void EXPORT PostponeNext( void );
|
|
void EXPORT Find( void );
|
|
void EXPORT NearestPath( void );
|
|
void EXPORT DeadEnd( void );
|
|
|
|
void NextThink( float thinkTime, BOOL alwaysThink );
|
|
|
|
void SetTrack( CBaseEntity *track ) { pPath = ((CPathTrack *)track)->Nearest(pev->origin); }
|
|
void SetControls( entvars_t *pevControls );
|
|
BOOL OnControls( entvars_t *pev );
|
|
|
|
void StopSound ( void );
|
|
void UpdateSound ( void );
|
|
|
|
static CFuncTrackTrain *Instance( edict_t *pent )
|
|
{
|
|
if ( FClassnameIs( pent, "func_tracktrain" ) )
|
|
return (CFuncTrackTrain *)GET_PRIVATE(pent);
|
|
return NULL;
|
|
}
|
|
|
|
virtual int Save( CSave &save );
|
|
virtual int Restore( CRestore &restore );
|
|
|
|
static TYPEDESCRIPTION m_SaveData[];
|
|
virtual int ObjectCaps( void ) { return CBaseMover :: ObjectCaps() | FCAP_DIRECTIONAL_USE; }
|
|
|
|
virtual void OverrideReset( void );
|
|
|
|
CBaseEntity *pPath;
|
|
float m_length;
|
|
float m_height;
|
|
// I get it... this records the train's max speed (as set by the level designer), whereas
|
|
// pev->speed records the current speed (as set by the player). --LRC
|
|
// m_speed is also stored, as an int, in pev->impulse.
|
|
float m_speed;
|
|
float m_dir;
|
|
float m_startSpeed;
|
|
Vector m_controlMins;
|
|
Vector m_controlMaxs;
|
|
int m_soundPlaying;
|
|
float m_flBank;
|
|
float m_oldSpeed;
|
|
Vector m_vecBaseAvel; // LRC - the underlying avelocity, superceded by normal turning behaviour where applicable
|
|
};
|
|
|
|
TYPEDESCRIPTION CPathTrack::m_SaveData[] =
|
|
{
|
|
DEFINE_FIELD( CPathTrack, m_length, FIELD_FLOAT ),
|
|
DEFINE_FIELD( CPathTrack, m_pnext, FIELD_CLASSPTR ),
|
|
DEFINE_FIELD( CPathTrack, m_paltpath, FIELD_CLASSPTR ),
|
|
DEFINE_FIELD( CPathTrack, m_pprevious, FIELD_CLASSPTR ),
|
|
DEFINE_FIELD( CPathTrack, m_altName, FIELD_STRING ),
|
|
};
|
|
|
|
IMPLEMENT_SAVERESTORE( CPathTrack, CBaseEntity );
|
|
LINK_ENTITY_TO_CLASS( path_track, CPathTrack );
|
|
|
|
//
|
|
// Cache user-entity-field values until spawn is called.
|
|
//
|
|
void CPathTrack :: KeyValue( KeyValueData *pkvd )
|
|
{
|
|
if (FStrEq(pkvd->szKeyName, "altpath"))
|
|
{
|
|
m_altName = ALLOC_STRING(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "turnspeed")) //LRC
|
|
{
|
|
if (pkvd->szValue[0]) // if the field is blank, don't set the spawnflag.
|
|
{
|
|
pev->spawnflags |= SF_PATH_AVELOCITY;
|
|
UTIL_StringToVector( (float*)pev->avelocity, pkvd->szValue);
|
|
}
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else
|
|
CPointEntity::KeyValue( pkvd );
|
|
}
|
|
|
|
void CPathTrack :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
|
|
{
|
|
int on;
|
|
|
|
// Use toggles between two paths
|
|
if ( m_paltpath )
|
|
{
|
|
on = !FBitSet( pev->spawnflags, SF_PATH_ALTERNATE );
|
|
if (useType == USE_TOGGLE)
|
|
{
|
|
if(on) useType = USE_OFF;
|
|
else useType = USE_ON;
|
|
}
|
|
if (useType == USE_ON)ClearBits( pev->spawnflags, SF_PATH_ALTERNATE );
|
|
else if(useType == USE_OFF)SetBits( pev->spawnflags, SF_PATH_ALTERNATE );
|
|
}
|
|
else // Use toggles between enabled/disabled
|
|
{
|
|
on = !FBitSet( pev->spawnflags, SF_PATH_DISABLED );
|
|
if (useType == USE_TOGGLE)
|
|
{
|
|
if(on) useType = USE_OFF;
|
|
else useType = USE_ON;
|
|
}
|
|
if (useType == USE_ON)ClearBits( pev->spawnflags, SF_PATH_DISABLED );
|
|
else if(useType == USE_OFF)SetBits( pev->spawnflags, SF_PATH_DISABLED );
|
|
}
|
|
}
|
|
|
|
|
|
void CPathTrack :: Link( void )
|
|
{
|
|
CBaseEntity *pTarget;
|
|
|
|
if ( !FStringNull(pev->target) )
|
|
{
|
|
pTarget = UTIL_FindEntityByTargetname( NULL, STRING(pev->target) );
|
|
if ( pTarget )
|
|
{
|
|
m_pnext = (CPathTrack*)pTarget;
|
|
m_pnext->SetPrevious( this );
|
|
}
|
|
else
|
|
ALERT( at_console, "Dead end link %s\n", STRING(pev->target) );
|
|
}
|
|
|
|
// Find "alternate" path
|
|
if ( m_altName )
|
|
{
|
|
pTarget = UTIL_FindEntityByTargetname( NULL, STRING(m_altName) );
|
|
if ( pTarget ) // If no next pointer, this is the end of a path
|
|
{
|
|
m_paltpath = (CPathTrack*)pTarget;
|
|
m_paltpath->SetPrevious( this );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CPathTrack :: Spawn( void )
|
|
{
|
|
pev->solid = SOLID_TRIGGER;
|
|
UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8));
|
|
|
|
m_pnext = NULL;
|
|
m_pprevious = NULL;
|
|
// DEBUGGING CODE
|
|
#if PATH_SPARKLE_DEBUG
|
|
SetThink( Sparkle );
|
|
SetNextThink( 0.5 );
|
|
#endif
|
|
}
|
|
|
|
|
|
void CPathTrack::Activate( void )
|
|
{
|
|
if ( !FStringNull( pev->targetname ) ) // Link to next, and back-link
|
|
Link();
|
|
|
|
CPointEntity::Activate();
|
|
}
|
|
|
|
CBaseEntity *CPathTrack :: ValidPath( CBaseEntity *ppath, int testFlag )
|
|
{
|
|
if ( !ppath )
|
|
return NULL;
|
|
|
|
if ( testFlag && FBitSet( ppath->pev->spawnflags, SF_PATH_DISABLED ) )
|
|
return NULL;
|
|
|
|
return ppath;
|
|
}
|
|
|
|
|
|
void CPathTrack :: Project( CBaseEntity *pstart, CBaseEntity *pend, Vector *origin, float dist )
|
|
{
|
|
if ( pstart && pend )
|
|
{
|
|
Vector dir = (pend->pev->origin - pstart->pev->origin);
|
|
dir = dir.Normalize();
|
|
*origin = pend->pev->origin + dir * dist;
|
|
}
|
|
}
|
|
|
|
CBaseEntity *CPathTrack::GetNext( void )
|
|
{
|
|
if ( m_paltpath && FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ) && !FBitSet( pev->spawnflags, SF_PATH_ALTREVERSE ) )
|
|
return m_paltpath;
|
|
|
|
return m_pnext;
|
|
}
|
|
|
|
|
|
|
|
CBaseEntity *CPathTrack::GetPrev( void )
|
|
{
|
|
if ( m_paltpath && FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ) && FBitSet( pev->spawnflags, SF_PATH_ALTREVERSE ) )
|
|
return m_paltpath;
|
|
|
|
return m_pprevious;
|
|
}
|
|
|
|
|
|
|
|
void CPathTrack::SetPrevious( CPathTrack *pprev )
|
|
{
|
|
// Only set previous if this isn't my alternate path
|
|
if ( pprev && !FStrEq( STRING(pprev->pev->targetname), STRING(m_altName) ) )
|
|
m_pprevious = pprev;
|
|
}
|
|
|
|
|
|
// Assumes this is ALWAYS enabled
|
|
CBaseEntity *CPathTrack :: LookAhead( Vector *origin, float dist, int move )
|
|
{
|
|
CBaseEntity *pcurrent;
|
|
float originalDist = dist;
|
|
|
|
pcurrent = this;
|
|
Vector currentPos = *origin;
|
|
|
|
if ( dist < 0 ) // Travelling backwards through path
|
|
{
|
|
dist = -dist;
|
|
while ( dist > 0 )
|
|
{
|
|
Vector dir = pcurrent->pev->origin - currentPos;
|
|
float length = dir.Length();
|
|
if ( !length )
|
|
{
|
|
if ( !ValidPath(pcurrent->GetPrev(), move) ) // If there is no previous node, or it's disabled, return now.
|
|
{
|
|
if ( !move )
|
|
Project( pcurrent->GetNext(), pcurrent, origin, dist );
|
|
return NULL;
|
|
}
|
|
pcurrent = pcurrent->GetPrev();
|
|
}
|
|
else if ( length > dist ) // enough left in this path to move
|
|
{
|
|
*origin = currentPos + (dir * (dist / length));
|
|
return pcurrent;
|
|
}
|
|
else
|
|
{
|
|
dist -= length;
|
|
currentPos = pcurrent->pev->origin;
|
|
*origin = currentPos;
|
|
if ( !ValidPath(pcurrent->GetPrev(), move) ) // If there is no previous node, or it's disabled, return now.
|
|
return NULL;
|
|
|
|
pcurrent = pcurrent->GetPrev();
|
|
}
|
|
}
|
|
*origin = currentPos;
|
|
return pcurrent;
|
|
}
|
|
else
|
|
{
|
|
while ( dist > 0 )
|
|
{
|
|
if ( !ValidPath(pcurrent->GetNext(), move) ) // If there is no next node, or it's disabled, return now.
|
|
{
|
|
if ( !move )
|
|
Project( pcurrent->GetPrev(), pcurrent, origin, dist );
|
|
return NULL;
|
|
}
|
|
Vector dir = pcurrent->GetNext()->pev->origin - currentPos;
|
|
float length = dir.Length();
|
|
if ( !length && !ValidPath( pcurrent->GetNext()->GetNext(), move ) )
|
|
{
|
|
if ( dist == originalDist ) // HACK -- up against a dead end
|
|
return NULL;
|
|
return pcurrent;
|
|
}
|
|
if ( length > dist ) // enough left in this path to move
|
|
{
|
|
*origin = currentPos + (dir * (dist / length));
|
|
return pcurrent;
|
|
}
|
|
else
|
|
{
|
|
dist -= length;
|
|
currentPos = pcurrent->GetNext()->pev->origin;
|
|
pcurrent = pcurrent->GetNext();
|
|
*origin = currentPos;
|
|
}
|
|
}
|
|
*origin = currentPos;
|
|
}
|
|
|
|
return pcurrent;
|
|
}
|
|
|
|
|
|
// Assumes this is ALWAYS enabled
|
|
CBaseEntity *CPathTrack :: Nearest( Vector origin )
|
|
{
|
|
int deadCount;
|
|
float minDist, dist;
|
|
Vector delta;
|
|
CBaseEntity *ppath, *pnearest;
|
|
|
|
|
|
delta = origin - pev->origin;
|
|
delta.z = 0;
|
|
minDist = delta.Length();
|
|
pnearest = this;
|
|
ppath = GetNext();
|
|
|
|
// Hey, I could use the old 2 racing pointers solution to this, but I'm lazy :)
|
|
deadCount = 0;
|
|
while ( ppath && ppath != this )
|
|
{
|
|
deadCount++;
|
|
if ( deadCount > 9999 )
|
|
{
|
|
ALERT( at_error, "Bad sequence of path_tracks from %s", STRING(pev->targetname) );
|
|
return NULL;
|
|
}
|
|
delta = origin - ppath->pev->origin;
|
|
delta.z = 0;
|
|
dist = delta.Length();
|
|
if ( dist < minDist )
|
|
{
|
|
minDist = dist;
|
|
pnearest = ppath;
|
|
}
|
|
ppath = ppath->GetNext();
|
|
}
|
|
return pnearest;
|
|
}
|
|
|
|
|
|
CPathTrack *CPathTrack::Instance( edict_t *pent )
|
|
{
|
|
if ( FClassnameIs( pent, "path_track" ) )
|
|
return (CPathTrack *)GET_PRIVATE(pent);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// DEBUGGING CODE
|
|
#if PATH_SPARKLE_DEBUG
|
|
void CPathTrack :: Sparkle( void )
|
|
{
|
|
|
|
SetNextThink( 0.2 );
|
|
if ( FBitSet( pev->spawnflags, SF_PATH_DISABLED ) )
|
|
UTIL_ParticleEffect(pev->origin, Vector(0,0,100), 210, 10);
|
|
else
|
|
UTIL_ParticleEffect(pev->origin, Vector(0,0,100), 84, 10);
|
|
}
|
|
#endif
|
|
|
|
//=======================================================================
|
|
// func_tracktrain (controllable train)
|
|
//=======================================================================
|
|
void CFuncTrackTrain :: Precache( void )
|
|
{
|
|
CBaseBrush::Precache();//precache damage sound
|
|
|
|
int m_sounds = UTIL_LoadSoundPreset(m_iMoveSound);
|
|
switch (m_sounds)
|
|
{
|
|
case 1: pev->noise = UTIL_PrecacheSound("plats/ttrain1.wav");break;
|
|
case 2: pev->noise = UTIL_PrecacheSound("plats/ttrain2.wav");break;
|
|
case 3: pev->noise = UTIL_PrecacheSound("plats/ttrain3.wav");break;
|
|
case 4: pev->noise = UTIL_PrecacheSound("plats/ttrain4.wav");break;
|
|
case 5: pev->noise = UTIL_PrecacheSound("plats/ttrain6.wav");break;
|
|
case 6: pev->noise = UTIL_PrecacheSound("plats/ttrain7.wav");break;
|
|
default: pev->noise = UTIL_PrecacheSound(m_sounds); break;//custom sound or sentence
|
|
}
|
|
|
|
m_sounds = UTIL_LoadSoundPreset(m_iStopSound);
|
|
switch (m_sounds)
|
|
{
|
|
case 1: pev->noise1 = UTIL_PrecacheSound("plats/ttrain_brake1.wav");break;
|
|
default: pev->noise1 = UTIL_PrecacheSound(m_sounds); break;//custom sound or sentence
|
|
}
|
|
|
|
m_sounds = UTIL_LoadSoundPreset(m_iStartSound);
|
|
switch (m_sounds)
|
|
{
|
|
case 1: pev->noise2 = UTIL_PrecacheSound("plats/ttrain_start1.wav");break;
|
|
default: pev->noise2 = UTIL_PrecacheSound(m_sounds); break;//custom sound or sentence
|
|
}
|
|
}
|
|
|
|
void CFuncTrackTrain :: Spawn( void )
|
|
{
|
|
if ( pev->speed == 0 ) m_speed = 100;
|
|
else m_speed = pev->speed;
|
|
pev->speed = 0;
|
|
pev->velocity = g_vecZero;
|
|
m_vecBaseAvel = pev->avelocity; //LRC - save it for later
|
|
pev->avelocity = g_vecZero;
|
|
pev->impulse = m_speed;
|
|
m_dir = 1;
|
|
|
|
if ( FStringNull(pev->target) ) Msg("Warning: %s with no target!\n", STRING(pev->classname));
|
|
|
|
//if ( pev->spawnflags & SF_NOTSOLID ) pev->solid = SOLID_NOT;
|
|
if ( pev->spawnflags & 8 ) pev->solid = SOLID_NOT; //temp solution
|
|
else pev->solid = SOLID_BSP;
|
|
pev->movetype = MOVETYPE_PUSH;
|
|
SetBits (pFlags, PF_ANGULAR);
|
|
UTIL_SetModel( ENT(pev), pev->model );
|
|
|
|
UTIL_SetSize( pev, pev->mins, pev->maxs );
|
|
UTIL_SetOrigin( this, pev->origin );
|
|
pev->oldorigin = pev->origin; // Cache off placed origin for train controls
|
|
|
|
m_controlMins = pev->mins;
|
|
m_controlMaxs = pev->maxs;
|
|
m_controlMaxs.z += 72;
|
|
|
|
NextThink( 0.1, FALSE );
|
|
SetThink( Find );
|
|
Precache();
|
|
}
|
|
|
|
void CFuncTrackTrain :: NextThink( float thinkTime, BOOL alwaysThink )
|
|
{
|
|
if ( alwaysThink ) pev->flags |= FL_ALWAYSTHINK;
|
|
else pev->flags &= ~FL_ALWAYSTHINK;
|
|
SetNextThink( thinkTime, TRUE );
|
|
}
|
|
|
|
void CFuncTrackTrain :: Blocked( CBaseEntity *pOther )
|
|
{
|
|
// Blocker is on-ground on the train
|
|
if ( FBitSet( pOther->pev->flags, FL_ONGROUND ) && VARS(pOther->pev->groundentity) == pev )
|
|
{
|
|
float deltaSpeed = fabs(pev->speed);
|
|
if ( deltaSpeed > 50 ) deltaSpeed = 50;
|
|
if ( !pOther->pev->velocity.z ) pOther->pev->velocity.z += deltaSpeed;
|
|
return;
|
|
}
|
|
else pOther->pev->velocity = (pOther->pev->origin - pev->origin ).Normalize() * pev->dmg;
|
|
if ( pev->dmg <= 0 ) return;// we can't hurt this thing, so we're not concerned with it
|
|
pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH );
|
|
}
|
|
|
|
void CFuncTrackTrain::OverrideReset( void )
|
|
{
|
|
NextThink( 0.1, FALSE );
|
|
SetThink( NearestPath );
|
|
}
|
|
|
|
void CFuncTrackTrain :: Find( void )
|
|
{
|
|
pPath = (CPathTrack*)UTIL_FindEntityByTargetname( NULL, STRING(pev->target) );
|
|
if ( !pPath ) return;
|
|
|
|
entvars_t *pevTarget = pPath->pev;
|
|
if ( !FClassnameIs( pevTarget, "path_track" ) )
|
|
{
|
|
ALERT( at_error, "func_track_train must be on a path of path_track\n" );
|
|
pPath = NULL;
|
|
return;
|
|
}
|
|
|
|
Vector nextPos = pevTarget->origin;
|
|
nextPos.z += m_height;
|
|
|
|
Vector look = nextPos;
|
|
look.z -= m_height;
|
|
((CPathTrack *)pPath)->LookAhead( &look, m_length, 0 );
|
|
look.z += m_height;
|
|
|
|
Vector vTemp = UTIL_VecToAngles( look - nextPos );
|
|
vTemp.y += 180;
|
|
|
|
if ( pev->spawnflags & SF_TRACKTRAIN_NOPITCH )
|
|
{
|
|
vTemp.x = 0;
|
|
//pev->angles.x = 0;
|
|
}
|
|
|
|
UTIL_AssignAngles(this, vTemp);
|
|
UTIL_AssignOrigin ( this, nextPos );
|
|
|
|
NextThink( 0.1, FALSE );
|
|
SetThink( PostponeNext );
|
|
pev->speed = m_startSpeed;
|
|
|
|
UpdateSound();
|
|
}
|
|
|
|
TYPEDESCRIPTION CFuncTrackTrain::m_SaveData[] =
|
|
{
|
|
DEFINE_FIELD( CFuncTrackTrain, 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_flVolume, FIELD_FLOAT ),
|
|
DEFINE_FIELD( CFuncTrackTrain, m_flBank, FIELD_FLOAT ),
|
|
DEFINE_FIELD( CFuncTrackTrain, m_oldSpeed, FIELD_FLOAT ),
|
|
};
|
|
IMPLEMENT_SAVERESTORE( CFuncTrackTrain, CBaseMover );
|
|
|
|
|
|
void CFuncTrackTrain :: KeyValue( KeyValueData *pkvd )
|
|
{
|
|
if (FStrEq(pkvd->szKeyName, "wheels"))
|
|
{
|
|
m_length = atof(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "height"))
|
|
{
|
|
m_height = atof(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "startspeed"))
|
|
{
|
|
m_startSpeed = atof(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "sounds"))
|
|
{
|
|
m_iMoveSound = ALLOC_STRING(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else if (FStrEq(pkvd->szKeyName, "bank"))
|
|
{
|
|
m_flBank = atof(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else CBaseMover::KeyValue( pkvd );
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void CFuncTrackTrain :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
|
|
{
|
|
// ALERT(at_console, "TRAIN: use\n");
|
|
|
|
m_hActivator = pActivator; //AJH
|
|
|
|
if (useType == USE_TOGGLE)
|
|
{
|
|
if(pev->speed != 0) useType = USE_OFF;//temp solution
|
|
else useType = USE_ON;
|
|
}
|
|
if (useType == USE_ON)
|
|
{
|
|
pev->speed = m_speed * m_dir;
|
|
PostponeNext();
|
|
}
|
|
else if ( useType == USE_OFF )
|
|
{
|
|
pev->speed = 0;
|
|
UTIL_SetVelocity(this, g_vecZero); //LRC
|
|
UTIL_SetAvelocity(this, g_vecZero); //LRC
|
|
StopSound();
|
|
SetThink( NULL );
|
|
}
|
|
else if ( useType == USE_SET )
|
|
{
|
|
float delta = value;
|
|
|
|
delta = ((int)(pev->speed * 4) / (int)m_speed)*0.25 + 0.25 * delta;
|
|
if ( delta > 1 )
|
|
delta = 1;
|
|
else if ( delta < -1 )
|
|
delta = -1;
|
|
if ( pev->spawnflags & SF_TRACKTRAIN_FORWARDONLY )
|
|
{
|
|
if ( delta < 0 )delta = 0;
|
|
}
|
|
pev->speed = m_speed * delta;
|
|
|
|
if(pev->speed == 0)
|
|
{
|
|
UTIL_SetVelocity(this, g_vecZero);
|
|
UTIL_SetAvelocity(this, g_vecZero);
|
|
StopSound();
|
|
SetThink( NULL );
|
|
}
|
|
if(pPath == NULL)
|
|
{
|
|
delta = 0; //G-Cont. Set speed to 0, and don't controls, if tracktrain on trackchange
|
|
return;
|
|
}
|
|
PostponeNext();
|
|
ALERT( at_aiconsole, "TRAIN(%s), speed to %.2f\n", STRING(pev->targetname), pev->speed );
|
|
}
|
|
}
|
|
|
|
#define TRAIN_STARTPITCH 60
|
|
#define TRAIN_MAXPITCH 200
|
|
#define TRAIN_MAXSPEED 1000 // approx max speed for sound pitch calculation
|
|
|
|
void CFuncTrackTrain :: StopSound( void )
|
|
{
|
|
// if sound playing, stop it
|
|
if (m_soundPlaying && pev->noise)
|
|
{
|
|
STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise));
|
|
EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, "plats/ttrain_brake1.wav", m_flVolume, ATTN_NORM, 0, 100);
|
|
}
|
|
|
|
m_soundPlaying = 0;
|
|
}
|
|
|
|
void CFuncTrackTrain :: UpdateSound( void )
|
|
{
|
|
float flpitch;
|
|
|
|
if (!pev->noise)
|
|
return;
|
|
|
|
flpitch = TRAIN_STARTPITCH + (abs(pev->speed) * (TRAIN_MAXPITCH - TRAIN_STARTPITCH) / TRAIN_MAXSPEED);
|
|
|
|
if (!m_soundPlaying)
|
|
{
|
|
// play startup sound for train
|
|
EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, "plats/ttrain_start1.wav", m_flVolume, ATTN_NORM, 0, 100);
|
|
EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise), m_flVolume, ATTN_NORM, 0, (int) flpitch);
|
|
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);
|
|
}
|
|
}
|
|
|
|
void CFuncTrackTrain::ClearPointers( void )
|
|
{
|
|
CBaseEntity::ClearPointers();
|
|
pPath = NULL;
|
|
}
|
|
|
|
void CFuncTrackTrain :: PostActivate( void )
|
|
{
|
|
}
|
|
void CFuncTrackTrain :: PostponeNext( void )
|
|
{
|
|
UTIL_SetAction( this );
|
|
}
|
|
|
|
void CFuncTrackTrain :: DesiredAction( void ) // Next( void )
|
|
{
|
|
float time = 0.5;
|
|
|
|
if ( !pev->speed )
|
|
{
|
|
UTIL_SetVelocity(this, g_vecZero);
|
|
DontThink();
|
|
ALERT( at_aiconsole, "TRAIN(%s): Speed is 0\n", STRING(pev->targetname) );
|
|
StopSound();
|
|
return;
|
|
}
|
|
|
|
if ( !pPath )
|
|
{
|
|
UTIL_SetVelocity(this, g_vecZero);
|
|
DontThink();
|
|
ALERT( at_aiconsole, "TRAIN(%s): Lost path\n", STRING(pev->targetname) );
|
|
StopSound();
|
|
return;
|
|
}
|
|
|
|
UpdateSound();
|
|
|
|
Vector nextPos = pev->origin;
|
|
|
|
nextPos.z -= m_height;
|
|
CBaseEntity *pnext = ((CPathTrack *)pPath)->LookAhead( &nextPos, pev->speed * 0.1, 1 );
|
|
nextPos.z += m_height;
|
|
|
|
UTIL_SetVelocity( this, (nextPos - pev->origin) * 10 ); //LRC
|
|
Vector nextFront = pev->origin;
|
|
|
|
nextFront.z -= m_height;
|
|
if ( m_length > 0 )
|
|
((CPathTrack *)pPath)->LookAhead( &nextFront, m_length, 0 );
|
|
else
|
|
((CPathTrack *)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; //LRC, FIXME: add a 'built facing' field.
|
|
|
|
angles.fixangle();
|
|
pev->angles.fixangle();
|
|
|
|
if ( !pnext || (delta.x == 0 && delta.y == 0) ) angles = pev->angles;
|
|
|
|
float vy, vx, vz;
|
|
if ( !(pev->spawnflags & SF_TRACKTRAIN_NOPITCH) ) vx = 10*UTIL_AngleDistance( angles.x, pev->angles.x );
|
|
else vx = m_vecBaseAvel.x;
|
|
|
|
if ( !(pev->spawnflags & SF_TRACKTRAIN_NOYAW) ) vy = 10*UTIL_AngleDistance( angles.y, pev->angles.y );
|
|
else vy = m_vecBaseAvel.y;
|
|
|
|
if ( m_flBank != 0 )
|
|
{
|
|
if ( pev->avelocity.y < -5 ) vz = UTIL_AngleDistance( UTIL_ApproachAngle( -m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z);
|
|
else if ( pev->avelocity.y > 5 ) vz = UTIL_AngleDistance( UTIL_ApproachAngle( m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z);
|
|
else vz = UTIL_AngleDistance( UTIL_ApproachAngle( 0, pev->angles.z, m_flBank*4 ), pev->angles.z) * 4;
|
|
}
|
|
else vz = m_vecBaseAvel.z;
|
|
UTIL_SetAvelocity(this, Vector(vx, vy, vz));
|
|
|
|
if ( pnext )
|
|
{
|
|
if ( pnext != pPath )
|
|
{
|
|
CBaseEntity *pFire;
|
|
if ( pev->speed >= 0 ) // check whether we're going forwards or backwards
|
|
pFire = pnext;
|
|
else
|
|
pFire = pPath;
|
|
|
|
pPath = pnext;
|
|
// Fire the pass target if there is one
|
|
if ( pFire->pev->message )
|
|
{
|
|
UTIL_FireTargets( pFire->pev->message, this, this, USE_TOGGLE );
|
|
if ( FBitSet( pFire->pev->spawnflags, SF_PATH_FIREONCE ) )
|
|
pFire->pev->message = 0;
|
|
}
|
|
|
|
if ( pFire->pev->spawnflags & SF_PATH_DISABLE_TRAIN )
|
|
pev->spawnflags |= SF_TRACKTRAIN_NOCONTROL;
|
|
|
|
float setting = ((int)(pev->speed*4) / (int)m_speed) / 4.0; //LRC - one of { 1, 0.75, 0.5, 0.25, 0, ... -1 }
|
|
|
|
CBaseEntity* pDest; //LRC - the path_track we're heading for, after pFire.
|
|
if (pev->speed > 0)
|
|
pDest = pFire->GetNext();
|
|
else
|
|
pDest = pFire->GetPrev();
|
|
|
|
if ( pFire->pev->speed != 0)
|
|
{
|
|
//ALERT( at_console, "TrackTrain setting is %d / %d = %.2f\n", (int)(pev->speed*4), (int)m_speed, setting );
|
|
|
|
switch ( (int)(pFire->pev->armortype) )
|
|
{
|
|
case PATHSPEED_SET:
|
|
// Don't override speed if under user control
|
|
if (pev->spawnflags & SF_TRACKTRAIN_NOCONTROL)
|
|
pev->speed = pFire->pev->speed;
|
|
ALERT( at_aiconsole, "TrackTrain %s speed set to %4.2f\n", STRING(pev->targetname), pev->speed );
|
|
break;
|
|
case PATHSPEED_SET_MASTER:
|
|
m_speed = pFire->pev->speed;
|
|
pev->impulse = m_speed;
|
|
pev->speed = setting * m_speed;
|
|
ALERT( at_aiconsole, "TrackTrain %s master speed set to %4.2f\n", STRING(pev->targetname), pev->speed );
|
|
break;
|
|
case PATHSPEED_ACCEL:
|
|
m_speed += pFire->pev->speed;
|
|
pev->impulse = m_speed;
|
|
pev->speed = setting * m_speed;
|
|
ALERT( at_aiconsole, "TrackTrain %s speed accel to %4.2f\n", STRING(pev->targetname), pev->speed );
|
|
break;
|
|
case PATHSPEED_TIME:
|
|
float distance = (pev->origin - pDest->pev->origin).Length();
|
|
//ALERT(at_console, "pFire=%s, distance=%.2f, ospeed=%.2f, nspeed=%.2f\n", STRING(pFire->pev->targetname), distance, pev->speed, distance / pFire->pev->speed);
|
|
m_speed = distance / pFire->pev->speed;
|
|
pev->impulse = m_speed;
|
|
pev->speed = setting * m_speed;
|
|
ALERT( at_aiconsole, "TrackTrain %s speed to %4.2f (timed)\n", STRING(pev->targetname), pev->speed );
|
|
break;
|
|
}
|
|
}
|
|
//LRC- FIXME: add support, here, for a Teleport flag.
|
|
}
|
|
SetThink( PostponeNext );
|
|
NextThink( time, TRUE );
|
|
}
|
|
else // end of path, stop
|
|
{
|
|
Vector vecTemp; //LRC
|
|
StopSound();
|
|
vecTemp = (nextPos - pev->origin); //LRC
|
|
|
|
UTIL_SetAvelocity(this, g_vecZero);
|
|
float distance = vecTemp.Length(); //LRC
|
|
m_oldSpeed = pev->speed;
|
|
|
|
|
|
pev->speed = 0;
|
|
|
|
// Move to the dead end
|
|
|
|
// Are we there yet?
|
|
if ( distance > 0 )
|
|
{
|
|
// no, how long to get there?
|
|
time = distance / m_oldSpeed;
|
|
UTIL_SetVelocity( this, vecTemp * (m_oldSpeed / distance) );
|
|
SetThink( DeadEnd );
|
|
NextThink( time, FALSE );
|
|
}
|
|
else
|
|
{
|
|
UTIL_SetVelocity( this, vecTemp );
|
|
DeadEnd();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CFuncTrackTrain::DeadEnd( void )
|
|
{
|
|
// Fire the dead-end target if there is one
|
|
CBaseEntity *pTrack, *pNext;
|
|
|
|
pTrack = pPath;
|
|
|
|
ALERT( at_aiconsole, "TRAIN(%s): Dead end ", STRING(pev->targetname) );
|
|
// Find the dead end path node
|
|
// HACKHACK -- This is bugly, but the train can actually stop moving at a different node depending on it's speed
|
|
// so we have to traverse the list to it's end.
|
|
if ( pTrack )
|
|
{
|
|
if ( m_oldSpeed < 0 )
|
|
{
|
|
do
|
|
{
|
|
pNext = ((CPathTrack *)pTrack)->ValidPath( pTrack->GetPrev(), TRUE );
|
|
if ( pNext )
|
|
pTrack = pNext;
|
|
} while ( pNext );
|
|
}
|
|
else
|
|
{
|
|
do
|
|
{
|
|
pNext = ((CPathTrack *)pTrack)->ValidPath( pTrack->GetNext(), TRUE );
|
|
if ( pNext )
|
|
pTrack = pNext;
|
|
} while ( pNext );
|
|
}
|
|
}
|
|
|
|
UTIL_SetVelocity( this, g_vecZero );
|
|
UTIL_SetAvelocity(this, g_vecZero );
|
|
|
|
if ( pTrack )
|
|
{
|
|
ALERT( at_aiconsole, "at %s\n", STRING(pTrack->pev->targetname) );
|
|
if ( pTrack->pev->netname )
|
|
UTIL_FireTargets( pTrack->pev->netname, this, this, USE_TOGGLE );
|
|
}
|
|
}
|
|
|
|
|
|
void CFuncTrackTrain :: SetControls( entvars_t *pevControls )
|
|
{
|
|
Vector offset = pevControls->origin - pev->oldorigin;
|
|
|
|
m_controlMins = pevControls->mins + offset;
|
|
m_controlMaxs = pevControls->maxs + offset;
|
|
}
|
|
|
|
|
|
BOOL CFuncTrackTrain :: OnControls( entvars_t *pevTest )
|
|
{
|
|
Vector offset = pevTest->origin - pev->origin;
|
|
|
|
if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL )
|
|
return FALSE;
|
|
|
|
// Transform offset into local coordinates
|
|
UTIL_MakeVectors( pev->angles );
|
|
Vector local;
|
|
local.x = DotProduct( offset, gpGlobals->v_forward );
|
|
local.y = -DotProduct( offset, gpGlobals->v_right );
|
|
local.z = DotProduct( offset, gpGlobals->v_up );
|
|
|
|
if ( local.x >= m_controlMins.x && local.y >= m_controlMins.y && local.z >= m_controlMins.z &&
|
|
local.x <= m_controlMaxs.x && local.y <= m_controlMaxs.y && local.z <= m_controlMaxs.z )
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void CFuncTrackTrain :: NearestPath( void )
|
|
{
|
|
CBaseEntity *pTrack = NULL;
|
|
CBaseEntity *pNearest = NULL;
|
|
float dist, closest;
|
|
|
|
closest = 1024;
|
|
|
|
while ((pTrack = UTIL_FindEntityInSphere( pTrack, pev->origin, 1024 )) != NULL)
|
|
{
|
|
// filter out non-tracks
|
|
if ( !(pTrack->pev->flags & (FL_CLIENT|FL_MONSTER)) && FClassnameIs( pTrack->pev, "path_track" ) )
|
|
{
|
|
dist = (pev->origin - pTrack->pev->origin).Length();
|
|
if ( dist < closest )
|
|
{
|
|
closest = dist;
|
|
pNearest = pTrack;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !pNearest )
|
|
{
|
|
ALERT( at_console, "Can't find a nearby track !!!\n" );
|
|
SetThink(NULL);
|
|
return;
|
|
}
|
|
|
|
ALERT( at_aiconsole, "TRAIN: %s, Nearest track is %s\n", STRING(pev->targetname), STRING(pNearest->pev->targetname) );
|
|
// If I'm closer to the next path_track on this path, then it's my real path
|
|
pTrack = ((CPathTrack *)pNearest)->GetNext();
|
|
if ( pTrack )
|
|
{
|
|
if ( (pev->origin - pTrack->pev->origin).Length() < (pev->origin - pNearest->pev->origin).Length() )
|
|
pNearest = pTrack;
|
|
}
|
|
|
|
pPath = (CPathTrack *)pNearest;
|
|
|
|
if ( pev->speed != 0 )
|
|
{
|
|
NextThink( 0.1, FALSE );
|
|
SetThink( PostponeNext );
|
|
}
|
|
}
|
|
LINK_ENTITY_TO_CLASS( func_tracktrain, CFuncTrackTrain );
|
|
|
|
//=======================================================================
|
|
// volume of space that the player must stand in to control the train
|
|
//=======================================================================
|
|
class CFuncTrainControls : public CPointEntity
|
|
{
|
|
public:
|
|
void Spawn( void );
|
|
void PostSpawn( void );
|
|
};
|
|
LINK_ENTITY_TO_CLASS( func_traincontrols, CFuncTrainControls );
|
|
|
|
void CFuncTrainControls :: PostSpawn( void )
|
|
{
|
|
CBaseEntity *pTarget = NULL;
|
|
|
|
do
|
|
{
|
|
pTarget = UTIL_FindEntityByTargetname( pTarget, STRING(pev->target) );
|
|
} while ( pTarget && !FClassnameIs(pTarget->pev, "func_tracktrain") );
|
|
|
|
if ( !pTarget )
|
|
{
|
|
ALERT( at_console, "TrackTrainControls: No train %s\n", STRING(pev->target) );
|
|
return;
|
|
}
|
|
|
|
CFuncTrackTrain *ptrain = (CFuncTrackTrain*)pTarget;
|
|
ptrain->SetControls( pev );
|
|
UTIL_Remove( this );
|
|
}
|
|
|
|
void CFuncTrainControls :: Spawn( void )
|
|
{
|
|
pev->solid = SOLID_NOT;
|
|
pev->movetype = MOVETYPE_NONE;
|
|
UTIL_SetModel( ENT(pev), pev->model );
|
|
|
|
UTIL_SetSize( pev, pev->mins, pev->maxs );
|
|
UTIL_SetOrigin( this, pev->origin );
|
|
} |