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.
1190 lines
26 KiB
1190 lines
26 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// TODO: |
|
// Take advantage of new NPC fields like GetEnemy() and get rid of that OFFSET() stuff |
|
// Revisit enemy validation stuff, maybe it's not necessary with the newest NPC code |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "Sprite.h" |
|
#include "basecombatweapon.h" |
|
#include "ai_basenpc.h" |
|
#include "AI_Senses.h" |
|
#include "AI_Memory.h" |
|
#include "gamerules.h" |
|
#include "ammodef.h" |
|
#include "ndebugoverlay.h" |
|
#include "IEffects.h" |
|
#include "vstdlib/random.h" |
|
#include "engine/IEngineSound.h" |
|
|
|
class CSprite; |
|
|
|
#define TURRET_RANGE (100 * 12) |
|
#define TURRET_SPREAD VECTOR_CONE_5DEGREES |
|
#define TURRET_TURNRATE 360 // max angles per second |
|
#define TURRET_MAXWAIT 15 // seconds turret will stay active w/o a target |
|
#define TURRET_MAXSPIN 5 // seconds turret barrel will spin w/o a target |
|
#define TURRET_MACHINE_VOLUME 0.5 |
|
|
|
#define TURRET_BC_YAW "aim_yaw" |
|
#define TURRET_BC_PITCH "aim_pitch" |
|
|
|
#define TURRET_ORIENTATION_FLOOR 0 |
|
#define TURRET_ORIENTATION_CEILING 1 |
|
|
|
//========================================================= |
|
// private activities |
|
//========================================================= |
|
int ACT_TURRET_OPEN; |
|
int ACT_TURRET_CLOSE; |
|
int ACT_TURRET_OPEN_IDLE; |
|
int ACT_TURRET_CLOSED_IDLE; |
|
int ACT_TURRET_FIRE; |
|
int ACT_TURRET_RELOAD; |
|
|
|
// =============================================== |
|
// Private spawn flags (must be above (1<<15)) |
|
// =============================================== |
|
#define SF_NPC_TURRET_AUTOACTIVATE 0x00000020 |
|
#define SF_NPC_TURRET_STARTINACTIVE 0x00000040 |
|
|
|
extern short g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud |
|
|
|
ConVar sk_miniturret_health( "sk_miniturret_health","0"); |
|
ConVar sk_sentry_health( "sk_sentry_health","0"); |
|
ConVar sk_turret_health( "sk_turret_health","0"); |
|
|
|
class CBaseTurret : public CAI_BaseNPC |
|
{ |
|
DECLARE_CLASS( CBaseTurret, CAI_BaseNPC ); |
|
public: |
|
void Spawn(void); |
|
virtual void Precache(void); |
|
bool KeyValue( const char *szKeyName, const char *szValue ); |
|
//void TurretUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); |
|
|
|
virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr ); |
|
virtual int OnTakeDamage( const CTakeDamageInfo &info ); |
|
virtual Class_T Classify(void); |
|
|
|
int BloodColor( void ) { return DONT_BLEED; } |
|
bool Event_Gibbed( void ) { return FALSE; } // UNDONE: Throw turret gibs? |
|
|
|
Vector EyeOffset( Activity nActivity ); |
|
Vector EyePosition( void ); |
|
|
|
// Inputs |
|
void InputToggle( inputdata_t &inputdata ); |
|
|
|
// Think functions |
|
|
|
void ActiveThink(void); |
|
void SearchThink(void); |
|
void AutoSearchThink(void); |
|
void TurretDeath(void); |
|
|
|
void Deploy(void); |
|
void Retire(void); |
|
|
|
void Initialize(void); |
|
|
|
virtual void Ping(void); |
|
virtual void EyeOn(void); |
|
virtual void EyeOff(void); |
|
|
|
DECLARE_DATADESC(); |
|
|
|
// other functions |
|
int MoveTurret(void); |
|
virtual void Shoot(const Vector &vecSrc, const Vector &vecDirToEnemy) { }; |
|
|
|
CSprite *m_pEyeGlow; |
|
int m_eyeBrightness; |
|
|
|
int m_iDeployHeight; |
|
int m_iRetractHeight; |
|
int m_iMinPitch; |
|
|
|
int m_iBaseTurnRate; // angles per second |
|
float m_fTurnRate; // actual turn rate |
|
int m_iOn; |
|
int m_fBeserk; // Sometimes this bitch will just freak out |
|
int m_iAutoStart; // true if the turret auto deploys when a target |
|
// enters its range |
|
|
|
Vector m_vecLastSight; // Last seen position |
|
float m_flLastSight; // Last time we saw a target |
|
float m_flMaxWait; // Max time to search w/o a target |
|
|
|
// movement |
|
float m_flStartYaw; |
|
QAngle m_vecGoalAngles; |
|
|
|
int m_iAmmoType; |
|
|
|
float m_flPingTime; // Time until the next ping, used when searching |
|
float m_flDamageTime; // Time we last took damage. |
|
|
|
COutputEvent m_OnDeploy; |
|
COutputEvent m_OnRetire; |
|
|
|
// external |
|
//COutputEvent m_OnDamaged; |
|
//COutputEvent m_OnDeath; |
|
//COutputEvent m_OnHalfHealth; |
|
//COutputEHANDLE m_OnFoundEnemy; |
|
//COutputEvent m_OnLostEnemyLOS; |
|
//COutputEvent m_OnLostEnemy; |
|
//COutputEHANDLE m_OnFoundPlayer; |
|
//COutputEvent m_OnLostPlayerLOS; |
|
//COutputEvent m_OnLostPlayer; |
|
//COutputEvent m_OnHearWorld; |
|
//COutputEvent m_OnHearPlayer; |
|
//COutputEvent m_OnHearCombat; |
|
|
|
}; |
|
|
|
|
|
BEGIN_DATADESC( CBaseTurret ) |
|
|
|
DEFINE_FIELD( m_pEyeGlow, FIELD_CLASSPTR ), |
|
DEFINE_FIELD( m_eyeBrightness, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_iDeployHeight, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_iRetractHeight, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_iMinPitch, FIELD_INTEGER ), |
|
|
|
DEFINE_FIELD( m_iBaseTurnRate, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_fTurnRate, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_iOn, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_fBeserk, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_iAutoStart, FIELD_INTEGER ), |
|
|
|
DEFINE_FIELD( m_vecLastSight, FIELD_POSITION_VECTOR ), |
|
DEFINE_FIELD( m_flLastSight, FIELD_TIME ), |
|
DEFINE_FIELD( m_flMaxWait, FIELD_FLOAT ), |
|
|
|
DEFINE_FIELD( m_flStartYaw, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_vecGoalAngles, FIELD_VECTOR ), |
|
|
|
DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ), |
|
|
|
DEFINE_FIELD( m_flPingTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_flDamageTime, FIELD_TIME ), |
|
|
|
// Function pointers |
|
//DEFINE_USEFUNC( TurretUse ), |
|
DEFINE_THINKFUNC( ActiveThink ), |
|
DEFINE_THINKFUNC( SearchThink ), |
|
DEFINE_THINKFUNC( AutoSearchThink ), |
|
DEFINE_THINKFUNC( TurretDeath ), |
|
DEFINE_THINKFUNC( Deploy ), |
|
DEFINE_THINKFUNC( Retire ), |
|
DEFINE_THINKFUNC( Initialize ), |
|
|
|
// Inputs |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), |
|
|
|
// Outputs |
|
DEFINE_OUTPUT(m_OnDeploy, "OnDeploy"), |
|
DEFINE_OUTPUT(m_OnRetire, "OnRetire"), |
|
|
|
END_DATADESC() |
|
|
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
Vector CBaseTurret::EyeOffset( Activity nActivity ) |
|
{ |
|
return Vector( 0, 0, -20 ); |
|
} |
|
|
|
|
|
Vector CBaseTurret::EyePosition( void ) |
|
{ |
|
Vector vecOrigin; |
|
QAngle vecAngles; |
|
|
|
GetAttachment( "eyes", vecOrigin, vecAngles ); |
|
return vecOrigin; |
|
} |
|
|
|
bool CBaseTurret::KeyValue( const char *szKeyName, const char *szValue ) |
|
{ |
|
if (FStrEq(szKeyName, "maxsleep")) |
|
{ |
|
m_flMaxWait = atof(szValue); |
|
} |
|
else if (FStrEq(szKeyName, "turnrate")) |
|
{ |
|
m_iBaseTurnRate = atoi(szValue); |
|
} |
|
else if (FStrEq(szKeyName, "style") || |
|
FStrEq(szKeyName, "height") || |
|
FStrEq(szKeyName, "value1") || |
|
FStrEq(szKeyName, "value2") || |
|
FStrEq(szKeyName, "value3")) |
|
{ |
|
} |
|
else |
|
return BaseClass::KeyValue( szKeyName, szValue ); |
|
|
|
return true; |
|
} |
|
|
|
|
|
void CBaseTurret::Spawn() |
|
{ |
|
Precache( ); |
|
SetNextThink( gpGlobals->curtime + 1 ); |
|
SetMoveType( MOVETYPE_FLY ); |
|
m_nSequence = 0; |
|
m_flCycle = 0; |
|
SetSolid( SOLID_SLIDEBOX ); |
|
m_takedamage = DAMAGE_YES; |
|
AddFlag( FL_AIMTARGET ); |
|
|
|
m_iAmmoType = g_pGameRules->GetAmmoDef()->Index("SMG1"); |
|
|
|
AddFlag( FL_NPC ); |
|
|
|
if (( m_spawnflags & SF_NPC_TURRET_AUTOACTIVATE ) && !( m_spawnflags & SF_NPC_TURRET_STARTINACTIVE )) |
|
{ |
|
m_iAutoStart = true; |
|
} |
|
|
|
ResetSequenceInfo( ); |
|
|
|
SetPoseParameter( TURRET_BC_YAW, 0 ); |
|
SetPoseParameter( TURRET_BC_PITCH, 0 ); |
|
|
|
// Activities |
|
ADD_CUSTOM_ACTIVITY( CBaseTurret, ACT_TURRET_OPEN ); |
|
ADD_CUSTOM_ACTIVITY( CBaseTurret, ACT_TURRET_CLOSE ); |
|
ADD_CUSTOM_ACTIVITY( CBaseTurret, ACT_TURRET_CLOSED_IDLE ); |
|
ADD_CUSTOM_ACTIVITY( CBase`matTurret, ACT_TURRET_OPEN_IDLE ); |
|
ADD_CUSTOM_ACTIVITY( CBaseTurret, ACT_TURRET_FIRE ); |
|
ADD_CUSTOM_ACTIVITY( CBaseTurret, ACT_TURRET_RELOAD ); |
|
} |
|
|
|
|
|
void CBaseTurret::Precache( ) |
|
{ |
|
BaseClass::Precache(); |
|
|
|
PrecacheScriptSound( "NPC_Turret.Ping" ); |
|
PrecacheScriptSound( "NPC_Turret.Deploy" ); |
|
PrecacheScriptSound( "NPC_Turret.Retire" ); |
|
PrecacheScriptSound( "NPC_Turret.Alert" ); |
|
PrecacheScriptSound( "NPC_Turret.Die" ); |
|
} |
|
|
|
void CBaseTurret::Initialize(void) |
|
{ |
|
m_iOn = 0; |
|
m_fBeserk = 0; |
|
|
|
SetPoseParameter( TURRET_BC_YAW, 0 ); |
|
SetPoseParameter( TURRET_BC_PITCH, 0 ); |
|
|
|
if (m_iBaseTurnRate == 0) m_iBaseTurnRate = TURRET_TURNRATE; |
|
if (m_flMaxWait == 0) m_flMaxWait = TURRET_MAXWAIT; |
|
|
|
m_vecGoalAngles = GetAngles(); |
|
|
|
if (m_iAutoStart) |
|
{ |
|
m_flLastSight = gpGlobals->curtime + m_flMaxWait; |
|
SetThink(AutoSearchThink); |
|
SetNextThink( gpGlobals->curtime + .1 ); |
|
} |
|
else |
|
SetThink(SUB_DoNothing); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Input handler for toggling the turret on/off. |
|
//----------------------------------------------------------------------------- |
|
void CBaseTurret::InputToggle( inputdata_t &inputdata ) |
|
{ |
|
//if ( !ShouldToggle( useType, m_iOn ) ) |
|
// return; |
|
|
|
if (m_iOn) |
|
{ |
|
SetEnemy( NULL ); |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
m_iAutoStart = FALSE;// switching off a turret disables autostart |
|
//!!!! this should spin down first!!BUGBUG |
|
SetThink(Retire); |
|
} |
|
else |
|
{ |
|
SetNextThink( gpGlobals->curtime + 0.1f ); // turn on delay |
|
|
|
// if the turret is flagged as an autoactivate turret, re-enable its ability open self. |
|
if ( m_spawnflags & SF_NPC_TURRET_AUTOACTIVATE ) |
|
{ |
|
m_iAutoStart = TRUE; |
|
} |
|
|
|
SetThink(Deploy); |
|
} |
|
} |
|
|
|
|
|
void CBaseTurret::Ping( void ) |
|
{ |
|
// make the pinging noise every second while searching |
|
if (m_flPingTime == 0) |
|
m_flPingTime = gpGlobals->curtime + 1; |
|
else if (m_flPingTime <= gpGlobals->curtime) |
|
{ |
|
m_flPingTime = gpGlobals->curtime + 1; |
|
EmitSound( "NPC_Turret.Ping" ); |
|
EyeOn( ); |
|
} |
|
else if (m_eyeBrightness > 0) |
|
{ |
|
EyeOff( ); |
|
} |
|
} |
|
|
|
|
|
void CBaseTurret::EyeOn( ) |
|
{ |
|
if (m_pEyeGlow) |
|
{ |
|
if (m_eyeBrightness != 255) |
|
{ |
|
m_eyeBrightness = 255; |
|
} |
|
m_pEyeGlow->SetBrightness( m_eyeBrightness ); |
|
} |
|
} |
|
|
|
|
|
void CBaseTurret::EyeOff( ) |
|
{ |
|
if (m_pEyeGlow) |
|
{ |
|
if (m_eyeBrightness > 0) |
|
{ |
|
m_eyeBrightness = MAX( 0, m_eyeBrightness - 30 ); |
|
m_pEyeGlow->SetBrightness( m_eyeBrightness ); |
|
} |
|
} |
|
} |
|
|
|
|
|
void CBaseTurret::ActiveThink(void) |
|
{ |
|
int fAttack = 0; |
|
Vector vecDirToEnemy; |
|
|
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
StudioFrameAdvance( ); |
|
|
|
if ((!m_iOn) || (GetEnemy() == NULL)) |
|
{ |
|
SetEnemy( NULL ); |
|
m_flLastSight = gpGlobals->curtime + m_flMaxWait; |
|
SetThink(SearchThink); |
|
return; |
|
} |
|
|
|
// if it's dead, look for something new |
|
if ( !GetEnemy()->IsAlive() ) |
|
{ |
|
if (!m_flLastSight) |
|
{ |
|
m_flLastSight = gpGlobals->curtime + 0.5; // continue-shooting timeout |
|
} |
|
else |
|
{ |
|
if (gpGlobals->curtime > m_flLastSight) |
|
{ |
|
SetEnemy( NULL ); |
|
m_flLastSight = gpGlobals->curtime + m_flMaxWait; |
|
SetThink(SearchThink); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
Vector vecMid = EyePosition( ); |
|
Vector vecMidEnemy = GetEnemy()->BodyTarget(vecMid); |
|
|
|
// g_pEffects->Sparks( vecMid ); |
|
// g_pEffects->Sparks( vecMidEnemy ); |
|
|
|
// Look for our current enemy |
|
//int fEnemyVisible = FBoxVisible( this, GetEnemy(), vecMidEnemy ); |
|
int fEnemyVisible = FInViewCone( GetEnemy() ) && FVisible( GetEnemy() ); |
|
|
|
vecDirToEnemy = vecMidEnemy - vecMid; // calculate dir and dist to enemy |
|
// NDebugOverlay::Line( vecMid, vecMidEnemy, 0, 255, 0, false, 0.1 ); |
|
|
|
float flDistToEnemy = vecDirToEnemy.Length(); |
|
|
|
QAngle vecAnglesToEnemy; |
|
VectorNormalize( vecDirToEnemy ); |
|
VectorAngles( vecDirToEnemy, vecAnglesToEnemy ); |
|
|
|
// Current enmey is not visible. |
|
if (!fEnemyVisible || (flDistToEnemy > TURRET_RANGE)) |
|
{ |
|
// DevMsg( "lost you\n" ); |
|
|
|
if (!m_flLastSight) |
|
{ |
|
m_flLastSight = gpGlobals->curtime + 0.5; |
|
} |
|
else |
|
{ |
|
// Should we look for a new target? |
|
if (gpGlobals->curtime > m_flLastSight) |
|
{ |
|
ClearEnemyMemory(); |
|
SetEnemy( NULL ); |
|
m_flLastSight = gpGlobals->curtime + m_flMaxWait; |
|
SetThink(SearchThink); |
|
return; |
|
} |
|
} |
|
fEnemyVisible = 0; |
|
} |
|
else |
|
{ |
|
m_vecLastSight = vecMidEnemy; |
|
} |
|
|
|
Vector vecLOS = vecDirToEnemy; //vecMid - m_vecLastSight; |
|
VectorNormalize( vecLOS ); |
|
|
|
Vector vecMuzzle, vecMuzzleDir; |
|
QAngle vecMuzzleAng; |
|
GetAttachment( "eyes", vecMuzzle, vecMuzzleAng ); |
|
|
|
AngleVectors( vecMuzzleAng, &vecMuzzleDir ); |
|
|
|
// Is the Gun looking at the target |
|
if (DotProduct(vecLOS, vecMuzzleDir) <= 0.9848) // 10 degree slop |
|
{ |
|
fAttack = FALSE; |
|
} |
|
else |
|
{ |
|
fAttack = TRUE; |
|
} |
|
|
|
// fire the gun |
|
if (fAttack || m_fBeserk) |
|
{ |
|
m_Activity = ACT_RESET; |
|
SetActivity( (Activity)ACT_TURRET_FIRE ); |
|
Shoot(vecMuzzle, vecMuzzleDir ); |
|
} |
|
else |
|
{ |
|
SetActivity( (Activity)ACT_TURRET_OPEN_IDLE ); |
|
} |
|
|
|
//move the gun |
|
if (m_fBeserk) |
|
{ |
|
// DevMsg( "berserk" ); |
|
|
|
if (random->RandomInt(0,9) == 0) |
|
{ |
|
m_vecGoalAngles.y = random->RandomFloat(-180,180); |
|
m_vecGoalAngles.x = random->RandomFloat(-90,90); |
|
OnTakeDamage( CTakeDamageInfo( this, this, 1, DMG_GENERIC ) ); // don't beserk forever |
|
return; |
|
} |
|
} |
|
else if (fEnemyVisible) |
|
{ |
|
// DevMsg( "->[%.2f]\n", vec.x); |
|
m_vecGoalAngles.y = vecAnglesToEnemy.y; |
|
m_vecGoalAngles.x = vecAnglesToEnemy.x; |
|
|
|
} |
|
|
|
MoveTurret(); |
|
} |
|
|
|
|
|
void CBaseTurret::Deploy(void) |
|
{ |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
StudioFrameAdvance( ); |
|
|
|
if ( m_Activity != ACT_TURRET_OPEN ) |
|
{ |
|
m_iOn = 1; |
|
SetActivity( (Activity)ACT_TURRET_OPEN ); |
|
EmitSound( "NPC_Turret.Deploy" ); |
|
|
|
m_OnDeploy.FireOutput(NULL, this); |
|
} |
|
|
|
if (m_fSequenceFinished) |
|
{ |
|
Vector curmins, curmaxs; |
|
curmins = WorldAlignMins(); |
|
curmaxs = WorldAlignMaxs(); |
|
|
|
curmaxs.z = m_iDeployHeight; |
|
curmins.z = -m_iDeployHeight; |
|
|
|
SetCollisionBounds( curmins, curmaxs ); |
|
|
|
Relink(); |
|
|
|
SetActivity( (Activity)ACT_TURRET_OPEN_IDLE ); |
|
|
|
m_flPlaybackRate = 0; |
|
SetThink(SearchThink); |
|
} |
|
|
|
m_flLastSight = gpGlobals->curtime + m_flMaxWait; |
|
} |
|
|
|
void CBaseTurret::Retire(void) |
|
{ |
|
// make the turret level |
|
m_vecGoalAngles = GetAngles( ); |
|
|
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
|
|
StudioFrameAdvance( ); |
|
|
|
EyeOff( ); |
|
|
|
if ( m_Activity != ACT_TURRET_CLOSE ) |
|
{ |
|
SetActivity( (Activity)ACT_TURRET_OPEN_IDLE ); |
|
|
|
if (!MoveTurret()) |
|
{ |
|
SetActivity( (Activity)ACT_TURRET_CLOSE ); |
|
EmitSound( "NPC_Turret.Retire" ); |
|
|
|
m_OnRetire.FireOutput(NULL, this); |
|
} |
|
} |
|
else if (m_fSequenceFinished) |
|
{ |
|
m_iOn = 0; |
|
m_flLastSight = 0; |
|
|
|
SetActivity( (Activity)ACT_TURRET_CLOSED_IDLE ); |
|
|
|
Vector curmins, curmaxs; |
|
curmins = WorldAlignMins(); |
|
curmaxs = WorldAlignMaxs(); |
|
|
|
curmaxs.z = m_iRetractHeight; |
|
curmins.z = -m_iRetractHeight; |
|
|
|
SetCollisionBounds( curmins, curmaxs ); |
|
Relink(); |
|
|
|
if (m_iAutoStart) |
|
{ |
|
SetThink(AutoSearchThink); |
|
SetNextThink( gpGlobals->curtime + .1 ); |
|
} |
|
else |
|
{ |
|
SetThink(SUB_DoNothing); |
|
} |
|
} |
|
} |
|
|
|
|
|
// |
|
// This search function will sit with the turret deployed and look for a new target. |
|
// After a set amount of time, the barrel will spin down. After m_flMaxWait, the turret will |
|
// retact. |
|
// |
|
void CBaseTurret::SearchThink(void) |
|
{ |
|
// ensure rethink |
|
SetActivity( (Activity)ACT_TURRET_OPEN_IDLE ); |
|
|
|
StudioFrameAdvance( ); |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
|
|
Ping( ); |
|
|
|
// If we have a target and we're still healthy |
|
if (GetEnemy() != NULL) |
|
{ |
|
if (!GetEnemy()->IsAlive() ) |
|
SetEnemy( NULL );// Dead enemy forces a search for new one |
|
} |
|
|
|
// Acquire Target |
|
if (GetEnemy() == NULL) |
|
{ |
|
GetSenses()->Look(TURRET_RANGE); |
|
SetEnemy( BestEnemy() ); |
|
} |
|
|
|
// If we've found a target, spin up the barrel and start to attack |
|
if (GetEnemy() != NULL) |
|
{ |
|
m_flLastSight = 0; |
|
SetThink(ActiveThink); |
|
} |
|
else |
|
{ |
|
// Are we out of time, do we need to retract? |
|
if (gpGlobals->curtime > m_flLastSight) |
|
{ |
|
//Before we retrace, make sure that we are spun down. |
|
m_flLastSight = 0; |
|
SetThink(Retire); |
|
} |
|
|
|
// generic hunt for new victims |
|
m_vecGoalAngles.y = (m_vecGoalAngles.y + 0.1 * m_iBaseTurnRate); |
|
if (m_vecGoalAngles.y >= 360) |
|
m_vecGoalAngles.y -= 360; |
|
|
|
MoveTurret(); |
|
} |
|
} |
|
|
|
|
|
// |
|
// This think function will deploy the turret when something comes into range. This is for |
|
// automatically activated turrets. |
|
// |
|
void CBaseTurret::AutoSearchThink(void) |
|
{ |
|
// ensure rethink |
|
StudioFrameAdvance( ); |
|
SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.2, 0.3 ) ); |
|
|
|
// If we have a target and we're still healthy |
|
|
|
if (GetEnemy() != NULL) |
|
{ |
|
if (!GetEnemy()->IsAlive() ) |
|
SetEnemy( NULL );// Dead enemy forces a search for new one |
|
} |
|
|
|
// Acquire Target |
|
|
|
if (GetEnemy() == NULL) |
|
{ |
|
GetSenses()->Look( TURRET_RANGE ); |
|
SetEnemy( BestEnemy() ); |
|
} |
|
|
|
if (GetEnemy() != NULL) |
|
{ |
|
SetThink(Deploy); |
|
EmitSound( "NPC_Turret.Alert" ); |
|
} |
|
} |
|
|
|
|
|
void CBaseTurret :: TurretDeath( void ) |
|
{ |
|
StudioFrameAdvance( ); |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
|
|
if (m_lifeState != LIFE_DEAD) |
|
{ |
|
m_lifeState = LIFE_DEAD; |
|
|
|
EmitSound( "NPC_Turret.Die" ); |
|
|
|
SetActivity( (Activity)ACT_TURRET_CLOSE ); |
|
|
|
EyeOn( ); |
|
} |
|
|
|
EyeOff( ); |
|
|
|
if (m_flDamageTime + random->RandomFloat( 0, 2 ) > gpGlobals->curtime) |
|
{ |
|
// lots of smoke |
|
Vector pos; |
|
CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &pos ); |
|
pos.z = CollisionProp()->GetCollisionOrigin().z; |
|
|
|
CBroadcastRecipientFilter filter; |
|
te->Smoke( filter, 0.0, &pos, |
|
g_sModelIndexSmoke, |
|
2.5, |
|
10 ); |
|
} |
|
|
|
if (m_flDamageTime + random->RandomFloat( 0, 5 ) > gpGlobals->curtime) |
|
{ |
|
Vector vecSrc; |
|
CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &vecSrc ); |
|
g_pEffects->Sparks( vecSrc ); |
|
} |
|
|
|
if (m_fSequenceFinished && !MoveTurret( ) && m_flDamageTime + 5 < gpGlobals->curtime) |
|
{ |
|
m_flPlaybackRate = 0; |
|
SetThink( NULL ); |
|
} |
|
} |
|
|
|
|
|
|
|
void CBaseTurret::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr ) |
|
{ |
|
CTakeDamageInfo info = inputInfo; |
|
|
|
if ( ptr->hitgroup == 10 ) |
|
{ |
|
// hit armor |
|
if ( m_flDamageTime != gpGlobals->curtime || (random->RandomInt(0,10) < 1) ) |
|
{ |
|
g_pEffects->Ricochet( ptr->endpos, (vecDir*-1.0f) ); |
|
m_flDamageTime = gpGlobals->curtime; |
|
} |
|
|
|
info.SetDamage( 0.1 );// don't hurt the NPC much, but allow bits_COND_LIGHT_DAMAGE to be generated |
|
} |
|
|
|
if ( !m_takedamage ) |
|
return; |
|
|
|
AddMultiDamage( info, this ); |
|
} |
|
|
|
|
|
int CBaseTurret::OnTakeDamage( const CTakeDamageInfo &inputInfo ) |
|
{ |
|
if ( !m_takedamage ) |
|
return 0; |
|
|
|
CTakeDamageInfo info = inputInfo; |
|
|
|
if (!m_iOn) |
|
info.ScaleDamage( 0.1f ); |
|
|
|
m_iHealth -= info.GetDamage(); |
|
if (m_iHealth <= 0) |
|
{ |
|
m_iHealth = 0; |
|
m_takedamage = DAMAGE_NO; |
|
m_flDamageTime = gpGlobals->curtime; |
|
|
|
RemoveFlag( FL_NPC ); // why are they set in the first place??? |
|
|
|
SetThink(TurretDeath); |
|
|
|
m_OnDamaged.FireOutput( info.GetInflictor(), this ); |
|
|
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
|
|
return 0; |
|
} |
|
|
|
if (m_iHealth <= 10) |
|
{ |
|
if (m_iOn && (1 || random->RandomInt(0, 0x7FFF) > 800)) |
|
{ |
|
m_fBeserk = 1; |
|
SetThink(SearchThink); |
|
} |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
|
|
int CBaseTurret::MoveTurret(void) |
|
{ |
|
bool bDidMove = false; |
|
int iPose; |
|
|
|
matrix3x4_t localToWorld; |
|
|
|
GetAttachment( LookupAttachment( "eyes" ), localToWorld ); |
|
|
|
Vector vecGoalDir; |
|
AngleVectors( m_vecGoalAngles, &vecGoalDir ); |
|
|
|
Vector vecGoalLocalDir; |
|
VectorIRotate( vecGoalDir, localToWorld, vecGoalLocalDir ); |
|
|
|
QAngle vecGoalLocalAngles; |
|
VectorAngles( vecGoalLocalDir, vecGoalLocalAngles ); |
|
|
|
float flDiff; |
|
QAngle vecNewAngles; |
|
|
|
// update pitch |
|
flDiff = AngleNormalize( UTIL_ApproachAngle( vecGoalLocalAngles.x, 0.0, 0.1 * m_iBaseTurnRate ) ); |
|
iPose = LookupPoseParameter( TURRET_BC_PITCH ); |
|
SetPoseParameter( iPose, GetPoseParameter( iPose ) + flDiff / 1.5 ); |
|
|
|
if (fabs(flDiff) > 0.1) |
|
{ |
|
bDidMove = true; |
|
} |
|
|
|
// update yaw, with acceleration |
|
#if 0 |
|
float flDist = AngleNormalize( vecGoalLocalAngles.y ); |
|
float flNewDist; |
|
float flNewTurnRate; |
|
|
|
ChangeDistance( 0.1, flDist, 0.0, m_fTurnRate, m_iBaseTurnRate, m_iBaseTurnRate * 4, flNewDist, flNewTurnRate ); |
|
m_fTurnRate = flNewTurnRate; |
|
flDiff = flDist - flNewDist; |
|
#else |
|
flDiff = AngleNormalize( UTIL_ApproachAngle( vecGoalLocalAngles.y, 0.0, 0.1 * m_iBaseTurnRate ) ); |
|
#endif |
|
|
|
iPose = LookupPoseParameter( TURRET_BC_YAW ); |
|
SetPoseParameter( iPose, GetPoseParameter( iPose ) + flDiff / 1.5 ); |
|
if (fabs(flDiff) > 0.1) |
|
{ |
|
bDidMove = true; |
|
} |
|
|
|
if (bDidMove) |
|
{ |
|
// DevMsg( "(%.2f, %.2f)\n", AngleNormalize( vecGoalLocalAngles.x ), AngleNormalize( vecGoalLocalAngles.y ) ); |
|
} |
|
return bDidMove; |
|
} |
|
|
|
// |
|
// ID as a machine |
|
// |
|
Class_T CBaseTurret::Classify ( void ) |
|
|
|
{ |
|
if (m_iOn || m_iAutoStart) |
|
{ |
|
return CLASS_MILITARY; |
|
} |
|
|
|
return CLASS_MILITARY; |
|
} |
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////// |
|
|
|
|
|
class CCeilingTurret : public CBaseTurret |
|
{ |
|
DECLARE_CLASS( CCeilingTurret, CBaseTurret ); |
|
public: |
|
void Spawn(void); |
|
void Precache(void); |
|
|
|
// other functions |
|
void Shoot( const Vector &vecSrc, const Vector &vecDirToEnemy ); |
|
}; |
|
|
|
#define TURRET_GLOW_SPRITE "sprites/glow01.vmt" |
|
|
|
LINK_ENTITY_TO_CLASS( npc_turret_ceiling, CCeilingTurret ); |
|
|
|
void CCeilingTurret::Spawn() |
|
{ |
|
Precache( ); |
|
|
|
SetModel( "models/combine_turrets/ceiling_turret.mdl" ); |
|
|
|
BaseClass::Spawn( ); |
|
|
|
m_iHealth = sk_turret_health.GetFloat(); |
|
m_HackedGunPos = Vector( 0, 0, 12.75 ); |
|
|
|
AngleVectors( GetAngles(), NULL, NULL, &m_vecViewOffset ); |
|
m_vecViewOffset = m_vecViewOffset * Vector( 0, 0, -64 ); |
|
|
|
m_flFieldOfView = VIEW_FIELD_FULL; |
|
|
|
m_iRetractHeight = 16; |
|
m_iDeployHeight = 32; |
|
m_iMinPitch = -45; |
|
UTIL_SetSize(this, Vector(-32, -32, -m_iRetractHeight), Vector(32, 32, m_iRetractHeight)); |
|
|
|
SetThink(Initialize); |
|
|
|
m_pEyeGlow = CSprite::SpriteCreate( TURRET_GLOW_SPRITE, GetOrigin(), FALSE ); |
|
m_pEyeGlow->SetTransparency( kRenderGlow, 255, 0, 0, 0, kRenderFxNoDissipation ); |
|
m_pEyeGlow->SetAttachment( this, 2 ); |
|
m_eyeBrightness = 0; |
|
|
|
SetNextThink( gpGlobals->curtime + 0.3; ); |
|
} |
|
|
|
void CCeilingTurret::Precache() |
|
{ |
|
PrecacheModel( "models/combine_turrets/ceiling_turret.mdl"); |
|
PrecacheModel( TURRET_GLOW_SPRITE ); |
|
|
|
PrecacheScriptSound( "CeilingTurret.Shoot" ); |
|
BaseClass::Precache(); |
|
} |
|
|
|
void CCeilingTurret::Shoot(const Vector &vecSrc, const Vector &vecDirToEnemy) |
|
{ |
|
//NDebugOverlay::Line( vecSrc, vecSrc + vecDirToEnemy * 512, 0, 255, 255, false, 0.1 ); |
|
FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, m_iAmmoType, 1 ); |
|
EmitSound( "CeilingTurret.Shoot" ); |
|
|
|
DoMuzzleFlash(); |
|
} |
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////// |
|
|
|
#if 0 |
|
|
|
class CMiniTurret : public CBaseTurret |
|
{ |
|
DECLARE_CLASS( CMiniTurret, CBaseTurret ); |
|
public: |
|
void Spawn( ); |
|
void Precache(void); |
|
// other functions |
|
void Shoot(const Vector &vecSrc, const Vector &vecDirToEnemy); |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( npc_miniturret, CMiniTurret ); |
|
|
|
|
|
void CMiniTurret::Spawn() |
|
{ |
|
Precache( ); |
|
SetModel( "models/miniturret.mdl" ); |
|
m_iHealth = sk_miniturret_health.GetFloat(); |
|
m_HackedGunPos = Vector( 0, 0, 12.75 ); |
|
m_vecViewOffset.z = 12.75; |
|
m_flFieldOfView = VIEW_FIELD_NARROW; |
|
|
|
CBaseTurret::Spawn( ); |
|
|
|
m_iAmmoType = g_pGameRules->GetAmmoDef()->Index("Pistol"); |
|
|
|
m_iRetractHeight = 16; |
|
m_iDeployHeight = 32; |
|
m_iMinPitch = -45; |
|
UTIL_SetSize(this, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight)); |
|
|
|
SetThink(Initialize); |
|
SetNextThink( gpGlobals->curtime + 0.3; ); |
|
} |
|
|
|
|
|
void CMiniTurret::Precache() |
|
{ |
|
PrecacheModel ("models/miniturret.mdl"); |
|
|
|
PrecacheScriptSound( "MiniTurret.Shoot" ); |
|
|
|
BaseClass::Precache(); |
|
} |
|
|
|
|
|
|
|
void CMiniTurret::Shoot(const Vector &vecSrc, const Vector &vecDirToEnemy) |
|
{ |
|
FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, m_iAmmoType, 1 ); |
|
|
|
EmitSound( "MiniTurret.Shoot" ); |
|
|
|
DoMuzzleFlash(); |
|
} |
|
|
|
|
|
#endif |
|
|
|
|
|
//========================================================= |
|
// Sentry gun - smallest turret, placed near grunt entrenchments |
|
//========================================================= |
|
class CSentry : public CBaseTurret |
|
{ |
|
DECLARE_CLASS( CSentry, CBaseTurret ); |
|
public: |
|
void Spawn( ); |
|
void Precache(void); |
|
// other functions |
|
void Shoot(const Vector &vecSrc, const Vector &vecDirToEnemy); |
|
int OnTakeDamage( const CTakeDamageInfo &info ); |
|
void SentryTouch( CBaseEntity *pOther ); |
|
void SentryDeath( void ); |
|
|
|
protected: |
|
|
|
DECLARE_DATADESC(); |
|
}; |
|
|
|
|
|
BEGIN_DATADESC( CSentry ) |
|
|
|
// Function pointers |
|
DEFINE_ENTITYFUNC( SentryTouch ), |
|
DEFINE_THINKFUNC( SentryDeath ), |
|
|
|
END_DATADESC() |
|
|
|
|
|
LINK_ENTITY_TO_CLASS( NPC_sentry, CSentry ); |
|
|
|
void CSentry::Precache() |
|
{ |
|
PrecacheModel ("models/sentry.mdl"); |
|
|
|
PrecacheScriptSound( "Sentry.Shoot" ); |
|
PrecacheScriptSound( "Sentry.Die" ); |
|
|
|
BaseClass::Precache(); |
|
} |
|
|
|
void CSentry::Spawn() |
|
{ |
|
Precache( ); |
|
SetModel( "models/sentry.mdl" ); |
|
m_iHealth = sk_sentry_health.GetFloat(); |
|
m_HackedGunPos = Vector( 0, 0, 48 ); |
|
m_vecViewOffset.z = 48; |
|
m_flMaxWait = 1E6; |
|
|
|
CBaseTurret::Spawn(); |
|
m_iRetractHeight = 64; |
|
m_iDeployHeight = 64; |
|
m_iMinPitch = -60; |
|
UTIL_SetSize(this, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight)); |
|
|
|
SetTouch(SentryTouch); |
|
SetThink(Initialize); |
|
SetNextThink( gpGlobals->curtime + 0.3; ); |
|
} |
|
|
|
void CSentry::Shoot(const Vector &vecSrc, const Vector &vecDirToEnemy) |
|
{ |
|
FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, m_iAmmoType, 1 ); |
|
|
|
EmitSound( "Sentry.Shoot" ); |
|
|
|
DoMuzzleFlash(); |
|
} |
|
|
|
|
|
int CSentry::OnTakeDamage( const CTakeDamageInfo &info ) |
|
{ |
|
if ( !m_takedamage ) |
|
return 0; |
|
|
|
if (!m_iOn) |
|
{ |
|
SetThink( Deploy ); |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
} |
|
|
|
m_iHealth -= info.GetDamage(); |
|
if (m_iHealth <= 0) |
|
{ |
|
m_iHealth = 0; |
|
m_takedamage = DAMAGE_NO; |
|
m_flDamageTime = gpGlobals->curtime; |
|
|
|
RemoveFlag( FL_NPC ); // why are they set in the first place??? |
|
|
|
SetThink(SentryDeath); |
|
m_OnDamaged.FireOutput( info.GetInflictor(), this ); |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
|
|
return 0; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
|
|
void CSentry::SentryTouch( CBaseEntity *pOther ) |
|
{ |
|
if ( pOther && (pOther->IsPlayer() || (pOther->GetFlags() & FL_NPC)) ) |
|
{ |
|
OnTakeDamage( CTakeDamageInfo( pOther, pOther, 0, 0 ) ); |
|
} |
|
} |
|
|
|
|
|
void CSentry :: SentryDeath( void ) |
|
{ |
|
StudioFrameAdvance( ); |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
|
|
if (m_lifeState != LIFE_DEAD) |
|
{ |
|
m_lifeState = LIFE_DEAD; |
|
|
|
EmitSound( "Sentry.Die" ); |
|
|
|
SetPoseParameter( TURRET_BC_YAW, 0 ); |
|
SetPoseParameter( TURRET_BC_PITCH, 0 ); |
|
|
|
SetActivity( (Activity)ACT_TURRET_CLOSE ); |
|
|
|
SetSolid( SOLID_NOT ); |
|
QAngle angles = GetAngles(); |
|
angles.y = UTIL_AngleMod( GetAngles().y + random->RandomInt( 0, 2 ) * 120 ); |
|
SetAngles( angles ); |
|
|
|
EyeOn( ); |
|
} |
|
|
|
EyeOff( ); |
|
|
|
Vector vecSrc; |
|
QAngle vecAng; |
|
GetAttachment( "eyes", vecSrc, vecAng ); |
|
|
|
if (m_flDamageTime + random->RandomFloat( 0, 2 ) > gpGlobals->curtime) |
|
{ |
|
// lots of smoke |
|
Vector pos = vecSrc + Vector( random->RandomFloat( -16, 16 ), |
|
random->RandomFloat( -16, 16 ), |
|
-32 ); |
|
|
|
CBroadcastRecipientFilter filter; |
|
te->Smoke( filter, 0.0, &pos, |
|
g_sModelIndexSmoke, |
|
1.5, |
|
8 ); |
|
} |
|
|
|
if (m_flDamageTime + random->RandomFloat( 0, 8 ) > gpGlobals->curtime) |
|
{ |
|
g_pEffects->Sparks( vecSrc ); |
|
} |
|
|
|
if (m_fSequenceFinished && m_flDamageTime + 5 < gpGlobals->curtime) |
|
{ |
|
m_flPlaybackRate = 0; |
|
SetThink( NULL ); |
|
} |
|
} |
|
|
|
|