diff options
Diffstat (limited to 'game/shared/tf/tf_weapon_grenade_pipebomb.cpp')
| -rw-r--r-- | game/shared/tf/tf_weapon_grenade_pipebomb.cpp | 1402 |
1 files changed, 1402 insertions, 0 deletions
diff --git a/game/shared/tf/tf_weapon_grenade_pipebomb.cpp b/game/shared/tf/tf_weapon_grenade_pipebomb.cpp new file mode 100644 index 0000000..a5ed406 --- /dev/null +++ b/game/shared/tf/tf_weapon_grenade_pipebomb.cpp @@ -0,0 +1,1402 @@ +//========= 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; +} |