//========= Copyright Valve Corporation, All rights reserved. ============// // // TF Nail // //============================================================================= #include "cbase.h" #include "tf_projectile_flare.h" #include "soundent.h" #include "tf_fx.h" #include "tf_player.h" #include "tf_weapon_flaregun.h" #include "tf_gamerules.h" //============================================================================= // // TF Flare Projectile functions (Server specific). // #define FLARE_MODEL "models/weapons/w_models/w_flaregun_shell.mdl" #define FLARE_GRAVITY 0.3f #define FLARE_SPEED 2000.0f #define FLARE_THINK_CONTEXT "CTFProjectile_FlareThink" LINK_ENTITY_TO_CLASS( tf_projectile_flare, CTFProjectile_Flare ); PRECACHE_WEAPON_REGISTER( tf_projectile_flare ); IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_Flare, DT_TFProjectile_Flare ) BEGIN_NETWORK_TABLE( CTFProjectile_Flare, DT_TFProjectile_Flare ) SendPropBool( SENDINFO( m_bCritical ) ), END_NETWORK_TABLE() BEGIN_DATADESC( CTFProjectile_Flare ) DEFINE_THINKFUNC( ImpactThink ), END_DATADESC() //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFProjectile_Flare::CTFProjectile_Flare() { m_bIsFromTaunt = false; m_bCritical = false; m_bImpact = false; m_flImpactTime = 0.0f; m_flNextSeekUpdate = 0.0f; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFProjectile_Flare::~CTFProjectile_Flare() { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFProjectile_Flare *CTFProjectile_Flare::Create( CBaseEntity *pLauncher, const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner, CBaseEntity *pScorer ) { CTFProjectile_Flare *pFlare = static_cast( CBaseEntity::CreateNoSpawn( "tf_projectile_flare", vecOrigin, vecAngles, pOwner ) ); if ( !pFlare ) return NULL; pFlare->SetLauncher( pLauncher ); // Initialize the owner. pFlare->SetOwnerEntity( pOwner ); // Set team. pFlare->ChangeTeam( pOwner->GetTeamNumber() ); // Save the scoring player. pFlare->SetScorer( pScorer ); // Spawn. DispatchSpawn( pFlare ); // Setup the initial velocity. Vector vecForward, vecRight, vecUp; AngleVectors( vecAngles, &vecForward, &vecRight, &vecUp ); float flLaunchSpeed = pFlare->GetProjectileSpeed(); Vector vecVelocity = vecForward * flLaunchSpeed; pFlare->SetAbsVelocity( vecVelocity ); pFlare->SetupInitialTransmittedGrenadeVelocity( vecVelocity ); float flGravity = FLARE_GRAVITY; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pLauncher, flGravity, mult_projectile_speed ); pFlare->SetGravity( flGravity ); // Setup the initial angles. QAngle angles; VectorAngles( vecVelocity, angles ); pFlare->SetAbsAngles( angles ); return pFlare; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Flare::Spawn() { SetModel( FLARE_MODEL ); BaseClass::Spawn(); float flHeatSeekPower = GetHeatSeekPower(); if ( flHeatSeekPower > 0.0f ) { SetMoveType( MOVETYPE_CUSTOM, MOVECOLLIDE_DEFAULT ); SetGravity( FLARE_GRAVITY ); } else { SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM ); SetGravity( FLARE_GRAVITY ); } // Set team. m_nSkin = ( GetTeamNumber() == TF_TEAM_BLUE ) ? 1 : 0; m_flCreationTime = gpGlobals->curtime; CTFPlayer *pScorer = ToTFPlayer( GetScorer() ); if ( pScorer && pScorer->IsTaunting() && m_flCreationTime >= pScorer->GetTauntAttackTime() ) { m_bIsFromTaunt = true; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Flare::Precache() { PrecacheModel( FLARE_MODEL ); PrecacheParticleSystem( "flaregun_trail_red" ); PrecacheParticleSystem( "flaregun_trail_crit_red" ); PrecacheParticleSystem( "flaregun_trail_blue" ); PrecacheParticleSystem( "flaregun_trail_crit_blue" ); PrecacheParticleSystem( "drg_manmelter_projectile" ); PrecacheParticleSystem( "scorchshot_trail_red" ); PrecacheParticleSystem( "scorchshot_trail_crit_red" ); PrecacheParticleSystem( "scorchshot_trail_blue" ); PrecacheParticleSystem( "scorchshot_trail_crit_blue" ); PrecacheParticleSystem( "Explosions_MA_FlyingEmbers" ); PrecacheParticleSystem( "pyrovision_flaregun_trail_blue" ); PrecacheParticleSystem( "pyrovision_flaregun_trail_red" ); PrecacheParticleSystem( "pyrovision_flaregun_trail_crit_blue" ); PrecacheParticleSystem( "pyrovision_flaregun_trail_crit_red" ); PrecacheParticleSystem( "pyrovision_scorchshot_trail_blue" ); PrecacheParticleSystem( "pyrovision_scorchshot_trail_red" ); PrecacheParticleSystem( "pyrovision_scorchshot_trail_crit_blue" ); PrecacheParticleSystem( "pyrovision_scorchshot_trail_crit_red" ); BaseClass::Precache(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Flare::SetScorer( CBaseEntity *pScorer ) { m_Scorer = pScorer; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CBasePlayer *CTFProjectile_Flare::GetScorer( void ) { return dynamic_cast( m_Scorer.Get() ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFProjectile_Flare::GetDamageType() { int iDmgType = BaseClass::GetDamageType(); if ( m_bCritical ) { iDmgType |= DMG_CRITICAL; } return iDmgType; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Flare::Explode( trace_t *pTrace, CBaseEntity *pOther ) { Vector vecOrigin = GetAbsOrigin(); CBaseEntity *pAttacker = GetOwnerEntity(); IScorer *pScorerInterface = dynamic_cast( pAttacker ); if ( pScorerInterface ) { pAttacker = pScorerInterface->GetScorer(); } CTFPlayer *pTFVictim = ToTFPlayer( pOther ); CTFFlareGun *pFlareGun = dynamic_cast< CTFFlareGun* >( GetLauncher() ); if ( pFlareGun ) { if ( pFlareGun->GetFlareGunType() == FLAREGUN_SCORCHSHOT ) { // When the scorch shot hits a player... if ( pTFVictim ) { // Now it only collides with the world SetCollisionGroup( COLLISION_GROUP_DEBRIS ); Vector vVelocity = GetAbsVelocity(); // Check if burning before damage bool bIsBurningVictim = pTFVictim->m_Shared.InCond( TF_COND_BURNING ); int iDamageType = GetDamageType(); // Prevent the normal push force cause we are going to add it iDamageType |= DMG_PREVENT_PHYSICS_FORCE; // Damage the player to push them back CTakeDamageInfo info( this, pAttacker, m_hLauncher, vec3_origin, vecOrigin, GetDamage(), iDamageType, m_bIsFromTaunt ? TF_DMG_CUSTOM_FLARE_PELLET : 0 ); pTFVictim->TakeDamage( info ); bool bIsEnemy = pAttacker && pTFVictim->GetTeamNumber() != pAttacker->GetTeamNumber(); // Quick Fix Uber and teammates are immune to the force if ( !pTFVictim->m_Shared.InCond( TF_COND_MEGAHEAL ) && bIsEnemy ) { Vector vecToTarget; vecToTarget = vVelocity; VectorNormalize( vecToTarget ); vecToTarget.z = 1.0; // apply airblast - Apply stun if they are effectively grounded so we can knock them up if ( !pTFVictim->m_Shared.InCond( TF_COND_KNOCKED_INTO_AIR ) ) { pTFVictim->m_Shared.StunPlayer( 0.5, 1.f, TF_STUN_MOVEMENT, ToTFPlayer( pAttacker ) ); } float flForce = bIsBurningVictim ? 400.0f : 100.0f; pTFVictim->ApplyAirBlastImpulse( vecToTarget * flForce ); } // It loses almost all of its speed and pops into the air vVelocity.x *= 0.07f; vVelocity.y *= 0.07f; vVelocity.z = 100.0f; SetAbsVelocity( vVelocity + RandomVector( -2.0f, 2.0f ) ); // Point the new direction and randomly flip QAngle angForward; VectorAngles( vVelocity, angForward ); SetAbsAngles( angForward ); QAngle angRotation = RandomAngle( 180.0f, 720.0f ); angRotation.x *= ( RandomInt( 0, 1 ) == 0 ? 1 : -1 ); angRotation.y *= ( RandomInt( 0, 1 ) == 0 ? 1 : -1 ); angRotation.z *= ( RandomInt( 0, 1 ) == 0 ? 1 : -1 ); SetLocalAngularVelocity( angRotation ); CPVSFilter filter( vecOrigin ); EmitSound( filter, entindex(), "Rubber.BulletImpact" ); // Save this entity as enemy, they will take 100% damage. if ( m_hEnemy.Get() == NULL ) { m_hEnemy = pTFVictim; } return; } } } // If we've already got an impact time, don't impact again. if ( m_flImpactTime > 0.0 ) return; // Save this entity as enemy, they will take 100% damage. if ( m_hEnemy.Get() == NULL ) { m_hEnemy = pOther; } if ( !pTFVictim ) { m_bImpact = true; } // Invisible. AddSolidFlags( FSOLID_NOT_SOLID ); m_takedamage = DAMAGE_NO; bool bDetonate = false; bool bNoRandomCrit = false; if ( pFlareGun ) { switch ( pFlareGun->GetFlareGunType() ) { case FLAREGUN_DETONATE: bDetonate = true; break; case FLAREGUN_GRORDBORT: bNoRandomCrit = true; break; case FLAREGUN_SCORCHSHOT: bDetonate = true; bNoRandomCrit = true; break; } } // Flares that hit a burning player crit, unless it's a detonate flare - they mini-crit if ( pTFVictim && pTFVictim->m_Shared.InCond( TF_COND_BURNING ) && !bDetonate && !bNoRandomCrit ) { m_bCritical = true; } CTakeDamageInfo info( this, pAttacker, m_hLauncher, vec3_origin, vecOrigin, GetDamage(), GetDamageType(), TF_DMG_CUSTOM_BURNING_FLARE ); pOther->TakeDamage( info ); // Remove the flare. if ( m_bImpact ) { SetMoveType( MOVETYPE_FLY ); SetAbsVelocity( vec3_origin ); m_vecImpactNormal = pTrace->plane.normal; m_flImpactTime = gpGlobals->curtime + 0.1f; // Stick into and object and fizzle a little while. SetContextThink( &CTFProjectile_Flare::ImpactThink, gpGlobals->curtime, FLARE_THINK_CONTEXT ); // Only do this for the Detonator if ( bDetonate ) { // Scorch Shot can still light others in this case Detonate( pFlareGun->GetFlareGunType() != FLAREGUN_SCORCHSHOT ); } } else { // Impact player sound. CPVSFilter filter( vecOrigin ); EmitSound( filter, pOther->entindex(), "TFPlayer.FlareImpact" ); SendDeathNotice(); UTIL_Remove( this ); } } //----------------------------------------------------------------------------- // Purpose: Custom explode for air burst flare //----------------------------------------------------------------------------- void CTFProjectile_Flare::Explode_Air( trace_t *pTrace, int bitsDamageType, bool bSelfOnly ) { // Invisible. AddSolidFlags( FSOLID_NOT_SOLID ); m_takedamage = DAMAGE_NO; // Play explosion sound and effect. Vector vecOrigin = GetAbsOrigin(); CPVSFilter filter( vecOrigin ); CBaseEntity *pAttacker = GetOwnerEntity(); int nDefID = -1; WeaponSound_t nSound = SPECIAL1; if ( pAttacker ) { CTFFlareGun *pFlareGun = dynamic_cast( ToTFPlayer( pAttacker )->GetActiveWeapon() ); if ( pFlareGun ) { CEconItemView *pItem = pFlareGun->GetAttributeContainer()->GetItem(); nDefID = pItem->GetItemDefIndex(); } // Damage. IScorer *pScorerInterface = dynamic_cast( pAttacker ); if ( pScorerInterface ) { pAttacker = pScorerInterface->GetScorer(); } float flRadius = bSelfOnly ? 0.f : GetRadius(); if ( bSelfOnly ) { bitsDamageType |= DMG_BLAST; nSound = SPECIAL2; } #if defined( _DEBUG ) && defined( STAGING_ONLY ) // Debug! ConVarRef tf_rocket_show_radius( "tf_rocket_show_radius" ); if ( tf_rocket_show_radius.GetBool() ) { DrawRadius( flRadius ); } #endif CTakeDamageInfo info( this, pAttacker, m_hLauncher, vec3_origin, vecOrigin, GetDamage(), bitsDamageType | DMG_HALF_FALLOFF, TF_DMG_CUSTOM_FLARE_EXPLOSION ); CTFRadiusDamageInfo radiusinfo( &info, vecOrigin, flRadius, NULL, TF_FLARE_RADIUS_FOR_FJS ); TFGameRules()->RadiusDamage( radiusinfo ); } const char *pszParticle = bSelfOnly ? "Explosions_MA_FlyingEmbers" : "ExplosionCore_MidAir_Flare"; TE_TFExplosion( filter, 0.0f, vecOrigin, pTrace->plane.normal, GetWeaponID(), entindex(), nDefID, nSound ); TE_TFParticleEffect( filter, 0.0f, pszParticle, vecOrigin, pTrace->plane.normal, vec3_angle ); CSoundEnt::InsertSound ( SOUND_COMBAT, vecOrigin, 1024, 3.0 ); SendDeathNotice(); UTIL_Remove( this ); } //----------------------------------------------------------------------------- // Purpose: Alt-fire air burst flare //----------------------------------------------------------------------------- void CTFProjectile_Flare::Detonate( bool bSelfOnly ) { trace_t tr; Vector vecSpot; SetThink( NULL ); vecSpot = GetAbsOrigin() + Vector ( 0 , 0 , 8 ); UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -32 ), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, & tr); Explode_Air( &tr, GetDamageType(), bSelfOnly ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFProjectile_Flare::GetRadius( void ) { float flRadius = TF_FLARE_DET_RADIUS; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_hLauncher, flRadius, mult_explosion_radius ); return flRadius; } //----------------------------------------------------------------------------- // Purpose: Let the flaregun know we're gone //----------------------------------------------------------------------------- void CTFProjectile_Flare::SendDeathNotice( void ) { CBaseEntity *pAttacker = GetOwnerEntity(); if ( !pAttacker ) return; CTFFlareGun *pFlareGun = dynamic_cast( ToTFPlayer( pAttacker )->GetActiveWeapon() ); if ( pFlareGun && pFlareGun->GetFlareGunType() == FLAREGUN_DETONATE ) { pFlareGun->DeathNotice( this ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFProjectile_Flare::ImpactThink( void ) { if ( gpGlobals->curtime > m_flImpactTime ) { // If we hit anything other than the player create an impact - effect, sound, etc. if ( m_hEnemy.Get() ) { Vector vecOrigin = GetAbsOrigin(); CPVSFilter filter( vecOrigin ); TE_TFExplosion( filter, 0.0f, vecOrigin, m_vecImpactNormal, GetWeaponID(), m_hEnemy.Get()->entindex() ); } SendDeathNotice(); UTIL_Remove( this ); SetContextThink( NULL, 0, FLARE_THINK_CONTEXT ); } else { SetContextThink( &CTFProjectile_Flare::ImpactThink, gpGlobals->curtime + 0.1f, FLARE_THINK_CONTEXT ); } } void CTFProjectile_Flare::PerformCustomPhysics( Vector *pNewPosition, Vector *pNewVelocity, QAngle *pNewAngles, QAngle *pNewAngVelocity ) { if ( m_flNextSeekUpdate < gpGlobals->curtime ) { CTFPlayer *pBestTarget = NULL; const float flMaxSeekDistanceSqr = ( 1024.0f * 1024.0f ); float flBestDistance = flMaxSeekDistanceSqr; // Loop through players and attempt to find a seek target int i; for ( i = 1; i <= gpGlobals->maxClients; i++ ) { CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) ); if ( !pPlayer ) continue; bool bBurning = pPlayer->m_Shared.InCond( TF_COND_BURNING ); if ( !bBurning || pPlayer->InSameTeam( this ) || ( pPlayer->m_Shared.GetDisguiseTeam() == GetTeamNumber() && !bBurning ) || ( pPlayer->m_Shared.IsStealthed() && !bBurning ) || pPlayer->GetTeamNumber() == TEAM_SPECTATOR || !pPlayer->IsAlive() ) { continue; } Vector vToTarget = pPlayer->WorldSpaceCenter() - GetAbsOrigin(); VectorNormalize( vToTarget ); Vector vForward = *pNewVelocity; VectorNormalize( vForward ); if ( vToTarget.Dot( vForward ) < -0.25f ) continue; float flDistanceSqr = pPlayer->WorldSpaceCenter().DistToSqr( GetAbsOrigin() ); if ( flBestDistance > flDistanceSqr ) { trace_t tr; UTIL_TraceLine( pPlayer->WorldSpaceCenter(), GetAbsOrigin(), MASK_SOLID_BRUSHONLY, pPlayer, COLLISION_GROUP_NONE, &tr ); if ( !tr.DidHit() || tr.m_pEnt == this ) { flBestDistance = flDistanceSqr; pBestTarget = pPlayer; } } } float flHeatSeekPower = GetHeatSeekPower(); QAngle angToTarget = *pNewAngles; if ( pBestTarget ) { Vector vToTarget = pBestTarget->WorldSpaceCenter() - GetAbsOrigin(); VectorAngles( vToTarget, angToTarget ); } const float flUpdatesPerSecond = 4.0f; if ( angToTarget != *pNewAngles ) { pNewAngVelocity->x = Approach( UTIL_AngleDiff( angToTarget.x, pNewAngles->x ) * flUpdatesPerSecond, pNewAngVelocity->x, flHeatSeekPower ); pNewAngVelocity->y = Approach( UTIL_AngleDiff( angToTarget.y, pNewAngles->y ) * flUpdatesPerSecond, pNewAngVelocity->y, flHeatSeekPower ); pNewAngVelocity->z = Approach( UTIL_AngleDiff( angToTarget.z, pNewAngles->z ) * flUpdatesPerSecond, pNewAngVelocity->z, flHeatSeekPower ); const float flMaxAngularVelocity = 360.0f; pNewAngVelocity->x = clamp( pNewAngVelocity->x, -flMaxAngularVelocity, flMaxAngularVelocity ); pNewAngVelocity->y = clamp( pNewAngVelocity->y, -flMaxAngularVelocity, flMaxAngularVelocity ); pNewAngVelocity->z = clamp( pNewAngVelocity->z, -flMaxAngularVelocity, flMaxAngularVelocity ); } m_flNextSeekUpdate = gpGlobals->curtime + ( 1.0f / flUpdatesPerSecond ); } *pNewAngles += *pNewAngVelocity * gpGlobals->frametime; Vector vForward; AngleVectors( *pNewAngles, &vForward ); *pNewVelocity = vForward * GetProjectileSpeed(); *pNewPosition += *pNewVelocity * gpGlobals->frametime; } //----------------------------------------------------------------------------- // Purpose: Flare was deflected. //----------------------------------------------------------------------------- void CTFProjectile_Flare::Deflected( CBaseEntity *pDeflectedBy, Vector &vecDir ) { CTFPlayer *pTFDeflector = ToTFPlayer( pDeflectedBy ); if ( !pTFDeflector ) return; ChangeTeam( pTFDeflector->GetTeamNumber() ); SetLauncher( pTFDeflector->GetActiveWeapon() ); CTFPlayer* pOldOwner = ToTFPlayer( GetOwnerEntity() ); SetOwnerEntity( pTFDeflector ); if ( pOldOwner ) { pOldOwner->SpeakConceptIfAllowed( MP_CONCEPT_DEFLECTED, "projectile:1,victim:1" ); } if ( pTFDeflector->m_Shared.IsCritBoosted() ) { SetCritical( true ); } CTFWeaponBase::SendObjectDeflectedEvent( pTFDeflector, pOldOwner, GetWeaponID(), this ); IncrementDeflected(); SetScorer( pTFDeflector ); } float CTFProjectile_Flare::GetProjectileSpeed( void ) const { float flLaunchSpeed = FLARE_SPEED; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_hLauncher, flLaunchSpeed, mult_projectile_speed ); return flLaunchSpeed; } float CTFProjectile_Flare::GetHeatSeekPower( void ) const { float flHeatSeekPower = 0.0; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_hLauncher, flHeatSeekPower, mod_projectile_heat_seek_power ); return flHeatSeekPower; }