//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Engineer's Dispenser
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"

#include "tf_obj_dispenser.h"
#include "engine/IEngineSound.h"
#include "tf_player.h"
#include "tf_team.h"
#include "tf_gamerules.h"
#include "vguiscreen.h"
#include "world.h"
#include "explode.h"
#include "tf_gamestats.h"
#include "tf_halloween_souls_pickup.h"
#include "tf_fx.h"

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

#define DISPENSER_MINS			Vector( -20, -20,  0 )
#define DISPENSER_MAXS			Vector(  20,  20, 55 )	// tweak me

#define MINI_DISPENSER_MINS		Vector( -20, -20,  0 )
#define MINI_DISPENSER_MAXS		Vector(  20,  20, 20 )

#define DISPENSER_TRIGGER_MINS			Vector( -70, -70,  0 )
#define DISPENSER_TRIGGER_MAXS			Vector(  70,  70, 50 )	// tweak me

#define REFILL_CONTEXT			"RefillContext"
#define DISPENSE_CONTEXT		"DispenseContext"

ConVar tf_cart_spell_drop_rate( "tf_cart_spell_drop_rate", "4" );
ConVar tf_cart_duck_drop_rate( "tf_cart_duck_drop_rate", "10", FCVAR_DEVELOPMENTONLY );
ConVar tf_cart_soul_drop_rate( "tf_cart_soul_drop_rate", "10", FCVAR_DEVELOPMENTONLY );

