//========= 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;
	}
}