source-engine/game/server/tanktrain.cpp

536 lines
14 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "trains.h"
#include "entitylist.h"
#include "soundenvelope.h"
#include "engine/IEngineSound.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern short g_sModelIndexFireball;
#define SPRITE_FIREBALL "sprites/zerogxplode.vmt"
#define SPRITE_SMOKE "sprites/steam1.vmt"
void UTIL_RemoveHierarchy( CBaseEntity *pDead )
{
if ( !pDead )
return;
if ( pDead->edict() )
{
CBaseEntity *pChild = pDead->FirstMoveChild();
while ( pChild )
{
CBaseEntity *pEntity = pChild;
pChild = pChild->NextMovePeer();
UTIL_RemoveHierarchy( pEntity );
}
}
UTIL_Remove( pDead );
}
class CFuncTankTrain : public CFuncTrackTrain
{
public:
DECLARE_CLASS( CFuncTankTrain, CFuncTrackTrain );
void Spawn( void );
// Filter out damage messages that don't contain blast damage (impervious to other forms of attack)
int OnTakeDamage( const CTakeDamageInfo &info );
void Event_Killed( const CTakeDamageInfo &info );
void Blocked( CBaseEntity *pOther )
{
// FIxme, set speed to zero?
}
DECLARE_DATADESC();
private:
COutputEvent m_OnDeath;
};
LINK_ENTITY_TO_CLASS( func_tanktrain, CFuncTankTrain );
BEGIN_DATADESC( CFuncTankTrain )
// Outputs
DEFINE_OUTPUT(m_OnDeath, "OnDeath"),
END_DATADESC()
void CFuncTankTrain::Spawn( void )
{
m_takedamage = true;
BaseClass::Spawn();
}
// Filter out damage messages that don't contain blast damage (impervious to other forms of attack)
int CFuncTankTrain::OnTakeDamage( const CTakeDamageInfo &info )
{
if ( ! (info.GetDamageType() & DMG_BLAST) )
return 0;
return BaseClass::OnTakeDamage( info );
}
//-----------------------------------------------------------------------------
// Purpose: Called when the train is killed.
// Input : pInflictor - What killed us.
// pAttacker - Who killed us.
// flDamage - The damage that the killing blow inflicted.
// bitsDamageType - Bitfield of damage types that were inflicted.
//-----------------------------------------------------------------------------
void CFuncTankTrain::Event_Killed( const CTakeDamageInfo &info )
{
m_takedamage = DAMAGE_NO;
m_lifeState = LIFE_DEAD;
m_OnDeath.FireOutput( info.GetInflictor(), this );
}
//-----------------------------------------------------------------------------
// Purpose: Changes the target entity for a func_tank or tanktrain_ai
//-----------------------------------------------------------------------------
class CTankTargetChange : public CPointEntity
{
public:
DECLARE_CLASS( CTankTargetChange, CPointEntity );
void Precache( void );
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
DECLARE_DATADESC();
private:
variant_t m_newTarget;
string_t m_newTargetName;
};
LINK_ENTITY_TO_CLASS( tanktrain_aitarget, CTankTargetChange );
BEGIN_DATADESC( CTankTargetChange )
// DEFINE_FIELD( m_newTarget, variant_t ),
DEFINE_KEYFIELD( m_newTargetName, FIELD_STRING, "newtarget" ),
END_DATADESC()
void CTankTargetChange::Precache( void )
{
BaseClass::Precache();
// This needs to be in Precache so save/load works
m_newTarget.SetString( m_newTargetName );
}
void CTankTargetChange::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_target, NULL, pActivator, pCaller );
// UNDONE: This should use more of the event system
while ( pTarget )
{
// Change the target over
pTarget->AcceptInput( "TargetEntity", this, this, m_newTarget, 0 );
pTarget = gEntList.FindEntityByName( pTarget, m_target, NULL, pActivator, pCaller );
}
}
// UNDONE: Should be just a logical entity, but we act as another static sound channel for the train
class CTankTrainAI : public CPointEntity
{
public:
DECLARE_CLASS( CTankTrainAI, CPointEntity );
virtual ~CTankTrainAI( void );
void Precache( void );
void Spawn( void );
void Activate( void );
void Think( void );
int SoundEnginePitch( void );
void SoundEngineStart( void );
void SoundEngineStop( void );
void SoundShutdown( void );
CBaseEntity *FindTarget( string_t target, CBaseEntity *pActivator );
DECLARE_DATADESC();
// INPUTS
void InputTargetEntity( inputdata_t &inputdata );
private:
CHandle<CFuncTrackTrain> m_hTrain;
EHANDLE m_hTargetEntity;
int m_soundPlaying;
CSoundPatch *m_soundTreads;
CSoundPatch *m_soundEngine;
string_t m_startSoundName;
string_t m_engineSoundName;
string_t m_movementSoundName;
string_t m_targetEntityName;
};
LINK_ENTITY_TO_CLASS( tanktrain_ai, CTankTrainAI );
BEGIN_DATADESC( CTankTrainAI )
DEFINE_FIELD( m_hTrain, FIELD_EHANDLE),
DEFINE_FIELD( m_hTargetEntity, FIELD_EHANDLE),
DEFINE_FIELD( m_soundPlaying, FIELD_INTEGER),
DEFINE_SOUNDPATCH( m_soundTreads ),
DEFINE_SOUNDPATCH( m_soundEngine ),
DEFINE_KEYFIELD( m_startSoundName, FIELD_STRING, "startsound" ),
DEFINE_KEYFIELD( m_engineSoundName, FIELD_STRING, "enginesound" ),
DEFINE_KEYFIELD( m_movementSoundName, FIELD_STRING, "movementsound" ),
DEFINE_FIELD( m_targetEntityName, FIELD_STRING),
// Inputs
DEFINE_INPUTFUNC( FIELD_STRING, "TargetEntity", InputTargetEntity ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose: Input handler for setting the target entity by name.
//-----------------------------------------------------------------------------
void CTankTrainAI::InputTargetEntity( inputdata_t &inputdata )
{
m_targetEntityName = inputdata.value.StringID();
m_hTargetEntity = FindTarget( m_targetEntityName, inputdata.pActivator );
SetNextThink( gpGlobals->curtime );
}
//-----------------------------------------------------------------------------
// Purpose: Finds the first entity in the entity list with the given name.
// Input : target - String ID of the entity to find.
// pActivator - The activating entity if this is called from an input
// or Use handler, NULL otherwise.
//-----------------------------------------------------------------------------
CBaseEntity *CTankTrainAI::FindTarget( string_t target, CBaseEntity *pActivator )
{
return gEntList.FindEntityGeneric( NULL, STRING( target ), this, pActivator );
}
CTankTrainAI::~CTankTrainAI( void )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
if ( m_soundTreads )
{
controller.SoundDestroy( m_soundTreads );
}
if ( m_soundEngine )
{
controller.SoundDestroy( m_soundEngine );
}
}
void CTankTrainAI::Precache( void )
{
PrecacheScriptSound( STRING( m_startSoundName ) );
PrecacheScriptSound( STRING( m_engineSoundName ) );
PrecacheScriptSound( STRING( m_movementSoundName ) );
}
int CTankTrainAI::SoundEnginePitch( void )
{
CFuncTrackTrain *pTrain = m_hTrain;
// we know this isn't NULL here
if ( pTrain->GetMaxSpeed() )
{
return 90 + (fabs(pTrain->GetCurrentSpeed()) * (20) / pTrain->GetMaxSpeed());
}
return 100;
}
void CTankTrainAI::SoundEngineStart( void )
{
CFuncTrackTrain *pTrain = m_hTrain;
SoundEngineStop();
// play startup sound for train
if ( m_startSoundName != NULL_STRING )
{
CPASAttenuationFilter filter( pTrain );
EmitSound_t ep;
ep.m_nChannel = CHAN_ITEM;
ep.m_pSoundName = STRING(m_startSoundName);
ep.m_flVolume = 1.0f;
ep.m_SoundLevel = SNDLVL_NORM;
EmitSound( filter, pTrain->entindex(), ep );
}
// play the looping sounds using the envelope controller
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
if ( m_soundTreads )
{
controller.Play( m_soundTreads, 1.0, 100 );
}
if ( m_soundEngine )
{
controller.Play( m_soundEngine, 0.5, 90 );
controller.CommandClear( m_soundEngine );
controller.CommandAdd( m_soundEngine, 0, SOUNDCTRL_CHANGE_PITCH, 1.5, random->RandomInt(130, 145) );
controller.CommandAdd( m_soundEngine, 1.5, SOUNDCTRL_CHANGE_PITCH, 2, random->RandomInt(105, 115) );
}
m_soundPlaying = true;
}
void CTankTrainAI::SoundEngineStop( void )
{
if ( !m_soundPlaying )
return;
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
if ( m_soundTreads )
{
controller.SoundFadeOut( m_soundTreads, 0.25 );
}
if ( m_soundEngine )
{
controller.CommandClear( m_soundEngine );
controller.SoundChangePitch( m_soundEngine, 70, 3.0 );
}
m_soundPlaying = false;
}
void CTankTrainAI::SoundShutdown( void )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
if ( m_soundTreads )
{
controller.Shutdown( m_soundTreads );
}
if ( m_soundEngine )
{
controller.Shutdown( m_soundEngine );
}
m_soundPlaying = false;
}
//-----------------------------------------------------------------------------
// Purpose: Set up think and AI
//-----------------------------------------------------------------------------
void CTankTrainAI::Spawn( void )
{
Precache();
m_soundPlaying = false;
m_hTargetEntity = NULL;
}
void CTankTrainAI::Activate( void )
{
BaseClass::Activate();
CBaseEntity *pTarget = NULL;
CFuncTrackTrain *pTrain = NULL;
if ( m_target != NULL_STRING )
{
do
{
pTarget = gEntList.FindEntityByName( pTarget, m_target );
pTrain = dynamic_cast<CFuncTrackTrain *>(pTarget);
} while (!pTrain && pTarget);
}
m_hTrain = pTrain;
if ( pTrain )
{
SetNextThink( gpGlobals->curtime + 0.5f );
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
if ( m_movementSoundName != NULL_STRING )
{
CPASAttenuationFilter filter( this, ATTN_NORM * 0.5 );
m_soundTreads = controller.SoundCreate( filter, pTrain->entindex(), CHAN_STATIC, STRING(m_movementSoundName), ATTN_NORM*0.5 );
}
if ( m_engineSoundName != NULL_STRING )
{
CPASAttenuationFilter filter( this );
m_soundEngine = controller.SoundCreate( filter, pTrain->entindex(), CHAN_STATIC, STRING(m_engineSoundName), ATTN_NORM );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Dumb linear serach of the path
// Input : *pStart - starting path node
// &startPosition - starting position
// &destination - position to move close to
// Output : int move direction 1 = forward, -1 = reverse, 0 = stop
//-----------------------------------------------------------------------------
int PathFindDirection( CPathTrack *pStart, const Vector &startPosition, const Vector &destination )
{
if ( !pStart )
return 0; // no path, don't move
CPathTrack *pPath = pStart->m_pnext;
CPathTrack *pNearest = pStart;
float nearestDist = (pNearest->GetLocalOrigin() - destination).LengthSqr();
float length = 0;
float nearestForward = 0, nearestReverse = 0;
do
{
float dist = (pPath->GetLocalOrigin() - destination).LengthSqr();
// This is closer than our current estimate
if ( dist < nearestDist )
{
nearestDist = dist;
pNearest = pPath;
nearestForward = length; // current path length forward
nearestReverse = 0; // count until we hit the start again
}
CPathTrack *pNext = pPath->m_pnext;
if ( pNext )
{
// UNDONE: Cache delta in path?
float delta = (pNext->GetLocalOrigin() - pPath->GetLocalOrigin()).LengthSqr();
length += delta;
// add to current reverse estimate
nearestReverse += delta;
pPath = pNext;
}
else
{
// not a looping path
// traverse back to other end of the path
int fail = 0;
while ( pPath->m_pprevious )
{
fail++;
// HACKHACK: Don't infinite loop
if ( fail > 256 )
break;
pPath = pPath->m_pprevious;
}
// don't take the reverse path to old node
nearestReverse = nearestForward + 1;
// dont' take forward path to new node (if we find one)
length = (float)COORD_EXTENT * (float)COORD_EXTENT; // HACKHACK: Max quad length
}
} while ( pPath != pStart );
// UNDONE: Fix this fudge factor
// if you are already at the path, or <100 units away, don't move
if ( pNearest == pStart || (pNearest->GetLocalOrigin() - startPosition).LengthSqr() < 100 )
return 0;
if ( nearestForward <= nearestReverse )
return 1;
return -1;
}
//-----------------------------------------------------------------------------
// Purpose: Find a point on my path near to the target and move toward it
//-----------------------------------------------------------------------------
void CTankTrainAI::Think( void )
{
CFuncTrackTrain *pTrain = m_hTrain;
if ( !pTrain || pTrain->m_lifeState != LIFE_ALIVE )
{
SoundShutdown();
if ( pTrain )
UTIL_RemoveHierarchy( pTrain );
UTIL_Remove( this );
return;
}
int desired = 0;
CBaseEntity *pTarget = m_hTargetEntity;
if ( pTarget )
{
desired = PathFindDirection( pTrain->m_ppath, pTrain->GetLocalOrigin(), pTarget->GetLocalOrigin() );
}
// If the train wants to stop, figure out throttle
// otherwise, just throttle in the indicated direction and let the train logic
// clip the speed
if ( !desired )
{
if ( pTrain->m_flSpeed > 0 )
{
desired = -1;
}
else if ( pTrain->m_flSpeed < 0 )
{
desired = 1;
}
}
// UNDONE: Align the think time with arrival, and bump this up to a few seconds
SetNextThink( gpGlobals->curtime + 0.5f );
if ( desired != 0 )
{
int wasMoving = (pTrain->m_flSpeed == 0) ? false : true;
// chaser wants train to move, send message
pTrain->SetSpeed( desired );
int isMoving = (pTrain->m_flSpeed == 0) ? false : true;
if ( !isMoving && wasMoving )
{
SoundEngineStop();
}
else if ( isMoving )
{
if ( !wasMoving )
{
SoundEngineStart();
}
}
}
else
{
SoundEngineStop();
// UNDONE: Align the think time with arrival, and bump this up to a few seconds
SetNextThink( gpGlobals->curtime + 1.0f );
}
}