//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================

#include "cbase.h"
#include "tf_weapon_bat.h"
#include "decals.h"

// Client specific.
#ifdef CLIENT_DLL
#include "c_basedoor.h"
#include "c_tf_player.h"
#include "IEffects.h"
#include "bone_setup.h"
#include "c_tf_gamestats.h"
// Server specific.
#else
#include "doors.h"
#include "tf_player.h"
#include "tf_ammo_pack.h"
#include "tf_gamestats.h"
#include "ilagcompensationmanager.h"
#include "collisionutils.h"
#include "particle_parse.h"
#include "tf_projectile_base.h"
#include "tf_gamerules.h"
#endif

const float DEFAULT_ORNAMENT_EXPLODE_RADIUS = 50.0f;
const float DEFAULT_ORNAMENT_EXPLODE_DAMAGE_MULT = 0.9f;

//=============================================================================
//
// Weapon Bat tables.
//

// TFBat --
IMPLEMENT_NETWORKCLASS_ALIASED( TFBat, DT_TFWeaponBat )

BEGIN_NETWORK_TABLE( CTFBat, DT_TFWeaponBat )
END_NETWORK_TABLE()

BEGIN_PREDICTION_DATA( CTFBat )
END_PREDICTION_DATA()

LINK_ENTITY_TO_CLASS( tf_weapon_bat, CTFBat );
PRECACHE_WEAPON_REGISTER( tf_weapon_bat );
// -- TFBat


// TFBat_Fish --
IMPLEMENT_NETWORKCLASS_ALIASED( TFBat_Fish, DT_TFWeaponBat_Fish )

BEGIN_NETWORK_TABLE( CTFBat_Fish, DT_TFWeaponBat_Fish )
END_NETWORK_TABLE()

BEGIN_PREDICTION_DATA( CTFBat_Fish )
END_PREDICTION_DATA()

LINK_ENTITY_TO_CLASS( tf_weapon_bat_fish, CTFBat_Fish );
PRECACHE_WEAPON_REGISTER( tf_weapon_bat_fish );
// -- TFBat_Fish


// TFBat_Wood --
IMPLEMENT_NETWORKCLASS_ALIASED( TFBat_Wood, DT_TFWeaponBat_Wood )

BEGIN_NETWORK_TABLE( CTFBat_Wood, DT_TFWeaponBat_Wood )
END_NETWORK_TABLE()

BEGIN_PREDICTION_DATA( CTFBat_Wood )
END_PREDICTION_DATA()

LINK_ENTITY_TO_CLASS( tf_weapon_bat_wood, CTFBat_Wood );
PRECACHE_WEAPON_REGISTER( tf_weapon_bat_wood );
// -- TFBat_Wood


// CTFBat_Giftwrap --
IMPLEMENT_NETWORKCLASS_ALIASED( TFBat_Giftwrap, DT_TFWeaponBat_Giftwrap )

BEGIN_NETWORK_TABLE( CTFBat_Giftwrap, DT_TFWeaponBat_Giftwrap )
END_NETWORK_TABLE()

BEGIN_PREDICTION_DATA( CTFBat_Giftwrap )
END_PREDICTION_DATA()

LINK_ENTITY_TO_CLASS( tf_weapon_bat_giftwrap, CTFBat_Giftwrap );
PRECACHE_WEAPON_REGISTER( tf_weapon_bat_giftwrap );
// -- CTFBat_Giftwrap


// TFStunBall --
IMPLEMENT_NETWORKCLASS_ALIASED( TFStunBall, DT_TFProjectile_StunBall )
BEGIN_NETWORK_TABLE( CTFStunBall, DT_TFProjectile_StunBall )
END_NETWORK_TABLE()

LINK_ENTITY_TO_CLASS( tf_projectile_stun_ball, CTFStunBall );
PRECACHE_WEAPON_REGISTER( tf_projectile_stun_ball );

#define TF_WEAPON_STUNBALL_VM_MODEL			"models/weapons/v_models/v_baseball.mdl"
#define TF_WEAPON_STUNBALL_MODEL			"models/weapons/w_models/w_baseball.mdl"

#if defined( GAME_DLL )
ConVar tf_scout_stunball_base_duration( "tf_scout_stunball_base_duration", "6.0", FCVAR_DEVELOPMENTONLY );
ConVar tf_scout_stunball_base_speed( "tf_scout_stunball_base_speed", "3000", FCVAR_DEVELOPMENTONLY );
ConVar sv_proj_stunball_damage( "sv_proj_stunball_damage", "15", FCVAR_DEVELOPMENTONLY );
#endif
// -- TFStunBall


// CTFBall_Ornament --
IMPLEMENT_NETWORKCLASS_ALIASED( TFBall_Ornament, DT_TFProjectileBall_Ornament )
BEGIN_NETWORK_TABLE( CTFBall_Ornament, DT_TFProjectileBall_Ornament )
END_NETWORK_TABLE()

LINK_ENTITY_TO_CLASS( tf_projectile_ball_ornament, CTFBall_Ornament );
PRECACHE_WEAPON_REGISTER( tf_projectile_ball_ornament );

#define TF_WEAPON_BALL_ORNAMENT_VM_MODEL		"models/weapons/c_models/c_xms_festive_ornament.mdl"
#define TF_WEAPON_BALL_ORNAMENT_MODEL			"models/weapons/c_models/c_xms_festive_ornament.mdl"

