source-engine/game/server/portal/npc_rocket_turret.cpp
2022-04-16 12:05:19 +03:00

1413 lines
41 KiB
C++

//========= 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 &params );
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 &params )
{
// 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 );