//========= Copyright Valve Corporation, All rights reserved. ============//
//
// TF Grappling Hook
//
//=============================================================================
#include "cbase.h"
#include "in_buttons.h"
#include "tf_weapon_grapplinghook.h"

// Client specific.
#ifdef CLIENT_DLL
#include "c_tf_player.h"
#include "gc_clientsystem.h"
#include "prediction.h"
#include "soundenvelope.h"
// Server specific.
#else
#include "tf_player.h"
#include "entity_rune.h"
#include "effect_dispatch_data.h"
#include "tf_fx.h"
#include "func_respawnroom.h"
#endif

//=============================================================================
//
// Grappling hook tables.
//
IMPLEMENT_NETWORKCLASS_ALIASED( TFGrapplingHook, DT_GrapplingHook )

BEGIN_NETWORK_TABLE( CTFGrapplingHook, DT_GrapplingHook )
#ifdef GAME_DLL
	SendPropEHandle( SENDINFO( m_hProjectile ) ),
#else // GAME_DLL
	RecvPropEHandle( RECVINFO( m_hProjectile ) ),
#endif // CLIENT_DLL
END_NETWORK_TABLE()

BEGIN_PREDICTION_DATA( CTFGrapplingHook )
END_PREDICTION_DATA()

LINK_ENTITY_TO_CLASS( tf_weapon_grapplinghook, CTFGrapplingHook );
PRECACHE_WEAPON_REGISTER( tf_weapon_grapplinghook );

// Server specific.
#ifndef CLIENT_DLL
BEGIN_DATADESC( CTFGrapplingHook )
END_DATADESC()
#endif // CLIENT_DLL

// This is basically a copy of s_acttableMeleeAllclass table except primary fire to use grappling hook specific
acttable_t s_grapplinghook_normal_acttable[] = 
{
	{ ACT_MP_STAND_IDLE,		ACT_MP_STAND_MELEE_ALLCLASS,				false },
	{ ACT_MP_CROUCH_IDLE,		ACT_MP_CROUCH_MELEE_ALLCLASS,			false },
	{ ACT_MP_RUN,				ACT_MP_RUN_MELEE_ALLCLASS,				false },
	{ ACT_MP_WALK,				ACT_MP_WALK_MELEE_ALLCLASS,				false },
	{ ACT_MP_AIRWALK,			ACT_GRAPPLE_PULL_IDLE,					false },
	{ ACT_MP_CROUCHWALK,		ACT_MP_CROUCHWALK_MELEE_ALLCLASS,		false },
	{ ACT_MP_JUMP,				ACT_MP_JUMP_MELEE_ALLCLASS,				false },
	{ ACT_MP_JUMP_START,		ACT_MP_JUMP_START_MELEE_ALLCLASS,		false },
	{ ACT_MP_JUMP_FLOAT,		ACT_MP_JUMP_FLOAT_MELEE_ALLCLASS,		false },
	{ ACT_MP_JUMP_LAND,			ACT_MP_JUMP_LAND_MELEE_ALLCLASS,			false },
	{ ACT_MP_SWIM,				ACT_MP_SWIM_MELEE_ALLCLASS,				false },
	{ ACT_MP_DOUBLEJUMP_CROUCH,	ACT_MP_DOUBLEJUMP_CROUCH_MELEE,	false },

	{ ACT_MP_ATTACK_STAND_PRIMARYFIRE,		ACT_GRAPPLE_FIRE_START,		false },
	{ ACT_MP_ATTACK_CROUCH_PRIMARYFIRE,		ACT_GRAPPLE_FIRE_START,		false },
	{ ACT_MP_ATTACK_SWIM_PRIMARYFIRE,		ACT_GRAPPLE_FIRE_START,		false },
	{ ACT_MP_ATTACK_AIRWALK_PRIMARYFIRE,	ACT_GRAPPLE_FIRE_START,		false },

	{ ACT_MP_ATTACK_STAND_SECONDARYFIRE,	ACT_MP_ATTACK_STAND_MELEE_SECONDARY, false },
	{ ACT_MP_ATTACK_CROUCH_SECONDARYFIRE,	ACT_MP_ATTACK_CROUCH_MELEE_SECONDARY,false },
	{ ACT_MP_ATTACK_SWIM_SECONDARYFIRE,		ACT_MP_ATTACK_SWIM_MELEE_ALLCLASS,		false },
	{ ACT_MP_ATTACK_AIRWALK_SECONDARYFIRE,	ACT_MP_ATTACK_AIRWALK_MELEE_ALLCLASS,	false },

	{ ACT_MP_GESTURE_FLINCH,	ACT_MP_GESTURE_FLINCH_MELEE, false },

	{ ACT_MP_GRENADE1_DRAW,		ACT_MP_MELEE_GRENADE1_DRAW,	false },
	{ ACT_MP_GRENADE1_IDLE,		ACT_MP_MELEE_GRENADE1_IDLE,	false },
	{ ACT_MP_GRENADE1_ATTACK,	ACT_MP_MELEE_GRENADE1_ATTACK,	false },
	{ ACT_MP_GRENADE2_DRAW,		ACT_MP_MELEE_GRENADE2_DRAW,	false },
	{ ACT_MP_GRENADE2_IDLE,		ACT_MP_MELEE_GRENADE2_IDLE,	false },
	{ ACT_MP_GRENADE2_ATTACK,	ACT_MP_MELEE_GRENADE2_ATTACK,	false },

	{ ACT_MP_GESTURE_VC_HANDMOUTH,	ACT_MP_GESTURE_VC_HANDMOUTH_MELEE,	false },
	{ ACT_MP_GESTURE_VC_FINGERPOINT,	ACT_MP_GESTURE_VC_FINGERPOINT_MELEE,	false },
	{ ACT_MP_GESTURE_VC_FISTPUMP,	ACT_MP_GESTURE_VC_FISTPUMP_MELEE,	false },
	{ ACT_MP_GESTURE_VC_THUMBSUP,	ACT_MP_GESTURE_VC_THUMBSUP_MELEE,	false },
	{ ACT_MP_GESTURE_VC_NODYES,	ACT_MP_GESTURE_VC_NODYES_MELEE,	false },
	{ ACT_MP_GESTURE_VC_NODNO,	ACT_MP_GESTURE_VC_NODNO_MELEE,	false },
};

