//========= Copyright Valve Corporation, All rights reserved. ============// // // TF Flame Thrower // //============================================================================= #include "cbase.h" #include "tf_weapon_flamethrower.h" #include "tf_fx_shared.h" #include "in_buttons.h" #include "ammodef.h" #include "tf_gamerules.h" #if defined( CLIENT_DLL ) #include "c_tf_player.h" #include "vstdlib/random.h" #include "engine/IEngineSound.h" #include "soundenvelope.h" #include "prediction.h" #include "haptics/ihaptics.h" #include "c_tf_gamestats.h" #else #include "explode.h" #include "tf_player.h" #include "tf_gamestats.h" #include "ilagcompensationmanager.h" #include "collisionutils.h" #include "tf_team.h" #include "tf_obj.h" #include "tf_weapon_grenade_pipebomb.h" #include "particle_parse.h" #include "tf_weaponbase_grenadeproj.h" #include "tf_weapon_compound_bow.h" #include "tf_projectile_arrow.h" #include "tf_gamestats.h" #include "NextBot/NextBotManager.h" #include "halloween/merasmus/merasmus_trick_or_treat_prop.h" #include "tf_logic_robot_destruction.h" #ifdef STAGING_ONLY #include "tf_fx.h" #endif // STAGING_ONLY #include "tf_passtime_logic.h" ConVar tf_debug_flamethrower("tf_debug_flamethrower", "0", FCVAR_CHEAT , "Visualize the flamethrower damage." ); ConVar tf_flamethrower_velocity( "tf_flamethrower_velocity", "2300.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Initial velocity of flame damage entities." ); ConVar tf_flamethrower_drag("tf_flamethrower_drag", "0.87", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Air drag of flame damage entities." ); ConVar tf_flamethrower_float("tf_flamethrower_float", "50.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Upward float velocity of flame damage entities." ); ConVar tf_flamethrower_vecrand("tf_flamethrower_vecrand", "0.05", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Random vector added to initial velocity of flame damage entities." ); ConVar tf_flamethrower_boxsize("tf_flamethrower_boxsize", "12.0", FCVAR_CHEAT , "Size of flame damage entities." ); ConVar tf_flamethrower_maxdamagedist("tf_flamethrower_maxdamagedist", "350.0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Maximum damage distance for flamethrower." ); ConVar tf_flamethrower_shortrangedamagemultiplier("tf_flamethrower_shortrangedamagemultiplier", "1.2", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Damage multiplier for close-in flamethrower damage." ); ConVar tf_flamethrower_velocityfadestart("tf_flamethrower_velocityfadestart", ".3", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Time at which attacker's velocity contribution starts to fade." ); ConVar tf_flamethrower_velocityfadeend("tf_flamethrower_velocityfadeend", ".5", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Time at which attacker's velocity contribution finishes fading." ); ConVar tf_flamethrower_burst_zvelocity( "tf_flamethrower_burst_zvelocity", "350", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); static const char *s_pszFlameThrowerHitTargetThink = "FlameThrowerHitTargetThink"; extern ConVar tf_player_movement_stun_time; #endif #include "tf_pumpkin_bomb.h" ConVar tf_flamethrower_burstammo("tf_flamethrower_burstammo", "20", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "How much ammo does the air burst uses per shot." ); ConVar tf_flamethrower_flametime("tf_flamethrower_flametime", "0.5", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Time to live of flame damage entities." ); // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" // position of end of muzzle relative to shoot position #define TF_FLAMETHROWER_MUZZLEPOS_FORWARD 70.0f #define TF_FLAMETHROWER_MUZZLEPOS_RIGHT 12.0f #define TF_FLAMETHROWER_MUZZLEPOS_UP -12.0f #define TF_FLAMETHROWER_AMMO_PER_SECOND_PRIMARY_ATTACK 14.0f #define TF_FLAMETHROWER_HITACCURACY_MED 40.0f #define TF_FLAMETHROWER_HITACCURACY_HIGH 60.0f //----------------------------------------------------------------------------- #define TF_WEAPON_BUBBLE_WAND_MODEL "models/player/items/pyro/mtp_bubble_wand.mdl" //----------------------------------------------------------------------------- #ifndef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: Only send to local player //----------------------------------------------------------------------------- void* SendProxy_SendLocalFlameThrowerDataTable( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID ) { // Get the weapon entity CBaseCombatWeapon *pWeapon = (CBaseCombatWeapon*)pVarData; if ( pWeapon ) { // Only send this chunk of data to the player carrying this weapon CBasePlayer *pPlayer = ToBasePlayer( pWeapon->GetOwner() ); if ( pPlayer ) { pRecipients->SetOnly( pPlayer->GetClientIndex() ); return (void*)pVarData; } } return NULL; } REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_SendLocalFlameThrowerDataTable ); #endif // CLIENT_DLL IMPLEMENT_NETWORKCLASS_ALIASED( TFFlameThrower, DT_WeaponFlameThrower ) //----------------------------------------------------------------------------- // Purpose: Only sent to the local player //----------------------------------------------------------------------------- BEGIN_NETWORK_TABLE_NOBASE( CTFFlameThrower, DT_LocalFlameThrower ) #if defined( CLIENT_DLL ) RecvPropInt( RECVINFO( m_iActiveFlames ) ), RecvPropInt( RECVINFO( m_iDamagingFlames ) ), RecvPropBool( RECVINFO( m_bHasHalloweenSpell ) ), #else SendPropInt( SENDINFO( m_iActiveFlames ), 5, SPROP_UNSIGNED | SPROP_CHANGES_OFTEN ), SendPropInt( SENDINFO( m_iDamagingFlames ), 10, SPROP_UNSIGNED | SPROP_CHANGES_OFTEN ), SendPropBool( SENDINFO( m_bHasHalloweenSpell ) ), #endif END_NETWORK_TABLE() //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- BEGIN_NETWORK_TABLE( CTFFlameThrower, DT_WeaponFlameThrower ) #if defined( CLIENT_DLL ) RecvPropInt( RECVINFO( m_iWeaponState ) ), RecvPropBool( RECVINFO( m_bCritFire ) ), RecvPropBool( RECVINFO( m_bHitTarget ) ), RecvPropFloat( RECVINFO( m_flChargeBeginTime ) ), RecvPropDataTable("LocalFlameThrowerData", 0, 0, &REFERENCE_RECV_TABLE( DT_LocalFlameThrower ) ), #else SendPropInt( SENDINFO( m_iWeaponState ), 4, SPROP_UNSIGNED | SPROP_CHANGES_OFTEN ), SendPropBool( SENDINFO( m_bCritFire ) ), SendPropBool( SENDINFO( m_bHitTarget ) ), SendPropFloat( SENDINFO( m_flChargeBeginTime ) ), SendPropDataTable("LocalFlameThrowerData", 0, &REFERENCE_SEND_TABLE( DT_LocalFlameThrower ), SendProxy_SendLocalFlameThrowerDataTable ), #endif END_NETWORK_TABLE() #if defined( CLIENT_DLL ) BEGIN_PREDICTION_DATA( CTFFlameThrower ) DEFINE_PRED_FIELD( m_iWeaponState, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_bCritFire, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), DEFINE_FIELD( m_flChargeBeginTime, FIELD_FLOAT ), END_PREDICTION_DATA() #endif LINK_ENTITY_TO_CLASS( tf_weapon_flamethrower, CTFFlameThrower ); PRECACHE_WEAPON_REGISTER( tf_weapon_flamethrower ); BEGIN_DATADESC( CTFFlameThrower ) END_DATADESC() // ------------------------------------------------------------------------------------------------ // // CTFFlameThrower implementation. // ------------------------------------------------------------------------------------------------ // //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFFlameThrower::CTFFlameThrower() #if defined( CLIENT_DLL ) : m_FlameEffects( this ) , m_MmmmphEffect( this ) #endif { WeaponReset(); #if defined( CLIENT_DLL ) m_pFiringStartSound = NULL; m_pFiringLoop = NULL; m_pFiringAccuracyLoop = NULL; m_pFiringHitLoop = NULL; m_bFiringLoopCritical = false; m_pPilotLightSound = NULL; m_pSpinUpSound = NULL; m_szAccuracySound = NULL; m_bEffectsThinking = false; m_bFullRageEffect = false; #else m_flTimeToStopHitSound = 0; #endif m_bHasHalloweenSpell.Set( false ); ListenForGameEvent( "recalculate_holidays" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFFlameThrower::~CTFFlameThrower() { DestroySounds(); #if defined( CLIENT_DLL ) StopFullCritEffect(); #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::Precache( void ) { BaseClass::Precache(); int iModelIndex = PrecacheModel( TF_WEAPON_BUBBLE_WAND_MODEL ); PrecacheGibsForModel( iModelIndex ); PrecacheParticleSystem( "pyro_blast" ); PrecacheScriptSound( "Weapon_FlameThrower.AirBurstAttack" ); PrecacheScriptSound( "TFPlayer.AirBlastImpact" ); PrecacheScriptSound( "Weapon_FlameThrower.AirBurstAttackDeflect" ); PrecacheParticleSystem( "deflect_fx" ); PrecacheParticleSystem( "drg_bison_idle" ); PrecacheParticleSystem( "medicgun_invulnstatus_fullcharge_blue" ); PrecacheParticleSystem( "medicgun_invulnstatus_fullcharge_red" ); PrecacheParticleSystem( "halloween_burningplayer_flyingbits"); #ifdef STAGING_ONLY PrecacheScriptSound( "Equipment.RocketPack_Activate" ); PrecacheParticleSystem( "muzzle_bignasty" ); #endif // STAGING_ONLY } bool CTFFlameThrower::CanAirBlast() const { int iAirblastDisabled = 0; CALL_ATTRIB_HOOK_INT( iAirblastDisabled, airblast_disabled ); bool bAllowed = ( iAirblastDisabled == 0 ); #ifdef STAGING_ONLY int nRocketPack = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwner(), nRocketPack, rocket_pack ); if ( nRocketPack ) { bAllowed = false; } #endif // STAGING_ONLY return bAllowed; } void CTFFlameThrower::DestroySounds( void ) { #if defined( CLIENT_DLL ) CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); if ( m_pFiringStartSound ) { controller.SoundDestroy( m_pFiringStartSound ); m_pFiringStartSound = NULL; } if ( m_pFiringLoop ) { controller.SoundDestroy( m_pFiringLoop ); m_pFiringLoop = NULL; } if ( m_pPilotLightSound ) { controller.SoundDestroy( m_pPilotLightSound ); m_pPilotLightSound = NULL; } if ( m_pSpinUpSound ) { controller.SoundDestroy( m_pSpinUpSound ); m_pSpinUpSound = NULL; } if ( m_pFiringAccuracyLoop ) { controller.SoundDestroy( m_pFiringAccuracyLoop ); m_pFiringAccuracyLoop = NULL; } StopHitSound(); #endif } void CTFFlameThrower::WeaponReset( void ) { BaseClass::WeaponReset(); SetWeaponState( FT_STATE_IDLE ); m_bCritFire = false; m_bHitTarget = false; m_flStartFiringTime = 0; m_flAmmoUseRemainder = 0; m_flChargeBeginTime = 0; m_flSpinupBeginTime = 0; ResetFlameHitCount(); DestroySounds(); #if defined( CLIENT_DLL ) StopFullCritEffect(); #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::Spawn( void ) { m_iAltFireHint = HINT_ALTFIRE_FLAMETHROWER; BaseClass::Spawn(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFFlameThrower::Holster( CBaseCombatWeapon *pSwitchingTo ) { SetWeaponState( FT_STATE_IDLE ); m_bCritFire = false; m_bHitTarget = false; m_flChargeBeginTime = 0; #if defined ( CLIENT_DLL ) StopFlame(); StopPilotLight(); StopFullCritEffect(); m_bEffectsThinking = false; #endif return BaseClass::Holster( pSwitchingTo ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::ItemPostFrame() { if ( m_bLowered ) return; // Get the player owning the weapon. CTFPlayer *pOwner = GetTFPlayerOwner(); if ( !pOwner ) return; #ifdef CLIENT_DLL if ( !m_bEffectsThinking ) { m_bEffectsThinking = true; SetContextThink( &CTFFlameThrower::ClientEffectsThink, gpGlobals->curtime, "EFFECTS_THINK" ); } #endif int iAmmo = pOwner->GetAmmoCount( m_iPrimaryAmmoType ); m_bFiredSecondary = false; if ( pOwner->IsAlive() && ( pOwner->m_nButtons & IN_ATTACK2 ) ) { SecondaryAttack(); } // Fixes an exploit where the airblast effect repeats while +attack is active if ( m_bFiredBothAttacks ) { if ( pOwner->m_nButtons & IN_ATTACK && !( pOwner->m_nButtons & IN_ATTACK2 ) ) { pOwner->m_nButtons &= ~IN_ATTACK; } m_bFiredBothAttacks = false; } if ( pOwner->m_nButtons & IN_ATTACK && pOwner->m_nButtons & IN_ATTACK2 ) { m_bFiredBothAttacks = true; } if ( !m_bFiredSecondary ) { bool bSpinDown = m_flSpinupBeginTime > 0.0f; if ( pOwner->IsAlive() && ( pOwner->m_nButtons & IN_ATTACK ) && iAmmo > 0 ) { PrimaryAttack(); bSpinDown = false; } else if ( m_iWeaponState > FT_STATE_IDLE ) { SendWeaponAnim( ACT_MP_ATTACK_STAND_POSTFIRE ); pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_POST ); SetWeaponState( FT_STATE_IDLE ); m_bCritFire = false; m_bHitTarget = false; } if ( bSpinDown ) { m_flSpinupBeginTime = 0.0f; #if defined( CLIENT_DLL ) if ( m_pSpinUpSound ) { float flSpinUpTime = GetSpinUpTime(); CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); controller.SoundChangePitch( m_pSpinUpSound, 40, flSpinUpTime * 0.5f ); controller.SoundChangeVolume( m_pSpinUpSound, 0.0f, flSpinUpTime * 2.0f ); } #endif } } if (!((pOwner->m_nButtons & IN_ATTACK) || (pOwner->m_nButtons & IN_RELOAD)) || (!(pOwner->m_nButtons & IN_ATTACK2) || !m_bFiredSecondary)) { // no fire buttons down or reloading if ( !ReloadOrSwitchWeapons() && ( m_bInReload == false ) ) { WeaponIdle(); } } // charged airblast int iChargedAirblast = 0; CALL_ATTRIB_HOOK_INT( iChargedAirblast, set_charged_airblast ); if ( iChargedAirblast != 0 ) { if ( m_flChargeBeginTime > 0 ) { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return; // If we're not holding down the attack button, launch the flame rocket if ( !(pPlayer->m_nButtons & IN_ATTACK2) ) { //FireProjectile( pOwner ); float flMultAmmoPerShot = 1.0f; CALL_ATTRIB_HOOK_FLOAT( flMultAmmoPerShot, mult_airblast_cost ); int iAmmoPerShot = tf_flamethrower_burstammo.GetInt() * flMultAmmoPerShot; FireAirBlast( iAmmoPerShot ); } } } } class CTraceFilterIgnoreObjects : public CTraceFilterSimple { public: // It does have a base, but we'll never network anything below here.. DECLARE_CLASS( CTraceFilterIgnoreObjects, CTraceFilterSimple ); CTraceFilterIgnoreObjects( const IHandleEntity *passentity, int collisionGroup ) : CTraceFilterSimple( passentity, collisionGroup ) { } virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask ) { CBaseEntity *pEntity = EntityFromEntityHandle( pServerEntity ); if ( pEntity && pEntity->IsBaseObject() ) return false; return BaseClass::ShouldHitEntity( pServerEntity, contentsMask ); } }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::PrimaryAttack() { float flSpinUpTime = GetSpinUpTime(); if ( flSpinUpTime > 0.0f ) { if ( m_flSpinupBeginTime > 0.0f ) { if ( gpGlobals->curtime - m_flSpinupBeginTime < flSpinUpTime ) { return; } } else { m_flSpinupBeginTime = gpGlobals->curtime; #if defined( CLIENT_DLL ) CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); if ( !m_pSpinUpSound ) { // Create the looping pilot light sound const char *pchSpinUpSound = GetShootSound( RELOAD ); CLocalPlayerFilter filter; m_pSpinUpSound = controller.SoundCreate( filter, entindex(), pchSpinUpSound ); controller.Play( m_pSpinUpSound, 0.0f, 40 ); } if ( m_pSpinUpSound ) { controller.SoundChangePitch( m_pSpinUpSound, 100, flSpinUpTime ); controller.SoundChangeVolume( m_pSpinUpSound, 1.0f, flSpinUpTime * 0.1f ); } #endif return; } } // Are we capable of firing again? if ( m_flNextPrimaryAttack > gpGlobals->curtime ) return; // Get the player owning the weapon. CTFPlayer *pOwner = GetTFPlayerOwner(); if ( !pOwner ) return; if ( !CanAttack() ) { #if defined ( CLIENT_DLL ) StopFlame(); #endif SetWeaponState( FT_STATE_IDLE ); return; } m_iWeaponMode = TF_WEAPON_PRIMARY_MODE; CalcIsAttackCritical(); // Because the muzzle is so long, it can stick through a wall if the player is right up against it. // Make sure the weapon can't fire in this condition by tracing a line between the eye point and the end of the muzzle. trace_t trace; Vector vecEye = pOwner->EyePosition(); Vector vecMuzzlePos = GetVisualMuzzlePos(); CTraceFilterIgnoreObjects traceFilter( this, COLLISION_GROUP_NONE ); UTIL_TraceLine( vecEye, vecMuzzlePos, MASK_SOLID, &traceFilter, &trace ); if ( trace.fraction < 1.0 && ( !trace.m_pEnt || trace.m_pEnt->m_takedamage == DAMAGE_NO ) ) { // there is something between the eye and the end of the muzzle, most likely a wall, don't fire, and stop firing if we already are if ( m_iWeaponState > FT_STATE_IDLE ) { #if defined ( CLIENT_DLL ) StopFlame(); #endif SetWeaponState( FT_STATE_IDLE ); } return; } switch ( m_iWeaponState ) { case FT_STATE_IDLE: { // Just started, play PRE and start looping view model anim pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRE ); SendWeaponAnim( ACT_VM_PRIMARYATTACK ); m_flStartFiringTime = gpGlobals->curtime + 0.16; // 5 frames at 30 fps SetWeaponState( FT_STATE_STARTFIRING ); } break; case FT_STATE_STARTFIRING: { // if some time has elapsed, start playing the looping third person anim if ( gpGlobals->curtime > m_flStartFiringTime ) { SetWeaponState( FT_STATE_FIRING ); m_flNextPrimaryAttackAnim = gpGlobals->curtime; } } break; case FT_STATE_FIRING: { if ( gpGlobals->curtime >= m_flNextPrimaryAttackAnim ) { pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY ); m_flNextPrimaryAttackAnim = gpGlobals->curtime + 1.4; // fewer than 45 frames! } } break; default: break; } #ifdef CLIENT_DLL // Restart our particle effect if we've transitioned across water boundaries if ( m_iParticleWaterLevel != -1 && pOwner->GetWaterLevel() != m_iParticleWaterLevel ) { if ( m_iParticleWaterLevel == WL_Eyes || pOwner->GetWaterLevel() == WL_Eyes ) { RestartParticleEffect(); } } #endif #if !defined (CLIENT_DLL) // Let the player remember the usercmd he fired a weapon on. Assists in making decisions about lag compensation. pOwner->NoteWeaponFired(); pOwner->SpeakWeaponFire(); CTF_GameStats.Event_PlayerFiredWeapon( pOwner, m_bCritFire ); // Move other players back to history positions based on local player's lag lagcompensation->StartLagCompensation( pOwner, pOwner->GetCurrentCommand() ); // PASSTIME custom lag compensation for the ball; see also tf_fx_shared.cpp // it would be better if all entities could opt-in to this, or a way for lagcompensation to handle non-players automatically if ( g_pPasstimeLogic && g_pPasstimeLogic->GetBall() ) { g_pPasstimeLogic->GetBall()->StartLagCompensation( pOwner, pOwner->GetCurrentCommand() ); } #endif #ifdef CLIENT_DLL C_CTF_GameStats.Event_PlayerFiredWeapon( pOwner, IsCurrentAttackACrit() ); #endif float flFiringInterval = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay; #ifdef STAGING_ONLY if ( ShootsNapalm() ) { flFiringInterval *= 4.f; } #endif // STAGING_ONLY // Don't attack if we're underwater if ( pOwner->GetWaterLevel() != WL_Eyes ) { // Find eligible entities in a cone in front of us. // Vector vOrigin = pOwner->Weapon_ShootPosition(); Vector vForward, vRight, vUp; QAngle vAngles = pOwner->EyeAngles() + pOwner->GetPunchAngle(); AngleVectors( vAngles, &vForward, &vRight, &vUp ); #define NUM_TEST_VECTORS 30 #ifdef CLIENT_DLL bool bWasCritical = m_bCritFire; #endif // Burn & Ignite 'em int iDmgType = g_aWeaponDamageTypes[ GetWeaponID() ]; m_bCritFire = IsCurrentAttackACrit(); if ( m_bCritFire ) { iDmgType |= DMG_CRITICAL; } #ifdef CLIENT_DLL if ( bWasCritical != m_bCritFire ) { RestartParticleEffect(); } #endif #ifdef GAME_DLL // create the flame entity int iDamagePerSec = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_nDamage; float flDamage = (float)iDamagePerSec * flFiringInterval; CALL_ATTRIB_HOOK_FLOAT( flDamage, mult_dmg ); int iCritFromBehind = 0; CALL_ATTRIB_HOOK_INT( iCritFromBehind, set_flamethrower_back_crit ); #ifdef STAGING_ONLY if ( ShootsNapalm() ) { CTFProjectile_Napalm::Create( pOwner, this ); } else #endif // STAGING_ONLY { CTFFlameEntity::Create( GetFlameOriginPos(), pOwner->EyeAngles(), this, tf_flamethrower_velocity.GetFloat(), iDmgType, flDamage, iCritFromBehind == 1 ); } // Pyros can become invis in some game modes. Hitting fire normally handles this, // but in the case of flamethrowers it's likely that stealth will be applied while // the fire button is down, so we have to call into RemoveInvisibility here, too. if ( pOwner->m_Shared.IsStealthed() ) { pOwner->RemoveInvisibility(); } #endif } #ifdef GAME_DLL // Figure how much ammo we're using per shot and add it to our remainder to subtract. (We may be using less than 1.0 ammo units // per frame, depending on how constants are tuned, so keep an accumulator so we can expend fractional amounts of ammo per shot.) // Note we do this only on server and network it to client. If we predict it on client, it can get slightly out of sync w/server // and cause ammo pickup indicators to appear float flAmmoPerSecond = TF_FLAMETHROWER_AMMO_PER_SECOND_PRIMARY_ATTACK; CALL_ATTRIB_HOOK_FLOAT( flAmmoPerSecond, mult_flame_ammopersec ); m_flAmmoUseRemainder += flAmmoPerSecond * flFiringInterval; // take the integer portion of the ammo use accumulator and subtract it from player's ammo count; any fractional amount of ammo use // remains and will get used in the next shot int iAmmoToSubtract = (int) m_flAmmoUseRemainder; if ( iAmmoToSubtract > 0 ) { pOwner->RemoveAmmo( iAmmoToSubtract, m_iPrimaryAmmoType ); m_flAmmoUseRemainder -= iAmmoToSubtract; // round to 2 digits of precision m_flAmmoUseRemainder = (float) ( (int) (m_flAmmoUseRemainder * 100) ) / 100.0f; } #endif m_flNextPrimaryAttack = gpGlobals->curtime + flFiringInterval; m_flTimeWeaponIdle = gpGlobals->curtime + flFiringInterval; #if !defined (CLIENT_DLL) lagcompensation->FinishLagCompensation( pOwner ); // PASSTIME custom lag compensation for the ball; see also tf_fx_shared.cpp // it would be better if all entities could opt-in to this, or a way for lagcompensation to handle non-players automatically if ( g_pPasstimeLogic && g_pPasstimeLogic->GetBall() ) { g_pPasstimeLogic->GetBall()->FinishLagCompensation( pOwner ); } #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float AirBurstDamageForce( const Vector &size, float damage, float scale ) { float force = damage * ((48 * 48 * 82.0) / (size.x * size.y * size.z)) * scale; if ( force > 1000.0) { force = 1000.0; } return force; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFFlameThrower::SupportsAirBlastFunction( EFlameThrowerAirblastFunction eFunction ) const { int iSupportedAirBlastFunctions = 0; CALL_ATTRIB_HOOK_INT( iSupportedAirBlastFunctions, airblast_functionality_flags ); // If we don't have this attribute specified, or it is set to the value 0, we interpret // that as "I can do everything!". if ( iSupportedAirBlastFunctions == 0 ) { // They can do everything unless airblast is disabled, in which case they can do nothing return CanAirBlast(); } return (iSupportedAirBlastFunctions & eFunction) != 0; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::FireAirBlast( int iAmmoPerShot ) { CTFPlayer *pOwner = GetTFPlayerOwner(); if ( !pOwner ) return; m_bFiredSecondary = true; #ifdef CLIENT_DLL // Stop the flame if we're currently firing StopFlame( false ); #endif SetWeaponState( FT_STATE_SECONDARY ); #ifdef GAME_DLL SendWeaponAnim( ACT_VM_SECONDARYATTACK ); pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_SECONDARY ); int nDash = 0; CALL_ATTRIB_HOOK_INT( nDash, airblast_dashes ); if ( !nDash ) { DeflectProjectiles(); } else { #ifdef STAGING_ONLY Vector vDashDir; AngleVectors( pOwner->EyeAngles() + QAngle( 0.0f, 180.0f, 0.0f ), &vDashDir ); #else Vector vDashDir = pOwner->GetAbsVelocity(); if ( !pOwner->GetGroundEntity() || vDashDir.Length() == 0.0f ) { AngleVectors( pOwner->EyeAngles(), &vDashDir ); } #endif vDashDir.z = 0.0f; VectorNormalize( vDashDir ); Vector vCenter = pOwner->WorldSpaceCenter(); Vector vSize = GetDeflectionSize(); DeflectPlayer( pOwner, pOwner, vDashDir, vCenter, vSize ); } // for charged airblast int iChargedAirblast = 0; CALL_ATTRIB_HOOK_INT( iChargedAirblast, set_charged_airblast ); if ( iChargedAirblast != 0 ) { m_flChargeBeginTime = 0; } // compression blast doesn't go through the normal "weapon fired" code path TheNextBots().OnWeaponFired( pOwner, this ); #endif #ifdef CLIENT_DLL if ( prediction->IsFirstTimePredicted() == true ) { StartFlame(); } #endif float fAirblastRefireTimeScale = 1.0f; CALL_ATTRIB_HOOK_FLOAT( fAirblastRefireTimeScale, mult_airblast_refire_time ); if ( fAirblastRefireTimeScale <= 0.0f ) { fAirblastRefireTimeScale = 1.0f; } float fAirblastPrimaryRefireTimeScale = 1.0f; CALL_ATTRIB_HOOK_FLOAT( fAirblastPrimaryRefireTimeScale, mult_airblast_primary_refire_time ); if ( fAirblastPrimaryRefireTimeScale <= 0.0f ) { fAirblastPrimaryRefireTimeScale = 1.0f; } // Haste Powerup Rune adds multiplier to fire delay time if ( pOwner->m_Shared.GetCarryingRuneType() == RUNE_HASTE ) { fAirblastRefireTimeScale *= 0.5f; } m_flNextSecondaryAttack = gpGlobals->curtime + (0.75f * fAirblastRefireTimeScale); m_flNextPrimaryAttack = gpGlobals->curtime + (1.0f * fAirblastRefireTimeScale * fAirblastPrimaryRefireTimeScale); m_flResetBurstEffect = gpGlobals->curtime + 0.05f; pOwner->RemoveAmmo( iAmmoPerShot, m_iPrimaryAmmoType ); } float CTFFlameThrower::GetSpinUpTime( void ) const { float flSpinUpTime = 0.0f; CALL_ATTRIB_HOOK_FLOAT( flSpinUpTime, mod_flamethrower_spinup_time ); return flSpinUpTime; } void CTFFlameThrower::SetWeaponState( int nWeaponState ) { if ( m_iWeaponState == nWeaponState ) return; CTFPlayer *pOwner = GetTFPlayerOwner(); switch ( nWeaponState ) { case FT_STATE_IDLE: if ( pOwner ) { float flFiringForwardPull = 0.0f; CALL_ATTRIB_HOOK_FLOAT( flFiringForwardPull, firing_forward_pull ); if ( flFiringForwardPull ) { pOwner->m_Shared.RemoveCond( TF_COND_SPEED_BOOST ); } } break; case FT_STATE_STARTFIRING: if ( pOwner ) { float flFiringForwardPull = 0.0f; CALL_ATTRIB_HOOK_FLOAT( flFiringForwardPull, firing_forward_pull ); if ( flFiringForwardPull ) { pOwner->m_Shared.AddCond( TF_COND_SPEED_BOOST ); } } break; } m_iWeaponState = nWeaponState; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::UseRage( void ) { if ( !IsRageFull() ) return; CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return; if ( !pPlayer->IsAllowedToTaunt() ) return; float flNextAttack = m_flNextSecondaryAttack; #if GAME_DLL // Do a taunt so everyone has a chance to run pPlayer->Taunt( TAUNT_BASE_WEAPON ); if ( pPlayer->m_Shared.IsRageDraining() ) { // taunt succeeded flNextAttack = gpGlobals->curtime + 1.0f; } #else flNextAttack = gpGlobals->curtime + 1.0f; #endif m_flNextSecondaryAttack = flNextAttack; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::SecondaryAttack() { if ( m_flChargeBeginTime > 0 ) { m_bFiredSecondary = true; return; } if ( m_flNextSecondaryAttack > gpGlobals->curtime ) { #ifndef CLIENT_DLL if ( m_flResetBurstEffect <= gpGlobals->curtime ) { SetWeaponState( FT_STATE_IDLE ); } #endif return; } CTFPlayer *pOwner = GetTFPlayerOwner(); if ( !pOwner ) return; if ( pOwner->GetWaterLevel() == WL_Eyes ) return; if ( !CanAttack() ) { SetWeaponState( FT_STATE_IDLE ); return; } int iAmmo = pOwner->GetAmmoCount( m_iPrimaryAmmoType ); // charged airblast int iChargedAirblast = 0; CALL_ATTRIB_HOOK_INT( iChargedAirblast, set_charged_airblast ); int iBuffType = 0; CALL_ATTRIB_HOOK_INT( iBuffType, set_buff_type ); float flMultAmmoPerShot = 1.0f; CALL_ATTRIB_HOOK_FLOAT( flMultAmmoPerShot, mult_airblast_cost ); int iAmmoPerShot = tf_flamethrower_burstammo.GetInt() * flMultAmmoPerShot; if ( iBuffType != 0 ) { UseRage(); return; } if ( iAmmo < iAmmoPerShot ) return; // normal air blast? if ( iChargedAirblast == 0 && CanAirBlast() ) { FireAirBlast( iAmmoPerShot ); return; } #ifdef CLIENT_DLL // Stop the flame if we're currently firing StopFlame( false ); #endif SetWeaponState( FT_STATE_SECONDARY ); #ifdef STAGING_ONLY if ( RocketPackCanActivate( iAmmoPerShot ) ) { RocketPackLaunch( iAmmoPerShot ); return; } #endif // STAGING_ONLY #ifdef GAME_DLL m_iWeaponMode = TF_WEAPON_SECONDARY_MODE; m_flChargeBeginTime = gpGlobals->curtime; SendWeaponAnim( ACT_VM_PULLBACK ); // @todo replace with the correct one WeaponSound( SINGLE ); #endif } #ifdef GAME_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- Vector CTFFlameThrower::GetDeflectionSize() { const Vector vecBaseDeflectionSize = BaseClass::GetDeflectionSize(); float fMultiplier = 1.0f; // int iChargedAirblast = 0; // CALL_ATTRIB_HOOK_INT( iChargedAirblast, set_charged_airblast ); // if ( iChargedAirblast != 0 ) // { // fMultiplier *= RemapValClamped( ( gpGlobals->curtime - m_flChargeBeginTime ), // 0.0f, // GetChargeMaxTime(), // AIRBLAST_CHARGE_MULT_MIN, // AIRBLAST_CHARGE_MULT_MAX ); // } // Allow custom attributes to scale the deflection size. CALL_ATTRIB_HOOK_FLOAT( fMultiplier, deflection_size_multiplier ); return vecBaseDeflectionSize * fMultiplier; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- #ifdef _DEBUG ConVar tf_pushbackscalescale( "tf_pushbackscalescale", "1.0" ); ConVar tf_pushbackscalescale_vertical( "tf_pushbackscalescale_vertical", "1.0" ); #endif void ExtinguishPlayer( CEconEntity *pExtinguisher, CTFPlayer *pOwner, CTFPlayer *pTarget, const char *pExtinguisherName ) { pTarget->EmitSound( "TFPlayer.FlameOut" ); pTarget->m_Shared.RemoveCond( TF_COND_BURNING ); // we're going to limit the number of times you can be awarded bonus points to prevent exploits if ( pOwner->ShouldGetBonusPointsForExtinguishEvent( pTarget->GetUserID() ) ) { CTF_GameStats.Event_PlayerAwardBonusPoints( pOwner, pTarget, 10 ); } CRecipientFilter involved_filter; involved_filter.AddRecipient( pOwner ); involved_filter.AddRecipient( pTarget ); UserMessageBegin( involved_filter, "PlayerExtinguished" ); WRITE_BYTE( pOwner->entindex() ); WRITE_BYTE( pTarget->entindex() ); MessageEnd(); IGameEvent *event = gameeventmanager->CreateEvent( "player_extinguished" ); if ( event ) { event->SetInt( "victim", pTarget->entindex() ); event->SetInt( "healer", pOwner->entindex() ); gameeventmanager->FireEvent( event, true ); } // stats EconEntity_OnOwnerKillEaterEvent( pExtinguisher, pOwner, pTarget, kKillEaterEvent_BurningAllyExtinguished ); UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"player_extinguished\" against \"%s<%i><%s><%s>\" with \"%s\" (attacker_position \"%d %d %d\") (victim_position \"%d %d %d\")\n", pOwner->GetPlayerName(), pOwner->GetUserID(), pOwner->GetNetworkIDString(), pOwner->GetTeam()->GetName(), pTarget->GetPlayerName(), pTarget->GetUserID(), pTarget->GetNetworkIDString(), pTarget->GetTeam()->GetName(), pExtinguisherName, (int)pOwner->GetAbsOrigin().x, (int)pOwner->GetAbsOrigin().y, (int)pOwner->GetAbsOrigin().z, (int)pTarget->GetAbsOrigin().x, (int)pTarget->GetAbsOrigin().y, (int)pTarget->GetAbsOrigin().z ); } bool CTFFlameThrower::DeflectPlayer( CTFPlayer *pTarget, CTFPlayer *pOwner, Vector &vecForward, Vector &vecCenter, Vector &vecSize ) { if ( pTarget->GetTeamNumber() == pOwner->GetTeamNumber() && pTarget != pOwner ) { if ( pTarget->m_Shared.InCond( TF_COND_BURNING ) && SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_PUT_OUT_TEAMMATES ) ) { ExtinguishPlayer( this, pOwner, pTarget, "tf_weapon_flamethrower" ); // Return health to the Pyro. // We may want to cap the amount of health per extinguish but for now lets test this int iRestoreHealthOnExtinguish = 0; CALL_ATTRIB_HOOK_INT( iRestoreHealthOnExtinguish, extinguish_restores_health ); if ( iRestoreHealthOnExtinguish > 0 ) { pOwner->TakeHealth( iRestoreHealthOnExtinguish, DMG_GENERIC ); IGameEvent *healevent = gameeventmanager->CreateEvent( "player_healonhit" ); if ( healevent ) { healevent->SetInt( "amount", iRestoreHealthOnExtinguish ); healevent->SetInt( "entindex", pOwner->entindex() ); item_definition_index_t healingItemDef = INVALID_ITEM_DEF_INDEX; if ( GetAttributeContainer() && GetAttributeContainer()->GetItem() ) { healingItemDef = GetAttributeContainer()->GetItem()->GetItemDefIndex(); } healevent->SetInt( "weapon_def_index", healingItemDef ); gameeventmanager->FireEvent( healevent ); } } } return false; } if ( SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_PUSHBACK ) ) { int iReverseBlast = 0; CALL_ATTRIB_HOOK_INT( iReverseBlast, reverse_airblast ); // Against players, let's force the pyro to be actually looking at them. // We'll be a bit more laxed when it comes to aiming at rockets and grenades. Vector vecToTarget; if ( pTarget == pOwner ) { vecToTarget = vecForward; } else { vecToTarget = pTarget->WorldSpaceCenter() - pOwner->WorldSpaceCenter(); VectorNormalize( vecToTarget ); } // Quick Fix Uber is immune if ( pTarget->m_Shared.InCond( TF_COND_MEGAHEAL )) return false; // Require our target be in a cone in front of us. Default threshold is the dot-product needs to be at least 0.8 = 1 - 0.2. float flDot = DotProduct( vecForward, vecToTarget ); float flAirblastConeScale = 0.2f; CALL_ATTRIB_HOOK_FLOAT( flAirblastConeScale, mult_airblast_cone_scale ); float flAirblastConeThreshold = Clamp(1.0f - flAirblastConeScale, 0.0f, 1.0f); if (flDot < flAirblastConeThreshold) { return false; } if ( pTarget != pOwner ) { pTarget->SetAbsVelocity( vec3_origin ); if ( SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_PUSHBACK__STUN ) ) { if ( !pTarget->m_Shared.InCond( TF_COND_KNOCKED_INTO_AIR ) ) { pTarget->m_Shared.StunPlayer( tf_player_movement_stun_time.GetFloat(), 1.f, TF_STUN_MOVEMENT, pOwner ); } } if ( SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_PUSHBACK__VIEW_PUNCH ) ) { pTarget->ApplyPunchImpulseX( RandomInt( 10, 15 ) ); } } pTarget->SpeakConceptIfAllowed( MP_CONCEPT_DEFLECTED, "projectile:0,victim:1" ); float flForce = AirBurstDamageForce( pTarget->WorldAlignSize(), 60, 6.f ); CALL_ATTRIB_HOOK_FLOAT( flForce, airblast_pushback_scale ); #ifdef _DEBUG Vector vecForce = vecToTarget * flForce * tf_pushbackscalescale.GetFloat(); #else Vector vecForce = vecToTarget * flForce; #endif if ( iReverseBlast ) { vecForce = -vecForce; } float flVerticalPushbackScale = tf_flamethrower_burst_zvelocity.GetFloat(); if ( iReverseBlast ) { // Don't give quite so big a vertical kick if we're sucking rather than blowing... flVerticalPushbackScale *= 0.75f; } #ifdef STAGING_ONLY if ( !( pTarget == pOwner && pOwner->GetGroundEntity() ) ) #endif { CALL_ATTRIB_HOOK_FLOAT( flVerticalPushbackScale, airblast_vertical_pushback_scale ); } #ifdef _DEBUG vecForce.z += flVerticalPushbackScale * tf_pushbackscalescale_vertical.GetFloat(); /* // Kyle says: this will force players off the ground for at least one frame. // This is disabled on purpose right now to match previous flamethrower functionality. if ( pTarget->GetFlags() & FL_ONGROUND ) { vecForce.z += 268.3281572999747f; } */ #else vecForce.z += flVerticalPushbackScale; #endif // Apply AirBlastImpulse pTarget->ApplyAirBlastImpulse( vecForce ); // Make sure we get credit for the airblast if the target falls to its death pTarget->m_AchievementData.AddDamagerToHistory( pOwner ); SendObjectDeflectedEvent( pOwner, pTarget, TF_WEAPON_NONE, pTarget ); // TF_WEAPON_NONE means the player got pushed // If the target is charging, stop the charge and keep the charge meter where it is. pTarget->m_Shared.InterruptCharge(); // Track for achievements pTarget->m_AchievementData.AddPusherToHistory( pOwner ); // Give bonus points whenever a pyro pushes high-value targets back if ( TFGameRules() && ( pTarget->IsMiniBoss() || pTarget->m_Shared.IsInvulnerable() ) ) { int nAmount = pTarget->IsMiniBoss() ? 10 : 5; CTF_GameStats.Event_PlayerAwardBonusPoints( pOwner, pTarget, nAmount ); } return true; } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::PlayDeflectionSound( bool bPlayer ) { if ( bPlayer ) { EmitSound( "TFPlayer.AirBlastImpact" ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFFlameThrower::DeflectEntity( CBaseEntity *pTarget, CTFPlayer *pOwner, Vector &vecForward, Vector &vecCenter, Vector &vecSize ) { Assert( pTarget ); Assert( pOwner ); if ( !SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_REFLECT_PROJECTILES ) ) return false; // can't deflect things on our own team // except the passtime ball when in passtime mode if ( (pTarget->GetTeamNumber() == pOwner->GetTeamNumber()) && !(g_pPasstimeLogic && (g_pPasstimeLogic->GetBall() == pTarget)) ) { return false; } // Grab the owner of the projectile *before* we reflect it. CTFPlayer *pTFPlayerVictim = dynamic_cast( pTarget ); if ( !pTFPlayerVictim ) { pTFPlayerVictim = dynamic_cast( pTarget->GetOwnerEntity() ); } if ( !pTFPlayerVictim ) { // We can't use OwnerEntity for grenades, because then the owner can't shoot them with his hitscan weapons (due to collide rules) // Thrower is used to store the person who threw the grenade, for damage purposes. CBaseGrenade *pBaseGrenade = dynamic_cast< CBaseGrenade*>( pTarget ); if ( pBaseGrenade ) { pTFPlayerVictim = dynamic_cast( pBaseGrenade->GetThrower() ); } } if ( !pTFPlayerVictim ) { // Is the OwnerEntity() a base object, like a sentry gun shooting rockets at us? if ( pTarget->GetOwnerEntity() && pTarget->GetOwnerEntity()->IsBaseObject() ) { CBaseObject *pObj = dynamic_cast( pTarget->GetOwnerEntity() ); if ( pObj ) { pTFPlayerVictim = dynamic_cast( pObj->GetOwner() ); } } } bool bDeflected = BaseClass::DeflectEntity( pTarget, pOwner, vecForward, vecCenter, vecSize ); if ( bDeflected ) { pTarget->EmitSound( "Weapon_FlameThrower.AirBurstAttackDeflect" ); EconEntity_OnOwnerKillEaterEvent( this, pOwner, pTFPlayerVictim, kKillEaterEvent_ProjectileReflect ); } return bDeflected; } #endif //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFFlameThrower::Lower( void ) { if ( BaseClass::Lower() ) { // If we were firing, stop if ( m_iWeaponState > FT_STATE_IDLE ) { SendWeaponAnim( ACT_MP_ATTACK_STAND_POSTFIRE ); SetWeaponState( FT_STATE_IDLE ); } return true; } return false; } //----------------------------------------------------------------------------- // Purpose: Returns the position of the tip of the muzzle at it appears visually //----------------------------------------------------------------------------- Vector CTFFlameThrower::GetVisualMuzzlePos() { return GetMuzzlePosHelper( true ); } //----------------------------------------------------------------------------- // Purpose: Returns the position at which to spawn flame damage entities //----------------------------------------------------------------------------- Vector CTFFlameThrower::GetFlameOriginPos() { return GetMuzzlePosHelper( false ); } #ifdef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFFlameThrower::GetFlameHitRatio( void ) { // Safety net to avoid divide by zero if ( m_iActiveFlames == 0 ) return 0.1f; float flRatio = ( ( (float)m_iDamagingFlames ) / ( (float)m_iActiveFlames ) ); //Msg( "Act: %d Dmg: %d\n", m_iActiveFlames, m_iDamagingFlames ); return flRatio; } #endif //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::IncrementFlameDamageCount( void ) { m_iDamagingFlames++; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::DecrementFlameDamageCount( void ) { if ( m_iDamagingFlames <= 0 ) return; m_iDamagingFlames--; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::IncrementActiveFlameCount( void ) { m_iActiveFlames++; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::DecrementActiveFlameCount( void ) { if ( m_iActiveFlames <= 0 ) return; m_iActiveFlames--; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::ResetFlameHitCount( void ) { m_iDamagingFlames = 0; m_iActiveFlames = 0; } //----------------------------------------------------------------------------- // Purpose: UI Progress //----------------------------------------------------------------------------- float CTFFlameThrower::GetProgress( void ) { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return 0.f; return pPlayer->m_Shared.GetRageMeter() / 100.0f; } //----------------------------------------------------------------------------- // Purpose: UI Progress (same as GetProgress() without the division by 100.0f) //----------------------------------------------------------------------------- bool CTFFlameThrower::IsRageFull( void ) { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return false; return ( pPlayer->m_Shared.GetRageMeter() >= 100.0f ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFFlameThrower::EffectMeterShouldFlash( void ) { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return false; if ( pPlayer && (IsRageFull() || pPlayer->m_Shared.IsRageDraining()) ) return true; else return false; } //----------------------------------------------------------------------------- // Purpose: Returns the position of the tip of the muzzle //----------------------------------------------------------------------------- Vector CTFFlameThrower::GetMuzzlePosHelper( bool bVisualPos ) { Vector vecMuzzlePos; CTFPlayer *pOwner = GetTFPlayerOwner(); if ( pOwner ) { Vector vecForward, vecRight, vecUp; AngleVectors( pOwner->GetAbsAngles(), &vecForward, &vecRight, &vecUp ); vecMuzzlePos = pOwner->Weapon_ShootPosition(); vecMuzzlePos += vecRight * TF_FLAMETHROWER_MUZZLEPOS_RIGHT; // if asking for visual position of muzzle, include the forward component if ( bVisualPos ) { vecMuzzlePos += vecForward * TF_FLAMETHROWER_MUZZLEPOS_FORWARD; } } return vecMuzzlePos; } void CTFFlameThrower::CalculateHalloweenSpell( void ) { m_bHasHalloweenSpell.Set( false ); if ( TF_IsHolidayActive( kHoliday_HalloweenOrFullMoon ) ) { int iHalloweenSpell = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( this, iHalloweenSpell, halloween_green_flames ); m_bHasHalloweenSpell.Set( iHalloweenSpell > 0 ); } } bool CTFFlameThrower::Deploy( void ) { #if defined( CLIENT_DLL ) StartPilotLight(); m_flFlameHitRatio = 0; m_flPrevFlameHitRatio = -1; m_flChargeBeginTime = 0; m_bEffectsThinking = true; SetContextThink( &CTFFlameThrower::ClientEffectsThink, gpGlobals->curtime, "EFFECTS_THINK" ); StopFullCritEffect(); #endif // CLIENT_DLL CalculateHalloweenSpell(); return BaseClass::Deploy(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::FireGameEvent( IGameEvent *event ) { if ( FStrEq( event->GetName(), "recalculate_holidays" ) ) { CalculateHalloweenSpell(); } } #if defined( CLIENT_DLL ) //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::OnDataChanged(DataUpdateType_t updateType) { BaseClass::OnDataChanged(updateType); C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); C_TFPlayer *pPlayerOwner = GetTFPlayerOwner(); // bool bLocalPlayerAmmo = true; if ( pPlayerOwner == pLocalPlayer ) { bLocalPlayerAmmo = GetPlayerOwner()->GetAmmoCount( m_iPrimaryAmmoType ) > 0; } if ( IsCarrierAlive() && ( WeaponState() == WEAPON_IS_ACTIVE ) && bLocalPlayerAmmo == true ) { if ( m_iWeaponState > FT_STATE_IDLE ) { if ( ( m_iWeaponState == FT_STATE_SECONDARY && GetPlayerOwner() != C_BasePlayer::GetLocalPlayer() ) || m_iWeaponState != FT_STATE_SECONDARY ) { StartFlame(); #ifdef STAGING_ONLY if ( ShootsNapalm() ) { RestartParticleEffect(); } #endif // STAGING_ONLY } } else { StartPilotLight(); } } else { StopFlame(); StopPilotLight(); StopFullCritEffect(); m_bEffectsThinking = false; } if ( pPlayerOwner == pLocalPlayer ) { if ( m_pFiringLoop ) { m_flFlameHitRatio = GetFlameHitRatio(); m_flFlameHitRatio = RemapValClamped( m_flFlameHitRatio, 0.0f, 1.0f, 1.0f, 100.f ); //Msg ( "%f\n", m_flFlameHitRatio ); if ( m_flFlameHitRatio != m_flPrevFlameHitRatio ) { m_flPrevFlameHitRatio = m_flFlameHitRatio; CLocalPlayerFilter filter; CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); // We play accent sounds based on accuracy if ( m_flFlameHitRatio >= TF_FLAMETHROWER_HITACCURACY_HIGH ) { controller.SoundChangePitch( m_pFiringLoop, 140, 0.1 ); m_szAccuracySound = "Weapon_FlameThrower.FireHitHard"; } else { controller.SoundChangePitch( m_pFiringLoop, 100, 0.1 ); // If our accuracy is too low if ( m_pFiringAccuracyLoop ) { controller.SoundDestroy( m_pFiringAccuracyLoop ); m_pFiringAccuracyLoop = NULL; } return; } // Only start a new sound if there's been a change if ( !m_pFiringAccuracyLoop ) { m_pFiringAccuracyLoop = controller.SoundCreate( filter, entindex(), m_szAccuracySound ); controller.Play( m_pFiringAccuracyLoop, 1.0, 100 ); } } } else if ( m_pFiringAccuracyLoop ) { CSoundEnvelopeController::GetController().SoundDestroy( m_pFiringAccuracyLoop ); m_pFiringAccuracyLoop = NULL; } if ( GetBuffType() > 0 ) { if ( !m_bFullRageEffect && pPlayerOwner && pPlayerOwner->m_Shared.GetRageMeter() >= 100.0f ) { m_bFullRageEffect = true; m_MmmmphEffect.StartEffects( FullCritChargedEffectName() ); } else if ( m_bFullRageEffect && pPlayerOwner && pPlayerOwner->m_Shared.GetRageMeter() < 100.0f ) { StopFullCritEffect(); m_MmmmphEffect.StopEffects(); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::UpdateOnRemove( void ) { m_FlameEffects.StopEffects(); m_MmmmphEffect.StopEffects(); StopPilotLight(); StopFullCritEffect(); m_bEffectsThinking = false; BaseClass::UpdateOnRemove(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::SetDormant( bool bDormant ) { // If I'm going from active to dormant and I'm carried by another player, stop our firing sound. if ( !IsCarriedByLocalPlayer() ) { if ( !IsDormant() && bDormant ) { StopFlame(); StopPilotLight(); StopFullCritEffect(); m_bEffectsThinking = false; } } // Deliberately skip base combat weapon to avoid being holstered C_BaseEntity::SetDormant( bDormant ); } int CTFFlameThrower::GetWorldModelIndex( void ) { int iParticleEffectIndex = 0; CALL_ATTRIB_HOOK_INT( iParticleEffectIndex, set_weapon_mode ); // Pyro bubble wand support. if ( iParticleEffectIndex == 3 ) { CTFPlayer *pPlayer = ToTFPlayer( GetOwner() ); if ( pPlayer && pPlayer->m_Shared.InCond( TF_COND_TAUNTING ) && pPlayer->m_Shared.GetTauntIndex() == TAUNT_BASE_WEAPON ) { // While we are taunting, replace our normal world model with the bubble wand. m_iWorldModelIndex = modelinfo->GetModelIndex( TF_WEAPON_BUBBLE_WAND_MODEL ); return m_iWorldModelIndex; } } return BaseClass::GetWorldModelIndex(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::StartFlame() { if ( m_iWeaponState == FT_STATE_SECONDARY ) { GetAppropriateWorldOrViewModel()->ParticleProp()->Create( "pyro_blast", PATTACH_POINT_FOLLOW, "muzzle" ); CLocalPlayerFilter filter; const char *shootsound = GetShootSound( WPN_DOUBLE ); EmitSound( filter, entindex(), shootsound ); return; } CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); // normally, crossfade between start sound & firing loop in 3.5 sec float flCrossfadeTime = 3.5; if ( m_pFiringLoop && ( m_bCritFire != m_bFiringLoopCritical ) ) { // If we're firing and changing between critical & noncritical, just need to change the firing loop. // Set crossfade time to zero so we skip the start sound and go to the loop immediately. flCrossfadeTime = 0; StopFlame( true ); } StopPilotLight(); if ( !m_pFiringStartSound && !m_pFiringLoop ) { // NVNT if the local player is owning this weapon, process the start event if ( C_BasePlayer::GetLocalPlayer() == GetOwner() && haptics ) haptics->ProcessHapticEvent(2,"Weapons","flamer_start"); RestartParticleEffect(); CLocalPlayerFilter filter; // Play the fire start sound const char *shootsound = GetShootSound( SINGLE ); if ( flCrossfadeTime > 0.0 ) { // play the firing start sound and fade it out m_pFiringStartSound = controller.SoundCreate( filter, entindex(), shootsound ); controller.Play( m_pFiringStartSound, 1.0, 100 ); controller.SoundChangeVolume( m_pFiringStartSound, 0.0, flCrossfadeTime ); } // Start the fire sound loop and fade it in if ( m_bCritFire ) { shootsound = GetShootSound( BURST ); } else { shootsound = GetShootSound( SPECIAL1 ); } m_pFiringLoop = controller.SoundCreate( filter, entindex(), shootsound ); m_bFiringLoopCritical = m_bCritFire; // play the firing loop sound and fade it in if ( flCrossfadeTime > 0.0 ) { controller.Play( m_pFiringLoop, 0.0, 100 ); controller.SoundChangeVolume( m_pFiringLoop, 1.0, flCrossfadeTime ); } else { controller.Play( m_pFiringLoop, 1.0, 100 ); } } // check our "hit" sound if ( m_bHitTarget != m_bFiringHitTarget ) { if ( m_bHitTarget == false ) { StopHitSound(); } else { char *pchFireHitSound = "Weapon_FlameThrower.FireHit"; int iParticleEffectIndex = 0; CALL_ATTRIB_HOOK_INT( iParticleEffectIndex, set_weapon_mode ); if ( iParticleEffectIndex == 3 ) { pchFireHitSound = "Weapon_Rainblower.FireHit"; } CLocalPlayerFilter filter; m_pFiringHitLoop = controller.SoundCreate( filter, entindex(), pchFireHitSound ); controller.Play( m_pFiringHitLoop, 1.0, 100 ); } m_bFiringHitTarget = m_bHitTarget; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::StopHitSound() { if ( m_pFiringHitLoop ) { CSoundEnvelopeController::GetController().SoundDestroy( m_pFiringHitLoop ); m_pFiringHitLoop = NULL; } m_bHitTarget = m_bFiringHitTarget = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::StopFlame( bool bAbrupt /* = false */ ) { if ( ( m_pFiringLoop || m_pFiringStartSound ) && !bAbrupt ) { // play a quick wind-down poof when the flame stops CLocalPlayerFilter filter; const char *shootsound = GetShootSound( SPECIAL3 ); EmitSound( filter, entindex(), shootsound ); } if ( m_pFiringLoop ) { CSoundEnvelopeController::GetController().SoundDestroy( m_pFiringLoop ); m_pFiringLoop = NULL; } if ( m_pFiringStartSound ) { CSoundEnvelopeController::GetController().SoundDestroy( m_pFiringStartSound ); m_pFiringStartSound = NULL; } if ( m_FlameEffects.StopEffects() ) { C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); if ( pLocalPlayer && pLocalPlayer == GetOwner() ) { // NVNT local player is finished firing. send the stop event. if ( haptics ) haptics->ProcessHapticEvent(2,"Weapons","flamer_stop"); } } if ( !bAbrupt ) { StopHitSound(); } m_iParticleWaterLevel = -1; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::StartPilotLight() { if ( !m_pPilotLightSound ) { StopFlame(); // Create the looping pilot light sound const char *pilotlightsound = GetShootSound( SPECIAL2 ); CLocalPlayerFilter filter; CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); m_pPilotLightSound = controller.SoundCreate( filter, entindex(), pilotlightsound ); controller.Play( m_pPilotLightSound, 1.0, 100 ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::StopPilotLight() { if ( m_pPilotLightSound ) { CSoundEnvelopeController::GetController().SoundDestroy( m_pPilotLightSound ); m_pPilotLightSound = NULL; } } void CTFFlameThrower::StopFullCritEffect() { m_bFullRageEffect = false; m_MmmmphEffect.StopEffects(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::RestartParticleEffect( void ) { CTFPlayer *pOwner = GetTFPlayerOwner(); if ( !pOwner ) return; if ( m_iWeaponState != FT_STATE_FIRING && m_iWeaponState != FT_STATE_STARTFIRING ) { return; } m_iParticleWaterLevel = pOwner->GetWaterLevel(); bool bIsFirstPersonView = IsFirstPersonView(); // Start the appropriate particle effect const char *pszParticleEffect; if ( pOwner->GetWaterLevel() == WL_Eyes ) { pszParticleEffect = "flamethrower_underwater"; } else { if ( m_bCritFire ) { pszParticleEffect = FlameCritEffectName( bIsFirstPersonView ); } else { pszParticleEffect = FlameEffectName( bIsFirstPersonView ); } } m_FlameEffects.StartEffects( pszParticleEffect ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char* CTFFlameThrower::FlameEffectName( bool bIsFirstPersonView ) { CTFPlayer *pOwner = GetTFPlayerOwner(); if ( !pOwner ) return NULL; #ifdef STAGING_ONLY if ( ShootsNapalm() ) { return "muzzle_bignasty"; } #endif // STAGING_ONLY // Halloween Spell if ( m_bHasHalloweenSpell ) { return "flamethrower_halloween"; } int iParticleEffectIndex = 0; CALL_ATTRIB_HOOK_INT( iParticleEffectIndex, set_weapon_mode ); switch ( iParticleEffectIndex ) { case 1: return "drg_phlo_stream"; case 2: return "flamethrower_giant_mvm"; case 3: return ( bIsFirstPersonView ? "flamethrower_rainbow_FP" : "flamethrower_rainbow" ); default: return ( pOwner->GetTeamNumber() == TF_TEAM_BLUE ? "flamethrower_blue" : "flamethrower" ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char* CTFFlameThrower::FlameCritEffectName( bool bIsFirstPersonView ) { CTFPlayer *pOwner = GetTFPlayerOwner(); if ( !pOwner ) return NULL; #ifdef STAGING_ONLY if ( ShootsNapalm() ) { return "muzzle_bignasty"; } #endif // STAGING_ONLY // Halloween Spell if ( m_bHasHalloweenSpell ) { return ( pOwner->GetTeamNumber() == TF_TEAM_BLUE ? "flamethrower_halloween_crit_blue" : "flamethrower_halloween_crit_red" ); } int iParticleEffectIndex = 0; CALL_ATTRIB_HOOK_INT( iParticleEffectIndex, set_weapon_mode ); switch ( iParticleEffectIndex ) { case 1: return "drg_phlo_stream_crit"; case 2: return "flamethrower_crit_giant_mvm"; case 3: return ( bIsFirstPersonView ? "flamethrower_rainbow_FP" : "flamethrower_rainbow" ); default: return ( pOwner->GetTeamNumber() == TF_TEAM_BLUE ? "flamethrower_crit_blue" : "flamethrower_crit_red" ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char* CTFFlameThrower::FullCritChargedEffectName( void ) { switch( GetTeamNumber() ) { case TF_TEAM_BLUE: return "medicgun_invulnstatus_fullcharge_blue"; case TF_TEAM_RED: return "medicgun_invulnstatus_fullcharge_red"; default: return ""; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::ClientEffectsThink( void ) { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return; if ( !pPlayer->IsLocalPlayer() ) return; if ( !pPlayer->GetViewModel() ) return; if ( !m_bEffectsThinking ) return; float flRageInverse = 1.f; if ( GetBuffType() > 0 ) { flRageInverse = 1.0f - ( pPlayer->m_Shared.GetRageMeter() / 100.0f ); if ( flRageInverse < 1.0f ) { // We have some rage, let's spark! ParticleProp()->Init( this ); CNewParticleEffect* pEffect = ParticleProp()->Create( "drg_bison_idle", PATTACH_POINT_FOLLOW, "muzzle" ); if ( pEffect ) { pEffect->SetControlPoint( CUSTOM_COLOR_CP1, GetParticleColor( 1 ) ); pEffect->SetControlPoint( CUSTOM_COLOR_CP2, GetParticleColor( 2 ) ); } } } SetContextThink( &CTFFlameThrower::ClientEffectsThink, gpGlobals->curtime + 0.1f + RandomFloat( 1.0f, 5.0f ) * flRageInverse, "EFFECTS_THINK" ); } void CTFFlameThrower::FlameEffect_t::StartEffects( const char* pszEffectName ) { // Stop any old flame effects StopEffects(); // Figure out which weapon this flame effect is to be attached to. Store this for // later so we know which weapon to deactivate the effect on m_hEffectWeapon = m_pOwner->GetWeaponForEffect(); if( m_hEffectWeapon ) { CParticleProperty* pParticleProp = m_hEffectWeapon->ParticleProp(); if( pParticleProp ) { // Flame on m_pFlameEffect = pParticleProp->Create( pszEffectName, PATTACH_POINT_FOLLOW, "muzzle" ); } } } bool CTFFlameThrower::FlameEffect_t::StopEffects() { bool bStopped = false; // Stop any old flame effects if ( m_pFlameEffect && m_hEffectWeapon ) { m_hEffectWeapon->ParticleProp()->StopEmission( m_pFlameEffect ); bStopped = true; } m_pFlameEffect = NULL; m_hEffectWeapon = NULL; return bStopped; } #else //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::HitTargetThink( void ) { if ( ( m_flTimeToStopHitSound > 0 ) && ( m_flTimeToStopHitSound < gpGlobals->curtime ) ) { m_bHitTarget = false; m_flTimeToStopHitSound = 0; SetContextThink( NULL, 0, s_pszFlameThrowerHitTargetThink ); return; } SetNextThink( gpGlobals->curtime + 0.1f, s_pszFlameThrowerHitTargetThink ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameThrower::SetHitTarget( void ) { if ( m_iWeaponState > FT_STATE_IDLE ) { m_bHitTarget = true; m_flTimeToStopHitSound = gpGlobals->curtime + 0.2; // Start the hit target thinking SetContextThink( &CTFFlameThrower::HitTargetThink, gpGlobals->curtime + 0.1f, s_pszFlameThrowerHitTargetThink ); } } #endif #ifdef STAGING_ONLY //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFFlameThrower::RocketPackCanActivate( int nAmmoCost ) { CTFPlayer *pOwner = GetTFPlayerOwner(); if ( !pOwner ) return false; int nRocketPack = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pOwner, nRocketPack, rocket_pack ); if ( !nRocketPack ) return false; if ( pOwner->m_Shared.IsLoser() ) return false; if ( pOwner->m_Shared.InCond( TF_COND_STUNNED ) ) return false; if ( pOwner->IsTaunting() ) return false; // if ( pOwner->m_Shared.GetChargeMeter() < 100.f ) // return false; if ( pOwner->GetAmmoCount( TF_AMMO_PRIMARY ) < nAmmoCost ) return false; return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFFlameThrower::RocketPackLaunch( int nAmmoCost ) { CTFPlayer *pOwner = GetTFPlayerOwner(); if ( !pOwner ) return false; #ifdef CLIENT_DLL StopFlame( false ); #endif // CLIENT_DLL #ifdef GAME_DLL // Launch if ( !pOwner->m_Shared.InCond( TF_COND_ROCKETPACK ) ) { pOwner->m_Shared.AddCond( TF_COND_ROCKETPACK ); pOwner->m_Shared.StunPlayer( 0.5f, 1.0f, TF_STUN_MOVEMENT ); } Vector vecDir; pOwner->EyeVectors( &vecDir ); pOwner->SetAbsVelocity( vec3_origin ); Vector vecFlightDir = -vecDir; VectorNormalize( vecFlightDir ); float flForce = 450.f; const float flPushScale = ( pOwner->GetFlags() & FL_ONGROUND ) ? 1.2f : 1.8f; // Greater force while airborne const float flVertPushScale = ( pOwner->GetFlags() & FL_ONGROUND ) ? 1.2f : 0.25f; // Less vertical force while airborne Vector vecForce = vecFlightDir * -flForce * flPushScale; vecForce.z += 1.f * flForce * flVertPushScale; pOwner->RemoveFlag( FL_ONGROUND ); pOwner->ApplyAbsVelocityImpulse( vecForce ); m_flNextSecondaryAttack = gpGlobals->curtime + 0.75f; m_flNextPrimaryAttack = gpGlobals->curtime + 1.f; m_flResetBurstEffect = gpGlobals->curtime + 0.05f; m_bFiredSecondary = true; m_flChargeBeginTime = 0; pOwner->RemoveAmmo( nAmmoCost, m_iPrimaryAmmoType ); pOwner->EmitSound( "Equipment.RocketPack_Activate" ); #endif // GAME_DLL #ifdef CLIENT_DLL if ( prediction->IsFirstTimePredicted() == true ) { StartFlame(); } #endif // CLIENT_DLL return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFFlameThrower::ShootsNapalm( void ) { int iNapalm = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwnerEntity(), iNapalm, mod_flamethrower_napalm ); return ( iNapalm > 0 ); } #endif // STAGING_ONLY IMPLEMENT_NETWORKCLASS_ALIASED( TFFlameRocket, DT_TFFlameRocket ) BEGIN_NETWORK_TABLE( CTFFlameRocket, DT_TFFlameRocket ) END_NETWORK_TABLE() #ifdef GAME_DLL LINK_ENTITY_TO_CLASS( tf_flame, CTFFlameEntity ); IMPLEMENT_AUTO_LIST( ITFFlameEntityAutoList ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFFlameEntity::CTFFlameEntity() {} //----------------------------------------------------------------------------- // Purpose: Spawns this entity //----------------------------------------------------------------------------- void CTFFlameEntity::Spawn( void ) { BaseClass::Spawn(); // don't collide with anything, we do our own collision detection in our think method SetSolid( SOLID_NONE ); SetSolidFlags( FSOLID_NOT_SOLID ); SetCollisionGroup( COLLISION_GROUP_NONE ); // move noclip: update position from velocity, that's it SetMoveType( MOVETYPE_NOCLIP, MOVECOLLIDE_DEFAULT ); AddEFlags( EFL_NO_WATER_VELOCITY_CHANGE ); float iBoxSize = tf_flamethrower_boxsize.GetFloat(); CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwnerEntity(), iBoxSize, mult_flame_size ); UTIL_SetSize( this, -Vector( iBoxSize, iBoxSize, iBoxSize ), Vector( iBoxSize, iBoxSize, iBoxSize ) ); // Setup attributes. m_takedamage = DAMAGE_NO; m_vecInitialPos = GetAbsOrigin(); m_vecPrevPos = m_vecInitialPos; // Track total active flame entities m_hFlameThrower = dynamic_cast< CTFFlameThrower* >( GetOwnerEntity() ); if ( m_hFlameThrower ) { m_hFlameThrower->IncrementActiveFlameCount(); m_bBurnedEnemy = false; float flFlameLife = tf_flamethrower_flametime.GetFloat(); CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwnerEntity(), flFlameLife, mult_flame_life ); m_flTimeRemove = gpGlobals->curtime + ( flFlameLife * random->RandomFloat( 0.9f, 1.1f ) ); } else { m_flTimeRemove = gpGlobals->curtime + 3.f; } // Setup the think function. SetThink( &CTFFlameEntity::FlameThink ); SetNextThink( gpGlobals->curtime ); } //----------------------------------------------------------------------------- // Purpose: Creates an instance of this entity //----------------------------------------------------------------------------- CTFFlameEntity *CTFFlameEntity::Create( const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner, float flSpeed, int iDmgType, float flDmgAmount, bool bAlwaysCritFromBehind, bool bRandomize ) { CTFFlameEntity *pFlame = static_cast( CBaseEntity::Create( "tf_flame", vecOrigin, vecAngles, pOwner ) ); if ( !pFlame ) return NULL; // Initialize the owner. pFlame->SetOwnerEntity( pOwner ); if ( pOwner->GetOwnerEntity() ) pFlame->m_hAttacker = pOwner->GetOwnerEntity(); else pFlame->m_hAttacker = pOwner; CBaseEntity *pAttacker = (CBaseEntity *) pFlame->m_hAttacker; if ( pAttacker ) { pFlame->m_iAttackerTeam = pAttacker->GetTeamNumber(); } // Set team. pFlame->ChangeTeam( pOwner->GetTeamNumber() ); pFlame->m_iDmgType = iDmgType; pFlame->m_flDmgAmount = flDmgAmount; // Setup the initial velocity. Vector vecForward, vecRight, vecUp; AngleVectors( vecAngles, &vecForward, &vecRight, &vecUp ); float flFlameLifeMult = 1.0f; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pFlame->m_hAttacker, flFlameLifeMult, mult_flame_life ); float velocity = flFlameLifeMult * flSpeed; pFlame->m_vecBaseVelocity = vecForward * velocity; float iFlameSizeMult = 1.0f; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pFlame->m_hAttacker, iFlameSizeMult, mult_flame_size ); if ( bRandomize ) { pFlame->m_vecBaseVelocity += RandomVector( -velocity * iFlameSizeMult * tf_flamethrower_vecrand.GetFloat(), velocity * iFlameSizeMult * tf_flamethrower_vecrand.GetFloat() ); } if ( pOwner->GetOwnerEntity() ) { pFlame->m_vecAttackerVelocity = pOwner->GetOwnerEntity()->GetAbsVelocity(); } pFlame->SetAbsVelocity( pFlame->m_vecBaseVelocity ); // Setup the initial angles. pFlame->SetAbsAngles( vecAngles ); pFlame->SetCritFromBehind( bAlwaysCritFromBehind ); return pFlame; } //----------------------------------------------------------------------------- class CFlameEntityEnum : public IEntityEnumerator { public: CFlameEntityEnum( CBaseEntity *pShooter ) { m_pShooter = pShooter; } virtual bool EnumEntity( IHandleEntity *pHandleEntity ) { CBaseEntity *pEnt = static_cast( pHandleEntity ); // Ignore collisions with the shooter if ( pEnt == m_pShooter ) return true; if ( pEnt->IsPlayer() && pEnt->IsAlive() ) { m_Targets.AddToTail( pEnt ); } else if ( pEnt->MyNextBotPointer() && pEnt->IsAlive() ) { // add non-player bots m_Targets.AddToTail( pEnt ); } else if ( pEnt->IsBaseObject() && m_pShooter->GetTeamNumber() != pEnt->GetTeamNumber() ) { // only add enemy objects m_Targets.AddToTail( pEnt ); } else if ( CTFRobotDestructionLogic::GetRobotDestructionLogic() && m_pShooter->GetTeamNumber() != pEnt->GetTeamNumber() && FClassnameIs( pEnt, "tf_robot_destruction_robot" ) ) { // only add enemy robots m_Targets.AddToTail( pEnt ); } else if ( FClassnameIs( pEnt, "func_breakable" ) || FClassnameIs( pEnt, "tf_pumpkin_bomb" ) || FClassnameIs( pEnt, "tf_merasmus_trick_or_treat_prop" ) ) { m_Targets.AddToTail( pEnt ); } return true; } const CUtlVector< CBaseEntity* >& GetTargets() { return m_Targets; } public: Ray_t *m_pRay; CBaseEntity *m_pShooter; CUtlVector< CBaseEntity* > m_Targets; }; //----------------------------------------------------------------------------- // Purpose: Think method //----------------------------------------------------------------------------- void CTFFlameEntity::FlameThink( void ) { TM_ZONE_DEFAULT( TELEMETRY_LEVEL0 ) // if we've expired, remove ourselves if ( gpGlobals->curtime >= m_flTimeRemove ) { RemoveFlame(); return; } else { // Always think, if we haven't died due to our timeout. SetNextThink( gpGlobals->curtime ); } // Did we move? should we check collision? if ( GetAbsOrigin() != m_vecPrevPos ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s Collision", __FUNCTION__ ); CTFPlayer *pAttacker = dynamic_cast( (CBaseEntity *) m_hAttacker ); if ( !pAttacker ) return; // Create a ray for flame entity to trace Ray_t rayWorld; rayWorld.Init( m_vecInitialPos, GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs() ); // check against world first // if we collide with world, just destroy the flame trace_t trWorld; UTIL_TraceRay( rayWorld, MASK_SOLID, this, COLLISION_GROUP_DEBRIS, &trWorld ); bool bHitWorld = trWorld.startsolid || trWorld.fraction < 1.f; // update the ray Ray_t rayEnt; rayEnt.Init( m_vecPrevPos, GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs() ); // burn all entities that we should collide with CFlameEntityEnum eFlameEnum( pAttacker ); enginetrace->EnumerateEntities( rayEnt, false, &eFlameEnum ); bool bHitSomething = false; FOR_EACH_VEC( eFlameEnum.GetTargets(), i ) { CBaseEntity *pEnt = eFlameEnum.GetTargets()[i]; // skip ent that's already burnt by this flame int iIndex = m_hEntitiesBurnt.Find( pEnt ); if ( iIndex != m_hEntitiesBurnt.InvalidIndex() ) continue; // if we're removing the flame this frame from hitting world, check if we hit this ent before hitting the world if ( bHitWorld ) { trace_t trEnt; enginetrace->ClipRayToEntity( rayWorld, MASK_SOLID | CONTENTS_HITBOX, pEnt, &trEnt ); // hit world before this ent, skip it if ( trEnt.fraction >= trWorld.fraction ) continue; } // burn them all! if ( pEnt->IsPlayer() && pEnt->InSameTeam( pAttacker ) ) { OnCollideWithTeammate( ToTFPlayer( pEnt ) ); } else { OnCollide( pEnt ); } bHitSomething = true; } // now, let's see if the flame visual could have actually hit this player. Trace backward from the // point of impact to where the flame was fired, see if we hit anything. if ( bHitSomething && tf_debug_flamethrower.GetBool() ) { NDebugOverlay::SweptBox( m_vecPrevPos, GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs(), vec3_angle, 255, 255, 0, 100, 5.0 ); NDebugOverlay::EntityBounds( this, 255, 255, 0, 100, 5.0 ); } // remove the flame if it hits the world if ( bHitWorld ) { if ( tf_debug_flamethrower.GetInt() ) { NDebugOverlay::SweptBox( m_vecInitialPos, GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs(), vec3_angle, 255, 0, 0, 100, 3.0 ); } RemoveFlame(); } } // Reduce our base velocity by the air drag constant m_vecBaseVelocity *= GetFlameDrag(); // Add our float upward velocity Vector vecVelocity = m_vecBaseVelocity + Vector( 0, 0, GetFlameFloat() ) + m_vecAttackerVelocity; // Update our velocity SetAbsVelocity( vecVelocity ); // Render debug visualization if convar on if ( tf_debug_flamethrower.GetInt() ) { if ( m_hEntitiesBurnt.Count() > 0 ) { int val = ( (int) ( gpGlobals->curtime * 10 ) ) % 255; NDebugOverlay::EntityBounds(this, val, 255, val, 0 ,0 ); } else { NDebugOverlay::EntityBounds(this, 0, 100, 255, 0 ,0) ; } } m_vecPrevPos = GetAbsOrigin(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameEntity::SetHitTarget( void ) { if ( !m_hFlameThrower ) return; m_hFlameThrower->SetHitTarget(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameEntity::RemoveFlame() { UpdateFlameThrowerHitRatio(); UTIL_Remove( this ); } //----------------------------------------------------------------------------- // Purpose: Called when we've collided with another entity //----------------------------------------------------------------------------- void CTFFlameEntity::OnCollide( CBaseEntity *pOther ) { int nContents = UTIL_PointContents( GetAbsOrigin() ); if ( (nContents & MASK_WATER) ) { RemoveFlame(); return; } // remember that we've burnt this player m_hEntitiesBurnt.AddToTail( pOther ); float flDistance = GetAbsOrigin().DistTo( m_vecInitialPos ); float flDamage = m_flDmgAmount * RemapValClamped( flDistance, tf_flamethrower_maxdamagedist.GetFloat()/2, tf_flamethrower_maxdamagedist.GetFloat(), 1.0f, 0.70f ); flDamage = MAX( flDamage, 1.0 ); if ( tf_debug_flamethrower.GetInt() ) { Msg( "Flame touch dmg: %.1f\n", flDamage ); } CBaseEntity *pAttacker = m_hAttacker; if ( !pAttacker ) return; SetHitTarget(); int iDamageType = m_iDmgType; if ( pOther && pOther->IsPlayer() ) { CTFPlayer *pVictim = ToTFPlayer( pOther ); if ( IsBehindTarget( pOther ) ) { if ( m_bCritFromBehind == true ) { iDamageType |= DMG_CRITICAL; } if ( pVictim ) { pVictim->HandleAchievement_Pyro_BurnFromBehind( ToTFPlayer( pAttacker ) ); } } // Pyro-specific if ( pAttacker->IsPlayer() && pVictim ) { CTFPlayer *pPlayerAttacker = ToTFPlayer( pAttacker ); if ( pPlayerAttacker && pPlayerAttacker->IsPlayerClass( TF_CLASS_PYRO ) ) { // burn the victim while taunting? if ( pVictim->m_Shared.InCond( TF_COND_TAUNTING ) ) { static CSchemaItemDefHandle flipTaunt( "Flippin' Awesome Taunt" ); // if I'm the one being flipped, and getting lit on fire if ( !pVictim->IsTauntInitiator() && pVictim->GetTauntEconItemView() && pVictim->GetTauntEconItemView()->GetItemDefinition() == flipTaunt ) { pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_PYRO_IGNITE_PLAYER_BEING_FLIPPED ); } } pVictim->m_Shared.AddCond( TF_COND_HEALING_DEBUFF, 2.f, pAttacker ); } } } CTakeDamageInfo info( GetOwnerEntity(), pAttacker, GetOwnerEntity(), flDamage, iDamageType, TF_DMG_CUSTOM_BURNING ); info.SetReportedPosition( pAttacker->GetAbsOrigin() ); if ( info.GetDamageType() & DMG_CRITICAL ) { info.SetCritType( CTakeDamageInfo::CRIT_FULL ); } // terrible hack for flames hitting the Merasmus props to get the particle effect in the correct position if ( TFGameRules() && TFGameRules()->GetActiveBoss() && ( TFGameRules()->GetActiveBoss()->GetBossType() == HALLOWEEN_BOSS_MERASMUS ) ) { info.SetDamagePosition( GetAbsOrigin() ); } // Track hits for the Flamethrower, which is used to change the weapon sound based on hit ratio if ( m_hFlameThrower ) { m_bBurnedEnemy = true; m_hFlameThrower->IncrementFlameDamageCount(); } // We collided with pOther, so try to find a place on their surface to show blood trace_t pTrace; UTIL_TraceLine( WorldSpaceCenter(), pOther->WorldSpaceCenter(), MASK_SOLID|CONTENTS_HITBOX, this, COLLISION_GROUP_NONE, &pTrace ); pOther->DispatchTraceAttack( info, GetAbsVelocity(), &pTrace ); ApplyMultiDamage(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameEntity::OnCollideWithTeammate( CTFPlayer *pPlayer ) { // Only care about Snipers if ( !pPlayer->IsPlayerClass(TF_CLASS_SNIPER) ) return; int iIndex = m_hEntitiesBurnt.Find( pPlayer ); if ( iIndex != m_hEntitiesBurnt.InvalidIndex() ) return; m_hEntitiesBurnt.AddToTail( pPlayer ); // Does he have the bow? CTFWeaponBase *pWpn = pPlayer->GetActiveTFWeapon(); if ( pWpn && pWpn->GetWeaponID() == TF_WEAPON_COMPOUND_BOW ) { CTFCompoundBow *pBow = static_cast( pWpn ); pBow->SetArrowAlight( true ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFFlameEntity::IsBehindTarget( CBaseEntity *pTarget ) { return ( DotProductToTarget( pTarget ) > 0.8 ); } //----------------------------------------------------------------------------- // Purpose: Utility to calculate dot product between facing angles of flame and target //----------------------------------------------------------------------------- float CTFFlameEntity::DotProductToTarget( CBaseEntity *pTarget ) { Assert( pTarget ); // Get the forward view vector of the target, ignore Z Vector vecVictimForward; AngleVectors( pTarget->EyeAngles(), &vecVictimForward, NULL, NULL ); vecVictimForward.z = 0.0f; vecVictimForward.NormalizeInPlace(); Vector vecTraveling = m_vecBaseVelocity; vecTraveling.z = 0.0f; vecTraveling.NormalizeInPlace(); return DotProduct( vecVictimForward, vecTraveling ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFFlameEntity::UpdateFlameThrowerHitRatio( void ) { if ( !m_hFlameThrower ) return; if ( m_bBurnedEnemy ) { m_hFlameThrower->DecrementFlameDamageCount(); } m_hFlameThrower->DecrementActiveFlameCount(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFFlameEntity::GetFlameFloat( void ) { return tf_flamethrower_float.GetFloat(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFFlameEntity::GetFlameDrag( void ) { return tf_flamethrower_drag.GetFloat(); } #endif // GAME_DLL #ifdef STAGING_ONLY //----------------------------------------------------------------------------- // Purpose: Napalm //----------------------------------------------------------------------------- #ifdef GAME_DLL #define NAPALM_THINK_CONTEXT "CTFMedigunShield_ShieldThink" #endif // GAME_DLL LINK_ENTITY_TO_CLASS( tf_projectile_napalm, CTFProjectile_Napalm ); IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_Napalm, DT_TFProjectile_Napalm ) BEGIN_NETWORK_TABLE( CTFProjectile_Napalm, DT_TFProjectile_Napalm ) #ifdef GAME_DLL SendPropEHandle( SENDINFO( m_hFlameThrower ) ), #else RecvPropEHandle( RECVINFO( m_hFlameThrower ) ), #endif END_NETWORK_TABLE() BEGIN_PREDICTION_DATA( CTFProjectile_Napalm ) END_PREDICTION_DATA() // Data BEGIN_DATADESC( CTFProjectile_Napalm ) #ifdef GAME_DLL DEFINE_THINKFUNC( NapalmThink ), #endif // GAME_DLL END_DATADESC() //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFProjectile_Napalm::CTFProjectile_Napalm() { #ifdef GAME_DLL m_flRemoveTime = 0.f; m_nHitCount = 0; m_flLastBurnTime = 0.f; #endif // GAME_DLL } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFProjectile_Napalm::~CTFProjectile_Napalm() { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Napalm::Precache() { // PrecacheModel( TF_MODEL_NAPALM ); PrecacheParticleSystem( "burninggibs" ); PrecacheParticleSystem( "flaming_arrow" ); PrecacheScriptSound( "Player.PlasmaDamage" ); BaseClass::Precache(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Napalm::Spawn() { Precache(); #ifdef GAME_DLL m_flRemoveTime = gpGlobals->curtime + 2.2f; SetContextThink( &CTFProjectile_Napalm::NapalmThink, gpGlobals->curtime, NAPALM_THINK_CONTEXT ); #endif // GAME_DLL BaseClass::Spawn(); #ifdef GAME_DLL SetDetonateTimerLength( FLT_MAX ); #endif // GAME_DLL } #ifdef GAME_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFProjectile_Napalm *CTFProjectile_Napalm::Create( CBaseCombatCharacter *pOwner, CTFFlameThrower *pLauncher ) { if ( pOwner ) { Vector vecForward; AngleVectors( pOwner->EyeAngles(), &vecForward, NULL, NULL ); CTFProjectile_Napalm *pProjectile = static_cast< CTFProjectile_Napalm* >( CBaseEntity::Create( "tf_projectile_napalm", pLauncher->GetVisualMuzzlePos(), pOwner->EyeAngles() ) ); if ( pProjectile ) { pProjectile->ChangeTeam( pOwner->GetTeamNumber() ); // Setup the initial velocity. float flVelocity = 1100.f; pProjectile->m_vecBaseVelocity = vecForward * flVelocity; pProjectile->m_vecBaseVelocity += RandomVector( -flVelocity * tf_flamethrower_vecrand.GetFloat(), flVelocity * tf_flamethrower_vecrand.GetFloat() ); pProjectile->InitGrenade( pProjectile->m_vecBaseVelocity, vec3_origin, pOwner, pLauncher->GetTFWpnData() ); pProjectile->m_hFlameThrower = pLauncher; return pProjectile; } } return NULL; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFProjectile_Napalm::UpdateTransmitState() { return SetTransmitState( FL_EDICT_PVSCHECK ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Napalm::NapalmThink( void ) { if ( gpGlobals->curtime > m_flRemoveTime ) { SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" ); SetTouch( NULL ); return; } SetContextThink( &CTFProjectile_Napalm::NapalmThink, gpGlobals->curtime + 0.1f, NAPALM_THINK_CONTEXT ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Napalm::Explode( trace_t *pTrace, int bitsDamageType ) { if ( !m_nHitCount ) { SetModelName( NULL_STRING ); AddSolidFlags( FSOLID_TRIGGER ); m_takedamage = DAMAGE_NO; // Pull out of the wall a bit. if ( pTrace->fraction != 1.f ) { SetAbsOrigin( pTrace->endpos + ( pTrace->plane.normal * 1.0f ) ); } CTFPlayer *pThrower = ToTFPlayer( GetThrower() ); if ( pThrower ) { const Vector& vecOrigin = GetAbsOrigin(); // Any effects from the initial explosion if ( InitialExplodeEffects( pThrower, pTrace ) ) { // Particle if ( GetImpactEffect() ) { CPVSFilter filter( vecOrigin ); // Stick effect on the player CBaseEntity *pEnt = pTrace->m_pEnt; if ( pEnt && pEnt->IsPlayer() && !pThrower->InSameTeam( pEnt ) ) { // TE_TFParticleEffect( filter, 0.f, GetImpactEffect(), pEnt->GetAbsOrigin(), vec3_angle, pEnt, PATTACH_ABSORIGIN_FOLLOW ); m_flRemoveTime = gpGlobals->curtime; } // World else { TE_TFParticleEffect( filter, 0.0, GetImpactEffect(), vecOrigin, vec3_angle ); } } // Sounds // EmitSound( "Player.PlasmaDamage" ); // Treat this trace exactly like radius damage CTraceFilterIgnorePlayers traceFilter( pThrower, COLLISION_GROUP_PROJECTILE ); // Burn players in impact range CBaseEntity *pListOfEntities[32]; int iEntities = UTIL_EntitiesInSphere( pListOfEntities, 32, vecOrigin, GetDamageRadius(), FL_CLIENT | FL_FAKECLIENT | FL_NPC ); for ( int i = 0; i < iEntities; ++i ) { if ( pThrower->InSameTeam( pListOfEntities[i] ) ) continue; CBaseCombatCharacter *pBaseCombatCharacter = NULL; CTFPlayer *pPlayer = ToTFPlayer( pListOfEntities[i] ); if ( !pPlayer ) { pBaseCombatCharacter = dynamic_cast< CBaseCombatCharacter* >( pListOfEntities[i] ); } else { pBaseCombatCharacter = pPlayer; } if ( !pBaseCombatCharacter || !pBaseCombatCharacter->IsAlive() ) continue; // Do a quick trace to see if there's any geometry in the way. trace_t pImpactTrace; UTIL_TraceLine( GetAbsOrigin(), pBaseCombatCharacter->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &traceFilter, &pImpactTrace ); if ( pImpactTrace.DidHitWorld() ) continue; // Effects on the individual players ExplodeEffectOnTarget( pThrower, pPlayer, pBaseCombatCharacter ); } ApplyBlastDamage( pThrower, vecOrigin ); } } AddEffects( EF_NODRAW ); SetAbsVelocity( vec3_origin ); SetMoveType( MOVETYPE_NONE ); } m_nHitCount++; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Napalm::PipebombTouch( CBaseEntity *pOther ) { if ( gpGlobals->curtime - m_flLastBurnTime < 0.15f ) return; if ( InSameTeam( pOther ) ) return; if ( !m_hFlameThrower || !m_hFlameThrower->GetOwnerEntity() ) return; CTakeDamageInfo info; info.SetAttacker( m_hFlameThrower->GetOwnerEntity() ); info.SetInflictor( m_hFlameThrower ); info.SetWeapon( m_hFlameThrower ); info.SetDamage( 2.f ); info.SetDamageCustom( GetCustomDamageType() ); info.SetDamagePosition( GetAbsOrigin() ); info.SetDamageType( DMG_BURN ); pOther->TakeDamage( info ); m_flLastBurnTime = gpGlobals->curtime; BaseClass::PipebombTouch( pOther ); } //----------------------------------------------------------------------------- // Purpose: Radius damage //----------------------------------------------------------------------------- void CTFProjectile_Napalm::ApplyBlastDamage( CTFPlayer *pThrower, Vector vecOrigin ) { CTakeDamageInfo info; info.SetAttacker( pThrower ); info.SetInflictor( this ); info.SetWeapon( m_hFlameThrower ); info.SetDamage( 25.f ); info.SetDamageCustom( GetCustomDamageType() ); info.SetDamagePosition( vecOrigin ); info.SetDamageType( DMG_BURN ); CTFRadiusDamageInfo radiusinfo( &info, vecOrigin, 100.f, pThrower ); TFGameRules()->RadiusDamage( radiusinfo ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFProjectile_Napalm::InitialExplodeEffects( CTFPlayer *pThrower, const trace_t *pTrace ) { // Added Particle Vector vecOrigin = GetAbsOrigin(); // Particle CPVSFilter filter( vecOrigin ); TE_TFExplosion( filter, 0.0f, vecOrigin, pTrace->plane.normal, TF_WEAPON_FLAMETHROWER, kInvalidEHandleExplosion, -1, SPECIAL1, INVALID_STRING_INDEX ); return true; } //----------------------------------------------------------------------------- // Purpose: Direct hit //----------------------------------------------------------------------------- void CTFProjectile_Napalm::ExplodeEffectOnTarget( CTFPlayer *pThrower, CTFPlayer *pTarget, CBaseCombatCharacter *pBaseTarget ) { if ( pBaseTarget->GetTeamNumber() == GetTeamNumber() ) return; if ( pTarget ) { if ( pTarget->m_Shared.IsInvulnerable() ) return; if ( pTarget->m_Shared.InCond( TF_COND_PHASE ) || pTarget->m_Shared.InCond( TF_COND_PASSTIME_INTERCEPTION ) ) return; } Vector vecDir = pBaseTarget->WorldSpaceCenter() - GetAbsOrigin(); VectorNormalize( vecDir ); const trace_t *pTrace = &CBaseEntity::GetTouchTrace(); trace_t *pNewTrace = const_cast( pTrace ); CBaseEntity *pInflictor = GetLauncher(); CTakeDamageInfo info; info.SetAttacker( pThrower ); info.SetInflictor( this ); info.SetWeapon( pInflictor ); info.SetDamage( 50 ); info.SetDamageCustom( GetCustomDamageType() ); info.SetDamagePosition( GetAbsOrigin() ); info.SetDamageType( DMG_IGNITE ); // Hurt 'em. Vector dir; AngleVectors( GetAbsAngles(), &dir ); pBaseTarget->DispatchTraceAttack( info, dir, pNewTrace ); ApplyMultiDamage(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *CTFProjectile_Napalm::GetImpactEffect( void ) { return "burninggibs"; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Napalm::SetCustomPipebombModel( void ) { SetModel( "models/weapons/w_models/w_flaregun_shell.mdl" ); } #endif // GAME_DLL #ifdef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Napalm::OnDataChanged( DataUpdateType_t updateType ) { BaseClass::OnDataChanged( updateType ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *CTFProjectile_Napalm::GetTrailParticleName( void ) { if ( GetTeamNumber() == TF_TEAM_BLUE ) { return "flaming_arrow"; } else { return "flaming_arrow"; } } #endif // CLIENT_DLL #endif // STAGING_ONLY