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