// This is basically a copy of s_acttableSecondary table except primary fire to use grappling hook specific
acttable_t s_grapplinghook_engineer_acttable[] = 
{
	{ ACT_MP_STAND_IDLE,		ACT_MP_STAND_SECONDARY,				false },
	{ ACT_MP_CROUCH_IDLE,		ACT_MP_CROUCH_SECONDARY,			false },
	{ ACT_MP_RUN,				ACT_MP_RUN_SECONDARY,				false },
	{ ACT_MP_WALK,				ACT_MP_WALK_SECONDARY,				false },
	{ ACT_MP_AIRWALK,			ACT_MP_AIRWALK_SECONDARY,			false },
	{ ACT_MP_CROUCHWALK,		ACT_MP_CROUCHWALK_SECONDARY,		false },
	{ ACT_MP_JUMP,				ACT_MP_JUMP_SECONDARY,				false },
	{ ACT_MP_JUMP_START,		ACT_MP_JUMP_START_SECONDARY,		false },
	{ ACT_MP_JUMP_FLOAT,		ACT_MP_JUMP_FLOAT_SECONDARY,		false },
	{ ACT_MP_JUMP_LAND,			ACT_MP_JUMP_LAND_SECONDARY,			false },
	{ ACT_MP_SWIM,				ACT_MP_SWIM_SECONDARY,				false },
	{ ACT_MP_DOUBLEJUMP_CROUCH,	ACT_MP_DOUBLEJUMP_CROUCH_SECONDARY,	false },

	{ ACT_MP_ATTACK_STAND_PRIMARYFIRE,		ACT_GRAPPLE_FIRE_START,	false },
	{ ACT_MP_ATTACK_CROUCH_PRIMARYFIRE,		ACT_GRAPPLE_FIRE_START,	false },
	{ ACT_MP_ATTACK_SWIM_PRIMARYFIRE,		ACT_GRAPPLE_FIRE_START,	false },
	{ ACT_MP_ATTACK_AIRWALK_PRIMARYFIRE,	ACT_GRAPPLE_FIRE_START,	false },

	{ ACT_MP_RELOAD_STAND,		ACT_MP_RELOAD_STAND_SECONDARY,		false },
	{ ACT_MP_RELOAD_STAND_LOOP,	ACT_MP_RELOAD_STAND_SECONDARY_LOOP,	false },
	{ ACT_MP_RELOAD_STAND_END,	ACT_MP_RELOAD_STAND_SECONDARY_END,	false },
	{ ACT_MP_RELOAD_CROUCH,		ACT_MP_RELOAD_CROUCH_SECONDARY,		false },
	{ ACT_MP_RELOAD_CROUCH_LOOP,ACT_MP_RELOAD_CROUCH_SECONDARY_LOOP,false },
	{ ACT_MP_RELOAD_CROUCH_END,	ACT_MP_RELOAD_CROUCH_SECONDARY_END,	false },
	{ ACT_MP_RELOAD_SWIM,		ACT_MP_RELOAD_SWIM_SECONDARY,		false },
	{ ACT_MP_RELOAD_SWIM_LOOP,	ACT_MP_RELOAD_SWIM_SECONDARY_LOOP,	false },
	{ ACT_MP_RELOAD_SWIM_END,	ACT_MP_RELOAD_SWIM_SECONDARY_END,	false },
	{ ACT_MP_RELOAD_AIRWALK,	ACT_MP_RELOAD_AIRWALK_SECONDARY,	false },
	{ ACT_MP_RELOAD_AIRWALK_LOOP,	ACT_MP_RELOAD_AIRWALK_SECONDARY_LOOP,	false },
	{ ACT_MP_RELOAD_AIRWALK_END,ACT_MP_RELOAD_AIRWALK_SECONDARY_END,false },

	{ ACT_MP_GESTURE_FLINCH,	ACT_MP_GESTURE_FLINCH_SECONDARY, false },

	{ ACT_MP_GRENADE1_DRAW,		ACT_MP_SECONDARY_GRENADE1_DRAW,	false },
	{ ACT_MP_GRENADE1_IDLE,		ACT_MP_SECONDARY_GRENADE1_IDLE,	false },
	{ ACT_MP_GRENADE1_ATTACK,	ACT_MP_SECONDARY_GRENADE1_ATTACK,	false },
	{ ACT_MP_GRENADE2_DRAW,		ACT_MP_SECONDARY_GRENADE2_DRAW,	false },
	{ ACT_MP_GRENADE2_IDLE,		ACT_MP_SECONDARY_GRENADE2_IDLE,	false },
	{ ACT_MP_GRENADE2_ATTACK,	ACT_MP_SECONDARY_GRENADE2_ATTACK,	false },

	{ ACT_MP_ATTACK_STAND_GRENADE,		ACT_MP_ATTACK_STAND_GRENADE,	false },
	{ ACT_MP_ATTACK_CROUCH_GRENADE,		ACT_MP_ATTACK_STAND_GRENADE,	false },
	{ ACT_MP_ATTACK_SWIM_GRENADE,		ACT_MP_ATTACK_STAND_GRENADE,	false },
	{ ACT_MP_ATTACK_AIRWALK_GRENADE,	ACT_MP_ATTACK_STAND_GRENADE,	false },

	{ ACT_MP_GESTURE_VC_HANDMOUTH,	ACT_MP_GESTURE_VC_HANDMOUTH_SECONDARY,	false },
	{ ACT_MP_GESTURE_VC_FINGERPOINT,	ACT_MP_GESTURE_VC_FINGERPOINT_SECONDARY,	false },
	{ ACT_MP_GESTURE_VC_FISTPUMP,	ACT_MP_GESTURE_VC_FISTPUMP_SECONDARY,	false },
	{ ACT_MP_GESTURE_VC_THUMBSUP,	ACT_MP_GESTURE_VC_THUMBSUP_SECONDARY,	false },
	{ ACT_MP_GESTURE_VC_NODYES,	ACT_MP_GESTURE_VC_NODYES_SECONDARY,	false },
	{ ACT_MP_GESTURE_VC_NODNO,	ACT_MP_GESTURE_VC_NODNO_SECONDARY,	false },
};

