source-engine/game/server/hl1/hl1_npc_turret.cpp
2022-04-16 12:05:19 +03:00

1510 lines
34 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"
#include "ai_default.h"
#include "ai_task.h"
#include "ai_schedule.h"
#include "ai_node.h"
#include "ai_hull.h"
#include "ai_hint.h"
#include "ai_memory.h"
#include "ai_route.h"
#include "ai_motor.h"
#include "ai_senses.h"
#include "soundent.h"
#include "game.h"
#include "npcevent.h"
#include "entitylist.h"
#include "activitylist.h"
#include "animation.h"
#include "basecombatweapon.h"
#include "IEffects.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "ammodef.h"
#include "Sprite.h"
#define TURRET_SHOTS 2
#define TURRET_RANGE (100 * 12)
#define TURRET_SPREAD Vector( 0, 0, 0 )
#define TURRET_TURNRATE 30 //angles per 0.1 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
typedef enum
{
// TURRET_ANIM_NONE = 0,
TURRET_ANIM_FIRE = 0,
TURRET_ANIM_SPIN,
TURRET_ANIM_DEPLOY,
TURRET_ANIM_RETIRE,
TURRET_ANIM_DIE,
} TURRET_ANIM;
#define SF_MONSTER_TURRET_AUTOACTIVATE 32
#define SF_MONSTER_TURRET_STARTINACTIVE 64
#define TURRET_GLOW_SPRITE "sprites/flare3.vmt"
#define TURRET_ORIENTATION_FLOOR 0
#define TURRET_ORIENTATION_CEILING 1
class CNPC_BaseTurret : public CAI_BaseNPC
{
DECLARE_CLASS( CNPC_BaseTurret, CAI_BaseNPC );
public:
void Spawn(void);
virtual void Precache(void);
void EXPORT TurretUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
virtual int OnTakeDamage( const CTakeDamageInfo &info );
virtual int OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo );
Class_T Classify( void );
// Think functions
void EXPORT ActiveThink(void);
void EXPORT SearchThink(void);
void EXPORT AutoSearchThink(void);
void EXPORT TurretDeath(void);
virtual void EXPORT SpinDownCall(void) { m_iSpin = 0; }
virtual void EXPORT SpinUpCall(void) { m_iSpin = 1; }
void EXPORT Deploy(void);
void EXPORT Retire(void);
void EXPORT Initialize(void);
virtual void Ping(void);
virtual void EyeOn(void);
virtual void EyeOff(void);
void InputActivate( inputdata_t &inputdata );
void InputDeactivate( inputdata_t &inputdata );
void Event_Killed( const CTakeDamageInfo &info );
virtual bool ShouldFadeOnDeath( void ) { return false; }
bool ShouldGib( const CTakeDamageInfo &info ) { return false; }
// other functions
void SetTurretAnim(TURRET_ANIM anim);
int MoveTurret(void);
virtual void Shoot(Vector &vecSrc, Vector &vecDirToEnemy) { };
float m_flMaxSpin; // Max time to spin the barrel w/o a target
int m_iSpin;
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_iOrientation; // 0 = floor, 1 = Ceiling
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;
float m_flLastSight; // Last time we saw a target
float m_flMaxWait; // Max time to seach w/o a target
int m_iSearchSpeed; // Not Used!
// movement
float m_flStartYaw;
QAngle m_vecCurAngles;
Vector m_vecGoalAngles;
float m_flPingTime; // Time until the next ping, used when searching
float m_flSpinUpTime; // Amount of time until the barrel should spin down when searching
float m_flDamageTime;
int m_iAmmoType;
COutputEvent m_OnActivate;
COutputEvent m_OnDeactivate;
//DEFINE_CUSTOM_AI;
DECLARE_DATADESC();
};
BEGIN_DATADESC( CNPC_BaseTurret )
//FIELDS
DEFINE_FIELD( m_flMaxSpin, FIELD_FLOAT ),
DEFINE_FIELD( m_iSpin, FIELD_INTEGER ),
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_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_flStartYaw, FIELD_FLOAT ),
DEFINE_FIELD( m_vecCurAngles, FIELD_VECTOR ),
DEFINE_FIELD( m_vecGoalAngles, FIELD_VECTOR ),
DEFINE_FIELD( m_flPingTime, FIELD_TIME ),
DEFINE_FIELD( m_flSpinUpTime, FIELD_TIME ),
DEFINE_FIELD( m_flDamageTime, FIELD_TIME ),
//DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
//KEYFIELDS
DEFINE_KEYFIELD( m_flMaxWait, FIELD_FLOAT, "maxsleep" ),
DEFINE_KEYFIELD( m_iOrientation, FIELD_INTEGER, "orientation" ),
DEFINE_KEYFIELD( m_iSearchSpeed, FIELD_INTEGER, "searchspeed" ),
DEFINE_KEYFIELD( m_iBaseTurnRate, FIELD_INTEGER, "turnrate" ),
//Use
DEFINE_USEFUNC( TurretUse ),
//Thinks
DEFINE_THINKFUNC( ActiveThink ),
DEFINE_THINKFUNC( SearchThink ),
DEFINE_THINKFUNC( AutoSearchThink ),
DEFINE_THINKFUNC( TurretDeath ),
DEFINE_THINKFUNC( SpinDownCall ),
DEFINE_THINKFUNC( SpinUpCall ),
DEFINE_THINKFUNC( Deploy ),
DEFINE_THINKFUNC( Retire ),
DEFINE_THINKFUNC( Initialize ),
//Inputs
DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),
DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ),
//Outputs
DEFINE_OUTPUT( m_OnActivate, "OnActivate"),
DEFINE_OUTPUT( m_OnDeactivate, "OnDeactivate"),
END_DATADESC()
void CNPC_BaseTurret::Spawn()
{
Precache( );
SetNextThink( gpGlobals->curtime + 1 );
SetMoveType( MOVETYPE_FLY );
SetSequence( 0 );
SetCycle( 0 );
SetSolid( SOLID_BBOX );
AddSolidFlags( FSOLID_NOT_STANDABLE );
m_takedamage = DAMAGE_YES;
AddFlag( FL_AIMTARGET );
AddFlag( FL_NPC );
SetUse( &CNPC_BaseTurret::TurretUse );
if (( m_spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE )
&& !( m_spawnflags & SF_MONSTER_TURRET_STARTINACTIVE ))
{
m_iAutoStart = true;
}
ResetSequenceInfo( );
SetBoneController(0, 0);
SetBoneController(1, 0);
m_flFieldOfView = VIEW_FIELD_FULL;
m_bloodColor = DONT_BLEED;
m_flDamageTime = 0;
if ( GetSpawnFlags() & SF_MONSTER_TURRET_STARTINACTIVE )
{
SetTurretAnim( TURRET_ANIM_RETIRE );
SetCycle( 0.0f );
m_flPlaybackRate = 0.0f;
}
}
void CNPC_BaseTurret::Precache()
{
m_iAmmoType = GetAmmoDef()->Index("12mmRound");
PrecacheScriptSound( "Turret.Alert" );
PrecacheScriptSound( "Turret.Die" );
PrecacheScriptSound( "Turret.Deploy" );
PrecacheScriptSound( "Turret.Undeploy" );
PrecacheScriptSound( "Turret.Ping" );
PrecacheScriptSound( "Turret.Shoot" );
}
Class_T CNPC_BaseTurret::Classify( void )
{
if (m_iOn || m_iAutoStart)
return CLASS_MACHINE;
return CLASS_NONE;
}
//=========================================================
// TraceAttack - being attacked
//=========================================================
void CNPC_BaseTurret::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
{
CTakeDamageInfo ainfo = info;
if ( ptr->hitgroup == 10 )
{
// hit armor
if ( m_flDamageTime != gpGlobals->curtime || (random->RandomInt(0,10) < 1) )
{
g_pEffects->Ricochet( ptr->endpos, ptr->plane.normal );
m_flDamageTime = gpGlobals->curtime;
}
ainfo.SetDamage( 0.1 );// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated
}
if ( m_takedamage == DAMAGE_NO )
return;
//DevMsg( 1, "traceattack: %f\n", ainfo.GetDamage() );
AddMultiDamage( info, this );
}
//=========================================================
// TakeDamage - take damage.
//=========================================================
int CNPC_BaseTurret::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
{
if ( m_takedamage == DAMAGE_NO )
return 0;
float flDamage = inputInfo.GetDamage();
if (!m_iOn)
flDamage /= 10.0;
m_iHealth -= flDamage;
if (m_iHealth <= 0)
{
m_iHealth = 0;
m_takedamage = DAMAGE_NO;
m_flDamageTime = gpGlobals->curtime;
// ClearBits (pev->flags, FL_MONSTER); // why are they set in the first place???
SetUse(NULL);
SetThink(&CNPC_BaseTurret::TurretDeath);
SetNextThink( gpGlobals->curtime + 0.1 );
m_OnDeactivate.FireOutput(this, this);
return 0;
}
if (m_iHealth <= 10)
{
if (m_iOn)
{
m_fBeserk = 1;
SetThink(&CNPC_BaseTurret::SearchThink);
}
}
return 1;
}
int CNPC_BaseTurret::OnTakeDamage( const CTakeDamageInfo &info )
{
int retVal = 0;
if (!m_takedamage)
return 0;
switch( m_lifeState )
{
case LIFE_ALIVE:
retVal = OnTakeDamage_Alive( info );
if ( m_iHealth <= 0 )
{
IPhysicsObject *pPhysics = VPhysicsGetObject();
if ( pPhysics )
{
pPhysics->EnableCollisions( false );
}
Event_Killed( info );
Event_Dying();
}
return retVal;
break;
case LIFE_DYING:
return OnTakeDamage_Dying( info );
default:
case LIFE_DEAD:
return OnTakeDamage_Dead( info );
}
}
void CNPC_BaseTurret::SetTurretAnim( TURRET_ANIM anim )
{
/*
if (GetSequence() != anim)
{
switch(anim)
{
case TURRET_ANIM_FIRE:
case TURRET_ANIM_SPIN:
if (GetSequence() != TURRET_ANIM_FIRE && GetSequence() != TURRET_ANIM_SPIN)
{
m_flCycle = 0;
}
break;
default:
m_flCycle = 0;
break;
}
SetSequence( anim );
ResetSequenceInfo( );
switch(anim)
{
case TURRET_ANIM_RETIRE:
m_flCycle = 255;
m_flPlaybackRate = -1.0; //play the animation backwards
break;
case TURRET_ANIM_DIE:
m_flPlaybackRate = 1.0;
break;
}
//ALERT(at_console, "Turret anim #%d\n", anim);
}
*/
if (GetSequence() != anim)
{
SetSequence( anim );
ResetSequenceInfo( );
switch(anim)
{
case TURRET_ANIM_FIRE:
case TURRET_ANIM_SPIN:
if (GetSequence() != TURRET_ANIM_FIRE && GetSequence() != TURRET_ANIM_SPIN)
{
SetCycle( 0 );
}
break;
case TURRET_ANIM_RETIRE:
SetCycle( 1.0 );
m_flPlaybackRate = -1.0; //play the animation backwards
break;
case TURRET_ANIM_DIE:
SetCycle( 0.0 );
m_flPlaybackRate = 1.0;
break;
default:
SetCycle( 0 );
break;
}
}
}
//=========================================================
// Initialize - set up the turret, initial think
//=========================================================
void CNPC_BaseTurret::Initialize(void)
{
m_iOn = 0;
m_fBeserk = 0;
m_iSpin = 0;
SetBoneController( 0, 0 );
SetBoneController( 1, 0 );
if (m_iBaseTurnRate == 0) m_iBaseTurnRate = TURRET_TURNRATE;
if (m_flMaxWait == 0) m_flMaxWait = TURRET_MAXWAIT;
QAngle angles = GetAbsAngles();
m_flStartYaw = angles.y;
if (m_iOrientation == TURRET_ORIENTATION_CEILING)
{
angles.x = 180;
angles.y += 180;
if( angles.y > 360 )
angles.y -= 360;
SetAbsAngles( angles );
// pev->idealpitch = 180; //not used?
Vector view_ofs = GetViewOffset();
view_ofs.z = -view_ofs.z;
SetViewOffset( view_ofs );
// pev->effects |= EF_INVLIGHT; //no need
}
m_vecGoalAngles.x = 0;
if (m_iAutoStart)
{
m_flLastSight = gpGlobals->curtime + m_flMaxWait;
SetThink(&CNPC_BaseTurret::AutoSearchThink);
SetNextThink( gpGlobals->curtime + 0.1 );
}
else
{
SetThink( &CBaseEntity::SUB_DoNothing );
}
}
//=========================================================
// ActiveThink -
//=========================================================
void CNPC_BaseTurret::ActiveThink(void)
{
int fAttack = 0;
SetNextThink( gpGlobals->curtime + 0.1 );
StudioFrameAdvance( );
if ( (!m_iOn) || (GetEnemy() == NULL) )
{
SetEnemy( NULL );
m_flLastSight = gpGlobals->curtime + m_flMaxWait;
SetThink(&CNPC_BaseTurret::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(&CNPC_BaseTurret::SearchThink);
return;
}
}
}
Vector vecMid = EyePosition();
Vector vecMidEnemy = GetEnemy()->BodyTarget(vecMid, false);
// Look for our current enemy
int fEnemyVisible = FInViewCone( GetEnemy() ) && FVisible( GetEnemy() );
//We want to look at the enemy's eyes so we don't jitter
Vector vecDirToEnemyEyes = vecMidEnemy - vecMid;
// NDebugOverlay::Line( vecMid, vecMidEnemy, 0, 255, 0, false, 1.0 );
float flDistToEnemy = vecDirToEnemyEyes.Length();
VectorNormalize( vecDirToEnemyEyes );
QAngle vecAnglesToEnemy;
VectorAngles( vecDirToEnemyEyes, vecAnglesToEnemy );
// Current enmey is not visible.
if (!fEnemyVisible || (flDistToEnemy > TURRET_RANGE))
{
if (!m_flLastSight)
m_flLastSight = gpGlobals->curtime + 0.5;
else
{
// Should we look for a new target?
if (gpGlobals->curtime > m_flLastSight)
{
SetEnemy( NULL );
m_flLastSight = gpGlobals->curtime + m_flMaxWait;
SetThink(&CNPC_BaseTurret::SearchThink);
return;
}
}
fEnemyVisible = 0;
}
else
{
m_vecLastSight = vecMidEnemy;
}
Vector forward;
AngleVectors( m_vecCurAngles, &forward );
Vector2D vec2LOS = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ).AsVector2D();
vec2LOS.NormalizeInPlace();
float flDot = vec2LOS.Dot( forward.AsVector2D() );
if ( flDot <= 0.866 )
fAttack = FALSE;
else
fAttack = TRUE;
//forward
//NDebugOverlay::Line(vecMuzzle, vecMid + ( forward ), 255,0,0, false, 0.1);
//LOS
//NDebugOverlay::Line(vecMuzzle, vecMid + ( vecDirToEnemyEyes * 200 ), 0,0,255, false, 0.1);
// fire the gun
if (m_iSpin && ((fAttack) || (m_fBeserk)))
{
Shoot(vecMid, forward );
SetTurretAnim(TURRET_ANIM_FIRE);
}
else
{
SetTurretAnim(TURRET_ANIM_SPIN);
}
//move the gun
if (m_fBeserk)
{
if (random->RandomInt(0,9) == 0)
{
m_vecGoalAngles.y = random->RandomFloat(0,360);
m_vecGoalAngles.x = random->RandomFloat(0,90) - 90 * m_iOrientation;
CTakeDamageInfo info;
info.SetAttacker(this);
info.SetInflictor(this);
info.SetDamage( 1 );
info.SetDamageType( DMG_GENERIC );
TakeDamage( info ); // don't beserk forever
return;
}
}
else if (fEnemyVisible)
{
if (vecAnglesToEnemy.y > 360)
vecAnglesToEnemy.y -= 360;
if (vecAnglesToEnemy.y < 0)
vecAnglesToEnemy.y += 360;
//ALERT(at_console, "[%.2f]", vec.x);
if (vecAnglesToEnemy.x < -180)
vecAnglesToEnemy.x += 360;
if (vecAnglesToEnemy.x > 180)
vecAnglesToEnemy.x -= 360;
// now all numbers should be in [1...360]
// pin to turret limitations to [-90...14]
if (m_iOrientation == TURRET_ORIENTATION_FLOOR)
{
if (vecAnglesToEnemy.x > 90)
vecAnglesToEnemy.x = 90;
else if (vecAnglesToEnemy.x < m_iMinPitch)
vecAnglesToEnemy.x = m_iMinPitch;
}
else
{
if (vecAnglesToEnemy.x < -90)
vecAnglesToEnemy.x = -90;
else if (vecAnglesToEnemy.x > -m_iMinPitch)
vecAnglesToEnemy.x = -m_iMinPitch;
}
//DevMsg( 1, "->[%.2f]\n", vec.x);
m_vecGoalAngles.y = vecAnglesToEnemy.y;
m_vecGoalAngles.x = vecAnglesToEnemy.x;
}
SpinUpCall();
MoveTurret();
}
//=========================================================
// SearchThink
// 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 CNPC_BaseTurret::SearchThink(void)
{
// ensure rethink
SetTurretAnim(TURRET_ANIM_SPIN);
StudioFrameAdvance( );
SetNextThink( gpGlobals->curtime + 0.1 );
if (m_flSpinUpTime == 0 && m_flMaxSpin)
m_flSpinUpTime = gpGlobals->curtime + m_flMaxSpin;
Ping( );
CBaseEntity *pEnemy = GetEnemy();
// If we have a target and we're still healthy
if (pEnemy != NULL)
{
if (!pEnemy->IsAlive() )
pEnemy = NULL;// Dead enemy forces a search for new one
}
// Acquire Target
if (pEnemy == NULL)
{
GetSenses()->Look(TURRET_RANGE);
pEnemy = BestEnemy();
if ( pEnemy && !FVisible( pEnemy ) )
pEnemy = NULL;
}
// If we've found a target, spin up the barrel and start to attack
if (pEnemy != NULL)
{
m_flLastSight = 0;
m_flSpinUpTime = 0;
SetThink(&CNPC_BaseTurret::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;
m_flSpinUpTime = 0;
SetThink(&CNPC_BaseTurret::Retire);
}
// should we stop the spin?
else if ((m_flSpinUpTime) && (gpGlobals->curtime > m_flSpinUpTime))
{
SpinDownCall();
}
// generic hunt for new victims
m_vecGoalAngles.y = (m_vecGoalAngles.y + 0.1 * m_fTurnRate);
if (m_vecGoalAngles.y >= 360)
m_vecGoalAngles.y -= 360;
MoveTurret();
}
SetEnemy( pEnemy );
}
//=========================================================
// AutoSearchThink -
//=========================================================
void CNPC_BaseTurret::AutoSearchThink(void)
{
// ensure rethink
StudioFrameAdvance( );
SetNextThink( gpGlobals->curtime + 0.3 );
// If we have a target and we're still healthy
CBaseEntity *pEnemy = GetEnemy();
if (pEnemy != NULL)
{
if (!pEnemy->IsAlive() )
{
pEnemy = NULL;
}
}
// Acquire Target
if (pEnemy == NULL)
{
GetSenses()->Look( TURRET_RANGE );
pEnemy = BestEnemy();
if ( pEnemy && !FVisible( pEnemy ) )
pEnemy = NULL;
}
if (pEnemy != NULL)
{
SetThink(&CNPC_BaseTurret::Deploy);
CPASAttenuationFilter filter( this );
EmitSound( filter, entindex(), "Turret.Alert" );
}
SetEnemy( pEnemy );
}
extern short g_sModelIndexSmoke;
//=========================================================
// TurretDeath - I die as I have lived, beyond my means
//=========================================================
void CNPC_BaseTurret::TurretDeath(void)
{
StudioFrameAdvance( );
SetNextThink( gpGlobals->curtime + 0.1 );
if (m_lifeState != LIFE_DEAD)
{
m_lifeState = LIFE_DEAD;
CPASAttenuationFilter filter( this );
EmitSound( filter, entindex(), "Turret.Die" );
StopSound( entindex(), "Turret.Spinup" );
if (m_iOrientation == TURRET_ORIENTATION_FLOOR)
m_vecGoalAngles.x = -14;
else
m_vecGoalAngles.x = 90;//-90;
SetTurretAnim(TURRET_ANIM_DIE);
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 (IsSequenceFinished() && !MoveTurret() && m_flDamageTime + 5 < gpGlobals->curtime)
{
m_flPlaybackRate = 0;
SetThink( NULL );
}
}
//=========================================================
// Deploy - go active
//=========================================================
void CNPC_BaseTurret::Deploy(void)
{
SetNextThink( gpGlobals->curtime + 0.1 );
StudioFrameAdvance( );
if (GetSequence() != TURRET_ANIM_DEPLOY)
{
m_iOn = 1;
SetTurretAnim(TURRET_ANIM_DEPLOY);
CPASAttenuationFilter filter( this );
EmitSound( filter, entindex(), "Turret.Deploy" );
m_OnActivate.FireOutput(this, this);
}
if (IsSequenceFinished())
{
Vector curmins, curmaxs;
curmins = WorldAlignMins();
curmaxs = WorldAlignMaxs();
curmaxs.z = m_iDeployHeight;
curmins.z = -m_iDeployHeight;
SetCollisionBounds( curmins, curmaxs );
m_vecCurAngles.x = 0;
QAngle angles = GetAbsAngles();
if (m_iOrientation == TURRET_ORIENTATION_CEILING)
{
m_vecCurAngles.y = UTIL_AngleMod( angles.y + 180 );
}
else
{
m_vecCurAngles.y = UTIL_AngleMod( angles.y );
}
SetTurretAnim(TURRET_ANIM_SPIN);
m_flPlaybackRate = 0;
SetThink(&CNPC_BaseTurret::SearchThink);
}
m_flLastSight = gpGlobals->curtime + m_flMaxWait;
}
//=========================================================
// Retire - stop being active
//=========================================================
void CNPC_BaseTurret::Retire(void)
{
// make the turret level
m_vecGoalAngles.x = 0;
m_vecGoalAngles.y = m_flStartYaw;
SetNextThink( gpGlobals->curtime + 0.1 );
StudioFrameAdvance( );
EyeOff( );
if (!MoveTurret())
{
if (m_iSpin)
{
SpinDownCall();
}
else if (GetSequence() != TURRET_ANIM_RETIRE)
{
SetTurretAnim(TURRET_ANIM_RETIRE);
CPASAttenuationFilter filter( this );
EmitSound( filter, entindex(), "Turret.Undeploy" );
m_OnDeactivate.FireOutput(this, this);
}
//else if (IsSequenceFinished())
else if( GetSequence() == TURRET_ANIM_RETIRE && GetCycle() <= 0.0 )
{
m_iOn = 0;
m_flLastSight = 0;
//SetTurretAnim(TURRET_ANIM_NONE);
Vector curmins, curmaxs;
curmins = WorldAlignMins();
curmaxs = WorldAlignMaxs();
curmaxs.z = m_iRetractHeight;
curmins.z = -m_iRetractHeight;
SetCollisionBounds( curmins, curmaxs );
if (m_iAutoStart)
{
SetThink(&CNPC_BaseTurret::AutoSearchThink);
SetNextThink( gpGlobals->curtime + 0.1 );
}
else
{
SetThink( &CBaseEntity::SUB_DoNothing );
}
}
}
else
{
SetTurretAnim(TURRET_ANIM_SPIN);
}
}
//=========================================================
// Ping - make the pinging noise every second while searching
//=========================================================
void CNPC_BaseTurret::Ping(void)
{
if (m_flPingTime == 0)
m_flPingTime = gpGlobals->curtime + 1;
else if (m_flPingTime <= gpGlobals->curtime)
{
m_flPingTime = gpGlobals->curtime + 1;
CPASAttenuationFilter filter( this );
EmitSound( filter, entindex(), "Turret.Ping" );
EyeOn( );
}
else if (m_eyeBrightness > 0)
{
EyeOff( );
}
}
//=========================================================
// MoveTurret - handle turret rotation
// returns 1 if the turret moved.
//=========================================================
int CNPC_BaseTurret::MoveTurret(void)
{
int bMoved = 0;
if (m_vecCurAngles.x != m_vecGoalAngles.x)
{
float flDir = m_vecGoalAngles.x > m_vecCurAngles.x ? 1 : -1 ;
m_vecCurAngles.x += 0.1 * m_fTurnRate * flDir;
// if we started below the goal, and now we're past, peg to goal
if (flDir == 1)
{
if (m_vecCurAngles.x > m_vecGoalAngles.x)
m_vecCurAngles.x = m_vecGoalAngles.x;
}
else
{
if (m_vecCurAngles.x < m_vecGoalAngles.x)
m_vecCurAngles.x = m_vecGoalAngles.x;
}
if (m_iOrientation == TURRET_ORIENTATION_FLOOR)
SetBoneController(1, m_vecCurAngles.x);
else
SetBoneController(1, -m_vecCurAngles.x);
bMoved = 1;
}
if (m_vecCurAngles.y != m_vecGoalAngles.y)
{
float flDir = m_vecGoalAngles.y > m_vecCurAngles.y ? 1 : -1 ;
float flDist = fabs(m_vecGoalAngles.y - m_vecCurAngles.y);
if (flDist > 180)
{
flDist = 360 - flDist;
flDir = -flDir;
}
if (flDist > 30)
{
if (m_fTurnRate < m_iBaseTurnRate * 10)
{
m_fTurnRate += m_iBaseTurnRate;
}
}
else if (m_fTurnRate > 45)
{
m_fTurnRate -= m_iBaseTurnRate;
}
else
{
m_fTurnRate += m_iBaseTurnRate;
}
m_vecCurAngles.y += 0.1 * m_fTurnRate * flDir;
if (m_vecCurAngles.y < 0)
m_vecCurAngles.y += 360;
else if (m_vecCurAngles.y >= 360)
m_vecCurAngles.y -= 360;
if (flDist < (0.05 * m_iBaseTurnRate))
m_vecCurAngles.y = m_vecGoalAngles.y;
QAngle angles = GetAbsAngles();
//ALERT(at_console, "%.2f -> %.2f\n", m_vecCurAngles.y, y);
if (m_iOrientation == TURRET_ORIENTATION_FLOOR)
SetBoneController(0, m_vecCurAngles.y - angles.y );
else
SetBoneController(0, angles.y - 180 - m_vecCurAngles.y );
bMoved = 1;
}
if (!bMoved)
m_fTurnRate = m_iBaseTurnRate;
//DevMsg(1, "(%.2f, %.2f)->(%.2f, %.2f)\n", m_vecCurAngles.x,
// m_vecCurAngles.y, m_vecGoalAngles.x, m_vecGoalAngles.y);
return bMoved;
}
void CNPC_BaseTurret::TurretUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
if ( !ShouldToggle( useType, m_iOn ) )
return;
if (m_iOn)
{
SetEnemy( NULL );
SetNextThink( gpGlobals->curtime + 0.1 );
m_iAutoStart = FALSE;// switching off a turret disables autostart
//!!!! this should spin down first!!BUGBUG
SetThink(&CNPC_BaseTurret::Retire);
}
else
{
SetNextThink( gpGlobals->curtime + 0.1 ); // turn on delay
// if the turret is flagged as an autoactivate turret, re-enable it's ability open self.
if ( m_spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE )
{
m_iAutoStart = TRUE;
}
SetThink(&CNPC_BaseTurret::Deploy);
}
}
void CNPC_BaseTurret::InputDeactivate( inputdata_t &inputdata )
{
if( m_iOn && m_lifeState == LIFE_ALIVE )
{
SetEnemy( NULL );
SetNextThink( gpGlobals->curtime + 0.1 );
m_iAutoStart = FALSE;// switching off a turret disables autostart
//!!!! this should spin down first!!BUGBUG
SetThink(&CNPC_BaseTurret::Retire);
}
}
void CNPC_BaseTurret::InputActivate( inputdata_t &inputdata )
{
if( !m_iOn && m_lifeState == LIFE_ALIVE )
{
SetNextThink( gpGlobals->curtime + 0.1 ); // turn on delay
// if the turret is flagged as an autoactivate turret, re-enable it's ability open self.
if ( m_spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE )
{
m_iAutoStart = TRUE;
}
SetThink(&CNPC_BaseTurret::Deploy);
}
}
//=========================================================
// EyeOn - turn on light on the turret
//=========================================================
void CNPC_BaseTurret::EyeOn(void)
{
if (m_pEyeGlow)
{
if (m_eyeBrightness != 255)
{
m_eyeBrightness = 255;
}
m_pEyeGlow->SetBrightness( m_eyeBrightness );
}
}
//=========================================================
// EyeOn - turn off light on the turret
//=========================================================
void CNPC_BaseTurret::EyeOff(void)
{
if (m_pEyeGlow)
{
if (m_eyeBrightness > 0)
{
m_eyeBrightness = MAX( 0, m_eyeBrightness - 30 );
m_pEyeGlow->SetBrightness( m_eyeBrightness );
}
}
}
void CNPC_BaseTurret::Event_Killed( const CTakeDamageInfo &info )
{
BaseClass::Event_Killed( info );
SetMoveType( MOVETYPE_FLY );
}
//===
class CNPC_MiniTurret : public CNPC_BaseTurret
{
DECLARE_CLASS( CNPC_MiniTurret, CNPC_BaseTurret );
public:
void Spawn( );
void Precache(void);
// other functions
void Shoot(Vector &vecSrc, Vector &vecDirToEnemy);
};
class CNPC_Turret : public CNPC_BaseTurret
{
DECLARE_CLASS( CNPC_Turret, CNPC_BaseTurret );
public:
DECLARE_DATADESC();
void Spawn(void);
void Precache(void);
// Think functions
void SpinUpCall(void);
void SpinDownCall(void);
// other functions
void Shoot(Vector &vecSrc, Vector &vecDirToEnemy);
private:
int m_iStartSpin;
};
BEGIN_DATADESC(CNPC_Turret)
DEFINE_FIELD( m_iStartSpin, FIELD_INTEGER ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( monster_turret, CNPC_Turret );
LINK_ENTITY_TO_CLASS( monster_miniturret, CNPC_MiniTurret );
ConVar sk_turret_health ( "sk_turret_health","50");
ConVar sk_miniturret_health ( "sk_miniturret_health","40");
ConVar sk_sentry_health ( "sk_sentry_health","40");
void CNPC_Turret::Spawn()
{
Precache( );
SetModel( "models/turret.mdl" );
m_iHealth = sk_turret_health.GetFloat();
m_HackedGunPos = Vector( 0, 0, 12.75 );
m_flMaxSpin = TURRET_MAXSPIN;
Vector view_ofs( 0, 0, 12.75 );
SetViewOffset( view_ofs );
CNPC_BaseTurret::Spawn( );
m_iRetractHeight = 16;
m_iDeployHeight = 32;
m_iMinPitch = -90;
UTIL_SetSize(this, Vector(-32, -32, -m_iRetractHeight), Vector(32, 32, m_iRetractHeight));
SetThink(&CNPC_BaseTurret::Initialize);
m_pEyeGlow = CSprite::SpriteCreate( TURRET_GLOW_SPRITE, GetAbsOrigin(), FALSE );
m_pEyeGlow->SetTransparency( kRenderGlow, 255, 0, 0, 0, kRenderFxNoDissipation );
m_pEyeGlow->SetAttachment( this, 2 );
m_eyeBrightness = 0;
SetNextThink( gpGlobals->curtime + 0.3 );
}
void CNPC_Turret::Precache()
{
CNPC_BaseTurret::Precache( );
PrecacheModel ("models/turret.mdl");
PrecacheModel (TURRET_GLOW_SPRITE);
PrecacheModel( "sprites/xspark4.vmt" );
PrecacheScriptSound( "Turret.Shoot" );
PrecacheScriptSound( "Turret.SpinUpCall" );
PrecacheScriptSound( "Turret.Spinup" );
PrecacheScriptSound( "Turret.SpinDownCall" );
//precache sounds
}
void CNPC_Turret::Shoot(Vector &vecSrc, Vector &vecDirToEnemy)
{
CPASAttenuationFilter filter( this );
FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, m_iAmmoType, 1 );
EmitSound( filter, entindex(), "Turret.Shoot" );
DoMuzzleFlash();
}
void CNPC_Turret::SpinUpCall(void)
{
StudioFrameAdvance( );
SetNextThink( gpGlobals->curtime + 0.1 );
// Are we already spun up? If not start the two stage process.
if (!m_iSpin)
{
SetTurretAnim(TURRET_ANIM_SPIN);
// for the first pass, spin up the the barrel
if (!m_iStartSpin)
{
SetNextThink( gpGlobals->curtime + 1.0 ); //spinup delay
CPASAttenuationFilter filter( this );
EmitSound( filter, entindex(), "Turret.SpinUpCall" );
m_iStartSpin = 1;
m_flPlaybackRate = 0.1;
}
// after the barrel is spun up, turn on the hum
else if (m_flPlaybackRate >= 1.0)
{
SetNextThink( gpGlobals->curtime + 0.1 );// retarget delay
CPASAttenuationFilter filter( this );
EmitSound( filter, entindex(), "Turret.Spinup" );
SetThink(&CNPC_BaseTurret::ActiveThink);
m_iStartSpin = 0;
m_iSpin = 1;
}
else
{
m_flPlaybackRate += 0.075;
}
}
if (m_iSpin)
{
SetThink(&CNPC_BaseTurret::ActiveThink);
}
}
void CNPC_Turret::SpinDownCall(void)
{
if (m_iSpin)
{
CPASAttenuationFilter filter( this );
SetTurretAnim(TURRET_ANIM_SPIN);
if ( m_flPlaybackRate == 1.0)
{
StopSound( entindex(), "Turret.Spinup" );
EmitSound( filter, entindex(), "Turret.SpinDownCall" );
}
m_flPlaybackRate -= 0.02;
if (m_flPlaybackRate <= 0)
{
m_flPlaybackRate = 0;
m_iSpin = 0;
}
}
}
void CNPC_MiniTurret::Spawn()
{
Precache( );
SetModel( "models/miniturret.mdl" );
m_iHealth = sk_miniturret_health.GetFloat();
m_HackedGunPos = Vector( 0, 0, 12.75 );
m_flMaxSpin = 0;
Vector view_ofs( 0, 0, 12.75 );
SetViewOffset( view_ofs );
CNPC_BaseTurret::Spawn( );
m_iRetractHeight = 16;
m_iDeployHeight = 32;
m_iMinPitch = -90;
UTIL_SetSize(this, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight));
SetThink(&CNPC_MiniTurret::Initialize);
SetNextThink(gpGlobals->curtime + 0.3);
if (( m_spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE ) && !( m_spawnflags & SF_MONSTER_TURRET_STARTINACTIVE ))
{
m_iAutoStart = true;
}
}
void CNPC_MiniTurret::Precache()
{
CNPC_BaseTurret::Precache( );
m_iAmmoType = GetAmmoDef()->Index("9mmRound");
PrecacheScriptSound( "Turret.Shoot" );
PrecacheModel ("models/miniturret.mdl");
}
void CNPC_MiniTurret::Shoot(Vector &vecSrc, Vector &vecDirToEnemy)
{
FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, m_iAmmoType, 1 );
CPASAttenuationFilter filter( this );
EmitSound( filter, entindex(), "Turret.Shoot" );
DoMuzzleFlash();
}
//=========================================================
// Sentry gun - smallest turret, placed near grunt entrenchments
//=========================================================
class CNPC_Sentry : public CNPC_BaseTurret
{
DECLARE_CLASS( CNPC_Sentry, CNPC_BaseTurret );
public:
void Spawn( );
void Precache(void);
// other functions
void Shoot(Vector &vecSrc, Vector &vecDirToEnemy);
int OnTakeDamage_Alive(const CTakeDamageInfo &info);
void Event_Killed( const CTakeDamageInfo &info );
void SentryTouch( CBaseEntity *pOther );
DECLARE_DATADESC();
private:
bool m_bStartedDeploy; //set to true when the turret begins its deploy
};
BEGIN_DATADESC( CNPC_Sentry )
DEFINE_ENTITYFUNC( SentryTouch ),
DEFINE_FIELD( m_bStartedDeploy, FIELD_BOOLEAN ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( monster_sentry, CNPC_Sentry );
void CNPC_Sentry::Precache()
{
BaseClass::Precache( );
m_iAmmoType = GetAmmoDef()->Index("9mmRound");
PrecacheScriptSound( "Sentry.Shoot" );
PrecacheScriptSound( "Sentry.Die" );
PrecacheModel ("models/sentry.mdl");
}
void CNPC_Sentry::Spawn()
{
Precache( );
SetModel( "models/sentry.mdl" );
m_iHealth = sk_sentry_health.GetFloat();
m_HackedGunPos = Vector( 0, 0, 48 );
SetViewOffset( Vector(0,0,48) );
m_flMaxWait = 1E6;
m_flMaxSpin = 1E6;
BaseClass::Spawn();
SetSequence( TURRET_ANIM_RETIRE );
SetCycle( 0.0 );
m_flPlaybackRate = 0.0;
m_iRetractHeight = 64;
m_iDeployHeight = 64;
m_iMinPitch = -60;
UTIL_SetSize(this, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight));
SetTouch(&CNPC_Sentry::SentryTouch);
SetThink(&CNPC_Sentry::Initialize);
SetNextThink(gpGlobals->curtime + 0.3);
m_bStartedDeploy = false;
}
void CNPC_Sentry::Shoot(Vector &vecSrc, Vector &vecDirToEnemy)
{
FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, m_iAmmoType, 1 );
CPASAttenuationFilter filter( this );
EmitSound( filter, entindex(), "Sentry.Shoot" );
DoMuzzleFlash();
}
int CNPC_Sentry::OnTakeDamage_Alive(const CTakeDamageInfo &info)
{
if ( m_takedamage == DAMAGE_NO )
return 0;
if (!m_iOn && !m_bStartedDeploy)
{
m_bStartedDeploy = true;
SetThink( &CNPC_Sentry::Deploy );
SetUse( NULL );
SetNextThink( gpGlobals->curtime + 0.1 );
}
m_iHealth -= info.GetDamage();
if (m_iHealth <= 0)
{
m_iHealth = 0;
m_takedamage = DAMAGE_NO;
m_flDamageTime = gpGlobals->curtime;
SetUse(NULL);
SetThink(&CNPC_BaseTurret::TurretDeath); //should be SentryDeath ?
SetNextThink( gpGlobals->curtime + 0.1 );
m_OnDeactivate.FireOutput(this, this);
return 0;
}
return 1;
}
void CNPC_Sentry::Event_Killed( const CTakeDamageInfo &info )
{
CPASAttenuationFilter filter( this );
EmitSound( filter, entindex(), "Sentry.Die" );
StopSound( entindex(), "Turret.Spinup" );
AddSolidFlags( FSOLID_NOT_STANDABLE );
Vector vecSrc;
QAngle vecAng;
GetAttachment( 2, vecSrc, vecAng );
te->Smoke( filter, 0.0, &vecSrc,
g_sModelIndexSmoke,
2.5,
10 );
g_pEffects->Sparks( vecSrc );
BaseClass::Event_Killed( info );
}
void CNPC_Sentry::SentryTouch( CBaseEntity *pOther )
{
//trigger the sentry to turn on if a monster or player touches it
if ( pOther && (pOther->IsPlayer() || FBitSet ( pOther->GetFlags(), FL_NPC )) )
{
CTakeDamageInfo info;
info.SetAttacker( pOther );
info.SetInflictor( pOther );
info.SetDamage( 0 );
TakeDamage(info);
}
}