//-----------------------------------------------------------------------------
// Purpose: SendProxy that converts the Healing list UtlVector to entindices
//-----------------------------------------------------------------------------
void SendProxy_HealingList( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
{
	CObjectDispenser *pDispenser = (CObjectDispenser*)pStruct;

	// If this assertion fails, then SendProxyArrayLength_HealingArray must have failed.
	Assert( iElement < pDispenser->m_hHealingTargets.Size() );

	CBaseEntity *pEnt = pDispenser->m_hHealingTargets[iElement].Get();
	EHANDLE hOther = pEnt;

	SendProxy_EHandleToInt( pProp, pStruct, &hOther, pOut, iElement, objectID );
}

int SendProxyArrayLength_HealingArray( const void *pStruct, int objectID )
{
	CObjectDispenser *pDispenser = (CObjectDispenser*)pStruct;
	return pDispenser->m_hHealingTargets.Count();
}

IMPLEMENT_SERVERCLASS_ST( CObjectDispenser, DT_ObjectDispenser )
	SendPropInt( SENDINFO( m_iState ), 2 ),
	SendPropInt( SENDINFO( m_iAmmoMetal ), -1, SPROP_VARINT ),
	SendPropInt( SENDINFO( m_iMiniBombCounter ), -1, SPROP_VARINT ),

	SendPropArray2( 
		SendProxyArrayLength_HealingArray,
		SendPropInt("healing_array_element", 0, SIZEOF_IGNORE, NUM_NETWORKED_EHANDLE_BITS, SPROP_UNSIGNED, SendProxy_HealingList), 
		MAX_PLAYERS, 
		0, 
		"healing_array"
		)

END_SEND_TABLE()

BEGIN_DATADESC( CObjectDispenser )
	DEFINE_THINKFUNC( RefillThink ),
	DEFINE_THINKFUNC( DispenseThink ),

	// key
	DEFINE_KEYFIELD( m_iszCustomTouchTrigger, FIELD_STRING, "touch_trigger" ),

END_DATADESC()

LINK_ENTITY_TO_CLASS(obj_dispenser, CObjectDispenser);
PRECACHE_REGISTER(obj_dispenser);

// How much ammo is given our per use
#define DISPENSER_DROP_METAL		40

float g_flDispenserHealRates[4] = 
{
	0,
	10.0,
	15.0,
	20.0
};

float g_flDispenserAmmoRates[4] = 
{
	0,
	0.2,
	0.3,
	0.4
};

LINK_ENTITY_TO_CLASS( dispenser_touch_trigger, CDispenserTouchTrigger );

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CObjectDispenser::CObjectDispenser()
{
	int iHealth = GetMaxHealthForCurrentLevel();
	m_hTouchTrigger = NULL;
	SetMaxHealth( iHealth );
	SetHealth( iHealth );
	UseClientSideAnimation();

	m_hTouchingEntities.Purge();
	m_bUseGenerateMetalSound = true;
	m_bThrown = false;

	m_bPlayAmmoPickupSound = true;

	SetType( OBJ_DISPENSER );
}

CObjectDispenser::~CObjectDispenser()
{
	if ( m_hTouchTrigger.Get() )
	{
		UTIL_Remove( m_hTouchTrigger );
	}

	ResetHealingTargets();

	StopSound( "Building_Dispenser.Idle" );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectDispenser::DetonateObject( void )
{
	// We already dying?
	if ( m_bDying )
		return;

#ifdef STAGING_ONLY
	// If we're built, explode for damage
	if ( IsMiniBuilding() && !IsCarried() && !IsBuilding() && !IsPlacing() )
	{
		Vector vecOrigin = GetAbsOrigin();
		CTraceFilterIgnorePlayers traceFilter( NULL, COLLISION_GROUP_PROJECTILE );

		// base 50 damage, scale by metal amount
		float flDamage = RemapValClamped( m_iAmmoMetal, 0, MINI_DISPENSER_MAX_METAL, 50.0f, 300.0f );
		CTakeDamageInfo info( this, GetOwner(), flDamage, DMG_BLAST );

		// Scale blast radius
		float flRadius = RemapValClamped( m_iAmmoMetal, 0, MINI_DISPENSER_MAX_METAL, 150.0f, 200.0f );
		CTFRadiusDamageInfo radiusinfo( &info, vecOrigin, flRadius, NULL, flRadius );
		TFGameRules()->RadiusDamage( radiusinfo );

		CPVSFilter filter( vecOrigin );
		TE_TFExplosion( filter, 0.0f, vecOrigin, Vector(0,0,0), TF_WEAPON_GRENADE_PIPEBOMB, kInvalidEHandleExplosion, -1, SPECIAL1, INVALID_STRING_INDEX );
	}
#endif

	TFGameRules()->OnDispenserDestroyed( this );

	BaseClass::DetonateObject();
}

//-----------------------------------------------------------------------------
void CObjectDispenser::DestroyObject( void )
{
	if ( TFGameRules() )
	{
		TFGameRules()->OnDispenserDestroyed( this );
	}

	BaseClass::DestroyObject();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectDispenser::Spawn()
{
	SetModel( GetPlacementModel() );

	m_iState.Set( DISPENSER_STATE_IDLE );

	SetTouch( &CObjectDispenser::Touch );

	BaseClass::Spawn();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectDispenser::FirstSpawn()
{
	SetSolid( SOLID_BBOX );

	bool bShouldBeMini = ShouldBeMiniBuilding( GetOwner() );

	UTIL_SetSize(this,
		bShouldBeMini ? MINI_DISPENSER_MINS : DISPENSER_MINS,
		bShouldBeMini ? MINI_DISPENSER_MAXS : DISPENSER_MAXS );

	m_takedamage = DAMAGE_YES;
	m_iAmmoMetal = 0;

	int iHealth = GetMaxHealthForCurrentLevel();

	SetMaxHealth( iHealth );
	SetHealth( iHealth );

	BaseClass::FirstSpawn();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
const char* CObjectDispenser::GetBuildingModel( int iLevel )
{
#ifdef STAGING_ONLY
	if ( ShouldBeMiniBuilding( GetOwner() ) )
	{
		return MINI_DISPENSER_MODEL_BUILDING;
	}
	else
#endif // STAGING_ONLY
	{
		switch ( iLevel )
		{
		case 1:
			return DISPENSER_MODEL_BUILDING;
			break;
		case 2:
			return DISPENSER_MODEL_BUILDING_LVL2;
			break;
		case 3:
			return DISPENSER_MODEL_BUILDING_LVL3;
			break;
		default:
			return DISPENSER_MODEL_BUILDING;
			break;
		}
	}

	Assert( 0 );
	return DISPENSER_MODEL;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
const char* CObjectDispenser::GetFinishedModel( int iLevel )
{
#ifdef STAGING_ONLY
	if ( IsMiniBuilding() )
	{
		return MINI_DISPENSER_MODEL;
	}
	else
#endif // STAGING_ONLY
	{
		switch ( iLevel )
		{
		case 1:
			return DISPENSER_MODEL;
			break;
		case 2:
			return DISPENSER_MODEL_LVL2;
			break;
		case 3:
			return DISPENSER_MODEL_LVL3;
			break;
		default:
			return DISPENSER_MODEL;
			break;
		}
	}

	Assert( 0 );
	return DISPENSER_MODEL;
}

const char* CObjectDispenser::GetPlacementModel()
{
	return /*IsMiniBuilding() ? MINI_DISPENSER_MODEL_PLACEMENT :*/ DISPENSER_MODEL_PLACEMENT;
}

void CObjectDispenser::StartPlacement( CTFPlayer *pPlayer )
{
	BaseClass::StartPlacement( pPlayer );
}

//-----------------------------------------------------------------------------
// Purpose: Start building the object
//-----------------------------------------------------------------------------
bool CObjectDispenser::StartBuilding( CBaseEntity *pBuilder )
{
	SetStartBuildingModel();

	// Have to re-call this in case the player changed their weapon
	// between StartPlacement and StartBuilding.
	MakeMiniBuilding( GetBuilder() );

	CreateBuildPoints();

	TFGameRules()->OnDispenserBuilt( this );

	bool bIsCarried = IsCarried();

	bool bStartBuildingResult = BaseClass::StartBuilding( pBuilder );

	if ( bIsCarried )
	{
		OnEndBeingCarried( pBuilder );
	}
	
	return bStartBuildingResult;
}

void CObjectDispenser::SetStartBuildingModel( void )
{
	SetModel( GetBuildingModel( 1 ) );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectDispenser::MakeMiniBuilding( CTFPlayer* pPlayer )
{
	if ( !ShouldBeMiniBuilding( pPlayer ) || IsMiniBuilding() )
		return;

	BaseClass::MakeMiniBuilding( pPlayer );

	int iHealth = GetMaxHealthForCurrentLevel();

	SetMaxHealth( iHealth );
	SetHealth( iHealth );
}

//-----------------------------------------------------------------------------
// Purpose: Raises the Dispenser one level
//-----------------------------------------------------------------------------
void CObjectDispenser::StartUpgrading( void )
{
	// m_iUpgradeLevel is incremented in BaseClass::StartUpgrading(), 
	// but we need to set the model before calling it so SetActivity() will be successful
	SetModel( GetBuildingModel( m_iUpgradeLevel+1 ) );

	// clear our healing list, everyone will be 
	// added again at the new heal rate in DispenseThink()
	ResetHealingTargets();

	BaseClass::StartUpgrading();

	m_iState.Set( DISPENSER_STATE_UPGRADING );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectDispenser::FinishUpgrading( void )
{
	m_iState.Set( DISPENSER_STATE_IDLE );

	BaseClass::FinishUpgrading();

	if ( m_iUpgradeLevel > 1 )
	{
		SetModel( GetFinishedModel( m_iUpgradeLevel ) );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectDispenser::SetModel( const char *pModel )
{
	BaseClass::SetModel( pModel );

		// Reset this after model change
#ifdef STAGING_ONLY
		UTIL_SetSize(this,
			IsMiniBuilding() ? MINI_DISPENSER_MINS : DISPENSER_MINS,
			IsMiniBuilding() ? MINI_DISPENSER_MAXS : DISPENSER_MAXS );
#else
		UTIL_SetSize( this,
			DISPENSER_MINS,
			DISPENSER_MAXS );
#endif // STAGING_ONLY
	ResetSequenceInfo();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectDispenser::InitializeMapPlacedObject( void )
{
	// make sure we are using an appropriate model so that the control panels are in the right place
	SetModel( GetFinishedModel( 1 ) );

	BaseClass::InitializeMapPlacedObject();
}

bool CObjectDispenser::ShouldBeMiniBuilding( CTFPlayer* pPlayer )
{
#ifdef STAGING_ONLY
	int nMiniDispenserEnabled = 0;
	CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwner(), nMiniDispenserEnabled, allows_building_mini_dispenser );
	return nMiniDispenserEnabled != 0;
#else
	return false;
#endif // STAGING_ONLY
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CObjectDispenser::GetMaxUpgradeLevel()
{
#ifdef STAGING_ONLY
	if ( IsMiniBuilding() )
		return DISPENSER_MINI_MAX_LEVEL;
#endif // STAGING_ONLY

	return BaseClass::GetMaxUpgradeLevel();
}

//-----------------------------------------------------------------------------
// Purpose: Finished building
//-----------------------------------------------------------------------------
void CObjectDispenser::OnGoActive( void )
{
	SetModel( GetFinishedModel( 1 ) );

	CreateBuildPoints();

	ReattachChildren();

	// Put some ammo in the Dispenser
	if ( !m_bCarryDeploy && !IsMiniBuilding() )
	{
		m_iAmmoMetal = TFGameRules()->IsQuickBuildTime() ? DISPENSER_MAX_METAL_AMMO : 25;
	}

	// Begin thinking
	SetContextThink( &CObjectDispenser::RefillThink, gpGlobals->curtime + 3, REFILL_CONTEXT );
	SetContextThink( &CObjectDispenser::DispenseThink, gpGlobals->curtime + 0.1, DISPENSE_CONTEXT );

	m_flNextAmmoDispense = gpGlobals->curtime + 0.5;

	if ( m_hTouchTrigger.Get() && dynamic_cast<CObjectCartDispenser*>(this) != NULL )
	{
		UTIL_Remove( m_hTouchTrigger.Get() );
		m_hTouchTrigger = NULL;
	}

	float flRadius = GetDispenserRadius();
	
	if ( m_iszCustomTouchTrigger != NULL_STRING )
	{
		m_hTouchTrigger = dynamic_cast<CDispenserTouchTrigger *> ( gEntList.FindEntityByName( NULL, m_iszCustomTouchTrigger ) );

		if ( m_hTouchTrigger.Get() != NULL )
		{
			m_hTouchTrigger->SetOwnerEntity( this );	//owned
		}
	}

	if ( m_hTouchTrigger.Get() == NULL )
	{
		m_hTouchTrigger = CBaseEntity::Create( "dispenser_touch_trigger", GetAbsOrigin(), vec3_angle, this );
		UTIL_SetSize(m_hTouchTrigger.Get(), Vector(-flRadius,-flRadius,-flRadius), Vector(flRadius,flRadius,flRadius) );
		m_hTouchTrigger->SetSolid(SOLID_BBOX);
	}

	Assert( m_hTouchTrigger.Get() );

	BaseClass::OnGoActive();

	PlayActiveSound();
}

void CObjectDispenser::PlayActiveSound()
{
	EmitSound( "Building_Dispenser.Idle" );
}

//-----------------------------------------------------------------------------
// Spawn the vgui control screens on the object
//-----------------------------------------------------------------------------
void CObjectDispenser::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
{
	// Panels 0 and 1 are both control panels for now
	if ( nPanelIndex == 0 || nPanelIndex == 1 )
	{
		if ( GetTeamNumber() == TF_TEAM_RED )
		{
			pPanelName = "screen_obj_dispenser_red";
		}
		else
		{
			pPanelName = "screen_obj_dispenser_blue";
		}
	}
	else
	{
		BaseClass::GetControlPanelInfo( nPanelIndex, pPanelName );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectDispenser::Precache()
{
	BaseClass::Precache();

	int iModelIndex;

	PrecacheModel( DISPENSER_MODEL_PLACEMENT );

	iModelIndex = PrecacheModel( DISPENSER_MODEL_BUILDING );
	PrecacheGibsForModel( iModelIndex );

	iModelIndex = PrecacheModel( DISPENSER_MODEL );
	PrecacheGibsForModel( iModelIndex );

	iModelIndex = PrecacheModel( DISPENSER_MODEL_BUILDING_LVL2 );
	PrecacheGibsForModel( iModelIndex );

	iModelIndex = PrecacheModel( DISPENSER_MODEL_LVL2 );
	PrecacheGibsForModel( iModelIndex );

	iModelIndex = PrecacheModel( DISPENSER_MODEL_BUILDING_LVL3 );
	PrecacheGibsForModel( iModelIndex );

	iModelIndex = PrecacheModel( DISPENSER_MODEL_LVL3 );
	PrecacheGibsForModel( iModelIndex );

#ifdef STAGING_ONLY
	PrecacheGibsForModel( PrecacheModel( MINI_DISPENSER_MODEL_PLACEMENT ) );
	PrecacheGibsForModel( PrecacheModel( MINI_DISPENSER_MODEL_BUILDING ) );
	PrecacheGibsForModel( PrecacheModel( MINI_DISPENSER_MODEL ) );
#endif // STAGING_ONLY

	PrecacheVGuiScreen( "screen_obj_dispenser_blue" );
	PrecacheVGuiScreen( "screen_obj_dispenser_red" );

	PrecacheScriptSound( "Building_Dispenser.Idle" );
	PrecacheScriptSound( "Building_Dispenser.GenerateMetal" );
	PrecacheScriptSound( "Building_Dispenser.Heal" );

	PrecacheParticleSystem( "dispenser_heal_red" );
	PrecacheParticleSystem( "dispenser_heal_blue" );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CObjectDispenser::DispenseAmmo( CTFPlayer *pPlayer )
{
	int iTotalPickedUp = 0;
	int iAmmoToAdd = 0;

	int nNoPrimaryAmmoFromDispensersWhileActive = 0;
	CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer->GetActiveWeapon(), nNoPrimaryAmmoFromDispensersWhileActive, no_primary_ammo_from_dispensers );

#ifdef STAGING_ONLY
	float flAmmoRate = IsMiniBuilding() ? DISPENSER_MINI_AMMO_RATE : g_flDispenserAmmoRates[GetUpgradeLevel()];
#else
	float flAmmoRate = g_flDispenserAmmoRates[GetUpgradeLevel()];
#endif

	CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetBuilder(), flAmmoRate, mult_dispenser_rate );

	if ( nNoPrimaryAmmoFromDispensersWhileActive == 0 )
	{
		// primary
		iAmmoToAdd = (int)( pPlayer->GetMaxAmmo( TF_AMMO_PRIMARY ) * flAmmoRate );
		iTotalPickedUp += pPlayer->GiveAmmo( iAmmoToAdd, TF_AMMO_PRIMARY, !m_bPlayAmmoPickupSound, kAmmoSource_DispenserOrCart );
	}

	// secondary
	iAmmoToAdd = (int)( pPlayer->GetMaxAmmo( TF_AMMO_SECONDARY ) * flAmmoRate );
	iTotalPickedUp += pPlayer->GiveAmmo( iAmmoToAdd, TF_AMMO_SECONDARY, !m_bPlayAmmoPickupSound, kAmmoSource_DispenserOrCart );

	// metal
	int iNoMetalFromDispenserWhileActive = 0;
	CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer->GetActiveWeapon(), iNoMetalFromDispenserWhileActive, no_metal_from_dispensers_while_active );
	if ( iNoMetalFromDispenserWhileActive == 0 )
	{
		iTotalPickedUp += DispenseMetal( pPlayer );
	}

	if ( iTotalPickedUp > 0 )
	{
		if ( m_bPlayAmmoPickupSound )
		{
			EmitSound( "BaseCombatCharacter.AmmoPickup" );
		}

		CTFPlayer *pOwner = GetOwner();
		if ( pOwner && pOwner != pPlayer )
		{
			// This is crude; it doesn't account for the value difference in resupplying rockets vs pistol bullets.
			// Still, it's better than nothing when trying to measure the value classes generate.
			if ( TFGameRules() && 
				 TFGameRules()->GameModeUsesUpgrades() &&
				 TFGameRules()->State_Get() == GR_STATE_RND_RUNNING )
			{
				CTF_GameStats.Event_PlayerAwardBonusPoints( pOwner, pPlayer, 1 );
			}
		}

		return true;
	}

	// return false if we didn't pick up anything
	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CObjectDispenser::DispenseMetal( CTFPlayer *pPlayer )
{
	int iMetalToGive = DISPENSER_DROP_METAL + ((GetUpgradeLevel()-1) * 10);
	int iMetal = pPlayer->GiveAmmo( Min( m_iAmmoMetal.Get(), iMetalToGive ), TF_AMMO_METAL, false, kAmmoSource_DispenserOrCart );
	m_iAmmoMetal -= iMetal;

	return iMetal;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectDispenser::RefillThink( void )
{
	if ( IsCarried() )
		return;

	SetContextThink( &CObjectDispenser::RefillThink, gpGlobals->curtime + 6, REFILL_CONTEXT );

	if ( IsDisabled() )
	{
		return;
	}

	// Auto-refill half the amount as tfc, but twice as often
	if ( m_iAmmoMetal < DISPENSER_MAX_METAL_AMMO )
	{
		int iMetal = (DISPENSER_MAX_METAL_AMMO * 0.1) + ((GetUpgradeLevel()-1) * 10);

		CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetBuilder(), iMetal, mult_dispenser_rate );

		m_iAmmoMetal = Min( m_iAmmoMetal + iMetal, DISPENSER_MAX_METAL_AMMO );

		if ( m_bUseGenerateMetalSound )
		{
			EmitSound( "Building_Dispenser.GenerateMetal" );
		}
	}
}


//-----------------------------------------------------------------------------
// Generate ammo over time
//-----------------------------------------------------------------------------
void CObjectDispenser::DispenseThink( void )
{
	if ( IsCarried() )
		return;

	if ( IsDisabled() )
	{
		// Don't heal or dispense ammo
		SetContextThink( &CObjectDispenser::DispenseThink, gpGlobals->curtime + 0.1, DISPENSE_CONTEXT );

		// stop healing everyone
		ResetHealingTargets();
		return;
	}
	
	float flRadius = GetDispenserRadius();

	if ( m_flNextAmmoDispense <= gpGlobals->curtime )
	{
		int iNumNearbyPlayers = 0;

		if ( GetOwner() )
		{
			// find players in sphere, that are visible
			if ( ( flRadius != m_flPrevRadius ) && m_hTouchTrigger.Get() )
			{
				UTIL_SetSize( m_hTouchTrigger.Get(), Vector( -flRadius, -flRadius, -flRadius ), Vector( flRadius, flRadius, flRadius ) );	
			}
		}

		m_flPrevRadius = flRadius;

		Vector vecOrigin = GetAbsOrigin() + Vector(0,0,32);

		CBaseEntity *pListOfNearbyEntities[32];
		int iNumberOfNearbyEntities = UTIL_EntitiesInSphere( pListOfNearbyEntities, ARRAYSIZE( pListOfNearbyEntities ), vecOrigin, flRadius, FL_CLIENT );
		for ( int i=0;i<iNumberOfNearbyEntities;i++ )
		{
			CTFPlayer *pPlayer = ToTFPlayer( pListOfNearbyEntities[i] );

			if ( !pPlayer || !pPlayer->IsAlive() )
				continue;

			if ( pPlayer->GetTeamNumber() != GetTeamNumber() )
			{
				if ( !pPlayer->IsPlayerClass( TF_CLASS_SPY ) || ( pPlayer->m_Shared.GetDisguiseTeam() != GetTeamNumber() ) )
					continue;
			}

			DispenseAmmo( pPlayer );

			iNumNearbyPlayers++;
		}

		// Try to dispense more often when no players are around so we 
		// give it as soon as possible when a new player shows up
#ifdef STAGING_ONLY
		float flNextAmmoDelay = IsMiniBuilding() ? DISPENSER_MINI_AMMO_THINK : 1.0;
#else
		float flNextAmmoDelay = 1.0;
#endif
		m_flNextAmmoDispense = gpGlobals->curtime + ( ( iNumNearbyPlayers > 0 ) ? flNextAmmoDelay : 0.1 );
	}	

	// for each player in touching list
	int iSize = m_hTouchingEntities.Count();
	bool bIsAnyTeammateTouching = false;

	if ( m_hTouchTrigger )
	{
		for ( int i = iSize-1; i >= 0; i-- )
		{
			EHANDLE hOther = m_hTouchingEntities[i];

			CBaseEntity *pEnt = hOther.Get();
			if ( !pEnt )
				continue;

			// stop touching and healing a dead entity, or one that is grossly out of range (EndTouch() can be flakey)
			float flDistSqr = (m_hTouchTrigger->WorldSpaceCenter() - pEnt->WorldSpaceCenter()).LengthSqr();
			Vector vecMins, vecMaxs;
			m_hTouchTrigger->GetCollideable()->WorldSpaceSurroundingBounds( &vecMins, &vecMaxs );
			float flDoubleRadiusSqr = ( vecMaxs - vecMins ).LengthSqr();
			if ( !pEnt->IsAlive() || ( flDistSqr > flDoubleRadiusSqr ) )
			{
				m_hTouchingEntities.FindAndRemove( hOther );
				StopHealing( pEnt );
				continue;
			}

			bIsAnyTeammateTouching |= ( pEnt->IsPlayer() && pEnt->GetTeamNumber() == GetTeamNumber() );


			bool bHealingTarget = IsHealingTarget( pEnt );
			bool bValidHealTarget = CouldHealTarget( pEnt );

			if ( bHealingTarget && !bValidHealTarget )
			{
				// if we can't see them, remove them from healing list
				// does nothing if we are not healing them already
				StopHealing( pEnt );
			}
			else if ( !bHealingTarget && bValidHealTarget )
			{
				// if we can see them, add to healing list	
				// does nothing if we are healing them already
				StartHealing( pEnt );
			}	
		}
	}

	if ( bIsAnyTeammateTouching )
	{
		if ( !m_spellTimer.HasStarted() )
		{
			m_spellTimer.Start( tf_cart_spell_drop_rate.GetFloat() );
		}

		if ( !m_duckTimer.HasStarted() )
		{
			m_duckTimer.Start( tf_cart_duck_drop_rate.GetFloat() );
		}

		if ( !m_soulTimer.HasStarted() )
		{
			m_soulTimer.Start( tf_cart_soul_drop_rate.GetFloat() );
		}
	}
	else
	{
		m_spellTimer.Invalidate();
		m_duckTimer.Invalidate();
		m_soulTimer.Invalidate();
	}

	if ( m_spellTimer.HasStarted() && m_spellTimer.IsElapsed() )
	{
		m_spellTimer.Start( tf_cart_spell_drop_rate.GetFloat() );
		DropSpellPickup();
	}

	if ( m_duckTimer.HasStarted() && m_duckTimer.IsElapsed() )
	{
		m_duckTimer.Start( tf_cart_duck_drop_rate.GetFloat() );
		DropDuckPickup();
	}

	if ( m_soulTimer.HasStarted() && m_soulTimer.IsElapsed() )
	{
		m_soulTimer.Start( tf_cart_soul_drop_rate.GetFloat() );
		DispenseSouls();
	}

	SetContextThink( &CObjectDispenser::DispenseThink, gpGlobals->curtime + 0.1, DISPENSE_CONTEXT );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectDispenser::StartTouch( CBaseEntity *pOther )
{
	// add to touching entities
	EHANDLE hOther = pOther;
	m_hTouchingEntities.AddToTail( hOther );

	if ( !IsBuilding() && !IsDisabled() && CouldHealTarget( pOther ) && !IsHealingTarget( pOther ) )
	{
		// try to start healing them
		StartHealing( pOther );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectDispenser::Touch( CBaseEntity *pOther )
{
	// We dont want to touch these
	if ( pOther->IsSolidFlagSet( FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS ) )
		return;

	// Handle hitting skybox (disappear).
	const trace_t *pTrace = &CBaseEntity::GetTouchTrace();
	if( pTrace->surface.flags & SURF_SKY )
	{
		UTIL_Remove( this );
		return;
	}

}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectDispenser::EndTouch( CBaseEntity *pOther )
{
	// remove from touching entities
	EHANDLE hOther = pOther;
	m_hTouchingEntities.FindAndRemove( hOther );

	// remove from healing list
	StopHealing( pOther );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectDispenser::ResetHealingTargets( void )
{
	// tell all the players we're not healing them anymore
	for ( int i = m_hHealingTargets.Count()-1 ; i >= 0 ; i-- )
	{
		EHANDLE hEnt = m_hHealingTargets[i];
		CBaseEntity *pOther = hEnt.Get();

		if ( pOther )
		{
			StopHealing( pOther );
		}
	}

}

//-----------------------------------------------------------------------------
// Purpose: Try to start healing this target
//-----------------------------------------------------------------------------
float CObjectDispenser::GetHealRate() const
{
#ifdef STAGING_ONLY
	float flHealRate = IsMiniBuilding() ? DISPENSER_MINI_HEAL_RATE : g_flDispenserHealRates[GetUpgradeLevel()];
#else
	float flHealRate = g_flDispenserHealRates[GetUpgradeLevel()];
#endif
	CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetBuilder(), flHealRate, mult_dispenser_rate );

	return flHealRate;
}

//-----------------------------------------------------------------------------
// Purpose: Try to start healing this target
//-----------------------------------------------------------------------------
void CObjectDispenser::StartHealing( CBaseEntity *pOther )
{
	if ( IsCarried() )
		return;

	AddHealingTarget( pOther );

	CTFPlayer *pPlayer = ToTFPlayer( pOther );

	if ( pPlayer )
	{
		float flHealRate = GetHealRate();
		float flOverhealBonus = 1.0;
		pPlayer->m_Shared.Heal( this, flHealRate, flOverhealBonus, 1.0, true, GetBuilder() );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Stop healing this target
//-----------------------------------------------------------------------------
void CObjectDispenser::StopHealing( CBaseEntity *pOther )
{
	if ( RemoveHealingTarget( pOther ) )
	{
		CTFPlayer *pPlayer = ToTFPlayer( pOther );

		if ( pPlayer )
		{
			float flHealingDone = pPlayer->m_Shared.StopHealing( this );
			if ( GetBuilder() && pOther != GetBuilder() && flHealingDone > 0 )
			{
				GetBuilder()->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_DISPENSER_HEAL_GRIND, floor( flHealingDone ) );

				if ( GetBuilder()->GetTeam() == pOther->GetTeam() )
				{
					// Strange Health Provided to Allies
					EconEntity_OnOwnerKillEaterEvent( 
						dynamic_cast<CEconEntity *>( GetBuilder()->GetEntityForLoadoutSlot( LOADOUT_POSITION_PDA ) ),
						GetBuilder(),
						pPlayer,
						kKillEaterEvent_HealingProvided,
						(int)flHealingDone
					);
				}
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Is this a valid heal target? and not already healing them?
//-----------------------------------------------------------------------------
bool CObjectDispenser::CouldHealTarget( CBaseEntity *pTarget )
{
	if ( !HasSpawnFlags( SF_DISPENSER_IGNORE_LOS ) && !pTarget->FVisible( this, MASK_BLOCKLOS ) )
		return false;

	if ( pTarget->IsPlayer() && pTarget->IsAlive() )
	{
		CTFPlayer *pTFPlayer = ToTFPlayer( pTarget );

		// Don't heal while in purgatory.  Players take damage constantly while down there in
		// order to flush them out.  If we're healing players down there, that goes against
		// the purpose of the damage.
		if ( pTFPlayer->IsInPurgatory() )
			return false;

		// don't heal enemies unless they are disguised as our team
		int iTeam = GetTeamNumber();
		int iPlayerTeam = pTFPlayer->GetTeamNumber();

		if ( iPlayerTeam != iTeam && pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) )
		{
			iPlayerTeam = pTFPlayer->m_Shared.GetDisguiseTeam();
		}

		if ( iPlayerTeam != iTeam )
		{
			return false;
		}

		if ( HasSpawnFlags( SF_DISPENSER_DONT_HEAL_DISGUISED_SPIES ) )
		{
			// if they're invis, no heals
			if ( pTFPlayer->m_Shared.IsStealthed() )
			{
				return false;
			}

			// if they're disguised as enemy
			if ( pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) &&
				 pTFPlayer->m_Shared.GetDisguiseTeam() != iTeam )
			{
				return false;
			}
		}

		return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float CObjectDispenser::GetDispenserRadius( void )
{
	float flRadius = 64.f;

	if ( GetOwner() )
	{
		CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwner(), flRadius, mult_dispenser_radius );
	}

	return flRadius;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectDispenser::AddHealingTarget( CBaseEntity *pOther )
{
	// add to tail
	EHANDLE hOther = pOther;
	m_hHealingTargets.AddToTail( hOther );
	NetworkStateChanged();

	// check how many healing targets we now have and possibly award an achievement
	if ( m_hHealingTargets.Count() >= 3 && GetBuilder() )
	{
		GetBuilder()->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_DISPENSER_HEAL_GROUP );
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CObjectDispenser::RemoveHealingTarget( CBaseEntity *pOther )
{
	// remove
	EHANDLE hOther = pOther;
	bool bFound = m_hHealingTargets.FindAndRemove( hOther );
	NetworkStateChanged();

	return bFound;
}

//-----------------------------------------------------------------------------
// Purpose: Are we healing this target already
//-----------------------------------------------------------------------------
bool CObjectDispenser::IsHealingTarget( CBaseEntity *pTarget )
{
	EHANDLE hOther = pTarget;
	return m_hHealingTargets.HasElement( hOther );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CObjectDispenser::DrawDebugTextOverlays(void) 
{
	int text_offset = BaseClass::DrawDebugTextOverlays();

	if (m_debugOverlays & OVERLAY_TEXT_BIT) 
	{
		char tempstr[512];
		Q_snprintf( tempstr, sizeof( tempstr ),"Metal: %d", m_iAmmoMetal.Get() );
		EntityText(text_offset,tempstr,0);
		text_offset++;
	}
	return text_offset;
}

//-----------------------------------------------------------------------------
// 
//-----------------------------------------------------------------------------
void CObjectDispenser::MakeCarriedObject( CTFPlayer *pCarrier )
{
	if ( m_hTouchTrigger.Get() )
	{
		UTIL_Remove( m_hTouchTrigger );
	}

	ResetHealingTargets();

	m_hTouchingEntities.Purge();

	StopSound( "Building_Dispenser.Idle" );

	BaseClass::MakeCarriedObject( pCarrier );
}


//-----------------------------------------------------------------------------
// Cart Dispenser
//-----------------------------------------------------------------------------

BEGIN_DATADESC( CObjectCartDispenser )
	DEFINE_INPUTFUNC( FIELD_INTEGER, "FireHalloweenBonus", InputFireHalloweenBonus ),
	DEFINE_INPUTFUNC( FIELD_INTEGER, "SetDispenserLevel", InputSetDispenserLevel ),
 	DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
END_DATADESC()

IMPLEMENT_SERVERCLASS_ST( CObjectCartDispenser, DT_ObjectCartDispenser )
END_SEND_TABLE()

LINK_ENTITY_TO_CLASS( mapobj_cart_dispenser, CObjectCartDispenser );

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CObjectCartDispenser::CObjectCartDispenser()
{
	m_bUseGenerateMetalSound = false;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectCartDispenser::Spawn( void )
{
	// This cast is for the benefit of GCC
	m_fObjectFlags |= (int)OF_DOESNT_HAVE_A_MODEL;
	m_takedamage = DAMAGE_NO;
	m_iUpgradeLevel = 1;

	TFGameRules()->OnDispenserBuilt( this );
}

//-----------------------------------------------------------------------------
// Purpose: Finished building
//-----------------------------------------------------------------------------
void CObjectCartDispenser::OnGoActive( void )
{
	BaseClass::OnGoActive();

	SetModel( "" ); 
}

//-----------------------------------------------------------------------------
// Spawn the vgui control screens on the object
//-----------------------------------------------------------------------------
void CObjectCartDispenser::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
{
	// no panels
	return;
}

//-----------------------------------------------------------------------------
// Don't decrement our metal count
//-----------------------------------------------------------------------------
int CObjectCartDispenser::DispenseMetal( CTFPlayer *pPlayer )
{
	int iMetal = pPlayer->GiveAmmo( MIN( m_iAmmoMetal, DISPENSER_DROP_METAL ), TF_AMMO_METAL, false, kAmmoSource_DispenserOrCart );
	return iMetal;
}


void CObjectCartDispenser::DropSpellPickup()
{
	if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_HIGHTOWER ) )
	{
		TFGameRules()->DropSpellPickup( GetAbsOrigin() );
	}
}

void CObjectCartDispenser::DropDuckPickup()
{
	if ( TFGameRules()->IsHolidayActive( kHoliday_EOTL ) && TFGameRules()->ShouldDropBonusDuck() )
	{
		TFGameRules()->DropBonusDuck( GetAbsOrigin() );
	}
}

void CObjectCartDispenser::DispenseSouls()
{
	// Give a soul to the entire team
	if ( TFGameRules() && TFGameRules()->IsHolidayActive( kHoliday_Halloween ) )
	{
		TFGameRules()->DropHalloweenSoulPackToTeam( 1, GetAbsOrigin(), GetTeamNumber(), TEAM_SPECTATOR );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectCartDispenser::SetModel( const char *pModel )
{
	CBaseObject::SetModel( pModel );
}

//-----------------------------------------------------------------------------
// Purpose: Give players near the dispenser a bonus
//-----------------------------------------------------------------------------
void CObjectCartDispenser::InputFireHalloweenBonus( inputdata_t &inputdata )
{
	for ( int i = m_hHealingTargets.Count()-1 ; i >= 0 ; i-- )
	{
		EHANDLE hEnt = m_hHealingTargets[i];
		CTFPlayer *pTFPlayer = ToTFPlayer( hEnt.Get() );

		if ( pTFPlayer )
		{
			pTFPlayer->m_Shared.AddCond( TF_COND_CRITBOOSTED_CTF_CAPTURE, inputdata.value.Int() );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectCartDispenser::InputSetDispenserLevel( inputdata_t &inputdata )
{
	int iLevel = inputdata.value.Int();

	if ( iLevel >= 1 && iLevel <= 3 )
	{
		m_iUpgradeLevel = iLevel;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectCartDispenser::InputEnable( inputdata_t &inputdata )
{
	SetDisabled( false );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectCartDispenser::InputDisable( inputdata_t &inputdata )
{
	SetDisabled( true );
}