acttable_t *CTFGrapplingHook::ActivityList( int &iActivityCount )
{
	CTFPlayer *pOwner = GetTFPlayerOwner();
	if ( pOwner )
	{
		if ( pOwner->IsPlayerClass( TF_CLASS_ENGINEER ) )
		{
			iActivityCount = ARRAYSIZE( s_grapplinghook_engineer_acttable );
			return s_grapplinghook_engineer_acttable;
		}
		else
		{
			iActivityCount = ARRAYSIZE( s_grapplinghook_normal_acttable );
			return s_grapplinghook_normal_acttable;
		}
	}

	return BaseClass::ActivityList( iActivityCount );
}


poseparamtable_t s_grapplinghook_normal_poseparamtable[] =
{
	{ "R_hand_grip", 14 },
	{ "R_arm", 2 },
};

poseparamtable_t s_grapplinghook_engineer_poseparamtable[] =
{
	{ "r_handposes_engineer", 1 },
};

poseparamtable_t *CTFGrapplingHook::PoseParamList( int &iPoseParamCount )
{
	CTFPlayer *pOwner = GetTFPlayerOwner();
	if ( pOwner )
	{
		if ( pOwner->IsPlayerClass( TF_CLASS_ENGINEER ) )
		{
			iPoseParamCount = ARRAYSIZE( s_grapplinghook_engineer_poseparamtable );
			return s_grapplinghook_engineer_poseparamtable;
		}
		else
		{
			iPoseParamCount = ARRAYSIZE( s_grapplinghook_normal_poseparamtable );
			return s_grapplinghook_normal_poseparamtable;
		}
	}

	return BaseClass::PoseParamList( iPoseParamCount );
}


