source-engine/game/server/hl2/npc_turret.cpp
FluorescentCIAAfricanAmerican 3bf9df6b27 1
2020-04-22 12:56:21 -04:00

1191 lines
26 KiB
C++

//========= 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 );
}
}