summaryrefslogtreecommitdiff
path: root/game/server/tf/tf_obj_sapper.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/tf/tf_obj_sapper.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/server/tf/tf_obj_sapper.cpp')
-rw-r--r--game/server/tf/tf_obj_sapper.cpp914
1 files changed, 914 insertions, 0 deletions
diff --git a/game/server/tf/tf_obj_sapper.cpp b/game/server/tf/tf_obj_sapper.cpp
new file mode 100644
index 0000000..cba0eee
--- /dev/null
+++ b/game/server/tf/tf_obj_sapper.cpp
@@ -0,0 +1,914 @@
+//========= 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;
+}