#if defined( GAME_DLL )
//ConVar tf_scout_stunball_base_duration( "tf_scout_stunball_base_duration", "6.0", FCVAR_DEVELOPMENTONLY );
#endif
// -- CTFBall_Ornament



static string_t s_iszTrainName;

//=============================================================================
#define STUNBALL_TRAIL_ALPHA						128


//=============================================================================
//
// CTFBat
//

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTFBat::CTFBat()
{
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFBat::Smack( void )
{
	BaseClass::Smack();

#ifdef GAME_DLL
	if ( BatDeflects() )
	{
#ifdef TF_RAID_MODE
		if ( TFGameRules()->IsRaidMode() )
		{
		}
		else
#endif // TF_RAID_MODE
		{
			DeflectProjectiles();
		}
	}
#endif
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFBat::PlayDeflectionSound( bool bPlayer )
{
	WeaponSound( MELEE_HIT_WORLD );
}

//=============================================================================
//
// CTFBat_Wood
//

CTFBat_Wood::CTFBat_Wood()
{
	m_iEnemyBallID = 0;
#ifdef CLIENT_DLL
	m_hStunBallVM = NULL;
#endif
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
ConVar tf_scout_bat_launch_delay( "tf_scout_bat_launch_delay", "0.1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFBat_Wood::LaunchBallThink( void )
{
	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
		return;

	LaunchBall();

#ifdef GAME_DLL
	pPlayer->SpeakWeaponFire( MP_CONCEPT_BAT_BALL );
	CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() );
#endif
#ifdef CLIENT_DLL
	C_CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() );
#endif
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFBat_Wood::SecondaryAttackAnim( CTFPlayer *pPlayer )
{
	pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_SECONDARY );
}

// SERVER ONLY --
#ifdef GAME_DLL

//-----------------------------------------------------------------------------
// Purpose: Calculate the ball's initial position, angle, and velocity.
//-----------------------------------------------------------------------------
void CTFBat_Wood::GetBallDynamics( Vector& vecLoc, QAngle& vecAngles, Vector& vecVelocity, AngularImpulse& angImpulse, CTFPlayer* pPlayer )
{
	Vector vecForward, vecUp;
	AngleVectors( pPlayer->EyeAngles(), &vecForward, NULL, &vecUp );
	vecLoc    = pPlayer->GetAbsOrigin() + pPlayer->GetModelScale() * ( Vector( 0, 0, 50 ) + vecForward * 32.f );
	vecAngles = pPlayer->GetAbsAngles();

	// Calculate the initial impulse on the item.
	vecVelocity = Vector( 0.0f, 0.0f, 0.0f );
	vecVelocity += vecForward * 10;
	vecVelocity += vecUp * 1;
	VectorNormalize( vecVelocity );
	vecVelocity *= tf_scout_stunball_base_speed.GetInt();

	angImpulse = AngularImpulse( 0, random->RandomFloat( 0, 100 ), 0 );
}

// -- SERVER ONLY
#endif

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFBat_Wood::SecondaryAttack( void )
{
	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
		return;

	if ( !CanAttack() )
		return;

	if ( m_flNextPrimaryAttack > gpGlobals->curtime )
		return;

	// Do we have any balls? If so, use them.
	int iBallCount = pPlayer->GetAmmoCount( TF_AMMO_GRENADES1 );
	if ( (iBallCount > 0) && CanCreateBall( pPlayer ) )
	{
		SecondaryAttackAnim( pPlayer );
		SendWeaponAnim( ACT_VM_PRIMARYATTACK );

		SetContextThink( &CTFBat_Wood::LaunchBallThink, gpGlobals->curtime + tf_scout_bat_launch_delay.GetFloat(), "LAUNCH_BALL_THINK" );

		m_flNextPrimaryAttack = gpGlobals->curtime + 0.25;

#ifdef GAME_DLL
		if ( pPlayer->m_Shared.IsStealthed() )
		{
			pPlayer->RemoveInvisibility();
		}
#endif // GAME_DLL
	}
}

//-----------------------------------------------------------------------------
// Purpose: Client Only. Show the stunball view model if necessary.
//-----------------------------------------------------------------------------
#ifdef CLIENT_DLL
void CTFBat_Wood::SetWeaponVisible( bool visible )
{
	BaseClass::SetWeaponVisible( visible );

	if ( !m_hStunBallVM )
		return;

	if ( visible )
	{
		m_hStunBallVM->RemoveEffects( EF_NODRAW );
	}
	else
	{
		m_hStunBallVM->AddEffects( EF_NODRAW );
	}
}
#endif

#ifdef CLIENT_DLL

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFBat_Wood::OnDataChanged( DataUpdateType_t updateType )
{
	BaseClass::OnDataChanged( updateType );

	bool bLocalPlayerAmmo = true;
	if ( GetPlayerOwner() == C_BasePlayer::GetLocalPlayer() )
	{
		bLocalPlayerAmmo = GetPlayerOwner()->GetAmmoCount( TF_AMMO_GRENADES1 ) > 0;
	}

	if ( IsCarrierAlive() && ( WeaponState() == WEAPON_IS_ACTIVE ) && bLocalPlayerAmmo == true )
	{
		AddBallChild();
	}
	else 
	{
		RemoveBallChild();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Client Only. Show the stunball view model if necessary.
//-----------------------------------------------------------------------------
void CTFBat_Wood::AddBallChild( void )
{
	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
		return;

	if ( !pPlayer->IsLocalPlayer() )
		return;

	if ( !pPlayer->GetViewModel() )
		return;

	if ( m_hStunBallVM )
		return;

	CTFViewModel* pBall = new class CTFViewModel();
	if ( pBall != NULL )
	{
		pBall->InitializeAsClientEntity( GetBallViewModelName(), RENDER_GROUP_OPAQUE_ENTITY );
		pBall->SetAbsOrigin( pPlayer->GetViewModel()->GetAbsOrigin() );
		pBall->SetModel( GetBallViewModelName() );
		pBall->m_nSkin = ( pPlayer->GetTeamNumber() == TF_TEAM_BLUE ) ? 1 : 0;

		CStudioHdr *pStudioHdr = pPlayer->GetViewModel()->GetModelPtr();
		if ( pStudioHdr )
		{
			int iAttachment = Studio_FindAttachment( pStudioHdr, "weapon_bone_L" ) + 1;
			pBall->SetParent( pPlayer->GetViewModel(), iAttachment );
		}
		pBall->AddEffects( EF_BONEMERGE );
		pBall->SetMoveType( MOVETYPE_NONE );
		pBall->AddSolidFlags( FSOLID_NOT_SOLID );
		m_hStunBallVM.Set( pBall );
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFBat_Wood::Drop( const Vector &vecVelocity )
{
	BaseClass::Drop( vecVelocity );

	RemoveBallChild();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFBat_Wood::WeaponReset( void )
{
	RemoveBallChild();

	BaseClass::WeaponReset();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFBat_Wood::UpdateOnRemove( void )
{
	RemoveBallChild();

	BaseClass::UpdateOnRemove();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFBat_Wood::RemoveBallChild()
{
	if ( m_hStunBallVM )
	{
		m_hStunBallVM->Remove();
		m_hStunBallVM = NULL;
	}
}

#endif

//-----------------------------------------------------------------------------
// Purpose: Determines if there is space to create a ball.
//-----------------------------------------------------------------------------
bool CTFBat_Wood::CanCreateBall( CTFPlayer* pPlayer )
{
	int iWeaponMod = 0;
	CALL_ATTRIB_HOOK_INT( iWeaponMod, set_weapon_mode );
	if ( iWeaponMod == 0 )
		return false;

	if ( pPlayer->GetWaterLevel() == WL_Eyes )
		return false;

	Vector vecForward, vecUp;
	AngleVectors( pPlayer->EyeAngles(), &vecForward, NULL, &vecUp );
	Vector vecBallStart = pPlayer->GetAbsOrigin() + Vector( 0, 0, 50 );
	Vector vecBallEnd   = vecBallStart + vecForward * 32.f;
	
	// Trace out and see if we hit a wall.
	trace_t trace;
	CTraceFilterSimple traceFilter( this, COLLISION_GROUP_NONE );
	UTIL_TraceHull( vecBallStart, vecBallEnd, -Vector(8,8,8), Vector(8,8,8), MASK_SOLID_BRUSHONLY, &traceFilter, &trace );
	if ( trace.DidHitWorld() || trace.startsolid )
		return false;
	else
	{
		if ( trace.m_pEnt )
		{
			// Don't let the player bat through doors.
			CBaseDoor *pDoor = dynamic_cast<CBaseDoor*>( trace.m_pEnt );
			if ( pDoor )
				return false;
		}
		return true;
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFBat_Wood::LaunchBall( void )
{
	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
		return;

#if GAME_DLL
	// Make a ball.
	CBaseEntity* pBall = CreateBall();
	if ( !pBall )
		return;

	if ( IsCurrentAttackACrit() )
	{
		WeaponSound( BURST );
	}
	WeaponSound( SPECIAL2 );
	pPlayer->RemoveAmmo( 1, TF_AMMO_GRENADES1 );
#endif

	StartEffectBarRegen();
}

// SERVER ONLY --
#ifdef GAME_DLL

//-----------------------------------------------------------------------------
// Purpose: The wooden bat creates a baseball that stuns whomever it hits.
//-----------------------------------------------------------------------------
CBaseEntity* CTFBat_Wood::CreateBall( void )
{
	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
		return NULL;

	// Do another check here, as the player may have moved to an invalid position
	// since the first check (0.1 seconds ago).  This fixes the ball sometimes
	// going through thin geometry, such as windows and spawn blockers.
	if ( !CanCreateBall( pPlayer ) )
		return NULL;

	// Determine the ball's initial location, angles, and velocity.
	Vector vecLocation, vecVelocity;
	QAngle vecAngles;
	AngularImpulse angImpulse;
	GetBallDynamics( vecLocation, vecAngles, vecVelocity, angImpulse, pPlayer );

	// Create a stun ball.
	CTFStunBall* pBall = CTFStunBall::Create( vecLocation, vecAngles, pPlayer );
	Assert( pBall );
	if ( !pBall )
		return NULL;

	CalcIsAttackCritical();

	pBall->m_iOriginalOwnerID = m_iEnemyBallID;
	m_iEnemyBallID = 0;

	pBall->SetCritical( IsCurrentAttackACrit() );
	pBall->InitGrenade( vecVelocity, angImpulse, pPlayer, GetTFWpnData() );
	pBall->SetLauncher( this );
	pBall->SetOwnerEntity( pPlayer );
	pBall->SetInitialSpeed( tf_scout_stunball_base_speed.GetInt() );

	return pBall;
}

// -- SERVER ONLY
#endif

//-----------------------------------------------------------------------------
// Purpose: Play pickup anim when we grab a new ball.
//-----------------------------------------------------------------------------
void CTFBat_Wood::PickedUpBall( void )
{
	if ( WeaponState() == WEAPON_IS_ACTIVE )
	{
		SendWeaponAnim( ACT_VM_PULLBACK_SPECIAL );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Play animation appropriate to ball status.
//-----------------------------------------------------------------------------
bool CTFBat_Wood::SendWeaponAnim( int iActivity )
{
	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
		return BaseClass::SendWeaponAnim( iActivity );

	if ( pPlayer->GetAmmoCount( TF_AMMO_GRENADES1 ) > 0 )
	{
		switch ( iActivity )
		{
		case ACT_VM_DRAW:
			iActivity = ACT_VM_DRAW_SPECIAL;
			break;
		case ACT_VM_HOLSTER:
			iActivity = ACT_VM_HOLSTER_SPECIAL;
			break;
		case ACT_VM_IDLE:
			iActivity = ACT_VM_IDLE_SPECIAL;
			break;
		case ACT_VM_PULLBACK:
			iActivity = ACT_VM_PULLBACK_SPECIAL;
			break;
		case ACT_VM_PRIMARYATTACK:
			iActivity = ACT_VM_PRIMARYATTACK_SPECIAL;
			break;
		case ACT_VM_SECONDARYATTACK:
			iActivity = ACT_VM_PRIMARYATTACK_SPECIAL;
			break;
		case ACT_VM_HITCENTER:
			iActivity = ACT_VM_HITCENTER_SPECIAL;
			break;
		case ACT_VM_SWINGHARD:
			iActivity = ACT_VM_SWINGHARD_SPECIAL;
			break;
		case ACT_VM_IDLE_TO_LOWERED:
			iActivity = ACT_VM_IDLE_TO_LOWERED_SPECIAL;
			break;
		case ACT_VM_IDLE_LOWERED:
			iActivity = ACT_VM_IDLE_LOWERED_SPECIAL;
			break;
		case ACT_VM_LOWERED_TO_IDLE:
			iActivity = ACT_VM_LOWERED_TO_IDLE_SPECIAL;
			break;
		default:
			break;
		}
	}

	return BaseClass::SendWeaponAnim( iActivity );
}

//=============================================================================
//
// CTFStunBall
//

// SERVER ONLY --
#ifdef GAME_DLL

CTFStunBall::CTFStunBall()
{
	s_iszTrainName = AllocPooledString( "models/props_vehicles/train_enginecar.mdl" );
	m_iOriginalOwnerID = 0;
	m_pBallTrail = NULL;
	m_flBallTrailLife = 1.0f;
}

//-----------------------------------------------------------------------------
// Purpose: Static entity factory.
//-----------------------------------------------------------------------------
CTFStunBall* CTFStunBall::Create( const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner )
{
	CTFStunBall* pBall = static_cast<CTFStunBall*>( CBaseAnimating::CreateNoSpawn( "tf_projectile_stun_ball", vecOrigin, vecAngles, pOwner ) );
	if ( pBall )
	{
		DispatchSpawn( pBall );
	}

	return pBall;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFStunBall::Precache( void )
{
	PrecacheModel( GetBallModelName() );
	PrecacheModel( GetBallViewModelName() );
	PrecacheModel( "effects/baseballtrail_red.vmt" );
	PrecacheModel( "effects/baseballtrail_blu.vmt" );

	BaseClass::Precache();
}


//-----------------------------------------------------------------------------
const char *CTFStunBall::GetBallModelName( void ) const
{
	return TF_WEAPON_STUNBALL_MODEL;
}


//-----------------------------------------------------------------------------
const char *CTFStunBall::GetBallViewModelName( void ) const
{
	return TF_WEAPON_STUNBALL_VM_MODEL;
}


//-----------------------------------------------------------------------------
// Purpose: Sets up initial properties.
//-----------------------------------------------------------------------------
void CTFStunBall::Spawn( void )
{
	BaseClass::Spawn();

	SetModel( GetBallModelName() );
	VPhysicsDestroyObject();
	VPhysicsInitNormal( SOLID_BBOX, 0, false );

	AddSolidFlags( FSOLID_TRIGGER );
	AddFlag( FL_GRENADE );

	SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
	m_takedamage = DAMAGE_NO;

	SetContextThink( &CBaseEntity::SUB_Remove, gpGlobals->curtime + 15, "DieContext" );

	// Draw the trail for the Baseball on spawn
	if ( !m_pBallTrail )
	{
		const char *pTrailTeamName = ( GetTeamNumber() == TF_TEAM_RED ) ? "effects/baseballtrail_red.vmt" : "effects/baseballtrail_blu.vmt";
		CSpriteTrail *pTempTrail = NULL;

		pTempTrail = CSpriteTrail::SpriteTrailCreate( pTrailTeamName, GetAbsOrigin(), true );
		pTempTrail->FollowEntity( this );
		pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, STUNBALL_TRAIL_ALPHA, kRenderFxNone );
		pTempTrail->SetStartWidth( 9 );
		pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) );
		pTempTrail->SetLifeTime( 0.4 );
		pTempTrail->TurnOn();
		pTempTrail->SetAttachment( this, 0 );
		m_pBallTrail = pTempTrail;
		SetContextThink( &CTFStunBall::RemoveBallTrail, gpGlobals->curtime + 3, "FadeBallTrail");
	}

}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFStunBall::Explode( trace_t *pTrace, int bitsDamageType )
{
	if ( !IsAllowedToExplode() )
		return;

	BaseClass::Explode( pTrace, bitsDamageType );
}

//-----------------------------------------------------------------------------
// Purpose: Stun the person we smashed into.
//-----------------------------------------------------------------------------
#define FLIGHT_TIME_TO_MAX_STUN	1.f
void CTFStunBall::ApplyBallImpactEffectOnVictim( CBaseEntity *pOther )
{
	if ( !pOther || !pOther->IsPlayer() )
		return;

	CTFPlayer* pPlayer = ToTFPlayer( pOther );
	if ( !pPlayer )
		return;

	CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
	if ( !pOwner )
		return;

	if ( m_bTouched )
		return;

	// Can't stun an invul player.
	if ( pPlayer->m_Shared.IsInvulnerable() || pPlayer->m_Shared.InCond( TF_COND_INVULNERABLE_WEARINGOFF ) )
		return;

	// We have a more intense stun based on our travel time.
	float flLifeTime = MIN( gpGlobals->curtime - m_flCreationTime, FLIGHT_TIME_TO_MAX_STUN );
	float flLifeTimeRatio = flLifeTime / FLIGHT_TIME_TO_MAX_STUN;
	if ( flLifeTimeRatio > 0.1f )
	{

		float flStun = 0.5f;
		float flStunDuration = tf_scout_stunball_base_duration.GetFloat() * flLifeTimeRatio;
		if ( IsCritical() )
			flStunDuration += 2.0; // Extra two seconds of effect time if we're a critical hit.
		int iStunFlags = TF_STUN_LOSER_STATE | TF_STUN_MOVEMENT;
		if ( flLifeTimeRatio >= 1.f )
		{
			flStunDuration += 1.0;
			iStunFlags = TF_STUN_CONTROLS;
			iStunFlags |= TF_STUN_SPECIAL_SOUND;
			CTF_GameStats.Event_PlayerStunBall( pOwner, true );
		}
		else
		{
			CTF_GameStats.Event_PlayerStunBall( pOwner, false );
		}

		// Adjust stun amount and flags if we're hitting a boss or scaled enemy
		if ( TFGameRules() && TFGameRules()->GameModeUsesMiniBosses() && ( pPlayer->IsMiniBoss() || pPlayer->GetModelScale() > 1.0f ) )
		{
			// If max range, freeze them in place - otherwise adjust it based on distance
			flStun = flLifeTimeRatio >= 1.f ? 1.f : RemapValClamped( flLifeTimeRatio, 0.1f, 0.99f, 0.5f, 0.75 );
			iStunFlags = flLifeTimeRatio >= 1.f ? ( TF_STUN_SPECIAL_SOUND | TF_STUN_MOVEMENT ) : TF_STUN_MOVEMENT; 
		}

		if ( pPlayer->GetWaterLevel() != WL_Eyes )
		{
			pPlayer->m_Shared.StunPlayer( flStunDuration, flStun, iStunFlags, pOwner );
			if ( pPlayer->GetUserID() == m_iOriginalOwnerID )
			{
				// Holy crap! We just stunned a scout with their own ball.
				// Give the player an achievement for this.
				if ( pOwner->IsPlayerClass( TF_CLASS_SCOUT ) )
				{
					pOwner->AwardAchievement( ACHIEVEMENT_TF_SCOUT_STUN_SCOUT_WITH_THEIR_BALL );
				}
			}
		}
	}

	// Give 'em a love tap.
	const trace_t *pTrace = &CBaseEntity::GetTouchTrace();
	trace_t *pNewTrace = const_cast<trace_t*>( pTrace );

	CBaseEntity *pInflictor = GetLauncher();
	CTakeDamageInfo info;
	info.SetAttacker( GetOwnerEntity() );
	info.SetInflictor( pInflictor ); 
	info.SetWeapon( pInflictor );
	info.SetDamage( GetDamage() );
	info.SetDamageCustom( TF_DMG_CUSTOM_BASEBALL );
	info.SetDamageForce( GetDamageForce() );
	info.SetDamagePosition( GetAbsOrigin() );
	int iDamageType = GetDamageType();
	if ( IsCritical() )
		iDamageType |= DMG_CRITICAL;
	info.SetDamageType( iDamageType );

	// Hurt 'em.
	Vector dir;
	AngleVectors( GetAbsAngles(), &dir );
	pPlayer->DispatchTraceAttack( info, dir, pNewTrace );
	ApplyMultiDamage();

	// Make this ball fade faster now that it's hit something.
	SetContextThink( &CBaseEntity::SUB_Remove, gpGlobals->curtime + 4, "DieContext" );

	m_bTouched = true;
}

float CTFStunBall::GetDamage( void )
{
	return sv_proj_stunball_damage.GetFloat();
}

Vector CTFStunBall::GetDamageForce( void )
{
	Vector vecVelocity = GetAbsVelocity();
	IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
	if ( pPhysicsObject )
	{
		pPhysicsObject->GetVelocity( &vecVelocity, NULL );
		VectorNormalize( vecVelocity );
	}

	return (vecVelocity * GetDamage());
}

//-----------------------------------------------------------------------------
// Purpose: We hit something.
//-----------------------------------------------------------------------------
void CTFStunBall::PipebombTouch( CBaseEntity *pOther )
{
	CTFPlayer* pOwner = ToTFPlayer( GetOwnerEntity() );
	if ( !pOwner )
		return;

	if ( !pOther || !pOther->IsSolid() || pOther->IsSolidFlagSet( FSOLID_VOLUME_CONTENTS ) )
	{
		pOwner->SpeakConceptIfAllowed( MP_CONCEPT_BALL_MISSED );
		return;
	}

	// Go away if we're hit by a moving train.
	if ( pOther->GetModelName() == s_iszTrainName && ( pOther->GetAbsVelocity().LengthSqr() > 1.0f ) )
	{
		UTIL_Remove( this );
		return;
	}

	// Go away if we hit the skybox.
	trace_t pTrace;
	Vector velDir = GetAbsVelocity();
	VectorNormalize( velDir );
	Vector vecSpot = GetAbsOrigin() - velDir * 32;
	UTIL_TraceLine( vecSpot, vecSpot + velDir * 64, MASK_SOLID, this, COLLISION_GROUP_NONE, &pTrace );
	if ( pTrace.fraction < 1.0 && pTrace.surface.flags & SURF_SKY )
	{
		UTIL_Remove( this );
		return;
	}

	// Ignore things that aren't players.
	if ( !pOther->IsPlayer() )
		return;

	// If we hit a scout, pickup as ammo
	if ( m_bTouched )
	{
		CTFPlayer* pPlayer = ToTFPlayer( pOther );
		if ( pPlayer && pPlayer->IsPlayerClass( TF_CLASS_SCOUT ) &&
			(pPlayer->GetAmmoCount( TF_AMMO_GRENADES1 ) < pPlayer->GetMaxAmmo( TF_AMMO_GRENADES1 )) )
		{
			pPlayer->GiveAmmo( 1, TF_AMMO_GRENADES1 );
			RemoveBallTrail();
			UTIL_Remove( this );

			CTFBat_Wood *pBat = (CTFBat_Wood *) pPlayer->Weapon_OwnsThisID( TF_WEAPON_BAT_WOOD );
			if ( pBat )
			{
				// If this ball came from an enemy scout, remember who they were...
				if ( pPlayer->GetTeamNumber() != GetTeamNumber() )
				{
					if ( pOwner )
					{
						pBat->m_iEnemyBallID = pOwner->GetUserID();
					}
				}

				// If we have the bat up, we need to play the correct anim.
				pBat->PickedUpBall();
			}

			// Say something.
			pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_GRAB_BALL, (pOther->GetTeamNumber() == GetTeamNumber()) ? "my_team:1" : "my_team:0" );
		}
		return;
	}

	if ( pOther == GetThrower() )
		return;

	if ( !InSameTeam( pOther ) && pOther->m_takedamage != DAMAGE_NO )
	{
		ApplyBallImpactEffectOnVictim( pOther );
	}
}

//-----------------------------------------------------------------------------
// Purpose: We hit something.
//-----------------------------------------------------------------------------
void CTFStunBall::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
{
	CTFPlayer* pOwner = ToTFPlayer( GetOwnerEntity() );
	bool bWasTouched = m_bTouched;
	BaseClass::VPhysicsCollision( index, pEvent );
	if ( pOwner && !bWasTouched && m_bTouched )
	{
		pOwner->SpeakConceptIfAllowed( MP_CONCEPT_BALL_MISSED );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Fade and kill the trail
//-----------------------------------------------------------------------------
void CTFStunBall::RemoveBallTrail( void )
{
	if (!m_pBallTrail)
		return;

	if (m_pBallTrail)
	{
		if (m_flBallTrailLife <= 0)
		{
			UTIL_Remove( m_pBallTrail);
			m_flBallTrailLife = 1.0f;
		}
		else	
		{
			float fAlpha = STUNBALL_TRAIL_ALPHA * m_flBallTrailLife;

			CSpriteTrail *pTempTrail = dynamic_cast< CSpriteTrail*>( m_pBallTrail.Get() );

			if ( pTempTrail )
			{
				pTempTrail->SetBrightness( int(fAlpha) );
			}

			m_flBallTrailLife = m_flBallTrailLife - 0.1f;
			SetContextThink( &CTFStunBall::RemoveBallTrail, gpGlobals->curtime + 0.05, "FadeBallTrail");
		}
	}
}

// -- SERVER ONLY
#endif

#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
const char *CTFStunBall::GetTrailParticleName( void )
{
	int iTeamNumber = GetTeamNumber();

	if ( GetDeflected() )
	{
		CTFPlayer *pOwner =  ToTFPlayer( GetDeflectOwner() );

		if ( pOwner )
		{
			iTeamNumber = pOwner->GetTeamNumber();
		}
	}
	if ( iTeamNumber == TF_TEAM_BLUE )
	{
		return "stunballtrail_blue";
	}
	else
	{
		return "stunballtrail_red";
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFStunBall::CreateTrailParticles( void )
{
	if ( pEffectTrail )
	{
		ParticleProp()->StopEmission( pEffectTrail );
	}
	if ( pEffectCrit )
	{
		ParticleProp()->StopEmission( pEffectCrit );
	}
	pEffectTrail = ParticleProp()->Create( GetTrailParticleName(), PATTACH_ABSORIGIN_FOLLOW );
	int iTeamNumber = GetTeamNumber();

	if ( GetDeflected() )
	{
		CTFPlayer *pOwner =  ToTFPlayer( GetDeflectOwner() );

		if ( pOwner )
		{
			iTeamNumber = pOwner->GetTeamNumber();
		}
	}
	if ( m_bCritical )
	{
		if ( iTeamNumber == TF_TEAM_BLUE )
		{
			pEffectCrit = ParticleProp()->Create( "stunballtrail_blue_crit", PATTACH_ABSORIGIN_FOLLOW );
			
		}
		else
		{
			pEffectCrit = ParticleProp()->Create( "stunballtrail_red_crit", PATTACH_ABSORIGIN_FOLLOW );
		}
	}
}
#endif


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFBat_Giftwrap::Spawn( void )
{
	BaseClass::Spawn();

	m_nSkin = ( GetTeamNumber() == TF_TEAM_BLUE ) ? 1 : 0;
}


#ifdef GAME_DLL
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CBaseEntity *CTFBat_Giftwrap::CreateBall( void )
{
	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
		return NULL;

	// Do another check here, as the player may have moved to an invalid position
	// since the first check (0.1 seconds ago).  This fixes the ball sometimes
	// going through thin geometry, such as windows and spawn blockers.
	if ( !CanCreateBall( pPlayer ) )
		return NULL;

	// Determine the ball's initial location, angles, and velocity.
	Vector vecLocation, vecVelocity;
	QAngle vecAngles;
	AngularImpulse angImpulse;
	GetBallDynamics( vecLocation, vecAngles, vecVelocity, angImpulse, pPlayer );

	// Create the ornament ball.
	CTFBall_Ornament *pBall = CTFBall_Ornament::Create( vecLocation, vecAngles, pPlayer );
	Assert( pBall );
	if ( !pBall )
		return NULL;

	CalcIsAttackCritical();

	pBall->m_iOriginalOwnerID = m_iEnemyBallID;
	m_iEnemyBallID = 0;

	pBall->SetCritical( IsCurrentAttackACrit() );
	pBall->InitGrenade( vecVelocity, angImpulse, pPlayer, GetTFWpnData() );
	pBall->SetLauncher( this );
	pBall->SetOwnerEntity( pPlayer );
	pBall->SetInitialSpeed( tf_scout_stunball_base_speed.GetInt() );
	pBall->m_nSkin = ( pPlayer->GetTeamNumber() == TF_TEAM_BLUE ) ? 1 : 0;

	return pBall;
}


//-----------------------------------------------------------------------------
void CTFBall_Ornament::Precache( void )
{
	PrecacheScriptSound( "BallBuster.OrnamentImpactRange" );
	PrecacheScriptSound( "BallBuster.OrnamentImpact" );
	PrecacheScriptSound( "BallBuster.HitBall" );
	PrecacheScriptSound( "BallBuster.HitFlesh" );
	PrecacheScriptSound( "BallBuster.HitWorld" );
	PrecacheScriptSound( "BallBuster.DrawCatch" );
	PrecacheScriptSound( "BallBuster.Ornament_DrawCatch" );
	PrecacheScriptSound( "BallBuster.Ball_HitWorld" );

	BaseClass::Precache();
}


//-----------------------------------------------------------------------------
CTFBall_Ornament *CTFBall_Ornament::Create( const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner )
{
	CTFBall_Ornament* pBall = static_cast< CTFBall_Ornament * >( CBaseAnimating::CreateNoSpawn( "tf_projectile_ball_ornament", vecOrigin, vecAngles, pOwner ) );
	if ( pBall )
	{
		DispatchSpawn( pBall );
	}

	return pBall;
}


//-----------------------------------------------------------------------------
const char *CTFBall_Ornament::GetBallModelName( void ) const
{
	return TF_WEAPON_BALL_ORNAMENT_MODEL;
}


//-----------------------------------------------------------------------------
const char *CTFBall_Ornament::GetBallViewModelName( void ) const
{
	return TF_WEAPON_BALL_ORNAMENT_VM_MODEL;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFBall_Ornament::ApplyBallImpactEffectOnVictim( CBaseEntity *pOther )
{
	if ( !pOther || !pOther->IsPlayer() )
		return;

	CTFPlayer* pPlayer = ToTFPlayer( pOther );
	if ( !pPlayer )
		return;

	CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
	if ( !pOwner )
		return;

	if ( m_bTouched )
		return;

	// Can't bleed an invul player.
	if ( pPlayer->m_Shared.IsInvulnerable() || pPlayer->m_Shared.InCond( TF_COND_INVULNERABLE_WEARINGOFF ) )
		return;

	bool bIsCriticalHit = IsCritical();
	float flBleedTime = 5.0f;
	bool bIsLongRangeHit = false;

	// long distance hit is always a crit
	float flLifeTime = gpGlobals->curtime - m_flCreationTime;
	if ( flLifeTime >= FLIGHT_TIME_TO_MAX_STUN )
	{
		bIsCriticalHit = true;
		bIsLongRangeHit = true;
	}

	// just do the bleed effect directly since the bleed
	// attribute comes from the inflictor, which is the bat.
	pPlayer->m_Shared.MakeBleed( pOwner, (CTFBat_Giftwrap *)GetLauncher(), flBleedTime );

	// Apply particle effect to victim (the remaining effects happen inside Explode)
	DispatchParticleEffect( "xms_ornament_glitter", PATTACH_POINT_FOLLOW, pPlayer, "head" );

	// Give 'em a love tap.
	const trace_t *pTrace = &CBaseEntity::GetTouchTrace();
	trace_t *pNewTrace = const_cast<trace_t*>( pTrace );

	CBaseEntity *pInflictor = GetLauncher();
	CTakeDamageInfo info;
	info.SetAttacker( GetOwnerEntity() );
	info.SetInflictor( pInflictor ); 
	info.SetWeapon( pInflictor );
	info.SetDamage( GetDamage() );
	info.SetDamageCustom( TF_DMG_CUSTOM_BASEBALL );
	info.SetDamageForce( GetDamageForce() );
	info.SetDamagePosition( GetAbsOrigin() );
	int iDamageType = GetDamageType();
	if ( bIsCriticalHit )
		iDamageType |= DMG_CRITICAL;
	info.SetDamageType( iDamageType );

	// Hurt 'em.
	Vector dir;
	AngleVectors( GetAbsAngles(), &dir );
	pPlayer->DispatchTraceAttack( info, dir, pNewTrace );
	ApplyMultiDamage();

	// the ball shatters
	UTIL_Remove( this );

	m_bTouched = true;
}

void CTFBall_Ornament::PipebombTouch( CBaseEntity *pOther )
{
	CTFPlayer* pOwner = ToTFPlayer( GetOwnerEntity() );
	if ( !pOwner )
		return;

	// Go away if we're hit by a moving train.
	if ( pOther->GetModelName() == s_iszTrainName && ( pOther->GetAbsVelocity().LengthSqr() > 1.0f ) )
	{
		UTIL_Remove( this );
		return;
	}

	// Go away if we hit the skybox.
	trace_t pTrace;
	Vector velDir = GetAbsVelocity();
	VectorNormalize( velDir );
	Vector vecSpot = GetAbsOrigin() - velDir * 32;
	UTIL_TraceLine( vecSpot, vecSpot + velDir * 64, MASK_SOLID, this, COLLISION_GROUP_NONE, &pTrace );
	if ( pTrace.fraction < 1.0 && pTrace.surface.flags & SURF_SKY )
	{
		UTIL_Remove( this );
		return;
	}

	if ( pOther == GetThrower() )
		return;

	// Explode (does radius damage, triggers particles and sound effects).
	Explode( &pTrace, DMG_BLAST|DMG_PREVENT_PHYSICS_FORCE );

	if ( !InSameTeam( pOther ) && pOther->m_takedamage != DAMAGE_NO )
	{
		ApplyBallImpactEffectOnVictim( pOther );
	}
}

void CTFBall_Ornament::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
{
	BaseClass::VPhysicsCollision( index, pEvent );

	int otherIndex = !index;
	CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex];

	if ( !pHitEntity )
		return;

	// Break if we hit the world.
	if ( pHitEntity->IsWorld() )
	{
		// Explode immediately next frame. (Can't explode in the collision callback.)
		m_vCollisionVelocity = pEvent->preVelocity[index];
		SetContextThink( &CTFBall_Ornament::VPhysicsCollisionThink, gpGlobals->curtime, "OrnamentCollisionThink" );
	}
}

void CTFBall_Ornament::VPhysicsCollisionThink( void )
{
	trace_t pTrace;
	Vector velDir = m_vCollisionVelocity;
	VectorNormalize( velDir );
	Vector vecSpot = GetAbsOrigin() - velDir * 16;
	UTIL_TraceLine( vecSpot, vecSpot + velDir * 32, MASK_SOLID, this, COLLISION_GROUP_NONE, &pTrace );

	Explode( &pTrace, DMG_BLAST|DMG_PREVENT_PHYSICS_FORCE );
}

void CTFBall_Ornament::Explode( trace_t *pTrace, int bitsDamageType )
{
	// Create smashed glass particles when we explode
	CTFPlayer* pOwner = ToTFPlayer( GetOwnerEntity() );
	if ( pOwner && pOwner->GetTeamNumber() == TF_TEAM_RED )
	{
		DispatchParticleEffect( "xms_ornament_smash_red", GetAbsOrigin(), GetAbsAngles() );
	}
	else
	{
		DispatchParticleEffect( "xms_ornament_smash_blue", GetAbsOrigin(), GetAbsAngles() );
	}

	Vector vecOrigin = GetAbsOrigin();

	// sound effects
	EmitSound_t params;
	params.m_flSoundTime = 0;
	params.m_pflSoundDuration = 0;
	params.m_pSoundName = "BallBuster.OrnamentImpact";
	CPASFilter filter( vecOrigin );
	filter.RemoveRecipient( pOwner );
	EmitSound( filter, entindex(), params );
	CSingleUserRecipientFilter attackerFilter( pOwner );
	EmitSound( attackerFilter, pOwner->entindex(), params );

	// Explosion damage is some fraction of our base damage
	float flExplodeDamage = GetDamage() * DEFAULT_ORNAMENT_EXPLODE_DAMAGE_MULT;

	// Do radius damage
	Vector vecBlastForce(0.0f, 0.0f, 0.0f);
	CTakeDamageInfo info( this, GetThrower(), m_hLauncher, vecBlastForce, GetAbsOrigin(), flExplodeDamage, bitsDamageType, TF_DMG_CUSTOM_BASEBALL, &vecOrigin );
	CTFRadiusDamageInfo radiusinfo( &info, vecOrigin, DEFAULT_ORNAMENT_EXPLODE_RADIUS, nullptr, 0.0f, 0.0f );
	TFGameRules()->RadiusDamage( radiusinfo );

	UTIL_Remove( this );
}



#endif