aboutsummaryrefslogtreecommitdiff
path: root/sp/src/game/server/hl2/npc_turret_floor.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /sp/src/game/server/hl2/npc_turret_floor.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'sp/src/game/server/hl2/npc_turret_floor.cpp')
-rw-r--r--sp/src/game/server/hl2/npc_turret_floor.cpp2330
1 files changed, 2330 insertions, 0 deletions
diff --git a/sp/src/game/server/hl2/npc_turret_floor.cpp b/sp/src/game/server/hl2/npc_turret_floor.cpp
new file mode 100644
index 00000000..990f9094
--- /dev/null
+++ b/sp/src/game/server/hl2/npc_turret_floor.cpp
@@ -0,0 +1,2330 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "npc_turret_floor.h"
+#include "ai_senses.h"
+#include "ai_memory.h"
+#include "engine/IEngineSound.h"
+#include "ammodef.h"
+#include "hl2/hl2_player.h"
+#include "soundenvelope.h"
+#include "physics_saverestore.h"
+#include "IEffects.h"
+#include "basehlcombatweapon_shared.h"
+#include "phys_controller.h"
+#include "ai_interactions.h"
+#include "Sprite.h"
+#include "beam_shared.h"
+#include "props.h"
+#include "particle_parse.h"
+
+#ifdef PORTAL
+ #include "prop_portal_shared.h"
+ #include "portal_util_shared.h"
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+const char *GetMassEquivalent(float flMass);
+
+#define DISABLE_SHOT 0
+
+//Debug visualization
+ConVar g_debug_turret( "g_debug_turret", "0" );
+
+extern ConVar physcannon_tracelength;
+
+// Interactions
+int g_interactionTurretStillStanding = 0;
+
+float CNPC_FloorTurret::fMaxTipControllerVelocity = 300.0f * 300.0f;
+float CNPC_FloorTurret::fMaxTipControllerAngularVelocity = 90.0f * 90.0f;
+
+#define LASER_BEAM_SPRITE "effects/laser1.vmt"
+
+#define FLOOR_TURRET_MODEL "models/combine_turrets/floor_turret.mdl"
+#define FLOOR_TURRET_MODEL_CITIZEN "models/combine_turrets/citizen_turret.mdl"
+#define FLOOR_TURRET_GLOW_SPRITE "sprites/glow1.vmt"
+// #define FLOOR_TURRET_BC_YAW "aim_yaw"
+// #define FLOOR_TURRET_BC_PITCH "aim_pitch"
+#define FLOOR_TURRET_RANGE 1200
+#define FLOOR_TURRET_MAX_WAIT 5
+#define FLOOR_TURRET_SHORT_WAIT 2.0 // Used for FAST_RETIRE spawnflag
+#define FLOOR_TURRET_PING_TIME 1.0f //LPB!!
+
+#define FLOOR_TURRET_VOICE_PITCH_LOW 45
+#define FLOOR_TURRET_VOICE_PITCH_HIGH 100
+
+//Aiming variables
+#define FLOOR_TURRET_MAX_NOHARM_PERIOD 0.0f
+#define FLOOR_TURRET_MAX_GRACE_PERIOD 3.0f
+
+//Activities
+int ACT_FLOOR_TURRET_OPEN;
+int ACT_FLOOR_TURRET_CLOSE;
+int ACT_FLOOR_TURRET_OPEN_IDLE;
+int ACT_FLOOR_TURRET_CLOSED_IDLE;
+int ACT_FLOOR_TURRET_FIRE;
+
+//Datatable
+BEGIN_DATADESC( CNPC_FloorTurret )
+
+ DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
+ 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_bNoAlarmSounds, FIELD_BOOLEAN ),
+
+ DEFINE_FIELD( m_flShotTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flLastSight, FIELD_TIME ),
+ DEFINE_FIELD( m_flThrashTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flPingTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextActivateSoundTime, FIELD_TIME ),
+ DEFINE_FIELD( m_bCarriedByPlayer, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bUseCarryAngles, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flPlayerDropTime, FIELD_TIME ),
+ DEFINE_FIELD( m_hLastNPCToKickMe, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_flKnockOverFailedTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flDestructStartTime, FIELD_TIME ),
+ DEFINE_FIELD( m_hFizzleEffect, FIELD_EHANDLE ),
+
+ DEFINE_FIELD( m_vecGoalAngles,FIELD_VECTOR ),
+ DEFINE_FIELD( m_iEyeAttachment, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iMuzzleAttachment, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iEyeState, FIELD_INTEGER ),
+ DEFINE_FIELD( m_hEyeGlow, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_pMotionController,FIELD_EHANDLE),
+ DEFINE_FIELD( m_vecEnemyLKP, FIELD_VECTOR ),
+ DEFINE_FIELD( m_hLaser, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_bSelfDestructing, FIELD_BOOLEAN ),
+
+ DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ),
+
+ DEFINE_FIELD( m_bHackedByAlyx, FIELD_BOOLEAN ),
+
+ DEFINE_KEYFIELD( m_iKeySkin, FIELD_INTEGER, "SkinNumber" ),
+
+ DEFINE_THINKFUNC( Retire ),
+ DEFINE_THINKFUNC( Deploy ),
+ DEFINE_THINKFUNC( ActiveThink ),
+ DEFINE_THINKFUNC( SearchThink ),
+ DEFINE_THINKFUNC( AutoSearchThink ),
+ DEFINE_THINKFUNC( TippedThink ),
+ DEFINE_THINKFUNC( InactiveThink ),
+ DEFINE_THINKFUNC( SuppressThink ),
+ DEFINE_THINKFUNC( DisabledThink ),
+ DEFINE_THINKFUNC( SelfDestructThink ),
+ DEFINE_THINKFUNC( BreakThink ),
+
+ DEFINE_USEFUNC( ToggleUse ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DepleteAmmo", InputDepleteAmmo ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "RestoreAmmo", InputRestoreAmmo ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "SelfDestruct", InputSelfDestruct ),
+
+ DEFINE_OUTPUT( m_OnDeploy, "OnDeploy" ),
+ DEFINE_OUTPUT( m_OnRetire, "OnRetire" ),
+ DEFINE_OUTPUT( m_OnTipped, "OnTipped" ),
+ DEFINE_OUTPUT( m_OnPhysGunPickup, "OnPhysGunPickup" ),
+ DEFINE_OUTPUT( m_OnPhysGunDrop, "OnPhysGunDrop" ),
+
+ DEFINE_BASENPCINTERACTABLE_DATADESC(),
+
+ // DEFINE_FIELD( m_ShotSounds, FIELD_SHORT ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( npc_turret_floor, CNPC_FloorTurret );
+
+//-----------------------------------------------------------------------------
+// Constructor
+//-----------------------------------------------------------------------------
+CNPC_FloorTurret::CNPC_FloorTurret( void ) :
+ m_bActive( false ),
+ m_hEyeGlow( NULL ),
+ m_hLaser( NULL ),
+ m_iAmmoType( -1 ),
+ m_bAutoStart( false ),
+ m_flPingTime( 0.0f ),
+ m_flNextActivateSoundTime( 0.0f ),
+ m_bCarriedByPlayer( false ),
+ m_bUseCarryAngles( false ),
+ m_flPlayerDropTime( 0.0f ),
+ m_flShotTime( 0.0f ),
+ m_flLastSight( 0.0f ),
+ m_bBlinkState( false ),
+ m_flThrashTime( 0.0f ),
+ m_pMotionController( NULL ),
+ m_bEnabled( false ),
+ m_bSelfDestructing( false )
+{
+ m_vecGoalAngles.Init();
+
+ m_vecEnemyLKP = vec3_invalid;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+Class_T CNPC_FloorTurret::Classify( void )
+{
+ if ( m_bEnabled )
+ {
+ // Hacked or friendly turrets don't attack players
+ if( m_bHackedByAlyx || IsCitizenTurret() )
+ return CLASS_PLAYER_ALLY;
+
+ return CLASS_COMBINE;
+ }
+
+ return CLASS_NONE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::UpdateOnRemove( void )
+{
+ if ( m_pMotionController != NULL )
+ {
+ UTIL_Remove( m_pMotionController );
+ m_pMotionController = NULL;
+ }
+
+ if ( m_hLaser != NULL )
+ {
+ UTIL_Remove( m_hLaser );
+ m_hLaser = NULL;
+ }
+
+ if ( m_hEyeGlow != NULL )
+ {
+ UTIL_Remove( m_hEyeGlow );
+ m_hEyeGlow = NULL;
+ }
+
+ BaseClass::UpdateOnRemove();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Precache
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::Precache( void )
+{
+ const char *pModelName = STRING( GetModelName() );
+ pModelName = ( pModelName && pModelName[ 0 ] != '\0' ) ? pModelName : FLOOR_TURRET_MODEL;
+ PrecacheModel( pModelName );
+ PrecacheModel( FLOOR_TURRET_GLOW_SPRITE );
+
+ PropBreakablePrecacheAll( MAKE_STRING( pModelName ) );
+
+ if ( IsCitizenTurret() )
+ {
+ PrecacheModel( LASER_BEAM_SPRITE );
+ PrecacheScriptSound( "NPC_FloorTurret.AlarmPing");
+ }
+
+ // Activities
+ ADD_CUSTOM_ACTIVITY( CNPC_FloorTurret, ACT_FLOOR_TURRET_OPEN );
+ ADD_CUSTOM_ACTIVITY( CNPC_FloorTurret, ACT_FLOOR_TURRET_CLOSE );
+ ADD_CUSTOM_ACTIVITY( CNPC_FloorTurret, ACT_FLOOR_TURRET_CLOSED_IDLE );
+ ADD_CUSTOM_ACTIVITY( CNPC_FloorTurret, ACT_FLOOR_TURRET_OPEN_IDLE );
+ ADD_CUSTOM_ACTIVITY( CNPC_FloorTurret, ACT_FLOOR_TURRET_FIRE );
+
+ PrecacheScriptSound( "NPC_FloorTurret.Retire" );
+ PrecacheScriptSound( "NPC_FloorTurret.Deploy" );
+ PrecacheScriptSound( "NPC_FloorTurret.Move" );
+ PrecacheScriptSound( "NPC_Combine.WeaponBash" );
+ PrecacheScriptSound( "NPC_FloorTurret.Activate" );
+ PrecacheScriptSound( "NPC_FloorTurret.Alert" );
+ m_ShotSounds = PrecacheScriptSound( "NPC_FloorTurret.ShotSounds" );
+ PrecacheScriptSound( "NPC_FloorTurret.Die" );
+ PrecacheScriptSound( "NPC_FloorTurret.Retract");
+ PrecacheScriptSound( "NPC_FloorTurret.Alarm");
+ PrecacheScriptSound( "NPC_FloorTurret.Ping");
+ PrecacheScriptSound( "NPC_FloorTurret.DryFire");
+ PrecacheScriptSound( "NPC_FloorTurret.Destruct" );
+
+#ifdef HL2_EPISODIC
+ PrecacheParticleSystem( "explosion_turret_break" );
+#endif // HL2_EPISODIC
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Spawn the entity
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::Spawn( void )
+{
+ Precache();
+
+ const char *pModelName = STRING( GetModelName() );
+ SetModel( ( pModelName && pModelName[ 0 ] != '\0' ) ? pModelName : FLOOR_TURRET_MODEL );
+
+ // If we're a citizen turret, we use a different skin
+ if ( IsCitizenTurret() )
+ {
+ if (m_iKeySkin == 0)
+ { // select a "random" skin -- rather than being truly random, use a static variable
+ // to cycle through them evenly. The static won't be saved across save/load, but
+ // frankly I don't care so much about that.
+ // m_nSkin = random->RandomInt( 1, 4 );
+ static unsigned int nextSkin = 0;
+ m_nSkin = nextSkin + 1;
+
+ // add one mod 4
+ nextSkin = (nextSkin + 1) & 0x03;
+ }
+ else
+ { // at least make sure that it's in the right range
+ m_nSkin = clamp(m_iKeySkin,1,4);
+ }
+ }
+
+ BaseClass::Spawn();
+
+ SetBlocksLOS( false );
+
+ m_HackedGunPos = Vector( 0, 0, 12.75 );
+ SetViewOffset( EyeOffset( ACT_IDLE ) );
+ m_flFieldOfView = 0.4f; // 60 degrees
+ m_takedamage = DAMAGE_EVENTS_ONLY;
+ m_iHealth = 100;
+ m_iMaxHealth = 100;
+
+ AddEFlags( EFL_NO_DISSOLVE );
+
+ SetPoseParameter( m_poseAim_Yaw, 0 );
+ SetPoseParameter( m_poseAim_Pitch, 0 );
+
+ m_iAmmoType = GetAmmoDef()->Index( "PISTOL" );
+
+ m_iMuzzleAttachment = LookupAttachment( "eyes" );
+ m_iEyeAttachment = LookupAttachment( "light" );
+
+ // FIXME: Do we ever need m_bAutoStart? (Sawyer)
+ m_spawnflags |= SF_FLOOR_TURRET_AUTOACTIVATE;
+
+ //Set our autostart state
+ m_bAutoStart = !!( m_spawnflags & SF_FLOOR_TURRET_AUTOACTIVATE );
+ m_bEnabled = ( ( m_spawnflags & SF_FLOOR_TURRET_STARTINACTIVE ) == false );
+
+ //Do we start active?
+ if ( m_bAutoStart && m_bEnabled )
+ {
+ SetThink( &CNPC_FloorTurret::AutoSearchThink );
+ SetEyeState( TURRET_EYE_DORMANT );
+ }
+ else
+ {
+ SetThink( &CNPC_FloorTurret::DisabledThink );
+ SetEyeState( TURRET_EYE_DISABLED );
+ }
+
+ // Start
+ if ( OnSide() )
+ {
+ SetThink( &CNPC_FloorTurret::DisabledThink );
+ SetEyeState( TURRET_EYE_DISABLED );
+ }
+
+ //Stagger our starting times
+ SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1f, 0.3f ) );
+
+ SetUse( &CNPC_FloorTurret::ToggleUse );
+
+ // Don't allow us to skip animation setup because our attachments are critical to us!
+ SetBoneCacheFlags( BCF_NO_ANIMATION_SKIP );
+
+ CreateVPhysics();
+
+ SetState(NPC_STATE_IDLE);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::Activate( void )
+{
+ BaseClass::Activate();
+
+ // Force the eye state to the current state so that our glows are recreated after transitions
+ SetEyeState( m_iEyeState );
+
+ if ( !m_pMotionController )
+ {
+ // Create the motion controller
+ m_pMotionController = CTurretTipController::CreateTipController( this );
+
+ // Enable the controller
+ if ( m_pMotionController != NULL )
+ {
+ m_pMotionController->Enable();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+bool CNPC_FloorTurret::CreateVPhysics( void )
+{
+ //Spawn our physics hull
+ if ( VPhysicsInitNormal( SOLID_VPHYSICS, 0, false ) == NULL )
+ {
+ DevMsg( "npc_turret_floor unable to spawn physics object!\n" );
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Retract and stop attacking
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::Retire( void )
+{
+ if ( PreThink( TURRET_RETIRING ) )
+ return;
+
+ //Level out the turret
+ m_vecGoalAngles = GetAbsAngles();
+ SetNextThink( gpGlobals->curtime + 0.05f );
+
+ //Set ourselves to close
+ if ( GetActivity() != ACT_FLOOR_TURRET_CLOSE )
+ {
+ //Set our visible state to dormant
+ SetEyeState( TURRET_EYE_DORMANT );
+
+ SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE );
+
+ //If we're done moving to our desired facing, close up
+ if ( UpdateFacing() == false )
+ {
+ SetActivity( (Activity) ACT_FLOOR_TURRET_CLOSE );
+ EmitSound( "NPC_FloorTurret.Retire" );
+
+ //Notify of the retraction
+ m_OnRetire.FireOutput( NULL, this );
+ }
+ }
+ else if ( IsActivityFinished() )
+ {
+ m_bActive = false;
+ m_flLastSight = 0;
+
+ SetActivity( (Activity) ACT_FLOOR_TURRET_CLOSED_IDLE );
+
+ //Go back to auto searching
+ if ( m_bAutoStart )
+ {
+ SetThink( &CNPC_FloorTurret::AutoSearchThink );
+ SetNextThink( gpGlobals->curtime + 0.05f );
+ }
+ else
+ {
+ //Set our visible state to dormant
+ SetEyeState( TURRET_EYE_DISABLED );
+ SetThink( &CNPC_FloorTurret::DisabledThink );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Deploy and start attacking
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::Deploy( void )
+{
+ if ( PreThink( TURRET_DEPLOYING ) )
+ return;
+
+ m_vecGoalAngles = GetAbsAngles();
+
+ SetNextThink( gpGlobals->curtime + 0.05f );
+
+ //Show we've seen a target
+ SetEyeState( TURRET_EYE_SEE_TARGET );
+
+ //Open if we're not already
+ if ( GetActivity() != ACT_FLOOR_TURRET_OPEN )
+ {
+ m_bActive = true;
+ SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN );
+ EmitSound( "NPC_FloorTurret.Deploy" );
+
+ //Notify we're deploying
+ m_OnDeploy.FireOutput( NULL, this );
+ }
+
+ //If we're done, then start searching
+ if ( IsActivityFinished() )
+ {
+ SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE );
+
+ m_flShotTime = gpGlobals->curtime + 1.0f;
+
+ m_flPlaybackRate = 0;
+ SetThink( &CNPC_FloorTurret::SearchThink );
+
+ EmitSound( "NPC_FloorTurret.Move" );
+ }
+
+ m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_MAX_WAIT;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
+{
+ m_hPhysicsAttacker = pPhysGunUser;
+ m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
+
+ // Drop our mass a lot so that we can be moved easily with +USE
+ if ( reason != PUNTED_BY_CANNON )
+ {
+ Assert( VPhysicsGetObject() );
+
+ m_bCarriedByPlayer = true;
+ m_OnPhysGunPickup.FireOutput( this, this );
+
+ // We want to use preferred carry angles if we're not nicely upright
+ Vector vecToTurret = pPhysGunUser->GetAbsOrigin() - GetAbsOrigin();
+ vecToTurret.z = 0;
+ VectorNormalize( vecToTurret );
+
+ // We want to use preferred carry angles if we're not nicely upright
+ Vector forward, up;
+ GetVectors( &forward, NULL, &up );
+
+ bool bUpright = DotProduct( up, Vector(0,0,1) ) > 0.9f;
+ bool bBehind = DotProduct( vecToTurret, forward ) < 0.85f;
+
+ // Correct our angles only if we're not upright or we're mostly behind the turret
+ if ( hl2_episodic.GetBool() )
+ {
+ m_bUseCarryAngles = ( bUpright == false || bBehind );
+ }
+ else
+ {
+ m_bUseCarryAngles = ( bUpright == false );
+ }
+ }
+
+ // Clear out our last NPC to kick me, because it makes no sense now
+ m_hLastNPCToKickMe = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
+{
+ m_hPhysicsAttacker = pPhysGunUser;
+ m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
+
+ m_bCarriedByPlayer = false;
+ m_bUseCarryAngles = false;
+ m_OnPhysGunDrop.FireOutput( this, this );
+
+ // If this is a friendly turret, remember that it was just dropped
+ if ( IRelationType( pPhysGunUser ) != D_HT )
+ {
+ m_flPlayerDropTime = gpGlobals->curtime + 2.0;
+ }
+
+ // Restore our mass to the original value
+ Assert( VPhysicsGetObject() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Whether this should return carry angles
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_FloorTurret::HasPreferredCarryAnglesForPlayer( CBasePlayer *pPlayer )
+{
+ // Don't use preferred angles on enemy turrets
+ if ( IRelationType( pPlayer ) == D_HT )
+ return false;
+
+ return m_bUseCarryAngles;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CNPC_FloorTurret::OnAttemptPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
+{
+ // Prevent players pulling enemy turrets from afar if they're in front of the turret
+ if ( reason == PICKED_UP_BY_CANNON && IRelationType( pPhysGunUser ) == D_HT )
+ {
+ Vector vecForward;
+ GetVectors( &vecForward, NULL, NULL );
+ Vector vecForce = (pPhysGunUser->GetAbsOrigin() - GetAbsOrigin());
+ float flDistance = VectorNormalize( vecForce );
+
+ // If it's over the physcannon tracelength, we're pulling it
+ if ( flDistance > physcannon_tracelength.GetFloat() )
+ {
+ float flDot = DotProduct( vecForward, vecForce );
+ if ( flDot > 0.5 )
+ return false;
+ }
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CNPC_FloorTurret::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter *sourceEnt)
+{
+ if ( interactionType == g_interactionCombineBash )
+ {
+ // We've been bashed by a combine soldier. Remember who it was, if we haven't got an active kicker
+ if ( !m_hLastNPCToKickMe )
+ {
+ m_hLastNPCToKickMe = sourceEnt;
+ m_flKnockOverFailedTime = gpGlobals->curtime + 3.0;
+ }
+
+ // Get knocked away
+ Vector forward, up;
+ AngleVectors( sourceEnt->GetLocalAngles(), &forward, NULL, &up );
+ ApplyAbsVelocityImpulse( forward * 100 + up * 50 );
+ CTakeDamageInfo info( sourceEnt, sourceEnt, 30, DMG_CLUB );
+ CalculateMeleeDamageForce( &info, forward, GetAbsOrigin() );
+ TakeDamage( info );
+
+ EmitSound( "NPC_Combine.WeaponBash" );
+ return true;
+ }
+
+ return BaseClass::HandleInteraction( interactionType, data, sourceEnt );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the speed at which the turret can face a target
+//-----------------------------------------------------------------------------
+float CNPC_FloorTurret::MaxYawSpeed( void )
+{
+ //TODO: Scale by difficulty?
+ return 360.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if this turret was recently dropped by a player
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_FloorTurret::WasJustDroppedByPlayer( void )
+{
+ if ( m_flPlayerDropTime > gpGlobals->curtime )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Causes the turret to face its desired angles
+//-----------------------------------------------------------------------------
+bool CNPC_FloorTurret::UpdateFacing( void )
+{
+ bool bMoved = false;
+ UpdateMuzzleMatrix();
+
+ Vector vecGoalDir;
+ AngleVectors( m_vecGoalAngles, &vecGoalDir );
+
+ Vector vecGoalLocalDir;
+ VectorIRotate( vecGoalDir, m_muzzleToWorld, vecGoalLocalDir );
+
+ if ( g_debug_turret.GetBool() )
+ {
+ Vector vecMuzzle, vecMuzzleDir;
+
+ MatrixGetColumn( m_muzzleToWorld, 3, vecMuzzle );
+ MatrixGetColumn( m_muzzleToWorld, 0, 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.05f * 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.05f * MaxYawSpeed() ) );
+
+ SetPoseParameter( m_poseAim_Yaw, GetPoseParameter( m_poseAim_Yaw ) + ( flDiff / 1.5f ) );
+
+ if ( fabs( flDiff ) > 0.1f )
+ {
+ bMoved = true;
+ }
+
+ // You're going to make decisions based on this info. So bump the bone cache after you calculate everything
+ InvalidateBoneCache();
+
+ return bMoved;
+}
+
+void CNPC_FloorTurret::DryFire( void )
+{
+ EmitSound( "NPC_FloorTurret.DryFire");
+ EmitSound( "NPC_FloorTurret.Activate" );
+
+ if ( RandomFloat( 0, 1 ) > 0.5 )
+ {
+ m_flShotTime = gpGlobals->curtime + random->RandomFloat( 1, 2.5 );
+ }
+ else
+ {
+ m_flShotTime = gpGlobals->curtime;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Turret will continue to fire on a target's position when it loses sight of it
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::SuppressThink( void )
+{
+ //Allow descended classes a chance to do something before the think function
+ if ( PreThink( TURRET_SUPPRESSING ) )
+ return;
+
+ //Update our think time
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ // Look for a new enemy
+ HackFindEnemy();
+
+ //If we've acquired an enemy, start firing at it
+ if ( !GetEnemy() )
+ {
+ SetThink( &CNPC_FloorTurret::ActiveThink );
+ return;
+ }
+
+ //See if we're done suppressing
+ if ( gpGlobals->curtime > m_flLastSight )
+ {
+ // Should we look for a new target?
+ ClearEnemyMemory();
+ SetEnemy( NULL );
+ SetThink( &CNPC_FloorTurret::SearchThink );
+ m_vecGoalAngles = GetAbsAngles();
+
+ SpinDown();
+
+ if ( m_spawnflags & SF_FLOOR_TURRET_FASTRETIRE )
+ {
+ // Retire quickly in this case. (The case where we saw the player, but he hid again).
+ m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_SHORT_WAIT;
+ }
+ else
+ {
+ m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_MAX_WAIT;
+ }
+
+ return;
+ }
+
+ //Get our shot positions
+ Vector vecMid = EyePosition();
+ Vector vecMidEnemy = m_vecEnemyLKP;
+
+ //Calculate dir and dist to enemy
+ Vector vecDirToEnemy = vecMidEnemy - vecMid;
+
+ //We want to look at the enemy's eyes so we don't jitter
+ Vector vecDirToEnemyEyes = vecMidEnemy - vecMid;
+ VectorNormalize( vecDirToEnemyEyes );
+
+ QAngle vecAnglesToEnemy;
+ VectorAngles( vecDirToEnemyEyes, vecAnglesToEnemy );
+
+ //Draw debug info
+ if ( g_debug_turret.GetBool() )
+ {
+ 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 );
+ }
+
+ if ( m_flShotTime < gpGlobals->curtime && m_vecEnemyLKP != vec3_invalid )
+ {
+ Vector vecMuzzle, vecMuzzleDir;
+ UpdateMuzzleMatrix();
+ MatrixGetColumn( m_muzzleToWorld, 0, vecMuzzleDir );
+ MatrixGetColumn( m_muzzleToWorld, 3, vecMuzzle );
+
+ //Fire the gun
+ if ( DotProduct( vecDirToEnemy, vecMuzzleDir ) >= 0.9848 ) // 10 degree slop
+ {
+ if( m_spawnflags & SF_FLOOR_TURRET_OUT_OF_AMMO )
+ {
+ DryFire();
+ }
+ else
+ {
+ ResetActivity();
+ SetActivity( (Activity) ACT_FLOOR_TURRET_FIRE );
+
+ //Fire the weapon
+#if !DISABLE_SHOT
+ Shoot( vecMuzzle, vecMuzzleDir );
+#endif
+ }
+ }
+ }
+ else
+ {
+ SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE );
+ }
+
+ //If we can see our enemy, face it
+ m_vecGoalAngles.y = vecAnglesToEnemy.y;
+ m_vecGoalAngles.x = vecAnglesToEnemy.x;
+
+ //Turn to face
+ UpdateFacing();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows the turret to fire on targets if they're visible
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::ActiveThink( void )
+{
+ //Allow descended classes a chance to do something before the think function
+ if ( PreThink( TURRET_ACTIVE ) )
+ return;
+
+ HackFindEnemy();
+
+ //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 );
+ m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_MAX_WAIT;
+ SetThink( &CNPC_FloorTurret::SearchThink );
+ m_vecGoalAngles = GetAbsAngles();
+ return;
+ }
+
+ //Get our shot positions
+ Vector vecMid = EyePosition();
+ Vector vecMidEnemy = GetEnemy()->BodyTarget( vecMid );
+
+ // Store off our last seen location so we can suppress it later
+ m_vecEnemyLKP = vecMidEnemy;
+
+ //Look for our current enemy
+ bool bEnemyInFOV = FInViewCone( GetEnemy() );
+ bool bEnemyVisible = FVisible( GetEnemy() ) && GetEnemy()->IsAlive();
+
+ // Robin: This is a hack to get around the fact that the muzzle for the turret
+ // is outside it's vcollide. This means that if it leans against a thin wall,
+ // the muzzle can be on the other side of the wall, where it's then able to see
+ // and shoot at targets. This check ensures that nothing has come between the
+ // center of the turret and the muzzle.
+ if ( bEnemyVisible )
+ {
+ trace_t tr;
+ Vector vecCenter;
+ CollisionProp()->CollisionToWorldSpace( Vector(0,0,52), &vecCenter );
+ UTIL_TraceLine( vecCenter, vecMid, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction != 1.0 )
+ {
+ bEnemyVisible = false;
+ }
+ }
+
+ //Calculate dir and dist to enemy
+ Vector vecDirToEnemy = vecMidEnemy - vecMid;
+ float flDistToEnemy = VectorNormalize( vecDirToEnemy );
+
+ //Draw debug info
+ if ( g_debug_turret.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 );
+ }
+
+ //See if they're past our FOV of attack
+ if ( bEnemyInFOV == false )
+ {
+ // Should we look for a new target?
+ ClearEnemyMemory();
+ SetEnemy( NULL );
+
+ if ( m_spawnflags & SF_FLOOR_TURRET_FASTRETIRE )
+ {
+ // Retire quickly in this case. (The case where we saw the player, but he hid again).
+ m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_SHORT_WAIT;
+ }
+ else
+ {
+ m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_MAX_WAIT;
+ }
+
+ SetThink( &CNPC_FloorTurret::SearchThink );
+ m_vecGoalAngles = GetAbsAngles();
+
+ SpinDown();
+
+ return;
+ }
+
+ //Current enemy is not visible
+ if ( ( bEnemyVisible == false ) || ( flDistToEnemy > FLOOR_TURRET_RANGE ))
+ {
+ m_flLastSight = gpGlobals->curtime + 2.0f;
+
+ ClearEnemyMemory();
+ SetEnemy( NULL );
+ SetThink( &CNPC_FloorTurret::SuppressThink );
+
+ return;
+ }
+
+ if ( g_debug_turret.GetBool() )
+ {
+ Vector vecMuzzle, vecMuzzleDir;
+
+ UpdateMuzzleMatrix();
+ MatrixGetColumn( m_muzzleToWorld, 0, vecMuzzleDir );
+ MatrixGetColumn( m_muzzleToWorld, 3, vecMuzzle );
+
+ // Visualize vertical firing ranges
+ for ( int i = 0; i < 4; i++ )
+ {
+ QAngle angMaxDownPitch = GetAbsAngles();
+
+ switch( i )
+ {
+ case 0: angMaxDownPitch.x -= 15; break;
+ case 1: angMaxDownPitch.x += 15; break;
+ case 2: angMaxDownPitch.x -= 25; break;
+ case 3: angMaxDownPitch.x += 25; break;
+ default:
+ break;
+ }
+
+ Vector vecMaxDownPitch;
+ AngleVectors( angMaxDownPitch, &vecMaxDownPitch );
+ NDebugOverlay::Line( vecMuzzle, vecMuzzle + (vecMaxDownPitch*256), 255, 255, 255, false, 0.1 );
+ }
+ }
+
+ if ( m_flShotTime < gpGlobals->curtime )
+ {
+ Vector vecMuzzle, vecMuzzleDir;
+
+ UpdateMuzzleMatrix();
+ MatrixGetColumn( m_muzzleToWorld, 0, vecMuzzleDir );
+ MatrixGetColumn( m_muzzleToWorld, 3, vecMuzzle );
+
+ Vector2D vecDirToEnemy2D = vecDirToEnemy.AsVector2D();
+ Vector2D vecMuzzleDir2D = vecMuzzleDir.AsVector2D();
+
+ bool bCanShoot = true;
+ float minCos3d = DOT_10DEGREE; // 10 degrees slop
+
+ if ( flDistToEnemy < 60.0 )
+ {
+ vecDirToEnemy2D.NormalizeInPlace();
+ vecMuzzleDir2D.NormalizeInPlace();
+
+ bCanShoot = ( vecDirToEnemy2D.Dot(vecMuzzleDir2D) >= DOT_10DEGREE );
+ minCos3d = 0.7071; // 45 degrees
+ }
+
+ //Fire the gun
+ if ( bCanShoot ) // 10 degree slop XY
+ {
+ float dot3d = DotProduct( vecDirToEnemy, vecMuzzleDir );
+
+ if( m_spawnflags & SF_FLOOR_TURRET_OUT_OF_AMMO )
+ {
+ DryFire();
+ }
+ else
+ {
+ if ( dot3d >= minCos3d )
+ {
+ ResetActivity();
+ SetActivity( (Activity) ACT_FLOOR_TURRET_FIRE );
+
+ //Fire the weapon
+#if !DISABLE_SHOT
+ Shoot( vecMuzzle, vecMuzzleDir, (dot3d < DOT_10DEGREE) );
+#endif
+ }
+ }
+ }
+ }
+ else
+ {
+ SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE );
+ }
+
+ //If we can see our enemy, face it
+ if ( bEnemyVisible )
+ {
+ //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 );
+
+ 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_FloorTurret::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_FLOOR_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 )
+ {
+ HackFindEnemy();
+ }
+
+ //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_FloorTurret::ActiveThink );
+ SetEyeState( TURRET_EYE_SEE_TARGET );
+
+ SpinUp();
+
+ if ( gpGlobals->curtime > m_flNextActivateSoundTime )
+ {
+ EmitSound( "NPC_FloorTurret.Activate" );
+ m_flNextActivateSoundTime = gpGlobals->curtime + 3.0;
+ }
+ 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_FloorTurret::Retire );
+ return;
+ }
+
+ //Display that we're scanning
+ m_vecGoalAngles.x = GetAbsAngles().x + ( sin( gpGlobals->curtime * 1.0f ) * 15.0f );
+ m_vecGoalAngles.y = GetAbsAngles().y + ( sin( gpGlobals->curtime * 2.0f ) * 60.0f );
+
+ //Turn and ping
+ UpdateFacing();
+ Ping();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Watch for a target to wander into our view
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::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 )
+ {
+ HackFindEnemy();
+ }
+
+ //Deploy if we've got an active target
+ if ( GetEnemy() != NULL )
+ {
+ SetThink( &CNPC_FloorTurret::Deploy );
+ if ( !m_bNoAlarmSounds )
+ {
+ EmitSound( "NPC_FloorTurret.Alert" );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Fire!
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::Shoot( const Vector &vecSrc, const Vector &vecDirToEnemy, bool bStrict )
+{
+ FireBulletsInfo_t info;
+
+ if ( !bStrict && 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
+ {
+ 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, GetEnemy() );
+ info.m_flDistance = MAX_COORD_RANGE;
+ info.m_iAmmoType = m_iAmmoType;
+ }
+
+ FireBullets( info );
+ EmitSound( "NPC_FloorTurret.ShotSounds", m_ShotSounds );
+ DoMuzzleFlash();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEnemy -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_FloorTurret::IsValidEnemy( CBaseEntity *pEnemy )
+{
+ if ( m_NPCState == NPC_STATE_DEAD )
+ return false;
+
+ // Don't shoot at other turrets.
+ if ( pEnemy->m_iClassname == m_iClassname )
+ return false;
+
+ // If our eye is stuck in something, don't shoot
+ if ( UTIL_PointContents(EyePosition()) & MASK_SHOT )
+ return false;
+
+ // Turrets have limited vertical aim capability
+ // - Can only aim +-15 degrees, + the 10 degree slop they're allowed.
+ Vector vEnemyPos = pEnemy->EyePosition();
+
+#ifdef PORTAL
+ if ( !FInViewCone( pEnemy ) || !FVisible( pEnemy ) )
+ {
+ CProp_Portal *pPortal = FInViewConeThroughPortal( pEnemy );
+
+ if ( pPortal )
+ {
+ // Translate our target across the portal
+ UTIL_Portal_PointTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), vEnemyPos, vEnemyPos );
+ }
+ }
+#endif
+
+ Vector los = ( vEnemyPos - EyePosition() );
+
+ QAngle angleToTarget;
+ VectorAngles( los, angleToTarget );
+ float flZDiff = fabs( AngleNormalize( angleToTarget.x - GetAbsAngles().x) );
+ if ( flZDiff > 28.0f && los.LengthSqr() > 4096.0f )
+ return false;
+
+ return BaseClass::IsValidEnemy( pEnemy );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEnemy -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_FloorTurret::CanBeAnEnemyOf( CBaseEntity *pEnemy )
+{
+ // If we're out of ammo, make friendly companions ignore us
+ if ( m_spawnflags & SF_FLOOR_TURRET_OUT_OF_AMMO )
+ {
+ if ( pEnemy->Classify() == CLASS_PLAYER_ALLY_VITAL )
+ return false;
+ }
+
+ // If we're on the side, we're never anyone's enemy
+ if ( OnSide() )
+ return false;
+
+ return BaseClass::CanBeAnEnemyOf( pEnemy );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The turret has been tipped over and will thrash for awhile
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::TippedThink( void )
+{
+ // Update our PVS state
+ CheckPVSCondition();
+
+ //Animate
+ StudioFrameAdvance();
+
+ SetNextThink( gpGlobals->curtime + 0.05f );
+ SetEnemy( NULL );
+
+ // If we're not on side anymore, stop thrashing
+ if ( !OnSide() )
+ {
+ ReturnToLife();
+ return;
+ }
+
+ //See if we should continue to thrash
+ if ( gpGlobals->curtime < m_flThrashTime )
+ {
+ if ( m_flShotTime < gpGlobals->curtime )
+ {
+ if( m_spawnflags & SF_FLOOR_TURRET_OUT_OF_AMMO )
+ {
+ DryFire();
+ }
+ else if ( IsCitizenTurret() == false ) // Citizen turrets don't wildly fire
+ {
+ Vector vecMuzzle, vecMuzzleDir;
+ UpdateMuzzleMatrix();
+ MatrixGetColumn( m_muzzleToWorld, 0, vecMuzzleDir );
+ MatrixGetColumn( m_muzzleToWorld, 3, vecMuzzle );
+
+ ResetActivity();
+ SetActivity( (Activity) ACT_FLOOR_TURRET_FIRE );
+
+#if !DISABLE_SHOT
+ Shoot( vecMuzzle, vecMuzzleDir );
+#endif
+ }
+
+ m_flShotTime = gpGlobals->curtime + 0.05f;
+ }
+
+ m_vecGoalAngles.x = GetAbsAngles().x + random->RandomFloat( -60, 60 );
+ m_vecGoalAngles.y = GetAbsAngles().y + random->RandomFloat( -60, 60 );
+
+ UpdateFacing();
+ }
+ else
+ {
+ //Face forward
+ m_vecGoalAngles = GetAbsAngles();
+
+ //Set ourselves to close
+ if ( GetActivity() != ACT_FLOOR_TURRET_CLOSE )
+ {
+ SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE );
+
+ //If we're done moving to our desired facing, close up
+ if ( UpdateFacing() == false )
+ {
+ //Make any last death noises and anims
+ EmitSound( "NPC_FloorTurret.Die" );
+ SpinDown();
+
+ SetActivity( (Activity) ACT_FLOOR_TURRET_CLOSE );
+ EmitSound( "NPC_FloorTurret.Retract" );
+
+ CTakeDamageInfo info;
+ info.SetDamage( 1 );
+ info.SetDamageType( DMG_CRUSH );
+ Event_Killed( info );
+ }
+ }
+ else if ( IsActivityFinished() )
+ {
+ m_bActive = false;
+ m_flLastSight = 0;
+
+ SetActivity( (Activity) ACT_FLOOR_TURRET_CLOSED_IDLE );
+
+ // Don't need to store last NPC anymore, because I've been knocked over
+ if ( m_hLastNPCToKickMe )
+ {
+ m_hLastNPCToKickMe = NULL;
+ m_flKnockOverFailedTime = 0;
+ }
+
+ //Try to look straight
+ if ( UpdateFacing() == false )
+ {
+ m_OnTipped.FireOutput( this, this );
+ SetEyeState( TURRET_EYE_DEAD );
+ SetCollisionGroup( COLLISION_GROUP_DEBRIS_TRIGGER );
+
+ // Start thinking slowly to see if we're ever set upright somehow
+ SetThink( &CNPC_FloorTurret::InactiveThink );
+ SetNextThink( gpGlobals->curtime + 1.0f );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: This turret is dead. See if it ever becomes upright again, and if
+// so, become active again.
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::InactiveThink( void )
+{
+ // Update our PVS state
+ CheckPVSCondition();
+
+ // Wake up if we're not on our side
+ if ( !OnSide() && m_bEnabled )
+ {
+ ReturnToLife();
+ return;
+ }
+
+ if ( IsCitizenTurret() )
+ {
+ // Blink if we have ammo or our current blink is "on" and we need to turn it off again
+ if ( HasSpawnFlags( SF_FLOOR_TURRET_OUT_OF_AMMO ) == false || m_bBlinkState )
+ {
+ // If we're on our side, ping and complain to the player
+ if ( m_bBlinkState == false )
+ {
+ // Ping when the light is going to come back on
+ EmitSound( "NPC_FloorTurret.AlarmPing" );
+ }
+
+ SetEyeState( TURRET_EYE_ALARM );
+ SetNextThink( gpGlobals->curtime + 0.25f );
+ }
+ }
+ else
+ {
+ SetNextThink( gpGlobals->curtime + 1.0f );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::ReturnToLife( void )
+{
+ m_flThrashTime = 0;
+
+ // Enable the tip controller
+ m_pMotionController->Enable( true );
+
+ // Return to life
+ SetState( NPC_STATE_IDLE );
+ m_lifeState = LIFE_ALIVE;
+ SetCollisionGroup( COLLISION_GROUP_NONE );
+
+ // Become active again
+ Enable();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The turret is not doing anything at all
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::DisabledThink( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.5 );
+ if ( OnSide() )
+ {
+ m_OnTipped.FireOutput( this, this );
+ SetEyeState( TURRET_EYE_DEAD );
+ SetCollisionGroup( COLLISION_GROUP_DEBRIS );
+ SetThink( NULL );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The turret doesn't run base AI properly, which is a bad decision.
+// As a result, it has to manually find enemies.
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::HackFindEnemy( void )
+{
+ // We have to refresh our memories before finding enemies, so
+ // dead enemies are cleared out before new ones are added.
+ GetEnemies()->RefreshMemories();
+
+ GetSenses()->Look( FLOOR_TURRET_RANGE );
+ SetEnemy( BestEnemy() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Determines whether the turret is upright enough to function
+// Output : Returns true if the turret is tipped over
+//-----------------------------------------------------------------------------
+inline bool CNPC_FloorTurret::OnSide( void )
+{
+ Vector up;
+ GetVectors( NULL, NULL, &up );
+
+ return ( DotProduct( up, Vector(0,0,1) ) < 0.5f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows a generic think function before the others are called
+// Input : state - which state the turret is currently in
+//-----------------------------------------------------------------------------
+bool CNPC_FloorTurret::PreThink( turretState_e state )
+{
+ // Hack to disable turrets when ai is disabled
+ if ( CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI )
+ {
+ // Push our think out into the future
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ return true;
+ }
+
+ CheckPVSCondition();
+
+ //Animate
+ StudioFrameAdvance();
+
+ // We're gonna blow up, so don't interrupt us
+ if ( state == TURRET_SELF_DESTRUCTING )
+ return false;
+
+ //See if we've tipped, but only do this if we're not being carried
+ if ( !IsBeingCarriedByPlayer() )
+ {
+ if ( OnSide() == false )
+ {
+ // If I still haven't fallen over after an NPC has tried to knock me down, let them know
+ if ( m_hLastNPCToKickMe && m_flKnockOverFailedTime < gpGlobals->curtime )
+ {
+ m_hLastNPCToKickMe->DispatchInteraction( g_interactionTurretStillStanding, NULL, this );
+ m_hLastNPCToKickMe = NULL;
+ }
+
+ //Debug visualization
+ if ( g_debug_turret.GetBool() )
+ {
+ Vector up;
+ GetVectors( NULL, NULL, &up );
+
+ NDebugOverlay::Line( GetAbsOrigin()+(up*32), GetAbsOrigin()+(up*128), 0, 255, 0, false, 2.0f );
+ NDebugOverlay::Cross3D( GetAbsOrigin()+(up*32), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 2.0f );
+ NDebugOverlay::Cross3D( GetAbsOrigin()+(up*128), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 2.0f );
+ }
+ }
+ else
+ {
+ if ( HasSpawnFlags( SF_FLOOR_TURRET_OUT_OF_AMMO ) == false )
+ {
+ //Thrash around for a bit
+ m_flThrashTime = gpGlobals->curtime + random->RandomFloat( 2.0f, 2.5f );
+ SetNextThink( gpGlobals->curtime + 0.05f );
+
+ SetThink( &CNPC_FloorTurret::TippedThink );
+ SetEyeState( TURRET_EYE_SEE_TARGET );
+
+ SpinUp();
+ if ( !m_bNoAlarmSounds )
+ {
+ EmitSound( "NPC_FloorTurret.Alarm" );
+ }
+ }
+ else
+ {
+ // Take away the laser
+ UTIL_Remove( m_hLaser );
+ m_hLaser = NULL;
+
+ // Become inactive
+ SetThink( &CNPC_FloorTurret::InactiveThink );
+ SetEyeState( TURRET_EYE_DEAD );
+ }
+
+ //Stop being targetted
+ SetState( NPC_STATE_DEAD );
+ m_lifeState = LIFE_DEAD;
+
+ //Disable the tip controller
+ m_pMotionController->Enable( false );
+
+ //Debug visualization
+ if ( g_debug_turret.GetBool() )
+ {
+ Vector up;
+ GetVectors( NULL, NULL, &up );
+
+ NDebugOverlay::Line( GetAbsOrigin()+(up*32), GetAbsOrigin()+(up*128), 255, 0, 0, false, 2.0f );
+ NDebugOverlay::Cross3D( GetAbsOrigin()+(up*32), -Vector(2,2,2), Vector(2,2,2), 255, 0, 0, false, 2.0f );
+ NDebugOverlay::Cross3D( GetAbsOrigin()+(up*128), -Vector(2,2,2), Vector(2,2,2), 255, 0, 0, false, 2.0f );
+ }
+
+ //Interrupt current think function
+ return true;
+ }
+ }
+
+ //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_FloorTurret::SetEyeState( eyeState_t state )
+{
+ // Must have a valid eye to affect
+ if ( !m_hEyeGlow )
+ {
+ // Create our eye sprite
+ m_hEyeGlow = CSprite::SpriteCreate( FLOOR_TURRET_GLOW_SPRITE, GetLocalOrigin(), false );
+ if ( !m_hEyeGlow )
+ return;
+
+ m_hEyeGlow->SetTransparency( kRenderWorldGlow, 255, 0, 0, 128, kRenderFxNoDissipation );
+ m_hEyeGlow->SetAttachment( this, m_iEyeAttachment );
+ }
+
+ // Add the laser if it doesn't already exist
+ if ( IsCitizenTurret() && HasSpawnFlags( SF_FLOOR_TURRET_OUT_OF_AMMO ) == false && m_hLaser == NULL )
+ {
+ m_hLaser = CBeam::BeamCreate( LASER_BEAM_SPRITE, 1.0f );
+ if ( m_hLaser == NULL )
+ return;
+
+ m_hLaser->EntsInit( this, this );
+ m_hLaser->FollowEntity( this );
+ m_hLaser->SetStartAttachment( LookupAttachment( "laser_start" ) );
+ m_hLaser->SetEndAttachment( LookupAttachment( "laser_end" ) );
+ m_hLaser->SetNoise( 0 );
+ m_hLaser->SetColor( 255, 0, 0 );
+ m_hLaser->SetScrollRate( 0 );
+ m_hLaser->SetWidth( 1.0f );
+ m_hLaser->SetEndWidth( 1.0f );
+ m_hLaser->SetBrightness( 160 );
+ m_hLaser->SetBeamFlags( SF_BEAM_SHADEIN );
+ }
+
+ m_iEyeState = state;
+
+ //Set the state
+ switch( state )
+ {
+ default:
+ case TURRET_EYE_SEE_TARGET: //Fade in and scale up
+ m_hEyeGlow->SetColor( 255, 0, 0 );
+ m_hEyeGlow->SetBrightness( 164, 0.1f );
+ m_hEyeGlow->SetScale( 0.4f, 0.1f );
+ break;
+
+ case TURRET_EYE_SEEKING_TARGET: //Ping-pongs
+
+ //Toggle our state
+ m_bBlinkState = !m_bBlinkState;
+ m_hEyeGlow->SetColor( 255, 128, 0 );
+
+ if ( m_bBlinkState )
+ {
+ //Fade up and scale up
+ m_hEyeGlow->SetScale( 0.25f, 0.1f );
+ m_hEyeGlow->SetBrightness( 164, 0.1f );
+ }
+ else
+ {
+ //Fade down and scale down
+ m_hEyeGlow->SetScale( 0.2f, 0.1f );
+ m_hEyeGlow->SetBrightness( 64, 0.1f );
+ }
+
+ break;
+
+ case TURRET_EYE_DORMANT: //Fade out and scale down
+ m_hEyeGlow->SetColor( 0, 255, 0 );
+ m_hEyeGlow->SetScale( 0.1f, 0.5f );
+ m_hEyeGlow->SetBrightness( 64, 0.5f );
+ break;
+
+ case TURRET_EYE_DEAD: //Fade out slowly
+ m_hEyeGlow->SetColor( 255, 0, 0 );
+ m_hEyeGlow->SetScale( 0.1f, 3.0f );
+ m_hEyeGlow->SetBrightness( 0, 3.0f );
+ break;
+
+ case TURRET_EYE_DISABLED:
+ m_hEyeGlow->SetColor( 0, 255, 0 );
+ m_hEyeGlow->SetScale( 0.1f, 1.0f );
+ m_hEyeGlow->SetBrightness( 0, 1.0f );
+ break;
+
+ case TURRET_EYE_ALARM:
+ {
+ //Toggle our state
+ m_bBlinkState = !m_bBlinkState;
+ m_hEyeGlow->SetColor( 255, 0, 0 );
+
+ if ( m_bBlinkState )
+ {
+ //Fade up and scale up
+ m_hEyeGlow->SetScale( 0.75f, 0.05f );
+ m_hEyeGlow->SetBrightness( 192, 0.05f );
+ }
+ else
+ {
+ //Fade down and scale down
+ m_hEyeGlow->SetScale( 0.25f, 0.25f );
+ m_hEyeGlow->SetBrightness( 64, 0.25f );
+ }
+ }
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Make a pinging noise so the player knows where we are
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::Ping( void )
+{
+ //See if it's time to ping again
+ if ( m_flPingTime > gpGlobals->curtime )
+ return;
+
+ //Ping!
+ EmitSound( "NPC_FloorTurret.Ping" );
+
+ SetEyeState( TURRET_EYE_SEEKING_TARGET );
+
+ m_flPingTime = gpGlobals->curtime + FLOOR_TURRET_PING_TIME;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Toggle the turret's state
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::Toggle( void )
+{
+ //This turret is on its side, it can't function
+ if ( OnSide() || ( IsAlive() == false ) )
+ return;
+
+ //Toggle the state
+ if ( m_bEnabled )
+ {
+ Disable();
+ }
+ else
+ {
+ Enable();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Enable the turret and deploy
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::Enable( void )
+{
+ // Don't interrupt blowing up!
+ if ( m_bSelfDestructing )
+ return;
+
+ // Always allow us to come back to life, even if not right now
+ m_bEnabled = true;
+
+ //This turret is on its side, it can't function
+ if ( OnSide() || ( IsAlive() == false ) )
+ return;
+
+ // if the turret is flagged as an autoactivate turret, re-enable its ability open self.
+ if ( m_spawnflags & SF_FLOOR_TURRET_AUTOACTIVATE )
+ {
+ m_bAutoStart = true;
+ }
+
+ SetThink( &CNPC_FloorTurret::Deploy );
+ SetNextThink( gpGlobals->curtime + 0.05f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Retire the turret until enabled again
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::Disable( void )
+{
+ //This turret is on its side, it can't function
+ if ( OnSide() || ( IsAlive() == false ) || m_bSelfDestructing )
+ return;
+
+ if ( m_bEnabled )
+ {
+ m_bEnabled = false;
+ m_bAutoStart = false;
+
+ SetEnemy( NULL );
+ SetThink( &CNPC_FloorTurret::Retire );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ }
+ else
+ SetThink( &CNPC_FloorTurret::DisabledThink );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Toggle the turret's state via input function
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::InputToggle( inputdata_t &inputdata )
+{
+ Toggle();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::InputEnable( inputdata_t &inputdata )
+{
+ Enable();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::InputDisable( inputdata_t &inputdata )
+{
+ Disable();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Stops the turret from firing live rounds (still attempts to though)
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::InputDepleteAmmo( inputdata_t &inputdata )
+{
+ AddSpawnFlags( SF_FLOOR_TURRET_OUT_OF_AMMO );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows the turret to fire live rounds again
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::InputRestoreAmmo( inputdata_t &inputdata )
+{
+ RemoveSpawnFlags( SF_FLOOR_TURRET_OUT_OF_AMMO );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allow players and npc's to turn the turret on and off
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ switch( useType )
+ {
+ case USE_OFF:
+ Disable();
+ break;
+ case USE_ON:
+ Enable();
+ break;
+ case USE_SET:
+ break;
+ case USE_TOGGLE:
+ Toggle( );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Reduce physics forces from the front
+//-----------------------------------------------------------------------------
+int CNPC_FloorTurret::VPhysicsTakeDamage( const CTakeDamageInfo &info )
+{
+ bool bShouldIgnoreFromFront = false;
+
+ // Ignore crossbow bolts hitting us from the front
+ bShouldIgnoreFromFront = ( info.GetDamageType() & DMG_BULLET ) != 0;
+
+ // Ignore bullets from the front
+ if ( !bShouldIgnoreFromFront )
+ {
+ bShouldIgnoreFromFront = FClassnameIs( info.GetInflictor(), "crossbow_bolt" );
+ }
+
+ // Did it hit us on the front?
+ if ( bShouldIgnoreFromFront )
+ {
+ Vector vecForward;
+ GetVectors( &vecForward, NULL, NULL );
+ Vector vecForce = info.GetDamageForce();
+ VectorNormalize( vecForce );
+ float flDot = DotProduct( vecForward, vecForce );
+ if ( flDot < -0.85 )
+ return 0;
+ }
+
+ return BaseClass::VPhysicsTakeDamage( info );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &info -
+//-----------------------------------------------------------------------------
+int CNPC_FloorTurret::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ CTakeDamageInfo newInfo = info;
+
+ if ( info.GetDamageType() & (DMG_SLASH|DMG_CLUB) )
+ {
+ // Take extra force from melee hits
+ newInfo.ScaleDamageForce( 2.0f );
+
+ // Disable our upright controller for some time
+ if ( m_pMotionController != NULL )
+ {
+ m_pMotionController->Suspend( 2.0f );
+ }
+ }
+ else if ( info.GetDamageType() & DMG_BLAST )
+ {
+ newInfo.ScaleDamageForce( 2.0f );
+ }
+ else if ( (info.GetDamageType() & DMG_BULLET) && !(info.GetDamageType() & DMG_BUCKSHOT) )
+ {
+ // Bullets, but not buckshot, do extra push
+ newInfo.ScaleDamageForce( 2.5f );
+ }
+
+ // Manually apply vphysics because AI_BaseNPC takedamage doesn't call back to CBaseEntity OnTakeDamage
+ VPhysicsTakeDamage( newInfo );
+
+ // Bump up our search time
+ m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_MAX_WAIT;
+
+ // Start looking around in anger if we were idle
+ if ( IsAlive() && m_bEnabled && m_bAutoStart && GetActivity() == ACT_FLOOR_TURRET_CLOSED_IDLE && m_bSelfDestructing == false )
+ {
+ SetThink( &CNPC_FloorTurret::Deploy );
+ }
+
+ return BaseClass::OnTakeDamage( newInfo );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::SpinUp( void )
+{
+}
+
+#define FLOOR_TURRET_MIN_SPIN_DOWN 1.0f
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : const QAngle
+//-----------------------------------------------------------------------------
+QAngle CNPC_FloorTurret::PreferredCarryAngles( void )
+{
+ // FIXME: Embed this into the class
+ static QAngle g_prefAngles;
+
+ Vector vecUserForward;
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+ pPlayer->EyeVectors( &vecUserForward );
+
+ // If we're looking up, then face directly forward
+ if ( vecUserForward.z >= 0.0f )
+ return vec3_angle;
+
+ // Otherwise, stay "upright"
+ g_prefAngles.Init();
+ g_prefAngles.x = -pPlayer->EyeAngles().x;
+
+ return g_prefAngles;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::SpinDown( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pVictim -
+// Output : float
+//-----------------------------------------------------------------------------
+float CNPC_FloorTurret::GetAttackDamageScale( CBaseEntity *pVictim )
+{
+ CBaseCombatCharacter *pBCC = pVictim->MyCombatCharacterPointer();
+
+ // Do extra damage to antlions & combine
+ if ( pBCC )
+ {
+ if ( pBCC->Classify() == CLASS_ANTLION )
+ return 2.0;
+
+ if ( pBCC->Classify() == CLASS_COMBINE )
+ return 2.0;
+ }
+
+ return BaseClass::GetAttackDamageScale( pVictim );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+Vector CNPC_FloorTurret::GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget )
+{
+ WeaponProficiency_t weaponProficiency = WEAPON_PROFICIENCY_AVERAGE;
+
+ // Switch our weapon proficiency based upon our target
+ if ( pTarget )
+ {
+ if ( pTarget->Classify() == CLASS_PLAYER || pTarget->Classify() == CLASS_ANTLION || pTarget->Classify() == CLASS_ZOMBIE )
+ {
+ // Make me much more accurate
+ weaponProficiency = WEAPON_PROFICIENCY_PERFECT;
+ }
+ else if ( pTarget->Classify() == CLASS_COMBINE )
+ {
+ // Make me more accurate
+ weaponProficiency = WEAPON_PROFICIENCY_VERY_GOOD;
+ }
+ }
+
+ return VECTOR_CONE_10DEGREES * ((CBaseHLCombatWeapon::GetDefaultProficiencyValues())[ weaponProficiency ].spreadscale);
+}
+
+//------------------------------------------------------------------------------
+// Do we have a physics attacker?
+//------------------------------------------------------------------------------
+CBasePlayer *CNPC_FloorTurret::HasPhysicsAttacker( float dt )
+{
+ // If the player is holding me now, or I've been recently thrown
+ // then return a pointer to that player
+ if ( IsHeldByPhyscannon( ) || (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime) )
+ {
+ return m_hPhysicsAttacker;
+ }
+ return NULL;
+}
+//-----------------------------------------------------------------------------
+// Purpose: Draw any debug text overlays
+// Output : Current text offset from the top
+//-----------------------------------------------------------------------------
+int CNPC_FloorTurret::DrawDebugTextOverlays( void )
+{
+ int text_offset = BaseClass::DrawDebugTextOverlays();
+
+ if (m_debugOverlays & OVERLAY_TEXT_BIT)
+ {
+ if (VPhysicsGetObject())
+ {
+ char tempstr[512];
+ Q_snprintf(tempstr, sizeof(tempstr),"Mass: %.2f kg / %.2f lb (%s)", VPhysicsGetObject()->GetMass(), kg2lbs(VPhysicsGetObject()->GetMass()), GetMassEquivalent(VPhysicsGetObject()->GetMass()));
+ EntityText( text_offset, tempstr, 0);
+ text_offset++;
+ }
+ }
+
+ return text_offset;
+}
+
+void CNPC_FloorTurret::UpdateMuzzleMatrix()
+{
+ if ( gpGlobals->tickcount != m_muzzleToWorldTick )
+ {
+ m_muzzleToWorldTick = gpGlobals->tickcount;
+ GetAttachment( m_iMuzzleAttachment, m_muzzleToWorld );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: We override this code because otherwise we start to move into the
+// tricky realm of player avoidance. Since we don't go through the
+// normal NPC thinking but we ARE an NPC (...) we miss a bunch of
+// book keeping. This means we can become invisible and then never
+// reappear.
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::PlayerPenetratingVPhysics( void )
+{
+ // We don't care!
+}
+
+#define SELF_DESTRUCT_DURATION 4.0f
+#define SELF_DESTRUCT_BEEP_MIN_DELAY 0.1f
+#define SELF_DESTRUCT_BEEP_MAX_DELAY 0.75f
+#define SELF_DESTRUCT_BEEP_MIN_PITCH 100.0f
+#define SELF_DESTRUCT_BEEP_MAX_PITCH 225.0f
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::BreakThink( void )
+{
+ Vector vecUp;
+ GetVectors( NULL, NULL, &vecUp );
+ Vector vecOrigin = WorldSpaceCenter() + ( vecUp * 12.0f );
+
+ // Our effect
+#ifdef HL2_EPISODIC
+ DispatchParticleEffect( "explosion_turret_break", vecOrigin, GetAbsAngles() );
+#endif // HL2_EPISODIC
+
+ // K-boom
+ RadiusDamage( CTakeDamageInfo( this, this, 15.0f, DMG_BLAST ), vecOrigin, (10*12), CLASS_NONE, this );
+
+ EmitSound( "NPC_FloorTurret.Destruct" );
+
+ breakablepropparams_t params( GetAbsOrigin(), GetAbsAngles(), vec3_origin, RandomAngularImpulse( -800.0f, 800.0f ) );
+ params.impactEnergyScale = 1.0f;
+ params.defCollisionGroup = COLLISION_GROUP_INTERACTIVE;
+
+ // no damage/damage force? set a burst of 100 for some movement
+ params.defBurstScale = 100;
+ PropBreakableCreateAll( GetModelIndex(), VPhysicsGetObject(), params, this, -1, true );
+
+ // Throw out some small chunks too obscure the explosion even more
+ CPVSFilter filter( vecOrigin );
+ for ( int i = 0; i < 4; i++ )
+ {
+ Vector gibVelocity = RandomVector(-100,100);
+ int iModelIndex = modelinfo->GetModelIndex( g_PropDataSystem.GetRandomChunkModel( "MetalChunks" ) );
+ te->BreakModel( filter, 0.0, vecOrigin, GetAbsAngles(), Vector(40,40,40), gibVelocity, iModelIndex, 150, 4, 2.5, BREAK_METAL );
+ }
+
+ // We're done!
+ UTIL_Remove( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The countdown to destruction!
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::SelfDestructThink( void )
+{
+ // Continue to animate
+ PreThink( TURRET_SELF_DESTRUCTING );
+
+ // If we're done, explode
+ if ( ( gpGlobals->curtime - m_flDestructStartTime ) >= SELF_DESTRUCT_DURATION )
+ {
+ SetThink( &CNPC_FloorTurret::BreakThink );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ UTIL_Remove( m_hFizzleEffect );
+ m_hFizzleEffect = NULL;
+ return;
+ }
+
+ // Find out where we are in the cycle of our destruction
+ float flDestructPerc = clamp( ( gpGlobals->curtime - m_flDestructStartTime ) / SELF_DESTRUCT_DURATION, 0.0f, 1.0f );
+
+ // Figure out when our next beep should occur
+ float flBeepTime = SELF_DESTRUCT_BEEP_MAX_DELAY + ( ( SELF_DESTRUCT_BEEP_MIN_DELAY - SELF_DESTRUCT_BEEP_MAX_DELAY ) * flDestructPerc );
+
+ // If it's time to beep again, do so
+ if ( gpGlobals->curtime > ( m_flPingTime + flBeepTime ) )
+ {
+ // Figure out what our beep pitch will be
+ float flBeepPitch = SELF_DESTRUCT_BEEP_MIN_PITCH + ( ( SELF_DESTRUCT_BEEP_MAX_PITCH - SELF_DESTRUCT_BEEP_MIN_PITCH ) * flDestructPerc );
+
+ StopSound( "NPC_FloorTurret.AlarmPing" );
+
+ // Play the beep
+ CPASAttenuationFilter filter( this, "NPC_FloorTurret.AlarmPing" );
+ EmitSound_t params;
+ params.m_pSoundName = "NPC_FloorTurret.AlarmPing";
+ params.m_nPitch = floor( flBeepPitch );
+ params.m_nFlags = SND_CHANGE_PITCH;
+ EmitSound( filter, entindex(), params );
+
+ // Flash our eye
+ SetEyeState( TURRET_EYE_ALARM );
+
+ // Save this as the last time we pinged
+ m_flPingTime = gpGlobals->curtime;
+
+ // Randomly twitch
+ m_vecGoalAngles.x = GetAbsAngles().x + random->RandomFloat( -60*flDestructPerc, 60*flDestructPerc );
+ m_vecGoalAngles.y = GetAbsAngles().y + random->RandomFloat( -60*flDestructPerc, 60*flDestructPerc );
+ }
+
+ UpdateFacing();
+
+ // Think again!
+ SetNextThink( gpGlobals->curtime + 0.05f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Make us explode
+//-----------------------------------------------------------------------------
+void CNPC_FloorTurret::InputSelfDestruct( inputdata_t &inputdata )
+{
+ // Ka-boom!
+ m_flDestructStartTime = gpGlobals->curtime;
+ m_flPingTime = gpGlobals->curtime;
+ m_bSelfDestructing = true;
+
+ SetThink( &CNPC_FloorTurret::SelfDestructThink );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ // Create the dust effect in place
+ m_hFizzleEffect = (CParticleSystem *) CreateEntityByName( "info_particle_system" );
+ if ( m_hFizzleEffect != NULL )
+ {
+ Vector vecUp;
+ GetVectors( NULL, NULL, &vecUp );
+
+ // Setup our basic parameters
+ m_hFizzleEffect->KeyValue( "start_active", "1" );
+ m_hFizzleEffect->KeyValue( "effect_name", "explosion_turret_fizzle" );
+ m_hFizzleEffect->SetParent( this );
+ m_hFizzleEffect->SetAbsOrigin( WorldSpaceCenter() + ( vecUp * 12.0f ) );
+ DispatchSpawn( m_hFizzleEffect );
+ m_hFizzleEffect->Activate();
+ }
+}
+
+//
+// Tip controller
+//
+
+LINK_ENTITY_TO_CLASS( floorturret_tipcontroller, CTurretTipController );
+
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CTurretTipController )
+
+ DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flSuspendTime, FIELD_TIME ),
+ DEFINE_FIELD( m_worldGoalAxis, FIELD_VECTOR ),
+ DEFINE_FIELD( m_localTestAxis, FIELD_VECTOR ),
+ DEFINE_PHYSPTR( m_pController ),
+ DEFINE_FIELD( m_angularLimit, FIELD_FLOAT ),
+ DEFINE_FIELD( m_pParentTurret, FIELD_CLASSPTR ),
+
+END_DATADESC()
+
+
+
+CTurretTipController::~CTurretTipController()
+{
+ if ( m_pController )
+ {
+ physenv->DestroyMotionController( m_pController );
+ m_pController = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTurretTipController::Spawn( void )
+{
+ m_bEnabled = true;
+
+ // align the object's local Z axis
+ m_localTestAxis.Init( 0, 0, 1 );
+ // with the world's Z axis
+ m_worldGoalAxis.Init( 0, 0, 1 );
+
+ // recover from up to 25 degrees / sec angular velocity
+ m_angularLimit = 25;
+ m_flSuspendTime = 0;
+
+ SetMoveType( MOVETYPE_NONE );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTurretTipController::Activate( void )
+{
+ BaseClass::Activate();
+
+ if ( m_pParentTurret == NULL )
+ {
+ UTIL_Remove(this);
+ return;
+ }
+
+ IPhysicsObject *pPhys = m_pParentTurret->VPhysicsGetObject();
+
+ if ( pPhys == NULL )
+ {
+ UTIL_Remove(this);
+ return;
+ }
+
+ //Setup the motion controller
+ if ( !m_pController )
+ {
+ m_pController = physenv->CreateMotionController( (IMotionEvent *)this );
+ m_pController->AttachObject( pPhys, true );
+ }
+ else
+ {
+ m_pController->SetEventHandler( this );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Actual simulation for tip controller
+//-----------------------------------------------------------------------------
+IMotionEvent::simresult_e CTurretTipController::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
+{
+ if ( Enabled() == false )
+ return SIM_NOTHING;
+
+ // Don't simulate if we're being carried by the player
+ if ( m_pParentTurret->IsBeingCarriedByPlayer() )
+ return SIM_NOTHING;
+
+ float flAngularLimit = m_angularLimit;
+
+ // If we were just dropped by a friendly player, stabilise better
+ if ( m_pParentTurret->WasJustDroppedByPlayer() )
+ {
+ // Increase the controller strength a little
+ flAngularLimit += 20;
+ }
+ else
+ {
+ // If the turret has some vertical velocity, don't simulate
+ Vector vecVelocity;
+ AngularImpulse angImpulse;
+ pObject->GetVelocity( &vecVelocity, &angImpulse );
+ if ( (vecVelocity.LengthSqr() > CNPC_FloorTurret::fMaxTipControllerVelocity) || (angImpulse.LengthSqr() > CNPC_FloorTurret::fMaxTipControllerAngularVelocity) )
+ return SIM_NOTHING;
+ }
+
+ linear.Init();
+
+ AngularImpulse angVel;
+ pObject->GetVelocity( NULL, &angVel );
+
+ matrix3x4_t matrix;
+ // get the object's local to world transform
+ pObject->GetPositionMatrix( &matrix );
+
+ // Get the alignment axis in object space
+ Vector currentLocalTargetAxis;
+ VectorIRotate( m_worldGoalAxis, matrix, currentLocalTargetAxis );
+
+ float invDeltaTime = (1/deltaTime);
+ angular = ComputeRotSpeedToAlignAxes( m_localTestAxis, currentLocalTargetAxis, angVel, 1.0, invDeltaTime * invDeltaTime, flAngularLimit * invDeltaTime );
+
+ return SIM_LOCAL_ACCELERATION;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTurretTipController::Enable( bool state )
+{
+ m_bEnabled = state;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : time -
+//-----------------------------------------------------------------------------
+void CTurretTipController::Suspend( float time )
+{
+ m_flSuspendTime = gpGlobals->curtime + time;
+}
+
+
+float CTurretTipController::SuspendedTill( void )
+{
+ return m_flSuspendTime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CTurretTipController::Enabled( void )
+{
+ if ( m_flSuspendTime > gpGlobals->curtime )
+ return false;
+
+ return m_bEnabled;
+}
+
+//-----------------------------------------------------------------------------
+//
+// Schedules
+//
+//-----------------------------------------------------------------------------
+AI_BEGIN_CUSTOM_NPC( npc_turret_floor, CNPC_FloorTurret )
+
+ DECLARE_INTERACTION( g_interactionTurretStillStanding );
+
+AI_END_CUSTOM_NPC()