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.
1402 lines
39 KiB
1402 lines
39 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: TF Pipebomb Grenade. |
|
// |
|
//=============================================================================// |
|
#include "cbase.h" |
|
#include "tf_weaponbase.h" |
|
#include "tf_gamerules.h" |
|
#include "npcevent.h" |
|
#include "engine/IEngineSound.h" |
|
#include "tf_weapon_grenade_pipebomb.h" |
|
#include "tf_weapon_pipebomblauncher.h" |
|
#include "tf_weapon_grenadelauncher.h" |
|
|
|
// Client specific. |
|
#ifdef CLIENT_DLL |
|
#include "c_tf_player.h" |
|
#include "IEffects.h" |
|
#include "materialsystem/imaterialvar.h" |
|
#include "functionproxy.h" |
|
// Server specific. |
|
#else |
|
#include "tf_player.h" |
|
#include "items.h" |
|
#include "tf_weaponbase_grenadeproj.h" |
|
#include "soundent.h" |
|
#include "KeyValues.h" |
|
#include "IEffects.h" |
|
#include "props.h" |
|
#include "func_respawnroom.h" |
|
#include "tf_ammo_pack.h" |
|
#include "takedamageinfo.h" |
|
#include "tf_team.h" |
|
#include "physics_collisionevent.h" |
|
#ifdef TF_RAID_MODE |
|
#include "player_vs_environment/boss_alpha/boss_alpha.h" |
|
#endif // TF_RAID_MODE |
|
#include "tf_weapon_medigun.h" |
|
#endif |
|
|
|
#define TF_WEAPON_PIPEBOMB_TIMER 3.0f //Seconds |
|
|
|
#define TF_WEAPON_PIPEBOMB_GRAVITY 0.5f |
|
#define TF_WEAPON_PIPEBOMB_FRICTION 0.8f |
|
#define TF_WEAPON_PIPEBOMB_ELASTICITY 0.45f |
|
|
|
#define TF_WEAPON_PIPEBOMB_TIMER_DMG_REDUCTION 0.6 |
|
|
|
extern ConVar tf_grenadelauncher_max_chargetime; |
|
ConVar tf_grenadelauncher_chargescale( "tf_grenadelauncher_chargescale", "1.0", FCVAR_CHEAT | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY ); |
|
ConVar tf_grenadelauncher_livetime( "tf_grenadelauncher_livetime", "0.8", FCVAR_CHEAT | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY ); |
|
extern ConVar tf_sticky_radius_ramp_time; |
|
extern ConVar tf_sticky_airdet_radius; |
|
|
|
#ifndef CLIENT_DLL |
|
|
|
ConVar tf_grenadelauncher_min_contact_speed( "tf_grenadelauncher_min_contact_speed", "100", FCVAR_DEVELOPMENTONLY ); |
|
extern ConVar tf_obj_gib_velocity_min; |
|
extern ConVar tf_obj_gib_velocity_max; |
|
extern ConVar tf_obj_gib_maxspeed; |
|
#endif |
|
|
|
IMPLEMENT_NETWORKCLASS_ALIASED( TFGrenadePipebombProjectile, DT_TFProjectile_Pipebomb ) |
|
|
|
BEGIN_NETWORK_TABLE( CTFGrenadePipebombProjectile, DT_TFProjectile_Pipebomb ) |
|
#ifdef CLIENT_DLL |
|
RecvPropInt( RECVINFO( m_bTouched ) ), |
|
RecvPropInt( RECVINFO( m_iType ) ), |
|
RecvPropEHandle( RECVINFO( m_hLauncher ) ), |
|
RecvPropBool( RECVINFO( m_bDefensiveBomb ) ), |
|
#else |
|
SendPropBool( SENDINFO( m_bTouched ) ), |
|
SendPropInt( SENDINFO( m_iType ), 3 ), |
|
SendPropEHandle( SENDINFO( m_hLauncher ) ), |
|
SendPropBool( SENDINFO( m_bDefensiveBomb ) ), |
|
#endif |
|
END_NETWORK_TABLE() |
|
|
|
#ifdef GAME_DLL |
|
static string_t s_iszTrainName; |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : - |
|
//----------------------------------------------------------------------------- |
|
CTFGrenadePipebombProjectile::CTFGrenadePipebombProjectile() |
|
{ |
|
m_bTouched = false; |
|
m_flChargeTime = 0.0f; |
|
m_bDetonateOnPulse = false; |
|
#ifdef GAME_DLL |
|
s_iszTrainName = AllocPooledString( "models/props_vehicles/train_enginecar.mdl" ); |
|
m_flDeflectedTime = 0.0f; |
|
m_bWallShatter = false; |
|
m_bDefensiveBomb = false; |
|
m_bSendPlayerDestroyedEvent = true; |
|
m_bCanTakeDamage = true; |
|
#else |
|
pEffectTrail = NULL; |
|
pEffectCrit = NULL; |
|
m_iCachedDeflect = 0; |
|
m_bHighlight = false; |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : - |
|
//----------------------------------------------------------------------------- |
|
CTFGrenadePipebombProjectile::~CTFGrenadePipebombProjectile() |
|
{ |
|
#ifdef CLIENT_DLL |
|
|
|
if ( pEffectTrail ) |
|
{ |
|
ParticleProp()->StopEmission( pEffectTrail ); |
|
} |
|
if ( pEffectCrit ) |
|
{ |
|
ParticleProp()->StopEmission( pEffectCrit ); |
|
} |
|
if ( m_pGlowEffect ) |
|
{ |
|
delete m_pGlowEffect; |
|
m_pGlowEffect = NULL; |
|
} |
|
|
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CTFGrenadePipebombProjectile::GetWeaponID( void ) const |
|
{ |
|
if ( m_iType == TF_GL_MODE_CANNONBALL ) |
|
{ |
|
return TF_WEAPON_CANNON; |
|
} |
|
|
|
return ( HasStickyEffects() ? TF_WEAPON_GRENADE_PIPEBOMB : TF_WEAPON_GRENADE_DEMOMAN ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CTFGrenadePipebombProjectile::GetDamageType( void ) |
|
{ |
|
int iDmgType = BaseClass::GetDamageType(); |
|
|
|
// If we're a pipebomb, we do distance based damage falloff for just the first few seconds of our life |
|
if ( m_iType == TF_GL_MODE_REMOTE_DETONATE ) |
|
{ |
|
if ( gpGlobals->curtime - m_flCreationTime < 5.0 ) |
|
{ |
|
iDmgType |= DMG_USEDISTANCEMOD; |
|
} |
|
} |
|
|
|
return iDmgType; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTFGrenadePipebombProjectile::ShouldMiniCritOnReflect() const |
|
{ |
|
return GetType() == TF_GL_MODE_REGULAR; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGrenadePipebombProjectile::UpdateOnRemove( void ) |
|
{ |
|
// Tell our launcher that we were removed |
|
CTFPipebombLauncher *pLauncher = dynamic_cast<CTFPipebombLauncher*>( m_hLauncher.Get() ); |
|
|
|
if ( pLauncher ) |
|
{ |
|
pLauncher->DeathNotice( this ); |
|
} |
|
|
|
BaseClass::UpdateOnRemove(); |
|
} |
|
|
|
#ifdef CLIENT_DLL |
|
//============================================================================= |
|
// |
|
// TF Pipebomb Grenade Projectile functions (Client specific). |
|
// |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : const char |
|
//----------------------------------------------------------------------------- |
|
const char *CTFGrenadePipebombProjectile::GetTrailParticleName( void ) |
|
{ |
|
int iTeamNumber = GetTeamNumber(); |
|
|
|
if ( GetDeflected() && m_iType != TF_GL_MODE_REMOTE_DETONATE ) |
|
{ |
|
CTFPlayer *pOwner = ToTFPlayer( GetDeflectOwner() ); |
|
|
|
if ( pOwner ) |
|
{ |
|
iTeamNumber = pOwner->GetTeamNumber(); |
|
} |
|
} |
|
|
|
if ( HasStickyEffects() ) |
|
{ |
|
if ( iTeamNumber == TF_TEAM_BLUE ) |
|
{ |
|
return "stickybombtrail_blue"; |
|
} |
|
else |
|
{ |
|
return "stickybombtrail_red"; |
|
} |
|
} |
|
else |
|
{ |
|
if ( iTeamNumber == TF_TEAM_BLUE ) |
|
{ |
|
return "pipebombtrail_blue"; |
|
} |
|
else |
|
{ |
|
return "pipebombtrail_red"; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : updateType - |
|
//----------------------------------------------------------------------------- |
|
void CTFGrenadePipebombProjectile::OnDataChanged(DataUpdateType_t updateType) |
|
{ |
|
BaseClass::OnDataChanged( updateType ); |
|
|
|
if ( updateType == DATA_UPDATE_CREATED ) |
|
{ |
|
m_flCreationTime = gpGlobals->curtime; |
|
m_bPulsed = false; |
|
|
|
CTFPipebombLauncher *pLauncher = dynamic_cast<CTFPipebombLauncher*>( m_hLauncher.Get() ); |
|
|
|
if ( pLauncher ) |
|
{ |
|
pLauncher->AddPipeBomb( this ); |
|
} |
|
|
|
if ( m_bDefensiveBomb && C_BasePlayer::GetLocalPlayer() == GetThrower() ) |
|
{ |
|
if ( GetTeamNumber() == TF_TEAM_RED ) |
|
{ |
|
m_pGlowEffect = new CGlowObject( this, Vector( 150, 0, 0 ), 1.0, true ); |
|
} |
|
else |
|
{ |
|
m_pGlowEffect = new CGlowObject( this, Vector( 0, 0, 150 ), 1.0, true ); |
|
} |
|
} |
|
|
|
CreateTrailParticles(); |
|
} |
|
else if ( m_bTouched ) |
|
{ |
|
//ParticleProp()->StopEmission(); |
|
} |
|
|
|
if ( m_iCachedDeflect != GetDeflected() ) |
|
{ |
|
CreateTrailParticles(); |
|
} |
|
|
|
m_iCachedDeflect = GetDeflected(); |
|
} |
|
|
|
void CTFGrenadePipebombProjectile::CreateTrailParticles( void ) |
|
{ |
|
if ( pEffectTrail ) |
|
{ |
|
ParticleProp()->StopEmission( pEffectTrail ); |
|
} |
|
|
|
if ( pEffectCrit ) |
|
{ |
|
ParticleProp()->StopEmission( pEffectCrit ); |
|
} |
|
|
|
pEffectTrail = ParticleProp()->Create( GetTrailParticleName(), PATTACH_ABSORIGIN_FOLLOW ); |
|
|
|
int iTeamNumber = GetTeamNumber(); |
|
|
|
if ( GetDeflected() && m_iType != TF_GL_MODE_REMOTE_DETONATE ) |
|
{ |
|
CTFPlayer *pOwner = ToTFPlayer( GetDeflectOwner() ); |
|
|
|
if ( pOwner ) |
|
{ |
|
iTeamNumber = pOwner->GetTeamNumber(); |
|
} |
|
} |
|
|
|
if ( m_bCritical ) |
|
{ |
|
switch( iTeamNumber ) |
|
{ |
|
case TF_TEAM_BLUE: |
|
|
|
if ( HasStickyEffects() ) |
|
{ |
|
pEffectCrit = ParticleProp()->Create( "critical_grenade_blue", PATTACH_ABSORIGIN_FOLLOW ); |
|
} |
|
else |
|
{ |
|
pEffectCrit = ParticleProp()->Create( "critical_pipe_blue", PATTACH_ABSORIGIN_FOLLOW ); |
|
} |
|
break; |
|
case TF_TEAM_RED: |
|
|
|
if ( HasStickyEffects() ) |
|
{ |
|
pEffectCrit = ParticleProp()->Create( "critical_grenade_red", PATTACH_ABSORIGIN_FOLLOW ); |
|
} |
|
else |
|
{ |
|
pEffectCrit = ParticleProp()->Create( "critical_pipe_red", PATTACH_ABSORIGIN_FOLLOW ); |
|
} |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
} |
|
|
|
|
|
extern ConVar tf_grenadelauncher_livetime; |
|
|
|
void CTFGrenadePipebombProjectile::Simulate( void ) |
|
{ |
|
BaseClass::Simulate(); |
|
|
|
if ( !HasStickyEffects() ) |
|
return; |
|
|
|
if ( m_bPulsed == false ) |
|
{ |
|
if ( (gpGlobals->curtime - m_flCreationTime) >= GetLiveTime() ) |
|
{ |
|
if ( GetTeamNumber() == TF_TEAM_RED ) |
|
{ |
|
ParticleProp()->Create( "stickybomb_pulse_red", PATTACH_ABSORIGIN_FOLLOW ); |
|
} |
|
else |
|
{ |
|
ParticleProp()->Create( "stickybomb_pulse_blue", PATTACH_ABSORIGIN_FOLLOW ); |
|
} |
|
|
|
m_bPulsed = true; |
|
|
|
if ( m_bDetonateOnPulse ) |
|
{ |
|
Detonate(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Don't draw if we haven't yet gone past our original spawn point |
|
// Input : flags - |
|
//----------------------------------------------------------------------------- |
|
int CTFGrenadePipebombProjectile::DrawModel( int flags ) |
|
{ |
|
if ( gpGlobals->curtime < ( m_flCreationTime + 0.1 ) ) |
|
return 0; |
|
|
|
return BaseClass::DrawModel( flags ); |
|
} |
|
|
|
#else |
|
|
|
//============================================================================= |
|
// |
|
// TF Pipebomb Grenade Projectile functions (Server specific). |
|
// |
|
#define TF_WEAPON_PIPEGRENADE_MODEL "models/weapons/w_models/w_grenade_grenadelauncher.mdl" |
|
#define TF_WEAPON_CANNONBALL_MODEL "models/weapons/w_models/w_cannonball.mdl" |
|
#define TF_WEAPON_PIPEBOMB_MODEL "models/weapons/w_models/w_stickybomb.mdl" |
|
#define TF_WEAPON_PIPEBOMB2_MODEL "models/weapons/w_models/w_stickybomb2.mdl" |
|
#define TF_WEAPON_PIPEBOMBD_MODEL "models/weapons/w_models/w_stickybomb_d.mdl" |
|
#define TF_WEAPON_PIPEBOMB_BOUNCE_SOUND "Weapon_Grenade_Pipebomb.Bounce" |
|
#define TF_WEAPON_CANNON_IMPACT_SOUND "Weapon_LooseCannon.BallImpact" |
|
#define TF_WEAPON_GRENADE_DETONATE_TIME 2.0f |
|
#define TF_WEAPON_GRENADE_XBOX_DAMAGE 112 |
|
|
|
BEGIN_DATADESC( CTFGrenadePipebombProjectile ) |
|
END_DATADESC() |
|
|
|
LINK_ENTITY_TO_CLASS( tf_projectile_pipe_remote, CTFGrenadePipebombProjectile ); |
|
PRECACHE_WEAPON_REGISTER( tf_projectile_pipe_remote ); |
|
|
|
LINK_ENTITY_TO_CLASS( tf_projectile_pipe, CTFGrenadePipebombProjectile ); |
|
PRECACHE_WEAPON_REGISTER( tf_projectile_pipe ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
const char* CTFGrenadePipebombProjectile::GetPipebombClass( int iPipeBombType ) |
|
{ |
|
switch ( iPipeBombType ) |
|
{ |
|
case TF_GL_MODE_REGULAR: |
|
return "tf_projectile_pipe"; |
|
case TF_GL_MODE_REMOTE_DETONATE: |
|
case TF_GL_MODE_REMOTE_DETONATE_PRACTICE: |
|
return "tf_projectile_pipe_remote"; |
|
default: |
|
return "tf_projectile_pipe"; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CTFGrenadePipebombProjectile* CTFGrenadePipebombProjectile::Create( const Vector &position, const QAngle &angles, |
|
const Vector &velocity, const AngularImpulse &angVelocity, |
|
CBaseCombatCharacter *pOwner, const CTFWeaponInfo &weaponInfo, |
|
int iPipeBombType, float flMultDmg ) |
|
{ |
|
// Translate a projectile type into a pipebomb type. |
|
int iPipeBombDetonateType; |
|
switch ( iPipeBombType ) |
|
{ |
|
case TF_PROJECTILE_PIPEBOMB_REMOTE: |
|
{ |
|
iPipeBombDetonateType = TF_GL_MODE_REMOTE_DETONATE; |
|
} |
|
break; |
|
case TF_PROJECTILE_PIPEBOMB_PRACTICE: |
|
{ |
|
iPipeBombDetonateType = TF_GL_MODE_REMOTE_DETONATE_PRACTICE; |
|
} |
|
break; |
|
case TF_PROJECTILE_CANNONBALL: |
|
{ |
|
iPipeBombDetonateType = TF_GL_MODE_CANNONBALL; |
|
} |
|
break; |
|
default: |
|
iPipeBombDetonateType = TF_GL_MODE_REGULAR; |
|
} |
|
|
|
const char* pszBombClass = GetPipebombClass( iPipeBombDetonateType ); |
|
CTFGrenadePipebombProjectile *pGrenade = static_cast<CTFGrenadePipebombProjectile*>( CBaseEntity::CreateNoSpawn( pszBombClass, position, angles, pOwner ) ); |
|
if ( pGrenade ) |
|
{ |
|
// Set the pipebomb mode before calling spawn, so the model & associated vphysics get setup properly |
|
pGrenade->SetPipebombMode( iPipeBombDetonateType ); |
|
DispatchSpawn( pGrenade ); |
|
|
|
pGrenade->InitGrenade( velocity, angVelocity, pOwner, weaponInfo ); |
|
pGrenade->SetDamage( pGrenade->GetDamage() * flMultDmg ); |
|
pGrenade->SetFullDamage( pGrenade->GetDamage() ); |
|
|
|
if ( pGrenade->m_iType != TF_GL_MODE_REMOTE_DETONATE ) |
|
{ |
|
// Some hackery here. Reduce the damage, so that if we explode on timeout, |
|
// we'll do less damage. If we explode on contact, we'll restore this to full damage. |
|
pGrenade->SetDamage( pGrenade->GetDamage() * TF_WEAPON_PIPEBOMB_TIMER_DMG_REDUCTION ); |
|
} |
|
|
|
pGrenade->ApplyLocalAngularVelocityImpulse( angVelocity ); |
|
|
|
if ( pOwner ) |
|
{ |
|
pGrenade->SetTruceValidForEnt( pOwner->IsTruceValidForEnt() ); |
|
} |
|
} |
|
|
|
return pGrenade; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGrenadePipebombProjectile::Spawn() |
|
{ |
|
if ( HasStickyEffects() ) |
|
{ |
|
// Set this to max, so effectively they do not self-implode. |
|
|
|
if ( m_iType == TF_GL_MODE_REMOTE_DETONATE_PRACTICE ) |
|
{ |
|
SetModel( TF_WEAPON_PIPEBOMB2_MODEL ); |
|
} |
|
else |
|
{ |
|
SetModel( TF_WEAPON_PIPEBOMB_MODEL ); |
|
} |
|
SetDetonateTimerLength( FLT_MAX ); |
|
SetContextThink( &CTFGrenadePipebombProjectile::PreArmThink, gpGlobals->curtime + 0.001f, "PRE_ARM_THINK" ); // Next frame. |
|
SetTouch( &CTFGrenadePipebombProjectile::StickybombTouch ); |
|
} |
|
else |
|
{ |
|
if ( m_iType == TF_GL_MODE_CANNONBALL ) |
|
{ |
|
SetModel( TF_WEAPON_CANNONBALL_MODEL ); |
|
} |
|
else |
|
{ |
|
SetModel( TF_WEAPON_PIPEGRENADE_MODEL ); |
|
} |
|
SetDetonateTimerLength( TF_WEAPON_GRENADE_DETONATE_TIME ); |
|
SetTouch( &CTFGrenadePipebombProjectile::PipebombTouch ); |
|
} |
|
|
|
SetCustomPipebombModel(); |
|
|
|
BaseClass::Spawn(); |
|
|
|
m_bTouched = false; |
|
m_flCreationTime = gpGlobals->curtime; |
|
|
|
// We want to get touch functions called so we can damage enemy players |
|
AddSolidFlags( FSOLID_TRIGGER ); |
|
|
|
m_flMinSleepTime = 0; |
|
AddFlag( FL_GRENADE ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGrenadePipebombProjectile::Precache() |
|
{ |
|
int iModel = PrecacheModel( TF_WEAPON_PIPEBOMB_MODEL ); |
|
PrecacheGibsForModel( iModel ); |
|
|
|
iModel = PrecacheModel( TF_WEAPON_PIPEBOMB2_MODEL ); |
|
PrecacheGibsForModel( iModel ); |
|
|
|
iModel = PrecacheModel( TF_WEAPON_PIPEBOMBD_MODEL ); |
|
PrecacheGibsForModel( iModel ); |
|
|
|
iModel = PrecacheModel( TF_WEAPON_PIPEGRENADE_MODEL ); |
|
PrecacheGibsForModel( iModel ); |
|
|
|
iModel = PrecacheModel( TF_WEAPON_CANNONBALL_MODEL ); |
|
PrecacheGibsForModel( iModel ); |
|
|
|
// Must add All custom Models here |
|
iModel = PrecacheModel( "models/workshop/weapons/c_models/c_kingmaker_sticky/w_kingmaker_stickybomb.mdl" ); |
|
iModel = PrecacheModel( "models/workshop/weapons/c_models/c_quadball/w_quadball_grenade.mdl" ); |
|
|
|
PrecacheParticleSystem( "stickybombtrail_blue" ); |
|
PrecacheParticleSystem( "stickybombtrail_red" ); |
|
|
|
PrecacheScriptSound( TF_WEAPON_PIPEBOMB_BOUNCE_SOUND ); |
|
PrecacheScriptSound( TF_WEAPON_CANNON_IMPACT_SOUND ); |
|
|
|
BaseClass::Precache(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGrenadePipebombProjectile::SetPipebombMode( int iPipebombMode /* = TF_GL_MODE_REGULAR */ ) |
|
{ |
|
m_iType.Set( iPipebombMode ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGrenadePipebombProjectile::BounceSound( void ) |
|
{ |
|
EmitSound( TF_WEAPON_PIPEBOMB_BOUNCE_SOUND ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGrenadePipebombProjectile::Detonate() |
|
{ |
|
if ( gpGlobals->curtime > m_flDetonateTime ) |
|
{ |
|
if ( GetLauncher() ) |
|
{ |
|
float flFizzle = 0; |
|
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetLauncher(), flFizzle, stickybomb_fizzle_time ); |
|
if ( flFizzle ) |
|
{ |
|
Fizzle(); |
|
} |
|
} |
|
} |
|
|
|
if ( m_bFizzle ) |
|
{ |
|
g_pEffects->Sparks( GetAbsOrigin(), 1, 2 ); |
|
Destroy( false ); |
|
|
|
if ( HasStickyEffects() ) |
|
{ |
|
CreatePipebombGibs(); |
|
} |
|
|
|
return; |
|
} |
|
|
|
BaseClass::Detonate(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTFGrenadePipebombProjectile::DetonateStickies() |
|
{ |
|
if ( !GetLauncher() ) |
|
return false; |
|
|
|
bool bDetonateSticky = false; |
|
|
|
Vector vecOrigin = GetAbsOrigin(); |
|
const int maxEntities = 64; |
|
CBaseEntity *pObjects[ maxEntities ]; |
|
int count = UTIL_EntitiesInSphere( pObjects, maxEntities, vecOrigin, GetDamageRadius(), FL_GRENADE ); |
|
|
|
int iStickiesRemoved = 0; |
|
|
|
trace_t tr; |
|
for ( int i = 0; i < count; i++ ) |
|
{ |
|
if ( pObjects[i]->GetTeamNumber() == GetLauncher()->GetTeamNumber() ) |
|
continue; |
|
|
|
CTFGrenadePipebombProjectile *pGrenade = dynamic_cast < CTFGrenadePipebombProjectile*> ( pObjects[i] ); |
|
if ( !pGrenade ) |
|
continue; |
|
|
|
if ( pGrenade->m_iType != TF_GL_MODE_REMOTE_DETONATE ) |
|
continue; |
|
|
|
if ( pGrenade->m_bFizzle ) |
|
continue; |
|
|
|
UTIL_TraceLine( vecOrigin, pGrenade->GetAbsOrigin(), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); |
|
if ( tr.fraction < 1.0 ) |
|
continue; // No line of sight to the bomb. |
|
|
|
pGrenade->Fizzle(); |
|
pGrenade->Detonate(); |
|
|
|
iStickiesRemoved++; |
|
|
|
bDetonateSticky = true; |
|
} |
|
|
|
CTFPlayer *pOwner = ToTFPlayer( GetThrower() ); |
|
if ( iStickiesRemoved && pOwner ) |
|
{ |
|
pOwner->AwardAchievement( ACHIEVEMENT_TF_DEMOMAN_DESTROY_X_STICKYBOMBS, iStickiesRemoved ); |
|
} |
|
|
|
return bDetonateSticky; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGrenadePipebombProjectile::CreatePipebombGibs( void ) |
|
{ |
|
CPVSFilter filter( GetAbsOrigin() ); |
|
UserMessageBegin( filter, "CheapBreakModel" ); |
|
WRITE_SHORT( GetModelIndex() ); |
|
WRITE_VEC3COORD( GetAbsOrigin() ); |
|
MessageEnd(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGrenadePipebombProjectile::Fizzle( void ) |
|
{ |
|
m_bFizzle = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGrenadePipebombProjectile::StickybombTouch( CBaseEntity *pOther ) |
|
{ |
|
#ifdef GAME_DLL |
|
#ifdef TF_RAID_MODE |
|
if ( TFGameRules()->IsRaidMode() ) |
|
{ |
|
if ( dynamic_cast< CBossAlpha * >( pOther ) != NULL ) |
|
{ |
|
// stickies stick to the boss |
|
m_bTouched = true; |
|
VPhysicsGetObject()->EnableMotion( false ); |
|
|
|
SetParent( pOther ); |
|
} |
|
} |
|
#endif |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGrenadePipebombProjectile::PipebombTouch( CBaseEntity *pOther ) |
|
{ |
|
if ( pOther == GetThrower() ) |
|
return; |
|
|
|
// Verify a correct "other." |
|
if ( !pOther->IsSolid() || pOther->IsSolidFlagSet( FSOLID_VOLUME_CONTENTS ) ) |
|
return; |
|
|
|
// Handle hitting skybox (disappear). |
|
trace_t pTrace; |
|
Vector velDir = GetAbsVelocity(); |
|
VectorNormalize( velDir ); |
|
Vector vOrigin = GetAbsOrigin(); |
|
Vector vecSpot = vOrigin - velDir * 32; |
|
UTIL_TraceLine( vecSpot, vecSpot + velDir * 64, MASK_SOLID, this, COLLISION_GROUP_NONE, &pTrace ); |
|
|
|
if ( pTrace.fraction < 1.0 && pTrace.surface.flags & SURF_SKY ) |
|
{ |
|
UTIL_Remove( this ); |
|
return; |
|
} |
|
|
|
// PASSTIME always explode when it hits the ball |
|
// fixme find a non-strcmp way to do this |
|
if ( !V_strcmp( pOther->GetClassname(), "passtime_ball" ) ) |
|
{ |
|
Explode( &pTrace, GetDamageType() ); |
|
return; |
|
} |
|
|
|
//If we already touched a surface then we're not exploding on contact anymore. |
|
if ( m_bTouched == true ) |
|
return; |
|
|
|
bool bExploded = false; |
|
|
|
// Blow up if we hit an enemy we can damage |
|
if ( pOther->GetTeamNumber() && pOther->GetTeamNumber() != GetTeamNumber() && pOther->m_takedamage != DAMAGE_NO ) |
|
{ |
|
// Check to see if this is a respawn room. |
|
if ( !pOther->IsPlayer() ) |
|
{ |
|
CFuncRespawnRoom *pRespawnRoom = dynamic_cast<CFuncRespawnRoom*>( pOther ); |
|
if ( pRespawnRoom ) |
|
{ |
|
if ( !pRespawnRoom->PointIsWithin( vOrigin ) ) |
|
return; |
|
} |
|
} |
|
|
|
if ( m_iType == TF_GL_MODE_CANNONBALL ) |
|
{ |
|
// Damage the player to push them back |
|
CBaseEntity *pAttacker = GetThrower(); |
|
if ( pAttacker && ( pOther->IsPlayer() || pOther->IsBaseObject() ) ) |
|
{ |
|
// check if we already penetrate through this victim |
|
if ( !m_penetratedEntities.HasElement( pOther ) ) |
|
{ |
|
// Impact damage scales with distance |
|
float flDistanceSq = (pOther->GetAbsOrigin() - pAttacker->GetAbsOrigin()).LengthSqr(); |
|
float flImpactDamage = RemapValClamped( flDistanceSq, 512 * 512, 1024 * 1024, 50, 25 ); |
|
|
|
CTakeDamageInfo info( this, pAttacker, m_hLauncher, vec3_origin, vOrigin, flImpactDamage, GetDamageType(), TF_DMG_CUSTOM_CANNONBALL_PUSH ); |
|
pOther->TakeDamage( info ); |
|
|
|
CTFPlayer *pVictim = ToTFPlayer( pOther ); |
|
if ( pVictim ) |
|
{ |
|
// apply airblast - Apply stun if they are effectively grounded so we can knock them up |
|
if ( !pVictim->m_Shared.InCond( TF_COND_KNOCKED_INTO_AIR ) ) |
|
{ |
|
pVictim->m_Shared.StunPlayer( 0.5, 1.f, TF_STUN_MOVEMENT, ToTFPlayer( pAttacker ) ); |
|
} |
|
|
|
Vector vecToTarget = pVictim->WorldSpaceCenter() - pAttacker->WorldSpaceCenter(); |
|
VectorNormalize( vecToTarget ); |
|
vecToTarget *= 400; |
|
vecToTarget.z += 350; // Mimic Flamethrower AirBlast |
|
pVictim->ApplyAirBlastImpulse( vecToTarget ); |
|
} |
|
|
|
m_penetratedEntities.AddToTail( pOther ); |
|
|
|
EmitSound( TF_WEAPON_CANNON_IMPACT_SOUND ); |
|
|
|
// Add this guy to our donk list. If this grenade explodes and hits anyone on our launcher's |
|
// donk list, they get minicrit |
|
CTFGrenadeLauncher* pLauncher = dynamic_cast<CTFGrenadeLauncher*>( GetLauncher() ); |
|
if( pLauncher ) |
|
{ |
|
pLauncher->AddDonkVictim( pOther ); |
|
} |
|
} |
|
return; |
|
} |
|
} |
|
|
|
// Save this entity as enemy, they will take 100% damage. |
|
m_hEnemy = pOther; |
|
|
|
// Restore damage. See comment in CTFGrenadePipebombProjectile::Create() above to understand this. |
|
m_flDamage = m_flFullDamage; |
|
Explode( &pTrace, GetDamageType() ); |
|
bExploded = true; |
|
} |
|
|
|
// Train hack! |
|
if ( !bExploded && pOther->GetModelName() == s_iszTrainName && ( pOther->GetAbsVelocity().LengthSqr() > 1.0f ) ) |
|
{ |
|
Explode( &pTrace, GetDamageType() ); |
|
bExploded = true; |
|
} |
|
|
|
// Explode on contact with a Boss, too |
|
if ( !bExploded && TFGameRules()->GetActiveBoss() && pOther == TFGameRules()->GetActiveBoss() ) |
|
{ |
|
Explode( &pTrace, GetDamageType() ); |
|
bExploded = true; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
extern bool PropDynamic_CollidesWithGrenades( CBaseEntity* pBaseEntity ); |
|
|
|
void CTFGrenadePipebombProjectile::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) |
|
{ |
|
BaseClass::VPhysicsCollision( index, pEvent ); |
|
|
|
int otherIndex = !index; |
|
CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex]; |
|
|
|
if ( !pHitEntity ) |
|
return; |
|
|
|
if ( m_bWallShatter ) |
|
{ |
|
Fizzle(); |
|
Detonate(); |
|
return; |
|
} |
|
|
|
if ( m_iType == TF_GL_MODE_REGULAR || m_iType == TF_GL_MODE_CANNONBALL ) |
|
{ |
|
if ( PropDynamic_CollidesWithGrenades( pHitEntity) ) |
|
{ |
|
if ( m_bTouched == false ) |
|
{ |
|
SetThink( &CTFGrenadePipebombProjectile::Detonate ); |
|
SetNextThink( gpGlobals->curtime ); |
|
} |
|
} |
|
// Blow up if we hit an enemy we can damage |
|
else if ( pHitEntity->GetTeamNumber() && pHitEntity->GetTeamNumber() != GetTeamNumber() && pHitEntity->m_takedamage != DAMAGE_NO ) |
|
{ |
|
SetThink( &CTFGrenadePipebombProjectile::Detonate ); |
|
SetNextThink( gpGlobals->curtime ); |
|
} |
|
|
|
if ( m_bTouched == false ) |
|
{ |
|
SetDamage( GetDamageScaleOnWorldContact() * GetDamage() ); |
|
|
|
int iNoBounce = 0; |
|
if ( GetLauncher() ) |
|
{ |
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( GetLauncher(), iNoBounce, grenade_no_bounce ) |
|
if ( iNoBounce ) |
|
{ |
|
Vector velocity; |
|
AngularImpulse angularVelocity; |
|
VPhysicsGetObject()->GetVelocity( &velocity, &angularVelocity ); |
|
velocity *= 0.1f; |
|
VPhysicsGetObject()->SetVelocity( &velocity, &angularVelocity ); |
|
} |
|
} |
|
} |
|
|
|
m_bTouched = true; |
|
return; |
|
} |
|
|
|
// Handle hitting skybox (disappear). |
|
surfacedata_t *pprops = physprops->GetSurfaceData( pEvent->surfaceProps[otherIndex] ); |
|
if ( pprops->game.material == 'X' ) |
|
{ |
|
// uncomment to destroy grenade upon hitting sky brush |
|
//SetThink( &CTFGrenadePipebombProjectile::SUB_Remove ); |
|
//SetNextThink( gpGlobals->curtime ); |
|
return; |
|
} |
|
|
|
bool bIsDynamicProp = ( NULL != dynamic_cast<CDynamicProp *>( pHitEntity ) ); |
|
|
|
// Temp: Don't stick to the saw blades in sawmill. |
|
// We should make the saws their own entity type for networking. |
|
if ( FStrEq( pHitEntity->m_iParent.ToCStr(), "sawmovelinear01" ) || |
|
FStrEq( pHitEntity->m_iParent.ToCStr(), "sawmovelinear02" ) || |
|
PropDynamic_CollidesWithGrenades( pHitEntity) ) |
|
{ |
|
bIsDynamicProp = false; |
|
} |
|
|
|
// Pipebombs stick to the world when they touch it |
|
if ( pHitEntity && ( pHitEntity->IsWorld() || bIsDynamicProp ) && gpGlobals->curtime > m_flMinSleepTime ) |
|
{ |
|
m_bTouched = true; |
|
|
|
g_PostSimulationQueue.QueueCall( VPhysicsGetObject(), &IPhysicsObject::EnableMotion, false ); |
|
|
|
// Save impact data for explosions. |
|
m_bUseImpactNormal = true; |
|
pEvent->pInternalData->GetSurfaceNormal( m_vecImpactNormal ); |
|
m_vecImpactNormal.Negate(); |
|
m_flTouchedTime = gpGlobals->curtime; |
|
|
|
float flFizzle = 0; |
|
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetLauncher(), flFizzle, stickybomb_fizzle_time ); |
|
if ( flFizzle > 0 ) |
|
{ |
|
SetDetonateTimerLength( flFizzle ); |
|
} |
|
} |
|
} |
|
|
|
ConVar tf_grenade_forcefrom_bullet( "tf_grenade_forcefrom_bullet", "2.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); |
|
ConVar tf_grenade_forcefrom_buckshot( "tf_grenade_forcefrom_buckshot", "0.75", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); |
|
ConVar tf_grenade_forcefrom_blast( "tf_grenade_forcefrom_blast", "0.15", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); |
|
ConVar tf_grenade_force_sleeptime( "tf_grenade_force_sleeptime", "1.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); // How long after being shot will we re-stick to the world. |
|
ConVar tf_pipebomb_force_to_move( "tf_pipebomb_force_to_move", "1500.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); |
|
ConVar tf_pipebomb_deflect_reset_time( "tf_pipebomb_deflect_reset_time", "10.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: If we are shot after being stuck to the world, move a bit |
|
//----------------------------------------------------------------------------- |
|
int CTFGrenadePipebombProjectile::OnTakeDamage( const CTakeDamageInfo &info ) |
|
{ |
|
if ( !info.GetAttacker() ) |
|
{ |
|
Assert( !info.GetAttacker() ); |
|
return 0; |
|
} |
|
|
|
bool bSameTeam = ( info.GetAttacker()->GetTeamNumber() == GetTeamNumber() ); |
|
if ( !bSameTeam && CanTakeDamage() ) |
|
{ |
|
if ( m_bTouched && HasStickyEffects() && ( info.GetDamageType() & (DMG_BULLET|DMG_BUCKSHOT|DMG_BLAST|DMG_SONIC|DMG_MELEE) ) ) |
|
{ |
|
Vector vecForce = info.GetDamageForce(); |
|
|
|
bool bBreakPipes = false; |
|
|
|
if ( info.GetDamageType() & (DMG_BULLET|DMG_MELEE) ) |
|
{ |
|
vecForce *= tf_grenade_forcefrom_bullet.GetFloat(); |
|
bBreakPipes = true; |
|
} |
|
if ( info.GetDamageType() & DMG_SONIC ) |
|
{ |
|
vecForce *= tf_grenade_forcefrom_bullet.GetFloat(); |
|
} |
|
else if ( info.GetDamageType() & DMG_BUCKSHOT ) |
|
{ |
|
vecForce *= tf_grenade_forcefrom_buckshot.GetFloat(); |
|
bBreakPipes = true; |
|
} |
|
else if ( info.GetDamageType() & DMG_BLAST ) |
|
{ |
|
vecForce *= tf_grenade_forcefrom_blast.GetFloat(); |
|
} |
|
|
|
if ( bBreakPipes == true ) |
|
{ |
|
// we might get multiple calls for the same pipe when shooting it with a shotgun, |
|
// so make sure it only sends the player_destroyed_pipebomb event once |
|
if ( m_bSendPlayerDestroyedEvent ) |
|
{ |
|
if ( info.GetAttacker()->IsPlayer() ) |
|
{ |
|
CTFPlayer *pPlayer = ToTFPlayer( info.GetAttacker() ); |
|
if ( pPlayer ) |
|
{ |
|
IGameEvent * event = gameeventmanager->CreateEvent( "player_destroyed_pipebomb" ); |
|
if ( event ) |
|
{ |
|
event->SetInt( "userid", pPlayer->GetUserID() ); |
|
gameeventmanager->FireEvent( event ); |
|
} |
|
|
|
if ( pPlayer->IsPlayerClass( TF_CLASS_ENGINEER ) ) |
|
{ |
|
// If we are near a building, award achievement progress. |
|
CTFTeam *pTeam = pPlayer->GetTFTeam(); |
|
if ( pTeam ) |
|
{ |
|
for ( int i=0; i<pTeam->GetNumObjects(); i++ ) |
|
{ |
|
CBaseObject *pObject = pTeam->GetObject(i); |
|
if ( pObject && pObject->GetAbsOrigin().DistTo( GetAbsOrigin() ) < 100 && |
|
pObject->ObjectType() != OBJ_ATTACHMENT_SAPPER ) |
|
{ |
|
pPlayer->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_DESTROY_STICKIES, 1 ); |
|
break; // Only one award per sticky. |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
m_bSendPlayerDestroyedEvent = false; |
|
} |
|
} |
|
|
|
Fizzle(); |
|
Detonate(); |
|
} |
|
|
|
// If the force is sufficient, detach & move the pipebomb |
|
float flForce = tf_pipebomb_force_to_move.GetFloat(); |
|
if ( vecForce.LengthSqr() > (flForce*flForce) ) |
|
{ |
|
if ( VPhysicsGetObject() ) |
|
{ |
|
VPhysicsGetObject()->EnableMotion( true ); |
|
} |
|
|
|
CTakeDamageInfo newInfo = info; |
|
newInfo.SetDamageForce( vecForce ); |
|
|
|
VPhysicsTakeDamage( newInfo ); |
|
|
|
// The pipebomb will re-stick to the ground after this time expires |
|
m_flMinSleepTime = gpGlobals->curtime + tf_grenade_force_sleeptime.GetFloat(); |
|
m_bTouched = false; |
|
|
|
// It has moved the data is no longer valid. |
|
m_bUseImpactNormal = false; |
|
m_vecImpactNormal.Init(); |
|
|
|
return 1; |
|
} |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
void CTFGrenadePipebombProjectile::IncrementDeflected( void ) |
|
{ |
|
BaseClass::IncrementDeflected(); |
|
|
|
if ( GetDeflected() && HasStickyEffects() ) |
|
{ |
|
m_flDeflectedTime = gpGlobals->curtime + tf_pipebomb_deflect_reset_time.GetFloat(); |
|
} |
|
|
|
int iTeamNumber = GetTeamNumber(); |
|
|
|
CTFPlayer *pOwner = ToTFPlayer( GetDeflectOwner() ); |
|
|
|
if ( pOwner ) |
|
{ |
|
iTeamNumber = pOwner->GetTeamNumber(); |
|
} |
|
|
|
if ( !HasStickyEffects() ) |
|
{ |
|
m_nSkin = ( iTeamNumber == TF_TEAM_BLUE ) ? 1 : 0; |
|
} |
|
} |
|
|
|
void CTFGrenadePipebombProjectile::DetonateThink( void ) |
|
{ |
|
BaseClass::DetonateThink(); |
|
|
|
if ( m_flDeflectedTime <= gpGlobals->curtime && HasStickyEffects() ) |
|
{ |
|
ResetDeflected(); |
|
SetDeflectOwner( NULL ); |
|
} |
|
|
|
// If we received our crit via a medic, make sure they still exist. |
|
if ( m_CritMedics.Count() ) |
|
{ |
|
if ( TFGameRules() && ( TFGameRules()->InSetup() || TFGameRules()->State_Get() == GR_STATE_BETWEEN_RNDS ) ) |
|
{ |
|
bool bRemove = true; |
|
|
|
FOR_EACH_VEC( m_CritMedics, i ) |
|
{ |
|
if ( m_CritMedics[i] && m_CritMedics[i]->GetPlayerClass()->GetClassIndex() == TF_CLASS_MEDIC ) |
|
{ |
|
bRemove = false; |
|
break; |
|
} |
|
} |
|
|
|
// No medic(s) |
|
if ( bRemove ) |
|
{ |
|
Fizzle(); |
|
Detonate(); |
|
return; |
|
} |
|
} |
|
else |
|
{ |
|
// Clear the vector when the game starts |
|
m_CritMedics.RemoveAll(); |
|
} |
|
} |
|
} |
|
|
|
void CTFGrenadePipebombProjectile::PreArmThink( void ) |
|
{ |
|
SetContextThink( &CTFGrenadePipebombProjectile::ArmThink, gpGlobals->curtime + GetLiveTime(), "ARM_THINK" ); |
|
} |
|
|
|
void CTFGrenadePipebombProjectile::ArmThink( void ) |
|
{ |
|
// When between waves in MvM, players sometimes switch to medic just so demos can place crit stickies, |
|
// and then switch back. This code removes the sticky if the medic switches ( in DetonateThink() ) |
|
if ( IsCritical() && HasStickyEffects() && TFGameRules() && ( TFGameRules()->InSetup() || TFGameRules()->State_Get() == GR_STATE_BETWEEN_RNDS ) ) |
|
{ |
|
CTFPlayer *pOwner = ToTFPlayer( GetThrower() ); |
|
if ( pOwner && pOwner->m_Shared.InCond( TF_COND_CRITBOOSTED ) && !pOwner->m_Shared.InCond( TF_COND_CRITBOOSTED_USER_BUFF ) ) |
|
{ |
|
// Find the medic(s) |
|
for ( int i = 0; i < pOwner->m_Shared.GetNumHealers(); i++ ) |
|
{ |
|
CTFPlayer *pMedic = ToTFPlayer( pOwner->m_Shared.GetHealerByIndex( i ) ); |
|
if ( !pMedic ) |
|
continue; |
|
|
|
CWeaponMedigun *pMedigun = dynamic_cast <CWeaponMedigun*>( pMedic->GetActiveTFWeapon() ); |
|
if ( pMedigun && pMedigun->IsReleasingCharge() && pMedigun->GetChargeType() == MEDIGUN_CHARGE_CRITICALBOOST ) |
|
{ |
|
m_CritMedics.AddToTail( pMedic ); |
|
} |
|
} |
|
|
|
// We didn't find the medic. What provided TF_COND_CRITBOOSTED? |
|
Assert( m_CritMedics.Count() ); |
|
} |
|
} |
|
|
|
if ( m_bDetonateOnPulse ) |
|
{ |
|
Detonate(); |
|
} |
|
} |
|
|
|
#endif |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
float CTFGrenadePipebombProjectile::GetLiveTime( void ) |
|
{ |
|
float flLiveTime = tf_grenadelauncher_livetime.GetFloat(); |
|
|
|
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetLauncher(), flLiveTime, sticky_arm_time ); |
|
|
|
if ( TFGameRules() && TFGameRules()->IsPowerupMode() ) |
|
{ |
|
CTFPlayer *pOwner = ToTFPlayer( GetThrower() ); |
|
|
|
if ( pOwner ) |
|
{ |
|
if ( pOwner->m_Shared.GetCarryingRuneType() == RUNE_HASTE ) |
|
{ |
|
flLiveTime *= 0.5f; |
|
} |
|
else if ( pOwner->m_Shared.GetCarryingRuneType() == RUNE_KING || pOwner->m_Shared.InCond( TF_COND_KING_BUFFED ) ) |
|
{ |
|
flLiveTime *= 0.75f; |
|
} |
|
} |
|
} |
|
|
|
return flLiveTime; |
|
} |
|
|
|
#ifdef GAME_DLL |
|
//----------------------------------------------------------------------------- |
|
// Purpose: Grenade was deflected. |
|
//----------------------------------------------------------------------------- |
|
void CTFGrenadePipebombProjectile::Deflected( CBaseEntity *pDeflectedBy, Vector& vecDir ) |
|
{ |
|
CTFPlayer *pTFDeflector = ToTFPlayer( pDeflectedBy ); |
|
if ( !pTFDeflector ) |
|
return; |
|
|
|
CTFPlayer* pOldOwner = NULL; |
|
if ( HasStickyEffects() ) |
|
{ |
|
CTakeDamageInfo info; |
|
|
|
float flForceMultiplier = 1.0f; |
|
ITFChargeUpWeapon *pWeapon = dynamic_cast<ITFChargeUpWeapon*>( pTFDeflector->GetActiveWeapon() ); |
|
if ( pWeapon ) |
|
{ |
|
flForceMultiplier = RemapValClamped( ( gpGlobals->curtime - pWeapon->GetChargeBeginTime() ), |
|
0.0f, |
|
pWeapon->GetChargeMaxTime(), |
|
1.0f, |
|
2.0f ); |
|
} |
|
Vector vecForce = vecDir * flForceMultiplier * -CTFWeaponBase::DeflectionForce( WorldAlignSize(), 90, 12.0f ); |
|
|
|
pOldOwner = ToTFPlayer( GetThrower() ); |
|
info.SetAttacker( pDeflectedBy ); |
|
info.SetDamageForce( vecForce ); |
|
info.SetDamageType( DMG_SONIC ); |
|
info.SetWeapon( pTFDeflector->GetActiveTFWeapon() ); |
|
OnTakeDamage( info ); |
|
} |
|
else |
|
{ |
|
ChangeTeam( pTFDeflector->GetTeamNumber() ); |
|
SetLauncher( pTFDeflector->GetActiveWeapon() ); |
|
pOldOwner = ToTFPlayer( GetThrower() ); |
|
SetThrower( pTFDeflector ); |
|
|
|
if ( pTFDeflector->m_Shared.IsCritBoosted() ) |
|
{ |
|
SetCritical( true ); |
|
} |
|
} |
|
|
|
if ( pOldOwner ) |
|
{ |
|
pOldOwner->SpeakConceptIfAllowed( MP_CONCEPT_DEFLECTED, "projectile:1,victim:1" ); |
|
} |
|
|
|
CTFWeaponBase::SendObjectDeflectedEvent( pTFDeflector, pOldOwner, GetWeaponID(), this ); |
|
|
|
SetDeflectOwner( pTFDeflector ); |
|
IncrementDeflected(); |
|
} |
|
#endif |
|
|
|
|
|
#ifdef CLIENT_DLL |
|
//----------------------------------------------------------------------------- |
|
// Purpose: Highlight FX |
|
//----------------------------------------------------------------------------- |
|
class CProxyStickybombGlowColor : public CResultProxy |
|
{ |
|
public: |
|
void OnBind( void *pC_BaseEntity ) |
|
{ |
|
Assert( m_pResult ); |
|
|
|
if ( !pC_BaseEntity ) |
|
{ |
|
m_pResult->SetVecValue( 1, 1, 1 ); |
|
return; |
|
} |
|
|
|
CTFGrenadePipebombProjectile *pGrenade = NULL; |
|
C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity ); |
|
if ( !pEntity ) |
|
{ |
|
m_pResult->SetVecValue( 1, 1, 1 ); |
|
return; |
|
} |
|
|
|
// default to [1 1 1] |
|
Vector vResult = Vector( 1, 1, 1 ); |
|
|
|
pGrenade = dynamic_cast<CTFGrenadePipebombProjectile*>( pEntity ); |
|
if ( pGrenade ) |
|
{ |
|
if ( pGrenade->IsHighlighted() ) |
|
{ |
|
int iTeamNumber = pGrenade->GetTeamNumber(); |
|
if ( iTeamNumber == TF_TEAM_RED ) |
|
{ |
|
vResult = Vector ( 100.f, 0.f, 0.f ); |
|
if ( pGrenade->m_pGlowEffect ) |
|
{ |
|
pGrenade->m_pGlowEffect->SetColor( Vector( 250, 0, 0 ) ); |
|
} |
|
} |
|
else |
|
{ |
|
vResult = Vector ( 0.f, 0.f, 100.f ); |
|
if ( pGrenade->m_pGlowEffect ) |
|
{ |
|
pGrenade->m_pGlowEffect->SetColor( Vector( 0, 0, 250 ) ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
int iTeamNumber = pGrenade->GetTeamNumber(); |
|
if ( iTeamNumber == TF_TEAM_RED ) |
|
{ |
|
if ( pGrenade->m_pGlowEffect ) |
|
{ |
|
pGrenade->m_pGlowEffect->SetColor( Vector( 200, 100, 100 ) ); |
|
} |
|
} |
|
else |
|
{ |
|
if ( pGrenade->m_pGlowEffect ) |
|
{ |
|
pGrenade->m_pGlowEffect->SetColor( Vector( 100, 100, 200 ) ); |
|
} |
|
} |
|
} |
|
} |
|
m_pResult->SetVecValue( vResult.x, vResult.y, vResult.z ); |
|
} |
|
}; |
|
EXPOSE_INTERFACE( CProxyStickybombGlowColor, IMaterialProxy, "StickybombGlowColor" IMATERIAL_PROXY_INTERFACE_VERSION ); |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
#if GAME_DLL |
|
int CTFGrenadePipebombProjectile::GetDamageCustom() |
|
{ |
|
if ( m_iType == TF_GL_MODE_REMOTE_DETONATE ) |
|
{ |
|
if ( !m_bTouched ) |
|
{ |
|
return TF_DMG_CUSTOM_AIR_STICKY_BURST; |
|
} |
|
else if ( m_bDefensiveBomb ) |
|
{ |
|
return TF_DMG_CUSTOM_DEFENSIVE_STICKY; |
|
} |
|
else |
|
{ |
|
return TF_DMG_CUSTOM_STANDARD_STICKY; |
|
} |
|
} |
|
else if ( m_iType == TF_GL_MODE_REMOTE_DETONATE_PRACTICE ) |
|
{ |
|
return TF_DMG_CUSTOM_PRACTICE_STICKY; |
|
} |
|
|
|
return BaseClass::GetDamageCustom(); |
|
} |
|
|
|
|
|
float CTFGrenadePipebombProjectile::GetDamageScaleOnWorldContact() |
|
{ |
|
float flGrenadeDamageScaleOnWorldContact = 1.f; |
|
if ( GetLauncher() ) |
|
{ |
|
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetLauncher(),flGrenadeDamageScaleOnWorldContact, grenade_damage_reduction_on_world_contact ); |
|
} |
|
return flGrenadeDamageScaleOnWorldContact; |
|
} |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
float CTFGrenadePipebombProjectile::GetDamageRadius() |
|
{ |
|
float flRadiusMod = 1.0f; |
|
|
|
#ifdef GAME_DLL |
|
// winbomb prevention. |
|
// Air Det |
|
if ( m_iType == TF_GL_MODE_REMOTE_DETONATE ) |
|
{ |
|
if ( m_bTouched == false ) |
|
{ |
|
float flArmTime = tf_grenadelauncher_livetime.GetFloat(); |
|
flRadiusMod *= RemapValClamped( gpGlobals->curtime - m_flCreationTime, flArmTime, flArmTime + tf_sticky_radius_ramp_time.GetFloat(), tf_sticky_airdet_radius.GetFloat(), 1.0 ); |
|
} |
|
} |
|
#endif // GAME_DLL |
|
return BaseClass::GetDamageRadius() * flRadiusMod; |
|
}
|
|
|