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.
1178 lines
32 KiB
1178 lines
32 KiB
//========= 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; |
|
}
|
|
|