//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Engineer's Sentrygun OMG
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"

#include "tf_obj_sentrygun.h"
#include "engine/IEngineSound.h"
#include "tf_player.h"
#include "tf_team.h"
#include "world.h"
#include "tf_projectile_rocket.h"
#include "te_effect_dispatch.h"
#include "tf_gamerules.h"
#include "ammodef.h"
#include "tf_weapon_wrench.h"
#include "tf_weapon_laser_pointer.h"
#include "tf_weapon_shotgun.h"
#include "bot/map_entities/tf_bot_hint_sentrygun.h"
#include "bot/tf_bot.h"
#include "nav_mesh/tf_nav_mesh.h"
#include "nav_pathfind.h"
#include "tf_weapon_knife.h"
#include "tf_logic_robot_destruction.h"
#include "tf_target_dummy.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"


extern bool IsInCommentaryMode();

extern ConVar tf_nav_in_combat_range;

// Ground placed version
#define SENTRY_MODEL_PLACEMENT			"models/buildables/sentry1_blueprint.mdl"
#define SENTRY_MODEL_LEVEL_1			"models/buildables/sentry1.mdl"
#define SENTRY_MODEL_LEVEL_1_UPGRADE	"models/buildables/sentry1_heavy.mdl"
#define SENTRY_MODEL_LEVEL_2			"models/buildables/sentry2.mdl"
#define SENTRY_MODEL_LEVEL_2_UPGRADE	"models/buildables/sentry2_heavy.mdl"
#define SENTRY_MODEL_LEVEL_3			"models/buildables/sentry3.mdl"
#define SENTRY_MODEL_LEVEL_3_UPGRADE	"models/buildables/sentry3_heavy.mdl"

#define SENTRY_ROCKET_MODEL "models/buildables/sentry3_rockets.mdl"

#define SENTRYGUN_MINS			Vector(-20, -20, 0)
#define SENTRYGUN_MAXS			Vector( 20,  20, 66)

#define SENTRYGUN_ADD_SHELLS	40
#define SENTRYGUN_ADD_ROCKETS	8

#define SENTRY_THINK_DELAY	0.05

#define	SENTRYGUN_CONTEXT	"SentrygunContext"

#define SENTRYGUN_RECENTLY_ATTACKED_TIME 2.0

#define SENTRYGUN_MINIGUN_RESIST_LVL_1		0.0
#define SENTRYGUN_MINIGUN_RESIST_LVL_2		0.15
#define SENTRYGUN_MINIGUN_RESIST_LVL_3		0.20

#define SENTRYGUN_SAPPER_OWNER_DAMAGE_MODIFIER	0.66f

#define SENTRYGUN_MAX_LEVEL_MINI			1
#define MINI_SENTRY_SCALE			0.75f
#define DISPOSABLE_SCALE			0.65f
#define SMALL_SENTRY_SCALE			0.80f

#define WRANGLER_DISABLE_TIME		3.0f

enum
{	
	SENTRYGUN_ATTACHMENT_MUZZLE = 0,
	SENTRYGUN_ATTACHMENT_MUZZLE_ALT,
	SENTRYGUN_ATTACHMENT_ROCKET,
};

enum target_ranges
{
	RANGE_MELEE,
	RANGE_NEAR,
	RANGE_MID,
	RANGE_FAR,
};

#define VECTOR_CONE_TF_SENTRY		Vector( 0.1, 0.1, 0 )

//-----------------------------------------------------------------------------
// Purpose: Only send the LocalWeaponData to the player carrying the weapon
//-----------------------------------------------------------------------------
void* SendProxy_SendLocalObjectDataTable( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID )
{
	// Get the weapon entity
	CBaseObject *pObject = (CBaseObject*)pVarData;
	if ( pObject )
	{
		// Only send this chunk of data to the player carrying this weapon
		CBasePlayer *pPlayer = ToBasePlayer( pObject->GetOwner() );
		if ( pPlayer )
		{
			pRecipients->SetOnly( pPlayer->GetClientIndex() );
			return (void*)pVarData;
		}
	}

	return NULL;
}
REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_SendLocalObjectDataTable );

BEGIN_NETWORK_TABLE_NOBASE( CObjectSentrygun, DT_SentrygunLocalData )
	SendPropInt( SENDINFO(m_iKills), -1, SPROP_VARINT | SPROP_CHANGES_OFTEN ),
	SendPropInt( SENDINFO(m_iAssists), -1, SPROP_VARINT | SPROP_CHANGES_OFTEN ),
END_NETWORK_TABLE()

IMPLEMENT_SERVERCLASS_ST( CObjectSentrygun, DT_ObjectSentrygun )
	SendPropInt( SENDINFO(m_iAmmoShells), -1, SPROP_VARINT | SPROP_CHANGES_OFTEN ),
	SendPropInt( SENDINFO(m_iAmmoRockets), -1, SPROP_VARINT | SPROP_CHANGES_OFTEN ),
	SendPropInt( SENDINFO(m_iState), Q_log2( SENTRY_NUM_STATES ) + 1, SPROP_UNSIGNED ),
	SendPropBool( SENDINFO( m_bPlayerControlled ) ),
	SendPropInt( SENDINFO( m_nShieldLevel ), 4, SPROP_UNSIGNED ),
	SendPropEHandle( SENDINFO( m_hEnemy ) ),
	SendPropEHandle( SENDINFO( m_hAutoAimTarget ) ),
	SendPropDataTable( "SentrygunLocalData", 0, &REFERENCE_SEND_TABLE( DT_SentrygunLocalData ), SendProxy_SendLocalObjectDataTable ),
END_SEND_TABLE()

BEGIN_DATADESC( CObjectSentrygun )
END_DATADESC()

LINK_ENTITY_TO_CLASS(obj_sentrygun, CObjectSentrygun);
PRECACHE_REGISTER(obj_sentrygun);

