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