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.
914 lines
26 KiB
914 lines
26 KiB
//========= 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;
|
|
}
|