summaryrefslogtreecommitdiff
path: root/game/server/tf/tf_projectile_arrow.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/tf/tf_projectile_arrow.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/server/tf/tf_projectile_arrow.cpp')
-rw-r--r--game/server/tf/tf_projectile_arrow.cpp1573
1 files changed, 1573 insertions, 0 deletions
diff --git a/game/server/tf/tf_projectile_arrow.cpp b/game/server/tf/tf_projectile_arrow.cpp
new file mode 100644
index 0000000..ff59dcb
--- /dev/null
+++ b/game/server/tf/tf_projectile_arrow.cpp
@@ -0,0 +1,1573 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// TF Arrow
+//
+//=============================================================================
+#include "cbase.h"
+#include "tf_projectile_arrow.h"
+#include "soundent.h"
+#include "tf_fx.h"
+#include "props.h"
+#include "baseobject_shared.h"
+#include "SpriteTrail.h"
+#include "IEffects.h"
+#include "te_effect_dispatch.h"
+#include "collisionutils.h"
+#include "bone_setup.h"
+#include "decals.h"
+#include "tf_player.h"
+#include "tf_gamestats.h"
+#include "tf_pumpkin_bomb.h"
+#include "tf_weapon_shovel.h"
+#include "player_vs_environment/tf_tank_boss.h"
+#include "halloween/halloween_base_boss.h"
+#include "halloween/merasmus/merasmus_trick_or_treat_prop.h"
+#include "tf_logic_robot_destruction.h"
+
+#include "tf_gamerules.h"
+#include "bot/tf_bot.h"
+#include "tf_weapon_medigun.h"
+#include "soundenvelope.h"
+
+
+//=============================================================================
+//
+// TF Arrow Projectile functions (Server specific).
+//
+#define ARROW_MODEL_GIB1 "models/weapons/w_models/w_arrow_gib1.mdl"
+#define ARROW_MODEL_GIB2 "models/weapons/w_models/w_arrow_gib2.mdl"
+
+#define ARROW_GRAVITY 0.3f
+
+#define ARROW_THINK_CONTEXT "CTFProjectile_ArrowThink"
+
+#define CLAW_TRAIL_RED "effects/repair_claw_trail_red.vmt"
+#define CLAW_TRAIL_BLU "effects/repair_claw_trail_blue.vmt"
+#define CLAW_GIB1 "models/weapons/w_models/w_repair_claw_gib1.mdl"
+#define CLAW_GIB2 "models/weapons/w_models/w_repair_claw_gib2.mdl"
+
+#define CLAW_REPAIR_EFFECT_BLU "repair_claw_heal_blue"
+#define CLAW_REPAIR_EFFECT_RED "repair_claw_heal_red"
+//-----------------------------------------------------------------------------
+LINK_ENTITY_TO_CLASS( tf_projectile_arrow, CTFProjectile_Arrow );
+PRECACHE_WEAPON_REGISTER( tf_projectile_arrow );
+
+IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_Arrow, DT_TFProjectile_Arrow )
+
+BEGIN_NETWORK_TABLE( CTFProjectile_Arrow, DT_TFProjectile_Arrow )
+ SendPropBool( SENDINFO( m_bArrowAlight ) ),
+ SendPropBool( SENDINFO( m_bCritical ) ),
+ SendPropInt( SENDINFO( m_iProjectileType ) ),
+END_NETWORK_TABLE()
+
+BEGIN_DATADESC( CTFProjectile_Arrow )
+DEFINE_THINKFUNC( ImpactThink ),
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+LINK_ENTITY_TO_CLASS( tf_projectile_healing_bolt, CTFProjectile_HealingBolt );
+PRECACHE_WEAPON_REGISTER( tf_projectile_healing_bolt );
+
+IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_HealingBolt, DT_TFProjectile_HealingBolt )
+
+BEGIN_NETWORK_TABLE( CTFProjectile_HealingBolt, DT_TFProjectile_HealingBolt )
+END_NETWORK_TABLE()
+
+BEGIN_DATADESC( CTFProjectile_HealingBolt )
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+LINK_ENTITY_TO_CLASS( tf_projectile_grapplinghook, CTFProjectile_GrapplingHook );
+PRECACHE_WEAPON_REGISTER( tf_projectile_grapplinghook );
+
+IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_GrapplingHook, DT_TFProjectile_GrapplingHook )
+
+BEGIN_NETWORK_TABLE( CTFProjectile_GrapplingHook, DT_TFProjectile_GrapplingHook )
+END_NETWORK_TABLE()
+
+BEGIN_DATADESC( CTFProjectile_GrapplingHook )
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Purpose: Helper to set a grappling hook target on all healers of this player
+//-----------------------------------------------------------------------------
+static void SetMedicsGrapplingHookTarget( CTFPlayer *pTFPlayer, CBaseEntity *pGrappleTarget )
+{
+ int i;
+ int iNumHealers = pTFPlayer->m_Shared.GetNumHealers();
+ for ( i = 0 ; i < iNumHealers ; i++ )
+ {
+ CTFPlayer *pMedic = ToTFPlayer( pTFPlayer->m_Shared.GetHealerByIndex( i ) );
+ // Only want medics who are directly healing us with their medigun, not e.g. AoE healers.
+ if ( pMedic && ToTFPlayer ( pMedic->MedicGetHealTarget() ) == pTFPlayer )
+ {
+ pMedic->SetGrapplingHookTarget( pGrappleTarget );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFProjectile_Arrow::CTFProjectile_Arrow()
+{
+ m_flImpactTime = 0.0f;
+ m_flTrailLife = 0.f;
+ m_pTrail = NULL;
+ m_bStruckEnemy = false;
+ m_bArrowAlight = false;
+ m_iDeflected = 0;
+ m_bCritical = false;
+ m_flInitTime = 0;
+ m_bPenetrate = false;
+ m_iProjectileType = TF_PROJECTILE_ARROW;
+ m_iWeaponId = TF_WEAPON_COMPOUND_BOW;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFProjectile_Arrow::~CTFProjectile_Arrow()
+{
+ m_HitEntities.Purge();
+}
+
+static const char* GetArrowEntityName( ProjectileType_t projectileType )
+{
+ switch ( projectileType )
+ {
+ case TF_PROJECTILE_HEALING_BOLT:
+ case TF_PROJECTILE_FESTIVE_HEALING_BOLT:
+#ifdef STAGING_ONLY
+ case TF_PROJECTILE_MILK_BOLT:
+#endif
+ return "tf_projectile_healing_bolt";
+ case TF_PROJECTILE_GRAPPLINGHOOK:
+ return "tf_projectile_grapplinghook";
+
+ default:
+ return "tf_projectile_arrow";
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFProjectile_Arrow *CTFProjectile_Arrow::Create( const Vector &vecOrigin, const QAngle &vecAngles, const float fSpeed, const float fGravity, ProjectileType_t projectileType, CBaseEntity *pOwner, CBaseEntity *pScorer )
+{
+ const char* pszArrowEntityName = GetArrowEntityName( projectileType );
+ CTFProjectile_Arrow *pArrow = static_cast<CTFProjectile_Arrow*>( CBaseEntity::Create( pszArrowEntityName, vecOrigin, vecAngles, pOwner ) );
+ if ( pArrow )
+ {
+ pArrow->InitArrow( vecAngles, fSpeed, fGravity, projectileType, pOwner, pScorer );
+ }
+
+ return pArrow;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Arrow::InitArrow( const QAngle &vecAngles, const float fSpeed, const float fGravity, ProjectileType_t projectileType, CBaseEntity *pOwner, CBaseEntity *pScorer )
+{
+ // Initialize the owner.
+ SetOwnerEntity( pOwner );
+
+ // Set team.
+ ChangeTeam( pOwner->GetTeamNumber() );
+
+ // must override projectile type before Spawn for proper model
+ m_iProjectileType = projectileType;
+
+ // Spawn.
+ Spawn();
+
+ SetGravity( fGravity );
+
+ SetCritical( true );
+
+ // Setup the initial velocity.
+ Vector vecForward, vecRight, vecUp;
+ AngleVectors( vecAngles, &vecForward, &vecRight, &vecUp );
+
+ Vector vecVelocity = vecForward * fSpeed;
+
+ SetAbsVelocity( vecVelocity );
+ SetupInitialTransmittedGrenadeVelocity( vecVelocity );
+
+ // Setup the initial angles.
+ QAngle angles;
+ VectorAngles( vecVelocity, angles );
+ SetAbsAngles( angles );
+
+ // Save the scoring player.
+ SetScorer( pScorer );
+
+ // Create a trail.
+ CreateTrail();
+
+ // Add ourselves to the hit entities list so we dont shoot ourselves
+ m_HitEntities.AddToTail( pOwner->entindex() );
+
+ m_flInitTime = gpGlobals->curtime;
+
+#ifdef STAGING_ONLY
+ if ( m_iProjectileType == TF_PROJECTILE_SNIPERBULLET )
+ {
+ CTFPlayer* pTFOwner = ToTFPlayer( pOwner );
+ m_bFiredWhileZoomed = ( pTFOwner && pTFOwner->m_Shared.InCond( TF_COND_ZOOMED ) );
+ }
+ else
+#endif // STAGING_ONLY
+ {
+ m_bFiredWhileZoomed = false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Arrow::Spawn()
+{
+ if ( m_iProjectileType == TF_PROJECTILE_BUILDING_REPAIR_BOLT )
+ {
+ SetModel( g_pszArrowModels[MODEL_ARROW_BUILDING_REPAIR] );
+ m_iWeaponId = TF_WEAPON_SHOTGUN_BUILDING_RESCUE;
+ }
+ else if ( m_iProjectileType == TF_PROJECTILE_FESTIVE_ARROW )
+ {
+ SetModel( g_pszArrowModels[MODEL_FESTIVE_ARROW_REGULAR] );
+ }
+ else if ( m_iProjectileType == TF_PROJECTILE_HEALING_BOLT
+#ifdef STAGING_ONLY
+ || m_iProjectileType == TF_PROJECTILE_MILK_BOLT
+#endif
+ ) {
+ SetModel( g_pszArrowModels[MODEL_SYRINGE] );
+ SetModelScale( 3.0f );
+ }
+ else if ( m_iProjectileType == TF_PROJECTILE_FESTIVE_HEALING_BOLT )
+ {
+ SetModel( g_pszArrowModels[MODEL_FESTIVE_HEALING_BOLT] );
+ SetModelScale( 3.0f );
+ }
+#ifdef STAGING_ONLY
+ else if ( m_iProjectileType == TF_PROJECTILE_SNIPERBULLET )
+ {
+ SetModel( g_pszArrowModels[MODEL_SYRINGE] );
+ //SetModelScale( 3.0f );
+ }
+#endif // STAGING_ONLY
+ else if ( m_iProjectileType == TF_PROJECTILE_GRAPPLINGHOOK )
+ {
+ SetModel( g_pszArrowModels[MODEL_GRAPPLINGHOOK] );
+ }
+ else
+ {
+ SetModel( g_pszArrowModels[MODEL_ARROW_REGULAR] );
+ }
+
+ SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
+ UTIL_SetSize( this, Vector( -1.0f, -1.0f, -1.0f ), Vector( 1.0f, 1.0f, 1.0f ) );
+ SetSolid( SOLID_BBOX );
+
+ SetCollisionGroup( TFCOLLISION_GROUP_ROCKETS );
+ AddEffects( EF_NOSHADOW );
+ AddFlag( FL_GRENADE );
+
+ SetTouch( &CTFProjectile_Arrow::ArrowTouch );
+
+ // Set team.
+ m_nSkin = GetArrowSkin();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Arrow::Precache()
+{
+ int arrow_model = PrecacheModel( g_pszArrowModels[MODEL_ARROW_REGULAR] );
+ int claw_model = PrecacheModel( g_pszArrowModels[MODEL_ARROW_BUILDING_REPAIR] );
+ int festive_arrow_model = PrecacheModel( g_pszArrowModels[MODEL_FESTIVE_ARROW_REGULAR] );
+ PrecacheModel( g_pszArrowModels[MODEL_FESTIVE_HEALING_BOLT] );
+
+ PrecacheGibsForModel( arrow_model );
+ PrecacheGibsForModel( claw_model );
+ PrecacheGibsForModel( festive_arrow_model );
+ //PrecacheGibsForModel( festive_healing_arrow_model );
+ PrecacheModel( "effects/arrowtrail_red.vmt" );
+ PrecacheModel( "effects/arrowtrail_blu.vmt" );
+ PrecacheModel( "effects/healingtrail_red.vmt" );
+ PrecacheModel( "effects/healingtrail_blu.vmt" );
+ PrecacheModel( CLAW_TRAIL_RED );
+ PrecacheModel( CLAW_TRAIL_BLU );
+ PrecacheParticleSystem( CLAW_REPAIR_EFFECT_BLU );
+ PrecacheParticleSystem( CLAW_REPAIR_EFFECT_RED );
+ PrecacheScriptSound( "Weapon_Arrow.ImpactFlesh" );
+ PrecacheScriptSound( "Weapon_Arrow.ImpactMetal" );
+ PrecacheScriptSound( "Weapon_Arrow.ImpactWood" );
+ PrecacheScriptSound( "Weapon_Arrow.ImpactConcrete" );
+ PrecacheScriptSound( "Weapon_Arrow.Nearmiss" );
+ PrecacheScriptSound( "Weapon_Arrow.ImpactFleshCrossbowHeal" );
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Arrow::SetScorer( CBaseEntity *pScorer )
+{
+ m_Scorer = pScorer;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBasePlayer *CTFProjectile_Arrow::GetScorer( void )
+{
+ return dynamic_cast<CBasePlayer *>( m_Scorer.Get() );
+}
+
+//-----------------------------------------------------------------------------
+bool CTFProjectile_Arrow::CanHeadshot()
+{
+ CBaseEntity *pOwner = GetScorer();
+ if ( pOwner == NULL )
+ return false;
+
+ if ( m_iProjectileType == TF_PROJECTILE_BUILDING_REPAIR_BOLT
+ || m_iProjectileType == TF_PROJECTILE_HEALING_BOLT
+ || m_iProjectileType == TF_PROJECTILE_FESTIVE_HEALING_BOLT
+#ifdef STAGING_ONLY
+ || m_iProjectileType == TF_PROJECTILE_MILK_BOLT
+#endif
+ ) {
+ return false;
+ }
+
+#ifdef STAGING_ONLY
+ if ( m_iProjectileType == TF_PROJECTILE_SNIPERBULLET )
+ {
+ return m_bFiredWhileZoomed;
+ }
+#endif // STAGING_ONLY
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Healing bolt damage.
+//-----------------------------------------------------------------------------
+float CTFProjectile_Arrow::GetDamage()
+{
+ if ( m_iProjectileType == TF_PROJECTILE_HEALING_BOLT
+ || m_iProjectileType == TF_PROJECTILE_FESTIVE_HEALING_BOLT
+#ifdef STAGING_ONLY
+ || m_iProjectileType == TF_PROJECTILE_MILK_BOLT
+#endif
+ ) {
+ float lifeTimeScale = RemapValClamped( gpGlobals->curtime - m_flInitTime, 0.0f, 0.6f, 0.5f, 1.0f );
+ return m_flDamage * lifeTimeScale;
+ }
+ return BaseClass::GetDamage();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Moves the arrow to a particular bbox.
+//-----------------------------------------------------------------------------
+bool CTFProjectile_Arrow::PositionArrowOnBone( mstudiobbox_t *pBox, CBaseAnimating *pOtherAnim )
+{
+ CStudioHdr *pStudioHdr = pOtherAnim->GetModelPtr();
+ if ( !pStudioHdr )
+ return false;
+
+ mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( pOtherAnim->GetHitboxSet() );
+ if ( !set )
+ return false;
+ if ( !set->numhitboxes ) // Target must have hit boxes.
+ return false;
+
+ if ( pBox->bone < 0 || pBox->bone >= pStudioHdr->numbones() ) // Bone index must be valid.
+ return false;
+
+ CBoneCache *pCache = pOtherAnim->GetBoneCache();
+ if ( !pCache )
+ return false;
+
+ matrix3x4_t *bone_matrix = pCache->GetCachedBone( pBox->bone );
+ if ( !bone_matrix )
+ return false;
+
+ Vector vecBoxAbsMins, vecBoxAbsMaxs;
+ TransformAABB( *bone_matrix, pBox->bbmin, pBox->bbmax, vecBoxAbsMins, vecBoxAbsMaxs );
+
+ // Adjust the arrow so it isn't exactly in the center of the box.
+ Vector position;
+ Vector vecDelta = vecBoxAbsMaxs - vecBoxAbsMins;
+ float frand = (float) rand() / VALVE_RAND_MAX;
+ position.x = vecBoxAbsMins.x + vecDelta.x*0.6f - vecDelta.x*frand*0.2f;
+ frand = (float) rand() / VALVE_RAND_MAX;
+ position.y = vecBoxAbsMins.y + vecDelta.y*0.6f - vecDelta.y*frand*0.2f;
+ frand = (float) rand() / VALVE_RAND_MAX;
+ position.z = vecBoxAbsMins.z + vecDelta.z*0.6f - vecDelta.z*frand*0.2f;
+ SetAbsOrigin( position );
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: This was written after PositionArrowOnBone, but the two might be mergable?
+//-----------------------------------------------------------------------------
+void CTFProjectile_Arrow::GetBoneAttachmentInfo( mstudiobbox_t *pBox, CBaseAnimating *pOtherAnim, Vector &bonePosition, QAngle &boneAngles, int &boneIndexAttached, int &physicsBoneIndex )
+{
+ // Find a bone to stick to.
+ matrix3x4_t arrowWorldSpace;
+ MatrixCopy( EntityToWorldTransform(), arrowWorldSpace );
+
+ // Get the bone info so we can follow the bone.
+ boneIndexAttached = pBox->bone;
+ physicsBoneIndex = pOtherAnim->GetPhysicsBone( boneIndexAttached );
+ matrix3x4_t boneToWorld;
+ pOtherAnim->GetBoneTransform( boneIndexAttached, boneToWorld );
+
+ Vector attachedBonePos;
+ QAngle attachedBoneAngles;
+ pOtherAnim->GetBonePosition( boneIndexAttached, attachedBonePos, attachedBoneAngles );
+
+ // Transform my current position/orientation into the hit bone's space.
+ matrix3x4_t worldToBone, localMatrix;
+ MatrixInvert( boneToWorld, worldToBone );
+ ConcatTransforms( worldToBone, arrowWorldSpace, localMatrix );
+ MatrixAngles( localMatrix, boneAngles, bonePosition );
+}
+
+//-----------------------------------------------------------------------------
+int CTFProjectile_Arrow::GetProjectileType ( void ) const
+{
+ return m_iProjectileType;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFProjectile_Arrow::StrikeTarget( mstudiobbox_t *pBox, CBaseEntity *pOther )
+{
+ if ( !pOther )
+ return false;
+
+ // Different path for arrows that heal friendly buildings.
+ if ( pOther->IsBaseObject() )
+ {
+ if ( OnArrowImpactObject( pOther ) )
+ {
+ return false;
+ }
+ }
+
+ // Block and break on invulnerable players
+ CTFPlayer *pTFPlayerOther = ToTFPlayer( pOther );
+ if ( pTFPlayerOther && pTFPlayerOther->m_Shared.IsInvulnerable() )
+ return false;
+
+ CBaseAnimating *pOtherAnim = dynamic_cast< CBaseAnimating* >(pOther);
+ if ( !pOtherAnim )
+ return false;
+
+ bool bBreakArrow = IsBreakable() && ( ( dynamic_cast< CTFTankBoss* >( pOther ) != NULL ) || ( dynamic_cast< CHalloweenBaseBoss* >( pOther ) != NULL ) );
+
+ // Position the arrow so its on the bone, within a reasonable region defined by the bbox.
+ if ( !m_bPenetrate && !bBreakArrow )
+ {
+ if ( !PositionArrowOnBone( pBox, pOtherAnim ) )
+ {
+ return false;
+ }
+ }
+
+ //
+ const Vector &vecOrigin = GetAbsOrigin();
+ Vector vecVelocity = GetAbsVelocity();
+ int nDamageCustom = 0;
+ bool bApplyEffect = true;
+ int nDamageType = GetDamageType();
+
+ // Are we a headshot?
+ bool bHeadshot = false;
+ if ( pBox->group == HITGROUP_HEAD && CanHeadshot() )
+ {
+ bHeadshot = true;
+ }
+
+ // Damage the entity we struck.
+ CBaseEntity *pAttacker = GetScorer();
+ if ( !pAttacker )
+ {
+ // likely not launched by a player
+ pAttacker = GetOwnerEntity();
+ }
+
+ if ( pAttacker )
+ {
+ // Check if we have the penetrate attribute. We don't want
+ // to strike the same target multiple times.
+ if ( m_bPenetrate )
+ {
+ // Don't strike the same target again
+ if ( m_HitEntities.Find( pOther->entindex() ) != m_HitEntities.InvalidIndex() )
+ {
+ bApplyEffect = false;
+ }
+ else
+ {
+ m_HitEntities.AddToTail( pOther->entindex() );
+ }
+ }
+
+ if ( !InSameTeam( pOther ) )
+ {
+ IScorer *pScorerInterface = dynamic_cast<IScorer*>( pAttacker );
+ if ( pScorerInterface )
+ {
+ pAttacker = pScorerInterface->GetScorer();
+ }
+
+ if ( m_bArrowAlight )
+ {
+ nDamageType |= DMG_IGNITE;
+ nDamageCustom = TF_DMG_CUSTOM_FLYINGBURN;
+ }
+
+ if ( bHeadshot )
+ {
+ nDamageType |= DMG_CRITICAL;
+ nDamageCustom = TF_DMG_CUSTOM_HEADSHOT;
+ }
+
+ if ( m_bCritical )
+ {
+ nDamageType |= DMG_CRITICAL;
+ }
+
+#ifdef GAME_DLL
+ if ( TFGameRules()->IsPVEModeControlled( pAttacker ) )
+ {
+ // scenario bots cant crit (unless they always do)
+ CTFBot *bot = ToTFBot( pAttacker );
+ if ( !bot || !bot->HasAttribute( CTFBot::ALWAYS_CRIT ) )
+ {
+ nDamageType &= ~DMG_CRITICAL;
+ }
+ }
+#endif
+ // Damage
+ if ( bApplyEffect )
+ {
+ // Apply Milk First so we can get health from this
+ if ( m_bApplyMilkOnHit && pOther->IsPlayer() )
+ {
+ CTFPlayer *pVictim = ToTFPlayer( pOther );
+ if ( pVictim && pVictim->m_Shared.CanBeDebuffed() && pVictim->CanGetWet() )
+ {
+ // duration is based on damage
+ float flDuration = RemapValClamped( GetDamage(), 25.0f, 75.0f, 6.0f, 10.0f );
+ pVictim->m_Shared.AddCond( TF_COND_MAD_MILK, flDuration, pAttacker );
+ pVictim->m_Shared.SetPeeAttacker( ToTFPlayer( pAttacker ) );
+ pVictim->SpeakConceptIfAllowed( MP_CONCEPT_JARATE_HIT );
+ }
+ }
+
+ CTakeDamageInfo info( this, pAttacker, m_hLauncher, vecVelocity, vecOrigin, GetDamage(), nDamageType, nDamageCustom );
+ pOther->TakeDamage( info );
+
+ // Play an impact sound.
+ ImpactSound( "Weapon_Arrow.ImpactFlesh", true );
+ }
+ }
+ else if ( pOther->IsPlayer() ) // Hit a team-mate.
+ {
+ // Heal
+ if ( bApplyEffect )
+ {
+ ImpactTeamPlayer( dynamic_cast<CTFPlayer*>( pOther ) );
+ }
+ }
+ }
+
+ if ( !m_bPenetrate && !bBreakArrow )
+ {
+ OnArrowImpact( pBox, pOther, pAttacker );
+ }
+
+ // Perform a blood mesh decal trace.
+ trace_t tr;
+ Vector start = vecOrigin - vecVelocity * gpGlobals->frametime;
+ Vector end = vecOrigin + vecVelocity * gpGlobals->frametime;
+ CTraceFilterCollisionArrows filter( this, GetOwnerEntity() );
+ UTIL_TraceLine( start, end, CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, &filter, &tr );
+ UTIL_ImpactTrace( &tr, 0 );
+
+ // Break it?
+ if ( bBreakArrow )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Arrow::OnArrowImpact( mstudiobbox_t *pBox, CBaseEntity *pOther, CBaseEntity *pAttacker )
+{
+ CBaseAnimating *pOtherAnim = dynamic_cast< CBaseAnimating* >(pOther);
+ if ( !pOtherAnim )
+ return;
+
+ const Vector &vecOrigin = GetAbsOrigin();
+ Vector vecVelocity = GetAbsVelocity();
+
+ Vector bonePosition = vec3_origin;
+ QAngle boneAngles = QAngle(0,0,0);
+ int boneIndexAttached = -1;
+ int physicsBoneIndex = -1;
+ GetBoneAttachmentInfo( pBox, pOtherAnim, bonePosition, boneAngles, boneIndexAttached, physicsBoneIndex );
+ bool bSendImpactMessage = true;
+
+ // Did we kill the target?
+ if ( !pOther->IsAlive() && pOther->IsPlayer() )
+ {
+ CTFPlayer *pTFPlayerOther = dynamic_cast<CTFPlayer*>(pOther);
+ if ( pTFPlayerOther && pTFPlayerOther->m_hRagdoll )
+ {
+ VectorNormalize( vecVelocity );
+ if ( CheckRagdollPinned( vecOrigin, vecVelocity, boneIndexAttached, physicsBoneIndex, pTFPlayerOther->m_hRagdoll, pBox->group, pTFPlayerOther->entindex() ) )
+ {
+ pTFPlayerOther->StopRagdollDeathAnim();
+ bSendImpactMessage = false;
+ }
+ }
+ }
+
+ // Notify relevant clients of an arrow impact.
+ if ( bSendImpactMessage )
+ {
+ IGameEvent * event = gameeventmanager->CreateEvent( "arrow_impact" );
+ if ( event )
+ {
+ event->SetInt( "attachedEntity", pOther->entindex() );
+ event->SetInt( "shooter", pAttacker ? pAttacker->entindex() : 0 );
+ event->SetInt( "attachedEntity", pOther->entindex() );
+ event->SetInt( "boneIndexAttached", boneIndexAttached );
+ event->SetFloat( "bonePositionX", bonePosition.x );
+ event->SetFloat( "bonePositionY", bonePosition.y );
+ event->SetFloat( "bonePositionZ", bonePosition.z );
+ event->SetFloat( "boneAnglesX", boneAngles.x );
+ event->SetFloat( "boneAnglesY", boneAngles.y );
+ event->SetFloat( "boneAnglesZ", boneAngles.z );
+ event->SetInt( "projectileType", GetProjectileType() );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+
+ FadeOut( 3.0 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFProjectile_Arrow::OnArrowImpactObject( CBaseEntity *pOther )
+{
+ if ( InSameTeam( pOther ) )
+ {
+ BuildingHealingArrow( pOther );
+ }
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Arrow::ImpactThink( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+void CTFProjectile_Arrow::BuildingHealingArrow( CBaseEntity *pOther )
+{
+ // This arrow impacted a building
+ // If its a building on our team, heal it
+ if ( !pOther->IsBaseObject() )
+ return;
+
+ CBaseEntity *pAttacker = GetScorer();
+ if ( pAttacker == NULL )
+ return;
+
+ // if not on our team, forget about it
+ if ( GetTeamNumber() != pOther->GetTeamNumber() )
+ return;
+
+ int iArrowsHealBuildings = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pAttacker, iArrowsHealBuildings, arrow_heals_buildings );
+ if ( iArrowsHealBuildings == 0 )
+ return;
+
+ CBaseObject *pBuilding = dynamic_cast< CBaseObject * >( pOther );
+ if ( !pBuilding || !pBuilding->CanBeRepaired() || pBuilding->HasSapper() || pBuilding->IsPlasmaDisabled() || pBuilding->IsBuilding() || pBuilding->IsPlacing() )
+ return;
+
+ // if building is sheilded, reduce health gain
+ if ( pBuilding->GetShieldLevel() == SHIELD_NORMAL )
+ {
+ iArrowsHealBuildings *= SHIELD_NORMAL_VALUE;
+ }
+
+ float flNewHealth = MIN( pBuilding->GetMaxHealth(), (int)pBuilding->GetHealth() + iArrowsHealBuildings );
+ int iHealthAdded = (int)(flNewHealth - pBuilding->GetHealth());
+ if ( iHealthAdded > 0 )
+ {
+ pBuilding->SetHealth( flNewHealth );
+
+ IGameEvent * event = gameeventmanager->CreateEvent( "building_healed" );
+ if ( event )
+ {
+ // HLTV event priority, not transmitted
+ event->SetInt( "priority", 1 );
+
+ // Healed by another player.
+ event->SetInt( "building", pBuilding->entindex() );
+ event->SetInt( "healer", pAttacker->entindex() );
+ event->SetInt( "amount", iHealthAdded );
+ gameeventmanager->FireEvent( event );
+ }
+
+ const char *pParticleName = GetTeamNumber() == TF_TEAM_BLUE ? CLAW_REPAIR_EFFECT_BLU : CLAW_REPAIR_EFFECT_RED;
+ CPVSFilter filter( GetAbsOrigin() );
+ TE_TFParticleEffect( filter, 0.0, pParticleName, GetAbsOrigin(), vec3_angle );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFProjectile_Arrow::GetArrowSkin() const
+{
+ int nTeam = GetTeamNumber();
+ if ( GetOwnerEntity() && GetOwnerEntity()->IsPlayer() )
+ {
+ CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
+ if ( pOwner && pOwner->IsPlayerClass( TF_CLASS_SPY ) && pOwner->m_Shared.InCond( TF_COND_DISGUISED ) )
+ {
+ nTeam = pOwner->m_Shared.GetDisguiseTeam();
+ }
+ }
+ return ( nTeam == TF_TEAM_BLUE ) ? 1 : 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Arrow::OnArrowMissAllPlayers()
+{
+ CTFPlayer* pOwner = ToTFPlayer( GetOwnerEntity() );
+ if( pOwner && pOwner->IsPlayerClass( TF_CLASS_SNIPER ) )
+ {
+ EconEntity_OnOwnerKillEaterEventNoPartner( assert_cast<CEconEntity *>( m_hLauncher.Get() ), pOwner, kKillEaterEvent_NEGATIVE_SniperShotsMissed );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Arrow::ArrowTouch( CBaseEntity *pOther )
+{
+ // Safety net hack:
+ // We routinely introduce new entity types, and arrows
+ // are repeat-offenders at not getting along with them.
+ // If enough time goes by, just remove the arrow.
+ float flAliveTime = gpGlobals->curtime - m_flInitTime;
+ if ( flAliveTime >= 10.f )
+ {
+ Warning( "Arrow alive for %f3.2\n seconds", flAliveTime );
+ UTIL_Remove( this );
+ }
+
+ if ( m_bStruckEnemy || (GetMoveType() == MOVETYPE_NONE) )
+ return;
+
+ if ( !pOther )
+ return;
+
+ bool bShield = pOther->IsCombatItem() && !InSameTeam( pOther );
+ CTFPumpkinBomb *pPumpkinBomb = dynamic_cast< CTFPumpkinBomb * >( pOther );
+
+ if ( pOther->IsSolidFlagSet( FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS ) && !pPumpkinBomb && !bShield )
+ return;
+
+ // test against combat characters, which include players, engineer buildings, and NPCs
+ CBaseCombatCharacter *pOtherCombatCharacter = dynamic_cast< CBaseCombatCharacter * >( pOther );
+
+ if ( !pOtherCombatCharacter )
+ {
+ // It might be a track train with boss parented
+ pOtherCombatCharacter = dynamic_cast< CBaseCombatCharacter * >( pOther->FirstMoveChild() );
+ if ( pOtherCombatCharacter )
+ {
+ pOther = pOtherCombatCharacter;
+ }
+ }
+
+ CTFMerasmusTrickOrTreatProp *pMerasmusProp = dynamic_cast< CTFMerasmusTrickOrTreatProp* >( pOther );
+ CTFRobotDestruction_Robot *pRobot = dynamic_cast< CTFRobotDestruction_Robot* >( pOther );
+ if ( pOther->IsWorld() || ( !pOtherCombatCharacter && !pPumpkinBomb && !pMerasmusProp && !bShield && !pRobot ) )
+ {
+ // Check to see if we struck the skybox.
+ CheckSkyboxImpact( pOther );
+
+ // If we've only got 1 entity in the hit list (the attacker by default) and we've not been deflected
+ // then we can consider this arrow to have completely missed all players.
+ if( m_HitEntities.Count() == 1 && GetDeflected() == 0 )
+ {
+ OnArrowMissAllPlayers();
+ }
+
+ return;
+ }
+
+ CBaseAnimating *pAnimOther = dynamic_cast<CBaseAnimating*>(pOther);
+ CStudioHdr *pStudioHdr = NULL;
+ mstudiohitboxset_t *set = NULL;
+ if ( pAnimOther )
+ {
+ pStudioHdr = pAnimOther->GetModelPtr();
+ if ( pStudioHdr )
+ {
+ set = pStudioHdr->pHitboxSet( pAnimOther->GetHitboxSet() );
+ }
+ }
+
+ if ( !pAnimOther || !pStudioHdr || !set )
+ {
+ // Whatever we hit doesn't have hitboxes. Ignore it.
+ UTIL_Remove( this );
+ return;
+ }
+
+ // We struck the collision box of a player or a buildable object.
+ // Trace forward to see if we struck a hitbox.
+ CTraceFilterCollisionArrows filter( this, GetOwnerEntity() );
+ Vector start = GetAbsOrigin();
+ Vector vel = GetAbsVelocity();
+ trace_t tr;
+ UTIL_TraceLine( start, start + vel * gpGlobals->frametime, CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, &filter, &tr );
+
+ // If we hit a hitbox, stop tracing.
+ mstudiobbox_t *closest_box = NULL;
+ if ( tr.m_pEnt && tr.m_pEnt->GetTeamNumber() != GetTeamNumber() )
+ {
+ // This means the arrow was true and was flying directly at a hitbox on the target.
+ // We'll attach to that hitbox.
+ closest_box = set->pHitbox( tr.hitbox );
+ }
+
+ if ( !closest_box )
+ {
+ // Locate the hitbox closest to our point of impact on the collision box.
+ Vector position, start, forward;
+ QAngle angles;
+ float closest_dist = 99999;
+
+ // Intense, but extremely accurate:
+ AngleVectors( GetAbsAngles(), &forward );
+ start = GetAbsOrigin() + forward*16;
+ for ( int i = 0; i < set->numhitboxes; i++ )
+ {
+ mstudiobbox_t *pbox = set->pHitbox( i );
+
+ pAnimOther->GetBonePosition( pbox->bone, position, angles );
+
+ Ray_t ray;
+ ray.Init( start, position );
+ trace_t tr;
+ IntersectRayWithBox( ray, position+pbox->bbmin, position+pbox->bbmax, 0.f, &tr );
+ float dist = tr.endpos.DistTo( start );
+
+ if ( dist < closest_dist )
+ {
+ closest_dist = dist;
+ closest_box = pbox;
+ }
+ }
+ }
+
+ if ( closest_box )
+ {
+ // See if we're supposed to stick in the target.
+ bool bStrike = StrikeTarget( closest_box, pOther );
+ if ( bStrike && !m_bPenetrate)
+ {
+ // If we're here, it means StrikeTarget() called FadeOut( 3.0 )
+ SetAbsOrigin( start );
+ }
+
+ if ( !bStrike || bShield )
+ {
+ BreakArrow();
+ }
+
+ // Slightly confusing. If we're here, the arrow stopped at the
+ // target and will fade or break. Setting this prevents the
+ // touch code from re-running during the delay.
+ if ( !m_bPenetrate )
+ {
+ m_bStruckEnemy = true;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Arrow::CheckSkyboxImpact( CBaseEntity *pOther )
+{
+ trace_t tr;
+ Vector velDir = GetAbsVelocity();
+ VectorNormalize( velDir );
+ Vector vecSpot = GetAbsOrigin() - velDir * 32;
+ UTIL_TraceLine( vecSpot, vecSpot + velDir * 64, MASK_SOLID, this, COLLISION_GROUP_DEBRIS, &tr );
+ if ( tr.fraction < 1.0 && tr.surface.flags & SURF_SKY )
+ {
+ // We hit the skybox, go away soon.
+ FadeOut( 3.f );
+ return;
+ }
+
+ if ( !pOther->IsWorld() )
+ {
+ BreakArrow();
+ }
+ else
+ {
+ CEffectData data;
+ data.m_vOrigin = tr.endpos;
+ data.m_vNormal = velDir;
+ data.m_nEntIndex = 0;/*tr.fraction != 1.0f;*/
+ data.m_nAttachmentIndex = 0;
+ data.m_nMaterial = 0;
+ data.m_fFlags = GetProjectileType();
+ data.m_nColor = GetArrowSkin();
+
+ DispatchEffect( "TFBoltImpact", data );
+
+ FadeOut( 3.f );
+
+ // Play an impact sound.
+ const char* pszSoundName = "Weapon_Arrow.ImpactMetal";
+ surfacedata_t *psurf = physprops->GetSurfaceData( tr.surface.surfaceProps );
+ if ( psurf )
+ {
+ switch ( psurf->game.material )
+ {
+ case CHAR_TEX_GRATE:
+ case CHAR_TEX_METAL:
+ pszSoundName = "Weapon_Arrow.ImpactMetal";
+ break;
+
+ case CHAR_TEX_CONCRETE:
+ pszSoundName = "Weapon_Arrow.ImpactConcrete";
+ break;
+
+ case CHAR_TEX_WOOD:
+ pszSoundName = "Weapon_Arrow.ImpactWood";
+ break;
+ }
+ }
+ ImpactSound( pszSoundName );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Plays an impact sound. Louder for the attacker.
+//-----------------------------------------------------------------------------
+void CTFProjectile_Arrow::ImpactSound( const char *pszSoundName, bool bLoudForAttacker )
+{
+ CTFPlayer *pAttacker = ToTFPlayer( GetScorer() );
+ if ( !pAttacker )
+ return;
+
+ if ( bLoudForAttacker )
+ {
+ float soundlen = 0;
+ EmitSound_t params;
+ params.m_flSoundTime = 0;
+ params.m_pSoundName = pszSoundName;
+ params.m_pflSoundDuration = &soundlen;
+ CPASFilter filter( GetAbsOrigin() );
+ filter.RemoveRecipient( ToTFPlayer(pAttacker) );
+ EmitSound( filter, entindex(), params );
+
+ CSingleUserRecipientFilter attackerFilter( ToTFPlayer(pAttacker) );
+ EmitSound( attackerFilter, pAttacker->entindex(), params );
+ }
+ else
+ {
+ EmitSound( pszSoundName );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Arrow::BreakArrow()
+{
+ FadeOut( 3.f );
+ CPVSFilter filter( GetAbsOrigin() );
+ UserMessageBegin( filter, "BreakModel" );
+ WRITE_SHORT( GetModelIndex() );
+ WRITE_VEC3COORD( GetAbsOrigin() );
+ WRITE_ANGLES( GetAbsAngles() );
+ WRITE_SHORT( m_nSkin );
+ MessageEnd();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFProjectile_Arrow::CheckRagdollPinned( const Vector &start, const Vector &vel, int boneIndexAttached, int physicsBoneIndex, CBaseEntity *pOther, int iHitGroup, int iVictim )
+{
+ // Pin to the wall.
+ trace_t tr;
+ UTIL_TraceLine( start, start + vel * 125, MASK_BLOCKLOS, NULL, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction != 1.0f && tr.DidHitWorld() )
+ {
+ CEffectData data;
+
+ data.m_vOrigin = tr.endpos;
+ data.m_vNormal = vel;
+ data.m_nEntIndex = pOther->entindex();
+ data.m_nAttachmentIndex = boneIndexAttached;
+ data.m_nMaterial = physicsBoneIndex;
+ data.m_nDamageType = iHitGroup;
+ data.m_nSurfaceProp = iVictim;
+ data.m_fFlags = GetProjectileType();
+ data.m_nColor = GetArrowSkin();
+
+ if ( GetScorer() )
+ {
+ data.m_nHitBox = GetScorer()->entindex();
+ }
+
+ DispatchEffect( "TFBoltImpact", data );
+
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Arrow::FadeOut( int iTime )
+{
+ SetMoveType( MOVETYPE_NONE );
+ SetAbsVelocity( vec3_origin );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ AddEffects( EF_NODRAW );
+
+ // Start remove timer.
+ SetContextThink( &CTFProjectile_Arrow::RemoveThink, gpGlobals->curtime + iTime, "ARROW_REMOVE_THINK" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Arrow::RemoveThink( void )
+{
+ UTIL_Remove( this );
+}
+
+//-----------------------------------------------------------------------------
+const char *CTFProjectile_Arrow::GetTrailParticleName( void )
+{
+ if ( m_iProjectileType == TF_PROJECTILE_BUILDING_REPAIR_BOLT )
+ {
+ return ( GetTeamNumber() == TF_TEAM_RED ) ? CLAW_TRAIL_RED : CLAW_TRAIL_BLU;
+ }
+ else if ( m_iProjectileType == TF_PROJECTILE_HEALING_BOLT || m_iProjectileType == TF_PROJECTILE_FESTIVE_HEALING_BOLT )
+ {
+ return ( GetTeamNumber() == TF_TEAM_RED ) ? "effects/healingtrail_red.vmt" : "effects/healingtrail_blu.vmt";
+ }
+
+ return ( GetTeamNumber() == TF_TEAM_RED ) ? "effects/arrowtrail_red.vmt" : "effects/arrowtrail_blu.vmt";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Arrow::CreateTrail( void )
+{
+ if ( IsDormant() )
+ return;
+
+ if ( !m_pTrail )
+ {
+ int width = 3;
+ switch ( m_iProjectileType )
+ {
+ case TF_PROJECTILE_BUILDING_REPAIR_BOLT:
+ width = 5;
+ break;
+ case TF_PROJECTILE_HEALING_BOLT:
+ case TF_PROJECTILE_FESTIVE_HEALING_BOLT:
+ case TF_PROJECTILE_GRAPPLINGHOOK:
+#ifdef STAGING_ONLY
+ case TF_PROJECTILE_SNIPERBULLET:
+#endif // STAGING_ONLY
+ return; // do not create arrow trail for healing bolt, use particle instead (client only)
+ }
+
+ const char *pTrailTeamName = GetTrailParticleName();
+ CSpriteTrail *pTempTrail = NULL;
+
+ pTempTrail = CSpriteTrail::SpriteTrailCreate( pTrailTeamName, GetAbsOrigin(), true );
+ pTempTrail->FollowEntity( this );
+ pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, 255, kRenderFxNone );
+ pTempTrail->SetStartWidth( width );
+ pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) );
+ pTempTrail->SetLifeTime( 0.3 );
+ pTempTrail->TurnOn();
+ pTempTrail->SetAttachment( this, 0 );
+ m_pTrail = pTempTrail;
+ SetContextThink( &CTFProjectile_Arrow::RemoveTrail, gpGlobals->curtime + 3, "FadeTrail");
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Fade and kill the trail
+//-----------------------------------------------------------------------------
+void CTFProjectile_Arrow::RemoveTrail( void )
+{
+ if ( !m_pTrail )
+ return;
+
+ if ( m_pTrail )
+ {
+ if ( m_flTrailLife <= 0 )
+ {
+ UTIL_Remove( m_pTrail );
+ m_flTrailLife = 1.0f;
+ }
+ else
+ {
+ float fAlpha = 128 * m_flTrailLife;
+
+ CSpriteTrail *pTempTrail = dynamic_cast< CSpriteTrail*>( m_pTrail.Get() );
+
+ if ( pTempTrail )
+ {
+ pTempTrail->SetBrightness( int(fAlpha) );
+ }
+
+ m_flTrailLife = m_flTrailLife - 0.1f;
+ SetContextThink( &CTFProjectile_Arrow::RemoveTrail, gpGlobals->curtime + 0.05, "FadeTrail");
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_Arrow::AdjustDamageDirection( const CTakeDamageInfo &info, Vector &dir, CBaseEntity *pEnt )
+{
+ if ( pEnt )
+ {
+ dir = info.GetDamagePosition() - info.GetDamageForce() - pEnt->WorldSpaceCenter();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Arrow was deflected.
+//-----------------------------------------------------------------------------
+void CTFProjectile_Arrow::IncrementDeflected( void )
+{
+ m_iDeflected++;
+
+ // Change trail color.
+ if ( m_pTrail )
+ {
+ UTIL_Remove( m_pTrail );
+ m_pTrail = NULL;
+ m_flTrailLife = 1.0f;
+ }
+ CreateTrail();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Arrow was deflected.
+//-----------------------------------------------------------------------------
+void CTFProjectile_Arrow::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 );
+
+ // Purge our hit list so we can hit everyone again
+ m_HitEntities.Purge();
+ // Add ourselves so we dont hit ourselves
+ m_HitEntities.AddToTail( pTFDeflector->entindex() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Setup function.
+//-----------------------------------------------------------------------------
+void CTFProjectile_HealingBolt::InitArrow( const QAngle &vecAngles, const float fSpeed, const float fGravity, ProjectileType_t projectileType, CBaseEntity *pOwner, CBaseEntity *pScorer )
+{
+ BaseClass::InitArrow( vecAngles, fSpeed, fGravity, projectileType, pOwner, pScorer );
+
+ //SetNextThink( gpGlobals->curtime );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Healing bolt heal.
+//-----------------------------------------------------------------------------
+void CTFProjectile_HealingBolt::ImpactTeamPlayer( CTFPlayer *pOther )
+{
+ if ( !pOther )
+ return;
+
+#ifdef STAGING_ONLY
+ // Milk Arrows only heal teammates on special shot
+ if ( GetProjectileType() == TF_PROJECTILE_MILK_BOLT && !m_bApplyMilkOnHit )
+ return;
+#endif
+
+ CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
+ if ( !pOwner )
+ return;
+
+ // Don't heal players using a weapon that blocks healing
+ CTFWeaponBase *pWeapon = pOther->GetActiveTFWeapon();
+ if ( pWeapon )
+ {
+ int iBlockHealing = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iBlockHealing, weapon_blocks_healing );
+ if ( iBlockHealing )
+ return;
+ }
+
+ float flHealth = GetDamage() * 2.0f;
+
+#ifdef STAGING_ONLY
+ // Milk Arrows give a resist bubble on hitting a teammate
+ if ( GetProjectileType() == TF_PROJECTILE_MILK_BOLT )
+ {
+ // use damage to scale time
+ float flResistDuration = RemapValClamped( flHealth, 0, 150, 1, 3 );
+ pOther->m_Shared.AddCond( TF_COND_MEDIGUN_UBER_BULLET_RESIST, flResistDuration, pOwner );
+ pOther->m_Shared.AddCond( TF_COND_MEDIGUN_UBER_BLAST_RESIST, flResistDuration, pOwner );
+ pOther->m_Shared.AddCond( TF_COND_MEDIGUN_UBER_FIRE_RESIST, flResistDuration, pOwner );
+ }
+#endif
+
+ // Scale this if needed
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pOther, flHealth, mult_healing_from_medics );
+
+ int iActualHealed = pOther->TakeHealth( flHealth, DMG_GENERIC );
+ if ( iActualHealed <= 0 )
+ return;
+
+ // Play an impact sound.
+ ImpactSound( "Weapon_Arrow.ImpactFleshCrossbowHeal" );
+
+ CTF_GameStats.Event_PlayerHealedOther( pOwner, flHealth );
+
+ IGameEvent * event = gameeventmanager->CreateEvent( "player_healed" );
+ if ( event )
+ {
+ // HLTV event priority, not transmitted
+ event->SetInt( "priority", 1 );
+
+ // Healed by another player.
+ event->SetInt( "patient", pOther->GetUserID() );
+ event->SetInt( "healer", pOwner->GetUserID() );
+ event->SetInt( "amount", flHealth );
+ gameeventmanager->FireEvent( event );
+ }
+
+ event = gameeventmanager->CreateEvent( "player_healonhit" );
+ if ( event )
+ {
+ event->SetInt( "amount", flHealth );
+ event->SetInt( "entindex", pOther->entindex() );
+ item_definition_index_t healingItemDef = INVALID_ITEM_DEF_INDEX;
+ if ( pWeapon && pWeapon->GetAttributeContainer() && pWeapon->GetAttributeContainer()->GetItem() )
+ {
+ healingItemDef = pWeapon->GetAttributeContainer()->GetItem()->GetItemDefIndex();
+ }
+ event->SetInt( "weapon_def_index", healingItemDef );
+ gameeventmanager->FireEvent( event );
+ }
+
+ event = gameeventmanager->CreateEvent( "crossbow_heal" );
+ if ( event )
+ {
+ event->SetInt( "healer", pOwner->GetUserID() );
+ event->SetInt( "target", pOther->GetUserID() );
+ event->SetInt( "amount", flHealth );
+ gameeventmanager->FireEvent( event );
+ }
+
+ // Give a litte bit of uber based on actual healing
+ // Give them a little bit of Uber
+ CWeaponMedigun *pMedigun = static_cast<CWeaponMedigun *>( pOwner->Weapon_OwnsThisID( TF_WEAPON_MEDIGUN ) );
+ if ( pMedigun )
+ {
+ // On Mediguns, per frame, the amount of uber added is based on
+ // Default heal rate is 24per second, we scale based on that and frametime
+ pMedigun->AddCharge( ( iActualHealed / 24.0f ) * gpGlobals->frametime );
+ }
+ pOther->m_Shared.AddCond( TF_COND_HEALTH_OVERHEALED, 1.2f );
+
+ EconEntity_OnOwnerKillEaterEvent_Batched( dynamic_cast<CEconEntity *>( GetLauncher() ), pOwner, pOther, kKillEaterEvent_AllyHealingDone, flHealth );
+}
+
+
+CTFProjectile_GrapplingHook::CTFProjectile_GrapplingHook()
+ : m_pImpactFleshSoundLoop( NULL )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Spawn
+//-----------------------------------------------------------------------------
+void CTFProjectile_GrapplingHook::Spawn()
+{
+ BaseClass::Spawn();
+
+ SetMoveType( MOVETYPE_FLY, MOVECOLLIDE_FLY_CUSTOM );
+}
+
+
+void CTFProjectile_GrapplingHook::Precache()
+{
+ BaseClass::Precache();
+
+ PrecacheModel( "models/weapons/c_models/c_grapple_proj/c_grapple_proj.mdl" );
+ PrecacheScriptSound( "WeaponGrapplingHook.ImpactFlesh" );
+ PrecacheScriptSound( "WeaponGrapplingHook.ImpactDefault" );
+ PrecacheScriptSound( "WeaponGrapplingHook.ImpactFleshLoop" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Spawn
+//-----------------------------------------------------------------------------
+void CTFProjectile_GrapplingHook::UpdateOnRemove()
+{
+ // clear hook target
+ CTFPlayer *pTFPlayer = ToTFPlayer( GetOwnerEntity() );
+ if ( pTFPlayer )
+ {
+ // Clear any healers grappling with us
+ SetMedicsGrapplingHookTarget( pTFPlayer, NULL );
+ pTFPlayer->SetGrapplingHookTarget( NULL );
+ pTFPlayer->m_Shared.RemoveCond( TF_COND_GRAPPLINGHOOK );
+ }
+
+ StopImpactFleshSoundLoop();
+
+ BaseClass::UpdateOnRemove();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Setup function.
+//-----------------------------------------------------------------------------
+void CTFProjectile_GrapplingHook::InitArrow( const QAngle &vecAngles, const float fSpeed, const float fGravity, ProjectileType_t projectileType, CBaseEntity *pOwner, CBaseEntity *pScorer )
+{
+ BaseClass::InitArrow( vecAngles, fSpeed, fGravity, projectileType, pOwner, pScorer );
+
+ CTFPlayer *pTFPlayer = ToTFPlayer( pOwner );
+ if ( pTFPlayer )
+ {
+ pTFPlayer->m_Shared.AddCond( TF_COND_GRAPPLINGHOOK );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: OnArrowImpact
+//-----------------------------------------------------------------------------
+void CTFProjectile_GrapplingHook::OnArrowImpact( mstudiobbox_t *pBox, CBaseEntity *pOther, CBaseEntity *pAttacker )
+{
+ HookTarget( pOther );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: OnArrowImpactObject
+//-----------------------------------------------------------------------------
+bool CTFProjectile_GrapplingHook::OnArrowImpactObject( CBaseEntity *pOther )
+{
+ HookTarget( pOther );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: CheckSkyboxImpact
+//-----------------------------------------------------------------------------
+void CTFProjectile_GrapplingHook::CheckSkyboxImpact( CBaseEntity *pOther )
+{
+ trace_t tr;
+ Vector velDir = GetAbsVelocity();
+ VectorNormalize( velDir );
+ Vector vecSpot = GetAbsOrigin() - velDir * 32;
+ UTIL_TraceLine( vecSpot, vecSpot + velDir * 64, MASK_SOLID, this, COLLISION_GROUP_DEBRIS, &tr );
+ if ( tr.fraction < 1.0 && tr.surface.flags & SURF_SKY )
+ {
+ // We hit the skybox, go away soon.
+ FadeOut( 1.f );
+ return;
+ }
+
+ if ( !pOther->IsWorld() )
+ {
+ HookTarget( pOther );
+ }
+ else
+ {
+ HookTarget( pOther );
+
+ // rotate the hook model to be perpendicular to the world surface
+ Vector vUp;
+ AngleVectors( GetAbsAngles(), NULL, NULL, &vUp );
+ QAngle qNewAngles;
+ VectorAngles( -tr.plane.normal, vUp, qNewAngles );
+ SetAbsAngles( qNewAngles );
+ SetAbsOrigin( GetAbsOrigin() + 3.f * tr.plane.normal );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: HookTarget
+//-----------------------------------------------------------------------------
+void CTFProjectile_GrapplingHook::HookTarget( CBaseEntity *pOther )
+{
+ if ( !GetOwnerEntity() || !pOther )
+ return;
+
+ CTFPlayer *pTFPlayer = ToTFPlayer( GetOwnerEntity() );
+ if ( !pTFPlayer || pTFPlayer->GetGrapplingHookTarget() )
+ return;
+
+ CBaseEntity *pTarget = pOther->IsWorld() ? this : pOther;
+ const char *pszSoundName = NULL;
+ if ( pTarget->IsPlayer() )
+ {
+ pszSoundName = "WeaponGrapplingHook.ImpactFlesh";
+ }
+ else
+ {
+ pszSoundName = "WeaponGrapplingHook.ImpactDefault";
+ }
+ ImpactSound( pszSoundName );
+
+ pTFPlayer->SetGrapplingHookTarget( pTarget, true );
+ // Grapple any medics to us
+ SetMedicsGrapplingHookTarget( pTFPlayer, pTFPlayer );
+
+ // Stop moving!
+ if ( pOther->IsPlayer() )
+ {
+ FollowEntity( pOther, false );
+ StartImpactFleshSoundLoop();
+ }
+ else
+ SetMoveType( MOVETYPE_NONE );
+
+ SetContextThink( &CTFProjectile_GrapplingHook::HookLatchedThink, gpGlobals->curtime + 0.1f, "HookLatchedThink" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: HookLatchedThink
+//-----------------------------------------------------------------------------
+void CTFProjectile_GrapplingHook::HookLatchedThink()
+{
+ // if owner is dead, remove the hook
+ CTFPlayer *pTFPlayer = ToTFPlayer( GetOwnerEntity() );
+ if ( !pTFPlayer || !pTFPlayer->IsAlive() )
+ {
+ UTIL_Remove( this );
+ return;
+ }
+
+ // if the target nolonger exist or target player is dead, remove the hook
+ CBaseEntity *pHookTarget = pTFPlayer->GetGrapplingHookTarget();
+ if ( !pHookTarget || ( pHookTarget->IsPlayer() && !pHookTarget->IsAlive() ) )
+ {
+ UTIL_Remove( this );
+ return;
+ }
+
+ SetContextThink( &CTFProjectile_GrapplingHook::HookLatchedThink, gpGlobals->curtime + 0.1f, "HookLatchedThink" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_GrapplingHook::StartImpactFleshSoundLoop()
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ CPASAttenuationFilter filter( this );
+ m_pImpactFleshSoundLoop = controller.SoundCreate( filter, entindex(), "WeaponGrapplingHook.ImpactFleshLoop" );
+ controller.Play( m_pImpactFleshSoundLoop, 1.0, 100 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFProjectile_GrapplingHook::StopImpactFleshSoundLoop()
+{
+ if ( m_pImpactFleshSoundLoop )
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundDestroy( m_pImpactFleshSoundLoop );
+ m_pImpactFleshSoundLoop = NULL;
+ }
+}