summaryrefslogtreecommitdiff
path: root/game/server/hl1/hl1_npc_turret.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/hl1/hl1_npc_turret.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/server/hl1/hl1_npc_turret.cpp')
-rw-r--r--game/server/hl1/hl1_npc_turret.cpp1509
1 files changed, 1509 insertions, 0 deletions
diff --git a/game/server/hl1/hl1_npc_turret.cpp b/game/server/hl1/hl1_npc_turret.cpp
new file mode 100644
index 0000000..9084b57
--- /dev/null
+++ b/game/server/hl1/hl1_npc_turret.cpp
@@ -0,0 +1,1509 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include "cbase.h"
+#include "ai_default.h"
+#include "ai_task.h"
+#include "ai_schedule.h"
+#include "ai_node.h"
+#include "ai_hull.h"
+#include "ai_hint.h"
+#include "ai_memory.h"
+#include "ai_route.h"
+#include "ai_motor.h"
+#include "ai_senses.h"
+#include "soundent.h"
+#include "game.h"
+#include "npcevent.h"
+#include "entitylist.h"
+#include "activitylist.h"
+#include "animation.h"
+#include "basecombatweapon.h"
+#include "IEffects.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "ammodef.h"
+#include "Sprite.h"
+
+#define TURRET_SHOTS 2
+#define TURRET_RANGE (100 * 12)
+#define TURRET_SPREAD Vector( 0, 0, 0 )
+#define TURRET_TURNRATE 30 //angles per 0.1 second
+#define TURRET_MAXWAIT 15 // seconds turret will stay active w/o a target
+#define TURRET_MAXSPIN 5 // seconds turret barrel will spin w/o a target
+
+typedef enum
+{
+// TURRET_ANIM_NONE = 0,
+ TURRET_ANIM_FIRE = 0,
+ TURRET_ANIM_SPIN,
+ TURRET_ANIM_DEPLOY,
+ TURRET_ANIM_RETIRE,
+ TURRET_ANIM_DIE,
+} TURRET_ANIM;
+
+#define SF_MONSTER_TURRET_AUTOACTIVATE 32
+#define SF_MONSTER_TURRET_STARTINACTIVE 64
+
+#define TURRET_GLOW_SPRITE "sprites/flare3.vmt"
+
+#define TURRET_ORIENTATION_FLOOR 0
+#define TURRET_ORIENTATION_CEILING 1
+
+class CNPC_BaseTurret : public CAI_BaseNPC
+{
+ DECLARE_CLASS( CNPC_BaseTurret, CAI_BaseNPC );
+public:
+ void Spawn(void);
+ virtual void Precache(void);
+ void EXPORT TurretUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+
+ virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+ virtual int OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo );
+
+ Class_T Classify( void );
+
+ // Think functions
+ void EXPORT ActiveThink(void);
+ void EXPORT SearchThink(void);
+ void EXPORT AutoSearchThink(void);
+ void EXPORT TurretDeath(void);
+
+ virtual void EXPORT SpinDownCall(void) { m_iSpin = 0; }
+ virtual void EXPORT SpinUpCall(void) { m_iSpin = 1; }
+
+ void EXPORT Deploy(void);
+ void EXPORT Retire(void);
+
+ void EXPORT Initialize(void);
+
+ virtual void Ping(void);
+ virtual void EyeOn(void);
+ virtual void EyeOff(void);
+
+
+ void InputActivate( inputdata_t &inputdata );
+ void InputDeactivate( inputdata_t &inputdata );
+
+ void Event_Killed( const CTakeDamageInfo &info );
+ virtual bool ShouldFadeOnDeath( void ) { return false; }
+ bool ShouldGib( const CTakeDamageInfo &info ) { return false; }
+
+ // other functions
+ void SetTurretAnim(TURRET_ANIM anim);
+ int MoveTurret(void);
+ virtual void Shoot(Vector &vecSrc, Vector &vecDirToEnemy) { };
+
+ float m_flMaxSpin; // Max time to spin the barrel w/o a target
+ int m_iSpin;
+
+ CSprite *m_pEyeGlow;
+ int m_eyeBrightness;
+
+ int m_iDeployHeight;
+ int m_iRetractHeight;
+ int m_iMinPitch;
+
+ int m_iBaseTurnRate; // angles per second
+ float m_fTurnRate; // actual turn rate
+ int m_iOrientation; // 0 = floor, 1 = Ceiling
+ int m_iOn;
+ int m_fBeserk; // Sometimes this bitch will just freak out
+ int m_iAutoStart; // true if the turret auto deploys when a target
+ // enters its range
+
+ Vector m_vecLastSight;
+ float m_flLastSight; // Last time we saw a target
+ float m_flMaxWait; // Max time to seach w/o a target
+ int m_iSearchSpeed; // Not Used!
+
+ // movement
+ float m_flStartYaw;
+ QAngle m_vecCurAngles;
+ Vector m_vecGoalAngles;
+
+
+ float m_flPingTime; // Time until the next ping, used when searching
+ float m_flSpinUpTime; // Amount of time until the barrel should spin down when searching
+
+ float m_flDamageTime;
+
+ int m_iAmmoType;
+
+ COutputEvent m_OnActivate;
+ COutputEvent m_OnDeactivate;
+
+ //DEFINE_CUSTOM_AI;
+ DECLARE_DATADESC();
+};
+
+BEGIN_DATADESC( CNPC_BaseTurret )
+
+ //FIELDS
+ DEFINE_FIELD( m_flMaxSpin, FIELD_FLOAT ),
+ DEFINE_FIELD( m_iSpin, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_pEyeGlow, FIELD_CLASSPTR ),
+ DEFINE_FIELD( m_eyeBrightness, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iDeployHeight, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iRetractHeight, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iMinPitch, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_fTurnRate, FIELD_FLOAT ),
+ DEFINE_FIELD( m_iOn, FIELD_INTEGER ),
+ DEFINE_FIELD( m_fBeserk, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iAutoStart, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_vecLastSight, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_flLastSight, FIELD_TIME ),
+
+ DEFINE_FIELD( m_flStartYaw, FIELD_FLOAT ),
+ DEFINE_FIELD( m_vecCurAngles, FIELD_VECTOR ),
+ DEFINE_FIELD( m_vecGoalAngles, FIELD_VECTOR ),
+
+ DEFINE_FIELD( m_flPingTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flSpinUpTime, FIELD_TIME ),
+
+ DEFINE_FIELD( m_flDamageTime, FIELD_TIME ),
+ //DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
+
+ //KEYFIELDS
+ DEFINE_KEYFIELD( m_flMaxWait, FIELD_FLOAT, "maxsleep" ),
+ DEFINE_KEYFIELD( m_iOrientation, FIELD_INTEGER, "orientation" ),
+ DEFINE_KEYFIELD( m_iSearchSpeed, FIELD_INTEGER, "searchspeed" ),
+ DEFINE_KEYFIELD( m_iBaseTurnRate, FIELD_INTEGER, "turnrate" ),
+
+ //Use
+ DEFINE_USEFUNC( TurretUse ),
+
+ //Thinks
+ DEFINE_THINKFUNC( ActiveThink ),
+ DEFINE_THINKFUNC( SearchThink ),
+ DEFINE_THINKFUNC( AutoSearchThink ),
+ DEFINE_THINKFUNC( TurretDeath ),
+ DEFINE_THINKFUNC( SpinDownCall ),
+ DEFINE_THINKFUNC( SpinUpCall ),
+ DEFINE_THINKFUNC( Deploy ),
+ DEFINE_THINKFUNC( Retire ),
+ DEFINE_THINKFUNC( Initialize ),
+
+ //Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ),
+
+ //Outputs
+ DEFINE_OUTPUT( m_OnActivate, "OnActivate"),
+ DEFINE_OUTPUT( m_OnDeactivate, "OnDeactivate"),
+
+END_DATADESC()
+
+
+void CNPC_BaseTurret::Spawn()
+{
+ Precache( );
+ SetNextThink( gpGlobals->curtime + 1 );
+ SetMoveType( MOVETYPE_FLY );
+ SetSequence( 0 );
+ SetCycle( 0 );
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ m_takedamage = DAMAGE_YES;
+ AddFlag( FL_AIMTARGET );
+
+ AddFlag( FL_NPC );
+ SetUse( &CNPC_BaseTurret::TurretUse );
+
+ if (( m_spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE )
+ && !( m_spawnflags & SF_MONSTER_TURRET_STARTINACTIVE ))
+ {
+ m_iAutoStart = true;
+ }
+
+ ResetSequenceInfo( );
+
+ SetBoneController(0, 0);
+ SetBoneController(1, 0);
+
+ m_flFieldOfView = VIEW_FIELD_FULL;
+
+ m_bloodColor = DONT_BLEED;
+ m_flDamageTime = 0;
+
+ if ( GetSpawnFlags() & SF_MONSTER_TURRET_STARTINACTIVE )
+ {
+ SetTurretAnim( TURRET_ANIM_RETIRE );
+ SetCycle( 0.0f );
+ m_flPlaybackRate = 0.0f;
+ }
+}
+
+
+void CNPC_BaseTurret::Precache()
+{
+ m_iAmmoType = GetAmmoDef()->Index("12mmRound");
+
+ PrecacheScriptSound( "Turret.Alert" );
+ PrecacheScriptSound( "Turret.Die" );
+ PrecacheScriptSound( "Turret.Deploy" );
+ PrecacheScriptSound( "Turret.Undeploy" );
+ PrecacheScriptSound( "Turret.Ping" );
+ PrecacheScriptSound( "Turret.Shoot" );
+}
+
+Class_T CNPC_BaseTurret::Classify( void )
+{
+ if (m_iOn || m_iAutoStart)
+ return CLASS_MACHINE;
+ return CLASS_NONE;
+}
+
+//=========================================================
+// TraceAttack - being attacked
+//=========================================================
+void CNPC_BaseTurret::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ CTakeDamageInfo ainfo = info;
+
+ if ( ptr->hitgroup == 10 )
+ {
+ // hit armor
+ if ( m_flDamageTime != gpGlobals->curtime || (random->RandomInt(0,10) < 1) )
+ {
+ g_pEffects->Ricochet( ptr->endpos, ptr->plane.normal );
+ m_flDamageTime = gpGlobals->curtime;
+ }
+
+ ainfo.SetDamage( 0.1 );// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated
+ }
+
+ if ( m_takedamage == DAMAGE_NO )
+ return;
+
+ //DevMsg( 1, "traceattack: %f\n", ainfo.GetDamage() );
+
+ AddMultiDamage( info, this );
+}
+
+//=========================================================
+// TakeDamage - take damage.
+//=========================================================
+int CNPC_BaseTurret::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
+{
+ if ( m_takedamage == DAMAGE_NO )
+ return 0;
+
+ float flDamage = inputInfo.GetDamage();
+
+ if (!m_iOn)
+ flDamage /= 10.0;
+
+ m_iHealth -= flDamage;
+ if (m_iHealth <= 0)
+ {
+ m_iHealth = 0;
+ m_takedamage = DAMAGE_NO;
+ m_flDamageTime = gpGlobals->curtime;
+
+// ClearBits (pev->flags, FL_MONSTER); // why are they set in the first place???
+
+ SetUse(NULL);
+ SetThink(&CNPC_BaseTurret::TurretDeath);
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ m_OnDeactivate.FireOutput(this, this);
+
+ return 0;
+ }
+
+ if (m_iHealth <= 10)
+ {
+ if (m_iOn)
+ {
+ m_fBeserk = 1;
+ SetThink(&CNPC_BaseTurret::SearchThink);
+ }
+ }
+ return 1;
+}
+
+int CNPC_BaseTurret::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ int retVal = 0;
+
+ if (!m_takedamage)
+ return 0;
+
+ switch( m_lifeState )
+ {
+ case LIFE_ALIVE:
+ retVal = OnTakeDamage_Alive( info );
+ if ( m_iHealth <= 0 )
+ {
+ IPhysicsObject *pPhysics = VPhysicsGetObject();
+ if ( pPhysics )
+ {
+ pPhysics->EnableCollisions( false );
+ }
+
+ Event_Killed( info );
+ Event_Dying();
+ }
+ return retVal;
+ break;
+
+ case LIFE_DYING:
+ return OnTakeDamage_Dying( info );
+
+ default:
+ case LIFE_DEAD:
+ return OnTakeDamage_Dead( info );
+ }
+}
+
+void CNPC_BaseTurret::SetTurretAnim( TURRET_ANIM anim )
+{
+ /*
+ if (GetSequence() != anim)
+ {
+ switch(anim)
+ {
+ case TURRET_ANIM_FIRE:
+ case TURRET_ANIM_SPIN:
+ if (GetSequence() != TURRET_ANIM_FIRE && GetSequence() != TURRET_ANIM_SPIN)
+ {
+ m_flCycle = 0;
+ }
+ break;
+ default:
+ m_flCycle = 0;
+ break;
+ }
+
+ SetSequence( anim );
+
+ ResetSequenceInfo( );
+
+ switch(anim)
+ {
+ case TURRET_ANIM_RETIRE:
+ m_flCycle = 255;
+ m_flPlaybackRate = -1.0; //play the animation backwards
+ break;
+ case TURRET_ANIM_DIE:
+ m_flPlaybackRate = 1.0;
+ break;
+ }
+ //ALERT(at_console, "Turret anim #%d\n", anim);
+ }
+ */
+
+ if (GetSequence() != anim)
+ {
+ SetSequence( anim );
+
+ ResetSequenceInfo( );
+
+ switch(anim)
+ {
+ case TURRET_ANIM_FIRE:
+ case TURRET_ANIM_SPIN:
+ if (GetSequence() != TURRET_ANIM_FIRE && GetSequence() != TURRET_ANIM_SPIN)
+ {
+ SetCycle( 0 );
+ }
+ break;
+ case TURRET_ANIM_RETIRE:
+ SetCycle( 1.0 );
+ m_flPlaybackRate = -1.0; //play the animation backwards
+ break;
+ case TURRET_ANIM_DIE:
+ SetCycle( 0.0 );
+ m_flPlaybackRate = 1.0;
+ break;
+ default:
+ SetCycle( 0 );
+ break;
+ }
+
+
+ }
+}
+
+//=========================================================
+// Initialize - set up the turret, initial think
+//=========================================================
+void CNPC_BaseTurret::Initialize(void)
+{
+ m_iOn = 0;
+ m_fBeserk = 0;
+ m_iSpin = 0;
+
+ SetBoneController( 0, 0 );
+ SetBoneController( 1, 0 );
+
+ if (m_iBaseTurnRate == 0) m_iBaseTurnRate = TURRET_TURNRATE;
+ if (m_flMaxWait == 0) m_flMaxWait = TURRET_MAXWAIT;
+
+ QAngle angles = GetAbsAngles();
+ m_flStartYaw = angles.y;
+ if (m_iOrientation == TURRET_ORIENTATION_CEILING)
+ {
+ angles.x = 180;
+ angles.y += 180;
+ if( angles.y > 360 )
+ angles.y -= 360;
+ SetAbsAngles( angles );
+
+// pev->idealpitch = 180; //not used?
+
+ Vector view_ofs = GetViewOffset();
+ view_ofs.z = -view_ofs.z;
+ SetViewOffset( view_ofs );
+
+// pev->effects |= EF_INVLIGHT; //no need
+ }
+
+ m_vecGoalAngles.x = 0;
+
+ if (m_iAutoStart)
+ {
+ m_flLastSight = gpGlobals->curtime + m_flMaxWait;
+ SetThink(&CNPC_BaseTurret::AutoSearchThink);
+
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ }
+ else
+ {
+ SetThink( &CBaseEntity::SUB_DoNothing );
+ }
+}
+
+//=========================================================
+// ActiveThink -
+//=========================================================
+void CNPC_BaseTurret::ActiveThink(void)
+{
+ int fAttack = 0;
+
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ StudioFrameAdvance( );
+
+ if ( (!m_iOn) || (GetEnemy() == NULL) )
+ {
+ SetEnemy( NULL );
+ m_flLastSight = gpGlobals->curtime + m_flMaxWait;
+ SetThink(&CNPC_BaseTurret::SearchThink);
+ return;
+ }
+
+ // if it's dead, look for something new
+ if ( !GetEnemy()->IsAlive() )
+ {
+ if (!m_flLastSight)
+ {
+ m_flLastSight = gpGlobals->curtime + 0.5; // continue-shooting timeout
+ }
+ else
+ {
+ if (gpGlobals->curtime > m_flLastSight)
+ {
+ SetEnemy( NULL );
+ m_flLastSight = gpGlobals->curtime + m_flMaxWait;
+ SetThink(&CNPC_BaseTurret::SearchThink);
+ return;
+ }
+ }
+ }
+
+ Vector vecMid = EyePosition();
+ Vector vecMidEnemy = GetEnemy()->BodyTarget(vecMid, false);
+
+ // Look for our current enemy
+ int fEnemyVisible = FInViewCone( GetEnemy() ) && FVisible( GetEnemy() );
+
+ //We want to look at the enemy's eyes so we don't jitter
+ Vector vecDirToEnemyEyes = vecMidEnemy - vecMid;
+// NDebugOverlay::Line( vecMid, vecMidEnemy, 0, 255, 0, false, 1.0 );
+
+ float flDistToEnemy = vecDirToEnemyEyes.Length();
+
+ VectorNormalize( vecDirToEnemyEyes );
+
+ QAngle vecAnglesToEnemy;
+ VectorAngles( vecDirToEnemyEyes, vecAnglesToEnemy );
+
+ // Current enmey is not visible.
+ if (!fEnemyVisible || (flDistToEnemy > TURRET_RANGE))
+ {
+ if (!m_flLastSight)
+ m_flLastSight = gpGlobals->curtime + 0.5;
+ else
+ {
+ // Should we look for a new target?
+ if (gpGlobals->curtime > m_flLastSight)
+ {
+ SetEnemy( NULL );
+ m_flLastSight = gpGlobals->curtime + m_flMaxWait;
+ SetThink(&CNPC_BaseTurret::SearchThink);
+ return;
+ }
+ }
+ fEnemyVisible = 0;
+ }
+ else
+ {
+ m_vecLastSight = vecMidEnemy;
+ }
+
+ Vector forward;
+ AngleVectors( m_vecCurAngles, &forward );
+
+ Vector2D vec2LOS = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ).AsVector2D();
+ vec2LOS.NormalizeInPlace();
+
+ float flDot = vec2LOS.Dot( forward.AsVector2D() );
+
+ if ( flDot <= 0.866 )
+ fAttack = FALSE;
+ else
+ fAttack = TRUE;
+
+ //forward
+ //NDebugOverlay::Line(vecMuzzle, vecMid + ( forward ), 255,0,0, false, 0.1);
+ //LOS
+ //NDebugOverlay::Line(vecMuzzle, vecMid + ( vecDirToEnemyEyes * 200 ), 0,0,255, false, 0.1);
+
+ // fire the gun
+ if (m_iSpin && ((fAttack) || (m_fBeserk)))
+ {
+ Shoot(vecMid, forward );
+ SetTurretAnim(TURRET_ANIM_FIRE);
+ }
+ else
+ {
+ SetTurretAnim(TURRET_ANIM_SPIN);
+ }
+
+ //move the gun
+ if (m_fBeserk)
+ {
+ if (random->RandomInt(0,9) == 0)
+ {
+ m_vecGoalAngles.y = random->RandomFloat(0,360);
+ m_vecGoalAngles.x = random->RandomFloat(0,90) - 90 * m_iOrientation;
+
+ CTakeDamageInfo info;
+ info.SetAttacker(this);
+ info.SetInflictor(this);
+ info.SetDamage( 1 );
+ info.SetDamageType( DMG_GENERIC );
+
+ TakeDamage( info ); // don't beserk forever
+ return;
+ }
+ }
+ else if (fEnemyVisible)
+ {
+ if (vecAnglesToEnemy.y > 360)
+ vecAnglesToEnemy.y -= 360;
+
+ if (vecAnglesToEnemy.y < 0)
+ vecAnglesToEnemy.y += 360;
+
+ //ALERT(at_console, "[%.2f]", vec.x);
+
+ if (vecAnglesToEnemy.x < -180)
+ vecAnglesToEnemy.x += 360;
+
+ if (vecAnglesToEnemy.x > 180)
+ vecAnglesToEnemy.x -= 360;
+
+ // now all numbers should be in [1...360]
+ // pin to turret limitations to [-90...14]
+
+ if (m_iOrientation == TURRET_ORIENTATION_FLOOR)
+ {
+ if (vecAnglesToEnemy.x > 90)
+ vecAnglesToEnemy.x = 90;
+ else if (vecAnglesToEnemy.x < m_iMinPitch)
+ vecAnglesToEnemy.x = m_iMinPitch;
+ }
+ else
+ {
+ if (vecAnglesToEnemy.x < -90)
+ vecAnglesToEnemy.x = -90;
+ else if (vecAnglesToEnemy.x > -m_iMinPitch)
+ vecAnglesToEnemy.x = -m_iMinPitch;
+ }
+
+ //DevMsg( 1, "->[%.2f]\n", vec.x);
+
+ m_vecGoalAngles.y = vecAnglesToEnemy.y;
+ m_vecGoalAngles.x = vecAnglesToEnemy.x;
+
+ }
+
+ SpinUpCall();
+ MoveTurret();
+}
+
+//=========================================================
+// SearchThink
+// This search function will sit with the turret deployed and look for a new target.
+// After a set amount of time, the barrel will spin down. After m_flMaxWait, the turret will
+// retact.
+//=========================================================
+void CNPC_BaseTurret::SearchThink(void)
+{
+ // ensure rethink
+ SetTurretAnim(TURRET_ANIM_SPIN);
+ StudioFrameAdvance( );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ if (m_flSpinUpTime == 0 && m_flMaxSpin)
+ m_flSpinUpTime = gpGlobals->curtime + m_flMaxSpin;
+
+ Ping( );
+
+ CBaseEntity *pEnemy = GetEnemy();
+
+ // If we have a target and we're still healthy
+ if (pEnemy != NULL)
+ {
+ if (!pEnemy->IsAlive() )
+ pEnemy = NULL;// Dead enemy forces a search for new one
+ }
+
+
+ // Acquire Target
+ if (pEnemy == NULL)
+ {
+ GetSenses()->Look(TURRET_RANGE);
+ pEnemy = BestEnemy();
+
+ if ( pEnemy && !FVisible( pEnemy ) )
+ pEnemy = NULL;
+ }
+
+ // If we've found a target, spin up the barrel and start to attack
+ if (pEnemy != NULL)
+ {
+ m_flLastSight = 0;
+ m_flSpinUpTime = 0;
+ SetThink(&CNPC_BaseTurret::ActiveThink);
+ }
+ else
+ {
+ // Are we out of time, do we need to retract?
+ if (gpGlobals->curtime > m_flLastSight)
+ {
+ //Before we retrace, make sure that we are spun down.
+ m_flLastSight = 0;
+ m_flSpinUpTime = 0;
+ SetThink(&CNPC_BaseTurret::Retire);
+ }
+ // should we stop the spin?
+ else if ((m_flSpinUpTime) && (gpGlobals->curtime > m_flSpinUpTime))
+ {
+ SpinDownCall();
+ }
+
+ // generic hunt for new victims
+ m_vecGoalAngles.y = (m_vecGoalAngles.y + 0.1 * m_fTurnRate);
+ if (m_vecGoalAngles.y >= 360)
+ m_vecGoalAngles.y -= 360;
+ MoveTurret();
+ }
+
+ SetEnemy( pEnemy );
+}
+
+//=========================================================
+// AutoSearchThink -
+//=========================================================
+void CNPC_BaseTurret::AutoSearchThink(void)
+{
+ // ensure rethink
+ StudioFrameAdvance( );
+ SetNextThink( gpGlobals->curtime + 0.3 );
+
+ // If we have a target and we're still healthy
+
+ CBaseEntity *pEnemy = GetEnemy();
+
+ if (pEnemy != NULL)
+ {
+ if (!pEnemy->IsAlive() )
+ {
+ pEnemy = NULL;
+ }
+ }
+
+ // Acquire Target
+
+ if (pEnemy == NULL)
+ {
+ GetSenses()->Look( TURRET_RANGE );
+ pEnemy = BestEnemy();
+
+ if ( pEnemy && !FVisible( pEnemy ) )
+ pEnemy = NULL;
+ }
+
+ if (pEnemy != NULL)
+ {
+ SetThink(&CNPC_BaseTurret::Deploy);
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Turret.Alert" );
+ }
+
+ SetEnemy( pEnemy );
+}
+
+extern short g_sModelIndexSmoke;
+
+//=========================================================
+// TurretDeath - I die as I have lived, beyond my means
+//=========================================================
+void CNPC_BaseTurret::TurretDeath(void)
+{
+ StudioFrameAdvance( );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ if (m_lifeState != LIFE_DEAD)
+ {
+ m_lifeState = LIFE_DEAD;
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Turret.Die" );
+
+ StopSound( entindex(), "Turret.Spinup" );
+
+ if (m_iOrientation == TURRET_ORIENTATION_FLOOR)
+ m_vecGoalAngles.x = -14;
+ else
+ m_vecGoalAngles.x = 90;//-90;
+
+ SetTurretAnim(TURRET_ANIM_DIE);
+
+ EyeOn( );
+ }
+
+ EyeOff( );
+
+ if (m_flDamageTime + random->RandomFloat( 0, 2 ) > gpGlobals->curtime)
+ {
+ // lots of smoke
+ Vector pos;
+ CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &pos );
+ pos.z = CollisionProp()->GetCollisionOrigin().z;
+
+ CBroadcastRecipientFilter filter;
+ te->Smoke( filter, 0.0, &pos,
+ g_sModelIndexSmoke,
+ 2.5,
+ 10 );
+ }
+
+ if (m_flDamageTime + random->RandomFloat( 0, 5 ) > gpGlobals->curtime)
+ {
+ Vector vecSrc;
+ CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &vecSrc );
+ g_pEffects->Sparks( vecSrc );
+ }
+
+ if (IsSequenceFinished() && !MoveTurret() && m_flDamageTime + 5 < gpGlobals->curtime)
+ {
+ m_flPlaybackRate = 0;
+ SetThink( NULL );
+ }
+}
+
+//=========================================================
+// Deploy - go active
+//=========================================================
+void CNPC_BaseTurret::Deploy(void)
+{
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ StudioFrameAdvance( );
+
+ if (GetSequence() != TURRET_ANIM_DEPLOY)
+ {
+ m_iOn = 1;
+ SetTurretAnim(TURRET_ANIM_DEPLOY);
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Turret.Deploy" );
+ m_OnActivate.FireOutput(this, this);
+ }
+
+ if (IsSequenceFinished())
+ {
+ Vector curmins, curmaxs;
+ curmins = WorldAlignMins();
+ curmaxs = WorldAlignMaxs();
+
+ curmaxs.z = m_iDeployHeight;
+ curmins.z = -m_iDeployHeight;
+
+ SetCollisionBounds( curmins, curmaxs );
+
+ m_vecCurAngles.x = 0;
+
+ QAngle angles = GetAbsAngles();
+
+ if (m_iOrientation == TURRET_ORIENTATION_CEILING)
+ {
+ m_vecCurAngles.y = UTIL_AngleMod( angles.y + 180 );
+ }
+ else
+ {
+ m_vecCurAngles.y = UTIL_AngleMod( angles.y );
+ }
+
+ SetTurretAnim(TURRET_ANIM_SPIN);
+ m_flPlaybackRate = 0;
+ SetThink(&CNPC_BaseTurret::SearchThink);
+ }
+
+ m_flLastSight = gpGlobals->curtime + m_flMaxWait;
+}
+
+//=========================================================
+// Retire - stop being active
+//=========================================================
+void CNPC_BaseTurret::Retire(void)
+{
+ // make the turret level
+ m_vecGoalAngles.x = 0;
+ m_vecGoalAngles.y = m_flStartYaw;
+
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ StudioFrameAdvance( );
+
+ EyeOff( );
+
+ if (!MoveTurret())
+ {
+ if (m_iSpin)
+ {
+ SpinDownCall();
+ }
+ else if (GetSequence() != TURRET_ANIM_RETIRE)
+ {
+ SetTurretAnim(TURRET_ANIM_RETIRE);
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Turret.Undeploy" );
+ m_OnDeactivate.FireOutput(this, this);
+ }
+ //else if (IsSequenceFinished())
+ else if( GetSequence() == TURRET_ANIM_RETIRE && GetCycle() <= 0.0 )
+ {
+ m_iOn = 0;
+ m_flLastSight = 0;
+ //SetTurretAnim(TURRET_ANIM_NONE);
+
+ Vector curmins, curmaxs;
+ curmins = WorldAlignMins();
+ curmaxs = WorldAlignMaxs();
+
+ curmaxs.z = m_iRetractHeight;
+ curmins.z = -m_iRetractHeight;
+
+ SetCollisionBounds( curmins, curmaxs );
+ if (m_iAutoStart)
+ {
+ SetThink(&CNPC_BaseTurret::AutoSearchThink);
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ }
+ else
+ {
+ SetThink( &CBaseEntity::SUB_DoNothing );
+ }
+ }
+ }
+ else
+ {
+ SetTurretAnim(TURRET_ANIM_SPIN);
+ }
+}
+
+//=========================================================
+// Ping - make the pinging noise every second while searching
+//=========================================================
+void CNPC_BaseTurret::Ping(void)
+{
+ if (m_flPingTime == 0)
+ m_flPingTime = gpGlobals->curtime + 1;
+ else if (m_flPingTime <= gpGlobals->curtime)
+ {
+ m_flPingTime = gpGlobals->curtime + 1;
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Turret.Ping" );
+
+ EyeOn( );
+ }
+ else if (m_eyeBrightness > 0)
+ {
+ EyeOff( );
+ }
+}
+
+//=========================================================
+// MoveTurret - handle turret rotation
+// returns 1 if the turret moved.
+//=========================================================
+int CNPC_BaseTurret::MoveTurret(void)
+{
+ int bMoved = 0;
+
+ if (m_vecCurAngles.x != m_vecGoalAngles.x)
+ {
+ float flDir = m_vecGoalAngles.x > m_vecCurAngles.x ? 1 : -1 ;
+
+ m_vecCurAngles.x += 0.1 * m_fTurnRate * flDir;
+
+ // if we started below the goal, and now we're past, peg to goal
+ if (flDir == 1)
+ {
+ if (m_vecCurAngles.x > m_vecGoalAngles.x)
+ m_vecCurAngles.x = m_vecGoalAngles.x;
+ }
+ else
+ {
+ if (m_vecCurAngles.x < m_vecGoalAngles.x)
+ m_vecCurAngles.x = m_vecGoalAngles.x;
+ }
+
+ if (m_iOrientation == TURRET_ORIENTATION_FLOOR)
+ SetBoneController(1, m_vecCurAngles.x);
+ else
+ SetBoneController(1, -m_vecCurAngles.x);
+
+ bMoved = 1;
+ }
+
+ if (m_vecCurAngles.y != m_vecGoalAngles.y)
+ {
+ float flDir = m_vecGoalAngles.y > m_vecCurAngles.y ? 1 : -1 ;
+ float flDist = fabs(m_vecGoalAngles.y - m_vecCurAngles.y);
+
+ if (flDist > 180)
+ {
+ flDist = 360 - flDist;
+ flDir = -flDir;
+ }
+ if (flDist > 30)
+ {
+ if (m_fTurnRate < m_iBaseTurnRate * 10)
+ {
+ m_fTurnRate += m_iBaseTurnRate;
+ }
+ }
+ else if (m_fTurnRate > 45)
+ {
+ m_fTurnRate -= m_iBaseTurnRate;
+ }
+ else
+ {
+ m_fTurnRate += m_iBaseTurnRate;
+ }
+
+ m_vecCurAngles.y += 0.1 * m_fTurnRate * flDir;
+
+ if (m_vecCurAngles.y < 0)
+ m_vecCurAngles.y += 360;
+ else if (m_vecCurAngles.y >= 360)
+ m_vecCurAngles.y -= 360;
+
+ if (flDist < (0.05 * m_iBaseTurnRate))
+ m_vecCurAngles.y = m_vecGoalAngles.y;
+
+ QAngle angles = GetAbsAngles();
+
+ //ALERT(at_console, "%.2f -> %.2f\n", m_vecCurAngles.y, y);
+
+ if (m_iOrientation == TURRET_ORIENTATION_FLOOR)
+ SetBoneController(0, m_vecCurAngles.y - angles.y );
+ else
+ SetBoneController(0, angles.y - 180 - m_vecCurAngles.y );
+ bMoved = 1;
+ }
+
+ if (!bMoved)
+ m_fTurnRate = m_iBaseTurnRate;
+
+ //DevMsg(1, "(%.2f, %.2f)->(%.2f, %.2f)\n", m_vecCurAngles.x,
+ // m_vecCurAngles.y, m_vecGoalAngles.x, m_vecGoalAngles.y);
+
+ return bMoved;
+}
+
+void CNPC_BaseTurret::TurretUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ if ( !ShouldToggle( useType, m_iOn ) )
+ return;
+
+ if (m_iOn)
+ {
+ SetEnemy( NULL );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ m_iAutoStart = FALSE;// switching off a turret disables autostart
+ //!!!! this should spin down first!!BUGBUG
+ SetThink(&CNPC_BaseTurret::Retire);
+ }
+ else
+ {
+ SetNextThink( gpGlobals->curtime + 0.1 ); // turn on delay
+
+ // if the turret is flagged as an autoactivate turret, re-enable it's ability open self.
+ if ( m_spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE )
+ {
+ m_iAutoStart = TRUE;
+ }
+
+ SetThink(&CNPC_BaseTurret::Deploy);
+ }
+}
+
+void CNPC_BaseTurret::InputDeactivate( inputdata_t &inputdata )
+{
+ if( m_iOn && m_lifeState == LIFE_ALIVE )
+ {
+ SetEnemy( NULL );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ m_iAutoStart = FALSE;// switching off a turret disables autostart
+ //!!!! this should spin down first!!BUGBUG
+ SetThink(&CNPC_BaseTurret::Retire);
+ }
+}
+
+void CNPC_BaseTurret::InputActivate( inputdata_t &inputdata )
+{
+ if( !m_iOn && m_lifeState == LIFE_ALIVE )
+ {
+ SetNextThink( gpGlobals->curtime + 0.1 ); // turn on delay
+
+ // if the turret is flagged as an autoactivate turret, re-enable it's ability open self.
+ if ( m_spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE )
+ {
+ m_iAutoStart = TRUE;
+ }
+
+ SetThink(&CNPC_BaseTurret::Deploy);
+ }
+}
+
+
+//=========================================================
+// EyeOn - turn on light on the turret
+//=========================================================
+void CNPC_BaseTurret::EyeOn(void)
+{
+ if (m_pEyeGlow)
+ {
+ if (m_eyeBrightness != 255)
+ {
+ m_eyeBrightness = 255;
+ }
+ m_pEyeGlow->SetBrightness( m_eyeBrightness );
+ }
+}
+
+//=========================================================
+// EyeOn - turn off light on the turret
+//=========================================================
+void CNPC_BaseTurret::EyeOff(void)
+{
+ if (m_pEyeGlow)
+ {
+ if (m_eyeBrightness > 0)
+ {
+ m_eyeBrightness = MAX( 0, m_eyeBrightness - 30 );
+ m_pEyeGlow->SetBrightness( m_eyeBrightness );
+ }
+ }
+}
+
+void CNPC_BaseTurret::Event_Killed( const CTakeDamageInfo &info )
+{
+ BaseClass::Event_Killed( info );
+
+ SetMoveType( MOVETYPE_FLY );
+}
+
+//===
+
+class CNPC_MiniTurret : public CNPC_BaseTurret
+{
+ DECLARE_CLASS( CNPC_MiniTurret, CNPC_BaseTurret );
+
+public:
+ void Spawn( );
+ void Precache(void);
+
+ // other functions
+ void Shoot(Vector &vecSrc, Vector &vecDirToEnemy);
+};
+
+class CNPC_Turret : public CNPC_BaseTurret
+{
+ DECLARE_CLASS( CNPC_Turret, CNPC_BaseTurret );
+
+public:
+ DECLARE_DATADESC();
+
+ void Spawn(void);
+ void Precache(void);
+
+ // Think functions
+ void SpinUpCall(void);
+ void SpinDownCall(void);
+
+ // other functions
+ void Shoot(Vector &vecSrc, Vector &vecDirToEnemy);
+
+private:
+ int m_iStartSpin;
+};
+
+BEGIN_DATADESC(CNPC_Turret)
+ DEFINE_FIELD( m_iStartSpin, FIELD_INTEGER ),
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( monster_turret, CNPC_Turret );
+LINK_ENTITY_TO_CLASS( monster_miniturret, CNPC_MiniTurret );
+
+ConVar sk_turret_health ( "sk_turret_health","50");
+ConVar sk_miniturret_health ( "sk_miniturret_health","40");
+ConVar sk_sentry_health ( "sk_sentry_health","40");
+
+void CNPC_Turret::Spawn()
+{
+ Precache( );
+ SetModel( "models/turret.mdl" );
+ m_iHealth = sk_turret_health.GetFloat();
+ m_HackedGunPos = Vector( 0, 0, 12.75 );
+ m_flMaxSpin = TURRET_MAXSPIN;
+
+ Vector view_ofs( 0, 0, 12.75 );
+ SetViewOffset( view_ofs );
+
+ CNPC_BaseTurret::Spawn( );
+
+ m_iRetractHeight = 16;
+ m_iDeployHeight = 32;
+ m_iMinPitch = -90;
+ UTIL_SetSize(this, Vector(-32, -32, -m_iRetractHeight), Vector(32, 32, m_iRetractHeight));
+
+ SetThink(&CNPC_BaseTurret::Initialize);
+
+ m_pEyeGlow = CSprite::SpriteCreate( TURRET_GLOW_SPRITE, GetAbsOrigin(), FALSE );
+ m_pEyeGlow->SetTransparency( kRenderGlow, 255, 0, 0, 0, kRenderFxNoDissipation );
+ m_pEyeGlow->SetAttachment( this, 2 );
+
+ m_eyeBrightness = 0;
+
+ SetNextThink( gpGlobals->curtime + 0.3 );
+}
+
+void CNPC_Turret::Precache()
+{
+ CNPC_BaseTurret::Precache( );
+ PrecacheModel ("models/turret.mdl");
+ PrecacheModel (TURRET_GLOW_SPRITE);
+
+ PrecacheModel( "sprites/xspark4.vmt" );
+
+ PrecacheScriptSound( "Turret.Shoot" );
+
+ PrecacheScriptSound( "Turret.SpinUpCall" );
+ PrecacheScriptSound( "Turret.Spinup" );
+ PrecacheScriptSound( "Turret.SpinDownCall" );
+ //precache sounds
+}
+
+void CNPC_Turret::Shoot(Vector &vecSrc, Vector &vecDirToEnemy)
+{
+ CPASAttenuationFilter filter( this );
+
+ FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, m_iAmmoType, 1 );
+
+ EmitSound( filter, entindex(), "Turret.Shoot" );
+
+ DoMuzzleFlash();
+}
+
+void CNPC_Turret::SpinUpCall(void)
+{
+ StudioFrameAdvance( );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ // Are we already spun up? If not start the two stage process.
+ if (!m_iSpin)
+ {
+ SetTurretAnim(TURRET_ANIM_SPIN);
+ // for the first pass, spin up the the barrel
+ if (!m_iStartSpin)
+ {
+ SetNextThink( gpGlobals->curtime + 1.0 ); //spinup delay
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Turret.SpinUpCall" );
+ m_iStartSpin = 1;
+ m_flPlaybackRate = 0.1;
+ }
+ // after the barrel is spun up, turn on the hum
+ else if (m_flPlaybackRate >= 1.0)
+ {
+ SetNextThink( gpGlobals->curtime + 0.1 );// retarget delay
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Turret.Spinup" );
+ SetThink(&CNPC_BaseTurret::ActiveThink);
+ m_iStartSpin = 0;
+ m_iSpin = 1;
+ }
+ else
+ {
+ m_flPlaybackRate += 0.075;
+ }
+ }
+
+ if (m_iSpin)
+ {
+ SetThink(&CNPC_BaseTurret::ActiveThink);
+ }
+}
+
+void CNPC_Turret::SpinDownCall(void)
+{
+ if (m_iSpin)
+ {
+ CPASAttenuationFilter filter( this );
+
+ SetTurretAnim(TURRET_ANIM_SPIN);
+
+ if ( m_flPlaybackRate == 1.0)
+ {
+ StopSound( entindex(), "Turret.Spinup" );
+ EmitSound( filter, entindex(), "Turret.SpinDownCall" );
+ }
+ m_flPlaybackRate -= 0.02;
+ if (m_flPlaybackRate <= 0)
+ {
+ m_flPlaybackRate = 0;
+ m_iSpin = 0;
+ }
+ }
+}
+
+void CNPC_MiniTurret::Spawn()
+{
+ Precache( );
+
+ SetModel( "models/miniturret.mdl" );
+ m_iHealth = sk_miniturret_health.GetFloat();
+ m_HackedGunPos = Vector( 0, 0, 12.75 );
+ m_flMaxSpin = 0;
+
+ Vector view_ofs( 0, 0, 12.75 );
+ SetViewOffset( view_ofs );
+
+ CNPC_BaseTurret::Spawn( );
+ m_iRetractHeight = 16;
+ m_iDeployHeight = 32;
+ m_iMinPitch = -90;
+ UTIL_SetSize(this, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight));
+
+ SetThink(&CNPC_MiniTurret::Initialize);
+ SetNextThink(gpGlobals->curtime + 0.3);
+
+ if (( m_spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE ) && !( m_spawnflags & SF_MONSTER_TURRET_STARTINACTIVE ))
+ {
+ m_iAutoStart = true;
+ }
+}
+
+
+void CNPC_MiniTurret::Precache()
+{
+ CNPC_BaseTurret::Precache( );
+
+ m_iAmmoType = GetAmmoDef()->Index("9mmRound");
+
+ PrecacheScriptSound( "Turret.Shoot" );
+
+ PrecacheModel ("models/miniturret.mdl");
+}
+
+void CNPC_MiniTurret::Shoot(Vector &vecSrc, Vector &vecDirToEnemy)
+{
+ FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, m_iAmmoType, 1 );
+
+ CPASAttenuationFilter filter( this );
+
+ EmitSound( filter, entindex(), "Turret.Shoot" );
+
+ DoMuzzleFlash();
+}
+
+//=========================================================
+// Sentry gun - smallest turret, placed near grunt entrenchments
+//=========================================================
+class CNPC_Sentry : public CNPC_BaseTurret
+{
+ DECLARE_CLASS( CNPC_Sentry, CNPC_BaseTurret );
+
+public:
+ void Spawn( );
+ void Precache(void);
+
+ // other functions
+ void Shoot(Vector &vecSrc, Vector &vecDirToEnemy);
+ int OnTakeDamage_Alive(const CTakeDamageInfo &info);
+ void Event_Killed( const CTakeDamageInfo &info );
+ void SentryTouch( CBaseEntity *pOther );
+
+ DECLARE_DATADESC();
+
+private:
+ bool m_bStartedDeploy; //set to true when the turret begins its deploy
+};
+
+BEGIN_DATADESC( CNPC_Sentry )
+ DEFINE_ENTITYFUNC( SentryTouch ),
+ DEFINE_FIELD( m_bStartedDeploy, FIELD_BOOLEAN ),
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( monster_sentry, CNPC_Sentry );
+
+void CNPC_Sentry::Precache()
+{
+ BaseClass::Precache( );
+
+ m_iAmmoType = GetAmmoDef()->Index("9mmRound");
+
+ PrecacheScriptSound( "Sentry.Shoot" );
+ PrecacheScriptSound( "Sentry.Die" );
+
+ PrecacheModel ("models/sentry.mdl");
+}
+
+void CNPC_Sentry::Spawn()
+{
+ Precache( );
+ SetModel( "models/sentry.mdl" );
+ m_iHealth = sk_sentry_health.GetFloat();
+ m_HackedGunPos = Vector( 0, 0, 48 );
+
+
+ SetViewOffset( Vector(0,0,48) );
+
+ m_flMaxWait = 1E6;
+ m_flMaxSpin = 1E6;
+
+ BaseClass::Spawn();
+
+ SetSequence( TURRET_ANIM_RETIRE );
+ SetCycle( 0.0 );
+ m_flPlaybackRate = 0.0;
+
+ m_iRetractHeight = 64;
+ m_iDeployHeight = 64;
+ m_iMinPitch = -60;
+
+ UTIL_SetSize(this, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight));
+
+ SetTouch(&CNPC_Sentry::SentryTouch);
+ SetThink(&CNPC_Sentry::Initialize);
+
+ SetNextThink(gpGlobals->curtime + 0.3);
+
+ m_bStartedDeploy = false;
+}
+
+void CNPC_Sentry::Shoot(Vector &vecSrc, Vector &vecDirToEnemy)
+{
+ FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, m_iAmmoType, 1 );
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Sentry.Shoot" );
+
+ DoMuzzleFlash();
+}
+
+int CNPC_Sentry::OnTakeDamage_Alive(const CTakeDamageInfo &info)
+{
+ if ( m_takedamage == DAMAGE_NO )
+ return 0;
+
+ if (!m_iOn && !m_bStartedDeploy)
+ {
+ m_bStartedDeploy = true;
+ SetThink( &CNPC_Sentry::Deploy );
+ SetUse( NULL );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ }
+
+ m_iHealth -= info.GetDamage();
+ if (m_iHealth <= 0)
+ {
+ m_iHealth = 0;
+ m_takedamage = DAMAGE_NO;
+ m_flDamageTime = gpGlobals->curtime;
+
+ SetUse(NULL);
+ SetThink(&CNPC_BaseTurret::TurretDeath); //should be SentryDeath ?
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ m_OnDeactivate.FireOutput(this, this);
+
+ return 0;
+ }
+
+ return 1;
+}
+
+void CNPC_Sentry::Event_Killed( const CTakeDamageInfo &info )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Sentry.Die" );
+
+ StopSound( entindex(), "Turret.Spinup" );
+
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+
+ Vector vecSrc;
+ QAngle vecAng;
+ GetAttachment( 2, vecSrc, vecAng );
+
+ te->Smoke( filter, 0.0, &vecSrc,
+ g_sModelIndexSmoke,
+ 2.5,
+ 10 );
+
+ g_pEffects->Sparks( vecSrc );
+
+ BaseClass::Event_Killed( info );
+}
+
+
+void CNPC_Sentry::SentryTouch( CBaseEntity *pOther )
+{
+ //trigger the sentry to turn on if a monster or player touches it
+ if ( pOther && (pOther->IsPlayer() || FBitSet ( pOther->GetFlags(), FL_NPC )) )
+ {
+ CTakeDamageInfo info;
+ info.SetAttacker( pOther );
+ info.SetInflictor( pOther );
+ info.SetDamage( 0 );
+
+ TakeDamage(info);
+ }
+}
+