diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/portal/npc_rocket_turret.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/server/portal/npc_rocket_turret.cpp')
| -rw-r--r-- | game/server/portal/npc_rocket_turret.cpp | 1413 |
1 files changed, 1413 insertions, 0 deletions
diff --git a/game/server/portal/npc_rocket_turret.cpp b/game/server/portal/npc_rocket_turret.cpp new file mode 100644 index 0000000..6975f5c --- /dev/null +++ b/game/server/portal/npc_rocket_turret.cpp @@ -0,0 +1,1413 @@ +//========= 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 );
\ No newline at end of file |