//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:			The Medic's Medikit weapon
//					
//
// $Workfile:     $
// $Date:         $
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "in_buttons.h"
#include "engine/IEngineSound.h"
#include "tf_gamerules.h"
#include "tf_item.h"
#include "entity_capture_flag.h"

#if defined( CLIENT_DLL )
#include <vgui_controls/Panel.h>
#include <vgui/ISurface.h>
#include "particles_simple.h"
#include "c_tf_player.h"
#include "soundenvelope.h"
#include "tf_hud_mediccallers.h"
#include "c_tf_playerresource.h"
#include "prediction.h"
#else
#include "ndebugoverlay.h"
#include "tf_player.h"
#include "tf_team.h"
#include "tf_gamestats.h"
#include "ilagcompensationmanager.h"
#include "tf_obj.h"
#include "inetchannel.h"
#include "IEffects.h"
#include "baseprojectile.h"
#include "soundenvelope.h"
#include "effect_dispatch_data.h"
#include "func_respawnroom.h"
#endif

#include "tf_revive.h"
#include "tf_weapon_medigun.h"
#include "tf_weapon_shovel.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"


MedigunEffects_t g_MedigunEffects[MEDIGUN_NUM_CHARGE_TYPES] =
{
	{ TF_COND_INVULNERABLE,					TF_COND_INVULNERABLE_WEARINGOFF, "TFPlayer.InvulnerableOn",						"TFPlayer.InvulnerableOff" },	// MEDIGUN_CHARGE_INVULN = 0,
	{ TF_COND_CRITBOOSTED,					TF_COND_LAST,					 "TFPlayer.CritBoostOn",						"TFPlayer.CritBoostOff" },		// MEDIGUN_CHARGE_CRITICALBOOST,
	{ TF_COND_MEGAHEAL,						TF_COND_LAST,					 "TFPlayer.QuickFixInvulnerableOn",				"TFPlayer.MegaHealOff" },		// MEDIGUN_CHARGE_MEGAHEAL,
	{ TF_COND_MEDIGUN_UBER_BULLET_RESIST,	TF_COND_LAST,					 "WeaponMedigun_Vaccinator.InvulnerableOn",		"WeaponMedigun_Vaccinator.InvulnerableOff" },		// TF_COND_MEDIGUN_UBER_BULLET_RESIST,
	{ TF_COND_MEDIGUN_UBER_BLAST_RESIST,	TF_COND_LAST,					 "WeaponMedigun_Vaccinator.InvulnerableOn",		"WeaponMedigun_Vaccinator.InvulnerableOff" },		// TF_COND_MEDIGUN_UBER_BLAST_RESIST,
	{ TF_COND_MEDIGUN_UBER_FIRE_RESIST,		TF_COND_LAST,					 "WeaponMedigun_Vaccinator.InvulnerableOn",		"WeaponMedigun_Vaccinator.InvulnerableOff" },		// TF_COND_MEDIGUN_UBER_FIRE_RESIST,
};

struct MedigunResistConditions_t
{
	medigun_resist_types_t eResistType;
	ETFCond passiveCond;
	ETFCond uberCond;
};

MedigunResistConditions_t g_MedigunResistConditions[MEDIGUN_NUM_RESISTS] = 
{
	{ MEDIGUN_BULLET_RESIST,	TF_COND_MEDIGUN_SMALL_BULLET_RESIST,	TF_COND_MEDIGUN_UBER_BULLET_RESIST },
	{ MEDIGUN_BLAST_RESIST,		TF_COND_MEDIGUN_SMALL_BLAST_RESIST,		TF_COND_MEDIGUN_UBER_BLAST_RESIST },
	{ MEDIGUN_FIRE_RESIST,		TF_COND_MEDIGUN_SMALL_FIRE_RESIST,		TF_COND_MEDIGUN_UBER_FIRE_RESIST }
};