ConVar tf_grapplinghook_projectile_speed( "tf_grapplinghook_projectile_speed", "1500", FCVAR_REPLICATED | FCVAR_CHEAT, "How fast does the grappliing hook projectile travel" );
ConVar tf_grapplinghook_max_distance( "tf_grapplinghook_max_distance", "2000", FCVAR_REPLICATED | FCVAR_CHEAT, "Valid distance for grappling hook to travel" );
ConVar tf_grapplinghook_fire_delay( "tf_grapplinghook_fire_delay", "0.5", FCVAR_REPLICATED | FCVAR_CHEAT );

float m_flNextSupernovaDenyWarning = 0.f;


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CTFGrapplingHook::CTFGrapplingHook()
{
#ifdef GAME_DLL
	m_bReleasedAfterLatched = false;
#endif // GAME_DLL

#ifdef CLIENT_DLL
	m_pHookSound = NULL;
	m_bLatched = false;
#endif // CLIENT_DLL
}


void CTFGrapplingHook::Precache()
{
	BaseClass::Precache();

#ifdef GAME_DLL
	PrecacheScriptSound( "WeaponGrapplingHook.Shoot" );
#endif // GAME_DLL
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CBaseEntity *CTFGrapplingHook::FireProjectile( CTFPlayer *pPlayer )
{
#ifdef GAME_DLL
	Assert( m_hProjectile == NULL );
	m_hProjectile = BaseClass::FireProjectile( pPlayer );

	return m_hProjectile;
#else
	return BaseClass::FireProjectile( pPlayer );
#endif // GAME_DLL
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFGrapplingHook::ItemPostFrame( void )
{
	CTFPlayer *pOwner = GetTFPlayerOwner();
	if (!pOwner)
		return;

#ifdef CLIENT_DLL
	static bool bFired = false;
#endif // CLIENT_DLL

	CBaseEntity *pHookTarget = pOwner->GetGrapplingHookTarget();
	bool bJump = ( pOwner->m_nButtons & IN_JUMP ) > 0;
	bool bForceReleaseHook = pHookTarget != NULL && bJump;

	if ( !bForceReleaseHook && ( pOwner->m_nButtons & IN_ATTACK || pOwner->IsUsingActionSlot() ) )
	{
#ifdef CLIENT_DLL
		if ( !bFired )
		{
			PrimaryAttack();
			bFired = true;
		}
#else
		PrimaryAttack();
#endif // CLIENT_DLL

		if ( pOwner->GetGrapplingHookTarget() )
		{
			SendWeaponAnim( ACT_GRAPPLE_PULL_START );
		}
		else if ( m_hProjectile )
		{
			SendWeaponAnim( ACT_VM_PRIMARYATTACK );
		}
		else
		{
			SendWeaponAnim( ACT_GRAPPLE_IDLE );
		}
	}
	else
	{
#ifdef CLIENT_DLL
		bFired = false;
#endif // CLIENT_DLL

		OnHookReleased( bForceReleaseHook );
	}

	if ( pOwner->GetViewModel(0) )
	{
		pOwner->GetViewModel(0)->SetPlaybackRate( 1.f );
	}
	if ( pOwner->GetViewModel(1) )
	{
		pOwner->GetViewModel(1)->SetPlaybackRate( 1.f );
	}

	BaseClass::ItemPostFrame();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTFGrapplingHook::CanAttack( void )
{
	CTFPlayer *pPlayer = GetTFPlayerOwner();

	if ( pPlayer->m_Shared.IsFeignDeathReady() )
		return false;

	return BaseClass::CanAttack();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFGrapplingHook::PrimaryAttack( void )
{
	CTFPlayer *pOwner = GetTFPlayerOwner();

#ifdef GAME_DLL
	// make sure to unlatch from the current target and remove the old projectile before we fire a new one
	if ( m_bReleasedAfterLatched )
	{
		RemoveHookProjectile( true );
	}
#endif // GAME_DLL

	if ( m_hProjectile )
		return;

	if ( m_flNextPrimaryAttack > gpGlobals->curtime )
		return;

	if ( pOwner && pOwner->m_Shared.IsControlStunned() )
		return;

	Vector vecSrc;
	QAngle angForward;
	Vector vecOffset( 23.5f, -8.0f, -3.0f ); // copied from CTFWeaponBaseGun::FireArrow
	GetProjectileFireSetup( pOwner, vecOffset, &vecSrc, &angForward, false );
	Vector vecForward;
	AngleVectors( angForward, &vecForward );

	// check if aiming at skybox
	trace_t tr;
	UTIL_TraceLine( vecSrc, vecSrc + tf_grapplinghook_max_distance.GetFloat() * vecForward, MASK_SOLID, pOwner, COLLISION_GROUP_DEBRIS, &tr );
	if ( !tr.DidHit() || ( tr.fraction < 1.0 && tr.surface.flags & SURF_SKY ) )
	{
#ifdef CLIENT_DLL
		// play fail sound on client here
		if ( pOwner && prediction->IsFirstTimePredicted() )
		{
			pOwner->EmitSound( "Player.DenyWeaponSelection" );
		}
#endif // CLIENT_DLL
		return;
	}

	BaseClass::PrimaryAttack();

	m_flNextPrimaryAttack = gpGlobals->curtime + tf_grapplinghook_fire_delay.GetFloat();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTFGrapplingHook::Deploy( void )
{
#ifdef GAME_DLL
	RemoveHookProjectile( true );
	m_bReleasedAfterLatched = IsLatchedToTargetPlayer();
#endif // GAME_DLL
	return BaseClass::Deploy();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTFGrapplingHook::Holster( CBaseCombatWeapon *pSwitchingTo )
{
#ifdef GAME_DLL
	RemoveHookProjectile();
	m_bReleasedAfterLatched = IsLatchedToTargetPlayer();
#endif // GAME_DLL

	return BaseClass::Holster( pSwitchingTo );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFGrapplingHook::GetProjectileFireSetup( CTFPlayer *pPlayer, Vector vecOffset, Vector *vecSrc, QAngle *angForward, bool bHitTeammates /*= true*/, float flEndDist /*= 2000.f*/ )
{
	BaseClass::GetProjectileFireSetup( pPlayer, vecOffset, vecSrc, angForward, bHitTeammates, flEndDist );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float CTFGrapplingHook::GetProjectileSpeed()
{
	CTFPlayer *pOwner = GetTFPlayerOwner();
	
	if ( pOwner && pOwner->m_Shared.GetCarryingRuneType() == RUNE_AGILITY )
	{
		switch ( pOwner->GetPlayerClass()->GetClassIndex() )
		{
		case TF_CLASS_SOLDIER:
		case TF_CLASS_HEAVYWEAPONS:
			return 2600.f;
		default:
			return 3000.f;
		}
	}

	return tf_grapplinghook_projectile_speed.GetFloat();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTFGrapplingHook::SendWeaponAnim( int actBase )
{
	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
		return BaseClass::SendWeaponAnim( actBase );

	if ( actBase == ACT_VM_DRAW )
	{
		actBase = ACT_GRAPPLE_DRAW;
	}
	else if ( pPlayer->GetGrapplingHookTarget() )
	{
		if ( GetActivity() != ACT_GRAPPLE_PULL_START && GetActivity() != ACT_GRAPPLE_PULL_IDLE )
		{
			bool bResult = BaseClass::SendWeaponAnim( ACT_GRAPPLE_PULL_START );
			//DevMsg("pull start %f\n", gpGlobals->curtime );

			m_startPullingTimer.Start( SequenceDuration() );

			return bResult;
		}
		else
		{
			if ( GetActivity() == ACT_GRAPPLE_PULL_IDLE )
				return true;

			if ( GetActivity() == ACT_GRAPPLE_PULL_START && m_startPullingTimer.HasStarted() && !m_startPullingTimer.IsElapsed() )
				return true;

			actBase = ACT_GRAPPLE_PULL_IDLE;
			//DevMsg("pull idle %f\n", gpGlobals->curtime );

			m_startPullingTimer.Invalidate();
		}
	}
	else if ( actBase == ACT_VM_PRIMARYATTACK )
	{
		if ( GetActivity() != ACT_GRAPPLE_FIRE_START && GetActivity() != ACT_GRAPPLE_FIRE_IDLE )
		{
			bool bResult = BaseClass::SendWeaponAnim( ACT_GRAPPLE_FIRE_START );
			//DevMsg("fire start %f\n", gpGlobals->curtime );

			m_startFiringTimer.Start( SequenceDuration() );

			return bResult;
		}
		else
		{
			if ( GetActivity() == ACT_GRAPPLE_FIRE_IDLE )
				return true;

			if ( GetActivity() == ACT_GRAPPLE_FIRE_START && m_startFiringTimer.HasStarted() && !m_startFiringTimer.IsElapsed() )
				return true;

			actBase = ACT_GRAPPLE_FIRE_IDLE;
			//DevMsg("fire idle %f\n", gpGlobals->curtime );

			m_startFiringTimer.Invalidate();
		}
	}
	else
	{
		if ( GetActivity() == ACT_GRAPPLE_PULL_IDLE )
		{
			actBase = ACT_GRAPPLE_PULL_END;
			//DevMsg("pull end %f\n", gpGlobals->curtime );
		}
		else
		{
			if ( GetActivity() == ACT_GRAPPLE_IDLE )
				return true;

			actBase = ACT_GRAPPLE_IDLE;
			//DevMsg("grapple idle %f\n", gpGlobals->curtime );
		}
	}

	return BaseClass::SendWeaponAnim( actBase );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFGrapplingHook::PlayWeaponShootSound( void )
{
#ifdef GAME_DLL
	EmitSound( "WeaponGrapplingHook.Shoot" );
#endif // GAME_DLL
}



#ifdef GAME_DLL
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFGrapplingHook::ActivateRune()
{
	CTFPlayer *pOwner = GetTFPlayerOwner();
	if ( pOwner && pOwner->m_Shared.IsRuneCharged() )
	{
		RuneTypes_t type = pOwner->m_Shared.GetCarryingRuneType();
		if ( type == RUNE_SUPERNOVA )
		{
			// don't allow stealthed player to activate the power
			if ( pOwner->m_Shared.IsStealthed() )
				return;

			int nEnemyTeam = GetEnemyTeam( pOwner->GetTeamNumber() );

			// apply super nova effect to all enemies
			bool bHitAnyTarget = false;
			CUtlVector< CTFPlayer* > vecPlayers;
			CUtlVector< CTFPlayer* > vecVictims;
			CollectPlayers( &vecPlayers, nEnemyTeam, COLLECT_ONLY_LIVING_PLAYERS );
			const float flEffectRadiusSqr = Sqr( 1500.f );
			const float flMinPushForce = 200.f;
			const float flMaxPushForce = 500.f;
			const float flMinStunDuration = 2.f;
			const float flMaxStunDuration = 4.f;
			for ( int i = 0; i < vecPlayers.Count(); ++i )
			{
				CTFPlayer *pOther = vecPlayers[i];
				Vector toPlayer = pOther->WorldSpaceCenter() - pOwner->WorldSpaceCenter();
				float flDistSqr = toPlayer.LengthSqr();

				// Collect valid enemies
				if ( flDistSqr <= flEffectRadiusSqr && pOwner->IsLineOfSightClear( pOther, CBaseCombatCharacter::IGNORE_ACTORS ) && !PointInRespawnRoom( pOther, pOther->WorldSpaceCenter() ) )
				{
					vecVictims.AddToTail( pOther );
				}
			}

			// if there is more than one victim, the stun duration increases
			float flStunDuration = MIN( flMinStunDuration + ( ( vecVictims.Count() - 1 ) * 0.5 ), flMaxStunDuration );

			for ( int i = 0; i < vecVictims.Count(); ++i )
			{
				// force enemy to drop rune, stun, and push them
				CTFPlayer *pOther = vecVictims[i];
				const char *pszEffect = pOwner->GetTeamNumber() == TF_TEAM_RED ? "powerup_supernova_strike_red" : "powerup_supernova_strike_blue";

				CPVSFilter filter( WorldSpaceCenter() );
				Vector vStart = pOwner->WorldSpaceCenter();
				Vector vEnd = pOther->GetAbsOrigin() + Vector( 0, 0, 56 );
				te_tf_particle_effects_control_point_t controlPoint = { PATTACH_ABSORIGIN, vEnd };
				TE_TFParticleEffectComplex( filter, 0.f, pszEffect, vStart, QAngle( 0.f, 0.f, 0.f ), NULL, &controlPoint, pOther, PATTACH_CUSTOMORIGIN );

					
				pOther->DropRune( false, pOwner->GetTeamNumber() );
				pOther->DropFlag();

				pOther->m_Shared.StunPlayer( flStunDuration, 1.f, TF_STUN_MOVEMENT | TF_STUN_CONTROLS, pOwner );
	
				// send the player flying
				// make sure we push players up and away
				Vector toPlayer = pOther->WorldSpaceCenter() - pOwner->WorldSpaceCenter();
				toPlayer.z = 0.0f;
				toPlayer.NormalizeInPlace();
				toPlayer.z = 1.0f;

				// scale push force based on distance from the supernova origin
				float flDistSqr = toPlayer.LengthSqr();
				float flPushForce = RemapValClamped( flDistSqr, 0.f, flEffectRadiusSqr, flMaxPushForce, flMinPushForce );
				Vector vPush = flPushForce * toPlayer;
				pOther->ApplyAbsVelocityImpulse( vPush );

				bHitAnyTarget = true;
			}

			// don't deploy with no target
			if ( bHitAnyTarget )
			{
				// play effect
				const char *pszEffect = pOwner->GetTeamNumber() == TF_TEAM_RED ? "powerup_supernova_explode_red" : "powerup_supernova_explode_blue";
				CEffectData	data;
				data.m_nHitBox = GetParticleSystemIndex( pszEffect );
				data.m_vOrigin = pOwner->GetAbsOrigin();
				data.m_vAngles = vec3_angle;

				CPASFilter filter( data.m_vOrigin );
				filter.SetIgnorePredictionCull( true );

				te->DispatchEffect( filter, 0.0, data.m_vOrigin, "ParticleEffect", data );
				pOwner->EmitSound( "Powerup.PickUpSupernovaActivate" );

				// remove the power and reposition instantly
				pOwner->m_Shared.SetCarryingRuneType( RUNE_NONE );
				CTFRune::RepositionRune( type, TEAM_ANY );
			}
			else
			{
				if ( gpGlobals->curtime > m_flNextSupernovaDenyWarning )
				{
					m_flNextSupernovaDenyWarning = gpGlobals->curtime + 0.5f;

					CSingleUserRecipientFilter singleFilter( pOwner );
					EmitSound( singleFilter, pOwner->entindex(), "Player.UseDeny" );
					ClientPrint( pOwner, HUD_PRINTCENTER, "#TF_Powerup_Supernova_Deny" );
				}
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFGrapplingHook::RemoveHookProjectile( bool bForce /*= false*/ )
{
	if ( !bForce && IsLatchedToTargetPlayer() )
	{
		// don't remove the projectile until we unlatched from the target (by hooking again)
		return;
	}

	if ( m_hProjectile )
	{
		UTIL_Remove( m_hProjectile );
		m_hProjectile = NULL;
	}
}

bool CTFGrapplingHook::IsLatchedToTargetPlayer() const
{
	CTFPlayer *pOwner = GetTFPlayerOwner();
	return pOwner && pOwner->GetGrapplingHookTarget() && pOwner->GetGrapplingHookTarget()->IsPlayer();
}

#endif // GAME_DLL

void CTFGrapplingHook::OnHookReleased( bool bForce )
{
#ifdef GAME_DLL
	RemoveHookProjectile( bForce );
	m_bReleasedAfterLatched = IsLatchedToTargetPlayer();
#endif // GAME_DLL

	if ( GetActivity() != ACT_GRAPPLE_DRAW && GetActivity() != ACT_GRAPPLE_IDLE && GetActivity() != ACT_GRAPPLE_PULL_END )
		SendWeaponAnim( ACT_GRAPPLE_PULL_END );

	if ( bForce )
		m_flNextPrimaryAttack = gpGlobals->curtime + tf_grapplinghook_fire_delay.GetFloat();
}

#ifdef CLIENT_DLL


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFGrapplingHook::UpdateOnRemove()
{
	StopHookSound();

	BaseClass::UpdateOnRemove();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFGrapplingHook::OnDataChanged( DataUpdateType_t type )
{
	BaseClass::OnDataChanged( type );

	UpdateHookSound();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFGrapplingHook::StartHookSound()
{
	StopHookSound();

	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
	CLocalPlayerFilter filter;
	m_pHookSound = controller.SoundCreate( filter, entindex(), "WeaponGrapplingHook.ReelStart" );
	controller.Play( m_pHookSound, 1.0, 100 );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFGrapplingHook::StopHookSound()
{
	if ( m_pHookSound )
	{
		CSoundEnvelopeController::GetController().SoundDestroy( m_pHookSound );
		m_pHookSound = NULL;
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFGrapplingHook::UpdateHookSound()
{
	CTFPlayer *pOwner = GetTFPlayerOwner();
	if ( pOwner )
	{
		bool bLatched = pOwner->GetGrapplingHookTarget() != NULL && m_hProjectile != NULL;
		if ( m_bLatched != bLatched )
		{
			if ( !m_bLatched )
			{
				StartHookSound();
			}
			else
			{
				StopHookSound();

				CLocalPlayerFilter filter;
				EmitSound( filter, entindex(), "WeaponGrapplingHook.ReelStop" );
			}

			m_bLatched = bLatched;
		}
	}
}


//-----------------------------------------------------------------------------
// CEquipGrapplingHookNotification
//-----------------------------------------------------------------------------
void CEquipGrapplingHookNotification::Accept()
{
	m_bHasTriggered = true;
		
	CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory();
	if ( !pLocalInv )
	{
		MarkForDeletion();
		return;
	}

	C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
	if ( !pLocalPlayer )
	{
		MarkForDeletion();
		return;
	}

	// try to equip non-stock-grapplinghook first
	/*static CSchemaItemDefHandle pItemDef_GrapplingHook( "TF_WEAPON_GRAPPLINGHOOK" );

	Assert( pItemDef_GrapplingHook );

	CEconItemView *pGrapplingHook = NULL;

	if ( pItemDef_GrapplingHook )
	{
		for ( int i = 0 ; i < pLocalInv->GetItemCount() ; ++i )
		{
			CEconItemView *pItem = pLocalInv->GetItem( i );
			Assert( pItem );
			if ( pItem->GetItemDefinition() == pItemDef_GrapplingHook )
			{
				pGrapplingHook = pItem;
				break;
			}
		}
	}*/

	// Default item becomes a grappling hook in this mode
	itemid_t iItemId = INVALID_ITEM_ID;
	/*if ( pGrapplingHook )
	{
		iItemId = pGrapplingHook->GetItemID();
	}*/
	
	if ( iItemId == INVALID_ITEM_ID )
	{
		iItemId = 0;

		static CSchemaItemDefHandle pItemDef_Grapple( "TF_WEAPON_GRAPPLINGHOOK" );
		CEconItemView *pDefaultGrapple = TFInventoryManager()->GetBaseItemForClass( pLocalPlayer->GetPlayerClass()->GetClassIndex(), LOADOUT_POSITION_ACTION );
		if ( pDefaultGrapple )
		{
			if ( pDefaultGrapple->GetItemDefinition() == pItemDef_Grapple )
			{
				iItemId = pDefaultGrapple->GetItemID();
			}
		}
	}

	TFInventoryManager()->EquipItemInLoadout( pLocalPlayer->GetPlayerClass()->GetClassIndex(), LOADOUT_POSITION_ACTION, iItemId );
	
	// Tell the GC to tell server that we should respawn if we're in a respawn room
	GCSDK::CGCMsg< GCSDK::MsgGCEmpty_t > msg( k_EMsgGCRespawnPostLoadoutChange );
	GCClientSystem()->BSendMessage( msg );

	MarkForDeletion();
}

//===========================================================================================
void CEquipGrapplingHookNotification::UpdateTick()
{
	C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
	if ( pLocalPlayer )
	{
		CTFGrapplingHook *pGrapplingHook = dynamic_cast<CTFGrapplingHook*>( pLocalPlayer->Weapon_OwnsThisID( TF_WEAPON_GRAPPLINGHOOK ) );
		if ( pGrapplingHook )
		{
			MarkForDeletion();
		}
	}
}

#endif // CLIENT_DLL