mirror of
https://github.com/alliedmodders/hl2sdk.git
synced 2024-12-23 01:59:43 +08:00
1127 lines
31 KiB
C++
1127 lines
31 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 "ammodef.h"
|
|
#include "Sprite.h"
|
|
#include "hl2/hl2_player.h"
|
|
#include "soundenvelope.h"
|
|
#include "explode.h"
|
|
#include "IEffects.h"
|
|
#include "animation.h"
|
|
#include "basehlcombatweapon_shared.h"
|
|
#include "iservervehicle.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
//Debug visualization
|
|
ConVar g_debug_turret_ceiling( "g_debug_turret_ceiling", "0" );
|
|
|
|
#define CEILING_TURRET_MODEL "models/combine_turrets/ceiling_turret.mdl"
|
|
#define CEILING_TURRET_GLOW_SPRITE "sprites/glow1.vmt"
|
|
/* // we now inherit these from the ai_basenpc baseclass
|
|
#define CEILING_TURRET_BC_YAW "aim_yaw"
|
|
#define CEILING_TURRET_BC_PITCH "aim_pitch"
|
|
*/
|
|
#define CEILING_TURRET_RANGE 1500
|
|
#define CEILING_TURRET_SPREAD VECTOR_CONE_2DEGREES
|
|
#define CEILING_TURRET_MAX_WAIT 5
|
|
#define CEILING_TURRET_PING_TIME 1.0f //LPB!!
|
|
|
|
#define CEILING_TURRET_VOICE_PITCH_LOW 45
|
|
#define CEILING_TURRET_VOICE_PITCH_HIGH 100
|
|
|
|
//Aiming variables
|
|
#define CEILING_TURRET_MAX_NOHARM_PERIOD 0.0f
|
|
#define CEILING_TURRET_MAX_GRACE_PERIOD 3.0f
|
|
|
|
//Spawnflags
|
|
#define SF_CEILING_TURRET_AUTOACTIVATE 0x00000020
|
|
#define SF_CEILING_TURRET_STARTINACTIVE 0x00000040
|
|
#define SF_CEILING_TURRET_NEVERRETIRE 0x00000080
|
|
#define SF_CEILING_TURRET_OUT_OF_AMMO 0x00000100
|
|
|
|
//Heights
|
|
#define CEILING_TURRET_RETRACT_HEIGHT 24
|
|
#define CEILING_TURRET_DEPLOY_HEIGHT 64
|
|
|
|
//Activities
|
|
int ACT_CEILING_TURRET_OPEN;
|
|
int ACT_CEILING_TURRET_CLOSE;
|
|
int ACT_CEILING_TURRET_OPEN_IDLE;
|
|
int ACT_CEILING_TURRET_CLOSED_IDLE;
|
|
int ACT_CEILING_TURRET_FIRE;
|
|
int ACT_CEILING_TURRET_DRYFIRE;
|
|
|
|
//Turret states
|
|
enum turretState_e
|
|
{
|
|
TURRET_SEARCHING,
|
|
TURRET_AUTO_SEARCHING,
|
|
TURRET_ACTIVE,
|
|
TURRET_DEPLOYING,
|
|
TURRET_RETIRING,
|
|
TURRET_DEAD,
|
|
};
|
|
|
|
//Eye states
|
|
enum eyeState_t
|
|
{
|
|
TURRET_EYE_SEE_TARGET, //Sees the target, bright and big
|
|
TURRET_EYE_SEEKING_TARGET, //Looking for a target, blinking (bright)
|
|
TURRET_EYE_DORMANT, //Not active
|
|
TURRET_EYE_DEAD, //Completely invisible
|
|
TURRET_EYE_DISABLED, //Turned off, must be reactivated before it'll deploy again (completely invisible)
|
|
};
|
|
|
|
//
|
|
// Ceiling Turret
|
|
//
|
|
|
|
class CNPC_CeilingTurret : public CAI_BaseNPC
|
|
{
|
|
DECLARE_CLASS( CNPC_CeilingTurret, CAI_BaseNPC );
|
|
public:
|
|
|
|
CNPC_CeilingTurret( void );
|
|
~CNPC_CeilingTurret( void );
|
|
|
|
void Precache( void );
|
|
void Spawn( void );
|
|
|
|
// Think functions
|
|
void Retire( void );
|
|
void Deploy( void );
|
|
void ActiveThink( void );
|
|
void SearchThink( void );
|
|
void AutoSearchThink( void );
|
|
void DeathThink( void );
|
|
|
|
// Inputs
|
|
void InputToggle( inputdata_t &inputdata );
|
|
void InputEnable( inputdata_t &inputdata );
|
|
void InputDisable( inputdata_t &inputdata );
|
|
|
|
void SetLastSightTime();
|
|
|
|
float MaxYawSpeed( void );
|
|
|
|
int OnTakeDamage( const CTakeDamageInfo &inputInfo );
|
|
|
|
virtual bool CanBeAnEnemyOf( CBaseEntity *pEnemy );
|
|
|
|
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 )
|
|
{
|
|
Vector vecEyeOffset(0,0,-64);
|
|
GetEyePosition( GetModelPtr(), vecEyeOffset );
|
|
return vecEyeOffset;
|
|
}
|
|
|
|
Vector EyePosition( void )
|
|
{
|
|
return GetAbsOrigin() + EyeOffset(GetActivity());
|
|
}
|
|
|
|
Vector GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget )
|
|
{
|
|
return VECTOR_CONE_5DEGREES * ((CBaseHLCombatWeapon::GetDefaultProficiencyValues())[ WEAPON_PROFICIENCY_PERFECT ].spreadscale);
|
|
}
|
|
|
|
protected:
|
|
|
|
bool PreThink( turretState_e state );
|
|
void Shoot( const Vector &vecSrc, const Vector &vecDirToEnemy );
|
|
void SetEyeState( eyeState_t state );
|
|
void Ping( void );
|
|
void Toggle( void );
|
|
void Enable( void );
|
|
void Disable( void );
|
|
void SpinUp( void );
|
|
void SpinDown( void );
|
|
void SetHeight( float height );
|
|
|
|
bool UpdateFacing( void );
|
|
|
|
int m_iAmmoType;
|
|
int m_iMinHealthDmg;
|
|
|
|
bool m_bAutoStart;
|
|
bool m_bActive; //Denotes the turret is deployed and looking for targets
|
|
bool m_bBlinkState;
|
|
bool m_bEnabled; //Denotes whether the turret is able to deploy or not
|
|
|
|
float m_flShotTime;
|
|
float m_flLastSight;
|
|
float m_flPingTime;
|
|
|
|
QAngle m_vecGoalAngles;
|
|
|
|
CSprite *m_pEyeGlow;
|
|
|
|
COutputEvent m_OnDeploy;
|
|
COutputEvent m_OnRetire;
|
|
COutputEvent m_OnTipped;
|
|
|
|
DECLARE_DATADESC();
|
|
};
|
|
|
|
//Datatable
|
|
BEGIN_DATADESC( CNPC_CeilingTurret )
|
|
|
|
DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
|
|
DEFINE_KEYFIELD( m_iMinHealthDmg, FIELD_INTEGER, "minhealthdmg" ),
|
|
DEFINE_FIELD( m_bAutoStart, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bBlinkState, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_flShotTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_flLastSight, FIELD_TIME ),
|
|
DEFINE_FIELD( m_flPingTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_vecGoalAngles, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_pEyeGlow, FIELD_CLASSPTR ),
|
|
|
|
DEFINE_THINKFUNC( Retire ),
|
|
DEFINE_THINKFUNC( Deploy ),
|
|
DEFINE_THINKFUNC( ActiveThink ),
|
|
DEFINE_THINKFUNC( SearchThink ),
|
|
DEFINE_THINKFUNC( AutoSearchThink ),
|
|
DEFINE_THINKFUNC( DeathThink ),
|
|
|
|
// Inputs
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
|
|
|
|
DEFINE_OUTPUT( m_OnDeploy, "OnDeploy" ),
|
|
DEFINE_OUTPUT( m_OnRetire, "OnRetire" ),
|
|
DEFINE_OUTPUT( m_OnTipped, "OnTipped" ),
|
|
|
|
END_DATADESC()
|
|
|
|
LINK_ENTITY_TO_CLASS( npc_turret_ceiling, CNPC_CeilingTurret );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Constructor
|
|
//-----------------------------------------------------------------------------
|
|
CNPC_CeilingTurret::CNPC_CeilingTurret( void )
|
|
{
|
|
m_bActive = false;
|
|
m_pEyeGlow = NULL;
|
|
m_iAmmoType = -1;
|
|
m_iMinHealthDmg = 0;
|
|
m_bAutoStart = false;
|
|
m_flPingTime = 0;
|
|
m_flShotTime = 0;
|
|
m_flLastSight = 0;
|
|
m_bBlinkState = false;
|
|
m_bEnabled = false;
|
|
|
|
m_vecGoalAngles.Init();
|
|
}
|
|
|
|
CNPC_CeilingTurret::~CNPC_CeilingTurret( void )
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Precache
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::Precache( void )
|
|
{
|
|
PrecacheModel( CEILING_TURRET_MODEL );
|
|
PrecacheModel( CEILING_TURRET_GLOW_SPRITE );
|
|
|
|
// Activities
|
|
ADD_CUSTOM_ACTIVITY( CNPC_CeilingTurret, ACT_CEILING_TURRET_OPEN );
|
|
ADD_CUSTOM_ACTIVITY( CNPC_CeilingTurret, ACT_CEILING_TURRET_CLOSE );
|
|
ADD_CUSTOM_ACTIVITY( CNPC_CeilingTurret, ACT_CEILING_TURRET_CLOSED_IDLE );
|
|
ADD_CUSTOM_ACTIVITY( CNPC_CeilingTurret, ACT_CEILING_TURRET_OPEN_IDLE );
|
|
ADD_CUSTOM_ACTIVITY( CNPC_CeilingTurret, ACT_CEILING_TURRET_FIRE );
|
|
ADD_CUSTOM_ACTIVITY( CNPC_CeilingTurret, ACT_CEILING_TURRET_DRYFIRE );
|
|
|
|
PrecacheScriptSound( "NPC_CeilingTurret.Retire" );
|
|
PrecacheScriptSound( "NPC_CeilingTurret.Deploy" );
|
|
PrecacheScriptSound( "NPC_CeilingTurret.Move" );
|
|
PrecacheScriptSound( "NPC_CeilingTurret.Active" );
|
|
PrecacheScriptSound( "NPC_CeilingTurret.Alert" );
|
|
PrecacheScriptSound( "NPC_CeilingTurret.ShotSounds" );
|
|
PrecacheScriptSound( "NPC_CeilingTurret.Ping" );
|
|
PrecacheScriptSound( "NPC_CeilingTurret.Die" );
|
|
|
|
PrecacheScriptSound( "NPC_FloorTurret.DryFire" );
|
|
|
|
BaseClass::Precache();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Spawn the entity
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::Spawn( void )
|
|
{
|
|
Precache();
|
|
|
|
SetModel( CEILING_TURRET_MODEL );
|
|
|
|
BaseClass::Spawn();
|
|
|
|
m_HackedGunPos = Vector( 0, 0, 12.75 );
|
|
SetViewOffset( EyeOffset( ACT_IDLE ) );
|
|
m_flFieldOfView = 0.0f;
|
|
m_takedamage = DAMAGE_YES;
|
|
m_iHealth = 1000;
|
|
m_bloodColor = BLOOD_COLOR_MECH;
|
|
|
|
SetSolid( SOLID_BBOX );
|
|
AddSolidFlags( FSOLID_NOT_STANDABLE );
|
|
|
|
SetHeight( CEILING_TURRET_RETRACT_HEIGHT );
|
|
|
|
AddFlag( FL_AIMTARGET );
|
|
AddEFlags( EFL_NO_DISSOLVE );
|
|
|
|
SetPoseParameter( m_poseAim_Yaw, 0 );
|
|
SetPoseParameter( m_poseAim_Pitch, 0 );
|
|
|
|
m_iAmmoType = GetAmmoDef()->Index( "AR2" );
|
|
|
|
//Create our eye sprite
|
|
m_pEyeGlow = CSprite::SpriteCreate( CEILING_TURRET_GLOW_SPRITE, GetLocalOrigin(), false );
|
|
m_pEyeGlow->SetTransparency( kRenderTransAdd, 255, 0, 0, 128, kRenderFxNoDissipation );
|
|
m_pEyeGlow->SetAttachment( this, 2 );
|
|
|
|
//Set our autostart state
|
|
m_bAutoStart = !!( m_spawnflags & SF_CEILING_TURRET_AUTOACTIVATE );
|
|
m_bEnabled = ( ( m_spawnflags & SF_CEILING_TURRET_STARTINACTIVE ) == false );
|
|
|
|
//Do we start active?
|
|
if ( m_bAutoStart && m_bEnabled )
|
|
{
|
|
SetThink( &CNPC_CeilingTurret::AutoSearchThink );
|
|
SetEyeState( TURRET_EYE_DORMANT );
|
|
}
|
|
else
|
|
{
|
|
SetEyeState( TURRET_EYE_DISABLED );
|
|
}
|
|
|
|
//Stagger our starting times
|
|
SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1f, 0.3f ) );
|
|
|
|
// Don't allow us to skip animation setup because our attachments are critical to us!
|
|
SetBoneCacheFlags( BCF_NO_ANIMATION_SKIP );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
int CNPC_CeilingTurret::OnTakeDamage( const CTakeDamageInfo &inputInfo )
|
|
{
|
|
if ( !m_takedamage )
|
|
return 0;
|
|
|
|
CTakeDamageInfo info = inputInfo;
|
|
|
|
if ( m_bActive == false )
|
|
info.ScaleDamage( 0.1f );
|
|
|
|
// If attacker can't do at least the min required damage to us, don't take any damage from them
|
|
if ( info.GetDamage() < m_iMinHealthDmg )
|
|
return 0;
|
|
|
|
m_iHealth -= info.GetDamage();
|
|
|
|
if ( m_iHealth <= 0 )
|
|
{
|
|
m_iHealth = 0;
|
|
m_takedamage = DAMAGE_NO;
|
|
|
|
RemoveFlag( FL_NPC ); // why are they set in the first place???
|
|
|
|
//FIXME: This needs to throw a ragdoll gib or something other than animating the retraction -- jdw
|
|
|
|
ExplosionCreate( GetAbsOrigin(), GetLocalAngles(), this, 100, 100, false );
|
|
SetThink( &CNPC_CeilingTurret::DeathThink );
|
|
|
|
StopSound( "NPC_CeilingTurret.Alert" );
|
|
|
|
m_OnDamaged.FireOutput( info.GetInflictor(), this );
|
|
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Retract and stop attacking
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::Retire( void )
|
|
{
|
|
if ( PreThink( TURRET_RETIRING ) )
|
|
return;
|
|
|
|
//Level out the turret
|
|
m_vecGoalAngles = GetAbsAngles();
|
|
SetNextThink( gpGlobals->curtime );
|
|
|
|
//Set ourselves to close
|
|
if ( GetActivity() != ACT_CEILING_TURRET_CLOSE )
|
|
{
|
|
//Set our visible state to dormant
|
|
SetEyeState( TURRET_EYE_DORMANT );
|
|
|
|
SetActivity( (Activity) ACT_CEILING_TURRET_OPEN_IDLE );
|
|
|
|
//If we're done moving to our desired facing, close up
|
|
if ( UpdateFacing() == false )
|
|
{
|
|
SetActivity( (Activity) ACT_CEILING_TURRET_CLOSE );
|
|
EmitSound( "NPC_CeilingTurret.Retire" );
|
|
|
|
//Notify of the retraction
|
|
m_OnRetire.FireOutput( NULL, this );
|
|
}
|
|
}
|
|
else if ( IsActivityFinished() )
|
|
{
|
|
SetHeight( CEILING_TURRET_RETRACT_HEIGHT );
|
|
|
|
m_bActive = false;
|
|
m_flLastSight = 0;
|
|
|
|
SetActivity( (Activity) ACT_CEILING_TURRET_CLOSED_IDLE );
|
|
|
|
//Go back to auto searching
|
|
if ( m_bAutoStart )
|
|
{
|
|
SetThink( &CNPC_CeilingTurret::AutoSearchThink );
|
|
SetNextThink( gpGlobals->curtime + 0.05f );
|
|
}
|
|
else
|
|
{
|
|
//Set our visible state to dormant
|
|
SetEyeState( TURRET_EYE_DISABLED );
|
|
SetThink( &CNPC_CeilingTurret::SUB_DoNothing );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Deploy and start attacking
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::Deploy( void )
|
|
{
|
|
if ( PreThink( TURRET_DEPLOYING ) )
|
|
return;
|
|
|
|
m_vecGoalAngles = GetAbsAngles();
|
|
|
|
SetNextThink( gpGlobals->curtime );
|
|
|
|
//Show we've seen a target
|
|
SetEyeState( TURRET_EYE_SEE_TARGET );
|
|
|
|
//Open if we're not already
|
|
if ( GetActivity() != ACT_CEILING_TURRET_OPEN )
|
|
{
|
|
m_bActive = true;
|
|
SetActivity( (Activity) ACT_CEILING_TURRET_OPEN );
|
|
EmitSound( "NPC_CeilingTurret.Deploy" );
|
|
|
|
//Notify we're deploying
|
|
m_OnDeploy.FireOutput( NULL, this );
|
|
}
|
|
|
|
//If we're done, then start searching
|
|
if ( IsActivityFinished() )
|
|
{
|
|
SetHeight( CEILING_TURRET_DEPLOY_HEIGHT );
|
|
|
|
SetActivity( (Activity) ACT_CEILING_TURRET_OPEN_IDLE );
|
|
|
|
m_flShotTime = gpGlobals->curtime + 1.0f;
|
|
|
|
m_flPlaybackRate = 0;
|
|
SetThink( &CNPC_CeilingTurret::SearchThink );
|
|
|
|
EmitSound( "NPC_CeilingTurret.Move" );
|
|
}
|
|
|
|
SetLastSightTime();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::SetLastSightTime()
|
|
{
|
|
if( HasSpawnFlags( SF_CEILING_TURRET_NEVERRETIRE ) )
|
|
{
|
|
m_flLastSight = FLT_MAX;
|
|
}
|
|
else
|
|
{
|
|
m_flLastSight = gpGlobals->curtime + CEILING_TURRET_MAX_WAIT;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the speed at which the turret can face a target
|
|
//-----------------------------------------------------------------------------
|
|
float CNPC_CeilingTurret::MaxYawSpeed( void )
|
|
{
|
|
//TODO: Scale by difficulty?
|
|
return 360.0f;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Causes the turret to face its desired angles
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_CeilingTurret::UpdateFacing( void )
|
|
{
|
|
bool bMoved = false;
|
|
matrix3x4_t localToWorld;
|
|
|
|
GetAttachment( LookupAttachment( "eyes" ), localToWorld );
|
|
|
|
Vector vecGoalDir;
|
|
AngleVectors( m_vecGoalAngles, &vecGoalDir );
|
|
|
|
Vector vecGoalLocalDir;
|
|
VectorIRotate( vecGoalDir, localToWorld, vecGoalLocalDir );
|
|
|
|
if ( g_debug_turret_ceiling.GetBool() )
|
|
{
|
|
Vector vecMuzzle, vecMuzzleDir;
|
|
QAngle vecMuzzleAng;
|
|
|
|
GetAttachment( "eyes", vecMuzzle, vecMuzzleAng );
|
|
AngleVectors( vecMuzzleAng, &vecMuzzleDir );
|
|
|
|
NDebugOverlay::Cross3D( vecMuzzle, -Vector(2,2,2), Vector(2,2,2), 255, 255, 0, false, 0.05 );
|
|
NDebugOverlay::Cross3D( vecMuzzle+(vecMuzzleDir*256), -Vector(2,2,2), Vector(2,2,2), 255, 255, 0, false, 0.05 );
|
|
NDebugOverlay::Line( vecMuzzle, vecMuzzle+(vecMuzzleDir*256), 255, 255, 0, false, 0.05 );
|
|
|
|
NDebugOverlay::Cross3D( vecMuzzle, -Vector(2,2,2), Vector(2,2,2), 255, 0, 0, false, 0.05 );
|
|
NDebugOverlay::Cross3D( vecMuzzle+(vecGoalDir*256), -Vector(2,2,2), Vector(2,2,2), 255, 0, 0, false, 0.05 );
|
|
NDebugOverlay::Line( vecMuzzle, vecMuzzle+(vecGoalDir*256), 255, 0, 0, false, 0.05 );
|
|
}
|
|
|
|
QAngle vecGoalLocalAngles;
|
|
VectorAngles( vecGoalLocalDir, vecGoalLocalAngles );
|
|
|
|
// Update pitch
|
|
float flDiff = AngleNormalize( UTIL_ApproachAngle( vecGoalLocalAngles.x, 0.0, 0.1f * MaxYawSpeed() ) );
|
|
|
|
SetPoseParameter( m_poseAim_Pitch, GetPoseParameter( m_poseAim_Pitch ) + ( flDiff / 1.5f ) );
|
|
|
|
if ( fabs( flDiff ) > 0.1f )
|
|
{
|
|
bMoved = true;
|
|
}
|
|
|
|
// Update yaw
|
|
flDiff = AngleNormalize( UTIL_ApproachAngle( vecGoalLocalAngles.y, 0.0, 0.1f * MaxYawSpeed() ) );
|
|
|
|
SetPoseParameter( m_poseAim_Yaw, GetPoseParameter( m_poseAim_Yaw ) + ( flDiff / 1.5f ) );
|
|
|
|
if ( fabs( flDiff ) > 0.1f )
|
|
{
|
|
bMoved = true;
|
|
}
|
|
|
|
InvalidateBoneCache();
|
|
|
|
return bMoved;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pEntity -
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_CeilingTurret::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
|
|
{
|
|
CBaseEntity *pHitEntity = NULL;
|
|
if ( BaseClass::FVisible( pEntity, traceMask, &pHitEntity ) )
|
|
return true;
|
|
|
|
// If we hit something that's okay to hit anyway, still fire
|
|
if ( pHitEntity && pHitEntity->MyCombatCharacterPointer() )
|
|
{
|
|
if (IRelationType(pHitEntity) == D_HT)
|
|
return true;
|
|
}
|
|
|
|
if (ppBlocker)
|
|
{
|
|
*ppBlocker = pHitEntity;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Allows the turret to fire on targets if they're visible
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::ActiveThink( void )
|
|
{
|
|
//Allow descended classes a chance to do something before the think function
|
|
if ( PreThink( TURRET_ACTIVE ) )
|
|
return;
|
|
|
|
//Update our think time
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
|
|
//If we've become inactive, go back to searching
|
|
if ( ( m_bActive == false ) || ( GetEnemy() == NULL ) )
|
|
{
|
|
SetEnemy( NULL );
|
|
SetLastSightTime();
|
|
SetThink( &CNPC_CeilingTurret::SearchThink );
|
|
m_vecGoalAngles = GetAbsAngles();
|
|
return;
|
|
}
|
|
|
|
//Get our shot positions
|
|
Vector vecMid = EyePosition();
|
|
Vector vecMidEnemy = GetEnemy()->GetAbsOrigin();
|
|
|
|
//Store off our last seen location
|
|
UpdateEnemyMemory( GetEnemy(), vecMidEnemy );
|
|
|
|
//Look for our current enemy
|
|
bool bEnemyVisible = FInViewCone( GetEnemy() ) && FVisible( GetEnemy() ) && GetEnemy()->IsAlive();
|
|
|
|
//Calculate dir and dist to enemy
|
|
Vector vecDirToEnemy = vecMidEnemy - vecMid;
|
|
float flDistToEnemy = VectorNormalize( vecDirToEnemy );
|
|
|
|
//We want to look at the enemy's eyes so we don't jitter
|
|
Vector vecDirToEnemyEyes = GetEnemy()->WorldSpaceCenter() - vecMid;
|
|
VectorNormalize( vecDirToEnemyEyes );
|
|
|
|
QAngle vecAnglesToEnemy;
|
|
VectorAngles( vecDirToEnemyEyes, vecAnglesToEnemy );
|
|
|
|
//Draw debug info
|
|
if ( g_debug_turret_ceiling.GetBool() )
|
|
{
|
|
NDebugOverlay::Cross3D( vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 );
|
|
NDebugOverlay::Cross3D( GetEnemy()->WorldSpaceCenter(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 );
|
|
NDebugOverlay::Line( vecMid, GetEnemy()->WorldSpaceCenter(), 0, 255, 0, false, 0.05 );
|
|
|
|
NDebugOverlay::Cross3D( vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 );
|
|
NDebugOverlay::Cross3D( vecMidEnemy, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 );
|
|
NDebugOverlay::Line( vecMid, vecMidEnemy, 0, 255, 0, false, 0.05f );
|
|
}
|
|
|
|
//Current enemy is not visible
|
|
if ( ( bEnemyVisible == false ) || ( flDistToEnemy > CEILING_TURRET_RANGE ))
|
|
{
|
|
if ( m_flLastSight )
|
|
{
|
|
m_flLastSight = gpGlobals->curtime + 0.5f;
|
|
}
|
|
else if ( gpGlobals->curtime > m_flLastSight )
|
|
{
|
|
// Should we look for a new target?
|
|
ClearEnemyMemory();
|
|
SetEnemy( NULL );
|
|
SetLastSightTime();
|
|
SetThink( &CNPC_CeilingTurret::SearchThink );
|
|
m_vecGoalAngles = GetAbsAngles();
|
|
|
|
SpinDown();
|
|
|
|
return;
|
|
}
|
|
|
|
bEnemyVisible = false;
|
|
}
|
|
|
|
Vector vecMuzzle, vecMuzzleDir;
|
|
QAngle vecMuzzleAng;
|
|
|
|
GetAttachment( "eyes", vecMuzzle, vecMuzzleAng );
|
|
AngleVectors( vecMuzzleAng, &vecMuzzleDir );
|
|
|
|
if ( m_flShotTime < gpGlobals->curtime )
|
|
{
|
|
//Fire the gun
|
|
if ( DotProduct( vecDirToEnemy, vecMuzzleDir ) >= 0.9848 ) // 10 degree slop
|
|
{
|
|
if ( m_spawnflags & SF_CEILING_TURRET_OUT_OF_AMMO )
|
|
{
|
|
SetActivity( (Activity) ACT_CEILING_TURRET_DRYFIRE );
|
|
}
|
|
else
|
|
{
|
|
SetActivity( (Activity) ACT_CEILING_TURRET_FIRE );
|
|
}
|
|
|
|
//Fire the weapon
|
|
Shoot( vecMuzzle, vecMuzzleDir );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetActivity( (Activity) ACT_CEILING_TURRET_OPEN_IDLE );
|
|
}
|
|
|
|
//If we can see our enemy, face it
|
|
if ( bEnemyVisible )
|
|
{
|
|
m_vecGoalAngles.y = vecAnglesToEnemy.y;
|
|
m_vecGoalAngles.x = vecAnglesToEnemy.x;
|
|
}
|
|
|
|
//Turn to face
|
|
UpdateFacing();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Target doesn't exist or has eluded us, so search for one
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::SearchThink( void )
|
|
{
|
|
//Allow descended classes a chance to do something before the think function
|
|
if ( PreThink( TURRET_SEARCHING ) )
|
|
return;
|
|
|
|
SetNextThink( gpGlobals->curtime + 0.05f );
|
|
|
|
SetActivity( (Activity) ACT_CEILING_TURRET_OPEN_IDLE );
|
|
|
|
//If our enemy has died, pick a new enemy
|
|
if ( ( GetEnemy() != NULL ) && ( GetEnemy()->IsAlive() == false ) )
|
|
{
|
|
SetEnemy( NULL );
|
|
}
|
|
|
|
//Acquire the target
|
|
if ( GetEnemy() == NULL )
|
|
{
|
|
GetSenses()->Look( CEILING_TURRET_RANGE );
|
|
CBaseEntity *pEnemy = BestEnemy();
|
|
if ( pEnemy )
|
|
{
|
|
SetEnemy( pEnemy );
|
|
}
|
|
}
|
|
|
|
//If we've found a target, spin up the barrel and start to attack
|
|
if ( GetEnemy() != NULL )
|
|
{
|
|
//Give players a grace period
|
|
if ( GetEnemy()->IsPlayer() )
|
|
{
|
|
m_flShotTime = gpGlobals->curtime + 0.5f;
|
|
}
|
|
else
|
|
{
|
|
m_flShotTime = gpGlobals->curtime + 0.1f;
|
|
}
|
|
|
|
m_flLastSight = 0;
|
|
SetThink( &CNPC_CeilingTurret::ActiveThink );
|
|
SetEyeState( TURRET_EYE_SEE_TARGET );
|
|
|
|
SpinUp();
|
|
EmitSound( "NPC_CeilingTurret.Active" );
|
|
return;
|
|
}
|
|
|
|
//Are we out of time and need to retract?
|
|
if ( gpGlobals->curtime > m_flLastSight )
|
|
{
|
|
//Before we retrace, make sure that we are spun down.
|
|
m_flLastSight = 0;
|
|
SetThink( &CNPC_CeilingTurret::Retire );
|
|
return;
|
|
}
|
|
|
|
//Display that we're scanning
|
|
m_vecGoalAngles.x = 15.0f;
|
|
m_vecGoalAngles.y = GetAbsAngles().y + ( sin( gpGlobals->curtime * 2.0f ) * 45.0f );
|
|
|
|
//Turn and ping
|
|
UpdateFacing();
|
|
Ping();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Watch for a target to wander into our view
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::AutoSearchThink( void )
|
|
{
|
|
//Allow descended classes a chance to do something before the think function
|
|
if ( PreThink( TURRET_AUTO_SEARCHING ) )
|
|
return;
|
|
|
|
//Spread out our thinking
|
|
SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.2f, 0.4f ) );
|
|
|
|
//If the enemy is dead, find a new one
|
|
if ( ( GetEnemy() != NULL ) && ( GetEnemy()->IsAlive() == false ) )
|
|
{
|
|
SetEnemy( NULL );
|
|
}
|
|
|
|
//Acquire Target
|
|
if ( GetEnemy() == NULL )
|
|
{
|
|
GetSenses()->Look( CEILING_TURRET_RANGE );
|
|
SetEnemy( BestEnemy() );
|
|
}
|
|
|
|
//Deploy if we've got an active target
|
|
if ( GetEnemy() != NULL )
|
|
{
|
|
SetThink( &CNPC_CeilingTurret::Deploy );
|
|
EmitSound( "NPC_CeilingTurret.Alert" );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Fire!
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::Shoot( const Vector &vecSrc, const Vector &vecDirToEnemy )
|
|
{
|
|
if ( m_spawnflags & SF_CEILING_TURRET_OUT_OF_AMMO )
|
|
{
|
|
EmitSound( "NPC_FloorTurret.DryFire");
|
|
EmitSound( "NPC_CeilingTurret.Activate" );
|
|
|
|
if ( RandomFloat( 0, 1 ) > 0.7 )
|
|
{
|
|
m_flShotTime = gpGlobals->curtime + random->RandomFloat( 0.5, 1.5 );
|
|
}
|
|
else
|
|
{
|
|
m_flShotTime = gpGlobals->curtime;
|
|
}
|
|
return;
|
|
}
|
|
|
|
FireBulletsInfo_t info;
|
|
|
|
if ( GetEnemy() != NULL )
|
|
{
|
|
Vector vecDir = GetActualShootTrajectory( vecSrc );
|
|
|
|
info.m_vecSrc = vecSrc;
|
|
info.m_vecDirShooting = vecDir;
|
|
info.m_iTracerFreq = 1;
|
|
info.m_iShots = 1;
|
|
info.m_pAttacker = this;
|
|
info.m_vecSpread = VECTOR_CONE_PRECALCULATED;
|
|
info.m_flDistance = MAX_COORD_RANGE;
|
|
info.m_iAmmoType = m_iAmmoType;
|
|
}
|
|
else
|
|
{
|
|
// Just shoot where you're facing!
|
|
Vector vecMuzzle, vecMuzzleDir;
|
|
QAngle vecMuzzleAng;
|
|
|
|
info.m_vecSrc = vecSrc;
|
|
info.m_vecDirShooting = vecDirToEnemy;
|
|
info.m_iTracerFreq = 1;
|
|
info.m_iShots = 1;
|
|
info.m_pAttacker = this;
|
|
info.m_vecSpread = GetAttackSpread( NULL, NULL );
|
|
info.m_flDistance = MAX_COORD_RANGE;
|
|
info.m_iAmmoType = m_iAmmoType;
|
|
}
|
|
|
|
FireBullets( info );
|
|
EmitSound( "NPC_CeilingTurret.ShotSounds" );
|
|
DoMuzzleFlash();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Allows a generic think function before the others are called
|
|
// Input : state - which state the turret is currently in
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_CeilingTurret::PreThink( turretState_e state )
|
|
{
|
|
CheckPVSCondition();
|
|
|
|
//Animate
|
|
StudioFrameAdvance();
|
|
|
|
//Do not interrupt current think function
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the state of the glowing eye attached to the turret
|
|
// Input : state - state the eye should be in
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::SetEyeState( eyeState_t state )
|
|
{
|
|
//Must have a valid eye to affect
|
|
if ( m_pEyeGlow == NULL )
|
|
return;
|
|
|
|
//Set the state
|
|
switch( state )
|
|
{
|
|
default:
|
|
case TURRET_EYE_SEE_TARGET: //Fade in and scale up
|
|
m_pEyeGlow->SetColor( 255, 0, 0 );
|
|
m_pEyeGlow->SetBrightness( 164, 0.1f );
|
|
m_pEyeGlow->SetScale( 0.4f, 0.1f );
|
|
break;
|
|
|
|
case TURRET_EYE_SEEKING_TARGET: //Ping-pongs
|
|
|
|
//Toggle our state
|
|
m_bBlinkState = !m_bBlinkState;
|
|
m_pEyeGlow->SetColor( 255, 128, 0 );
|
|
|
|
if ( m_bBlinkState )
|
|
{
|
|
//Fade up and scale up
|
|
m_pEyeGlow->SetScale( 0.25f, 0.1f );
|
|
m_pEyeGlow->SetBrightness( 164, 0.1f );
|
|
}
|
|
else
|
|
{
|
|
//Fade down and scale down
|
|
m_pEyeGlow->SetScale( 0.2f, 0.1f );
|
|
m_pEyeGlow->SetBrightness( 64, 0.1f );
|
|
}
|
|
|
|
break;
|
|
|
|
case TURRET_EYE_DORMANT: //Fade out and scale down
|
|
m_pEyeGlow->SetColor( 0, 255, 0 );
|
|
m_pEyeGlow->SetScale( 0.1f, 0.5f );
|
|
m_pEyeGlow->SetBrightness( 64, 0.5f );
|
|
break;
|
|
|
|
case TURRET_EYE_DEAD: //Fade out slowly
|
|
m_pEyeGlow->SetColor( 255, 0, 0 );
|
|
m_pEyeGlow->SetScale( 0.1f, 3.0f );
|
|
m_pEyeGlow->SetBrightness( 0, 3.0f );
|
|
break;
|
|
|
|
case TURRET_EYE_DISABLED:
|
|
m_pEyeGlow->SetColor( 0, 255, 0 );
|
|
m_pEyeGlow->SetScale( 0.1f, 1.0f );
|
|
m_pEyeGlow->SetBrightness( 0, 1.0f );
|
|
break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Make a pinging noise so the player knows where we are
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::Ping( void )
|
|
{
|
|
//See if it's time to ping again
|
|
if ( m_flPingTime > gpGlobals->curtime )
|
|
return;
|
|
|
|
//Ping!
|
|
EmitSound( "NPC_CeilingTurret.Ping" );
|
|
|
|
SetEyeState( TURRET_EYE_SEEKING_TARGET );
|
|
|
|
m_flPingTime = gpGlobals->curtime + CEILING_TURRET_PING_TIME;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Toggle the turret's state
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::Toggle( void )
|
|
{
|
|
//Toggle the state
|
|
if ( m_bEnabled )
|
|
{
|
|
Disable();
|
|
}
|
|
else
|
|
{
|
|
Enable();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Enable the turret and deploy
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::Enable( void )
|
|
{
|
|
m_bEnabled = true;
|
|
|
|
// if the turret is flagged as an autoactivate turret, re-enable its ability open self.
|
|
if ( m_spawnflags & SF_CEILING_TURRET_AUTOACTIVATE )
|
|
{
|
|
m_bAutoStart = true;
|
|
}
|
|
|
|
SetThink( &CNPC_CeilingTurret::Deploy );
|
|
SetNextThink( gpGlobals->curtime + 0.05f );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Retire the turret until enabled again
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::Disable( void )
|
|
{
|
|
m_bEnabled = false;
|
|
m_bAutoStart = false;
|
|
|
|
SetEnemy( NULL );
|
|
SetThink( &CNPC_CeilingTurret::Retire );
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Toggle the turret's state via input function
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::InputToggle( inputdata_t &inputdata )
|
|
{
|
|
Toggle();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::InputEnable( inputdata_t &inputdata )
|
|
{
|
|
Enable();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::InputDisable( inputdata_t &inputdata )
|
|
{
|
|
Disable();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::SpinUp( void )
|
|
{
|
|
}
|
|
|
|
#define CEILING_TURRET_MIN_SPIN_DOWN 1.0f
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::SpinDown( void )
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::DeathThink( void )
|
|
{
|
|
if ( PreThink( TURRET_DEAD ) )
|
|
return;
|
|
|
|
//Level out our angles
|
|
m_vecGoalAngles = GetAbsAngles();
|
|
SetNextThink( gpGlobals->curtime );
|
|
|
|
if ( m_lifeState != LIFE_DEAD )
|
|
{
|
|
m_lifeState = LIFE_DEAD;
|
|
|
|
EmitSound( "NPC_CeilingTurret.Die" );
|
|
|
|
SetActivity( (Activity) ACT_CEILING_TURRET_CLOSE );
|
|
}
|
|
|
|
// lots of smoke
|
|
Vector pos;
|
|
CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &pos );
|
|
|
|
CBroadcastRecipientFilter filter;
|
|
|
|
te->Smoke( filter, 0.0, &pos, g_sModelIndexSmoke, 2.5, 10 );
|
|
|
|
g_pEffects->Sparks( pos );
|
|
|
|
if ( IsActivityFinished() && ( UpdateFacing() == false ) )
|
|
{
|
|
SetHeight( CEILING_TURRET_RETRACT_HEIGHT );
|
|
|
|
m_flPlaybackRate = 0;
|
|
SetThink( NULL );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : height -
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_CeilingTurret::SetHeight( float height )
|
|
{
|
|
Vector forward, right, up;
|
|
AngleVectors( GetLocalAngles(), &forward, &right, &up );
|
|
|
|
Vector mins = ( forward * -16.0f ) + ( right * -16.0f );
|
|
Vector maxs = ( forward * 16.0f ) + ( right * 16.0f ) + ( up * -height );
|
|
|
|
if ( mins.x > maxs.x )
|
|
{
|
|
V_swap( mins.x, maxs.x );
|
|
}
|
|
|
|
if ( mins.y > maxs.y )
|
|
{
|
|
V_swap( mins.y, maxs.y );
|
|
}
|
|
|
|
if ( mins.z > maxs.z )
|
|
{
|
|
V_swap( mins.z, maxs.z );
|
|
}
|
|
|
|
SetCollisionBounds( mins, maxs );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pEnemy -
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_CeilingTurret::CanBeAnEnemyOf( CBaseEntity *pEnemy )
|
|
{
|
|
// If we're out of ammo, make friendly companions ignore us
|
|
if ( m_spawnflags & SF_CEILING_TURRET_OUT_OF_AMMO )
|
|
{
|
|
if ( pEnemy->Classify() == CLASS_PLAYER_ALLY_VITAL )
|
|
return false;
|
|
}
|
|
|
|
return BaseClass::CanBeAnEnemyOf( pEnemy );
|
|
} |