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.
1413 lines
41 KiB
1413 lines
41 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//===========================================================================// |
|
|
|
#include "cbase.h" |
|
#include "ai_basenpc.h" |
|
#include "ai_senses.h" |
|
#include "ai_memory.h" |
|
#include "engine/IEngineSound.h" |
|
#include "Sprite.h" |
|
#include "IEffects.h" |
|
#include "prop_portal_shared.h" |
|
#include "te.h" |
|
#include "te_effect_dispatch.h" |
|
#include "soundenvelope.h" // for looping sound effects |
|
#include "portal_gamerules.h" // for difficulty settings |
|
#include "weapon_rpg.h" |
|
#include "explode.h" |
|
#include "smoke_trail.h" // smoke trailers on the rocket |
|
#include "physics_bone_follower.h" // For bone follower manager |
|
#include "physicsshadowclone.h" // For translating hit entities shadow clones to real ent |
|
|
|
//#include "ndebugoverlay.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
#define ROCKET_TURRET_RANGE 8192 |
|
#define ROCKET_TURRET_EMITER_OFFSET 0.0 |
|
#define ROCKET_TURRET_THINK_RATE 0.05 |
|
#define ROCKET_TURRET_DEATH_EFFECT_TIME 1.5f |
|
#define ROCKET_TURRET_LOCKON_TIME 2.0f |
|
#define ROCKET_TURRET_HALF_LOCKON_TIME 1.0f |
|
#define ROCKET_TURRET_QUARTER_LOCKON_TIME 0.5f |
|
#define ROCKET_TURRET_ROCKET_FIRE_COOLDOWN_TIME 4.0f |
|
|
|
// For search thinks |
|
#define MAX_DIVERGENCE_X 30.0f |
|
#define MAX_DIVERGENCE_Y 15.0f |
|
|
|
#define ROCKET_TURRET_DECAL_NAME "decals/scorchfade" |
|
#define ROCKET_TURRET_MODEL_NAME "models/props_bts/rocket_sentry.mdl" |
|
#define ROCKET_TURRET_PROJECTILE_NAME "models/props_bts/rocket.mdl" |
|
|
|
#define ROCKET_TURRET_SOUND_LOCKING "NPC_RocketTurret.LockingBeep" |
|
#define ROCKET_TURRET_SOUND_LOCKED "NPC_FloorTurret.LockedBeep" |
|
|
|
#define ROCKET_PROJECTILE_FIRE_SOUND "NPC_FloorTurret.RocketFire" |
|
#define ROCKET_PROJECTILE_LOOPING_SOUND "NPC_FloorTurret.RocketFlyLoop" |
|
#define ROCKET_PROJECTILE_DEFAULT_LIFE 20.0 |
|
|
|
//Spawnflags |
|
#define SF_ROCKET_TURRET_START_INACTIVE 0x00000001 |
|
|
|
// These bones have physics shadows |
|
const char *pRocketTurretFollowerBoneNames[] = |
|
{ |
|
"Root", |
|
"Base", |
|
"Arm_1", |
|
"Arm_2", |
|
"Arm_3", |
|
"Arm_4", |
|
"Rot_LR", |
|
"Rot_UD", |
|
"Gun_casing", |
|
"Gun_Barrel_01", |
|
"gun_barrel_02", |
|
"loader", |
|
"missle_01", |
|
"missle_02", |
|
"panel", |
|
}; |
|
|
|
|
|
class CNPC_RocketTurret : public CAI_BaseNPC |
|
{ |
|
DECLARE_CLASS( CNPC_RocketTurret, CAI_BaseNPC ); |
|
DECLARE_SERVERCLASS(); |
|
DECLARE_DATADESC(); |
|
|
|
public: |
|
|
|
CNPC_RocketTurret( void ); |
|
~CNPC_RocketTurret( void ); |
|
|
|
void Precache( void ); |
|
void Spawn( void ); |
|
virtual void Activate( void ); |
|
|
|
virtual ITraceFilter* GetBeamTraceFilter( void ); |
|
void UpdateOnRemove( void ); |
|
bool CreateVPhysics( void ); |
|
|
|
// Think functions |
|
void SearchThink( void ); // Lost Target, spaz out |
|
void FollowThink( void ); // Found target, chase it |
|
void LockingThink( void ); // Charge up effects |
|
void FiringThink( void ); // Currently has rocket out |
|
void DyingThink( void ); // Overloading, blowing up |
|
void DeathThink( void ); // Destroyed, sparking |
|
void OpeningThink ( void ); // Finish open/close animation before using pose params |
|
void ClosingThink ( void ); |
|
|
|
// Inputs |
|
void InputToggle( inputdata_t &inputdata ); |
|
void InputEnable( inputdata_t &inputdata ); |
|
void InputDisable( inputdata_t &inputdata ); |
|
void InputSetTarget( inputdata_t &inputdata ); |
|
void InputDestroy( inputdata_t &inputdata ); |
|
|
|
void RocketDied( void ); // After rocket hits something and self-destructs (or times out) |
|
Class_T Classify( void ) |
|
{ |
|
if( m_bEnabled ) |
|
return CLASS_COMBINE; |
|
|
|
return CLASS_NONE; |
|
} |
|
|
|
bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL ); |
|
|
|
Vector EyeOffset( Activity nActivity ) |
|
{ |
|
return vec3_origin; |
|
} |
|
|
|
Vector EyePosition( void ) |
|
{ |
|
Vector vMuzzlePos; |
|
GetAttachment( m_iMuzzleAttachment, vMuzzlePos, NULL, NULL, NULL ); |
|
return vMuzzlePos; |
|
} |
|
|
|
protected: |
|
|
|
bool PreThink( void ); |
|
void Toggle( void ); |
|
void Enable( void ); |
|
void Disable( void ); |
|
void SetTarget( CBaseEntity* pTarget ); |
|
void Destroy ( void ); |
|
|
|
float UpdateFacing( void ); |
|
void UpdateAimPoint( void ); |
|
void FireRocket( void ); |
|
void UpdateSkin( int nSkin ); |
|
void UpdateMuzzleMatrix ( void ); |
|
|
|
bool TestLOS( const Vector& vAimPoint ); |
|
bool TestPortalsForLOS( Vector* pOutVec, bool bConsiderNonPortalAimPoint ); |
|
bool FindAimPointThroughPortal( const CProp_Portal* pPortal, Vector* pVecOut ); |
|
void SyncPoseToAimAngles ( void ); |
|
void LaserOn ( void ); |
|
void LaserOff ( void ); |
|
|
|
bool m_bEnabled; |
|
bool m_bHasSightOfEnemy; |
|
|
|
QAngle m_vecGoalAngles; |
|
CNetworkVar( QAngle, m_vecCurrentAngles ); |
|
QAngle m_vecAnglesToEnemy; |
|
|
|
enum |
|
{ |
|
ROCKET_SKIN_IDLE=0, |
|
ROCKET_SKIN_LOCKING, |
|
ROCKET_SKIN_LOCKED, |
|
|
|
ROCKET_SKIN_COUNT, |
|
}; |
|
|
|
Vector m_vecDirToEnemy; |
|
float m_flDistToEnemy; |
|
|
|
float m_flTimeSpentDying; |
|
float m_flTimeLocking; // Period spent locking on to target |
|
float m_flTimeLastFired; // Cooldown time between attacks |
|
|
|
float m_flTimeSpentPaused; // for search think's movements |
|
float m_flPauseLength; |
|
float m_flTotalDivergenceX; |
|
float m_flTotalDivergenceY; |
|
|
|
matrix3x4_t m_muzzleToWorld; |
|
int m_muzzleToWorldTick; |
|
|
|
int m_iPosePitch; |
|
int m_iPoseYaw; |
|
|
|
// Contained Bone Follower manager |
|
CBoneFollowerManager m_BoneFollowerManager; |
|
|
|
// Model indices for effects |
|
CNetworkVar( int, m_iLaserState ); |
|
CNetworkVar( int, m_nSiteHalo ); |
|
|
|
// Target indicator sprite info |
|
int m_iMuzzleAttachment; |
|
int m_iLightAttachment; |
|
|
|
COutputEvent m_OnFoundTarget; |
|
COutputEvent m_OnLostTarget; |
|
|
|
CTraceFilterSkipTwoEntities m_filterBeams; |
|
|
|
EHANDLE m_hCurRocket; |
|
|
|
}; |
|
|
|
//Datatable |
|
BEGIN_DATADESC( CNPC_RocketTurret ) |
|
|
|
DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_vecGoalAngles, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_vecCurrentAngles, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_bHasSightOfEnemy, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_vecAnglesToEnemy, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_vecDirToEnemy, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_flDistToEnemy, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flTimeSpentDying, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flTimeLocking, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flTimeLastFired, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_iLaserState, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_nSiteHalo, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_flTimeSpentPaused, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flPauseLength, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flTotalDivergenceX, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flTotalDivergenceY, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_iPosePitch, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_iPoseYaw, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_hCurRocket, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_iMuzzleAttachment, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_iLightAttachment, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_muzzleToWorldTick, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_muzzleToWorld, FIELD_MATRIX3X4_WORLDSPACE ), |
|
// DEFINE_FIELD( m_filterBeams, CTraceFilterSkipTwoEntities ), |
|
|
|
DEFINE_EMBEDDED( m_BoneFollowerManager ), |
|
|
|
DEFINE_THINKFUNC( SearchThink ), |
|
DEFINE_THINKFUNC( FollowThink ), |
|
DEFINE_THINKFUNC( LockingThink ), |
|
DEFINE_THINKFUNC( FiringThink ), |
|
DEFINE_THINKFUNC( DyingThink ), |
|
DEFINE_THINKFUNC( DeathThink ), |
|
DEFINE_THINKFUNC( OpeningThink ), |
|
DEFINE_THINKFUNC( ClosingThink ), |
|
|
|
// Inputs |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), |
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetTarget", InputSetTarget ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Destroy", InputDestroy ), |
|
|
|
DEFINE_OUTPUT( m_OnFoundTarget, "OnFoundTarget" ), |
|
DEFINE_OUTPUT( m_OnLostTarget, "OnLostTarget" ), |
|
|
|
END_DATADESC() |
|
|
|
IMPLEMENT_SERVERCLASS_ST(CNPC_RocketTurret, DT_NPC_RocketTurret) |
|
|
|
SendPropInt( SENDINFO( m_iLaserState ), 2 ), |
|
SendPropInt( SENDINFO( m_nSiteHalo ) ), |
|
SendPropVector( SENDINFO( m_vecCurrentAngles ) ), |
|
|
|
END_SEND_TABLE() |
|
|
|
LINK_ENTITY_TO_CLASS( npc_rocket_turret, CNPC_RocketTurret ); |
|
|
|
|
|
|
|
// Projectile class for this weapon, a rocket |
|
class CRocket_Turret_Projectile : public CMissile |
|
{ |
|
DECLARE_CLASS( CRocket_Turret_Projectile, CMissile ); |
|
DECLARE_DATADESC(); |
|
public: |
|
void Precache( void ); |
|
void Spawn( void ); |
|
|
|
virtual void NotifyLauncherOnDeath( void ); |
|
virtual void NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t ¶ms ); |
|
virtual void SetLauncher( EHANDLE hLauncher ); |
|
virtual void CreateSmokeTrail( void ); // overloaded from base |
|
|
|
virtual void MissileTouch( CBaseEntity *pOther ); |
|
|
|
EHANDLE m_hLauncher; |
|
CSoundPatch *m_pAmbientSound; |
|
protected: |
|
virtual void DoExplosion( void ); |
|
virtual void CreateSounds( void ); |
|
virtual void StopLoopingSounds ( void ); |
|
|
|
}; |
|
|
|
BEGIN_DATADESC( CRocket_Turret_Projectile ) |
|
|
|
DEFINE_FIELD( m_hLauncher, FIELD_EHANDLE ), |
|
|
|
DEFINE_FUNCTION( MissileTouch ), |
|
DEFINE_SOUNDPATCH( m_pAmbientSound ), |
|
|
|
END_DATADESC() |
|
|
|
LINK_ENTITY_TO_CLASS( rocket_turret_projectile, CRocket_Turret_Projectile ); |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Constructor |
|
//----------------------------------------------------------------------------- |
|
CNPC_RocketTurret::CNPC_RocketTurret( void ) |
|
: m_filterBeams( NULL, NULL, COLLISION_GROUP_DEBRIS ) |
|
{ |
|
m_bEnabled = false; |
|
m_bHasSightOfEnemy = false; |
|
|
|
m_vecGoalAngles.Init(); |
|
m_vecAnglesToEnemy.Init(); |
|
m_vecDirToEnemy.Init(); |
|
|
|
m_flTimeLastFired = m_flTimeLocking = m_flDistToEnemy = m_flTimeSpentDying = 0.0f; |
|
|
|
m_iLightAttachment = m_iMuzzleAttachment = m_nSiteHalo = 0; |
|
|
|
m_flTimeSpentPaused = m_flPauseLength = m_flTotalDivergenceX = m_flTotalDivergenceY = 0.0f; |
|
|
|
m_hCurRocket = NULL; |
|
} |
|
|
|
CNPC_RocketTurret::~CNPC_RocketTurret( void ) |
|
{ |
|
|
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Precache |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::Precache( void ) |
|
{ |
|
PrecacheModel("effects/bluelaser1.vmt"); |
|
m_nSiteHalo = PrecacheModel("sprites/light_glow03.vmt"); |
|
|
|
PrecacheScriptSound ( ROCKET_TURRET_SOUND_LOCKING ); |
|
PrecacheScriptSound ( ROCKET_TURRET_SOUND_LOCKED ); |
|
PrecacheScriptSound ( ROCKET_PROJECTILE_FIRE_SOUND ); |
|
PrecacheScriptSound ( ROCKET_PROJECTILE_LOOPING_SOUND ); |
|
|
|
UTIL_PrecacheDecal( ROCKET_TURRET_DECAL_NAME ); |
|
PrecacheModel( ROCKET_TURRET_MODEL_NAME ); |
|
PrecacheModel ( ROCKET_TURRET_PROJECTILE_NAME ); |
|
|
|
BaseClass::Precache(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: the entity |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::Spawn( void ) |
|
{ |
|
Precache(); |
|
|
|
BaseClass::Spawn(); |
|
|
|
SetViewOffset( vec3_origin ); |
|
|
|
AddEFlags( EFL_NO_DISSOLVE ); |
|
|
|
SetModel( ROCKET_TURRET_MODEL_NAME ); |
|
SetSolid( SOLID_VPHYSICS ); |
|
|
|
m_iMuzzleAttachment = LookupAttachment ( "barrel" ); |
|
m_iLightAttachment = LookupAttachment ( "eye" ); |
|
|
|
m_iPosePitch = LookupPoseParameter( "aim_pitch" ); |
|
m_iPoseYaw = LookupPoseParameter( "aim_yaw" ); |
|
|
|
m_vecCurrentAngles = m_vecGoalAngles = GetAbsAngles(); |
|
|
|
CreateVPhysics(); |
|
|
|
//Set our autostart state |
|
m_bEnabled = ( ( m_spawnflags & SF_ROCKET_TURRET_START_INACTIVE ) == false ); |
|
|
|
// Set Locked sprite |
|
if ( m_bEnabled ) |
|
{ |
|
m_iLaserState = 1; |
|
SetSequence(LookupSequence("idle")); |
|
} |
|
else |
|
{ |
|
m_iLaserState = 0; |
|
SetSequence(LookupSequence("inactive")); |
|
} |
|
SetCycle(1.0f); |
|
UpdateSkin( ROCKET_SKIN_IDLE ); |
|
|
|
SetPoseParameter( "aim_pitch", 0 ); |
|
SetPoseParameter( "aim_yaw", -180 ); |
|
|
|
if ( m_bEnabled ) |
|
{ |
|
SetThink( &CNPC_RocketTurret::FollowThink ); |
|
} |
|
|
|
SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE ); |
|
} |
|
|
|
bool CNPC_RocketTurret::CreateVPhysics( void ) |
|
{ |
|
m_BoneFollowerManager.InitBoneFollowers( this, ARRAYSIZE(pRocketTurretFollowerBoneNames), pRocketTurretFollowerBoneNames ); |
|
BaseClass::CreateVPhysics(); |
|
return true; |
|
} |
|
|
|
void CNPC_RocketTurret::Activate( void ) |
|
{ |
|
m_filterBeams.SetPassEntity( this ); |
|
m_filterBeams.SetPassEntity2( UTIL_GetLocalPlayer() ); |
|
BaseClass::Activate(); |
|
} |
|
|
|
ITraceFilter* CNPC_RocketTurret::GetBeamTraceFilter( void ) |
|
{ |
|
return &m_filterBeams; |
|
} |
|
|
|
void CNPC_RocketTurret::UpdateOnRemove( void ) |
|
{ |
|
m_BoneFollowerManager.DestroyBoneFollowers(); |
|
BaseClass::UpdateOnRemove(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pEntity - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_RocketTurret::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker ) |
|
{ |
|
CBaseEntity *pHitEntity = NULL; |
|
if ( BaseClass::FVisible( pEntity, traceMask, &pHitEntity ) ) |
|
return true; |
|
|
|
if (ppBlocker) |
|
{ |
|
*ppBlocker = pHitEntity; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : void CNPC_RocketTurret::UpdateAimPoint |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::UpdateAimPoint ( void ) |
|
{ |
|
//If we've become inactive |
|
if ( ( m_bEnabled == false ) || ( GetEnemy() == NULL ) ) |
|
{ |
|
SetEnemy( NULL ); |
|
SetNextThink( TICK_NEVER_THINK ); |
|
m_vecGoalAngles = GetAbsAngles(); |
|
return; |
|
} |
|
|
|
//Get our shot positions |
|
Vector vecMid = EyePosition(); |
|
Vector vecMidEnemy = GetEnemy()->GetAbsOrigin() + (GetEnemy()->WorldAlignMins() + GetEnemy()->WorldAlignMaxs()) * 0.5f; |
|
|
|
//Calculate dir and dist to enemy |
|
m_vecDirToEnemy = vecMidEnemy - vecMid; |
|
m_flDistToEnemy = VectorNormalize( m_vecDirToEnemy ); |
|
VectorAngles( m_vecDirToEnemy, m_vecAnglesToEnemy ); |
|
|
|
bool bEnemyVisible = false; |
|
if ( !(GetEnemy()->GetFlags() & FL_NOTARGET) ) |
|
{ |
|
bool bEnemyVisibleInWorld = FVisible( GetEnemy() ); |
|
|
|
// Test portals in our view as possible ways to view the player |
|
bool bEnemyVisibleThroughPortal = TestPortalsForLOS( &vecMidEnemy, bEnemyVisibleInWorld ); |
|
|
|
bEnemyVisible = bEnemyVisibleInWorld || bEnemyVisibleThroughPortal; |
|
} |
|
|
|
//Store off our last seen location |
|
UpdateEnemyMemory( GetEnemy(), vecMidEnemy ); |
|
|
|
if ( bEnemyVisible ) |
|
{ |
|
m_vecDirToEnemy = vecMidEnemy - vecMid; |
|
m_flDistToEnemy = VectorNormalize( m_vecDirToEnemy ); |
|
VectorAngles( m_vecDirToEnemy, m_vecAnglesToEnemy ); |
|
} |
|
|
|
//Current enemy is not visible |
|
if ( ( bEnemyVisible == false ) || ( m_flDistToEnemy > ROCKET_TURRET_RANGE ) ) |
|
{ |
|
// Had LOS, just lost it |
|
if ( m_bHasSightOfEnemy ) |
|
{ |
|
m_OnLostTarget.FireOutput( GetEnemy(), this ); |
|
} |
|
m_bHasSightOfEnemy = false; |
|
} |
|
|
|
//If we can see our enemy |
|
if ( bEnemyVisible ) |
|
{ |
|
// Had no LOS, just gained it |
|
if ( !m_bHasSightOfEnemy ) |
|
{ |
|
m_OnFoundTarget.FireOutput( GetEnemy(), this ); |
|
} |
|
m_bHasSightOfEnemy = true; |
|
} |
|
} |
|
|
|
bool SignDiffers ( float f1, float f2 ) |
|
{ |
|
return !( Sign(f1) == Sign(f2) ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::SearchThink() |
|
{ |
|
if ( PreThink() || GetEnemy() == NULL ) |
|
return; |
|
|
|
SetSequence ( LookupSequence( "idle" ) ); |
|
UpdateAimPoint(); |
|
|
|
//Update our think time |
|
SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE ); |
|
|
|
// Still can't see enemy, zip around frantically |
|
if ( !m_bHasSightOfEnemy ) |
|
{ |
|
if ( m_flTimeSpentPaused >= m_flPauseLength ) |
|
{ |
|
float flOffsetX = RandomFloat( -5.0f, 5.0f ); |
|
float flOffsetY = RandomFloat( -5.0f, 5.0f ); |
|
|
|
if ( fabs(m_flTotalDivergenceX) <= MAX_DIVERGENCE_X || |
|
SignDiffers( m_flTotalDivergenceX, flOffsetX ) ) |
|
{ |
|
m_flTotalDivergenceX += flOffsetX; |
|
m_vecGoalAngles.x += flOffsetX; |
|
} |
|
|
|
if ( fabs(m_flTotalDivergenceY) <= MAX_DIVERGENCE_Y || |
|
SignDiffers( m_flTotalDivergenceY, flOffsetY ) ) |
|
{ |
|
m_flTotalDivergenceY += flOffsetY; |
|
m_vecGoalAngles.y += flOffsetY; |
|
} |
|
|
|
// Reset pause timer |
|
m_flTimeSpentPaused = 0.0f; |
|
m_flPauseLength = RandomFloat( 0.3f, 2.5f ); |
|
} |
|
m_flTimeSpentPaused += ROCKET_TURRET_THINK_RATE; |
|
} |
|
else |
|
{ |
|
// Found target, go back to following it |
|
SetThink( &CNPC_RocketTurret::FollowThink ); |
|
SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE ); |
|
} |
|
|
|
// Move beam towards goal angles |
|
UpdateFacing(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Allows the turret to fire on targets if they're visible |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::FollowThink( void ) |
|
{ |
|
// Default to player as enemy |
|
if ( GetEnemy() == NULL ) |
|
{ |
|
SetEnemy( UTIL_GetLocalPlayer() ); |
|
} |
|
|
|
SetSequence ( LookupSequence( "idle" ) ); |
|
//Allow descended classes a chance to do something before the think function |
|
if ( PreThink() || GetEnemy() == NULL ) |
|
{ |
|
return; |
|
} |
|
//Update our think time |
|
SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE ); |
|
|
|
UpdateAimPoint(); |
|
|
|
m_vecGoalAngles = m_vecAnglesToEnemy; |
|
|
|
// Chase enemy |
|
if ( !m_bHasSightOfEnemy ) |
|
{ |
|
// Aim at the last known location |
|
m_vecGoalAngles = m_vecCurrentAngles; |
|
|
|
// Lost sight, move to search think |
|
SetThink( &CNPC_RocketTurret::SearchThink ); |
|
} |
|
|
|
//Turn to face |
|
UpdateFacing(); |
|
|
|
// If our facing direction hits our enemy, fire the beam |
|
Ray_t rayDmg; |
|
Vector vForward; |
|
AngleVectors( m_vecCurrentAngles, &vForward, NULL, NULL ); |
|
Vector vEndPoint = EyePosition() + vForward*ROCKET_TURRET_RANGE; |
|
rayDmg.Init( EyePosition(), vEndPoint ); |
|
rayDmg.m_IsRay = true; |
|
trace_t traceDmg; |
|
|
|
// This version reorients through portals |
|
CTraceFilterSimple subfilter( this, COLLISION_GROUP_NONE ); |
|
CTraceFilterTranslateClones filter ( &subfilter ); |
|
float flRequiredParameter = 2.0f; |
|
CProp_Portal* pFirstPortal = UTIL_Portal_FirstAlongRay( rayDmg, flRequiredParameter ); |
|
UTIL_Portal_TraceRay_Bullets( pFirstPortal, rayDmg, MASK_VISIBLE_AND_NPCS, &filter, &traceDmg, false ); |
|
|
|
if ( traceDmg.m_pEnt ) |
|
{ |
|
// This thing we're hurting is our enemy |
|
if ( traceDmg.m_pEnt == GetEnemy() ) |
|
{ |
|
// If we're past the cooldown time, fire another rocket |
|
if ( (gpGlobals->curtime - m_flTimeLastFired) > ROCKET_TURRET_ROCKET_FIRE_COOLDOWN_TIME ) |
|
{ |
|
SetThink( &CNPC_RocketTurret::LockingThink ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Charge up, prepare to fire and give player time to dodge |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::LockingThink( void ) |
|
{ |
|
//Allow descended classes a chance to do something before the think function |
|
if ( PreThink() ) |
|
return; |
|
|
|
//Turn to face |
|
UpdateFacing(); |
|
SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE ); |
|
|
|
if ( m_flTimeLocking == 0.0f ) |
|
{ |
|
// Play lockon sound |
|
EmitSound ( ROCKET_TURRET_SOUND_LOCKING ); |
|
EmitSound ( ROCKET_TURRET_SOUND_LOCKING, gpGlobals->curtime + ROCKET_TURRET_QUARTER_LOCKON_TIME ); |
|
EmitSound ( ROCKET_TURRET_SOUND_LOCKED, gpGlobals->curtime + ROCKET_TURRET_HALF_LOCKON_TIME ); |
|
|
|
ResetSequence(LookupSequence("load")); |
|
|
|
// Change lockon sprite |
|
UpdateSkin( ROCKET_SKIN_LOCKING ); |
|
} |
|
|
|
m_flTimeLocking += ROCKET_TURRET_THINK_RATE; |
|
|
|
if ( m_flTimeLocking > ROCKET_TURRET_LOCKON_TIME ) |
|
{ |
|
// Set Locked sprite to 'rocket out' color |
|
UpdateSkin( ROCKET_SKIN_LOCKED ); |
|
|
|
FireRocket(); |
|
SetThink ( &CNPC_RocketTurret::FiringThink ); |
|
m_flTimeLocking = 0.0f; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Charge up, deal damage along our facing direction. |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::FiringThink( void ) |
|
{ |
|
//Allow descended classes a chance to do something before the think function |
|
if ( PreThink() ) |
|
return; |
|
|
|
SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE ); |
|
CRocket_Turret_Projectile* pRocket = dynamic_cast<CRocket_Turret_Projectile*>(m_hCurRocket.Get()); |
|
|
|
if ( pRocket ) |
|
{ |
|
// If this rocket has been out too long, detonate it and launch a new one |
|
if ( (gpGlobals->curtime - m_flTimeLastFired) > ROCKET_PROJECTILE_DEFAULT_LIFE ) |
|
{ |
|
pRocket->ShotDown(); |
|
m_flTimeLastFired = gpGlobals->curtime; |
|
SetThink( &CNPC_RocketTurret::FollowThink ); |
|
} |
|
} |
|
else |
|
{ |
|
// Set Locked sprite |
|
UpdateSkin( ROCKET_SKIN_IDLE ); |
|
// Rocket dead, or never created. Revert to follow think |
|
m_flTimeLastFired = gpGlobals->curtime; |
|
SetThink( &CNPC_RocketTurret::FollowThink ); |
|
} |
|
} |
|
|
|
void CNPC_RocketTurret::FireRocket ( void ) |
|
{ |
|
UTIL_Remove( m_hCurRocket ); |
|
|
|
CRocket_Turret_Projectile *pRocket = (CRocket_Turret_Projectile *) CBaseEntity::Create( "rocket_turret_projectile", EyePosition(), m_vecCurrentAngles, this ); |
|
|
|
if ( !pRocket ) |
|
return; |
|
|
|
m_hCurRocket = pRocket; |
|
|
|
Vector vForward; |
|
AngleVectors( m_vecCurrentAngles, &vForward, NULL, NULL ); |
|
|
|
m_flTimeLastFired = gpGlobals->curtime; |
|
|
|
EmitSound ( ROCKET_PROJECTILE_FIRE_SOUND ); |
|
ResetSequence(LookupSequence("fire")); |
|
|
|
pRocket->SetThink( NULL ); |
|
pRocket->SetMoveType( MOVETYPE_FLY ); |
|
|
|
pRocket->CreateSmokeTrail(); |
|
|
|
pRocket->SetModel( ROCKET_TURRET_PROJECTILE_NAME ); |
|
UTIL_SetSize( pRocket, vec3_origin, vec3_origin ); |
|
|
|
pRocket->SetAbsVelocity( vForward * 550 ); |
|
pRocket->SetLauncher ( this ); |
|
} |
|
|
|
void CNPC_RocketTurret::UpdateSkin( int nSkin ) |
|
{ |
|
m_nSkin = nSkin; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Rocket destructed, resume search behavior |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::RocketDied( void ) |
|
{ |
|
// Set Locked sprite |
|
UpdateSkin( ROCKET_SKIN_IDLE ); |
|
|
|
// Rocket dead, return to follow think |
|
m_flTimeLastFired = gpGlobals->curtime; |
|
SetThink( &CNPC_RocketTurret::FollowThink ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Show this rocket turret has overloaded with effects and noise for a period of time |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::DyingThink( void ) |
|
{ |
|
// Make the beam graphics freak out a bit |
|
m_iLaserState = 2; |
|
|
|
|
|
UpdateSkin( ROCKET_SKIN_IDLE ); |
|
|
|
// If we've freaked out for long enough, be dead |
|
if ( m_flTimeSpentDying > ROCKET_TURRET_DEATH_EFFECT_TIME ) |
|
{ |
|
Vector vForward; |
|
AngleVectors( m_vecCurrentAngles, &vForward, NULL, NULL ); |
|
g_pEffects->EnergySplash( EyePosition(), vForward, true ); |
|
|
|
m_OnDeath.FireOutput( this, this ); |
|
SetThink( &CNPC_RocketTurret::DeathThink ); |
|
SetNextThink( gpGlobals->curtime + 1.0f ); |
|
m_flTimeSpentDying = 0.0f; |
|
} |
|
|
|
SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE ); |
|
m_flTimeSpentDying += ROCKET_TURRET_THINK_RATE; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Sparks and fizzes to show it's broken. |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::DeathThink( void ) |
|
{ |
|
Vector vForward; |
|
AngleVectors( m_vecCurrentAngles, &vForward, NULL, NULL ); |
|
|
|
m_iLaserState = 0; |
|
SetEnemy( NULL ); |
|
|
|
g_pEffects->Sparks( EyePosition(), 1, 1, &vForward ); |
|
g_pEffects->Smoke( EyePosition(), 0, 6.0f, 20 ); |
|
|
|
SetNextThink( gpGlobals->curtime + RandomFloat( 2.0f, 8.0f ) ); |
|
} |
|
|
|
void CNPC_RocketTurret::UpdateMuzzleMatrix() |
|
{ |
|
if ( gpGlobals->tickcount != m_muzzleToWorldTick ) |
|
{ |
|
m_muzzleToWorldTick = gpGlobals->tickcount; |
|
GetAttachment( m_iMuzzleAttachment, m_muzzleToWorld ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Avoid aiming/drawing beams while opening and closing |
|
// Input : - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::OpeningThink() |
|
{ |
|
StudioFrameAdvance(); |
|
|
|
// Require these poses for this animation |
|
QAngle vecNeutralAngles ( 0, 90, 0 ); |
|
m_vecGoalAngles = m_vecCurrentAngles = vecNeutralAngles; |
|
SyncPoseToAimAngles(); |
|
|
|
// Start following player after we're fully opened |
|
float flCurProgress = GetCycle(); |
|
if ( flCurProgress >= 0.99f ) |
|
{ |
|
|
|
LaserOn(); |
|
SetThink( &CNPC_RocketTurret::FollowThink ); |
|
} |
|
|
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Avoid aiming/drawing beams while opening and closing |
|
// Input : - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::ClosingThink() |
|
{ |
|
LaserOff(); |
|
|
|
// Require these poses for this animation |
|
QAngle vecNeutralAngles ( 0, 90, 0 ); |
|
m_vecGoalAngles = vecNeutralAngles; |
|
|
|
// Once we're within 10 degrees of the neutral pose, start close animation. |
|
if ( UpdateFacing() <= 10.0f ) |
|
{ |
|
StudioFrameAdvance(); |
|
} |
|
|
|
SetNextThink( gpGlobals->curtime + ROCKET_TURRET_THINK_RATE ); |
|
|
|
// Start following player after we're fully opened |
|
float flCurProgress = GetCycle(); |
|
if ( flCurProgress >= 0.99f ) |
|
{ |
|
SetThink( NULL ); |
|
SetNextThink( TICK_NEVER_THINK ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : void SyncPoseToAimAngles |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::SyncPoseToAimAngles ( void ) |
|
{ |
|
QAngle localAngles = TransformAnglesToLocalSpace( m_vecCurrentAngles.Get(), EntityToWorldTransform() ); |
|
|
|
// Update pitch |
|
SetPoseParameter( m_iPosePitch, localAngles.x ); |
|
|
|
// Update yaw -- NOTE: This yaw movement is screwy for this model, we must invert the yaw delta and also skew an extra 90 deg to |
|
// get the 'forward face' of the turret to match up with the look direction. If the model and it's pose parameters change, this will be wrong. |
|
SetPoseParameter( m_iPoseYaw, AngleNormalize( -localAngles.y - 90 ) ); |
|
|
|
InvalidateBoneCache(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Causes the turret to face its desired angles |
|
// Returns distance current and goal angles the angles in degrees. |
|
//----------------------------------------------------------------------------- |
|
float CNPC_RocketTurret::UpdateFacing( void ) |
|
{ |
|
Quaternion qtCurrent ( m_vecCurrentAngles.Get() ); |
|
Quaternion qtGoal ( m_vecGoalAngles ); |
|
Quaternion qtOut; |
|
|
|
float flDiff = QuaternionAngleDiff( qtCurrent, qtGoal ); |
|
|
|
// 1/10th degree is all the granularity we need, gives rocket player hit box width accuracy at 18k game units. |
|
if ( flDiff < 0.1 ) |
|
return flDiff; |
|
|
|
// Slerp 5% of the way to goal (distance dependant speed, but torque minimial and no euler wrapping issues). |
|
QuaternionSlerp( qtCurrent, qtGoal, 0.05, qtOut ); |
|
|
|
QAngle vNewAngles; |
|
QuaternionAngles( qtOut, vNewAngles ); |
|
|
|
m_vecCurrentAngles = vNewAngles; |
|
|
|
SyncPoseToAimAngles(); |
|
|
|
return flDiff; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Tests if this prop's front point will have direct line of sight to it's target entity once the pose parameters are set to face it |
|
// Input : vAimPoint - The point to aim at |
|
// Output : Returns true if target is in direct line of sight, false otherwise. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_RocketTurret::TestLOS( const Vector& vAimPoint ) |
|
{ |
|
// Snap to face (for accurate traces) |
|
QAngle vecOldAngles = m_vecCurrentAngles.m_Value; |
|
Vector vecToAimPoint = vAimPoint - EyePosition(); |
|
VectorAngles( vecToAimPoint, m_vecCurrentAngles.m_Value ); |
|
SyncPoseToAimAngles(); |
|
|
|
Vector vFaceOrigin = EyePosition(); |
|
trace_t trTarget; |
|
Ray_t ray; |
|
ray.Init( vFaceOrigin, vAimPoint ); |
|
ray.m_IsRay = true; |
|
|
|
// This aim point does hit target, now make sure there are no blocking objects in the way |
|
CTraceFilterSimple filter ( this, COLLISION_GROUP_NONE ); |
|
UTIL_Portal_TraceRay( ray, MASK_VISIBLE_AND_NPCS, &filter, &trTarget, false ); |
|
|
|
// Set model back to current facing |
|
m_vecCurrentAngles = vecOldAngles; |
|
SyncPoseToAimAngles(); |
|
|
|
return ( trTarget.m_pEnt == GetEnemy() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Tests all portals in the turret's vis for possible routes to see it's target point |
|
// Input : pOutVec - The location to aim at in order to hit the target ent, choosing least rotation if multiple |
|
// bConsiderNonPortalAimPoint - Output in pOutVec the non portal (direct) aimpoint if it requires the least rotation |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_RocketTurret::TestPortalsForLOS( Vector* pOutVec, bool bConsiderNonPortalAimPoint = false ) |
|
{ |
|
// Aim at the target through the world |
|
CBaseEntity* pTarget = GetEnemy(); |
|
if ( !pTarget ) |
|
{ |
|
return false; |
|
} |
|
Vector vAimPoint = pTarget->GetAbsOrigin() + (pTarget->WorldAlignMins() + pTarget->WorldAlignMaxs()) * 0.5f; |
|
|
|
int iPortalCount = CProp_Portal_Shared::AllPortals.Count(); |
|
if( iPortalCount == 0 ) |
|
{ |
|
*pOutVec = vAimPoint; |
|
return false; |
|
} |
|
|
|
Vector vCurAim; |
|
AngleVectors( m_vecCurrentAngles.m_Value, &vCurAim ); |
|
vCurAim.NormalizeInPlace(); |
|
|
|
CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base(); |
|
Vector *portalAimPoints = (Vector *)stackalloc( sizeof( Vector ) * iPortalCount ); |
|
bool *bUsable = (bool *)stackalloc( sizeof( bool ) * iPortalCount ); |
|
float *fPortalDot = (float *)stackalloc( sizeof( float ) * iPortalCount ); |
|
|
|
// Test through any active portals: This may be a shorter distance to the target |
|
for( int i = 0; i != iPortalCount; ++i ) |
|
{ |
|
CProp_Portal *pTempPortal = pPortals[i]; |
|
|
|
if( !pTempPortal->m_bActivated || |
|
(pTempPortal->m_hLinkedPortal.Get() == NULL) ) |
|
{ |
|
//portalAimPoints[i] = vec3_invalid; |
|
bUsable[i] = false; |
|
continue; |
|
} |
|
|
|
|
|
bUsable[i] = FindAimPointThroughPortal( pPortals[ i ], &portalAimPoints[ i ] ); |
|
if ( 1 ) |
|
{ |
|
QAngle goalAngles; |
|
Vector vecToEnemy = portalAimPoints[ i ] - EyePosition(); |
|
vecToEnemy.NormalizeInPlace(); |
|
|
|
// This value is for choosing the easiest aim point for the turret to see through. |
|
// 'Easiest' is the least rotation needed. |
|
fPortalDot[i] = DotProduct( vecToEnemy, vCurAim ); |
|
} |
|
} |
|
|
|
|
|
int iCountPortalsThatSeeTarget = 0; |
|
|
|
float fHighestDot = -1.0; |
|
if ( bConsiderNonPortalAimPoint ) |
|
{ |
|
QAngle enemyRotToFace; |
|
Vector vecToEnemy = vAimPoint - EyePosition(); |
|
vecToEnemy.NormalizeInPlace(); |
|
|
|
fHighestDot = DotProduct( vecToEnemy, vCurAim ); |
|
} |
|
|
|
// Compare aim points, use the closest aim point which has direct LOS |
|
for( int i = 0; i != iPortalCount; ++i ) |
|
{ |
|
if( bUsable[i] ) |
|
{ |
|
// This aim point has direct LOS |
|
if ( TestLOS( portalAimPoints[ i ] ) && fHighestDot < fPortalDot[ i ] ) |
|
{ |
|
*pOutVec = portalAimPoints[ i ]; |
|
fHighestDot = fPortalDot[ i ]; |
|
|
|
++iCountPortalsThatSeeTarget; |
|
} |
|
} |
|
} |
|
|
|
return (iCountPortalsThatSeeTarget != 0); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find the center of the target entity as seen through the specified portal |
|
// Input : pPortal - The portal to look through |
|
// Output : Vector& output point in world space where the target *appears* to be as seen through the portal |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_RocketTurret::FindAimPointThroughPortal( const CProp_Portal* pPortal, Vector* pVecOut ) |
|
{ |
|
if ( pPortal && pPortal->m_bActivated ) |
|
{ |
|
CProp_Portal* pLinked = pPortal->m_hLinkedPortal.Get(); |
|
CBaseEntity* pTarget = GetEnemy(); |
|
|
|
// Require that the portal is facing towards the beam to test through it |
|
Vector vRocketToPortal, vPortalForward; |
|
VectorSubtract ( pPortal->GetAbsOrigin(), EyePosition(), vRocketToPortal ); |
|
pPortal->GetVectors( &vPortalForward, NULL, NULL); |
|
float fDot = DotProduct( vRocketToPortal, vPortalForward ); |
|
|
|
// Portal must be facing the turret, and have a linked partner |
|
if ( fDot < 0.0f && pLinked && pLinked->m_bActivated && pTarget ) |
|
{ |
|
VMatrix matToPortalView = pLinked->m_matrixThisToLinked; |
|
Vector vTargetAimPoint = pTarget->GetAbsOrigin() + (pTarget->WorldAlignMins() + pTarget->WorldAlignMaxs()) * 0.5f; |
|
*pVecOut = matToPortalView * vTargetAimPoint; |
|
return true; |
|
} |
|
} |
|
|
|
// Bad portal pointer, not linked, no target or otherwise failed |
|
return false; |
|
} |
|
|
|
void CNPC_RocketTurret::LaserOn( void ) |
|
{ |
|
// Set Locked sprite |
|
m_iLaserState = 1; |
|
} |
|
|
|
void CNPC_RocketTurret::LaserOff( void ) |
|
{ |
|
// Set Locked sprite; |
|
m_iLaserState = 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Allows a generic think function before the others are called |
|
// Input : state - which state the turret is currently in |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_RocketTurret::PreThink( void ) |
|
{ |
|
StudioFrameAdvance(); |
|
CheckPVSCondition(); |
|
|
|
//Do not interrupt current think function |
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Toggle the turret's state |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::Toggle( void ) |
|
{ |
|
//Toggle the state |
|
if ( m_bEnabled ) |
|
{ |
|
Disable(); |
|
} |
|
else |
|
{ |
|
Enable(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Enable the turret and deploy |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::Enable( void ) |
|
{ |
|
if ( m_bEnabled ) |
|
return; |
|
|
|
m_bEnabled = true; |
|
ResetSequence( LookupSequence("open") ); |
|
|
|
SetThink( &CNPC_RocketTurret::OpeningThink ); |
|
SetNextThink( gpGlobals->curtime + 0.05 ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Retire the turret until enabled again |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::Disable( void ) |
|
{ |
|
if ( !m_bEnabled ) |
|
return; |
|
|
|
UpdateSkin( ROCKET_SKIN_IDLE ); |
|
|
|
m_bEnabled = false; |
|
ResetSequence(LookupSequence("close")); |
|
|
|
SetThink( &CNPC_RocketTurret::ClosingThink ); |
|
SetNextThink( gpGlobals->curtime + 0.05 ); |
|
SetEnemy( NULL ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Sets the enemy of this rocket |
|
// Input : pTarget - the enemy to set |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::SetTarget( CBaseEntity* pTarget ) |
|
{ |
|
SetEnemy( pTarget ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Explode and set death think |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::Destroy( void ) |
|
{ |
|
SetThink( &CNPC_RocketTurret::DyingThink ); |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::InputToggle( inputdata_t &inputdata ) |
|
{ |
|
Toggle(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::InputEnable( inputdata_t &inputdata ) |
|
{ |
|
Enable(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::InputDisable( inputdata_t &inputdata ) |
|
{ |
|
Disable(); |
|
} |
|
|
|
void CNPC_RocketTurret::InputSetTarget( inputdata_t &inputdata ) |
|
{ |
|
CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, inputdata.value.String(), NULL, NULL ); |
|
SetTarget( pTarget ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Plays some 'death' effects and sets the destroy think |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_RocketTurret::InputDestroy( inputdata_t &inputdata ) |
|
{ |
|
Destroy(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Projectile methods |
|
//----------------------------------------------------------------------------- |
|
void CRocket_Turret_Projectile::Spawn( void ) |
|
{ |
|
Precache(); |
|
BaseClass::Spawn(); |
|
SetTouch ( &CRocket_Turret_Projectile::MissileTouch ); |
|
CreateSounds(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pOther - |
|
//----------------------------------------------------------------------------- |
|
void CRocket_Turret_Projectile::MissileTouch( CBaseEntity *pOther ) |
|
{ |
|
Assert( pOther ); |
|
Vector vVel = GetAbsVelocity(); |
|
|
|
// Touched a launcher, and is heading towards that launcher |
|
if ( FClassnameIs( pOther, "npc_rocket_turret" ) ) |
|
{ |
|
Dissolve( NULL, gpGlobals->curtime + 0.1f, false, ENTITY_DISSOLVE_NORMAL ); |
|
Vector vBounceVel = Vector( -vVel.x, -vVel.y, 200 ); |
|
SetAbsVelocity ( vBounceVel * 0.1f ); |
|
QAngle vBounceAngles; |
|
VectorAngles( vBounceVel, vBounceAngles ); |
|
SetAbsAngles ( vBounceAngles ); |
|
SetLocalAngularVelocity ( QAngle ( 180, 90, 45 ) ); |
|
UTIL_Remove ( m_hRocketTrail ); |
|
|
|
SetSolid ( SOLID_NONE ); |
|
|
|
if( m_hRocketTrail ) |
|
{ |
|
m_hRocketTrail->SetLifetime(0.1f); |
|
m_hRocketTrail = NULL; |
|
} |
|
|
|
return; |
|
} |
|
|
|
// Don't touch triggers (but DO hit weapons) |
|
if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER|FSOLID_VOLUME_CONTENTS) && pOther->GetCollisionGroup() != COLLISION_GROUP_WEAPON ) |
|
return; |
|
|
|
Explode(); |
|
} |
|
|
|
|
|
void CRocket_Turret_Projectile::Precache( void ) |
|
{ |
|
BaseClass::Precache(); |
|
|
|
PrecacheScriptSound( ROCKET_PROJECTILE_LOOPING_SOUND ); |
|
} |
|
|
|
void CRocket_Turret_Projectile::NotifyLauncherOnDeath( void ) |
|
{ |
|
CNPC_RocketTurret* pLauncher = (CNPC_RocketTurret*)m_hLauncher.Get(); |
|
|
|
if ( pLauncher ) |
|
{ |
|
pLauncher->RocketDied(); |
|
} |
|
} |
|
|
|
// When teleported (usually by portal) |
|
void CRocket_Turret_Projectile::NotifySystemEvent(CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t ¶ms ) |
|
{ |
|
// On teleport, we record a pointer to the portal we are arriving at |
|
if ( eventType == NOTIFY_EVENT_TELEPORT ) |
|
{ |
|
// HACK: Clearing the owner allows collisions with launcher. |
|
// Players have had trouble realizing a launcher's own rockets don't kill it |
|
// because they didn't ever collide. We do this after a portal teleport so it avoids self-collisions on launch. |
|
SetOwnerEntity( NULL ); |
|
|
|
// Restart smoke trail |
|
UTIL_Remove( m_hRocketTrail ); |
|
m_hRocketTrail = NULL; // This shouldn't leak cause the pointer has been handed to the delete list |
|
CreateSmokeTrail(); |
|
} |
|
} |
|
|
|
void CRocket_Turret_Projectile::SetLauncher ( EHANDLE hLauncher ) |
|
{ |
|
m_hLauncher = hLauncher; |
|
} |
|
|
|
void CRocket_Turret_Projectile::DoExplosion( void ) |
|
{ |
|
NotifyLauncherOnDeath(); |
|
|
|
StopLoopingSounds(); |
|
|
|
// Explode |
|
ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), GetOwnerEntity(), 200, 25, |
|
SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE, 100.0f, this); |
|
|
|
// Hackish: Knock turrets in the area |
|
CBaseEntity* pTurretIter = NULL; |
|
|
|
while ( (pTurretIter = gEntList.FindEntityByClassnameWithin( pTurretIter, "npc_portal_turret_floor", GetAbsOrigin(), 128 )) != NULL ) |
|
{ |
|
CTakeDamageInfo info( this, this, 200, DMG_BLAST ); |
|
info.SetDamagePosition( GetAbsOrigin() ); |
|
CalculateExplosiveDamageForce( &info, (pTurretIter->GetAbsOrigin() - GetAbsOrigin()), GetAbsOrigin() ); |
|
|
|
pTurretIter->VPhysicsTakeDamage( info ); |
|
} |
|
} |
|
|
|
void CRocket_Turret_Projectile::CreateSounds() |
|
{ |
|
if (!m_pAmbientSound) |
|
{ |
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); |
|
|
|
CPASAttenuationFilter filter( this ); |
|
|
|
m_pAmbientSound = controller.SoundCreate( filter, entindex(), ROCKET_PROJECTILE_LOOPING_SOUND ); |
|
controller.Play( m_pAmbientSound, 1.0, 100 ); |
|
} |
|
} |
|
|
|
|
|
void CRocket_Turret_Projectile::StopLoopingSounds() |
|
{ |
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); |
|
|
|
controller.SoundDestroy( m_pAmbientSound ); |
|
m_pAmbientSound = NULL; |
|
|
|
BaseClass::StopLoopingSounds(); |
|
} |
|
|
|
|
|
void CRocket_Turret_Projectile::CreateSmokeTrail( void ) |
|
{ |
|
if ( m_hRocketTrail ) |
|
return; |
|
|
|
// Smoke trail. |
|
if ( (m_hRocketTrail = RocketTrail::CreateRocketTrail()) != NULL ) |
|
{ |
|
m_hRocketTrail->m_Opacity = 0.2f; |
|
m_hRocketTrail->m_SpawnRate = 100; |
|
m_hRocketTrail->m_ParticleLifetime = 0.8f; |
|
m_hRocketTrail->m_StartColor.Init( 0.65f, 0.65f , 0.65f ); |
|
m_hRocketTrail->m_EndColor.Init( 0.0, 0.0, 0.0 ); |
|
m_hRocketTrail->m_StartSize = 8; |
|
m_hRocketTrail->m_EndSize = 32; |
|
m_hRocketTrail->m_SpawnRadius = 4; |
|
m_hRocketTrail->m_MinSpeed = 2; |
|
m_hRocketTrail->m_MaxSpeed = 16; |
|
|
|
m_hRocketTrail->SetLifetime( 999 ); |
|
m_hRocketTrail->FollowEntity( this, "0" ); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
static void fire_rocket_projectile_f( void ) |
|
{ |
|
CBasePlayer *pPlayer = (CBasePlayer *)UTIL_GetCommandClient(); |
|
|
|
Vector ptEyes, vForward; |
|
QAngle vLookAng; |
|
ptEyes = pPlayer->EyePosition(); |
|
pPlayer->EyeVectors( &vForward ); |
|
vLookAng = pPlayer->EyeAngles(); |
|
|
|
CRocket_Turret_Projectile *pRocket = (CRocket_Turret_Projectile *) CBaseEntity::Create( "rocket_turret_projectile", ptEyes, vLookAng, pPlayer ); |
|
|
|
if ( !pRocket ) |
|
return; |
|
|
|
pRocket->SetThink( NULL ); |
|
pRocket->SetMoveType( MOVETYPE_FLY ); |
|
|
|
pRocket->SetModel( ROCKET_TURRET_PROJECTILE_NAME ); |
|
UTIL_SetSize( pRocket, vec3_origin, vec3_origin ); |
|
|
|
pRocket->CreateSmokeTrail(); |
|
|
|
pRocket->SetAbsVelocity( vForward * 550 ); |
|
pRocket->SetLauncher ( NULL ); |
|
} |
|
|
|
ConCommand fire_rocket_projectile( "fire_rocket_projectile", fire_rocket_projectile_f, "Fires a rocket turret projectile from the player's eyes for testing.", FCVAR_CHEAT ); |