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; |
|
}
|
|
|