// Buff ranges
ConVar weapon_medigun_damage_modifier( "weapon_medigun_damage_modifier", "1.5", FCVAR_CHEAT | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Scales the damage a player does while being healed with the medigun." );
ConVar weapon_medigun_construction_rate( "weapon_medigun_construction_rate", "10", FCVAR_CHEAT | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Constructing object health healed per second by the medigun." );
ConVar weapon_medigun_charge_rate( "weapon_medigun_charge_rate", "40", FCVAR_CHEAT | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Amount of time healing it takes to fully charge the medigun." );
ConVar weapon_medigun_chargerelease_rate( "weapon_medigun_chargerelease_rate", "8", FCVAR_CHEAT | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Amount of time it takes the a full charge of the medigun to be released." );
ConVar weapon_medigun_resist_num_chunks( "weapon_medigun_resist_num_chunks", "4", FCVAR_CHEAT | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "How many uber bar chunks the vaccinator has." );
ConVar tf_vaccinator_uber_charge_rate_modifier( "tf_vaccinator_uber_charge_rate_modifier", "1.0", FCVAR_CHEAT | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY , "Vaccinator uber charge rate." );

#if defined (CLIENT_DLL)
ConVar tf_medigun_autoheal( "tf_medigun_autoheal", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE | FCVAR_USERINFO, "Setting this to 1 will cause the Medigun's primary attack to be a toggle instead of needing to be held down." );
#endif

#if !defined (CLIENT_DLL)
ConVar tf_medigun_lagcomp(  "tf_medigun_lagcomp", "1", FCVAR_DEVELOPMENTONLY );
#endif

static const char *s_pszMedigunHealTargetThink = "MedigunHealTargetThink";

extern ConVar tf_invuln_time;

#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void RecvProxy_HealingTarget( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
	CWeaponMedigun *pMedigun = ((CWeaponMedigun*)(pStruct));
	if ( pMedigun != NULL )
	{
		pMedigun->ForceHealingTargetUpdate();
	}

	RecvProxy_IntToEHandle( pData, pStruct, pOut );
}
#endif

LINK_ENTITY_TO_CLASS( tf_weapon_medigun, CWeaponMedigun );
PRECACHE_WEAPON_REGISTER( tf_weapon_medigun );

IMPLEMENT_NETWORKCLASS_ALIASED( WeaponMedigun, DT_WeaponMedigun )

#ifdef GAME_DLL
void* SendProxy_SendActiveLocalWeaponDataTable( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID );
void* SendProxy_SendNonLocalWeaponDataTable( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID );
#endif

//-----------------------------------------------------------------------------
// Purpose: Only sent when a player's holding it.
//-----------------------------------------------------------------------------
BEGIN_NETWORK_TABLE_NOBASE( CWeaponMedigun, DT_LocalTFWeaponMedigunData )
#if defined( CLIENT_DLL )
RecvPropFloat( RECVINFO(m_flChargeLevel) ),
#else
SendPropFloat( SENDINFO(m_flChargeLevel), 0, SPROP_NOSCALE | SPROP_CHANGES_OFTEN ),
#endif
END_NETWORK_TABLE()

//-----------------------------------------------------------------------------
// Purpose: Variables sent at low precision to non-holding observers.
//-----------------------------------------------------------------------------
BEGIN_NETWORK_TABLE_NOBASE( CWeaponMedigun, DT_TFWeaponMedigunDataNonLocal )
#if defined( CLIENT_DLL )
RecvPropFloat( RECVINFO(m_flChargeLevel) ),
#else
SendPropFloat( SENDINFO(m_flChargeLevel), 12, SPROP_NOSCALE | SPROP_CHANGES_OFTEN, 0.0, 100.0f ),
#endif
END_NETWORK_TABLE()

//-----------------------------------------------------------------------------
// Purpose: Variables always sent
//-----------------------------------------------------------------------------
BEGIN_NETWORK_TABLE( CWeaponMedigun, DT_WeaponMedigun )
#if !defined( CLIENT_DLL )
//	SendPropFloat( SENDINFO(m_flChargeLevel), 0, SPROP_NOSCALE | SPROP_CHANGES_OFTEN ),
	SendPropEHandle( SENDINFO( m_hHealingTarget ) ),
	SendPropBool( SENDINFO( m_bHealing ) ),
	SendPropBool( SENDINFO( m_bAttacking ) ),
	SendPropBool( SENDINFO( m_bChargeRelease ) ),
	SendPropBool( SENDINFO( m_bHolstered ) ),
	SendPropInt( SENDINFO( m_nChargeResistType ) ),
	SendPropDataTable("LocalTFWeaponMedigunData", 0, &REFERENCE_SEND_TABLE(DT_LocalTFWeaponMedigunData), SendProxy_SendLocalWeaponDataTable ),
	SendPropDataTable("NonLocalTFWeaponMedigunData", 0, &REFERENCE_SEND_TABLE(DT_TFWeaponMedigunDataNonLocal), SendProxy_SendNonLocalWeaponDataTable ),
#else
//	RecvPropFloat( RECVINFO(m_flChargeLevel) ),
	RecvPropEHandle( RECVINFO( m_hHealingTarget ), RecvProxy_HealingTarget ),
	RecvPropBool( RECVINFO( m_bHealing ) ),
	RecvPropBool( RECVINFO( m_bAttacking ) ),
	RecvPropBool( RECVINFO( m_bChargeRelease ) ),
	RecvPropBool( RECVINFO( m_bHolstered ) ),
	RecvPropInt( RECVINFO( m_nChargeResistType ) ),
	RecvPropDataTable("LocalTFWeaponMedigunData", 0, 0, &REFERENCE_RECV_TABLE(DT_LocalTFWeaponMedigunData)),
	RecvPropDataTable("NonLocalTFWeaponMedigunData", 0, 0, &REFERENCE_RECV_TABLE(DT_TFWeaponMedigunDataNonLocal)),
#endif
END_NETWORK_TABLE()

#ifdef CLIENT_DLL
BEGIN_PREDICTION_DATA( CWeaponMedigun  )

	DEFINE_PRED_FIELD( m_bHealing, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
	DEFINE_PRED_FIELD( m_bAttacking, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
	DEFINE_PRED_FIELD( m_bHolstered, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
	DEFINE_PRED_FIELD( m_hHealingTarget, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ),

	DEFINE_FIELD( m_bCanChangeTarget, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flHealEffectLifetime, FIELD_FLOAT ),

	DEFINE_PRED_FIELD( m_flChargeLevel, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
	DEFINE_PRED_FIELD( m_bChargeRelease, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),

//	DEFINE_PRED_FIELD( m_bPlayingSound, FIELD_BOOLEAN ),
//	DEFINE_PRED_FIELD( m_bUpdateHealingTargets, FIELD_BOOLEAN ),

END_PREDICTION_DATA()
#endif

#define PARTICLE_PATH_VEL				140.0
#define NUM_PATH_PARTICLES_PER_SEC		300.0f
#define NUM_MEDIGUN_PATH_POINTS		8


extern ConVar tf_max_health_boost;

//-----------------------------------------------------------------------------
// Purpose: For HUD auto medic callers
//-----------------------------------------------------------------------------
#ifdef CLIENT_DLL
ConVar hud_medicautocallers( "hud_medicautocallers", "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX );
ConVar hud_medicautocallersthreshold( "hud_medicautocallersthreshold", "75", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX );
ConVar hud_medichealtargetmarker ( "hud_medichealtargetmarker", "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX );
#endif

const char *g_pszMedigunHealSounds[MEDIGUN_NUM_CHARGE_TYPES] =
{
	"WeaponMedigun.Healing",			// MEDIGUN_CHARGE_INVULN = 0,
	"WeaponMedigun.Healing",			// MEDIGUN_CHARGE_CRITICALBOOST,
	"Weapon_Quick_Fix.Healing",			// MEDIGUN_CHARGE_MEGAHEAL,
	"WeaponMedigun_Vaccinator.Healing",	// MEDIGUN_CHARGE_RESIST,
};

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CWeaponMedigun::CWeaponMedigun( void )
{
	WeaponReset();

	SetPredictionEligible( true );
}

CWeaponMedigun::~CWeaponMedigun()
{
#ifdef CLIENT_DLL
	StopChargeEffect( true );

	if ( m_pChargedSound )
	{
		CSoundEnvelopeController::GetController().SoundDestroy( m_pChargedSound );
	}

	if ( m_pDisruptSound )
	{
		CSoundEnvelopeController::GetController().SoundDestroy( m_pDisruptSound );
	}

	m_flAutoCallerCheckTime = 0.0f;
#endif
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponMedigun::WeaponReset( void )
{
	BaseClass::WeaponReset();

	CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
	if ( m_bHealing && pOwner && pOwner->m_Shared.InState( TF_STATE_DYING ) )
	{
		m_bWasHealingBeforeDeath = true;
	}
	else
	{
		m_bWasHealingBeforeDeath = false;
	}

	m_flHealEffectLifetime = 0;

	m_bHealing = false;
	m_bAttacking = false;
	m_bChargeRelease = false;
	m_DetachedTargets.Purge();
	m_flEndResistCharge = 0.f;

	m_bCanChangeTarget = true;

	m_flNextBuzzTime = 0;
	m_flReleaseStartedAt = 0;

	int iPreserveUber = 0;
	if ( TFGameRules()->State_Get() == GR_STATE_RND_RUNNING )
	{
		CALL_ATTRIB_HOOK_INT_ON_OTHER( pOwner, iPreserveUber, preserve_ubercharge );
		m_flChargeLevel = MIN( m_flChargeLevel, iPreserveUber / 100.f );
	}
	else
	{
		m_flChargeLevel = 0;
	}

	RemoveHealingTarget( true );

	m_bAttack2Down	= false;
	m_bAttack3Down	= false;
	m_bReloadDown	= false;

	m_nChargeResistType = 0;

#if defined( GAME_DLL )
	StopHealingOwner();
	m_hLastHealingTarget = NULL;
	RecalcEffectOnTarget( ToTFPlayer( GetOwnerEntity() ), true );
	m_nHealTargetClass = 0;
	m_nChargesReleased = 0;
#endif

#if defined( CLIENT_DLL )
	m_nOldChargeResistType = 0;
	m_bPlayingSound = false;
	m_bUpdateHealingTargets = false;
	m_bOldChargeRelease = false;

	UpdateEffects();
	StopChargeEffect( true );

	m_pChargeEffectOwner = NULL;
	m_pChargeEffect = NULL;
	m_pChargedSound = NULL;
	m_pDisruptSound = NULL;
	m_flDenySecondary = 0.f;
#endif


}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponMedigun::Precache()
{
	BaseClass::Precache();
	PrecacheModel( "models/weapons/c_models/c_proto_backpack/c_proto_backpack.mdl" );
	PrecacheScriptSound( "WeaponMedigun.NoTarget" );
	PrecacheScriptSound( "WeaponMedigun.Healing" );
	PrecacheScriptSound( "Weapon_Quick_Fix.Healing" );
	PrecacheScriptSound( "WeaponMedigun.Charged" );
	PrecacheParticleSystem( "medicgun_invulnstatus_fullcharge_blue" );
	PrecacheParticleSystem( "medicgun_invulnstatus_fullcharge_red" );
	PrecacheParticleSystem( "medicgun_beam_red_invun" );
	PrecacheParticleSystem( "medicgun_beam_red" );
	PrecacheParticleSystem( "medicgun_beam_red_targeted" );
	PrecacheParticleSystem( "medicgun_beam_blue_invun" );
	PrecacheParticleSystem( "medicgun_beam_blue" );
	PrecacheParticleSystem( "medicgun_beam_blue_targeted" );
	PrecacheParticleSystem( "vaccinator_red_buff1" );
	PrecacheParticleSystem( "vaccinator_red_buff2" );
	PrecacheParticleSystem( "vaccinator_red_buff3" );
	PrecacheParticleSystem( "vaccinator_blue_buff1" );
	PrecacheParticleSystem( "vaccinator_blue_buff2" );
	PrecacheParticleSystem( "vaccinator_blue_buff3" );
	PrecacheParticleSystem( "drain_effect" );
	PrecacheScriptSound( "WeaponMedigun_Vaccinator.Charged_tier_01");
	PrecacheScriptSound( "WeaponMedigun_Vaccinator.Charged_tier_02");
	PrecacheScriptSound( "WeaponMedigun_Vaccinator.Charged_tier_03");
	PrecacheScriptSound( "WeaponMedigun_Vaccinator.Charged_tier_04");
	PrecacheScriptSound( "WeaponMedigun.HealingDisrupt" );
	// PrecacheParticleSystem( "medicgun_beam_machinery" );

	for( int i=0; i<ARRAYSIZE(g_MedigunEffects); ++i )
	{
		if( g_MedigunEffects[i].pszChargeOnSound[0] )
			PrecacheScriptSound( g_MedigunEffects[i].pszChargeOnSound );

		if( g_MedigunEffects[i].pszChargeOffSound[0] )
			PrecacheScriptSound( g_MedigunEffects[i].pszChargeOffSound );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CWeaponMedigun::Deploy( void )
{
	if ( BaseClass::Deploy() )
	{
		m_bHolstered = false;

		m_bWasHealingBeforeDeath = false;

#ifdef GAME_DLL
		CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
		if ( m_bChargeRelease )
		{
			RecalcEffectOnTarget( pOwner );
		}

		if ( pOwner && pOwner->m_Shared.IsRageDraining() )
		{
			CreateMedigunShield();
		}

		// Resume healing self for Quick-Fix if we're still ubering and switch back to the Quick-Fix.
		if ( ( GetMedigunType() == MEDIGUN_QUICKFIX ) && m_bChargeRelease && !m_bHealingSelf )
		{
			StartHealingTarget( pOwner );
			m_bHealingSelf = true;
		}
#endif

#ifdef CLIENT_DLL
		ManageChargeEffect();
#endif

		m_flNextTargetCheckTime = gpGlobals->curtime;

		return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CWeaponMedigun::Holster( CBaseCombatWeapon *pSwitchingTo )
{
	RemoveHealingTarget( true );
	m_bAttacking = false;
	m_bHolstered = true;

#ifdef GAME_DLL
	RecalcEffectOnTarget( ToTFPlayer( GetOwnerEntity() ), true );
	StopHealingOwner();
#endif

	RemoveMedigunShield();

#ifdef CLIENT_DLL
	UpdateEffects();
	ManageChargeEffect();
#endif

	return BaseClass::Holster( pSwitchingTo );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponMedigun::UpdateOnRemove( void )
{
	RemoveHealingTarget( true );
	m_bAttacking = false;
	m_bChargeRelease = false;

#ifdef GAME_DLL
	RecalcEffectOnTarget( ToTFPlayer( GetOwnerEntity() ), true );
	StopHealingOwner();
#endif

#ifdef CLIENT_DLL
	if ( m_bPlayingSound )
	{
		m_bPlayingSound = false;
		StopHealSound();
	}

	UpdateEffects();
#endif

	RemoveMedigunShield();

	BaseClass::UpdateOnRemove();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float CWeaponMedigun::GetTargetRange( void )
{
	return (float)m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flRange;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float CWeaponMedigun::GetStickRange( void )
{
	return (GetTargetRange() * 1.2);
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float CWeaponMedigun::GetHealRate( void )
{
	float flHealRate = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_nDamage;
	CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwnerEntity(), flHealRate, mult_medigun_healrate );

	// This attribute represents a bucket of attributes.
	int iHealingMastery = 0;
	CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwnerEntity(), iHealingMastery, healing_mastery );
	if ( iHealingMastery )
	{
		float flPerc = RemapValClamped( (float)iHealingMastery, 1.f, 4.f, 1.25f, 2.f );
		flHealRate *= flPerc;
	}

	if ( TFGameRules() && TFGameRules()->IsPowerupMode() )
	{
		CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );

		if ( pOwner && pOwner->m_Shared.GetCarryingRuneType() == RUNE_HASTE )
		{
			flHealRate *= 2.f;
		}
		else if ( pOwner && ( pOwner->m_Shared.GetCarryingRuneType() == RUNE_KING || pOwner->m_Shared.InCond( TF_COND_KING_BUFFED ) ) )
		{
			flHealRate *= 1.5f;
		}

	}

	return flHealRate;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CWeaponMedigun::HealingTarget( CBaseEntity *pTarget )
{
	if ( pTarget == m_hHealingTarget )
		return true;

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CWeaponMedigun::AllowedToHealTarget( CBaseEntity *pTarget )
{
	CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
	if ( !pOwner )
		return false;

	if ( !pTarget )
		return false;

	if ( pTarget->IsPlayer() )
	{
		CTFPlayer *pTFPlayer = ToTFPlayer( pTarget );
		if ( !pTFPlayer )
			return false;

		if ( !pTFPlayer->IsAlive() )
			return false;

		// We cannot heal teammates who are using the Equalizer.
		CTFWeaponBase *pTFWeapon = pTFPlayer->GetActiveTFWeapon();
		int iWeaponBlocksHealing = 0;
		CALL_ATTRIB_HOOK_INT_ON_OTHER( pTFWeapon, iWeaponBlocksHealing, weapon_blocks_healing );
		if ( iWeaponBlocksHealing == 1 )
		{
			return false;
		}

		bool bStealthed = pTFPlayer->m_Shared.IsStealthed() && !pOwner->m_Shared.IsStealthed(); // Allow stealthed medics to heal stealthed targets
		bool bDisguised = pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED );

		// We can heal teammates and enemies that are disguised as teammates
		if ( !bStealthed && 
			( pTFPlayer->InSameTeam( pOwner ) || 
			( bDisguised && pTFPlayer->m_Shared.GetDisguiseTeam() == pOwner->GetTeamNumber() ) ) )
		{
			return true;
		}
	}
	else
	{
		if ( !pTarget->InSameTeam( pOwner ) )
			return false;

#ifdef STAGING_ONLY
		if ( pTarget->IsBaseObject() && IsAllowedToTargetBuildings() )
			return true;
#else
		if ( pTarget->IsBaseObject() )
			return false;
#endif // STAGING_ONLY

		CTFReviveMarker *pReviveMarker = dynamic_cast< CTFReviveMarker* >( pTarget );
		if ( pReviveMarker )
		{
			m_hReviveMarker = pReviveMarker;	// Store last marker we touched
			return true;
		}
	}

	return false;
}

// Now make sure there isn't something other than team players in the way.
class CMedigunFilter : public CTraceFilterSimple
{
public:
	CMedigunFilter( CBaseEntity *pShooter ) : CTraceFilterSimple( pShooter, COLLISION_GROUP_WEAPON )
	{
		m_pShooter = pShooter;
	}

	virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
	{
		// If it hit an edict that isn't the target and is on our team, then the ray is blocked.
		CBaseEntity *pEnt = static_cast<CBaseEntity*>(pHandleEntity);

		// Ignore collisions with the shooter
		if ( pEnt == m_pShooter )
			return false;
		
		if ( pEnt->GetTeam() == m_pShooter->GetTeam() )
			return false;

		return CTraceFilterSimple::ShouldHitEntity( pHandleEntity, contentsMask );
	}

	CBaseEntity	*m_pShooter;
};

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponMedigun::MaintainTargetInSlot()
{
	CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
	if ( !pOwner )
		return;

	CBaseEntity *pTarget = m_hHealingTarget;
	Assert( pTarget );

	// Make sure the guy didn't go out of range.
	bool bLostTarget = true;
	Vector vecSrc = pOwner->Weapon_ShootPosition( );
	Vector vecTargetPoint = pTarget->WorldSpaceCenter();
	Vector vecPoint;

	// If it's brush built, use absmins/absmaxs
	pTarget->CollisionProp()->CalcNearestPoint( vecSrc, &vecPoint );

	float flDistance = (vecPoint - vecSrc).Length();
	if ( flDistance < GetStickRange() )
	{
		if ( m_flNextTargetCheckTime > gpGlobals->curtime )
			return;

		m_flNextTargetCheckTime = gpGlobals->curtime + 1.0f;

		CheckAchievementsOnHealTarget();

		trace_t tr;
		CMedigunFilter drainFilter( pOwner );

		Vector vecAiming;
		pOwner->EyeVectors( &vecAiming );

		Vector vecEnd = vecSrc + vecAiming * GetTargetRange();
		UTIL_TraceLine( vecSrc, vecEnd, (MASK_SHOT & ~CONTENTS_HITBOX), pOwner, DMG_GENERIC, &tr );

		// Still visible?
		if ( tr.m_pEnt == pTarget )
			return;

		UTIL_TraceLine( vecSrc, vecTargetPoint, MASK_SHOT, &drainFilter, &tr );

		// Still visible?
		if (( tr.fraction == 1.0f) || (tr.m_pEnt == pTarget))
			return;

		// If we failed, try the target's eye point as well
		UTIL_TraceLine( vecSrc, pTarget->EyePosition(), MASK_SHOT, &drainFilter, &tr );
		if (( tr.fraction == 1.0f) || (tr.m_pEnt == pTarget))
			return;
	}

	// We've lost this guy
	if ( bLostTarget )
	{
		RemoveHealingTarget();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponMedigun::FindNewTargetForSlot()
{
	CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
	if ( !pOwner )
		return;

	Vector vecSrc = pOwner->Weapon_ShootPosition( );
	if ( m_hHealingTarget )
	{
		RemoveHealingTarget();
	}

	// In Normal mode, we heal players under our crosshair
	Vector vecAiming;
	pOwner->EyeVectors( &vecAiming );

	// Find a player in range of this player, and make sure they're healable.
	Vector vecEnd = vecSrc + vecAiming * GetTargetRange();
	trace_t tr;

	UTIL_TraceLine( vecSrc, vecEnd, (MASK_SHOT & ~CONTENTS_HITBOX), pOwner, DMG_GENERIC, &tr );
	if ( tr.fraction != 1.0 && tr.m_pEnt )
	{
		if ( !HealingTarget( tr.m_pEnt ) && AllowedToHealTarget( tr.m_pEnt ) )
		{
#ifdef GAME_DLL
			pOwner->SpeakConceptIfAllowed( MP_CONCEPT_MEDIC_STARTEDHEALING );
			if ( tr.m_pEnt->IsPlayer() )
			{
				CTFPlayer *pTarget = ToTFPlayer( tr.m_pEnt );
				pTarget->SpeakConceptIfAllowed( MP_CONCEPT_HEALTARGET_STARTEDHEALING );
			}

			// Start the heal target thinking.
			SetContextThink( &CWeaponMedigun::HealTargetThink, gpGlobals->curtime, s_pszMedigunHealTargetThink );

			m_hLastHealingTarget = tr.m_pEnt;
#endif

			m_hHealingTarget.Set( tr.m_pEnt );
			m_flNextTargetCheckTime = gpGlobals->curtime + 1.0f;
		}			
	}
}

bool CWeaponMedigun::IsReleasingCharge( void ) const
{ 
	return (m_bChargeRelease && !m_bHolstered);
}


int CWeaponMedigun::GetMedigunType( void ) const 
{ 
	int iMode = 0;
	CALL_ATTRIB_HOOK_INT( iMode, set_weapon_mode );
	return iMode;
}


float CWeaponMedigun::GetMinChargeAmount( void ) const
{
	if( GetMedigunType() == MEDIGUN_RESIST )
	{
		return 1.f / weapon_medigun_resist_num_chunks.GetInt();
	}
	else
	{
		return 1.f;
	}
}

medigun_charge_types CWeaponMedigun::GetChargeType( void ) const
{ 
	int iTmp = MEDIGUN_CHARGE_INVULN;
	CALL_ATTRIB_HOOK_INT( iTmp, set_charge_type );

	if( GetMedigunType() == MEDIGUN_RESIST )
	{
		// If this is a resist-medigun, then the charge type needs to be within the resist types
		Assert( iTmp >= MEDIGUN_CHARGE_BULLET_RESIST && iTmp <= MEDIGUN_CHARGE_FIRE_RESIST );
		Assert( m_nChargeResistType < MEDIGUN_NUM_RESISTS );

		iTmp += m_nChargeResistType;
	}

	return (medigun_charge_types)iTmp;
}

void CWeaponMedigun::CycleResistType()
{
	// Resist medigun only!
	if( GetMedigunType() != MEDIGUN_RESIST )
		return;

	if( IsReleasingCharge() )
		return;

#ifdef GAME_DLL
	// When cycling resist we have to remove the current resist, then add the new resist.
	CTFPlayer *pTFHealingTarget = ToTFPlayer( m_hHealingTarget );
	CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
	ETFCond cond = g_MedigunResistConditions[ GetResistType() ].passiveCond;
	// Remove from out healing target
	if( pTFHealingTarget)
	{
		CBaseEntity* pProvider = pTFHealingTarget->m_Shared.GetConditionProvider( cond );

		// Remove from our healing target if we're the provider
		if( pProvider == pOwner || pProvider == NULL )
		{
			pTFHealingTarget->m_Shared.RemoveCond( cond );
		}
	}
	// Remove from ourselves
	if( pOwner )
	{
		// Remove from ourselves if we're the provider
		CBaseEntity* pProvider = pOwner->m_Shared.GetConditionProvider( cond );
		if( pProvider == pOwner || pProvider == NULL )
		{
			pOwner->m_Shared.RemoveCond( cond );
		}
	}
	
#endif

	m_nChargeResistType += 1;
	m_nChargeResistType = m_nChargeResistType % MEDIGUN_NUM_RESISTS;


#ifdef GAME_DLL
	CTFPlayer *pTFOwner = ToTFPlayer( GetOwnerEntity() );

	if( pTFOwner )
	{
		RecalcEffectOnTarget( pTFOwner );
	}


	if( pTFHealingTarget )
	{
		// Now add the new resist
		RecalcEffectOnTarget( pTFHealingTarget );
		pTFHealingTarget->m_Shared.AddCond( g_MedigunResistConditions[ GetResistType() ].passiveCond, PERMANENT_CONDITION, pTFOwner );
		pTFOwner->m_Shared.AddCond( g_MedigunResistConditions[ GetResistType() ].passiveCond, PERMANENT_CONDITION, pTFOwner );
	}
#else
	// Updates our particles
	ForceHealingTargetUpdate();

#endif
}


medigun_resist_types_t CWeaponMedigun::GetResistType() const
{
	Assert( GetMedigunType() == MEDIGUN_RESIST );

	int nCurrentActiveResist = ( GetChargeType() - MEDIGUN_CHARGE_BULLET_RESIST );
	Assert( nCurrentActiveResist >= 0 && nCurrentActiveResist < MEDIGUN_NUM_RESISTS );
	nCurrentActiveResist = nCurrentActiveResist % MEDIGUN_NUM_RESISTS;

	return medigun_resist_types_t(nCurrentActiveResist);
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CWeaponMedigun::IsAllowedToTargetBuildings( void )
{
#ifdef STAGING_ONLY
	if ( !TFGameRules() || !TFGameRules()->GameModeUsesUpgrades() )
		return false;

	// See if we have the upgrade to heal buildings
	int iHealBuildings = 0;
	CALL_ATTRIB_HOOK_INT( iHealBuildings, medic_machinery_beam );

	return iHealBuildings ? true : false;
#else
	return false;
#endif // STAGING_ONLY
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CWeaponMedigun::IsAttachedToBuilding( void )
{
	if ( !m_hHealingTarget )
		return false;

	return m_hHealingTarget->IsBaseObject();
}

#ifdef GAME_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponMedigun::HealTargetThink( void )
{	
	// Verify that we still have a valid heal target.
	CBaseEntity *pTarget = m_hHealingTarget;
	if ( !pTarget || !pTarget->IsAlive() )
	{
		SetContextThink( NULL, 0, s_pszMedigunHealTargetThink );
		return;
	}

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

	float flTime = gpGlobals->curtime - pOwner->GetTimeBase();
	if ( flTime > 5.0f || !AllowedToHealTarget(pTarget) )
	{
		RemoveHealingTarget( true );
	}

	// Make sure our heal target hasn't changed classes while being healed
	CTFPlayer *pTFTarget = ToTFPlayer( pTarget );
	if ( pTFTarget )
	{
		int nPrevClass = m_nHealTargetClass;
		m_nHealTargetClass = pTFTarget->GetPlayerClass()->GetClassIndex();
		if ( m_nHealTargetClass != nPrevClass )
		{
			pOwner->TeamFortress_SetSpeed();
		}
	}

	if ( !pTarget->IsPlayer() )
	{
#ifdef STAGING_ONLY
		if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() )
		{
			if ( IsAttachedToBuilding() )
			{
				// Heal building
				if ( m_hHealingTarget->GetHealth() < m_hHealingTarget->GetMaxHealth() )
				{
					CBaseEntity *pEntity = m_hHealingTarget;
					CBaseObject *pObject = dynamic_cast<CBaseObject*>( pEntity );
					if ( pObject )
					{
						pObject->SetHealth( m_hHealingTarget->GetHealth() + ( GetHealRate() / 10.f ) );
					}
				}
			}
		}
#endif // STAGING_ONLY

		CTFReviveMarker *pReviveMarker = dynamic_cast< CTFReviveMarker* >( pTarget );
		if ( pReviveMarker )
		{
			CTFPlayer *pDeadPlayer = pReviveMarker->GetOwner();
			if ( pDeadPlayer )
			{
				pReviveMarker->SetReviver( pOwner );

				// Instantly revive players when deploying uber
				float flHealRate = GetHealRate();
				float flReviveRate = m_bChargeRelease ? flHealRate / 2.f : flHealRate / 8.f;
				pReviveMarker->AddMarkerHealth( flReviveRate );

				// Set observer target to reviver so they know they're being revived
				if ( pDeadPlayer->GetObserverMode() > OBS_MODE_FREEZECAM )
				{
					if ( pReviveMarker->GetReviver() && pDeadPlayer->GetObserverTarget() != pReviveMarker->GetReviver() )
					{
						pDeadPlayer->SetObserverTarget( pReviveMarker->GetReviver() );
					}
				}

				if ( !pReviveMarker->HasOwnerBeenPrompted() )
				{
					// This will give them a messagebox that has a Cancel button
					pReviveMarker->PromptOwner();
				}
			}
		}
	}

	SetNextThink( gpGlobals->curtime + 0.2f, s_pszMedigunHealTargetThink );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponMedigun::StartHealingTarget( CBaseEntity *pTarget )
{
	CTFPlayer *pTFTarget = ToTFPlayer( pTarget );
	if ( !pTFTarget )
		return;

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

	// Handle bonuses as additive, penalties as percentage...
	float flOverhealBonus = tf_max_health_boost.GetFloat() - 1.0f;
	float flMod = 1.0f;
	CALL_ATTRIB_HOOK_FLOAT( flMod, mult_medigun_overheal_amount );
	if ( flMod >= 1.0f )
	{
		flOverhealBonus += flMod;
	}
	else if ( flMod < 1.0f && flOverhealBonus > 0.0f )
	{
		flOverhealBonus *= flMod;
		flOverhealBonus += 1.0f;
	}

	// Safety net
	if ( flOverhealBonus <= 1.0f )
	{
		flOverhealBonus = 1.0f;
	}

	float flOverhealDecayMult = 1.0;
	CALL_ATTRIB_HOOK_FLOAT( flOverhealDecayMult, mult_medigun_overheal_decay );

	float flOverhealExpert = 0.f;
	CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pOwner, flOverhealExpert, overheal_expert );
	flOverhealBonus = Max( flOverhealBonus, flOverhealBonus + ( flOverhealExpert / 4 ) );
	flOverhealDecayMult = Max( flOverhealDecayMult, flOverhealDecayMult + ( flOverhealExpert / 2 ) );

	pTFTarget->m_Shared.Heal( pOwner, GetHealRate(), flOverhealBonus, flOverhealDecayMult );

	// If target is grappling, set ourselves as grappling them
	if ( pTFTarget->GetGrapplingHookTarget() )
	{
		pOwner->SetGrapplingHookTarget( pTFTarget );
	}

	// Add on the small passive resist when we attach onto a target
	if( GetMedigunType() == MEDIGUN_RESIST )
	{
		pTFTarget->m_Shared.AddCond( g_MedigunResistConditions[ GetResistType() ].passiveCond, PERMANENT_CONDITION, pOwner );
		pOwner->m_Shared.AddCond( g_MedigunResistConditions[ GetResistType() ].passiveCond, PERMANENT_CONDITION, pOwner );
	}
}

//-----------------------------------------------------------------------------
// Purpose: QuickFix uber heals the target and medic
//-----------------------------------------------------------------------------
void CWeaponMedigun::StopHealingOwner( void )
{
	if ( !m_bHealingSelf ) 
		return;

	CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
	if ( !pOwner )
		return;
		
	pOwner->m_Shared.StopHealing( pOwner );
	m_bHealingSelf = false;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponMedigun::AddCharge( float flPercentage )
{
	m_flChargeLevel = MIN( m_flChargeLevel + flPercentage, 1.0 );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponMedigun::SubtractCharge( float flPercentage )
{
	float flSubtractAmount = Max( flPercentage, 0.0f );
	SubtractChargeAndUpdateDeployState( flSubtractAmount, true );
}


//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponMedigun::RecalcEffectOnTarget( CTFPlayer *pPlayer, bool bInstantRemove )
{
	if ( !pPlayer )
		return;

	pPlayer->m_Shared.RecalculateChargeEffects( bInstantRemove );
}

#endif

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const char *CWeaponMedigun::GetHealSound( void ) const
{
	COMPILE_TIME_ASSERT( ARRAYSIZE(g_pszMedigunHealSounds) == MEDIGUN_NUM_CHARGE_TYPES );
	return g_pszMedigunHealSounds[ GetMedigunType() ];
}

#ifdef GAME_DLL
void CWeaponMedigun::UberchargeChunkDeployed()
{
	m_nChargesReleased++;
	if( m_nChargesReleased % weapon_medigun_resist_num_chunks.GetInt() == 0 )
	{
		CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
		if ( !pOwner )
			return;

		CTF_GameStats.Event_PlayerInvulnerable( pOwner );
		EconEntity_OnOwnerKillEaterEvent( this, pOwner, ToTFPlayer( m_hHealingTarget ), kKillEaterEvent_UberActivated );
	}
}
#endif

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponMedigun::CreateMedigunShield( void )
{
#ifdef GAME_DLL
	if ( m_hMedigunShield )
		return;

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

#ifdef STAGING_ONLY
	bool bHasPermanentShield = HasPermanentShield();
	if ( !bHasPermanentShield )
#endif // STAGING_ONLY
	{
		// check if we can activate the shield
		if ( ( pOwner->m_Shared.GetRageMeter() < 100.f ) && !pOwner->m_Shared.IsRageDraining() )
			return;
	}

	pOwner->SpeakConceptIfAllowed( MP_CONCEPT_MEDIC_HEAL_SHIELD );
	m_hMedigunShield = CTFMedigunShield::Create( pOwner );
	if ( m_hMedigunShield )
	{
		pOwner->m_Shared.StartRageDrain();
		
#ifdef STAGING_ONLY
		m_hMedigunShield->SetPermanentShield( bHasPermanentShield );
#endif // STAGING_ONLY
	}
#endif // GAME_DLL
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponMedigun::RemoveMedigunShield( void )
{
#ifdef GAME_DLL
	if ( m_hMedigunShield )
	{
		m_hMedigunShield->RemoveShield();
		m_hMedigunShield.Set( NULL );
	}
#endif // GAME_DLL
}


#ifdef STAGING_ONLY
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CWeaponMedigun::HasPermanentShield() const
{
	if ( !TFGameRules()->IsMannVsMachineMode() )
		return false;

	int iPermanentShield = 0;
	CALL_ATTRIB_HOOK_INT( iPermanentShield, permanent_medic_shield );
	return iPermanentShield != 0;
}
#endif // STAGING_ONLY


//-----------------------------------------------------------------------------
// Purpose: Returns a pointer to a healable target
//-----------------------------------------------------------------------------
bool CWeaponMedigun::FindAndHealTargets( void )
{
	CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
	if ( !pOwner )
		return false;

#ifdef GAME_DLL
	if ( !pOwner->IsBot() )
	{
		INetChannelInfo *pNetChanInfo = engine->GetPlayerNetInfo( pOwner->entindex() );
		if ( !pNetChanInfo || pNetChanInfo->IsTimingOut() )
			return false;
	}
#endif // GAME_DLL

	bool bFound = false;

	// Maintaining beam to existing target?
	CBaseEntity *pTarget = m_hHealingTarget;
	if ( pTarget && pTarget->IsAlive() )
	{
		MaintainTargetInSlot();
	}
	else
	{	
		FindNewTargetForSlot();
	}

	CBaseEntity *pNewTarget = m_hHealingTarget;
	if ( pNewTarget && pNewTarget->IsAlive() )
	{
		CTFPlayer *pTFPlayer = ToTFPlayer( pNewTarget );

#ifdef GAME_DLL
		// HACK: For now, just deal with players
		if ( pTFPlayer )
		{
			if ( pTarget != pNewTarget )
			{
				StartHealingTarget( pNewTarget );
			}

			RecalcEffectOnTarget( pTFPlayer );
		}
#endif
	
		bFound = true;

		// Charge up our power if we're not releasing it, and our target
		// isn't receiving any benefit from our healing.
		if ( !m_bChargeRelease )
		{
			float flChargeRate = weapon_medigun_charge_rate.GetFloat();
			float flChargeAmount = gpGlobals->frametime / flChargeRate;

			if ( pTFPlayer && weapon_medigun_charge_rate.GetFloat() )
			{
#ifdef GAME_DLL
				int iBoostMax = floor( pTFPlayer->m_Shared.GetMaxBuffedHealth() * 0.95);
				float flChargeModifier = 1.f;

				// Reduced charge for healing fully healed guys
				if ( pNewTarget->GetHealth() >= iBoostMax && ( TFGameRules() && !(TFGameRules()->InSetup() && TFGameRules()->GetActiveRoundTimer() ) ) )
				{
					flChargeModifier *= 0.5;
				}

				int iTotalHealers = pTFPlayer->m_Shared.GetNumHealers();
				if ( iTotalHealers > 1 )
				{
					flChargeModifier /= (float)iTotalHealers;
				}

				// The resist medigun has a uber charge rate
				flChargeAmount *= flChargeModifier;

				if ( pOwner->m_Shared.GetCarryingRuneType() == RUNE_HASTE )
				{
					flChargeAmount *= 2.f;
				}
				else if ( pOwner->m_Shared.GetCarryingRuneType() == RUNE_KING || pOwner->m_Shared.InCond( TF_COND_KING_BUFFED ) )
				{
					flChargeAmount *= 1.5f;
				}

				if ( pNewTarget->GetHealth() >= pNewTarget->GetMaxHealth() && ( TFGameRules() && !(TFGameRules()->InSetup() && TFGameRules()->GetActiveRoundTimer() ) ) )
				{
					CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pOwner, flChargeAmount, mult_medigun_overheal_uberchargerate );
				}

				CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pOwner, flChargeAmount, mult_medigun_uberchargerate );


				// Apply any bonus our target gives us.
				if ( pTarget )
				{
					bool bInRespawnRoom = 
						PointInRespawnRoom( pTarget, WorldSpaceCenter() ) ||
						PointInRespawnRoom( pOwner, WorldSpaceCenter() );

					if ( !bInRespawnRoom )
					{
						CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pTarget, flChargeAmount, mult_uberchargerate_for_healer );
					}
				}
				if ( TFGameRules() )
				{
					if ( TFGameRules()->IsQuickBuildTime() )
					{
						flChargeAmount *= 4.f;
					}
					else if ( TFGameRules()->InSetup() && TFGameRules()->GetActiveRoundTimer() )
					{
						flChargeAmount *= 3.f;
					}
				}
#endif

				float flNewLevel = MIN( m_flChargeLevel + flChargeAmount, 1.0 );

				float flMinChargeAmount = GetMinChargeAmount();

				if ( flNewLevel >= flMinChargeAmount && m_flChargeLevel < flMinChargeAmount )
				{
#ifdef GAME_DLL
					pOwner->SpeakConceptIfAllowed( MP_CONCEPT_MEDIC_CHARGEREADY );
					pTFPlayer->SpeakConceptIfAllowed( MP_CONCEPT_HEALTARGET_CHARGEREADY );
#else
					// send a message that we've got charge
					// if you change this from being a client-side only event, you have to 
					// fix ACHIEVEMENT_TF_MEDIC_KILL_WHILE_CHARGED to check the medic userid.
					IGameEvent *event = gameeventmanager->CreateEvent( "localplayer_chargeready" );
					if ( event )
					{
						gameeventmanager->FireEventClientSide( event );
					}
#endif
				}

#ifdef CLIENT_DLL
					if ( GetMedigunType() == MEDIGUN_RESIST )
					{
						// Play a sound when we tick over to a new charge level
						int nChargeLevel = int(floor(flNewLevel/flMinChargeAmount));
						float flNextChargeLevelAmount = nChargeLevel * flMinChargeAmount;
						if( flNewLevel >= flNextChargeLevelAmount && m_flChargeLevel < flNextChargeLevelAmount )
						{
							const char* pzsSoundName =  CFmtStr( "WeaponMedigun_Vaccinator.Charged_tier_0%d", nChargeLevel );

							if (  nChargeLevel == 1 )
							{
								if ( m_pChargedSound != NULL )
								{
									CSoundEnvelopeController::GetController().SoundDestroy( m_pChargedSound );
									m_pChargedSound = NULL;
								}

								CLocalPlayerFilter filter;

								CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();

								m_pChargedSound = controller.SoundCreate( filter, entindex(), pzsSoundName );
								controller.Play( m_pChargedSound, 1.0, 100 );
							}
							else
							{
								pOwner->EmitSound( pzsSoundName );
							}
						}
					}
#endif
					SetChargeLevel( flNewLevel );
			}
			else if ( IsAttachedToBuilding() )
			{
				m_flChargeLevel = MIN( m_flChargeLevel + flChargeAmount, 1.0 );
			}
		}
	}

	return bFound;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponMedigun::ItemHolsterFrame( void )
{
	BaseClass::ItemHolsterFrame();

	DrainCharge();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponMedigun::DrainCharge( void )
{
	// If we're in charge release mode, drain our charge
	if ( m_bChargeRelease )
	{
		CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
		if ( !pOwner )
			return;

		int flUberTime = weapon_medigun_chargerelease_rate.GetFloat();
		CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pOwner, flUberTime, add_uber_time );

		float flChargeAmount = gpGlobals->frametime / flUberTime;
		float flExtraPlayerCost = flChargeAmount * 0.5;

		// Drain faster the more targets we're applying to. Extra targets count for 50% drain to still reward juggling somewhat.
		for ( int i = m_DetachedTargets.Count()-1; i >= 0; i-- )
		{
			if ( m_DetachedTargets[i].hTarget == NULL || m_DetachedTargets[i].hTarget.Get() == m_hHealingTarget.Get() || 
				!m_DetachedTargets[i].hTarget->IsAlive() || m_DetachedTargets[i].flTime < (gpGlobals->curtime - tf_invuln_time.GetFloat()) )
			{
				m_DetachedTargets.Remove(i);
			}
			else
			{
				flChargeAmount += flExtraPlayerCost;
			}
		}

		SubtractChargeAndUpdateDeployState( flChargeAmount, false );
	}
}


void CWeaponMedigun::SubtractChargeAndUpdateDeployState( float flSubtractAmount, bool bForceDrain )
{
	CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
	if ( !pOwner )
		return;

	float flNewCharge = Max( m_flChargeLevel - flSubtractAmount, 0.0f );

	if( GetMedigunType() == MEDIGUN_RESIST )
	{
		if( flNewCharge <= m_flEndResistCharge )
		{
			// If the player is holding down ATTACK2 and they have a bar of Uber left,
			// let them burn straight into the next bar
			if( (m_flEndResistCharge > 0) && m_bAttack2Down )
			{
				float flChunkSize = GetMinChargeAmount();
				int nCurrentChunk = floor(m_flChargeLevel / flChunkSize);
				m_flEndResistCharge = flChunkSize * Max( 0, (nCurrentChunk - 1) );
#ifdef GAME_DLL
				UberchargeChunkDeployed();
#endif
			}
			else
			{

				// Make sure we don't cross over too far if this is a natural drain
				if( !bForceDrain )
				{
					flNewCharge = m_flEndResistCharge;
				}
				m_flEndResistCharge = 0.f;
				// Stop deploying
				m_bChargeRelease = false;
				m_flReleaseStartedAt = 0;
				m_DetachedTargets.Purge();

#ifdef GAME_DLL
				pOwner->ClearPunchVictims();
				RecalcEffectOnTarget( pOwner );
#endif
			}
		}
	}

	m_flChargeLevel = flNewCharge;

	if ( !m_flChargeLevel )
	{
		m_bChargeRelease = false;
		m_flReleaseStartedAt = 0;
		m_DetachedTargets.Purge();

#ifdef GAME_DLL
		pOwner->ClearPunchVictims();
		RecalcEffectOnTarget( pOwner );
		StopHealingOwner(); // QuickFix uber heals the target and medic
#endif
	}
}


//-----------------------------------------------------------------------------
// Purpose: Overloaded to handle the hold-down healing
//-----------------------------------------------------------------------------
void CWeaponMedigun::ItemPostFrame( void )
{
	CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
	if ( !pOwner )
		return;

	// If we're lowered, we're not allowed to fire
	if ( CanAttack() == false )
	{
		RemoveHealingTarget( true );
		return;
	}

#if !defined( CLIENT_DLL )
	if ( AppliesModifier() )
	{
		m_DamageModifier.SetModifier( weapon_medigun_damage_modifier.GetFloat() );
	}
#endif

	// Try to start healing
	m_bAttacking = false;
	if ( pOwner->GetMedigunAutoHeal() )
	{
		if ( pOwner->m_nButtons & IN_ATTACK )
		{
			if ( m_bCanChangeTarget )
			{
				RemoveHealingTarget();
#if defined( CLIENT_DLL )
				if (prediction->IsFirstTimePredicted() ) {
					m_bPlayingSound = false;
					StopHealSound();
				}
#endif
				// can't change again until we release the attack button
				m_bCanChangeTarget = false;
			}
		}
		else
		{
			m_bCanChangeTarget = true;
		}

		if ( m_bHealing && ( m_iState != WEAPON_IS_ACTIVE || pOwner->IsTaunting() ) )
		{
			RemoveHealingTarget();
		}
		else if ( m_bHealing || ( pOwner->m_nButtons & IN_ATTACK ) )
		{
			PrimaryAttack();
			m_bAttacking = true;
		}
	}
	else
	{
		if ( /*m_bChargeRelease || */ pOwner->m_nButtons & IN_ATTACK )
		{
			PrimaryAttack();
			m_bAttacking = true;
		}
 		else if ( m_bHealing )
 		{
 			// Detach from the player if they release the attack button.
 			RemoveHealingTarget();
 		}
	}

	if ( pOwner->m_nButtons & IN_ATTACK2 )
	{
		SecondaryAttack();
	}
	else
	{
		m_bAttack2Down = false;
	}

	if( (pOwner->m_nButtons & IN_ATTACK3) && !m_bAttack3Down )
	{
		CreateMedigunShield();
		m_bAttack3Down = true;
	}
	else if( !(pOwner->m_nButtons & IN_ATTACK3) && m_bAttack3Down )
	{
		m_bAttack3Down = false;
	}

	if ( pOwner->m_nButtons & IN_RELOAD && !m_bReloadDown )
	{
#ifdef GAME_DLL
		CycleResistType();
#endif
		m_bReloadDown = true;
	}
	else if ( !(pOwner->m_nButtons & IN_RELOAD) && m_bReloadDown )
	{
		m_bReloadDown = false;
	}

	WeaponIdle();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CWeaponMedigun::Lower( void )
{
	// Stop healing if we are
	if ( m_bHealing )
	{
		RemoveHealingTarget( true );
		m_bAttacking = false;

#ifdef CLIENT_DLL
		UpdateEffects();
#endif
	}

	return BaseClass::Lower();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponMedigun::RemoveHealingTarget( bool bStopHealingSelf )
{
	CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
	if ( !pOwner )
		return;


	// If this guy is already in our detached target list, update the time. Otherwise, add him.
	if ( m_bChargeRelease )
	{
		int i = 0;
		for ( i = 0; i < m_DetachedTargets.Count(); i++ )
		{
			if ( m_DetachedTargets[i].hTarget == m_hHealingTarget )
			{
				m_DetachedTargets[i].flTime = gpGlobals->curtime;
				break;
			}
		}
		if ( i == m_DetachedTargets.Count() )
		{
			int iIdx = m_DetachedTargets.AddToTail();
			m_DetachedTargets[iIdx].hTarget = m_hHealingTarget;
			m_DetachedTargets[iIdx].flTime = gpGlobals->curtime;
		}
	}

#ifdef GAME_DLL
	int nMedigunType = GetMedigunType();

	if ( m_hHealingTarget )
	{
		// HACK: For now, just deal with players
		if ( m_hHealingTarget->IsPlayer() )
		{
			CTFPlayer *pTFPlayer = ToTFPlayer( m_hHealingTarget );
			CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
			if ( pTFPlayer && pOwner )
			{
				pTFPlayer->m_Shared.StopHealing( pOwner );

				pOwner->SpeakConceptIfAllowed( MP_CONCEPT_MEDIC_STOPPEDHEALING, pTFPlayer->IsAlive() ? "healtarget:alive" : "healtarget:dead" );
				pTFPlayer->SpeakConceptIfAllowed( MP_CONCEPT_HEALTARGET_STOPPEDHEALING );

				// If we're grappled to this player, drop
				if ( pOwner->GetGrapplingHookTarget() == pTFPlayer )
				{
					pOwner->SetGrapplingHookTarget( NULL );
				}

				// Remove our passive resist
				if( nMedigunType == MEDIGUN_RESIST )
				{
					ETFCond cond = g_MedigunResistConditions[ GetResistType() ].passiveCond;
					CBaseEntity* pProvider = pTFPlayer->m_Shared.GetConditionProvider( cond );

					// Remove from our healing target if we're the provider
					if( pProvider == pOwner || pProvider == NULL )
					{
						pTFPlayer->m_Shared.RemoveCond( cond );
					}

					// Remove from ourselves if we're the provider
					pProvider = pOwner->m_Shared.GetConditionProvider( cond );
					if( pProvider == pOwner || pProvider == NULL )
					{
						pOwner->m_Shared.RemoveCond( cond );
					}
				}
			}
		}
	}

	// Stop thinking - we no longer have a heal target.
	SetContextThink( NULL, 0, s_pszMedigunHealTargetThink );
#endif

	m_hHealingTarget.Set( NULL );

#ifdef GAME_DLL
	// See if we have The QuickFix, which adjusts our move speed based on heal target
	pOwner->TeamFortress_SetSpeed();

#endif

	// Stop the welding animation
	if ( m_bHealing )
	{
		SendWeaponAnim( ACT_MP_ATTACK_STAND_POSTFIRE );
		pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_POST );
	}

#ifndef CLIENT_DLL
	m_DamageModifier.RemoveModifier();
#endif
	m_bHealing = false;
}


//-----------------------------------------------------------------------------
// Purpose: Attempt to heal any player within range of the medikit
//-----------------------------------------------------------------------------
void CWeaponMedigun::PrimaryAttack( void )
{
	CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
	if ( !pOwner )
		return;


	if ( !CanAttack() )
		return;

#ifdef GAME_DLL
	/*
	// Start boosting ourself if we're not
	if ( m_bChargeRelease && !m_bHealingSelf )
	{
		pOwner->m_Shared.Heal( pOwner, GetHealRate() * 2 );
		m_bHealingSelf = true;
	}
	*/
#endif

#if !defined (CLIENT_DLL)
	if ( tf_medigun_lagcomp.GetBool() )
		lagcompensation->StartLagCompensation( pOwner, pOwner->GetCurrentCommand() );
#endif

	if ( FindAndHealTargets() )
	{
		// Start the animation
		if ( !m_bHealing )
		{
#ifdef GAME_DLL
			pOwner->SpeakWeaponFire();
#endif

			SendWeaponAnim( ACT_MP_ATTACK_STAND_PREFIRE );
			pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRE );
		}

		m_bHealing = true;
	}
	else
	{
		RemoveHealingTarget();
	}
	
#if !defined (CLIENT_DLL)
	if ( tf_medigun_lagcomp.GetBool() )
		lagcompensation->FinishLagCompensation( pOwner );
#endif
}

//-----------------------------------------------------------------------------
// Purpose: Burn charge level to generate invulnerability
//-----------------------------------------------------------------------------
void CWeaponMedigun::SecondaryAttack( void )
{
	CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
	if ( !pOwner )
		return;

	if ( !CanAttack() )
		return;

	CTFPlayer *pTFPlayerPatient = NULL;
	if ( m_hHealingTarget && m_hHealingTarget->IsPlayer() )
	{
		pTFPlayerPatient = ToTFPlayer( m_hHealingTarget );
	}

	// STAGING_MEDIC
	// Resist gun early outs if the patient and medic both have the condition (or medic with no patient has condition)
	if ( GetMedigunType() == MEDIGUN_RESIST )
	{
		ETFCond uberCond = g_MedigunResistConditions[GetResistType()].uberCond;
		if ( pOwner->m_Shared.InCond( uberCond ) ) 
		{
			if ( !pTFPlayerPatient || pTFPlayerPatient->m_Shared.InCond( uberCond ) )
			{
				return;
			}
		}
	}

	m_bAttack2Down = true;

	// If using standard-uber-model-medigun, ensure they have a full charge and are not already in charge release mode
	bool bDenyUse = GetMedigunType() != MEDIGUN_RESIST && (m_flChargeLevel < 1.0);
	// If using the resist-medigun, they can shoot sooner
	float flChunkSize = GetMinChargeAmount();
	bDenyUse |= GetMedigunType() == MEDIGUN_RESIST && m_flChargeLevel < flChunkSize;

	if ( bDenyUse || m_bChargeRelease )
	{
#ifdef CLIENT_DLL
		// Deny, flash
		if ( !m_bChargeRelease && gpGlobals->curtime >= m_flDenySecondary )
		{
			m_flDenySecondary = gpGlobals->curtime + 0.5f;
			pOwner->EmitSound( "Player.DenyWeaponSelection" );
		}
#endif
		return;
	}

	if ( !pOwner->m_Shared.CanRecieveMedigunChargeEffect( GetChargeType() ) )
	{
		if ( pOwner->m_afButtonPressed & IN_ATTACK2 
#ifdef CLIENT_DLL
			&& prediction->IsFirstTimePredicted()
#endif
			)
		{
#ifdef GAME_DLL
			CSingleUserRecipientFilter filter( pOwner );
			TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_NO_INVULN_WITH_FLAG );
#else
			pOwner->EmitSound( "Player.DenyWeaponSelection" );
#endif
		}
		return;
	}


	// Toggle super charge state
	m_bChargeRelease = true;
	m_flReleaseStartedAt = gpGlobals->curtime;

#ifdef GAME_DLL
	if( GetMedigunType() == MEDIGUN_RESIST )
	{
		// We dont want to give the user a point every time they deploy an uber with the resist medigun.
		// Instead we give them a point for every 4 deploys
		UberchargeChunkDeployed();

		int nCurrentChunk = floor( m_flChargeLevel / flChunkSize );
		Assert( nCurrentChunk >= 1 );

		CPVSFilter filter( pOwner->WorldSpaceCenter() );
		pOwner->EmitSound( filter, pOwner->entindex(), CFmtStr( "WeaponMedigun_Vaccinator.Charged_tier_0%d", nCurrentChunk ) );
		pOwner->EmitSound( filter, pOwner->entindex(), g_MedigunEffects[MEDIGUN_CHARGE_BULLET_RESIST].pszChargeOnSound );	
	}
	else
	{
		// Award assist point
		CTF_GameStats.Event_PlayerInvulnerable( pOwner );
		// Award strange assist score
		EconEntity_OnOwnerKillEaterEvent( this, pOwner, ToTFPlayer( m_hHealingTarget ), kKillEaterEvent_UberActivated );
	}
	
	// STAGING_MEDIC
	if ( GetMedigunType() != MEDIGUN_RESIST )
	{
		RecalcEffectOnTarget( pOwner );
	}
	pOwner->SpeakConceptIfAllowed( MP_CONCEPT_MEDIC_CHARGEDEPLOYED );

	if ( pTFPlayerPatient )
	{
		// STAGING_MEDIC
		if ( GetMedigunType() != MEDIGUN_RESIST )
		{
			RecalcEffectOnTarget( pTFPlayerPatient );
		}

		pTFPlayerPatient->SpeakConceptIfAllowed( MP_CONCEPT_HEALTARGET_CHARGEDEPLOYED );
	}

	IGameEvent * event = gameeventmanager->CreateEvent( "player_chargedeployed" );
	if ( event )
	{
		event->SetInt( "userid", pOwner->GetUserID() );
		if ( m_hHealingTarget && m_hHealingTarget->IsPlayer() )
		{
			event->SetInt( "targetid", ToTFPlayer(m_hHealingTarget)->GetUserID() );
		}
		gameeventmanager->FireEvent( event );
	}

	// Check for achievements
	// Simultaneous uber charge with teammates.
	CTeam *pTeam = pOwner->GetTeam();
	if ( pTeam )
	{
		CUtlVector<CTFPlayer*> aChargingMedics;
		aChargingMedics.AddToTail( pOwner );
		for ( int i = 0; i < pTeam->GetNumPlayers(); i++ )
		{
			CTFPlayer *pTeamPlayer = ToTFPlayer( pTeam->GetPlayer(i) );
			if ( pTeamPlayer && pTeamPlayer->IsPlayerClass( TF_CLASS_MEDIC ) && pTeamPlayer != pOwner )
			{
				CWeaponMedigun *pWeapon = dynamic_cast <CWeaponMedigun*>( pTeamPlayer->GetActiveWeapon() );
				if ( pWeapon && pWeapon->IsReleasingCharge() )
				{
					aChargingMedics.AddToTail( pTeamPlayer );
				}
			}
		}

		if ( aChargingMedics.Count() >= 3 )
		{
			// Give the achievement to all the Medics
			for ( int i = 0; i < aChargingMedics.Count(); i++ )
			{
				if ( aChargingMedics[i] )
				{
					aChargingMedics[i]->AwardAchievement( ACHIEVEMENT_TF_MEDIC_SIMUL_CHARGE );
				}
			}
		}
	}

	// reset this count
	pOwner->HandleAchievement_Medic_AssistHeavy( NULL );

	// If using the QuickFix, heal the medic
	if ( GetMedigunType() == MEDIGUN_QUICKFIX )
	{
		if ( IsReleasingCharge() && !m_bHealingSelf )
		{
			StartHealingTarget( pOwner );
			m_bHealingSelf = true;
		}
	}
#endif // GAME_DLL

	// STAGING_MEDIC
	if ( GetMedigunType() == MEDIGUN_RESIST )
	{
		// Remove charge immediately and just give target and yourself the conditions
		m_bChargeRelease = false;
#ifdef GAME_DLL
		int iResistDuration = 3.0f;
		pOwner->m_Shared.AddCond( g_MedigunResistConditions[GetResistType()].uberCond, iResistDuration, pOwner );
		m_flChargeLevel -= flChunkSize;
		if ( pTFPlayerPatient )
		{
			pTFPlayerPatient->m_Shared.AddCond( g_MedigunResistConditions[GetResistType()].uberCond, iResistDuration, pOwner );
		}
		if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
		{
			CBaseEntity *pTarget = m_hHealingTarget;
			CTFReviveMarker *pReviveMarker = dynamic_cast<CTFReviveMarker*>( pTarget );
			if ( pReviveMarker )
			{
				CTFPlayer *pDeadPlayer = pReviveMarker->GetOwner();
				if ( pDeadPlayer )
				{
					pReviveMarker->SetReviver( pOwner );
					// fill almost to max, give a small time period so patient has time to see notifications from regular revive code
					pReviveMarker->AddMarkerHealth( pReviveMarker->GetMaxHealth() * 0.9f ); 
				}
			}
		}

#endif
	}
}

//-----------------------------------------------------------------------------
// Purpose: Idle tests to see if we're facing a valid target for the medikit
//			If so, move into the "heal-able" animation. 
//			Otherwise, move into the "not-heal-able" animation.
//-----------------------------------------------------------------------------
void CWeaponMedigun::WeaponIdle( void )
{
	if ( HasWeaponIdleTimeElapsed() )
	{
		// Loop the welding animation
		if ( m_bHealing )
		{
			SendWeaponAnim( ACT_VM_PRIMARYATTACK );
			return;
		}

		return BaseClass::WeaponIdle();
	}
}

#if defined( CLIENT_DLL )
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponMedigun::StopHealSound( bool bStopHealingSound, bool bStopNoTargetSound )
{
	if ( bStopHealingSound )
	{
		StopSound( GetHealSound() );
	}

	if ( bStopNoTargetSound )
	{
		StopSound( "WeaponMedigun.NoTarget" );
	}

	if ( m_pDisruptSound )
	{
		CSoundEnvelopeController::GetController().SoundDestroy( m_pDisruptSound );
		m_pDisruptSound = NULL;
	}
}

void CWeaponMedigun::StopChargeEffect( bool bImmediately )
{
	// Either these should both be NULL or neither NULL
	Assert( ( m_pChargeEffect != NULL && m_pChargeEffectOwner != NULL ) || ( m_pChargeEffect == NULL && m_pChargeEffectOwner == NULL ) );

	if ( m_pChargeEffect != NULL && m_pChargeEffectOwner != NULL )
	{
		if( bImmediately )
		{
			m_pChargeEffectOwner->ParticleProp()->StopEmissionAndDestroyImmediately( m_pChargeEffect );
		}
		else
		{
			m_pChargeEffectOwner->ParticleProp()->StopEmission( m_pChargeEffect );
		}
		m_pChargeEffect = NULL;
		m_pChargeEffectOwner = NULL;
	}

	if ( m_pChargedSound != NULL )
	{
		CSoundEnvelopeController::GetController().SoundDestroy( m_pChargedSound );
		m_pChargedSound = NULL;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponMedigun::ManageChargeEffect( void )
{
	C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
	C_BaseEntity *pEffectOwner = this;

	if ( pLocalPlayer == NULL )
		return;

	if ( pLocalPlayer == GetTFPlayerOwner() )
	{
		pEffectOwner = pLocalPlayer->GetRenderedWeaponModel();
		if ( !pEffectOwner )
		{
			return;
		}
	}

	bool bOwnerTaunting = false;

	if ( GetTFPlayerOwner() && GetTFPlayerOwner()->m_Shared.InCond( TF_COND_TAUNTING ) == true )
	{
		bOwnerTaunting = true;
	}

	float flMinChargeToDeploy = GetMinChargeAmount();

	if ( GetTFPlayerOwner() && bOwnerTaunting == false && m_bHolstered == false && ( m_flChargeLevel >= flMinChargeToDeploy || m_bChargeRelease == true ) )
	{
		// Did we switch from 1st to 3rd or 3rd to 1st?  Taunting does this.
		if( pEffectOwner != m_pChargeEffectOwner )
		{
			// Stop the current effect so we can make a new one
			StopChargeEffect( m_bHolstered );
		}

		if ( m_pChargeEffect == NULL )
		{
			const char *pszEffectName = NULL;

			switch( GetTFPlayerOwner()->GetTeamNumber() )
			{
			case TF_TEAM_BLUE:
				pszEffectName = "medicgun_invulnstatus_fullcharge_blue";
				break;
			case TF_TEAM_RED:
				pszEffectName = "medicgun_invulnstatus_fullcharge_red";
				break;
			default:
				pszEffectName = "";
				break;
			}

			m_pChargeEffect = pEffectOwner->ParticleProp()->Create( pszEffectName, PATTACH_POINT_FOLLOW, "muzzle" );
			m_pChargeEffectOwner = pEffectOwner;
		}

		if ( m_pChargedSound == NULL )
		{
			CLocalPlayerFilter filter;

			CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();

			m_pChargedSound = controller.SoundCreate( filter, entindex(), "WeaponMedigun.Charged" );
			controller.Play( m_pChargedSound, 1.0, 100 );
		}
	}
	else
	{
		StopChargeEffect( m_bHolstered );
	}
}

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

	if ( m_bUpdateHealingTargets )
	{
		UpdateEffects();
		m_bUpdateHealingTargets = false;
	}

	if ( m_nOldChargeResistType != m_nChargeResistType )
	{
		m_nOldChargeResistType = m_nChargeResistType;
		C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
		if( GetOwner() == pLocalPlayer && pLocalPlayer )
		{
			// Sound effect
			pLocalPlayer->EmitSound( "WeaponMedigun_Vaccinator.Toggle" );	
		}
	}

	if ( m_bHealing )
	{
		CTFPlayer *pTarget = ToTFPlayer( m_hHealingTarget );
		if ( !m_pDisruptSound && pTarget && pTarget->m_Shared.InCond( TF_COND_HEALING_DEBUFF ) )
		{
			CLocalPlayerFilter filter;
			CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
			m_pDisruptSound = controller.SoundCreate( filter, entindex(), "WeaponMedigun.HealingDisrupt" );
			controller.Play( m_pDisruptSound, 1.f, 100.f );
		}
		else if ( m_pDisruptSound )
		{
			CSoundEnvelopeController::GetController().SoundDestroy( m_pDisruptSound );
			m_pDisruptSound = NULL;
		}
	}
	else
	{
		ClientThinkList()->SetNextClientThink( GetClientHandle(), CLIENT_THINK_NEVER );
		m_bPlayingSound = false;
		StopHealSound( true, false );

		// Are they holding the attack button but not healing anyone? Give feedback.
		if ( IsActiveByLocalPlayer() && GetOwner() && GetOwner()->IsAlive() && m_bAttacking && GetOwner() == C_BasePlayer::GetLocalPlayer() && CanAttack() == true )
		{
			if ( gpGlobals->curtime >= m_flNextBuzzTime )
			{
				CLocalPlayerFilter filter;
				EmitSound( filter, entindex(), "WeaponMedigun.NoTarget" );
				m_flNextBuzzTime = gpGlobals->curtime + 0.5f;	// only buzz every so often.
			}
		}
		else
		{
			StopHealSound( false, true );	// Stop the "no target" sound.
		}
	}

	// Think?
	if ( m_bHealing || IsCarriedByLocalPlayer() )
	{
		ClientThinkList()->SetNextClientThink( GetClientHandle(), CLIENT_THINK_ALWAYS );
	}

	ManageChargeEffect();

	// Find teammates that need healing
	if ( IsCarriedByLocalPlayer() )
	{
		C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
		if ( !pLocalPlayer || !pLocalPlayer->IsPlayerClass( TF_CLASS_MEDIC ) )
		{
			return;
		}

		if ( pLocalPlayer == GetOwner() && hud_medicautocallers.GetBool() )
		{
			UpdateMedicAutoCallers();
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponMedigun::ClientThink()
{
	C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
	if ( !pLocalPlayer )
	{
		return;
	}

	// Don't show it while the player is dead. Ideally, we'd respond to m_bHealing in OnDataChanged,
	// but it stops sending the weapon when it's holstered, and it gets holstered when the player dies.
	CTFPlayer *pFiringPlayer = ToTFPlayer( GetOwnerEntity() );
	if ( !pFiringPlayer || pFiringPlayer->IsPlayerDead() || pFiringPlayer->IsDormant() )
	{
		ClientThinkList()->SetNextClientThink( GetClientHandle(), CLIENT_THINK_NEVER );
		m_bPlayingSound = false;
		StopHealSound();
		return;
	}

	// If the local player is the guy getting healed, let him know 
	// who's healing him, and their charge level.
	if( m_hHealingTarget != NULL )
	{
		if ( pLocalPlayer == m_hHealingTarget )
		{
			pLocalPlayer->SetHealer( pFiringPlayer, m_flChargeLevel );
		}

		// Setup whether we were last healed by the local player or by someone else (used by replay system)
		// since GetHealer() gets cleared out every frame before player_death events get fired.  See tf_replay.cpp.
		C_BaseEntity *pHealingTargetEnt = m_hHealingTarget;
		if ( pHealingTargetEnt && pHealingTargetEnt->IsPlayer() )
		{
			C_TFPlayer *pHealingTargetPlayer = ToTFPlayer( pHealingTargetEnt );
			pHealingTargetPlayer->SetWasHealedByLocalPlayer( pFiringPlayer == pLocalPlayer );
		}

		if ( !m_bPlayingSound )
		{
			m_bPlayingSound = true;
			CLocalPlayerFilter filter;
			EmitSound( filter, entindex(), GetHealSound() );
		}
	}

	if ( m_bOldChargeRelease != m_bChargeRelease )
	{
		m_bOldChargeRelease = m_bChargeRelease;
		ForceHealingTargetUpdate();
	}

	// If the rendered weapon has changed, we need to update our particles
	if ( m_hHealingTargetEffect.pOwner && pFiringPlayer->GetRenderedWeaponModel() != m_hHealingTargetEffect.pOwner )
	{
		ForceHealingTargetUpdate();
	}

	if ( pFiringPlayer->m_Shared.IsEnteringOrExitingFullyInvisible() )
	{
		UpdateEffects();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponMedigun::UpdateEffects( void )
{
	CTFPlayer *pFiringPlayer = ToTFPlayer( GetOwnerEntity() );
	if ( !pFiringPlayer )
		return;

	C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
	C_BaseEntity *pEffectOwner = this;
	if ( pLocalPlayer == pFiringPlayer )
	{
		pEffectOwner = pLocalPlayer->GetRenderedWeaponModel();
	}

	// If we're still healing and our owner changed, then we did something
	// like changed 
	bool bImmediate = pEffectOwner != m_hHealingTargetEffect.pOwner && m_bHealing;

	// Remove all the effects
	if ( m_hHealingTargetEffect.pOwner )
	{
		if ( m_hHealingTargetEffect.pEffect )
		{
			bImmediate ? m_hHealingTargetEffect.pOwner->ParticleProp()->StopEmissionAndDestroyImmediately( m_hHealingTargetEffect.pEffect )
					   : m_hHealingTargetEffect.pOwner->ParticleProp()->StopEmission( m_hHealingTargetEffect.pEffect );
		}
		if ( m_hHealingTargetEffect.pCustomEffect )
		{
			bImmediate ? m_hHealingTargetEffect.pOwner->ParticleProp()->StopEmissionAndDestroyImmediately( m_hHealingTargetEffect.pCustomEffect )
					   : m_hHealingTargetEffect.pOwner->ParticleProp()->StopEmission( m_hHealingTargetEffect.pCustomEffect );
		}
	}
	else
	{
		if ( m_hHealingTargetEffect.pEffect )
		{
			m_hHealingTargetEffect.pEffect->StopEmission();
		}
		if ( m_hHealingTargetEffect.pCustomEffect )
		{
			m_hHealingTargetEffect.pCustomEffect->StopEmission();
		}
	}
	m_hHealingTargetEffect.pOwner			= NULL;
	m_hHealingTargetEffect.pTarget			= NULL;
	m_hHealingTargetEffect.pEffect			= NULL;
	m_hHealingTargetEffect.pCustomEffect	= NULL;

	// Don't add targets if the medic is dead
	if ( !pEffectOwner || pFiringPlayer->IsPlayerDead() || !pFiringPlayer->IsPlayerClass( TF_CLASS_MEDIC ) || pFiringPlayer->m_Shared.IsFullyInvisible() )
		return;

	// Add our targets
	// Loops through the healing targets, and make sure we have an effect for each of them

	if ( m_hHealingTarget )
	{
		if ( m_hHealingTargetEffect.pTarget == m_hHealingTarget )
			return;

		bool bReviveMarker = m_hReviveMarker && m_hReviveMarker == m_hHealingTarget;	// Hack to avoid another dynamic_cast here
		bool bHealTargetMarker = hud_medichealtargetmarker.GetBool() && !bReviveMarker;

		const char *pszEffectName;
		if ( IsAttachedToBuilding() )
		{
			pszEffectName = "medicgun_beam_machinery";
		}
		else if ( pFiringPlayer->GetTeamNumber() == TF_TEAM_RED )
		{
			if ( m_bChargeRelease )
			{
				pszEffectName = "medicgun_beam_red_invun";
			}
			else
			{
				if ( bHealTargetMarker && pFiringPlayer == pLocalPlayer )
				{
					pszEffectName = "medicgun_beam_red_targeted";
				}
				else
				{
					pszEffectName = "medicgun_beam_red";
				}
			}
		}
		else
		{
			if ( m_bChargeRelease )
			{
				pszEffectName = "medicgun_beam_blue_invun";
			}
			else
			{
				if ( bHealTargetMarker && pFiringPlayer == pLocalPlayer )
				{
					pszEffectName = "medicgun_beam_blue_targeted";
				}
				else
				{
					pszEffectName = "medicgun_beam_blue";
				}
			}
		}

		// Attach differently if targeting a revive marker
		float flVecHeightOffset = bReviveMarker ? 0.f : 50.f;
		ParticleAttachment_t attachType = bReviveMarker ? PATTACH_POINT_FOLLOW : PATTACH_ABSORIGIN_FOLLOW;
		const char *pszAttachName = bReviveMarker ? "healbeam" : NULL;

		CNewParticleEffect *pEffect = pEffectOwner->ParticleProp()->Create( pszEffectName, PATTACH_POINT_FOLLOW, "muzzle" );
		pEffectOwner->ParticleProp()->AddControlPoint( pEffect, 1, m_hHealingTarget, attachType, pszAttachName, Vector(0.f,0.f,flVecHeightOffset) );

		m_hHealingTargetEffect.pTarget = m_hHealingTarget;
		m_hHealingTargetEffect.pEffect = pEffect;
		m_hHealingTargetEffect.pOwner  = pEffectOwner;

		// See if we have a custom particle effect that wants to add to the beam
		CEconItemView *pItem = m_AttributeManager.GetItem();
		int iSystems = pItem->GetStaticData()->GetNumAttachedParticles( GetTeamNumber() );
		for ( int i = 0; i < iSystems; i++ )
		{
			attachedparticlesystem_t *pSystem = pItem->GetStaticData()->GetAttachedParticleData( GetTeamNumber(),i );
			if ( pSystem->iCustomType == 1 )
			{
				pEffect = pEffectOwner->ParticleProp()->Create( pSystem->pszSystemName, PATTACH_POINT_FOLLOW, "muzzle" );
				pEffectOwner->ParticleProp()->AddControlPoint( pEffect, 1, m_hHealingTarget, attachType, pszAttachName, Vector(0.f,0.f,flVecHeightOffset) );
				m_hHealingTargetEffect.pCustomEffect = pEffect;
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Look for teammates that need healing
//-----------------------------------------------------------------------------
void CWeaponMedigun::UpdateMedicAutoCallers( void )
{
	// Find teammates that need healing
	if ( gpGlobals->curtime > m_flAutoCallerCheckTime )
	{
		if ( !g_TF_PR )
		{
			return;
		}

		C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();

		for( int playerIndex = 1; playerIndex <= MAX_PLAYERS; playerIndex++ )
		{
			if ( g_TF_PR->GetTeam( playerIndex ) == GetLocalPlayerTeam() )
			{
				C_TFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( playerIndex ) );

				// Don't do this for the local player
				if ( pPlayer == NULL || pPlayer == pLocalPlayer )
					continue;

				if( m_hHealingTarget != NULL )
				{
					// Don't do this for players the medic is healing
					if ( pPlayer == m_hHealingTarget )
						continue;
				}

				if ( pPlayer->IsAlive() )
				{
					int iHealth = float( pPlayer->GetHealth() ) / float( pPlayer->GetMaxHealth() ) * 100;
					int iHealthThreshold = hud_medicautocallersthreshold.GetInt();

					// If it's a healthy teammate....
					if ( iHealth > iHealthThreshold )
					{
						// Make sure we don't have them in our list if previously hurt
						if ( m_iAutoCallers.Find( playerIndex ) != m_iAutoCallers.InvalidIndex() )
						{
							m_iAutoCallers.FindAndRemove( playerIndex );
							continue;
						}
					}

					// If it's a hurt teammate....
					if ( iHealth <= iHealthThreshold )
					{

						// Make sure we're not already tracking this
						if ( m_iAutoCallers.Find( playerIndex ) != m_iAutoCallers.InvalidIndex())
							continue;

						// Distance check
						float flDistSq = pPlayer->GetAbsOrigin().DistToSqr( pLocalPlayer->GetAbsOrigin() );
						if ( flDistSq >= 1000000 )
						{
							continue;
						}

						// Now add auto-caller
						pPlayer->CreateSaveMeEffect( CALLER_TYPE_AUTO );

						// And track the player so we don't re-add them
						m_iAutoCallers.AddToTail( playerIndex );
					}
				}
			}
		}

		// Throttle this check
		m_flAutoCallerCheckTime = gpGlobals->curtime + 0.25f;
	}
}
#endif

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponMedigun::CheckAchievementsOnHealTarget( void )
{
	CTFPlayer *pTFPlayer = ToTFPlayer( m_hHealingTarget );
	if ( !pTFPlayer )
		return;

#ifdef GAME_DLL

	// Check for "target under fire" achievement
	if ( pTFPlayer->m_AchievementData.CountDamagersWithinTime(3.0) >= 4 )
	{
		if ( GetTFPlayerOwner() )
		{
			GetTFPlayerOwner()->AwardAchievement( ACHIEVEMENT_TF_MEDIC_HEAL_UNDER_FIRE );
		}
	}

	// Check for "Engineer repairing sentrygun" achievement
	if ( pTFPlayer->IsPlayerClass( TF_CLASS_ENGINEER ) )
	{
		// Has Engineer worked on his sentrygun recently?
		CBaseObject	*pSentry = pTFPlayer->GetObjectOfType( OBJ_SENTRYGUN );
		if ( pSentry && pTFPlayer->m_AchievementData.IsTargetInHistory( pSentry, 4.0 ) )
		{
			if ( pSentry->m_AchievementData.CountDamagersWithinTime(3.0) > 0 )
			{
				CTFPlayer *pOwner = GetTFPlayerOwner();
				if ( pOwner )
				{
					// give to medic
					pOwner->AwardAchievement( ACHIEVEMENT_TF_MEDIC_HEAL_ENGINEER );

					// give to the engineer!
					pTFPlayer->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_REPAIR_SENTRY_W_MEDIC );
				}
			}
		}
	}
#else

	// check for ACHIEVEMENT_TF_MEDIC_HEAL_CALLERS
	if ( pTFPlayer->m_flSaveMeExpireTime > gpGlobals->curtime )
	{
		IGameEvent *event = gameeventmanager->CreateEvent( "player_healedmediccall" );
		if ( event )
		{
			event->SetInt( "userid", pTFPlayer->GetUserID() );
			gameeventmanager->FireEventClientSide( event );
		}
	}

#endif
}

//-----------------------------------------------------------------------------
// Purpose: Our owner has become stunned.
//-----------------------------------------------------------------------------
void CWeaponMedigun::OnControlStunned( void )
{
	BaseClass::OnControlStunned();

	// Interrupt auto healing.
	RemoveHealingTarget( true );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CWeaponMedigun::EffectMeterShouldFlash( void )
{
	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
		return false;

	if ( pPlayer && ( pPlayer->m_Shared.GetRageMeter() >= 100.0f || pPlayer->m_Shared.IsRageDraining() ) )
		return true;
	else
		return false;
}

//-----------------------------------------------------------------------------
// Purpose: UI Progress
//-----------------------------------------------------------------------------
float CWeaponMedigun::GetProgress( void )
{
	CTFPlayer *pPlayer = GetTFPlayerOwner();
	if ( !pPlayer )
		return 0.f;

	return pPlayer->m_Shared.GetRageMeter() / 100.0f;
}

//-----------------------------------------------------------------------------
// entity_medigun_shield
//-----------------------------------------------------------------------------
#define MEDIGUNSHIELD_THINK_CONTEXT		"CTFMedigunShield_ShieldThink"

LINK_ENTITY_TO_CLASS( entity_medigun_shield, CTFMedigunShield );
PRECACHE_WEAPON_REGISTER( entity_medigun_shield );

// Network
IMPLEMENT_NETWORKCLASS_ALIASED( TFMedigunShield, DT_TFMedigunShield )

BEGIN_NETWORK_TABLE( CTFMedigunShield, DT_TFMedigunShield )
END_NETWORK_TABLE()

BEGIN_PREDICTION_DATA( CTFMedigunShield )
END_PREDICTION_DATA()

// Data
BEGIN_DATADESC( CTFMedigunShield )
#ifdef GAME_DLL
DEFINE_FUNCTION( ShieldTouch ),
DEFINE_THINKFUNC( ShieldThink ),
#endif // GAME_DLL
END_DATADESC()

#ifdef GAME_DLL
static const float PROJECTILE_SHIELD_ALPHA_BASE = 150.f;
static const float PROJECTILE_SHIELD_ENERGY_REFILL_PULSE = 10.f;
static const float PROJECTILE_SHIELD_ENERGY_MAX = 1000.f;
#endif // GAME_DLL

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
CTFMedigunShield::CTFMedigunShield()
{
	m_nBlinkCount = 0;
#ifdef GAME_DLL
	m_pTouchLoop = NULL;

#ifdef STAGING_ONLY
	m_bPermanentShield = false;
#endif // STAGING_ONLY

#endif // GAME_DLL
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
CTFMedigunShield::~CTFMedigunShield()
{
#ifdef GAME_DLL
	if ( m_pTouchLoop )
	{
		CSoundEnvelopeController::GetController().SoundDestroy( m_pTouchLoop );
		m_pTouchLoop = NULL;
	}
#endif // GAME_DLL
}

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

	SetModel( "models/props_mvm/mvm_player_shield.mdl" );
	SetSolid( SOLID_VPHYSICS );
	SetSolidFlags( FSOLID_TRIGGER );
	SetCollisionGroup( TFCOLLISION_GROUP_COMBATOBJECT );
	SetBlocksLOS( false );
	AddEffects( EF_NOSHADOW );

	m_takedamage = DAMAGE_EVENTS_ONLY;

#ifdef GAME_DLL
	m_flShieldEnergyLevel = PROJECTILE_SHIELD_ENERGY_MAX;

	SetTouch( &CTFMedigunShield::ShieldTouch );
	SetContextThink( &CTFMedigunShield::ShieldThink, gpGlobals->curtime, MEDIGUNSHIELD_THINK_CONTEXT );
#else
	SetNextClientThink( CLIENT_THINK_ALWAYS );
#endif // GAME_DLL
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFMedigunShield::Precache()
{
	PrecacheScriptSound( "WeaponMedi_Shield.Deploy" );
	PrecacheScriptSound( "WeaponMedi_Shield.Protection" );
	PrecacheScriptSound( "WeaponMedi_Shield.Retract" );
	PrecacheScriptSound( "WeaponMedi_Shield.Burn" );
	PrecacheScriptSound( "WeaponMedi_Shield.Burn_lp" );

	PrecacheModel( "models/props_mvm/mvm_player_shield.mdl" );
	PrecacheModel( "models/props_mvm/mvm_player_shield2.mdl" );

	BaseClass::Precache();
}

#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFMedigunShield::ClientThink()
{
	UpdateShieldPosition();
}
#endif // CLIENT_DLL

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFMedigunShield::UpdateShieldPosition( void )
{
	CBaseEntity *pOwner = GetOwnerEntity();
	if ( !pOwner )
		return;

	// Positioning logic
	Vector vecForward;
	AngleVectors( pOwner->EyeAngles(), &vecForward );
	vecForward.z = 0.f;
	Vector vecShieldOrigin = pOwner->GetAbsOrigin() + vecForward * 145.f;
	SetAbsOrigin( vecShieldOrigin );
	SetAbsAngles( QAngle( 0.f, pOwner->EyeAngles().y, 0.f ) );	// Ignore pitch
}

#ifdef GAME_DLL
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CTFMedigunShield *CTFMedigunShield::Create( CTFPlayer *pOwner )
{
	if ( pOwner )
	{
		CTFMedigunShield *pShield = static_cast< CTFMedigunShield* >( CBaseEntity::Create( "entity_medigun_shield", pOwner->GetAbsOrigin() + Vector( 0, 0, 60 ), pOwner->GetAbsAngles() ) );
		if ( pShield )
		{
			pShield->SetOwnerEntity( pOwner );
			pShield->ChangeTeam( pOwner->GetTeamNumber() );

			int nShieldLevel = 0;
			CALL_ATTRIB_HOOK_INT_ON_OTHER( pOwner, nShieldLevel, generate_rage_on_heal );
			if ( nShieldLevel > 1 )
			{
				pShield->SetModel( "models/props_mvm/mvm_player_shield2.mdl" );
			}

			pShield->m_nSkin = pShield->GetTeamNumber() == TF_TEAM_RED ? 0 : 1;

			CPVSFilter filter( pOwner->WorldSpaceCenter() );
			pOwner->EmitSound( filter, pOwner->entindex(), "WeaponMedi_Shield.Deploy" );

			return pShield;
		}
	}

	return NULL;
}

//------------------------------------------------------------------------------
// Purpose: 
//------------------------------------------------------------------------------
// bool CTFMedigunShield::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace )
// {
// 	switch( GetTeamNumber() )
// 	{
// 	case TF_TEAM_RED:
// 		if ( ( mask & CONTENTS_REDTEAM ) )
// 			return false;
// 		break;
// 
// 	case TF_TEAM_BLUE:
// 		if ( ( mask & CONTENTS_BLUETEAM ) )
// 			return false;
// 		break;
// 	}
// 
// 	vcollide_t *pCollide = modelinfo->GetVCollide( GetModelIndex() );
// 
// 	UTIL_ClearTrace( trace );
// 
// 	physcollision->TraceBox( ray, pCollide->solids[0], GetAbsOrigin(), GetAbsAngles(), &trace );
// 
// 	if ( trace.fraction >= 1 )
// 		return false;
// 
// 	// return owner as trace hit
// 	trace.m_pEnt = this;
// 	return true;
// }

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFMedigunShield::ShouldCollide( int collisionGroup, int contentsMask ) const
{
	if ( collisionGroup == COLLISION_GROUP_PROJECTILE || 
		 collisionGroup == TFCOLLISION_GROUP_ROCKETS || 
		 collisionGroup == TFCOLLISION_GROUP_ROCKET_BUT_NOT_WITH_OTHER_ROCKETS )
	{
		switch( GetTeamNumber() )
		{
		case TF_TEAM_RED:
			if ( ( contentsMask & CONTENTS_BLUETEAM ) )
				return false;
			break;

		case TF_TEAM_BLUE:
			if ( ( contentsMask & CONTENTS_REDTEAM ) )
				return false;
			break;
		}
	}

	return BaseClass::ShouldCollide( collisionGroup, contentsMask );
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CTFMedigunShield::StartTouch( CBaseEntity *pOther )
{
	BaseClass::StartTouch( pOther );

	CBaseEntity *pOwner = GetOwnerEntity();
	if ( !pOwner )
		return;

	if ( !InSameTeam( pOther ) )
	{
		CPVSFilter filter( WorldSpaceCenter() );
		pOwner->EmitSound( filter, entindex(), "WeaponMedi_Shield.Burn" );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFMedigunShield::ShieldTouch( CBaseEntity *pOther )
{
	if ( !pOther )
		return;

	if ( !InSameTeam( pOther ) )
	{
		// Drip pan for unknown projectiles
		if ( !pOther->IsDeflectable() && pOther->GetCollisionGroup() == COLLISION_GROUP_PROJECTILE )
		{
			UTIL_Remove( pOther );
			return;
		}

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

		if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && ( pTFOwner->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) )
			return;

		int nShieldLevel = 0;
		CALL_ATTRIB_HOOK_INT_ON_OTHER( pTFOwner, nShieldLevel, generate_rage_on_heal );

		// Damage it
		float flDamage = ( nShieldLevel > 1 ) ? 2.f : 1.f;
		CTakeDamageInfo info;
		info.SetAttacker( pTFOwner );
		info.SetInflictor( this ); 
		info.SetWeapon( pTFOwner->GetActiveTFWeapon() );
		info.SetDamage( flDamage );
		info.SetDamageType( DMG_ENERGYBEAM );
		info.SetDamageCustom( TF_DMG_CUSTOM_PLASMA );
		info.SetDamagePosition( pOther->EyePosition() );
		pOther->TakeDamage( info );

		// Slow enemy players
		if ( pOther->IsPlayer() )
		{
			CTFPlayer *pTFVictim = ToTFPlayer( pOther );
			if ( !pTFVictim )
				return;

			float flStun = ( nShieldLevel > 1 ) ? 0.5f : 0.3f;
			pTFVictim->m_Shared.StunPlayer( 0.5f, flStun, TF_STUN_MOVEMENT, pTFOwner );
		}

		// Sound
		if ( !m_pTouchLoop )
		{
			CPVSFilter filter( GetAbsOrigin() );
			CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
			m_pTouchLoop = controller.SoundCreate( filter, entindex(), "WeaponMedi_Shield.Burn_lp" );
			controller.Play( m_pTouchLoop, 1.0, 100 );
		}
	}
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CTFMedigunShield::EndTouch( CBaseEntity *pOther )
{
	BaseClass::EndTouch( pOther );

	if ( m_pTouchLoop )
	{
		CSoundEnvelopeController::GetController().SoundDestroy( m_pTouchLoop );
		m_pTouchLoop = NULL;
	}
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CTFMedigunShield::ShieldThink( void )
{
	CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
#ifdef STAGING_ONLY
	if ( !pOwner || ( !pOwner->m_Shared.IsRageDraining() && !m_bPermanentShield ) )
#else
	if ( !pOwner || !pOwner->m_Shared.IsRageDraining() )
#endif
	{
		RemoveShield();
		return;
	}

	UpdateShieldPosition();
	
	// Regen shield
	if ( m_flShieldEnergyLevel < PROJECTILE_SHIELD_ENERGY_MAX )
	{
		m_flShieldEnergyLevel += PROJECTILE_SHIELD_ENERGY_REFILL_PULSE;
		m_flShieldEnergyLevel = Min( m_flShieldEnergyLevel, PROJECTILE_SHIELD_ENERGY_MAX );
	}

	// Visuals
	SetRenderMode( kRenderTransAlpha );
	int nShieldOpacity = PROJECTILE_SHIELD_ALPHA_BASE;

	// Determine alpha
	float flFadePoint = PROJECTILE_SHIELD_ENERGY_MAX / 2.f;
	if ( m_flShieldEnergyLevel <= flFadePoint )
	{
		nShieldOpacity = (int)RemapValClamped( m_flShieldEnergyLevel, 0.f, flFadePoint, 255.f, PROJECTILE_SHIELD_ALPHA_BASE );
	}

	// Blink when we're about to expire
#ifdef STAGING_ONLY
	if ( pOwner->m_Shared.GetRageMeter() <= 25.f && !m_bPermanentShield )
#else
	if ( pOwner->m_Shared.GetRageMeter() <= 25.f )
#endif
	{
		++m_nBlinkCount;

		if ( m_nBlinkCount & 0x2 )
		{
			SetRenderColorA( PROJECTILE_SHIELD_ALPHA_BASE - 100 );
		}
		else
		{
			SetRenderColorA( nShieldOpacity );
		}

		// Play a retract sound
		if ( m_nBlinkCount == 1 )
		{
			CPVSFilter filter( pOwner->WorldSpaceCenter() );
			pOwner->EmitSound( filter, entindex(), "WeaponMedi_Shield.Retract" );
		}
	}
	else
	{
		SetRenderColorA( nShieldOpacity );
	}

	SetContextThink( &CTFMedigunShield::ShieldThink, gpGlobals->curtime + 0.1f, MEDIGUNSHIELD_THINK_CONTEXT );
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CTFMedigunShield::RemoveShield( void )
{
	SetTouch( NULL );
	AddEffects( EF_NODRAW );

	UTIL_Remove( this );
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CTFMedigunShield::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
{
	if ( !InSameTeam( info.GetAttacker() ) )
	{
		CEffectData	data;
		data.m_vOrigin = ptr->endpos - (vecDir * 8);
		data.m_vNormal = -vecDir;
		data.m_flMagnitude = RemapValClamped( info.GetDamage(), 1.f, 50.f, 0.5f, 2.f );

		CPVSFilter filter( GetAbsOrigin() );
		te->DispatchEffect( filter, 0.f, data.m_vOrigin, "EnergyShieldImpact", data );

		// g_pEffects->EnergySplash( ptr->endpos - (vecDir * 8), -vecDir, false );
	}

	BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
int	CTFMedigunShield::OnTakeDamage( const CTakeDamageInfo &info )
{
	if ( !InSameTeam( info.GetAttacker() ) )
	{
		CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
		if ( pOwner )
		{
			float flDamage = info.GetDamage();
			m_flShieldEnergyLevel -= flDamage;
			m_flShieldEnergyLevel = Max( m_flShieldEnergyLevel, 0.f );

			// Add to blocked damage stat
			CTF_GameStats.Event_PlayerBlockedDamage( pOwner, flDamage );

// 			CPVSFilter filter( pOwner->WorldSpaceCenter() );
// 			pOwner->EmitSound( filter, entindex(), "WeaponMedi_Shield.Protection" );

			pOwner->PlayDamageResistSound( 100.f, RandomFloat( 0.85f, 1.f ) );

			IGameEvent *event = gameeventmanager->CreateEvent( "medigun_shield_blocked_damage" );
			if ( event )
			{
				event->SetInt( "userid", pOwner->GetUserID() );
				event->SetFloat( "damage", flDamage );
				gameeventmanager->FireEvent( event );
			}
		}
	}

	return 0;
}
#endif // GAME_DLL


