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.
2441 lines
69 KiB
2441 lines
69 KiB
//========= 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
|