Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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.

1179 lines
32 KiB

5 years ago
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Defender's sentrygun objects
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "tf_player.h"
#include "tf_team.h"
#include "tf_obj.h"
#include "tf_obj_sentrygun.h"
#include "tf_obj_dragonsteeth.h"
#include "tf_obj_tower.h"
#include "tf_obj_sandbag_bunker.h"
#include "tf_obj_bunker.h"
#include "tf_obj_mapdefined.h"
#include "tf_gamerules.h"
#include "gamerules.h"
#include "ammodef.h"
#include "plasmaprojectile.h"
#include "tf_class_recon.h"
#include "sendproxy.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "grenade_rocket.h"
#include "VGuiScreen.h"
extern short g_sModelIndexFireball;
#define MAX_SUPPRESSION_TIME 5.0 // Max amount of time to supress for
// Sentrygun size
#define SENTRYGUN_MINS Vector(-16, -16, 0)
#define SENTRYGUN_MAXS Vector( 16, 16, 65)
//=============================================================================
// Link and precache all the sentrygun types
LINK_ENTITY_TO_CLASS(obj_sentrygun_plasma, CObjectSentrygunPlasma);
LINK_ENTITY_TO_CLASS(obj_sentrygun_rocketlauncher, CObjectSentrygunRocketlauncher);
PRECACHE_REGISTER(obj_sentrygun_plasma);
PRECACHE_REGISTER(obj_sentrygun_rocketlauncher);
//=============================================================================
// Data description
BEGIN_DATADESC( CObjectSentrygun )
DEFINE_THINKFUNC( SentryRotate ),
DEFINE_THINKFUNC( Attack ),
END_DATADESC()
// Sentrygun team-only vars.
BEGIN_SEND_TABLE_NOBASE( CObjectSentrygun, DT_SentrygunTeamOnlyVars )
SendPropInt( SENDINFO(m_iAmmo), 9 ),
END_SEND_TABLE()
#define SENTRY_ANIMATION_PARITY_BITS 2
IMPLEMENT_SERVERCLASS_ST(CObjectSentrygun, DT_ObjectSentrygun)
SendPropInt( SENDINFO( m_iBaseTurnRate ), 3, SPROP_UNSIGNED ),
SendPropEHandle( SENDINFO( m_hEnemy ) ),
SendPropDataTable( "teamonly", 0, &REFERENCE_SEND_TABLE( DT_SentrygunTeamOnlyVars ), SendProxy_OnlyToTeam ),
SendPropInt( SENDINFO(m_bTurtled), 1, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_nAnimationParity ), (1<<SENTRY_ANIMATION_PARITY_BITS), SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_nOrientationParity ), 1, SPROP_UNSIGNED ),
END_SEND_TABLE();
ConVar obj_sentrygun_plasma_health( "obj_sentrygun_plasma_health","200", FCVAR_NONE, "Plasma sentrygun health" );
ConVar obj_sentrygun_plasma_range( "obj_sentrygun_plasma_range","1500", FCVAR_NONE, "Plasma sentrygun's shot range" );
ConVar obj_sentrygun_rocketlauncher_health( "obj_sentrygun_rocketlauncher_health","250", FCVAR_NONE, "Rocket Launcher sentrygun health" );
ConVar obj_sentrygun_range_mid( "obj_sentrygun_range_mid","768", FCVAR_NONE, "Sentrygun's mid targeting range. Targets beyond this need to be in the viewcone to be seen." );
ConVar obj_sentrygun_range_max( "obj_sentrygun_range_max","1600", FCVAR_NONE, "Sentrygun's max targeting range." );
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CObjectSentrygun::CObjectSentrygun( void )
{
UseClientSideAnimation();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectSentrygun::Spawn( void )
{
m_bSmarter = false;
m_bSensors = false;
m_bSuppressing = false;
m_bTurtled = false;
m_bTurtling = false;
m_flTurtlingFinishedAt = 0;
SetViewOffset( Vector(0,0,22) );
// Setup
SetSolid( SOLID_BBOX );
AddSolidFlags( FSOLID_NOT_STANDABLE );
UTIL_SetSize(this, SENTRYGUN_MINS, SENTRYGUN_MAXS);
BaseClass::Spawn();
// Start searching for enemies
m_hEnemy = NULL;
m_hDesignatedEnemy = NULL;
SetThink( SentryRotate );
SetNextThink( gpGlobals->curtime + 0.5f );
m_flNextLook = gpGlobals->curtime;
SetTechnology( false, false );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectSentrygun::Precache()
{
PrecacheModel( SG_PLASMA_MODEL );
PrecacheModel( SG_ROCKETLAUNCHER_MODEL );
PrecacheVGuiScreen( "screen_obj_sentrygun" );
PrecacheScriptSound( "ObjectSentrygun.ResupplyAmmo" );
PrecacheScriptSound( "ObjectSentrygun.Idle" );
PrecacheScriptSound( "ObjectSentrygun.FoundTarget" );
PrecacheScriptSound( "ObjectSentrygun.Turtle" );
PrecacheScriptSound( "ObjectSentrygun.UnTurtle" );
PrecacheScriptSound( "ObjectSentrygun.Fire" );
PrecacheScriptSound( "ObjectSentrygunRocketlauncher.Fire" );
}
//-----------------------------------------------------------------------------
// Purpose: Gets info about the control panels
//-----------------------------------------------------------------------------
void CObjectSentrygun::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
{
pPanelName = "screen_obj_sentrygun";
}
//-----------------------------------------------------------------------------
// Purpose: Hide the base of the gun if it's on an attachment
//-----------------------------------------------------------------------------
void CObjectSentrygun::SetupAttachedVersion( void )
{
BaseClass::SetupAttachedVersion();
SetBodygroup( 1, true );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectSentrygun::SetupUnattachedVersion( void )
{
BaseClass::SetupUnattachedVersion();
SetBodygroup( 1, false );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectSentrygun::FinishedBuilding( void )
{
BaseClass::FinishedBuilding();
// Orient it
m_vecCurAngles.y = UTIL_AngleMod( GetLocalAngles().y );
RecomputeOrientation();
}
//-----------------------------------------------------------------------------
// Called when a rotation happens
//-----------------------------------------------------------------------------
void CObjectSentrygun::RecomputeOrientation( )
{
ResetOrientation();
m_iRightBound = UTIL_AngleMod( m_vecCurAngles.y - 50);
m_iLeftBound = UTIL_AngleMod( m_vecCurAngles.y + 50);
if ( m_iRightBound > m_iLeftBound )
{
m_iRightBound = m_iLeftBound;
m_iLeftBound = UTIL_AngleMod( m_vecCurAngles.y - 50);
}
// Start it rotating
m_vecGoalAngles.y = m_iRightBound;
m_vecGoalAngles.x = m_vecCurAngles.x = 0;
m_bTurningRight = true;
}
//-----------------------------------------------------------------------------
// Purpose: Handle commands sent from vgui panels on the client
//-----------------------------------------------------------------------------
bool CObjectSentrygun::ClientCommand( CBaseTFPlayer *pPlayer, const char *pCmd, ICommandArguments *pArg )
{
if ( FStrEq( pCmd, "addammo" ) )
{
if ( TakeAmmoFrom( pPlayer ) )
{
// We got some ammo, so make a sound
CPASAttenuationFilter filter( pPlayer, "ObjectSentrygun.ResupplyAmmo" );
EmitSound( filter, pPlayer->entindex(), "ObjectSentrygun.ResupplyAmmo" );
}
return true;
}
return BaseClass::ClientCommand( pPlayer, pCmd, pArg );
}
//-----------------------------------------------------------------------------
// Purpose: Return true if the player gave the sentrygun some ammo
//-----------------------------------------------------------------------------
bool CObjectSentrygun::TakeAmmoFrom( CBaseTFPlayer *pPlayer )
{
// Do I need ammo?
if ( m_iAmmo >= m_iMaxAmmo )
return false;
// Try to fill the sentry up a bit at a time
int iRoundsToGive = 10;
iRoundsToGive = MIN( iRoundsToGive, (m_iMaxAmmo - m_iAmmo) );
iRoundsToGive = MIN( iRoundsToGive, pPlayer->GetAmmoCount( m_iAmmoType ) );
if ( !iRoundsToGive )
return false;
// Give me the ammo
pPlayer->RemoveAmmo( iRoundsToGive, m_iAmmoType );
m_iAmmo += iRoundsToGive;
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Resupply has taken damage
//-----------------------------------------------------------------------------
int CObjectSentrygun::OnTakeDamage( const CTakeDamageInfo &info )
{
int iDamage = BaseClass::OnTakeDamage( info );
return iDamage;
}
//-----------------------------------------------------------------------------
// Purpose: Object has been blown up
//-----------------------------------------------------------------------------
void CObjectSentrygun::Killed( void )
{
// Tell the player he's lost this resupply beacon
if ( GetOwner() )
{
GetOwner()->OwnedObjectDestroyed( this );
}
BaseClass::Killed();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectSentrygun::RestartAnimation( void )
{
// Increment and mask parity counter
m_nAnimationParity += 1;
m_nAnimationParity &= ( (1<<SENTRY_ANIMATION_PARITY_BITS) - 1 );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectSentrygun::ResetOrientation()
{
m_nOrientationParity = !m_nOrientationParity;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectSentrygun::SetSentryAnim( TFTURRET_ANIM anim )
{
if ( GetSequence() != anim )
{
switch(anim)
{
case TFTURRET_ANIM_FIRE:
case TFTURRET_ANIM_SPIN:
if ( GetSequence() != TFTURRET_ANIM_FIRE && GetSequence() != TFTURRET_ANIM_SPIN )
{
RestartAnimation();
}
break;
default:
RestartAnimation();
break;
}
ResetSequence( anim );
}
}
//-----------------------------------------------------------------------------
// Purpose: Handle movement of the turret
//-----------------------------------------------------------------------------
bool CObjectSentrygun::MoveTurret(void)
{
bool bMoved = 0;
// any x movement?
if ( m_vecCurAngles.x != m_vecGoalAngles.x )
{
float flDir = m_vecGoalAngles.x > m_vecCurAngles.x ? 1 : -1 ;
m_vecCurAngles.x += 0.1 * (m_iBaseTurnRate * 5) * 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;
}
m_fBoneYRotator = 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);
bool bReversed = false;
if (flDist > 180)
{
flDist = 360 - flDist;
flDir = -flDir;
bReversed = true;
}
if (m_hEnemy == NULL && !m_bSuppressing)
{
if (flDist > 30)
{
if (m_fTurnRate < m_iBaseTurnRate * 20)
{
m_fTurnRate += m_iBaseTurnRate;
}
}
else
{
// Slow down
if ( m_fTurnRate > (m_iBaseTurnRate * 5) )
m_fTurnRate -= m_iBaseTurnRate;
}
}
else
{
// When tracking enemies, move faster and don't slow
if (flDist > 30)
{
if (m_fTurnRate < m_iBaseTurnRate * 30)
{
m_fTurnRate += m_iBaseTurnRate * 3;
}
}
}
m_vecCurAngles.y += 0.1 * m_fTurnRate * flDir;
// if we passed over the goal, peg right to it now
if (flDir == -1)
{
if ( (bReversed == false && m_vecGoalAngles.y > m_vecCurAngles.y) || (bReversed == true && m_vecGoalAngles.y < m_vecCurAngles.y) )
m_vecCurAngles.y = m_vecGoalAngles.y;
}
else
{
if ( (bReversed == false && m_vecGoalAngles.y < m_vecCurAngles.y) || (bReversed == true && m_vecGoalAngles.y > m_vecCurAngles.y) )
m_vecCurAngles.y = m_vecGoalAngles.y;
}
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;
m_fBoneXRotator = m_vecCurAngles.y - GetLocalAngles().y;
bMoved = 1;
}
if ( !bMoved || !m_fTurnRate )
{
m_fTurnRate = m_iBaseTurnRate;
}
return bMoved;
}
//-----------------------------------------------------------------------------
// Purpose: Returns true is the passed ent is in the caller's forward view cone.
// The dot product is performed in 2d, making the view cone infinitely tall.
//-----------------------------------------------------------------------------
bool CObjectSentrygun::FInViewCone( CBaseEntity *pEntity )
{
float flDot;
Vector vecFacingDir;
AngleVectors( m_vecCurAngles, &vecFacingDir );
Vector vecLOS = ( pEntity->GetAbsOrigin() - GetAbsOrigin() );
flDot = DotProduct( vecLOS , vecFacingDir );
if ( flDot > VIEW_FIELD_NARROW )
return true;
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Check the shield's values
//-----------------------------------------------------------------------------
void CObjectSentrygun::CheckShield( void )
{
if ( m_nRenderFX == kRenderFxNone )
return;
}
//-----------------------------------------------------------------------------
// Purpose: Rotate and scan for targets
//-----------------------------------------------------------------------------
void CObjectSentrygun::SentryRotate( void )
{
SetNextThink( gpGlobals->curtime + 0.1f );
CheckShield();
// I can't do anything if I'm not active
if ( !ShouldBeActive() )
return;
// If we're turtling, see if we're finished yet
if ( IsTurtling() )
{
if ( m_flTurtlingFinishedAt <= gpGlobals->curtime )
{
m_bTurtling = false;
if ( m_bTurtled )
{
AddSolidFlags( FSOLID_NOT_SOLID );
m_takedamage = DAMAGE_NO;
}
}
return;
}
// Turtling sentryguns don't think
if ( IsTurtled() )
return;
// animate
SetSentryAnim( TFTURRET_ANIM_SPIN );
// Abort if it's not time to search for enemies
if ( m_flNextLook < gpGlobals->curtime )
{
m_flNextLook = gpGlobals->curtime + 1.0;
// Look for a target
m_hEnemy = FindTarget();
if ( m_hEnemy != NULL )
{
FoundTarget();
return;
}
}
// Rotate
if ( !MoveTurret() )
{
// Play a sound occasionally
if ( random->RandomFloat(0, 1) < 0.02 )
{
EmitSound( "ObjectSentrygun.Idle" );
}
// Switch rotation direction
if (m_bTurningRight)
{
m_bTurningRight = false;
m_vecGoalAngles.y = m_iLeftBound;
}
else
{
m_bTurningRight = true;
m_vecGoalAngles.y = m_iRightBound;
}
// Randomly look up and down a bit
if ( random->RandomFloat(0, 1) < 0.3 )
{
m_vecGoalAngles.x = (int)random->RandomFloat(-10,10);
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Check to see if there's a valid target in sight
//-----------------------------------------------------------------------------
CBaseEntity *CObjectSentrygun::FindTarget( void )
{
CBaseEntity *pHighestPriorityTarget = NULL;
float fHighestPriority = 0;
// If I have a designated enemy, and it's valid, assume it will be the target, unless something higher priority shows up.
if ( m_hDesignatedEnemy.Get() && ValidTarget( m_hDesignatedEnemy) )
{
fHighestPriority = GetPriority( m_hDesignatedEnemy );
pHighestPriorityTarget = m_hDesignatedEnemy;
}
// Find a target.
CBaseEntity *pList[1024];
Vector delta( 2048, 2048, 2048 );
int count = UTIL_EntitiesInBox( pList, 1024, GetAbsOrigin() - delta, GetAbsOrigin() + delta, FL_CLIENT|FL_NPC|FL_OBJECT );
for ( int i = 0; i < count; i++ )
{
if( !pList[i] )
continue;
if ( pList[i] == this )
continue;
float fPriority = GetPriority( pList[i] );
if( !pHighestPriorityTarget || (fPriority > fHighestPriority) )
{
if ( ValidTarget( pList[i] ) )
{
pHighestPriorityTarget = pList[i];
fHighestPriority = fPriority;
}
}
}
return pHighestPriorityTarget;
}
//-----------------------------------------------------------------------------
// Purpose: Get the priority of the target
//-----------------------------------------------------------------------------
float CObjectSentrygun::GetPriority( CBaseEntity *pTarget )
{
// Players
if ( pTarget->IsPlayer() )
{
return 20;
}
// NPCs
if ( pTarget->GetFlags() & FL_NPC )
return 10;
// Objects
if ( pTarget->Classify() == CLASS_MILITARY )
{
// Sentryguns are highest priority
CBaseObject *pObject = (CBaseObject *)pTarget;
if ( pObject->IsSentrygun() )
return 5;
return 2;
}
return 1;
}
//-----------------------------------------------------------------------------
// Purpose: Returns the sentry targeting range the target is in
//-----------------------------------------------------------------------------
int CObjectSentrygun::Range( CBaseEntity *pTarget )
{
Vector vecOrg = EyePosition();
Vector vecTargetOrg = pTarget->EyePosition();
int iDist = ( vecTargetOrg - vecOrg ).Length();
// Sensors increase targeting range
if ( m_bSensors )
{
iDist *= 0.75;
}
if (iDist < obj_sentrygun_range_mid.GetFloat() )
return RANGE_NEAR;
if (iDist < obj_sentrygun_range_max.GetFloat() )
return RANGE_MID;
return RANGE_FAR;
}
//-----------------------------------------------------------------------------
// Purpose: Check to see if a target's valid
//-----------------------------------------------------------------------------
bool CObjectSentrygun::ValidTarget( CBaseEntity *pTarget )
{
// Make sure we aren't borked:
if ( !pTarget )
return false;
// Don't attack things that have already died
if ( !pTarget->IsAlive() )
return false;
// Don't attack things that cant be hurt
if ( pTarget->m_takedamage != DAMAGE_YES )
return false;
// Don't shoot at objects on the neutral team.
if( !pTarget->IsInAnyTeam() )
return false;
// Ignore camoed players
if ( pTarget->IsPlayer() )
{
CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)pTarget;
if ( InSameTeam( pPlayer ) )
return false;
if ( pPlayer->IsClass( TFCLASS_UNDECIDED ) )
return false;
if ( pPlayer->GetCamouflageAmount() >= 30.0f )
return false;
}
else
{
// Only attack enemies.
if ( InSameTeam( pTarget) )
return false;
}
if ( pTarget->GetFlags() & FL_NOTARGET )
return false;
if ( !FVisible(pTarget) )
return false;
// Ignore certain enemy infrastructure type objects:
CBaseObject *pObject = dynamic_cast< CBaseObject* >(pTarget);
if ( pObject )
{
// Make sure it's not placing
if ( pObject->IsPlacing() )
return false;
// Ignore upgrades
if ( pObject->IsAnUpgrade() )
return false;
// Ignore defensive structures
if ( IsObjectADefensiveBuilding( pObject->GetType() ) )
return false;
// Ignore mapdefined objects
if ( pObject->GetType() == OBJ_MAPDEFINED )
return false;
}
// Make sure there's nothing inbetween us
Vector vecSrc = EyePosition();
// Now make sure there isn't something other than team players in the way.
trace_t tr;
CTraceFilterSimpleList sentryFilter( COLLISION_GROUP_NONE );
sentryFilter.AddEntityToIgnore( GetOwner() );
sentryFilter.AddEntityToIgnore( this );
sentryFilter.AddEntityToIgnore( GetMoveParent() );
UTIL_TraceLine( vecSrc, pTarget->WorldSpaceCenter(), MASK_SHOT, &sentryFilter, &tr );
CBaseEntity *pEntity = tr.m_pEnt;
if ( (tr.fraction < 1.0) && ( pEntity != pTarget ) )
return false;
int iRange = Range(pTarget);
if ( iRange == RANGE_FAR )
return false;
// Better sensors allow them to track irrespective of facing
if ( iRange == RANGE_MID && (!FInViewCone(pTarget) && !m_bSensors) )
return false;
// Don't shoot at turtled sentry guns.
CObjectSentrygun *pSentry = dynamic_cast< CObjectSentrygun* >( pTarget );
if ( pSentry && pSentry->IsTurtled() )
return false;
// Don't shoot at targets blocked by enemy shields
bool bBlocked = TFGameRules()->IsBlockedByEnemyShields( GetAbsOrigin(), pTarget->GetAbsOrigin(), GetTeamNumber() );
if( bBlocked )
return false;
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Return true if the sentry has some ammo to fire
//-----------------------------------------------------------------------------
bool CObjectSentrygun::HasAmmo( void )
{
return (m_iAmmo > 0);
}
//-----------------------------------------------------------------------------
// Purpose: We've found a valid target
//-----------------------------------------------------------------------------
void CObjectSentrygun::FoundTarget()
{
if ( HasAmmo() )
{
EmitSound( "ObjectSentrygun.FoundTarget" );
}
SetThink( Attack );
SetNextThink( gpGlobals->curtime + 0.1 );
}
//-----------------------------------------------------------------------------
// Purpose: Make sure our target is still valid, and if so, fire at it
//-----------------------------------------------------------------------------
void CObjectSentrygun::Attack( void )
{
SetNextThink( gpGlobals->curtime + 0.1f );
CheckShield();
// Turtling sentryguns don't attack
if ( IsTurtled() )
{
SetThink( SentryRotate );
return;
}
// I can't do anything if I'm not active
if ( !ShouldBeActive() )
return;
// Make sure our target is still valid and that we've still got ammo
if ( m_bSuppressing && HasAmmo() )
{
if ( gpGlobals->curtime > (m_flStartedSuppressing + MAX_SUPPRESSION_TIME) )
{
m_bSuppressing = false;
SetThink( SentryRotate );
return;
}
// Check to see if we can find a valid target to switch to
m_hEnemy = FindTarget();
if ( m_hEnemy )
{
// Stop supressing and target this new enemy
m_bSuppressing = false;
m_flStartedSuppressing = 0;
FoundTarget();
}
}
else if ( !ValidTarget(m_hEnemy) || HasAmmo() == false )
{
m_hEnemy = NULL;
// Smarter sentryguns will suppression fire for a few seconds
if ( m_bSmarter && WillSuppress() && HasAmmo() )
{
m_bSuppressing = true;
m_flStartedSuppressing = gpGlobals->curtime;
}
else
{
RecomputeOrientation();
SetThink( SentryRotate );
return;
}
}
// If I have a designated enemy, and it's valid, target it instead of my current enemy
if ( m_hDesignatedEnemy.Get() && (m_hEnemy.Get() != m_hDesignatedEnemy.Get()) )
{
if ( ValidTarget( m_hDesignatedEnemy ) )
{
m_hEnemy = m_hDesignatedEnemy;
FoundTarget();
}
}
// Figure out where we're firing at
Vector vecMid = EyePosition();
if ( m_bSuppressing )
{
// Suppression fire should just shoot at it's last known position
m_vecFireTarget = m_vecLastKnownPosition;
}
else
{
m_vecFireTarget = m_hEnemy->BodyTarget( vecMid );
m_vecLastKnownPosition = m_vecFireTarget;
}
Vector vecDirToEnemy = m_vecFireTarget - vecMid;
QAngle angToTarget;
VectorAngles(vecDirToEnemy, angToTarget);
angToTarget.y = UTIL_AngleMod( angToTarget.y );
if (angToTarget.x < -180)
angToTarget.x += 360;
if (angToTarget.x > 180)
angToTarget.x -= 360;
// now all numbers should be in [1...360]
// pin to turret limitations to [-50...50]
if (angToTarget.x > 50)
angToTarget.x = 50;
else if (angToTarget.x < -50)
angToTarget.x = -50;
m_vecGoalAngles.y = angToTarget.y;
m_vecGoalAngles.x = angToTarget.x;
MoveTurret();
// Fire on the target if it's within 10 units of being aimed right at it
if ( m_flNextAttack <= gpGlobals->curtime && (m_vecGoalAngles - m_vecCurAngles).Length() <= 15 )
{
// Suppressing turrets fire randomly
if ( m_bSuppressing )
{
if ( random->RandomInt( 0,1 ) != 0 )
{
m_flNextAttack = gpGlobals->curtime + 0.5;
return;
}
}
// See if the object or its owner is taking emp damage, if so, don't fire
if ( ShouldBeActive() )
{
Fire();
}
}
else
{
SetSentryAnim( TFTURRET_ANIM_SPIN );
}
}
//-----------------------------------------------------------------------------
// Purpose: Called when a rotation happens
//-----------------------------------------------------------------------------
void CObjectSentrygun::ObjectMoved( void )
{
m_vecCurAngles.y = UTIL_AngleMod( GetLocalAngles().y );
RecomputeOrientation();
m_fBoneXRotator = 0;
BaseClass::ObjectMoved();
}
//-----------------------------------------------------------------------------
// Purpose: Fire at our target
//-----------------------------------------------------------------------------
bool CObjectSentrygun::Fire( void )
{
// Base sentry doesn't know how to fire
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Tell this sentrygun to attack the following target, if it can
//-----------------------------------------------------------------------------
void CObjectSentrygun::DesignateTarget( CBaseEntity *pTarget )
{
m_hDesignatedEnemy = pTarget;
if ( m_hEnemy.Get() != m_hDesignatedEnemy.Get() )
{
m_hEnemy = NULL;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CObjectSentrygun::IsTurtled( void )
{
return m_bTurtled;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CObjectSentrygun::IsTurtling( void )
{
return m_bTurtling;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectSentrygun::ToggleTurtle( void )
{
// Don't turtle while building
if ( IsPlacing() || IsBuilding() || IsTurtling() )
return;
// Don't turtle if I'm built on anything
if ( GetMoveParent() )
return;
// Swap turtle state
if ( IsTurtled() )
{
UnTurtle();
}
else
{
Turtle();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectSentrygun::Turtle( void )
{
m_bTurtled = true;
m_bTurtling = true;
m_flTurtlingFinishedAt = gpGlobals->curtime + SENTRY_TURTLE_TIME;
EmitSound( "ObjectSentrygun.Turtle" );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectSentrygun::UnTurtle( void )
{
// Make sure there's enough room to unturtle
// NJS: this seems a bit hacky and returns false positives sometimes, for now we're just assuming that if it can turtle, it can also unturtle.
//trace_t tr;
//UTIL_TraceHull( GetAbsOrigin(), GetAbsOrigin(), WorldAlignMins() - Vector( 4,4,4 ), WorldAlignMaxs() + Vector( 4,4,4 ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
//if ( tr.startsolid || tr.allsolid )
// return;
m_bTurtled = false;
m_bTurtling = true;
m_flTurtlingFinishedAt = gpGlobals->curtime + SENTRY_TURTLE_TIME;
EmitSound( "ObjectSentrygun.UnTurtle" );
RemoveSolidFlags( FSOLID_NOT_SOLID );
m_takedamage = DAMAGE_YES;
}
//-----------------------------------------------------------------------------
// Purpose: Set the technology levels of the sentrygun
//-----------------------------------------------------------------------------
void CObjectSentrygun::SetTechnology( bool bSmarter, bool bSensors )
{
m_bSmarter = bSmarter;
m_bSensors = bSensors;
// Smarter sentryguns turn faster
if ( m_bSmarter )
{
m_iBaseTurnRate = 6;
}
else
{
m_iBaseTurnRate = 4;
}
}
//========================================================================================================
// SENTRYGUN TYPES
//========================================================================================================
IMPLEMENT_SERVERCLASS_ST(CObjectSentrygunPlasma, DT_ObjectSentrygunPlasma)
END_SEND_TABLE();
IMPLEMENT_SERVERCLASS_ST(CObjectSentrygunRocketlauncher, DT_ObjectSentrygunRocketlauncher)
END_SEND_TABLE();
void CObjectSentrygunPlasma::Spawn( void )
{
m_iHealth = obj_sentrygun_plasma_health.GetInt();
SetModel( SG_PLASMA_MODEL );
BaseClass::Spawn();
SetType( OBJ_SENTRYGUN_PLASMA );
m_flNextAmmoRecharge = gpGlobals->curtime + PLASMA_SENTRYGUN_RECHARGE_TIME;
m_nBurstCount = PLASMA_SENTRY_BURST_COUNT;
m_iAmmo = m_iMaxAmmo = 50;
m_iAmmoType = GetAmmoDef()->Index( "ShotgunEnergy" );
}
void CObjectSentrygunRocketlauncher::Spawn( void )
{
m_iHealth = obj_sentrygun_rocketlauncher_health.GetInt();
SetModel( SG_ROCKETLAUNCHER_MODEL );
BaseClass::Spawn();
SetType( OBJ_SENTRYGUN_ROCKET_LAUNCHER );
m_iAmmo = m_iMaxAmmo = 50;
m_iAmmoType = GetAmmoDef()->Index( "Rockets" );
}
//-----------------------------------------------------------------------------
// Purpose: Plasma sentrygun's fire
//-----------------------------------------------------------------------------
bool CObjectSentrygunPlasma::Fire( void )
{
Vector vecSrc = EyePosition();
Vector vecTarget = m_vecFireTarget;
Vector vecAim;
QAngle vecAng;
// Because the plasma sentrygun always thinks it has ammo (see below)
// we might not have ammo here, in which case we should just abort.
if ( !m_iAmmo )
return true;
GetAttachment( "muzzle", vecSrc, vecAng );
// Get the distance to the target
float targetDist = (vecTarget - vecSrc).Length();
float targetTime = targetDist / PLASMA_VELOCITY;
// If we're not suppressing, calculate where the target's going to be in that time
if ( !m_bSuppressing )
{
Vector vecVelocity = m_hEnemy->GetSmoothedVelocity();
// Dampen Z velocity to prevent jumping people screwing the aim
vecVelocity.z *= 0.25;
// Get the target point to aim for
Vector vecEnd = vecTarget + ( vecVelocity * targetTime );
vecAim = (vecEnd - vecSrc);
}
else
{
vecAim = (vecTarget - vecSrc);
}
VectorNormalize( vecAim );
int damageType = GetAmmoDef()->DamageType( m_iAmmoType );
CBasePlasmaProjectile *pPlasma = CBasePlasmaProjectile::Create( vecSrc + (vecAim * 32), vecAim, damageType, this );
pPlasma->SetDamage( 15 );
pPlasma->SetMaxRange( obj_sentrygun_plasma_range.GetFloat() );
pPlasma->m_hOwner = GetBuilder();
EmitSound( "ObjectSentrygun.Fire" );
SetSentryAnim( TFTURRET_ANIM_FIRE );
DoMuzzleFlash();
m_iAmmo -= 1;
float flAttackTime;
if (--m_nBurstCount > 0)
{
flAttackTime = 0.2f;
}
else
{
flAttackTime = random->RandomFloat( 1.0f, 2.0f );
m_nBurstCount = PLASMA_SENTRY_BURST_COUNT + random->RandomInt( 0, PLASMA_SENTRY_BURST_COUNT );
}
// If I'm EMPed, slow the firing rate down
if ( HasPowerup(POWERUP_EMP) )
{
flAttackTime *= 3;
}
m_flNextAttack = gpGlobals->curtime + flAttackTime;
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Plasma sentry regenerates ammo, so always assume it has ammo left.
// This is to prevent it from continually unlocking & relocking when it's
// ammo is flickering between 0 and 1.
//-----------------------------------------------------------------------------
bool CObjectSentrygunPlasma::HasAmmo( void )
{
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Plasma sentrygun recharges it's ammo
//-----------------------------------------------------------------------------
void CObjectSentrygunPlasma::CheckShield( void )
{
// ROBIN: Disabled recharging for now
/*
if ( m_flNextAmmoRecharge < gpGlobals->curtime )
{
if ( m_iAmmo < m_iMaxAmmo )
{
m_iAmmo++;
}
m_flNextAmmoRecharge = gpGlobals->curtime + PLASMA_SENTRYGUN_RECHARGE_TIME;
}
*/
BaseClass::CheckShield();
}
//-----------------------------------------------------------------------------
// Purpose: Rocket launcher sentrygun's fire
//-----------------------------------------------------------------------------
bool CObjectSentrygunRocketlauncher::Fire()
{
Vector vecSrc = EyePosition();
Vector vecTarget = m_vecFireTarget;
Vector vecAim;
// Get the distance to the target
float targetDist = (vecTarget - vecSrc).Length();
float targetTime = targetDist / ROCKET_VELOCITY;
// If we're not suppressing, calculate where the target's going to be in that time
if ( !m_bSuppressing )
{
Vector vecVelocity = m_hEnemy->GetSmoothedVelocity();
// Dampen velocity to prevent people rapidly switching strafe
vecVelocity *= 0.5;
// Dampen Z velocity to prevent jumping people screwing the aim
vecVelocity.z *= 0.5;
// Get the target point to aim for
Vector vecEnd = vecTarget + ( vecVelocity * targetTime );
vecAim = (vecEnd - vecSrc);
}
else
{
vecAim = (vecTarget - vecSrc);
}
CGrenadeRocket::Create( vecSrc, vecAim, edict(), GetOwner() );
EmitSound( "ObjectSentrygunRocketlauncher.Fire" );
m_iAmmo -= 1;
m_flNextAttack = gpGlobals->curtime + 1.5f;
return true;
}
void CObjectSentrygunRocketlauncher::SetTechnology( bool bSmarter, bool bSensors )
{
BaseClass::SetTechnology( bSmarter, bSensors );
m_iBaseTurnRate = 2;
}