ConVar tf_sentrygun_damage( "tf_sentrygun_damage", "16", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
ConVar tf_sentrygun_mini_damage( "tf_sentrygun_mini_damage", "8", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
ConVar tf_sentrygun_ammocheat( "tf_sentrygun_ammocheat", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
extern ConVar tf_obj_upgrade_per_hit;
ConVar tf_sentrygun_newtarget_dist( "tf_sentrygun_newtarget_dist", "200", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
ConVar tf_sentrygun_metal_per_shell( "tf_sentrygun_metal_per_shell", "1", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
ConVar tf_sentrygun_metal_per_rocket( "tf_sentrygun_metal_per_rocket", "2", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
ConVar tf_sentrygun_notarget( "tf_sentrygun_notarget", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
ConVar tf_sentrygun_max_absorbed_damage_while_controlled_for_achievement( "tf_sentrygun_max_absorbed_damage_while_controlled_for_achievement", "500", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
ConVar tf_sentrygun_kill_after_redeploy_time_achievement( "tf_sentrygun_kill_after_redeploy_time_achievement", "10", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
extern ConVar tf_cheapobjects;

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CObjectSentrygun::CObjectSentrygun()
{
	// Don't bother with health modifying attributes here, because we don't have an owner yet, and it'll be stomped in FirstSpawn()
	int iHealth = GetMaxHealthForCurrentLevel();
	SetMaxHealth( iHealth );
	SetHealth( iHealth );
	SetType( OBJ_SENTRYGUN );

	m_bFireNextFrame = false;
	m_bFireRocketNextFrame = false;
	m_flAutoAimStartTime = 0.f;
	m_bPlayerControlled = false;
	m_iLifetimeShieldedDamage = 0;
	m_flFireRate = 1.f;
	m_flSentryRange = SENTRY_MAX_RANGE;
	m_nShieldLevel.Set( SHIELD_NONE );

	m_lastTeammateWrenchHit = NULL;
	m_lastTeammateWrenchHitTimer.Invalidate();

	m_flScaledSentry = 1.0f;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectSentrygun::Spawn()
{
	m_iPitchPoseParameter = -1;
	m_iYawPoseParameter = -1;

	SetModel( SENTRY_MODEL_PLACEMENT );

	// Rotate Details
	m_iRightBound = 45;
	m_iLeftBound = 315;
	m_iBaseTurnRate = 6;
	m_flFieldOfView = VIEW_FIELD_NARROW;

	// Give the Gun some ammo
	m_iAmmoShells = 0;
	m_iAmmoRockets = 0;

	float flMaxAmmoMult = 1.f;
	if ( GetOwner() )
	{
		CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwner(), flMaxAmmoMult, mvm_sentry_ammo );
	}

	m_iMaxAmmoShells = SENTRYGUN_MAX_SHELLS_1 * flMaxAmmoMult;
	m_iMaxAmmoRockets = SENTRYGUN_MAX_ROCKETS * flMaxAmmoMult;

	m_iAmmoType = GetAmmoDef()->Index( "TF_AMMO_PRIMARY" );

	// Start searching for enemies
	m_hEnemy = NULL;

	m_flHeavyBulletResist = SENTRYGUN_MINIGUN_RESIST_LVL_1;

	m_lastTeammateWrenchHit = NULL;
	m_lastTeammateWrenchHitTimer.Invalidate();

	BaseClass::Spawn();

	SetViewOffset( SENTRYGUN_EYE_OFFSET_LEVEL_1 );

	SetBuildingSize();

	m_iState.Set( SENTRY_STATE_INACTIVE );

	SetContextThink( &CObjectSentrygun::SentryThink, gpGlobals->curtime + SENTRY_THINK_DELAY, SENTRYGUN_CONTEXT );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectSentrygun::FirstSpawn()
{
	m_flLastAttackedTime = 0;

	int iHealth = GetMaxHealthForCurrentLevel();

	SetMaxHealth( iHealth );
	SetHealth( iHealth );

	BaseClass::FirstSpawn();
}

Vector CObjectSentrygun::GetEnemyAimPosition( CBaseEntity* pEnemy ) const
{
	// Default to pointing to the origin
	Vector vecPos = pEnemy->WorldSpaceCenter();

	CTFPlayer* pTFEnemy = ToTFPlayer( pEnemy );

	// This is expensive, so only do it if our target is in a state that requires it
	if ( pTFEnemy )
	{
		bool bShouldUseAccurateMethod = false; 

		int playerFlags = pTFEnemy->GetFlags();
		// Crouch jumping makes your box weird
		bShouldUseAccurateMethod |= !( playerFlags & FL_ONGROUND ) && ( playerFlags & FL_DUCKING );
		// Taunting can make your box weird
		bShouldUseAccurateMethod |= pTFEnemy->m_Shared.InCond( TF_COND_TAUNTING );

		if ( bShouldUseAccurateMethod )
		{
			// Use this bone as the the aim target
			int iSpineBone = pTFEnemy->LookupBone( "bip_spine_2" );
			if ( iSpineBone != -1 )
			{
				QAngle angles;
				pTFEnemy->GetBonePosition( iSpineBone, vecPos, angles );
			}
		}
	}

	return vecPos;
}

void CObjectSentrygun::SentryThink( void )
{
	m_flSentryRange = SENTRY_MAX_RANGE;
	if ( !IsDisposableBuilding() )
	{
		CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwner(), m_flSentryRange, mult_sentry_range );
	}

	switch( m_iState )
	{
	case SENTRY_STATE_INACTIVE:
	case SENTRY_STATE_UPGRADING:		// Base class handles this
		break;

	case SENTRY_STATE_SEARCHING:
		SentryRotate();
		break;

	case SENTRY_STATE_ATTACKING:
		Attack();
		break;

	default:
		Assert( 0 );
		break;
	}

	SetContextThink( &CObjectSentrygun::SentryThink, gpGlobals->curtime + SENTRY_THINK_DELAY, SENTRYGUN_CONTEXT );

	if ( m_nShieldLevel > 0 && (gpGlobals->curtime > m_flShieldFadeTime) )
	{
		m_nShieldLevel.Set( SHIELD_NONE );
		m_vecGoalAngles.x = 0;
	}

	// infinite ammo for enemy team in MvM mode
	if ( TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS )
	{
		m_iAmmoRockets = SENTRYGUN_MAX_ROCKETS;
		m_iMaxAmmoRockets = SENTRYGUN_MAX_ROCKETS;
		m_iAmmoShells = SENTRYGUN_MAX_SHELLS_3;
		m_iMaxAmmoShells = SENTRYGUN_MAX_SHELLS_3;
	}
}

void CObjectSentrygun::StartPlacement( CTFPlayer *pPlayer )
{
	BaseClass::StartPlacement( pPlayer );

	// Set my build size
	m_vecBuildMins = SENTRYGUN_MINS;
	m_vecBuildMaxs = SENTRYGUN_MAXS;
	m_vecBuildMins -= Vector( 4,4,0 );
	m_vecBuildMaxs += Vector( 4,4,0 );

	MakeMiniBuilding( pPlayer );
	MakeDisposableBuilding( pPlayer );
	MakeScaledBuilding( GetBuilder() );
}

//-----------------------------------------------------------------------------
// Purpose: Start building the object
//-----------------------------------------------------------------------------
bool CObjectSentrygun::StartBuilding( CBaseEntity *pBuilder )
{
	SetStartBuildingModel();

	// Have to re-call this in case the player changed their weapon
	// between StartPlacement and StartBuilding.
	MakeMiniBuilding( GetBuilder() );
	MakeDisposableBuilding( GetBuilder() );
	MakeScaledBuilding( GetBuilder() );

	if ( IsMiniBuilding() )
	{
		SetBodygroup( FindBodygroupByName( "mini_sentry_light" ), 1 );
	}

	CreateBuildPoints();

	SetPoseParameter( m_iPitchPoseParameter, 0.0 );
	SetPoseParameter( m_iYawPoseParameter, 0.0 );

	SetObjectMode( IsDisposableBuilding() ? MODE_SENTRYGUN_DISPOSABLE : MODE_SENTRYGUN_NORMAL );

	return BaseClass::StartBuilding( pBuilder );
}

void CObjectSentrygun::SetStartBuildingModel( void )
{
	SetModel( SENTRY_MODEL_LEVEL_1_UPGRADE );
	m_iState.Set( SENTRY_STATE_INACTIVE );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectSentrygun::MakeMiniBuilding( CTFPlayer* pPlayer )
{
	if ( !ShouldBeMiniBuilding( pPlayer ) || IsMiniBuilding() )
		return;

	BaseClass::MakeMiniBuilding( pPlayer );
	SetModelScale( MINI_SENTRY_SCALE );

	int iHealth = GetMaxHealthForCurrentLevel();

	SetMaxHealth( iHealth );
	SetHealth( iHealth / 2.0f );
	SetBuildingSize();
}

//-----------------------------------------------------------------------------
int CObjectSentrygun::GetMaxUpgradeLevel( )
{ 
	if ( IsDisposableBuilding() || IsMiniBuilding() )
		return SENTRYGUN_MAX_LEVEL_MINI;

	return BaseClass::GetMaxUpgradeLevel(); 
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectSentrygun::OnGoActive( void )
{
	SetModel( SENTRY_MODEL_LEVEL_1 );

	if ( IsMiniBuilding() )
	{
		SetBodygroup( FindBodygroupByName( "mini_sentry_light" ), 1 );
	}

	m_iState.Set( SENTRY_STATE_SEARCHING );

	// Orient it
	QAngle angles = GetAbsAngles();

	m_vecCurAngles.y = UTIL_AngleMod( angles.y );
	m_iRightBound = UTIL_AngleMod( (int)angles.y - 50 );
	m_iLeftBound = UTIL_AngleMod( (int)angles.y + 50 );
	if ( m_iRightBound > m_iLeftBound )
	{
		m_iRightBound = m_iLeftBound;
		m_iLeftBound = UTIL_AngleMod( (int)angles.y - 50);
	}

	// Start it rotating
	m_vecGoalAngles.y = m_iRightBound;
	m_vecGoalAngles.x = m_vecCurAngles.x = 0;
	m_bTurningRight = true;

	EmitSound( "Building_Sentrygun.Built" );

	// if our eye pos is underwater, we're waterlevel 3, else 0
	bool bUnderwater = ( UTIL_PointContents( EyePosition() ) & MASK_WATER ) ? true : false;
	SetWaterLevel( ( bUnderwater ) ? 3 : 0 );	

	if ( m_bCarryDeploy )
	{
		m_iAmmoShells = m_iOldAmmoShells;
		m_iAmmoRockets = m_iOldAmmoRockets;
	}
	else
	{
		m_iAmmoShells = m_iMaxAmmoShells;
		m_iAmmoRockets = m_iMaxAmmoRockets;
	}

	// Init attachments for level 1 sentry gun
	m_iAttachments[SENTRYGUN_ATTACHMENT_MUZZLE] = LookupAttachment( "muzzle" );
	m_iAttachments[SENTRYGUN_ATTACHMENT_MUZZLE_ALT] = 0;
	m_iAttachments[SENTRYGUN_ATTACHMENT_ROCKET] = 0;

	BaseClass::OnGoActive();

	IGameEvent * event = gameeventmanager->CreateEvent( "sentry_on_go_active" );
	if ( event )
	{
		event->SetInt( "index", entindex() );	// object entity index

		gameeventmanager->FireEvent( event, true );	// don't send to clients
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectSentrygun::Precache()
{
	BaseClass::Precache();

	int iModelIndex;

	// Models
	PrecacheModel( SENTRY_MODEL_PLACEMENT );

	iModelIndex = PrecacheModel( SENTRY_MODEL_LEVEL_1 );
	PrecacheGibsForModel( iModelIndex );

	iModelIndex = PrecacheModel( SENTRY_MODEL_LEVEL_1_UPGRADE );
	PrecacheGibsForModel( iModelIndex );

	iModelIndex = PrecacheModel( SENTRY_MODEL_LEVEL_2 );
	PrecacheGibsForModel( iModelIndex );

	iModelIndex = PrecacheModel( SENTRY_MODEL_LEVEL_2_UPGRADE );
	PrecacheGibsForModel( iModelIndex );

	iModelIndex = PrecacheModel( SENTRY_MODEL_LEVEL_3 );
	PrecacheGibsForModel( iModelIndex );

	iModelIndex = PrecacheModel( SENTRY_MODEL_LEVEL_3_UPGRADE );
	PrecacheGibsForModel( iModelIndex );

	PrecacheModel( SENTRY_ROCKET_MODEL );
	PrecacheModel( "models/effects/sentry1_muzzle/sentry1_muzzle.mdl" );

	PrecacheModel( "models/buildables/sentry_shield.mdl" );

	// Sounds
	PrecacheScriptSound( "Building_Sentrygun.Fire" );
	PrecacheScriptSound( "Building_Sentrygun.Fire2" );	// level 2 sentry
	PrecacheScriptSound( "Building_Sentrygun.Fire3" );	// level 3 sentry
	PrecacheScriptSound( "Building_Sentrygun.FireRocket" );
	PrecacheScriptSound( "Building_Sentrygun.Alert" );
	PrecacheScriptSound( "Building_Sentrygun.AlertTarget" );
	PrecacheScriptSound( "Building_Sentrygun.Idle" );
	PrecacheScriptSound( "Building_Sentrygun.Idle2" );	// level 2 sentry
	PrecacheScriptSound( "Building_Sentrygun.Idle3" );	// level 3 sentry
	PrecacheScriptSound( "Building_Sentrygun.Built" );
	PrecacheScriptSound( "Building_Sentrygun.Empty" );
	PrecacheScriptSound( "Building_Sentrygun.ShaftFire" );
	PrecacheScriptSound( "Building_Sentrygun.ShaftFire2" );
	PrecacheScriptSound( "Building_Sentrygun.ShaftFire3" );
	PrecacheScriptSound( "Building_Sentrygun.ShaftLaserPass" );
	PrecacheScriptSound( "Building_MiniSentrygun.Fire" );

	PrecacheParticleSystem( "sentrydamage_1" );
	PrecacheParticleSystem( "sentrydamage_2" );
	PrecacheParticleSystem( "sentrydamage_3" );
	PrecacheParticleSystem( "sentrydamage_4" );
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
bool CObjectSentrygun::CanBeUpgraded( CTFPlayer *pPlayer )
{
	if ( m_bWasMapPlaced && !HasSpawnFlags(SF_SENTRY_UPGRADEABLE) )
	{
		return false;
	}

	return BaseClass::CanBeUpgraded( pPlayer );
}

//-----------------------------------------------------------------------------
// Purpose: Raises the Sentrygun one level
//-----------------------------------------------------------------------------
void CObjectSentrygun::StartUpgrading( void )
{
	BaseClass::StartUpgrading();

	float flMaxAmmoMult = 1.f;
	if ( GetOwner() )
	{
		CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwner(), flMaxAmmoMult, mvm_sentry_ammo );
	}

	switch( m_iUpgradeLevel )
	{
	case 2:
		SetModel( SENTRY_MODEL_LEVEL_2_UPGRADE );
		m_flHeavyBulletResist = SENTRYGUN_MINIGUN_RESIST_LVL_2;
		SetViewOffset( SENTRYGUN_EYE_OFFSET_LEVEL_2 );
		m_iMaxAmmoShells = SENTRYGUN_MAX_SHELLS_2 * flMaxAmmoMult;
		break;
	case 3:
		SetModel( SENTRY_MODEL_LEVEL_3_UPGRADE );
		if ( !m_bCarryDeploy )
		{
			m_iAmmoRockets = SENTRYGUN_MAX_ROCKETS;
		}
		m_flHeavyBulletResist = SENTRYGUN_MINIGUN_RESIST_LVL_3;
		SetViewOffset( SENTRYGUN_EYE_OFFSET_LEVEL_3 );
		m_iMaxAmmoShells = SENTRYGUN_MAX_SHELLS_3 * flMaxAmmoMult;
		break;
	default:
		Assert(0);
		break;
	}

	// more ammo capability
	if ( !m_bCarryDeploy )
	{
		m_iAmmoShells = m_iMaxAmmoShells;
	}

	m_iState.Set( SENTRY_STATE_UPGRADING );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectSentrygun::FinishUpgrading( void )
{
	BaseClass::FinishUpgrading();

	m_iState.Set( SENTRY_STATE_SEARCHING );
	m_hEnemy = NULL;

	switch( m_iUpgradeLevel )
	{
	case 1:
		// This can happen when a saper downgrades a sentry
		// No need to do anything here
		break;
	case 2:
		SetModel( SENTRY_MODEL_LEVEL_2 );
		break;
	case 3:
		SetModel( SENTRY_MODEL_LEVEL_3 );
		break;
	default:
		Assert(0);
		break;
	}

	// Look up the new attachments
	m_iAttachments[SENTRYGUN_ATTACHMENT_MUZZLE] = LookupAttachment( "muzzle_l" );
	m_iAttachments[SENTRYGUN_ATTACHMENT_MUZZLE_ALT] = LookupAttachment( "muzzle_r" );
	m_iAttachments[SENTRYGUN_ATTACHMENT_ROCKET] = LookupAttachment( "rocket_l" );
}

//-----------------------------------------------------------------------------
// Hit by a friendly engineer's wrench
//-----------------------------------------------------------------------------
bool CObjectSentrygun::OnWrenchHit( CTFPlayer *pPlayer, CTFWrench *pWrench, Vector hitLoc )
{
	if ( IsDisposableBuilding() )
		return false;

	bool bDidWork = false;

	// If the player repairs it at all, we're done
	if ( GetHealth() < GetMaxHealth() )
	{
		// STAGING_ENGY
		// Mod repair value by shield value
		float flRepairValue = pWrench->GetRepairValue();
		if ( m_nShieldLevel == SHIELD_NORMAL )
		{
			flRepairValue *= SHIELD_NORMAL_VALUE;
		}
		
		if ( Command_Repair( pPlayer, flRepairValue ) )
		{
			DoWrenchHitEffect( hitLoc, true, false );
			bDidWork = true;
		}
	}

	// Don't put in upgrade metal until the sentry is fully healed
	if ( !bDidWork )
	{
		if ( CheckUpgradeOnHit( pPlayer ) )
		{
			DoWrenchHitEffect( hitLoc, false, true );
			bDidWork = true;
		}
	}

	if ( !IsUpgrading() )
	{
		// player ammo into rockets
		//	1 ammo = 1 shell
		//  2 ammo = 1 rocket
		//  only fill rockets if we have extra shells

		int iPlayerMetal = pPlayer->GetAmmoCount( TF_AMMO_METAL );

		// If the sentry has less that 100% ammo, put some ammo in it
		if ( m_iAmmoShells < m_iMaxAmmoShells && iPlayerMetal > 0 )
		{
			int iMaxShellsPlayerCanAfford = (int)( (float)iPlayerMetal / tf_sentrygun_metal_per_shell.GetFloat() );

			// cap the amount we can add
			int iAmountToAdd = MIN( SENTRYGUN_ADD_SHELLS, iMaxShellsPlayerCanAfford );
			iAmountToAdd = MIN( ( m_iMaxAmmoShells - m_iAmmoShells ), iAmountToAdd );

			// STAGING_ENGY
			// Mod Ammo if shielded
			if ( m_nShieldLevel == SHIELD_NORMAL )
			{
				iAmountToAdd *= SHIELD_NORMAL_VALUE;
			}

			pPlayer->RemoveAmmo( iAmountToAdd * tf_sentrygun_metal_per_shell.GetInt(), TF_AMMO_METAL );
			m_iAmmoShells += iAmountToAdd;

			if ( iAmountToAdd > 0 )
			{
				bDidWork = true;
			}
		}

		// One rocket per two ammo
		iPlayerMetal = pPlayer->GetAmmoCount( TF_AMMO_METAL );

		if ( m_iAmmoRockets < m_iMaxAmmoRockets && m_iUpgradeLevel == 3 && iPlayerMetal > 0  )
		{
			int iMaxRocketsPlayerCanAfford = (int)( (float)iPlayerMetal / tf_sentrygun_metal_per_rocket.GetFloat() );

			int iAmountToAdd = MIN( ( SENTRYGUN_ADD_ROCKETS ), iMaxRocketsPlayerCanAfford );
			iAmountToAdd = MIN( ( m_iMaxAmmoRockets - m_iAmmoRockets ), iAmountToAdd );

			// STAGING_ENGY
			// Mod Ammo if shielded
			if ( m_nShieldLevel == SHIELD_NORMAL )
			{
				iAmountToAdd *= SHIELD_NORMAL_VALUE;
			}

			pPlayer->RemoveAmmo( iAmountToAdd * tf_sentrygun_metal_per_rocket.GetFloat(), TF_AMMO_METAL );
			m_iAmmoRockets += iAmountToAdd;

			if ( iAmountToAdd > 0 )
			{
				bDidWork = true;
			}
		}
	}

	if ( GetOwner() != pPlayer )
	{
		if ( bDidWork && m_bPlayerControlled )
		{
			pPlayer->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_HELP_MANUAL_SENTRY, 1 );
		}

		// keep track of who last hit us with a wrench for kill assists
		m_lastTeammateWrenchHit = pPlayer;
		m_lastTeammateWrenchHitTimer.Start();
	}

	return bDidWork;
}

//-----------------------------------------------------------------------------
// Debug infos
//-----------------------------------------------------------------------------
int CObjectSentrygun::DrawDebugTextOverlays(void) 
{
	int text_offset = BaseClass::DrawDebugTextOverlays();

	if (m_debugOverlays & OVERLAY_TEXT_BIT) 
	{
		char tempstr[512];

		Q_snprintf( tempstr, sizeof( tempstr ), "Level: %d", m_iUpgradeLevel.Get() );
		EntityText(text_offset,tempstr,0);
		text_offset++;

		Q_snprintf( tempstr, sizeof( tempstr ), "Shells: %d / %d", m_iAmmoShells.Get(), m_iMaxAmmoShells.Get() );
		EntityText(text_offset,tempstr,0);
		text_offset++;

		if ( m_iUpgradeLevel == 3 )
		{
			Q_snprintf( tempstr, sizeof( tempstr ), "Rockets: %d / %d", m_iAmmoRockets.Get(), m_iMaxAmmoRockets.Get() );
			EntityText(text_offset,tempstr,0);
			text_offset++;
		}

		Q_snprintf( tempstr, sizeof( tempstr ), "Upgrade metal %d", m_iUpgradeMetal.Get() );
		EntityText(text_offset,tempstr,0);
		text_offset++;

		Vector vecSrc = EyePosition();
		Vector forward;

		// m_vecCurAngles
		AngleVectors( m_vecCurAngles, &forward );
		NDebugOverlay::Line( vecSrc, vecSrc + forward * 200, 0, 255, 0, false, 0.1 );

		// m_vecGoalAngles
		AngleVectors( m_vecGoalAngles, &forward );
		NDebugOverlay::Line( vecSrc, vecSrc + forward * 200, 0, 0, 255, false, 0.1 );
	}

	return text_offset;
}

//-----------------------------------------------------------------------------
// 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();

	if (iDist < 132)
		return RANGE_MELEE;
	if (iDist < 550)
		return RANGE_NEAR;
	if (iDist < m_flSentryRange)
		return RANGE_MID;
	return RANGE_FAR;
}

//-----------------------------------------------------------------------------
// Look for a target
//-----------------------------------------------------------------------------
bool CObjectSentrygun::FindTarget()
{
	if ( m_bPlayerControlled )
	{
		m_flShieldFadeTime = gpGlobals->curtime + WRANGLER_DISABLE_TIME;
	}
	m_bPlayerControlled = false;

	// Disable the sentry guns for ifm.
	if ( tf_sentrygun_notarget.GetBool() )
		return false;

	if ( IsInCommentaryMode() )
		return false;

	// Sapper, etc.
	if ( IsDisabled() )
		return false;

	// Loop through players within SENTRY_MAX_RANGE units (sentry range).
	Vector vecSentryOrigin = EyePosition();

	// find the enemy team
	int iEnemyTeam = ( GetTeamNumber() == TF_TEAM_BLUE ) ? TF_TEAM_RED : TF_TEAM_BLUE;
	CTFTeam *pTeam = TFTeamMgr()->GetTeam( iEnemyTeam );
	if ( !pTeam )
		return false;

	// If we have an enemy get his minimum distance to check against.
	Vector vecSegment;
	Vector vecTargetCenter;
	float flMinDist2 = m_flSentryRange * m_flSentryRange;
	CBaseEntity *pTargetCurrent = NULL;
	CBaseEntity *pTargetOld = m_hEnemy.Get();
	float flOldTargetDist2 = FLT_MAX;
	bool bDummyTarget = false;

	// Sentry Decoy
	// If we already have a sentry decoy target, keep shooting at it
	// Otherwise look for a sentry decoy's first
	if ( pTargetCurrent == NULL )
	{
		CTFTargetDummy *pDummy = dynamic_cast<CTFTargetDummy*>( pTargetOld );
		if ( pDummy )
		{
			pTargetCurrent = pDummy;
			bDummyTarget = true;
		}
		else
		{
			// Search through all dummies and find one in range
			for ( int i = 0; i < ITFTargetDummy::AutoList().Count(); ++i )
			{
				pDummy = static_cast<CTFTargetDummy*>( ITFTargetDummy::AutoList()[i] );
				if ( InSameTeam( pDummy ) )
					continue;

				vecTargetCenter = pDummy->GetAbsOrigin();
				vecTargetCenter += pDummy->GetViewOffset();
				VectorSubtract( vecTargetCenter, vecSentryOrigin, vecSegment );
				float flDist2 = vecSegment.LengthSqr();

				// Check to see if the target is closer than the already validated target.
				if ( flDist2 > flMinDist2 )
					continue;

				// Ray trace!!!
				if ( FVisible( pDummy, MASK_SHOT | CONTENTS_GRATE ) )
				{
					pTargetCurrent = pDummy;
					bDummyTarget = true;
				}
			}
		}
	}

	// If our builder has an active laser pointer we don't seek targets.
	CTFPlayer* pBuilder = GetBuilder();
	if ( pBuilder )
	{
		// CTFLaserPointer* pPointer = static_cast<CTFLaserPointer*>( pBuilder->Weapon_OwnsThisID( TF_WEAPON_LASER_POINTER ) );
		// FIX ME:  Temp fix until we find out why the pointer thinks its deployed after spawn
		CTFLaserPointer* pPointer = dynamic_cast<CTFLaserPointer*>( pBuilder->GetActiveWeapon() );
		if ( pPointer && pPointer->HasLaserDot() && !IsDisposableBuilding() )
		{
			m_bPlayerControlled = true;
			m_nShieldLevel.Set( SHIELD_NORMAL );
			m_flShieldFadeTime = gpGlobals->curtime + WRANGLER_DISABLE_TIME;

			// If not target dummy, use laserdot, otherwise targetdummy overrides
			if ( !bDummyTarget || !pTargetCurrent )
			{
				pTargetCurrent = pPointer->GetLaserDot();

				// Are we in our brief auto aim period?
				float flAutoAimTime = gpGlobals->curtime - m_flAutoAimStartTime;
				if ( m_hAutoAimTarget && (flAutoAimTime < 0.2f) )
				{
					// Only use the auto aim target if we can actually range to him.
					Vector vecSrc;
					QAngle vecAng;
					GetAttachment( GetFireAttachment(), vecSrc, vecAng );
					Vector vecEnemy = GetEnemyAimPosition( m_hAutoAimTarget );
					trace_t	trace;
					CTraceFilterIgnoreTeammatesAndTeamObjects filter( pBuilder, COLLISION_GROUP_NONE, pBuilder->GetTeamNumber() );
					UTIL_TraceLine( vecSrc, vecEnemy, MASK_SOLID, &filter, &trace );
					if ( trace.m_pEnt == m_hAutoAimTarget )
					{
						pTargetCurrent = m_hAutoAimTarget;
					}
					else
					{
						m_hAutoAimTarget = NULL;
					}
				}
				else
				{
					m_hAutoAimTarget = NULL;
				}
			}

			if ( pTargetCurrent->GetAbsOrigin().DistTo( vecSentryOrigin ) > 30.f )
			{
				if ( pTargetCurrent != pTargetOld )
				{
					FoundTarget( pTargetCurrent, vecSentryOrigin, true );
				}
				return true;
			}
			else
			{
				pTargetCurrent = NULL;
			}
		}
	}

	// Don't auto track to targets while under the effects of the player shield.
	// The shield fades 3 seconds after we disengage from player control.
	if ( m_nShieldLevel == SHIELD_NORMAL )
		return false;

	// is there an active truce?
	bool bTruceActive = TFGameRules() && TFGameRules()->IsTruceActive();
		
	if ( ( pTargetCurrent == NULL ) && !bTruceActive )
	{
		// Sentries will try to target players first, then objects.  However, if the enemy held was an object it will continue
		// to try and attack it first.
		int nTeamCount = pTeam->GetNumPlayers();
		for ( int iPlayer = 0; iPlayer < nTeamCount; ++iPlayer )
		{
			CTFPlayer *pTargetPlayer = static_cast<CTFPlayer*>( pTeam->GetPlayer( iPlayer ) );
			if ( pTargetPlayer == NULL )
				continue;

			// Make sure the player is alive.
			if ( !pTargetPlayer->IsAlive() )
				continue;

			if ( pTargetPlayer->GetFlags() & FL_NOTARGET )
				continue;

			vecTargetCenter = pTargetPlayer->GetAbsOrigin();
			vecTargetCenter += pTargetPlayer->GetViewOffset();
			VectorSubtract( vecTargetCenter, vecSentryOrigin, vecSegment );
			float flDist2 = vecSegment.LengthSqr();

			// Check to see if the target is closer than the already validated target.
			if ( flDist2 > flMinDist2 )
				continue;

			// It is closer, check to see if the target is valid.
			if ( ValidTargetPlayer( pTargetPlayer, vecSentryOrigin, vecTargetCenter ) )
			{
				flMinDist2 = flDist2;
				pTargetCurrent = pTargetPlayer;

				// Store the current target distance if we come across it
				if ( pTargetPlayer == pTargetOld )
				{
					flOldTargetDist2 = flDist2;
				}
			}
		}
	}

	// If we already have a target, don't check objects.
	if ( pTargetCurrent == NULL )
	{
		// target non-player bots
		CUtlVector< INextBot * > botVector;
		TheNextBots().CollectAllBots( &botVector );

		float closeBotRangeSq = m_flSentryRange * m_flSentryRange;

		for( int b=0; b<botVector.Count(); ++b )
		{
			CBaseCombatCharacter *bot = botVector[b]->GetEntity();

			Vector vecBotTarget = GetEnemyAimPosition( bot );
			float rangeSq = ( vecBotTarget - vecSentryOrigin ).LengthSqr();

			if ( rangeSq < closeBotRangeSq )
			{
				if ( ValidTargetBot( bot, vecSentryOrigin, vecBotTarget ) )
				{
					closeBotRangeSq = rangeSq;
					pTargetCurrent = bot;
				}
			}
		}

		if ( ( pTargetCurrent == NULL ) && !bTruceActive )
		{
			// target objects
			int nTeamObjectCount = pTeam->GetNumObjects();
			for ( int iObject = 0; iObject < nTeamObjectCount; ++iObject )
			{
				CBaseObject *pTargetObject = pTeam->GetObject( iObject );
				if ( !pTargetObject )
					continue;

				vecTargetCenter = pTargetObject->GetAbsOrigin();
				vecTargetCenter += pTargetObject->GetViewOffset();
				VectorSubtract( vecTargetCenter, vecSentryOrigin, vecSegment );
				float flDist2 = vecSegment.LengthSqr();

				// Store the current target distance if we come across it
				if ( pTargetObject == pTargetOld )
				{
					flOldTargetDist2 = flDist2;
				}

				// Check to see if the target is closer than the already validated target.
				if ( flDist2 > flMinDist2 )
					continue;

				// It is closer, check to see if the target is valid.
				if ( ValidTargetObject( pTargetObject, vecSentryOrigin, vecTargetCenter ) )
				{
					flMinDist2 = flDist2;
					pTargetCurrent = pTargetObject;
				}
			}
		}
	}

	// We have a target.
	if ( pTargetCurrent )
	{
		if ( pTargetCurrent != pTargetOld )
		{
			// Always target dummies
			// flMinDist2 is the new target's distance
			// flOldTargetDist2 is the old target's distance
			// Don't switch unless the new target is closer by some percentage
			if ( bDummyTarget || flMinDist2 < ( flOldTargetDist2 * 0.75f ) )
			{
				FoundTarget( pTargetCurrent, vecSentryOrigin );
			}
		}
		return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CObjectSentrygun::ValidTargetPlayer( CTFPlayer *pPlayer, const Vector &vecStart, const Vector &vecEnd )
{
	// Keep shooting at spies that go invisible after we acquire them as a target.
	if ( pPlayer->m_Shared.GetPercentInvisible() > 0.5 )
		return false;

	// Keep shooting at spies that disguise after we acquire them as at a target.
	if ( pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && pPlayer->m_Shared.GetDisguiseTeam() == GetTeamNumber() && pPlayer != m_hEnemy )
		return false;

	// Don't shoot spys that are pretending to be a dispenser
	if ( pPlayer->m_Shared.InCond( TF_COND_DISGUISED_AS_DISPENSER ) )
		return false;

	// Don't target spies after they OnKill disguise with 'Your Eternal Reward'
	if ( ( pPlayer->m_Shared.InCond( TF_COND_DISGUISING ) || pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) )
		&& pPlayer->m_Shared.GetDisguiseTeam() == GetTeamNumber() )
	{
		CTFKnife *pKnife = dynamic_cast<CTFKnife *>( pPlayer->GetActiveTFWeapon() );
		if ( pKnife && pKnife->GetKnifeType() == KNIFE_DISGUISE_ONKILL )
			return false;
	}

	// Not across water boundary.
	if ( ( GetWaterLevel() == 0 && pPlayer->GetWaterLevel() >= 3 ) || ( GetWaterLevel() == 3 && pPlayer->GetWaterLevel() <= 0 ) )
		return false;

	// Ray trace!!!
	return FVisible( pPlayer, MASK_SHOT | CONTENTS_GRATE );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CObjectSentrygun::ValidTargetObject( CBaseObject *pObject, const Vector &vecStart, const Vector &vecEnd )
{
	// Ignore objects being placed, they are not real objects yet.
	if ( pObject->IsPlacing() )
		return false;

	// Ignore sappers.
	if ( pObject->MustBeBuiltOnAttachmentPoint() )
		return false;

	// Not across water boundary.
	if ( ( GetWaterLevel() == 0 && pObject->GetWaterLevel() >= 3 ) || ( GetWaterLevel() == 3 && pObject->GetWaterLevel() <= 0 ) )
		return false;

	if ( pObject->GetObjectFlags() & OF_DOESNT_HAVE_A_MODEL )
		return false;

	// Ray trace.
	return FVisible( pObject, MASK_SHOT | CONTENTS_GRATE );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CObjectSentrygun::ValidTargetBot( CBaseCombatCharacter *pBot, const Vector &vecStart, const Vector &vecEnd )
{
	// Already collected all of the players in FindTarget()
	if ( pBot->IsPlayer() )
		return false;

	// Don't want to shoot bots that are dead, on the same team, or aren't solid (they won't take damage anyway)
	if  ( !pBot->IsAlive() || pBot->InSameTeam( this ) || pBot->IsSolidFlagSet( FSOLID_NOT_SOLID ) )
		return false;

	// Not across water boundary.
	if ( ( GetWaterLevel() == 0 && pBot->GetWaterLevel() >= 3 ) || ( GetWaterLevel() == 3 && pBot->GetWaterLevel() <= 0 ) )
		return false;

	if ( TFGameRules() && TFGameRules()->IsPlayingRobotDestructionMode() )
	{
		CTFRobotDestruction_Robot *pRobot = dynamic_cast< CTFRobotDestruction_Robot* >( pBot );
		if ( pRobot && pRobot->GetShieldedState() )
			return false;
	}

	// Ray trace.
	CBaseEntity *pBlocker;
	bool bVisible = FVisible( pBot, MASK_SHOT | CONTENTS_GRATE, &pBlocker );

	if ( bVisible )
		return true;

	// Also valid if it's parented to the blocker
	if ( pBlocker == pBot->GetParent() )
		return true;

	return false;
}

//-----------------------------------------------------------------------------
// Found a Target
//-----------------------------------------------------------------------------
void CObjectSentrygun::FoundTarget( CBaseEntity *pTarget, const Vector &vecSoundCenter, bool bNoSound )
{
	m_hEnemy = pTarget;

	if ( ( m_iAmmoShells > 0 ) || ( m_iAmmoRockets > 0 && m_iUpgradeLevel == 3 ) )
	{
		// Play one sound to everyone but the target.
		CPASFilter filter( vecSoundCenter );

		if ( pTarget->IsPlayer() )
		{
			CTFPlayer *pPlayer = ToTFPlayer( pTarget );

			// Play a specific sound just to the target and remove it from the general recipient list.
			if ( !bNoSound )
			{
				CSingleUserRecipientFilter singleFilter( pPlayer );
				EmitSentrySound( singleFilter, entindex(), "Building_Sentrygun.AlertTarget" );
				filter.RemoveRecipient( pPlayer );

				// if the target is a bot, alert it
				CTFBot *bot = ToTFBot( pPlayer );
				if ( bot )
				{
					bot->GetVisionInterface()->AddKnownEntity( this );
					bot->RememberEnemySentry( this, bot->GetAbsOrigin() );
				}
			}
		}

		if ( !bNoSound )
		{
			EmitSentrySound( filter, entindex(), "Building_Sentrygun.Alert" );
		}
	}

	// Update timers, we are attacking now!
	m_iState.Set( SENTRY_STATE_ATTACKING );
	m_flNextAttack = gpGlobals->curtime + SENTRY_THINK_DELAY;
	if ( m_flNextRocketAttack < gpGlobals->curtime )
	{
		m_flNextRocketAttack = gpGlobals->curtime;// + 0.5;
	}
}

//-----------------------------------------------------------------------------
// FInViewCone - 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 )
{
	Vector forward;
	AngleVectors( m_vecCurAngles, &forward );

	Vector2D vec2LOS = ( pEntity->GetAbsOrigin() - GetAbsOrigin() ).AsVector2D();
	vec2LOS.NormalizeInPlace();

	float flDot = vec2LOS.Dot( forward.AsVector2D() );

	if ( flDot > m_flFieldOfView )
	{
		return true;
	}
	else
	{
		return false;
	}
}

//-----------------------------------------------------------------------------
// Make sure our target is still valid, and if so, fire at it
//-----------------------------------------------------------------------------
void CObjectSentrygun::Attack()
{
	StudioFrameAdvance( );

	if ( IsUsingReverseBuild() || !FindTarget() )
	{
		m_iState.Set( SENTRY_STATE_SEARCHING );
		m_hEnemy = NULL;
		return;
	}

	// Track enemy
	Vector vecMid = EyePosition();
	Vector vecMidEnemy = GetEnemyAimPosition( m_hEnemy );
	Vector vecDirToEnemy = vecMidEnemy - 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() <= 10 )
	{
		if ( !m_bPlayerControlled || m_bFireNextFrame )
		{
			m_bFireNextFrame = false;
			Fire();
		}

		m_flFireRate = 1.f;
		CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwner(), m_flFireRate, mult_sentry_firerate );

		if ( m_bPlayerControlled )
		{
			m_flFireRate *= 0.5f;
		}
			
		if ( IsMiniBuilding() && !IsDisposableBuilding() )
		{
			m_flFireRate *= 0.75f;
		}

		if ( GetBuilder() && GetBuilder()->m_Shared.InCond( TF_COND_CRITBOOSTED_USER_BUFF ) )
		{
			m_flFireRate *= 0.4f;
		}

		if ( m_iUpgradeLevel == 1 )
		{
			// Level 1 sentries fire slower
			m_flNextAttack = gpGlobals->curtime + (0.2*m_flFireRate);
		}
		else
		{
			m_flNextAttack = gpGlobals->curtime + (0.1*m_flFireRate);
		}
	}
	else
	{
		// SetSentryAnim( TFTURRET_ANIM_SPIN );
	}

	if ( m_bPlayerControlled && m_bFireRocketNextFrame )
	{
		m_bFireRocketNextFrame = false;
		FireRocket();
	}
}

//-----------------------------------------------------------------------------
// 
//-----------------------------------------------------------------------------
bool CObjectSentrygun::FireRocket()
{
	if ( m_flNextRocketAttack >= gpGlobals->curtime || m_iAmmoRockets <= 0 )
		return false;

	if ( m_hEnemy.Get() == NULL )
		return false;

	Vector vecAimDir;

	Vector vecSrc;
	QAngle vecAng;

	GetAttachment( m_iAttachments[SENTRYGUN_ATTACHMENT_ROCKET], vecSrc, vecAng );

	Vector vecEnemyPos = GetEnemyAimPosition( m_hEnemy );
	vecAimDir = vecEnemyPos - vecSrc;
	vecAimDir.NormalizeInPlace();

	// If we cannot see their WorldSpaceCenter ( possible, as we do our target finding based
	// on the eye position of the target ) then fire at the eye position
	trace_t tr;

	CTraceFilterSimple traceFilter( this, COLLISION_GROUP_NONE );
	ITraceFilter *pFilterChain = NULL;

	CTraceFilterIgnoreFriendlyCombatItems traceFilterCombatItem( this, COLLISION_GROUP_NONE, GetTeamNumber() );
	if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() )
	{
		// Ignore teammates and their (physical) upgrade items in MvM
		pFilterChain = &traceFilterCombatItem;
	}

	CTraceFilterChain traceFilterChain( &traceFilter, pFilterChain );
	UTIL_TraceLine( vecSrc, vecEnemyPos, MASK_SOLID, &traceFilterChain, &tr);

	if ( m_bPlayerControlled || (tr.m_pEnt && !tr.m_pEnt->IsWorld()) )
	{
		// NOTE: vecAng is not actually set by GetAttachment!!!
		QAngle angDir;
		VectorAngles( vecAimDir, angDir );

		EmitSentrySound( "Building_Sentrygun.FireRocket" );

		QAngle angAimDir;
		VectorAngles( vecAimDir, angAimDir );
		CTFProjectile_SentryRocket *pProjectile = CTFProjectile_SentryRocket::Create( vecSrc, angAimDir, this, GetBuilder() );
		if ( pProjectile )
		{
			int iDamage = 100;
			CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwner(), iDamage, mult_engy_sentry_damage );
			pProjectile->SetDamage( iDamage );
		}

		// Setup next rocket shot
		if ( m_bPlayerControlled )
		{
			m_flNextRocketAttack = gpGlobals->curtime + 2.25;
		}
		else
		{
			AddGesture( ACT_RANGE_ATTACK2 );
			m_flNextRocketAttack = gpGlobals->curtime + 3;
		}

		if ( !tf_sentrygun_ammocheat.GetBool() && !HasSpawnFlags( SF_SENTRY_INFINITE_AMMO ) )
		{
			m_iAmmoRockets--;
		}
	}

	m_timeSinceLastFired.Start();

	return true;
}

//-----------------------------------------------------------------------------
// 
//-----------------------------------------------------------------------------
int CObjectSentrygun::GetFireAttachment()
{
	int iAttachment;

	if ( m_iUpgradeLevel > 1 && m_iLastMuzzleAttachmentFired == m_iAttachments[SENTRYGUN_ATTACHMENT_MUZZLE] )
	{
		// level 2 and 3 turrets alternate muzzles each time they fizzy fizzy fire.
		iAttachment = m_iAttachments[SENTRYGUN_ATTACHMENT_MUZZLE_ALT];
	}
	else
	{
		iAttachment = m_iAttachments[SENTRYGUN_ATTACHMENT_MUZZLE];
	}
	m_iLastMuzzleAttachmentFired = iAttachment;

	return iAttachment;
}

//-----------------------------------------------------------------------------
// 
//-----------------------------------------------------------------------------
void CObjectSentrygun::OnKilledEnemy(CBasePlayer* pVictim)
{
	if ( !pVictim )
		return;

	CTFPlayer *pOwner = GetOwner();
	if ( !pOwner )
		return;

	if ( m_bPlayerControlled && pVictim->GetAbsOrigin().DistToSqr( GetAbsOrigin() ) > ( m_flSentryRange * m_flSentryRange ) )
	{
		pOwner->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_MANUAL_SENTRY_KILLS_BEYOND_RANGE );
	}

	CTFPlayer *pCTFVictim = static_cast<CTFPlayer *>( pVictim );
	if ( pCTFVictim->GetControlPointStandingOn() != NULL )
	{
		pOwner->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_SENTRY_KILL_CAPS, 1 );
	}

	if ( (gpGlobals->curtime - GetCarryDeployTime() < tf_sentrygun_kill_after_redeploy_time_achievement.GetInt()) &&
		 GetUpgradeLevel() == 3 )
	{
		pOwner->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_MOVE_SENTRY_GET_KILL );
	}
}

//-----------------------------------------------------------------------------
// Fire on our target
//-----------------------------------------------------------------------------
bool CObjectSentrygun::Fire()
{
	//NDebugOverlay::Cross3D( m_hEnemy->WorldSpaceCenter(), 10, 255, 0, 0, false, 0.1 );

	Vector vecAimDir;

	// Level 3 Turrets fire rockets every 3 seconds
	if ( m_iUpgradeLevel == 3 &&
		 m_iAmmoRockets > 0 &&
		 m_flNextRocketAttack < gpGlobals->curtime &&
		 !m_bPlayerControlled )
	{
		FireRocket();
	}

	// All turrets fire shells
	if ( m_iAmmoShells > 0 )
	{
		if ( !IsPlayingGesture( ACT_RANGE_ATTACK1 ) )
		{
			RemoveGesture( ACT_RANGE_ATTACK1_LOW );
			AddGesture( ACT_RANGE_ATTACK1 );
		}

		if ( m_hEnemy.Get() == NULL )
			return false;

		Vector vecSrc;
		QAngle vecAng;

		int iAttachment = GetFireAttachment();
		GetAttachment( iAttachment, vecSrc, vecAng );

		Vector vecMidEnemy = GetEnemyAimPosition( m_hEnemy );

		// If we cannot see their WorldSpaceCenter ( possible, as we do our target finding based
		// on the eye position of the target ) then fire at the eye position
		trace_t tr;
		CTraceFilterSimple traceFilter( this, COLLISION_GROUP_NONE );
		ITraceFilter *pFilterChain = NULL;

		CTraceFilterIgnoreFriendlyCombatItems traceFilterCombatItem( this, COLLISION_GROUP_NONE, GetTeamNumber() );
		if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() )
		{
			// Ignore teammates and their (physical) upgrade items in MvM
			pFilterChain = &traceFilterCombatItem;
		}

		CTraceFilterChain traceFilterChain( &traceFilter, pFilterChain );
		UTIL_TraceLine( vecSrc, vecMidEnemy, MASK_SOLID, &traceFilterChain, &tr);

		if ( !tr.m_pEnt || tr.m_pEnt->IsWorld() )
		{
			// Hack it lower a little bit..
			// The eye position is not always within the hitboxes for a standing TF Player
			vecMidEnemy = m_hEnemy->EyePosition() + Vector(0,0,-5);
		}

		vecAimDir = vecMidEnemy - vecSrc;

		float flDistToTarget = vecAimDir.Length();

		vecAimDir.NormalizeInPlace();

		//NDebugOverlay::Cross3D( vecSrc, 10, 255, 0, 0, false, 0.1 );

		FireBulletsInfo_t info;

		info.m_vecSrc = vecSrc;
		info.m_vecDirShooting = vecAimDir;
		info.m_iTracerFreq = 1;
		info.m_iShots = 1;
		info.m_pAttacker = GetBuilder();
		if ( info.m_pAttacker == NULL )
		{
			info.m_pAttacker = this;
		}
		if ( m_bPlayerControlled )
		{
			info.m_vecSpread = VECTOR_CONE_3DEGREES;
		}
		else
		{
			info.m_vecSpread = vec3_origin;
		}
		info.m_flDistance = flDistToTarget + 100;
		info.m_iAmmoType = m_iAmmoType;

		if ( IsMiniBuilding() )
		{
			info.m_flDamage = tf_sentrygun_mini_damage.GetFloat();
			info.m_flDamageForceScale = 0.0f;
		}
		else
		{
			info.m_flDamage = tf_sentrygun_damage.GetFloat();
		}

		CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwner(), info.m_flDamage, mult_engy_sentry_damage );

		FireBullets( info );

		// sentry gun fire 'heats up' the nav mesh around it
		UpdateNavMeshCombatStatus();


		//NDebugOverlay::Line( vecSrc, vecSrc + vecAimDir * 1000, 255, 0, 0, false, 0.1 );

		CEffectData data;
		data.m_nEntIndex = entindex();
		data.m_nAttachmentIndex = iAttachment;
		data.m_fFlags = m_iUpgradeLevel;
		data.m_vOrigin = vecSrc;
		DispatchEffect( "TF_3rdPersonMuzzleFlash_SentryGun", data );

		if ( IsMiniBuilding() )
		{
			EmitSound_t params;
			params.m_pSoundName = "Building_MiniSentrygun.Fire";
			params.m_flSoundTime = 0;
			params.m_pflSoundDuration = 0;
			params.m_bWarnOnDirectWaveReference = true;
			CPASAttenuationFilter filter( this, "Building_MiniSentrygun.Fire" );
			EmitSound( filter, entindex(), params );
		}
		else
		{
			if ( !m_bPlayerControlled )
			{
				switch( m_iUpgradeLevel )
				{
				case 1:
				default:
					EmitSentrySound( "Building_Sentrygun.Fire" );
					break;
				case 2:
					EmitSentrySound( "Building_Sentrygun.Fire2" );
					break;
				case 3:
					EmitSentrySound( "Building_Sentrygun.Fire3" );
					break;
				}
			}
			else
			{
				switch ( m_iUpgradeLevel )
				{
				case 1:
					EmitSentrySound( "Building_Sentrygun.ShaftFire" );
					break;
				case 2:
					EmitSentrySound( "Building_Sentrygun.ShaftFire2" );
					break;
				case 3:
					EmitSentrySound( "Building_Sentrygun.ShaftFire3" );
					break;
				}
			}
		}

		if ( !tf_sentrygun_ammocheat.GetBool() && !HasSpawnFlags( SF_SENTRY_INFINITE_AMMO ) )
		{
			m_iAmmoShells--;
		}
	}
	else
	{
		if ( m_iUpgradeLevel > 1 )
		{
			if ( !IsPlayingGesture( ACT_RANGE_ATTACK1_LOW ) )
			{
				RemoveGesture( ACT_RANGE_ATTACK1 );
				AddGesture( ACT_RANGE_ATTACK1_LOW );
			}
		}

		// Out of ammo, play a click
		EmitSound( "Building_Sentrygun.Empty" );

		// Disposable sentries blow up when their ammo runs out
		if ( IsDisposableBuilding() )
		{
			DetonateObject();
		}

		m_flNextAttack = gpGlobals->curtime + 0.2;
	}

	// note when we last fired at en enemy (or tried to)
	m_timeSinceLastFired.Start();

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectSentrygun::ModifyFireBulletsDamage( CTakeDamageInfo* dmgInfo )
{
	if ( m_bPlayerControlled && dmgInfo )
	{
		dmgInfo->SetDamageCustom( TF_DMG_CUSTOM_PLAYER_SENTRY );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float CObjectSentrygun::GetPushMultiplier()
{
	if ( IsMiniBuilding() )
		return 8.f;
	else
		return 16.f;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectSentrygun::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
{
	trace_t tmptrace;
	tmptrace.endpos = tr.endpos + RandomVector(-10,10);

	// Sentryguns are perfectly accurate, but this doesn't look good for tracers.
	// Add a little noise to them, but not enough so that it looks like they're missing.
	BaseClass::MakeTracer( vecTracerSrc, tmptrace, iTracerType );
}

//-----------------------------------------------------------------------------
// Purpose: MakeTracer asks back for the attachment index
//-----------------------------------------------------------------------------
int	CObjectSentrygun::GetTracerAttachment( void )
{
	return m_iAttachments[SENTRYGUN_ATTACHMENT_MUZZLE];
}

//-----------------------------------------------------------------------------
// Rotate and scan for targets
//-----------------------------------------------------------------------------
void CObjectSentrygun::SentryRotate( void )
{
	if ( GetReversesBuildingConstructionSpeed() )
	{
		m_iState.Set( SENTRY_STATE_INACTIVE );
		return;
	}

	// if we're playing a fire gesture, stop it
	if ( IsPlayingGesture( ACT_RANGE_ATTACK1 ) )
	{
		RemoveGesture( ACT_RANGE_ATTACK1 );
	}

	if ( IsPlayingGesture( ACT_RANGE_ATTACK1_LOW ) )
	{
		RemoveGesture( ACT_RANGE_ATTACK1_LOW );
	}

	// animate
	StudioFrameAdvance();

	// Look for a target
	if ( FindTarget() )
		return;
	
	// Rotate
	if ( !MoveTurret() )
	{
		// Change direction

		if ( IsDisabled() || m_nShieldLevel == SHIELD_NORMAL )
		{
			EmitSound( "Building_Sentrygun.Disabled" );
			m_vecGoalAngles.x = 30;
		}
		else
		{
			switch( m_iUpgradeLevel )
			{
			case 1:
			default:
				EmitSentrySound( "Building_Sentrygun.Idle" );
				break;
			case 2:
				EmitSound( "Building_Sentrygun.Idle2" );
				break;
			case 3:
				EmitSound( "Building_Sentrygun.Idle3" );
				break;
			}

			// 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: Add the EMP effect
//-----------------------------------------------------------------------------
void CObjectSentrygun::OnStartDisabled( void )
{
	// stay at current rotation, angle down
	m_vecGoalAngles.x = m_vecCurAngles.x;
	m_vecGoalAngles.y = m_vecCurAngles.y;

	// target = nULL

	BaseClass::OnStartDisabled();
}

//-----------------------------------------------------------------------------
// Purpose: Remove the EMP effect
//-----------------------------------------------------------------------------
void CObjectSentrygun::OnEndDisabled( void )
{
	// return to normal rotations
	if ( m_bTurningRight )
	{
		m_bTurningRight = false;
		m_vecGoalAngles.y = m_iLeftBound;
	}
	else
	{
		m_bTurningRight = true;
		m_vecGoalAngles.y = m_iRightBound;
	}

	m_vecGoalAngles.x = 0;

	BaseClass::OnEndDisabled();
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CObjectSentrygun::GetBaseTurnRate( void )
{
	if ( m_bPlayerControlled )
	{
		return m_iBaseTurnRate * 100;
	}
	else
	{
		return m_iBaseTurnRate;
	}
}

//-----------------------------------------------------------------------------
// 
//-----------------------------------------------------------------------------
bool CObjectSentrygun::MoveTurret( void )
{
	bool bMoved = false;

	int iBaseTurnRate = GetBaseTurnRate();
	
	if ( IsMiniBuilding() )
	{
		iBaseTurnRate *= 1.35f;
	}

	// any x movement?
	if ( m_vecCurAngles.x != m_vecGoalAngles.x )
	{
		float flDir = m_vecGoalAngles.x > m_vecCurAngles.x ? 1 : -1 ;

		m_vecCurAngles.x += SENTRY_THINK_DELAY * ( 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;
		}

		SetPoseParameter( m_iPitchPoseParameter, -m_vecCurAngles.x );

		bMoved = true;
	}

	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.Get() == NULL )
		{
			if ( flDist > 30 )
			{
				if ( m_flTurnRate < iBaseTurnRate * 10 )
				{
					m_flTurnRate += iBaseTurnRate;
				}
			}
			else
			{
				// Slow down
				if ( m_flTurnRate > (iBaseTurnRate * 5) )
					m_flTurnRate -= iBaseTurnRate;
			}
		}
		else
		{
			// When tracking enemies, move faster and don't slow
			if ( flDist > 30 )
			{
				if (m_flTurnRate < iBaseTurnRate * 30)
				{
					m_flTurnRate += iBaseTurnRate * 3;
				}
			}
		}

		m_vecCurAngles.y += SENTRY_THINK_DELAY * m_flTurnRate * 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 < ( SENTRY_THINK_DELAY * 0.5 * iBaseTurnRate ) )
		{
			m_vecCurAngles.y = m_vecGoalAngles.y;
		}

		QAngle angles = GetAbsAngles();

		float flYaw = m_vecCurAngles.y - angles.y;

		SetPoseParameter( m_iYawPoseParameter, -flYaw );

		InvalidatePhysicsRecursive( ANIMATION_CHANGED );

		bMoved = true;
	}

	if ( !bMoved || m_flTurnRate <= 0 )
	{
		m_flTurnRate = iBaseTurnRate * 5;
	}

	return bMoved;
}

//-----------------------------------------------------------------------------
// Purpose: Note our last attacked time
//-----------------------------------------------------------------------------
int CObjectSentrygun::OnTakeDamage( const CTakeDamageInfo &info )
{
	CTakeDamageInfo newInfo = info;

	// As we increase in level, we get more resistant to minigun bullets, to compensate for
	// our increased surface area taking more minigun hits.
	if ( ( info.GetDamageType() & DMG_BULLET ) && ( info.GetDamageCustom() == TF_DMG_CUSTOM_MINIGUN ) )
	{
		float flDamage = newInfo.GetDamage();
		flDamage *= ( 1.0 - m_flHeavyBulletResist );
		newInfo.SetDamage( flDamage );
	}
	
	// If we are shielded due to player control, we take less damage.
	bool bFullyShielded = ( m_nShieldLevel > 0 ) && !HasSapper() && !IsPlasmaDisabled();
	if ( bFullyShielded )
	{
		float flDamage = newInfo.GetDamage();
		flDamage *= ( m_nShieldLevel == SHIELD_NORMAL ) ? SHIELD_NORMAL_VALUE : SHIELD_MAX_VALUE;
		newInfo.SetDamage( flDamage );
	}

	// Check to see if we are being sapped.
	if ( HasSapper() )
	{
		// Get the sapper owner.
		CBaseObject *pSapper = GetObjectOfTypeOnMe( OBJ_ATTACHMENT_SAPPER );

		// Take less damage if the owner is causing additional damage.
		if ( pSapper && ( info.GetAttacker() == pSapper->GetOwner() ) )
		{
			float flDamage = newInfo.GetDamage() * SENTRYGUN_SAPPER_OWNER_DAMAGE_MODIFIER;
			newInfo.SetDamage( flDamage );
		}
	}

	int iDamageTaken = BaseClass::OnTakeDamage( newInfo );

	if ( iDamageTaken > 0 )
	{
		m_flLastAttackedTime = gpGlobals->curtime;

		// check for achievement
		if ( bFullyShielded )
		{
			int iPrevLifetimeShieldedDamage = m_iLifetimeShieldedDamage;
			m_iLifetimeShieldedDamage += iDamageTaken;
			const int kMaxDamageForAchievement = tf_sentrygun_max_absorbed_damage_while_controlled_for_achievement.GetInt();
			if ( iPrevLifetimeShieldedDamage <= kMaxDamageForAchievement && m_iLifetimeShieldedDamage > kMaxDamageForAchievement )
			{
				CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
				if ( pOwner && pOwner->IsPlayerClass( TF_CLASS_ENGINEER ) )
				{
					pOwner->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_MANUAL_SENTRY_ABSORB_DMG );
				}
			}
		}
	}

	return iDamageTaken;
}

//-----------------------------------------------------------------------------
// Purpose: Called when this object is destroyed
//-----------------------------------------------------------------------------
void CObjectSentrygun::Killed( const CTakeDamageInfo &info )
{
	CTFPlayer *pTFKiller = ToTFPlayer( info.GetAttacker() );
	if ( pTFKiller && pTFKiller->IsPlayerClass( TF_CLASS_SOLDIER ) )
	{
		if ( pTFKiller->GetAbsOrigin().DistTo( GetAbsOrigin() ) > SENTRY_MAX_RANGE )
		{
			pTFKiller->AwardAchievement( ACHIEVEMENT_TF_SOLDIER_DESTROY_SENTRY_OUT_OF_RANGE );
		}
		//If we are in the corridor map, then we check for the achievement for it.
		else if ( m_hEnemy && !( pTFKiller->GetFlags() & FL_ONGROUND ) )
		{
			CBaseEntity *pDamager = GetBuilder();
			
			if ( NULL == pDamager )
			{
				pDamager = this;
			}

			static const float DAMAGE_INTERVAL = 2.0f;
			if ( pTFKiller->m_AchievementData.IsDamagerInHistory( pDamager, DAMAGE_INTERVAL ) )
			{
				//Check the map.
				if ( 0 == Q_stricmp( "tra_sol_corridor", STRING( gpGlobals->mapname ) ) )
				{
#ifdef TF_SOLDIER_TRAINING_ACHIEVEMENTS
					//If the attacker was in the air when this sentry died, give him an achievement.
					pTFKiller->AwardAchievement( ACHIEVEMENT_TF_SOLDIER_TRAINING_COR_SENTRY_FROM_AIR );
#endif // TF_SOLDIER_TRAINING_ACHIEVEMENTS
				}
			}
		}
	}

	// Tell our owner's shotgun the sentry died.
	CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
	if ( pOwner )
	{
		CTFShotgun_Revenge* pShotgun = dynamic_cast<CTFShotgun_Revenge*>( pOwner->Weapon_OwnsThisID( TF_WEAPON_SENTRY_REVENGE ) );
		if ( pShotgun )
		{
			pShotgun->SentryKilled( GetKills() * 2 + GetAssists() );
		}
	}

	// find nearby sentry hint
	if ( TFGameRules() && TFGameRules()->IsInTraining() )
	{
		CTFBotHintSentrygun *sentryHint;
		for( sentryHint = static_cast< CTFBotHintSentrygun * >( gEntList.FindEntityByClassname( NULL, "bot_hint_sentrygun" ) );
			sentryHint;
			sentryHint = static_cast< CTFBotHintSentrygun * >( gEntList.FindEntityByClassname( sentryHint, "bot_hint_sentrygun" ) ) )
		{
			if ( sentryHint->IsEnabled() && sentryHint->InSameTeam( this ) )
			{
				Vector toMe = GetAbsOrigin() - sentryHint->GetAbsOrigin();
				float dist2 = toMe.LengthSqr();
				if ( dist2 < 1.0f )
				{
					sentryHint->OnSentryGunDestroyed( this );
					sentryHint->DecrementUseCount();
					break;
				}
			}
		}
	}

	// Engineers destroying their own sentry don't escape the buster.
	// Destroying disposable sentries doesn't reset the buster.
	if ( info.GetAttacker() != this && !IsDisposableBuilding() )
	{
		// Sentry Buster mission accomplished
		if ( pOwner )
		{
			pOwner->ResetAccumulatedSentryGunDamageDealt();
			pOwner->ResetAccumulatedSentryGunKillCount();
		}
	}

	// do normal handling
	BaseClass::Killed( info );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectSentrygun::SetModel( const char *pModel )
{
	float flPoseParam0 = 0.0;
	float flPoseParam1 = 0.0;

	// Save pose parameters across model change
	if ( m_iPitchPoseParameter >= 0 )
	{
		flPoseParam0 = GetPoseParameter( m_iPitchPoseParameter );
	}

	if ( m_iYawPoseParameter >= 0 )
	{
		flPoseParam1 = GetPoseParameter( m_iYawPoseParameter );
	}

	BaseClass::SetModel( pModel );

	// Reset this after model change
	SetBuildingSize();
	SetSolid( SOLID_BBOX );

	// Restore pose parameters
	m_iPitchPoseParameter = LookupPoseParameter( "aim_pitch" );
	m_iYawPoseParameter = LookupPoseParameter( "aim_yaw" );

	SetPoseParameter( m_iPitchPoseParameter, flPoseParam0 );
	SetPoseParameter( m_iYawPoseParameter, flPoseParam1 );

	CreateBuildPoints();

	ReattachChildren();

	ResetSequenceInfo();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectSentrygun::SetBuildingSize()
{
	// Mini's do NOT need to have their size set here, SetModelScale already handles scaling for hulls (change from MvM)
	UTIL_SetSize( this, SENTRYGUN_MINS, SENTRYGUN_MAXS );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectSentrygun::MakeCarriedObject( CTFPlayer *pCarrier )
{
	BaseClass::MakeCarriedObject( pCarrier );

	m_iOldAmmoShells = m_iAmmoShells;
	m_iOldAmmoRockets = m_iAmmoRockets;

	m_nShieldLevel.Set( SHIELD_NONE );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectSentrygun::MakeDisposableBuilding( CTFPlayer* pPlayer )
{
	// We don't have our main gun
	if ( !( pPlayer->GetNumObjects( OBJ_SENTRYGUN ) && pPlayer->CanBuild( OBJ_SENTRYGUN ) == CB_CAN_BUILD ) )
		return;

	// We're carrying our main gun
	if ( pPlayer->m_Shared.IsCarryingObject() && pPlayer->m_Shared.GetCarriedObject() && !pPlayer->m_Shared.GetCarriedObject()->IsDisposableBuilding() )
		return;

	if ( IsDisposableBuilding() )
		return;

	SetMaxHealth( SENTRYGUN_MINI_MAX_HEALTH );
	SetHealth( SENTRYGUN_MINI_MAX_HEALTH );

	SetModelScale( DISPOSABLE_SCALE );
	
	BaseClass::MakeDisposableBuilding( pPlayer );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectSentrygun::RemoveAllAmmo()
{
	m_iOldAmmoShells = m_iAmmoShells;
	m_iOldAmmoRockets = m_iAmmoRockets;

	m_iAmmoShells = 0;
	m_iAmmoRockets = 0;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectSentrygun::EmitSentrySound( IRecipientFilter& filter, int iEntIndex, const char *soundname )
{
	EmitSound_t params;
	params.m_pSoundName = soundname;
	params.m_flSoundTime = 0;
	params.m_pflSoundDuration = 0;
	params.m_bWarnOnDirectWaveReference = true;
	
	if ( IsMiniBuilding() )
	{
		StopSound( soundname );
		params.m_nPitch = PITCH_HIGH;
		params.m_nFlags = SND_CHANGE_PITCH;
	}

	EmitSound( filter, entindex(), params );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectSentrygun::EmitSentrySound( const char* soundname )
{
	CPASAttenuationFilter filter( this, soundname );

	EmitSound_t params;
	params.m_pSoundName = soundname;
	params.m_flSoundTime = 0;
	params.m_pflSoundDuration = 0;
	params.m_bWarnOnDirectWaveReference = true;
	
	if ( IsMiniBuilding() || m_flFireRate != 1.f )
	{
		StopSound( soundname );
		params.m_nPitch = IsMiniBuilding() ? PITCH_HIGH : RemapValClamped( m_flFireRate, 1.0f, 0.5f, 100.f, 120.f );
		params.m_nFlags = SND_CHANGE_PITCH;
	}

	EmitSound( filter, entindex(), params );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CTFPlayer *CObjectSentrygun::GetAssistingTeammate( float maxAssistDuration ) const
{
	if ( maxAssistDuration > 0.0f && ( !m_lastTeammateWrenchHitTimer.HasStarted() || m_lastTeammateWrenchHitTimer.IsGreaterThen( maxAssistDuration ) ) )
		return NULL;

	return m_lastTeammateWrenchHit;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectSentrygun::SetAutoAimTarget( CTFPlayer* pPlayer )
{
	if ( !pPlayer )
		return;

	// No auto aim target if a dummy is found
	CBaseEntity *pTargetOld = m_hEnemy.Get();
	if ( pTargetOld )
	{
		CTFTargetDummy *pDummy = dynamic_cast<CTFTargetDummy*>( pTargetOld );
		if ( pDummy )
		{
			m_hAutoAimTarget = NULL;
			return;
		}
	}

	m_hAutoAimTarget = pPlayer;
	m_flAutoAimStartTime = gpGlobals->curtime;
}


//-----------------------------------------------------------------------------
void CObjectSentrygun::UpdateNavMeshCombatStatus( void )
{
	// mark region as 'in combat'
	if ( m_inCombatThrottleTimer.IsElapsed() )
	{
		// important to keep this at one second, so rate cvars make sense (units/sec)
		m_inCombatThrottleTimer.Start( 1.0f );

		UpdateLastKnownArea();

		// only search up/down StepHeight as a cheap substitute for line of sight
		CUtlVector< CNavArea * > nearbyAreaVector;
		CollectSurroundingAreas( &nearbyAreaVector, GetLastKnownArea(), tf_nav_in_combat_range.GetFloat(), StepHeight, StepHeight );

		for( int i=0; i<nearbyAreaVector.Count(); ++i )
		{
			CTFNavArea *area = static_cast< CTFNavArea * >( nearbyAreaVector[i] );

			// hacky - we want sentry gunfire to immediately heat the area since it is so dangerous
			area->OnCombat();
			area->OnCombat();
			area->OnCombat();
			area->OnCombat();
			area->OnCombat();
		}
	}
}
//-------------------------------------------------------------------------------------------------------------------------------
int CObjectSentrygun::GetUpgradeMetalRequired()
{
	int iMetal = BaseClass::GetUpgradeMetalRequired();
	int iSmallSentry = 0;
	CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwner(), iSmallSentry, build_small_sentries );
	if ( iSmallSentry )
	{
		iMetal *= 0.75f;
	}
	
	return iMetal;
}

//-------------------------------------------------------------------------------------------------------------------------------
int CObjectSentrygun::GetMaxHealthForCurrentLevel( void )
{
	int iHealth = BaseClass::GetMaxHealthForCurrentLevel();
	if ( IsScaledSentry() )
	{
		iHealth *= 0.66f;
	}
	return iHealth;
}
//-------------------------------------------------------------------------------------------------------------------------------
void CObjectSentrygun::MakeScaledBuilding( CTFPlayer *pPlayer )
{
	int iSmallSentry = 0;
	CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwner(), iSmallSentry, build_small_sentries );
	if ( iSmallSentry )
	{
		m_flScaledSentry = iSmallSentry ? SMALL_SENTRY_SCALE : 1.0f;

		SetModelScale( m_flScaledSentry );

		int iHealth = GetMaxHealthForCurrentLevel();

		SetMaxHealth( iHealth );
		SetHealth( iHealth );
		SetBuildingSize();
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
LINK_ENTITY_TO_CLASS( tf_projectile_sentryrocket, CTFProjectile_SentryRocket );

IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SentryRocket, DT_TFProjectile_SentryRocket )

BEGIN_NETWORK_TABLE( CTFProjectile_SentryRocket, DT_TFProjectile_SentryRocket )
END_NETWORK_TABLE()

//-----------------------------------------------------------------------------
// Purpose: Creation
//-----------------------------------------------------------------------------
CTFProjectile_SentryRocket *CTFProjectile_SentryRocket::Create( const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner, CBaseEntity *pScorer )
{
	CTFProjectile_SentryRocket *pRocket = static_cast<CTFProjectile_SentryRocket*>( CTFBaseRocket::Create( NULL, "tf_projectile_sentryrocket", vecOrigin, vecAngles, pOwner ) );

	if ( pRocket )
	{
		pRocket->SetScorer( pScorer );
	}

	return pRocket;
}

CTFProjectile_SentryRocket::CTFProjectile_SentryRocket()
{
	UseClientSideAnimation();
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFProjectile_SentryRocket::Spawn()
{
	BaseClass::Spawn();

	SetModel( SENTRY_ROCKET_MODEL );

	UTIL_SetSize( this, vec3_origin, vec3_origin );

	ResetSequence( LookupSequence("idle") );
}

#ifdef STAGING_ONLY
//-----------------------------------------------------------------------------
// Purpose: Directly create a sentry gun at the precise position and orientation desired
//-----------------------------------------------------------------------------
void CC_SentrygunSpawn( const CCommand& args )
{
	if ( !UTIL_IsCommandIssuedByServerAdmin() )
		return; 

	CObjectSentrygun *sentry = (CObjectSentrygun *)CreateEntityByName( "obj_sentrygun" );
	if ( sentry )
	{
		CBasePlayer* pPlayer = UTIL_GetCommandClient();
		trace_t tr;
		Vector forward;
		pPlayer->EyeVectors( &forward );
		UTIL_TraceLine( pPlayer->EyePosition(),
						pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH,MASK_SOLID, 
						pPlayer, COLLISION_GROUP_NONE, &tr );

		if ( tr.fraction != 1.0 )
		{
			sentry->SetAbsOrigin( tr.endpos );
			QAngle angles = pPlayer->BodyAngles();
			angles.x = 0.0f;
			angles.z = 0.0f;
			sentry->SetAbsAngles( angles );
		}

		int iSentryLevel = 2;
		int iTeamNum = pPlayer->GetTeamNumber();

		if ( args.ArgC() > 1 )
		{
			int i = atoi(args[1]);
			if ( abs(i) >= 1 && abs(i) <= 3)
			{
				iSentryLevel = abs(i)-1;
			}

			if ( i < 0)
			{
				iTeamNum = GetEnemyTeam( iTeamNum );
			}
		}

		sentry->m_nDefaultUpgradeLevel = iSentryLevel;

		sentry->Spawn();
		sentry->ChangeTeam( iTeamNum );

		sentry->InitializeMapPlacedObject();
	}
}
static ConCommand sentrygun_spawn( "sentrygun_spawn", CC_SentrygunSpawn, "Spawns a Sentrygun where the player is looking. Takes a parameter for level of sentry [1-3: default 3]. If the passed sentry level < 0, an enemy sentry is spawned.", FCVAR_GAMEDLL | FCVAR_CHEAT );

#endif // STAGING_ONLY