You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1127 lines
30 KiB
1127 lines
30 KiB
//========= 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 ); |
|
} |