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.
499 lines
14 KiB
499 lines
14 KiB
![]()
4 years ago
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
//
|
||
|
// Purpose:
|
||
|
//
|
||
|
// $NoKeywords: $
|
||
|
//=============================================================================//
|
||
|
|
||
|
#include "cbase.h"
|
||
|
#include "basecombatweapon.h"
|
||
|
#include "explode.h"
|
||
|
#include "eventqueue.h"
|
||
|
#include "gamerules.h"
|
||
|
#include "ammodef.h"
|
||
|
#include "in_buttons.h"
|
||
|
#include "soundent.h"
|
||
|
#include "ndebugoverlay.h"
|
||
|
#include "vstdlib/random.h"
|
||
|
#include "engine/IEngineSound.h"
|
||
|
|
||
|
#include "player.h"
|
||
|
#include "entitylist.h"
|
||
|
#include "iservervehicle.h"
|
||
|
|
||
|
// memdbgon must be the last include file in a .cpp file!!!
|
||
|
#include "tier0/memdbgon.h"
|
||
|
|
||
|
#define SF_TANK_ACTIVE 0x0001
|
||
|
|
||
|
|
||
|
class CAPCController : public CPointEntity
|
||
|
{
|
||
|
typedef CPointEntity BaseClass;
|
||
|
public:
|
||
|
~CAPCController( void );
|
||
|
void Spawn( void );
|
||
|
void Precache( void );
|
||
|
bool KeyValue( const char *szKeyName, const char *szValue );
|
||
|
void Think( void );
|
||
|
void TrackTarget( void );
|
||
|
|
||
|
void StartRotSound( void );
|
||
|
void StopRotSound( void );
|
||
|
|
||
|
// Bmodels don't go across transitions
|
||
|
virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
|
||
|
|
||
|
inline bool IsActive( void ) { return (m_spawnflags & SF_TANK_ACTIVE)?TRUE:FALSE; }
|
||
|
|
||
|
// Input handlers.
|
||
|
void InputActivate( inputdata_t &inputdata );
|
||
|
void InputDeactivate( inputdata_t &inputdata );
|
||
|
|
||
|
void ActivateRocketGuidance(void);
|
||
|
void DeactivateRocketGuidance(void);
|
||
|
|
||
|
bool InRange( float range );
|
||
|
|
||
|
Vector WorldBarrelPosition( void )
|
||
|
{
|
||
|
EntityMatrix tmp;
|
||
|
tmp.InitFromEntity( this );
|
||
|
return tmp.LocalToWorld( m_barrelPos );
|
||
|
}
|
||
|
|
||
|
void UpdateMatrix( void )
|
||
|
{
|
||
|
m_parentMatrix.InitFromEntity( GetParent() ? GetParent() : NULL );
|
||
|
}
|
||
|
QAngle AimBarrelAt( const Vector &parentTarget );
|
||
|
|
||
|
bool ShouldSavePhysics() { return false; }
|
||
|
|
||
|
DECLARE_DATADESC();
|
||
|
|
||
|
CBaseEntity *FindTarget( string_t targetName, CBaseEntity *pActivator );
|
||
|
|
||
|
protected:
|
||
|
float m_yawCenter; // "Center" yaw
|
||
|
float m_yawRate; // Max turn rate to track targets
|
||
|
// Zero is full rotation
|
||
|
float m_yawTolerance; // Tolerance angle
|
||
|
|
||
|
float m_pitchCenter; // "Center" pitch
|
||
|
float m_pitchRate; // Max turn rate on pitch
|
||
|
float m_pitchTolerance; // Tolerance angle
|
||
|
|
||
|
float m_minRange; // Minimum range to aim/track
|
||
|
float m_maxRange; // Max range to aim/track
|
||
|
|
||
|
Vector m_barrelPos; // Length of the barrel
|
||
|
|
||
|
Vector m_sightOrigin; // Last sight of target
|
||
|
|
||
|
string_t m_soundStartRotate;
|
||
|
string_t m_soundStopRotate;
|
||
|
string_t m_soundLoopRotate;
|
||
|
|
||
|
string_t m_targetEntityName;
|
||
|
EHANDLE m_hTarget;
|
||
|
EntityMatrix m_parentMatrix;
|
||
|
|
||
|
COutputVector m_OnFireAtTarget;
|
||
|
|
||
|
float m_flFiringDelay;
|
||
|
bool m_bFireDelayed;
|
||
|
};
|
||
|
|
||
|
LINK_ENTITY_TO_CLASS( point_apc_controller, CAPCController );
|
||
|
|
||
|
BEGIN_DATADESC( CAPCController )
|
||
|
|
||
|
DEFINE_FIELD( m_yawCenter, FIELD_FLOAT ),
|
||
|
DEFINE_KEYFIELD( m_yawRate, FIELD_FLOAT, "yawrate" ),
|
||
|
DEFINE_KEYFIELD( m_yawTolerance, FIELD_FLOAT, "yawtolerance" ),
|
||
|
|
||
|
|
||
|
DEFINE_FIELD( m_pitchCenter, FIELD_FLOAT ),
|
||
|
DEFINE_KEYFIELD( m_pitchRate, FIELD_FLOAT, "pitchrate" ),
|
||
|
DEFINE_KEYFIELD( m_pitchTolerance, FIELD_FLOAT, "pitchtolerance" ),
|
||
|
|
||
|
DEFINE_KEYFIELD( m_minRange, FIELD_FLOAT, "minRange" ),
|
||
|
DEFINE_KEYFIELD( m_maxRange, FIELD_FLOAT, "maxRange" ),
|
||
|
DEFINE_FIELD( m_barrelPos, FIELD_VECTOR ),
|
||
|
DEFINE_FIELD( m_sightOrigin, FIELD_VECTOR ),
|
||
|
DEFINE_KEYFIELD( m_soundStartRotate, FIELD_SOUNDNAME, "rotatestartsound" ),
|
||
|
DEFINE_KEYFIELD( m_soundStopRotate, FIELD_SOUNDNAME, "rotatestopsound" ),
|
||
|
DEFINE_KEYFIELD( m_soundLoopRotate, FIELD_SOUNDNAME, "rotatesound" ),
|
||
|
DEFINE_KEYFIELD( m_targetEntityName, FIELD_STRING, "targetentityname" ),
|
||
|
DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ),
|
||
|
DEFINE_FIELD( m_parentMatrix, FIELD_VMATRIX_WORLDSPACE ),
|
||
|
DEFINE_FIELD( m_flFiringDelay, FIELD_FLOAT ),
|
||
|
DEFINE_FIELD( m_bFireDelayed, FIELD_BOOLEAN ),
|
||
|
|
||
|
// Inputs
|
||
|
DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),
|
||
|
DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ),
|
||
|
|
||
|
// Outputs
|
||
|
DEFINE_OUTPUT(m_OnFireAtTarget, "OnFireAtTarget"),
|
||
|
|
||
|
END_DATADESC()
|
||
|
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
CAPCController::~CAPCController( void )
|
||
|
{
|
||
|
if ( m_soundLoopRotate != NULL_STRING )
|
||
|
{
|
||
|
StopSound( entindex(), CHAN_STATIC, STRING(m_soundLoopRotate) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Purpose: Input handler for activating the tank.
|
||
|
//------------------------------------------------------------------------------
|
||
|
void CAPCController::InputActivate( inputdata_t &inputdata )
|
||
|
{
|
||
|
ActivateRocketGuidance();
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CAPCController::ActivateRocketGuidance(void)
|
||
|
{
|
||
|
m_spawnflags |= SF_TANK_ACTIVE;
|
||
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Input handler for deactivating the tank.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CAPCController::InputDeactivate( inputdata_t &inputdata )
|
||
|
{
|
||
|
DeactivateRocketGuidance();
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CAPCController::DeactivateRocketGuidance(void)
|
||
|
{
|
||
|
m_spawnflags &= ~SF_TANK_ACTIVE;
|
||
|
StopRotSound();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : targetName -
|
||
|
// pActivator -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
CBaseEntity *CAPCController::FindTarget( string_t targetName, CBaseEntity *pActivator )
|
||
|
{
|
||
|
return gEntList.FindEntityGenericNearest( STRING( targetName ), GetAbsOrigin(), 0, this, pActivator );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Caches entity key values until spawn is called.
|
||
|
// Input : szKeyName -
|
||
|
// szValue -
|
||
|
// Output :
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool CAPCController::KeyValue( const char *szKeyName, const char *szValue )
|
||
|
{
|
||
|
if (FStrEq(szKeyName, "barrel"))
|
||
|
{
|
||
|
m_barrelPos.x = atof(szValue);
|
||
|
}
|
||
|
else if (FStrEq(szKeyName, "barrely"))
|
||
|
{
|
||
|
m_barrelPos.y = atof(szValue);
|
||
|
}
|
||
|
else if (FStrEq(szKeyName, "barrelz"))
|
||
|
{
|
||
|
m_barrelPos.z = atof(szValue);
|
||
|
}
|
||
|
else
|
||
|
return BaseClass::KeyValue( szKeyName, szValue );
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------
|
||
|
// Spawn
|
||
|
//-----------------------------------------
|
||
|
void CAPCController::Spawn( void )
|
||
|
{
|
||
|
Precache();
|
||
|
|
||
|
m_yawCenter = GetLocalAngles().y;
|
||
|
m_pitchCenter = GetLocalAngles().x;
|
||
|
|
||
|
if ( IsActive() )
|
||
|
{
|
||
|
SetNextThink( gpGlobals->curtime + 1.0f );
|
||
|
}
|
||
|
|
||
|
UpdateMatrix();
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------
|
||
|
// Precache
|
||
|
//-----------------------------------------
|
||
|
void CAPCController::Precache( void )
|
||
|
{
|
||
|
if ( m_soundStartRotate != NULL_STRING )
|
||
|
PrecacheScriptSound( STRING(m_soundStartRotate) );
|
||
|
if ( m_soundStopRotate != NULL_STRING )
|
||
|
PrecacheScriptSound( STRING(m_soundStopRotate) );
|
||
|
if ( m_soundLoopRotate != NULL_STRING )
|
||
|
PrecacheScriptSound( STRING(m_soundLoopRotate) );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------
|
||
|
// InRange
|
||
|
//-----------------------------------------
|
||
|
bool CAPCController::InRange( float range )
|
||
|
{
|
||
|
if ( range < m_minRange )
|
||
|
return FALSE;
|
||
|
if ( m_maxRange > 0 && range > m_maxRange )
|
||
|
return FALSE;
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------
|
||
|
// Think
|
||
|
//-----------------------------------------
|
||
|
void CAPCController::Think( void )
|
||
|
{
|
||
|
// refresh the matrix
|
||
|
UpdateMatrix();
|
||
|
|
||
|
SetLocalAngularVelocity( vec3_angle );
|
||
|
TrackTarget();
|
||
|
|
||
|
if ( fabs(GetLocalAngularVelocity().x) > 1 || fabs(GetLocalAngularVelocity().y) > 1 )
|
||
|
StartRotSound();
|
||
|
else
|
||
|
StopRotSound();
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Aim the offset barrel at a position in parent space
|
||
|
// Input : parentTarget - the position of the target in parent space
|
||
|
// Output : Vector - angles in local space
|
||
|
//-----------------------------------------------------------------------------
|
||
|
QAngle CAPCController::AimBarrelAt( const Vector &parentTarget )
|
||
|
{
|
||
|
Vector target = parentTarget - GetLocalOrigin();
|
||
|
float quadTarget = target.LengthSqr();
|
||
|
float quadTargetXY = target.x*target.x + target.y*target.y;
|
||
|
|
||
|
// We're trying to aim the offset barrel at an arbitrary point.
|
||
|
// To calculate this, I think of the target as being on a sphere with
|
||
|
// it's center at the origin of the gun.
|
||
|
// The rotation we need is the opposite of the rotation that moves the target
|
||
|
// along the surface of that sphere to intersect with the gun's shooting direction
|
||
|
// To calculate that rotation, we simply calculate the intersection of the ray
|
||
|
// coming out of the barrel with the target sphere (that's the new target position)
|
||
|
// and use atan2() to get angles
|
||
|
|
||
|
// angles from target pos to center
|
||
|
float targetToCenterYaw = atan2( target.y, target.x );
|
||
|
float centerToGunYaw = atan2( m_barrelPos.y, sqrt( quadTarget - (m_barrelPos.y*m_barrelPos.y) ) );
|
||
|
|
||
|
float targetToCenterPitch = atan2( target.z, sqrt( quadTargetXY ) );
|
||
|
float centerToGunPitch = atan2( -m_barrelPos.z, sqrt( quadTarget - (m_barrelPos.z*m_barrelPos.z) ) );
|
||
|
return QAngle( -RAD2DEG(targetToCenterPitch+centerToGunPitch), RAD2DEG( targetToCenterYaw + centerToGunYaw ), 0 );
|
||
|
}
|
||
|
|
||
|
void CAPCController::TrackTarget( void )
|
||
|
{
|
||
|
trace_t tr;
|
||
|
bool updateTime = FALSE, lineOfSight;
|
||
|
QAngle angles;
|
||
|
Vector barrelEnd;
|
||
|
CBaseEntity *pTarget = NULL;
|
||
|
|
||
|
barrelEnd.Init();
|
||
|
|
||
|
if ( IsActive() )
|
||
|
{
|
||
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// -----------------------------------
|
||
|
// Get world target position
|
||
|
// -----------------------------------
|
||
|
barrelEnd = WorldBarrelPosition();
|
||
|
Vector worldTargetPosition;
|
||
|
CBaseEntity *pEntity = (CBaseEntity *)m_hTarget;
|
||
|
if ( !pEntity || ( pEntity->GetFlags() & FL_NOTARGET ) )
|
||
|
{
|
||
|
m_hTarget = FindTarget( m_targetEntityName, NULL );
|
||
|
if ( IsActive() )
|
||
|
{
|
||
|
SetNextThink( gpGlobals->curtime + 2 ); // Wait 2 sec s
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
pTarget = pEntity;
|
||
|
|
||
|
// Calculate angle needed to aim at target
|
||
|
worldTargetPosition = pEntity->EyePosition();
|
||
|
|
||
|
float range = (worldTargetPosition - barrelEnd).Length();
|
||
|
|
||
|
if ( !InRange( range ) )
|
||
|
{
|
||
|
m_bFireDelayed = false;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
UTIL_TraceLine( barrelEnd, worldTargetPosition, MASK_BLOCKLOS, this, COLLISION_GROUP_NONE, &tr );
|
||
|
|
||
|
lineOfSight = FALSE;
|
||
|
// No line of sight, don't track
|
||
|
if ( tr.fraction == 1.0 || tr.m_pEnt == pTarget )
|
||
|
{
|
||
|
lineOfSight = TRUE;
|
||
|
|
||
|
CBaseEntity *pInstance = pTarget;
|
||
|
if ( InRange( range ) && pInstance && pInstance->IsAlive() )
|
||
|
{
|
||
|
updateTime = TRUE;
|
||
|
|
||
|
// Sight position is BodyTarget with no noise (so gun doesn't bob up and down)
|
||
|
m_sightOrigin = pInstance->BodyTarget( GetLocalOrigin(), false );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Convert targetPosition to parent
|
||
|
angles = AimBarrelAt( m_parentMatrix.WorldToLocal( m_sightOrigin ) );
|
||
|
|
||
|
|
||
|
// Force the angles to be relative to the center position
|
||
|
float offsetY = UTIL_AngleDistance( angles.y, m_yawCenter );
|
||
|
float offsetX = UTIL_AngleDistance( angles.x, m_pitchCenter );
|
||
|
angles.y = m_yawCenter + offsetY;
|
||
|
angles.x = m_pitchCenter + offsetX;
|
||
|
|
||
|
// Move toward target at rate or less
|
||
|
float distY = UTIL_AngleDistance( angles.y, GetLocalAngles().y );
|
||
|
|
||
|
QAngle vecAngVel = GetLocalAngularVelocity();
|
||
|
vecAngVel.y = distY * 10;
|
||
|
vecAngVel.y = clamp( vecAngVel.y, -m_yawRate, m_yawRate );
|
||
|
|
||
|
// Move toward target at rate or less
|
||
|
float distX = UTIL_AngleDistance( angles.x, GetLocalAngles().x );
|
||
|
vecAngVel.x = distX * 10;
|
||
|
vecAngVel.x = clamp( vecAngVel.x, -m_pitchRate, m_pitchRate );
|
||
|
SetLocalAngularVelocity( vecAngVel );
|
||
|
|
||
|
SetMoveDoneTime( 0.1 );
|
||
|
|
||
|
Vector forward;
|
||
|
AngleVectors( GetLocalAngles(), &forward );
|
||
|
forward = m_parentMatrix.ApplyRotation( forward );
|
||
|
|
||
|
AngleVectors(angles, &forward);
|
||
|
|
||
|
if ( lineOfSight == TRUE )
|
||
|
{
|
||
|
// FIXME: This will ultimately have to deal with NPCs being in the vehicle as well
|
||
|
// See if the target is in a vehicle. If so, check its relationship
|
||
|
CBasePlayer *pPlayer = ToBasePlayer( pTarget );
|
||
|
if ( pPlayer && pPlayer->IsInAVehicle() )
|
||
|
{
|
||
|
IServerVehicle *pVehicle = pPlayer->GetVehicle();
|
||
|
if ( pVehicle->ClassifyPassenger( pPlayer, CLASS_PLAYER ) == CLASS_PLAYER)
|
||
|
{
|
||
|
if ( !m_bFireDelayed )
|
||
|
{
|
||
|
m_bFireDelayed = true;
|
||
|
m_flFiringDelay = gpGlobals->curtime + 1.5; // setup delay time before we start firing
|
||
|
return;
|
||
|
}
|
||
|
if ( gpGlobals->curtime > m_flFiringDelay )
|
||
|
{
|
||
|
m_OnFireAtTarget.Set(forward, this, this); // tell apc to fire rockets, and what direction
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_bFireDelayed = false; // reset flag since we can no longer see target
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CAPCController::StartRotSound( void )
|
||
|
{
|
||
|
if ( m_soundLoopRotate != NULL_STRING )
|
||
|
{
|
||
|
CPASAttenuationFilter filter( this );
|
||
|
filter.MakeReliable();
|
||
|
|
||
|
EmitSound_t ep;
|
||
|
ep.m_nChannel = CHAN_STATIC;
|
||
|
ep.m_pSoundName = (char*)STRING(m_soundLoopRotate);
|
||
|
ep.m_SoundLevel = SNDLVL_NORM;
|
||
|
ep.m_flVolume = 0.85;
|
||
|
|
||
|
EmitSound( filter, entindex(), ep );
|
||
|
}
|
||
|
|
||
|
if ( m_soundStartRotate != NULL_STRING )
|
||
|
{
|
||
|
CPASAttenuationFilter filter( this );
|
||
|
|
||
|
EmitSound_t ep;
|
||
|
ep.m_nChannel = CHAN_BODY;
|
||
|
ep.m_pSoundName = (char*)STRING(m_soundStartRotate);
|
||
|
ep.m_SoundLevel = SNDLVL_NORM;
|
||
|
ep.m_flVolume = 1.0f;
|
||
|
|
||
|
EmitSound( filter, entindex(), ep );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CAPCController::StopRotSound( void )
|
||
|
{
|
||
|
if ( m_soundLoopRotate != NULL_STRING )
|
||
|
{
|
||
|
StopSound( entindex(), CHAN_STATIC, (char*)STRING(m_soundLoopRotate) );
|
||
|
}
|
||
|
if ( m_soundStopRotate != NULL_STRING )
|
||
|
{
|
||
|
CPASAttenuationFilter filter( this );
|
||
|
|
||
|
EmitSound_t ep;
|
||
|
ep.m_nChannel = CHAN_BODY;
|
||
|
ep.m_pSoundName = (char*)STRING(m_soundStopRotate);
|
||
|
ep.m_SoundLevel = SNDLVL_NORM;
|
||
|
|
||
|
EmitSound( filter, entindex(), ep );
|
||
|
}
|
||
|
}
|
||
|
|