|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Slowly damages the object it's attached to
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "tf_player.h"
#include "tf_team.h"
#include "tf_gamerules.h"
#include "tf_obj.h"
#include "tf_obj_sentrygun.h"
#include "tf_obj_sapper.h"
#include "ndebugoverlay.h"
#include "tf_gamestats.h"
#include "tf_obj_teleporter.h"
#include "tf_weapon_builder.h"
#include "tf_fx.h"
#include "bot/tf_bot.h"
ConVar tf_mvm_notice_sapped_squadmates_delay( "tf_mvm_notice_sapped_squadmates_delay", "1", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "How long it takes for a squad leader to notice his squadmate was sapped" );
// ------------------------------------------------------------------------ //
#define SAPPER_MINS Vector(0, 0, 0)
#define SAPPER_MAXS Vector(1, 1, 1)
const char * g_sapperModel = "models/buildables/sapper_placed.mdl"; const char * g_sapperPlacementModel = "models/buildables/sapper_placement.mdl";
BEGIN_DATADESC( CObjectSapper ) DEFINE_THINKFUNC( SapperThink ), END_DATADESC()
IMPLEMENT_SERVERCLASS_ST(CObjectSapper, DT_ObjectSapper) END_SEND_TABLE();
LINK_ENTITY_TO_CLASS(obj_attachment_sapper, CObjectSapper); PRECACHE_REGISTER(obj_attachment_sapper);
ConVar obj_sapper_amount( "obj_sapper_amount", "25", FCVAR_NONE, "Amount of health inflicted by a Sapper object per second" );
#define SAPPER_THINK_CONTEXT "SapperThink"
#define SAPPER_REMOVE_DISABLE_TIME 0.5f
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CObjectSapper::CObjectSapper() { m_szPlacementModel[ 0 ] = '\0'; m_szSapperModel[ 0 ] = '\0'; szSapperSound[ 0 ] = '\0';
m_iHealth = GetBaseHealth(); SetMaxHealth( m_iHealth );
m_flSelfDestructTime = 0;
UseClientSideAnimation(); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectSapper::UpdateOnRemove() { StopSound( "Weapon_Sapper.Timer" ); StopSound( "Weapon_sd_sapper.Timer" ); StopSound( "Weapon_p2rec.Timer" ); #ifdef STAGING_ONLY
StopSound( "WeaponDynamiteSapper.TickTock" ); StopSound( "WeaponDynamiteSapper.BellRing" ); #endif
if( GetBuilder() ) { GetBuilder()->OnSapperFinished( m_flSapperStartTime ); }
BaseClass::UpdateOnRemove(); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectSapper::Spawn() { SetModel( GetSapperModelName( SAPPER_MODEL_PLACEMENT ) );
m_takedamage = DAMAGE_YES; m_iHealth = GetBaseHealth();
SetType( OBJ_ATTACHMENT_SAPPER );
BaseClass::Spawn();
Vector mins = SAPPER_MINS; Vector maxs = SAPPER_MAXS; CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &mins, &maxs );
int nFlags = m_fObjectFlags | OF_ALLOW_REPEAT_PLACEMENT;
// Don't allow repeat placement as a human spy in MvM
if ( TFGameRules() && TFGameRules()->GameModeUsesMiniBosses() && GetBuilder() && !GetBuilder()->IsBot() ) { nFlags &= ~( OF_ALLOW_REPEAT_PLACEMENT ); }
m_fObjectFlags.Set( nFlags );
SetSolid( SOLID_NONE );
#ifdef STAGING_ONLY
m_bIsRinging = false; #endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectSapper::Precache() { Precache( "c_sapper.mdl" ); // Precache the placed and placement models for the sappers
#ifdef STAGING_ONLY
Precache( "c_sd_sapper.mdl" ); #else
Precache( "w_sd_sapper.mdl" ); #endif
Precache( "c_p2rec.mdl" ); Precache( "c_sapper_xmas.mdl" ); Precache( "c_breadmonster_sapper.mdl" );
PrecacheScriptSound( "Weapon_Sapper.Plant" ); PrecacheScriptSound( "Weapon_Sapper.Timer" ); PrecacheScriptSound( "Weapon_sd_sapper.Timer" ); PrecacheScriptSound( "Weapon_p2rec.Timer" ); #ifdef STAGING_ONLY
PrecacheScriptSound( "WeaponDynamiteSapper.TickTock" ); PrecacheScriptSound( "WeaponDynamiteSapper.BellRing" ); #endif
// Precache the Wheatley Sapper sounds
PrecacheScriptSound( "PSap.null" ); PrecacheScriptSound( "Psap.Attached" ); PrecacheScriptSound( "Psap.AttachedPW" ); PrecacheScriptSound( "PSap.Damage" ); PrecacheScriptSound( "PSap.Death" ); PrecacheScriptSound( "PSap.DeathLong" ); PrecacheScriptSound( "PSap.Deploy" ); PrecacheScriptSound( "PSap.DeployAgain" ); PrecacheScriptSound( "PSap.DeployIntro" ); PrecacheScriptSound( "PSap.Hacked" ); PrecacheScriptSound( "Psap.HackedFollowup" ); PrecacheScriptSound( "Psap.HackedLoud" ); PrecacheScriptSound( "PSap.Hacking" ); PrecacheScriptSound( "PSap.HackingPW" ); PrecacheScriptSound( "PSap.HackingShort" ); PrecacheScriptSound( "PSap.Holster" ); PrecacheScriptSound( "PSap.HolsterFast" ); PrecacheScriptSound( "Psap.Idle" ); PrecacheScriptSound( "Psap.IdleHack02" ); PrecacheScriptSound( "Psap.IdleHarmless02" ); PrecacheScriptSound( "PSap.IdleIntro01" ); PrecacheScriptSound( "PSap.IdleIntro02" ); PrecacheScriptSound( "PSap.IdleIntro03" ); PrecacheScriptSound( "PSap.IdleIntro04" ); PrecacheScriptSound( "PSap.IdleKnife02" ); PrecacheScriptSound( "PSap.IdleKnife03" ); PrecacheScriptSound( "PSap.Sneak" );
BaseClass::Precache(); }
void CObjectSapper::Precache( const char *pchBaseModel ) { m_szPlacementModel[ 0 ] = '\0'; m_szSapperModel[ 0 ] = '\0';
int iModelIndex;
iModelIndex = PrecacheModel( GetSapperModelName( SAPPER_MODEL_PLACED, pchBaseModel ) ); PrecacheGibsForModel( iModelIndex ); PrecacheModel( GetSapperModelName( SAPPER_MODEL_PLACEMENT, pchBaseModel ) );
m_szPlacementModel[ 0 ] = '\0'; m_szSapperModel[ 0 ] = '\0'; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectSapper::FinishedBuilding( void ) { BaseClass::FinishedBuilding();
CBaseEntity *pEntity = m_hBuiltOnEntity.Get(); if ( pEntity ) { if ( GetParentObject() ) { GetParentObject()->OnAddSapper();
CBaseObject *pObject = dynamic_cast<CBaseObject *>( m_hBuiltOnEntity.Get() ); if ( pObject ) { if ( GetBuilder() && pObject->GetBuilder() ) { IGameEvent * event = gameeventmanager->CreateEvent( "player_sapped_object" ); if ( event ) { event->SetInt( "userid", GetBuilder()->GetUserID() ); event->SetInt( "ownerid", pObject->GetBuilder()->GetUserID() ); event->SetInt( "object", pObject->ObjectType() ); event->SetInt( "sapperid", entindex() );
gameeventmanager->FireEvent( event ); } } } } }
if( GetBuilder() ) { m_flSapperStartTime = gpGlobals->curtime; GetBuilder()->OnSapperStarted( m_flSapperStartTime ); }
EmitSound( "Weapon_Sapper.Plant" ); EmitSound( GetSapperSoundName() ); // start looping "Weapon_Sapper.Timer", killed when we die
m_flSapperDamageAccumulator = 0; m_flLastThinkTime = gpGlobals->curtime; m_flLastHealthLeachTime = gpGlobals->curtime;
SetContextThink( &CObjectSapper::SapperThink, gpGlobals->curtime + 0.1, SAPPER_THINK_CONTEXT ); }
//-----------------------------------------------------------------------------
// Purpose: Change our model based on the object we are attaching to
//-----------------------------------------------------------------------------
void CObjectSapper::SetupAttachedVersion( void ) { if ( !IsParentValid() ) return;
if ( IsPlacing() ) { CBaseEntity *pEntity = m_hBuiltOnEntity.Get(); if ( pEntity ) { SetModel( GetSapperModelName( SAPPER_MODEL_PLACEMENT ) ); } }
BaseClass::SetupAttachedVersion(); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectSapper::OnGoActive( void ) { if ( !IsParentValid() ) return;
// set new model
CBaseEntity *pEntity = m_hBuiltOnEntity.Get();
m_flSelfDestructTime = 0; CTFPlayer *pBuilder = ToTFPlayer( GetBuilder() );
if ( pEntity ) { SetModel( GetSapperModelName( SAPPER_MODEL_PLACED ) ); if ( pEntity->IsPlayer() ) // Sapped bot in MvM mode, or player in bountymode
{ float flTime = 4.f;
if ( pBuilder ) { int iRoboSapper = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pBuilder, iRoboSapper, robo_sapper );
CTFPlayer *pTFParent = ToTFPlayer( GetParentEntity() ); if ( pTFParent && pTFParent->IsAlive() ) { int nRadius = 200;
switch( iRoboSapper ) { case 2: flTime = 5.5f; nRadius = 225; break; case 3: flTime = 7.f; nRadius = 250; break; default: break; }
// Unlimited, single-target version of the RoboSapper
if ( GetObjectMode() == MODE_SAPPER_ANTI_ROBOT ) { nRadius = 0; }
ApplyRoboSapper( pTFParent, flTime, nRadius ); } }
m_flSelfDestructTime = gpGlobals->curtime + flTime; }
#ifdef STAGING_ONLY
//if ( pBuilder )
//{
// float flExplodeOnTimer = 0;
// CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pBuilder, flExplodeOnTimer, sapper_explodes_on_timer )
// {
// if ( flExplodeOnTimer != 0 )
// {
// // timer is based on health of the object
// // Sappers normally do 25dps
// //float flTimer = pEntity->GetMaxHealth() * 0.04f;
// //m_flSelfDestructTime = gpGlobals->curtime + flExplodeOnTimer;
// }
// }
//}
#endif
}
UTIL_SetSize( this, SAPPER_MINS, SAPPER_MAXS ); SetSolid( SOLID_NONE );
BaseClass::OnGoActive(); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CObjectSapper::IsParentValid( void ) { bool bValid = false;
CBaseEntity *pEntity = m_hBuiltOnEntity.Get(); if ( pEntity ) { if ( pEntity->IsPlayer() ) // sapped bot in MvM mode
{ bValid = true; } else { CBaseObject *pObject = dynamic_cast<CBaseObject *>( pEntity ); if ( pObject ) { bValid = true; } } }
if ( !bValid ) { DestroyObject(); }
return bValid; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectSapper::DetachObjectFromObject( void ) { CBaseObject *pParent = GetParentObject(); if ( pParent ) { pParent->OnRemoveSapper();
#ifdef STAGING_ONLY
CTFPlayer *pBuilder = GetBuilder(); if ( pBuilder && pParent->GetHealth() < 0 ) { // Attr on Det
float flExplodeOnTimer = 0; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pBuilder, flExplodeOnTimer, sapper_explodes_on_det );
if ( flExplodeOnTimer ) { float flDamage = pParent->GetMaxHealth() * 1.5; Vector vecOrigin = GetAbsOrigin();
// Use the building as the det position
CTakeDamageInfo detInfo; detInfo.SetDamage( flDamage ); detInfo.SetAttacker( this ); detInfo.SetInflictor( this ); detInfo.SetDamageType( DMG_BLAST );
// Generate Large Radius Damage
float flRadius = 200.0f; CTFRadiusDamageInfo radiusinfo( &detInfo, vecOrigin, flRadius, NULL, flRadius ); TFGameRules()->RadiusDamage( radiusinfo );
DispatchParticleEffect( "explosionTrail_seeds_mvm", vecOrigin, GetAbsAngles() ); } } #endif
}
BaseClass::DetachObjectFromObject(); }
//-----------------------------------------------------------------------------
const char* CObjectSapper::GetSapperModelName( SapperModel_t nModel, const char *pchModelName /*= NULL */) { // Check to see if we have model names generated, if not we must generate
if ( m_szPlacementModel[0] == '\0' || m_szSapperModel[0] == '\0' ) { if ( !pchModelName ) { if ( GetBuilder() ) { CTFWeaponBuilder *pWeapon = dynamic_cast< CTFWeaponBuilder* >( GetBuilder()->Weapon_GetWeaponByType( TF_WPN_TYPE_BUILDING ) ); if ( pWeapon ) { pchModelName = pWeapon->GetWorldModel(); } } }
if ( !pchModelName ) { if ( nModel >= SAPPER_MODEL_PLACEMENT ) return g_sapperPlacementModel; return g_sapperModel; }
// Generate Models
// Name base
char szModelName[ _MAX_PATH ]; V_FileBase( pchModelName, szModelName, sizeof( szModelName ) ); pchModelName = szModelName + 2;
#ifdef STAGING_ONLY
if (!V_strcmp(pchModelName, "sd_sapper")) { V_snprintf(m_szPlacementModel, sizeof(m_szPlacementModel), "models/workshop_partner/buildables/%s%s", pchModelName, "_placement.mdl"); V_snprintf(m_szSapperModel, sizeof(m_szSapperModel), "models/workshop_partner/buildables/%s%s", pchModelName, "_placed.mdl"); } else #endif
{ V_snprintf(m_szPlacementModel, sizeof(m_szPlacementModel), "models/buildables/%s%s", pchModelName, "_placement.mdl"); V_snprintf(m_szSapperModel, sizeof(m_szSapperModel), "models/buildables/%s%s", pchModelName, "_placed.mdl"); } }
if ( nModel >= SAPPER_MODEL_PLACEMENT ) { return m_szPlacementModel; } return m_szSapperModel; }
//-----------------------------------------------------------------------------
const char* CObjectSapper::GetSapperSoundName( void ) { if ( szSapperSound[ 0 ] == '\0' ) { const char *pchModelName = NULL; if ( GetBuilder() ) { CTFWeaponBuilder *pWeapon = dynamic_cast< CTFWeaponBuilder* >( GetBuilder()->Weapon_GetWeaponByType( TF_WPN_TYPE_BUILDING ) ); if ( pWeapon ) { pchModelName = pWeapon->GetWorldModel(); } }
#ifdef STAGING_ONLY
// // Attr on Det
float flExplodeOnTimer = 0; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetBuilder(), flExplodeOnTimer, sapper_explodes_on_det ); if ( flExplodeOnTimer ) { EmitSound( "Weapon_Sapper.Timer" ); return "WeaponDynamiteSapper.TickTock"; } #endif
if ( !pchModelName ) { return "Weapon_Sapper.Timer"; }
char szModelName[ _MAX_PATH ]; V_FileBase( pchModelName, szModelName, sizeof( szModelName ) );
pchModelName = szModelName + 2;
V_snprintf( szSapperSound, sizeof( szSapperSound ), "Weapon_%s.Timer", pchModelName ); }
return szSapperSound; }
//-----------------------------------------------------------------------------
// Purpose: Slowly destroy the object I'm attached to
//-----------------------------------------------------------------------------
void CObjectSapper::SapperThink( void ) { if ( !GetTeam() ) return;
bool bThink = true;
CBaseEntity *pEntity = m_hBuiltOnEntity.Get(); if ( pEntity ) { if ( pEntity->IsPlayer() ) // sapping bots in MvM mode
{ bool bDestroy = false;
CTFPlayer *pTFOwner = ToTFPlayer( m_hBuiltOnEntity.Get() ); CTFPlayer *pBuilder = GetBuilder(); if ( !pBuilder || !pTFOwner || ( pTFOwner && !pTFOwner->IsAlive() ) ) { bDestroy = true; }
#ifdef STAGING_ONLY
/*if ( gpGlobals->curtime >= m_flSelfDestructTime )
{ bDestroy = true; Explode(); }*/ #else
if ( gpGlobals->curtime >= m_flSelfDestructTime ) { bDestroy = true; Explode(); } #endif
if ( bDestroy ) { DestroyObject(); bThink = false; return; } } else { CBaseObject *pObject = GetParentObject(); if ( !pObject ) { DestroyObject(); bThink = false; return; }
// Don't bring objects back from the dead
if ( !pObject->IsAlive() || pObject->IsDying() ) return;
CTFPlayer *pBuilder = GetBuilder();
// how much damage to give this think?
float flTimeSinceLastThink = gpGlobals->curtime - m_flLastThinkTime; float flDamageToGive = ( flTimeSinceLastThink ) * obj_sapper_amount.GetFloat(); CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pBuilder, flDamageToGive, mult_sapper_damage );
// add to accumulator
m_flSapperDamageAccumulator += flDamageToGive;
int iDamage = (int)m_flSapperDamageAccumulator;
m_flSapperDamageAccumulator -= iDamage;
// sapper building damage added to health of Vampire Powerup carrier
if ( TFGameRules() && TFGameRules()->IsPowerupMode() ) { CTFPlayer *pTFOwner = ToTFPlayer( GetOwner() ); if ( pTFOwner && pTFOwner->m_Shared.GetCarryingRuneType() == RUNE_VAMPIRE ) { pTFOwner->TakeHealth( flDamageToGive, DMG_GENERIC ); } }
int iCustomDamage = 0; if ( GetReversesBuildingConstructionSpeed() != 0.0f ) { iCustomDamage = TF_DMG_CUSTOM_SAPPER_RECORDER_DEATH; }
CTakeDamageInfo info; info.SetDamage( iDamage ); info.SetAttacker( this ); info.SetInflictor( this ); info.SetDamageType( DMG_CRUSH ); info.SetDamageCustom( iCustomDamage );
pObject->TakeDamage( info );
if ( gpGlobals->curtime - m_flLastHealthLeachTime > 1.0f ) { m_flLastHealthLeachTime = gpGlobals->curtime;
float flHealOwnerPerSecond = 0.0f; CALL_ATTRIB_HOOK_INT_ON_OTHER( pBuilder, flHealOwnerPerSecond, sapper_damage_leaches_health );
if ( flHealOwnerPerSecond ) { CTFPlayer *pSpyOwner = GetOwner(); if ( pSpyOwner && pSpyOwner->IsAlive() ) { pSpyOwner->TakeHealth( flHealOwnerPerSecond, DMG_IGNORE_MAXHEALTH ); pSpyOwner->m_Shared.HealthKitPickupEffects( flHealOwnerPerSecond ); } } }
#ifdef STAGING_ONLY
if ( !m_bIsRinging && pObject->GetHealth() < 60.0f ) { int iDetonate = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pBuilder, iDetonate, sapper_explodes_on_det ); if ( iDetonate ) { EmitSound( "WeaponDynamiteSapper.BellRing" ); m_bIsRinging = true; } } //float flExplodeOnTimer = 0;
//CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pBuilder, flExplodeOnTimer, sapper_explodes_on_det );
////if ( flExplodeOnTimer != 0 && m_flSelfDestructTime < gpGlobals->curtime )
//if ( flExplodeOnTimer )
//{
// float flDamage = pObject->GetMaxHealth() * 1.5;
// Explode();
// DestroyObject();
// Vector vecOrigin = GetAbsOrigin();
// // Use the building as the det position
// CTakeDamageInfo detInfo;
// detInfo.SetDamage( flDamage );
// detInfo.SetAttacker( this );
// detInfo.SetInflictor( this );
// detInfo.SetDamageType( DMG_BLAST );
// // Destroy the building by doubly applying damage
// pObject->TakeDamage( detInfo );
// // Generate Large Radius Damage
// float flRadius = 200.0f; // same as pipebomb launcher
// CTFRadiusDamageInfo radiusinfo( &detInfo, vecOrigin, flRadius, NULL, flRadius );
// TFGameRules()->RadiusDamage( radiusinfo );
// DispatchParticleEffect( "explosionTrail_seeds_mvm", vecOrigin, GetAbsAngles() );
//}
#endif
} }
if ( bThink ) { SetNextThink( gpGlobals->curtime + 0.1f, SAPPER_THINK_CONTEXT ); }
m_flLastThinkTime = gpGlobals->curtime; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CObjectSapper::OnTakeDamage( const CTakeDamageInfo &info ) { if ( info.GetDamageCustom() != TF_DMG_WRENCH_FIX ) { // See if the weapon has a "I damage sappers" attribute on it
int iDmgSappers = 0; CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase *>(info.GetWeapon()); if ( pWeapon ) { CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iDmgSappers, set_dmg_apply_to_sapper ); } if ( iDmgSappers == 0 ) return 0; }
// Is the damage from something other than another sapper? (which might be on our matching teleporter)
if ( !( info.GetDamageType() & DMG_FROM_OTHER_SAPPER ) ) { if ( GetParentObject() ) { CTakeDamageInfo localDamageInfo = info; localDamageInfo.AddDamageType( DMG_FROM_OTHER_SAPPER );
// If there's a matching teleporter with a sapper then have that sapper take damage, too.
CObjectTeleporter *pParentTeleporter = dynamic_cast< CObjectTeleporter * >( GetParentObject() ); if ( pParentTeleporter ) { // GetMatchingTeleporter is set when a matching teleporter is ACTIVE
// if we don't find the cache matching teleporter, try to find with a more expensive FindMatch func
CObjectTeleporter *pMatchingTeleporter = pParentTeleporter->GetMatchingTeleporter() ? pParentTeleporter->GetMatchingTeleporter() : pParentTeleporter->FindMatch(); if ( pMatchingTeleporter && pMatchingTeleporter->HasSapper() ) { // Do damage to any attached buildings
IHasBuildPoints *pBPInterface = dynamic_cast< IHasBuildPoints * >( pMatchingTeleporter ); int iNumObjects = pBPInterface->GetNumObjectsOnMe(); for ( int iPoint = 0 ; iPoint < iNumObjects ; iPoint++ ) { CBaseObject *pObject = pMatchingTeleporter->GetBuildPointObject( iPoint ); if ( pObject && pObject->IsHostileUpgrade() ) { pObject->TakeDamage( localDamageInfo ); } } } } } }
return BaseClass::OnTakeDamage( info ); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectSapper::Killed( const CTakeDamageInfo &info ) { CBaseEntity *pInflictor = info.GetInflictor(); CBaseEntity *pKiller = info.GetAttacker(); CTFPlayer *pScorer = ToTFPlayer( TFGameRules()->GetDeathScorer( pKiller, pInflictor, this ) );
// We don't own the building we removed the sapper from
if ( pScorer && GetParentObject() && GetParentObject()->GetOwner() != pScorer ) { // Give a bonus point for it
if ( TFGameRules()->GameModeUsesUpgrades() ) { CTF_GameStats.Event_PlayerAwardBonusPoints( pScorer, this, 10 ); }
if ( pScorer->IsPlayerClass( TF_CLASS_ENGINEER ) ) { pScorer->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_DESTROY_SAPPERS, 1 ); } }
// Optional: if a weapon was used to destroy this sapper, we give the weapon an opportunity
// to adjust its stats.
{ CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase *>( info.GetWeapon() ); if ( pWeapon ) { EconEntity_OnOwnerKillEaterEvent( dynamic_cast<CEconEntity *>( info.GetWeapon() ), // econ entity
pWeapon->GetTFPlayerOwner(), // scorer
GetOwner(), // victim
kKillEaterEvent_SapperDestroyed ); } }
CBaseObject *pParent = GetParentObject(); if ( pParent ) { pParent->SetPlasmaDisabled( SAPPER_REMOVE_DISABLE_TIME ); }
BaseClass::Killed( info ); }
int CObjectSapper::GetBaseHealth( void ) { float flSapperHealth = SAPPER_MAX_HEALTH; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetBuilder(), flSapperHealth, mult_sapper_health );
return flSapperHealth; }
//-----------------------------------------------------------------------------
// Purpose: Search for players to apply RoboSapper effects to
//-----------------------------------------------------------------------------
void CObjectSapper::ApplyRoboSapper( CTFPlayer *pTarget, float flDuration, int nRadius /*= 200*/ ) { // Apply effects to primary target
if ( IsValidRoboSapperTarget( pTarget ) ) { ApplyRoboSapperEffects( pTarget, flDuration ); }
// If we have a radius, search it for valid targets
if ( nRadius ) { int iCount = 0; for ( int i = 1; i < gpGlobals->maxClients; i++ ) { CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) ); if ( !pPlayer ) continue;
// Ignore the primary target (handled above)
if ( pPlayer == pTarget ) continue;
// Same team, alive, etc
if ( !IsValidRoboSapperTarget( pPlayer ) ) continue;
// Range check from pTarget
Vector vecDist = pPlayer->GetAbsOrigin() - GetAbsOrigin(); if ( vecDist.LengthSqr() > nRadius * nRadius ) continue;
// Ignore bots we can't see
trace_t trace; UTIL_TraceLine( pPlayer->WorldSpaceCenter(), WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &trace ); if ( trace.fraction < 1.0f ) continue;
// Apply
if ( ApplyRoboSapperEffects( pPlayer, flDuration ) ) iCount++; }
// ACHIEVEMENT_TF_MVM_SPY_SAP_ROBOTS
if ( iCount >= 10 ) { CTFPlayer *pBuilder = ToTFPlayer( GetBuilder() ); if ( pBuilder && TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) { pBuilder->AwardAchievement( ACHIEVEMENT_TF_MVM_SPY_SAP_ROBOTS ); } }
Vector vecOrigin = GetAbsOrigin(); CPVSFilter filter( vecOrigin ); TE_TFParticleEffect( filter, 0.f, "Explosion_ShockWave_01", vecOrigin, vec3_angle ); } }
//-----------------------------------------------------------------------------
// Purpose: Applies effects of the RoboSapper to pTarget for flDuration
//-----------------------------------------------------------------------------
bool CObjectSapper::ApplyRoboSapperEffects( CTFPlayer *pTarget, float flDuration ) { if ( !pTarget ) return false;
int iStunFlags = TF_STUN_MOVEMENT | TF_STUN_CONTROLS | TF_STUN_NO_EFFECTS;
// Giants and players can't be fully incapacitated - only slowed
CTFBot *pTFBot = static_cast<CTFBot *>( pTarget ); if ( ( pTFBot && pTFBot->IsMiniBoss() ) || !pTFBot ) { iStunFlags = TF_STUN_MOVEMENT; }
pTarget->m_Shared.StunPlayer( flDuration, 0.85f, iStunFlags, GetBuilder() ); pTarget->m_Shared.AddCond( TF_COND_SAPPED, flDuration, GetBuilder() );
return true; }
//-----------------------------------------------------------------------------
// Purpose: Valid player to apply RoboSapper effects to?
//-----------------------------------------------------------------------------
bool CObjectSapper::IsValidRoboSapperTarget( CTFPlayer *pTarget ) { if ( !pTarget ) return false;
if ( !pTarget->IsAlive() ) return false;
if ( GetBuilder() && GetBuilder()->GetTeamNumber() == pTarget->GetTeam()->GetTeamNumber() ) return false;
if ( pTarget->m_Shared.IsInvulnerable() ) return false;
if ( pTarget->m_Shared.InCond( TF_COND_PHASE ) ) return false;
if ( pTarget->m_Shared.InCond( TF_COND_SAPPED ) ) return false;
if ( pTarget->m_Shared.InCond( TF_COND_REPROGRAMMED ) ) return false;
return true; }
float CObjectSapper::GetReversesBuildingConstructionSpeed( void ) { float flReverseSpeed = 0.0f; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetBuilder(), flReverseSpeed, sapper_degenerates_buildings );
return flReverseSpeed; }
|