//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose: Team management class. Contains all the details for a specific team
//
// $NoKeywords: $
//=============================================================================
#include "cbase.h"
#include "team.h"
#include "tf_team.h"
#include "tf_func_resource.h"
#include "tf_player.h"
#include "techtree.h"
#include "tf_obj.h"
#include "tf_obj_resupply.h"
#include "orders.h"
#include "entitylist.h"
#include "team_spawnpoint.h"
#include "team_messages.h"
#include "tf_obj_powerpack.h"
#include "tf_gamerules.h"
#include "engine/IEngineSound.h"
#include "vstdlib/strtools.h"
#include "tf_stats.h"
#include "tf_obj_buff_station.h"

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

#define OBJECT_COVERED_DIST	1000
#define RESOURCE_GIVE_TIME 30
#define RESOURCE_GIVE_AMOUNT 150
#define RESOURCE_DONATION_AMT_PER_PLAYER 10

bool IsEntityVisibleToTactical( int iLocalTeamNumber, int iLocalTeamPlayers, 
	int iLocalTeamObjects, int iEntIndex, const char *pEntName, int pEntTeamNumber, const Vector &pEntOrigin );
extern ConVar tf_destroyobjects;

//-----------------------------------------------------------------------------
// Purpose: SendProxy that converts the UtlVector list of radar scanners to entindexes, where it's reassembled on the client
//-----------------------------------------------------------------------------
void SendProxy_ObjectList( const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
{
	CTFTeam *pTeam = (CTFTeam*)pData;

	// If this fails, then SendProxyArrayLength_TeamObjects didn't work.
	Assert( iElement < pTeam->GetNumObjects() );

	CBaseObject *pObject = pTeam->GetObject(iElement);
	EHANDLE hObject;
	hObject = pObject;

	SendProxy_EHandleToInt( pStruct, &hObject, pOut, iElement, objectID );
}


int SendProxyArrayLength_TeamObjects( const void *pStruct, int objectID )
{
	CTFTeam *pTeam = (CTFTeam*)pStruct;
	int iObjects = pTeam->GetNumObjects();
	Assert( iObjects < MAX_OBJECTS_PER_TEAM );
	return iObjects;
}


// Datatable
IMPLEMENT_SERVERCLASS_ST(CTFTeam, DT_TFTeam)
	SendPropFloat( SENDINFO(m_fResources), 16, SPROP_NOSCALE ),
	SendPropFloat( SENDINFO(m_fPotentialResources), 16, SPROP_NOSCALE ),
	SendPropInt( SENDINFO(m_bHaveZone), 1, SPROP_UNSIGNED ), 
	
	SendPropArray2( 
		SendProxyArrayLength_TeamObjects,
		SendPropInt("object_array_element", 0, SIZEOF_IGNORE, NUM_NETWORKED_EHANDLE_BITS, SPROP_UNSIGNED, SendProxy_ObjectList), 
		MAX_OBJECTS_PER_TEAM, 
		0, 
		"object_array"
		 )
END_SEND_TABLE()

LINK_ENTITY_TO_CLASS( tf_team_manager, CTFTeam );

//-----------------------------------------------------------------------------
// Purpose: Get a pointer to the specified TF team manager
//-----------------------------------------------------------------------------
CTFTeam *GetGlobalTFTeam( int iIndex )
{
	return (CTFTeam*)GetGlobalTeam( iIndex );
}


//-----------------------------------------------------------------------------
// Purpose: Needed because this is an entity, but should never be used
//-----------------------------------------------------------------------------
void CTFTeam::Init( const char *pName, int iNumber )
{
	BaseClass::Init( pName, iNumber );

	InitializeTeamResources();
	InitializeTechTree();
	InitializeOrders();
	ClearMessages();

	m_flNextResourceTime = 0;

	// Only detect changes every half-second.
	m_NetStateMgr.SetUpdateInterval( 0.75f );

	m_flTotalResourcesSoFar = m_iLastUpdateSentAt = 0;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CTFTeam::~CTFTeam( void )
{
	m_aResourcesBeingCollected.Purge();
	m_aResupplyBeacons.Purge();
	m_aObjects.Purge();
	m_aOrders.Purge();

	delete m_pTechnologyTree;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFTeam::Precache( void )
{
	// Precache all the technologies in the techtree
	for ( int i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ )
	{
		PrecacheTechnology( m_pTechnologyTree->GetTechnology(i) );
	}

	// Cache sounds
	enginesound->PrecacheSound( "vox/captured-alpha-resource.wav" );
	enginesound->PrecacheSound( "vox/captured-beta-resource.wav" );
	enginesound->PrecacheSound( "vox/captured-charlie-resource.wav" );
	enginesound->PrecacheSound( "vox/resource-lost.wav" );
	enginesound->PrecacheSound( "vox/voted-for-tech-bought.wav" );
	enginesound->PrecacheSound( "vox/stolen-tech-received.wav" );
}


//-----------------------------------------------------------------------------
// Purpose: Precache a technology's files
//-----------------------------------------------------------------------------
void CTFTeam::PrecacheTechnology( CBaseTechnology *pTech )
{
	// Precache sounds for every class result
	for (int i = 0; i < TFCLASS_CLASS_COUNT; i++ )
	{
		if ( pTech->GetSoundFile(i) && (pTech->GetSoundFile(i)[0] != 0) )
		{
			pTech->SetClassResultSound( i, enginesound->PrecacheSound( pTech->GetSoundFile(i) ) );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Called every frame
//-----------------------------------------------------------------------------
void CTFTeam::Think( void )
{
	UpdateOrders();
	UpdateMessages();

	// FIXME: Try this out?
	/*
	// Give resources to the team at regular intervals
	if (gpGlobals->curtime >= m_flNextResourceTime)
	{
		AddTeamResources( RESOURCE_GIVE_AMOUNT );
		m_flNextResourceTime = gpGlobals->curtime + RESOURCE_GIVE_TIME;
	}
	*/

	UpdateTechnologies();
		    
	/* FIXME: Re-enable once we figure out what the correct orders should be
	// Create new personal orders
	if ( m_flPersonalOrderUpdateTime < gpGlobals->curtime )
	{
		CreatePersonalOrders();
		m_flPersonalOrderUpdateTime = gpGlobals->curtime + PERSONAL_ORDER_UPDATE_TIME;
	}
	*/
}

//-----------------------------------------------------------------------------
// DATA HANDLING
//-----------------------------------------------------------------------------
// Purpose: Check to see if we should resend the entire tech tree to a player on Hud reinitialisation, which happens every player respawn
//-----------------------------------------------------------------------------
void CTFTeam::UpdateClientData( CBasePlayer *pPlayer )
{
	CBaseTFPlayer *pTFPlayer = (CBaseTFPlayer *)pPlayer;
	// If we're initialising the hud, update all technologies
	if ( pTFPlayer->HUDNeedsRestart() )
	{
		// Check all the technologies and resend any that differ from this client's representation
		for ( int i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ )
		{
			// Update all technologies
			CBaseTechnology *technology = m_pTechnologyTree->GetTechnology(i);
			if ( technology )
			{
				// Check to see if any resource levels have changed
				if ( pTFPlayer->AvailableTech(i).m_nResourceLevel != technology->GetResourceLevel() )
				{
					UpdateClientTechnology( i, pTFPlayer );
					continue;
				}

				if ( technology->GetAvailable() != pTFPlayer->AvailableTech(i).m_nAvailable )
				{
					UpdateClientTechnology( i, pTFPlayer );
					continue;
				}

				byte pcount = technology->GetPreferenceCount();
				if ( pTFPlayer->GetPreferredTechnology() == i )
				{
					pcount |= 0x80;
				}

				if ( pcount != pTFPlayer->AvailableTech(i).m_nUserCount )
				{
					UpdateClientTechnology( i, pTFPlayer );
				}
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Update a technology for a player
//-----------------------------------------------------------------------------
void CTFTeam::UpdateClientTechnology( int iTechID, CBaseTFPlayer *pPlayer )
{
	CBaseTechnology *pTechnology = m_pTechnologyTree->GetTechnology( iTechID );
	if ( !pTechnology )
		return;

	byte pcount = pTechnology->GetPreferenceCount();

	if ( pPlayer->GetPreferredTechnology() == iTechID )
	{
		pcount |= 0x80;
	}

	CSingleUserRecipientFilter user( pPlayer );
	user.MakeReliable();

	// Update this technology
	UserMessageBegin( user, "Technology" );
		WRITE_BYTE( iTechID );
		WRITE_BYTE( pTechnology->GetAvailable() );
		WRITE_BYTE( pcount );
		WRITE_SHORT( (short)pTechnology->GetResourceLevel() );
	MessageEnd();

	// Update the player's client tech representation
	pPlayer->AvailableTech(iTechID).m_nAvailable = pTechnology->GetAvailable();
	pPlayer->AvailableTech(iTechID).m_nUserCount = pcount;
	pPlayer->AvailableTech(iTechID).m_nResourceLevel = pTechnology->GetResourceLevel();

	/*
	Msg( "Sent %s(%d) to %s:\n", pTechnology->GetName(), iTechID, STRING(pPlayer->pl->netname) );
	Msg( "   Available: %d\n", pTechnology->GetAvailable() );
	Msg( "   PrefCount: %d\n", pTechnology->GetPreferenceCount() );
	Msg( "   Level    : %0.2f\n", pTechnology->GetResourceLevel() );
	*/
}

//-----------------------------------------------------------------------------
// Purpose: Check to see if any technology has changed, and resend it to players if it has
//-----------------------------------------------------------------------------
void CTFTeam::UpdateTechnologyData( void )
{
	for ( int i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ )
	{
		// Update all technologies
		CBaseTechnology *pTechnology = m_pTechnologyTree->GetTechnology(i);
		if ( pTechnology && pTechnology->IsDirty() )
		{
			// Send it to all our clients
			for ( int iPlayer = 0; iPlayer < m_aPlayers.Count(); iPlayer++ )
			{
				CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)m_aPlayers[iPlayer];
				UpdateClientTechnology( i, pPlayer );
			}

			pTechnology->SetDirty( false );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTFTeam::ShouldTransmitToPlayer( CBasePlayer* pRecipient, CBaseEntity* pEntity )
{
	return IsEntityVisibleToTactical( pEntity );
}

//-----------------------------------------------------------------------------
// Purpose: Is the specified entity visible on this team's tactical view?
//-----------------------------------------------------------------------------
bool CTFTeam::IsEntityVisibleToTactical( CBaseEntity *pEntity )
{
	return ::IsEntityVisibleToTactical( GetTeamNumber(), GetNumPlayers(), 
		GetNumObjects(), pEntity->entindex(), (char*)STRING(pEntity->m_iClassname), 
		pEntity->GetTeamNumber(), pEntity->GetAbsOrigin() );
}

//-----------------------------------------------------------------------------
// RESOURCES
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: Add a resource zone to the list of zones to collect from
//-----------------------------------------------------------------------------
void CTFTeam::AddResourceZone( CResourceZone *pResource )
{
	TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::AddResourceZone adding res zone %p to team %s\n", gpGlobals->curtime, 
		pResource, GetName() ) );

	// If this resource is already owned by another team, remove it from them
	CTFTeam *pOwners = pResource->GetOwningTeam();
	if ( pOwners )
	{
		pOwners->RemoveResourceZone( pResource );
	}

	pResource->SetOwningTeam( GetTeamNumber() );

	m_aResourcesBeingCollected.AddToTail( pResource );
	m_bHaveZone = true;

	// Tell all the team's members
	for ( int i = 0; i < m_aPlayers.Count(); i++ )
	{
		CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)m_aPlayers[i];
		CSingleUserRecipientFilter filter( pPlayer );
		filter.MakeReliable();
		CBaseEntity::EmitSound( filter, pPlayer->entindex(), "TFTeam.CapturedZone" );
	}

	// Recalculate team's orders
	RecalcOrders();
}

//-----------------------------------------------------------------------------
// Purpose: Remove a resource zone from the list of zones being collected from
//-----------------------------------------------------------------------------
void CTFTeam::RemoveResourceZone( CResourceZone *pResource )
{
	TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::RemoveResourceZone removing res zone %p from team %s\n", gpGlobals->curtime, 
		pResource, GetName() ) );

	// Now remove the zone from our list
	m_aResourcesBeingCollected.FindAndRemove( pResource );

	// Still have a zone if there are other zones in the list
	m_bHaveZone = ( m_aResourcesBeingCollected.Count() > 0 );

	// Tell all the team's members
	for ( int i = 0; i < m_aPlayers.Count(); i++ )
	{
		CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)m_aPlayers[i];
		CSingleUserRecipientFilter filter( pPlayer );
		filter.MakeReliable();
		CBaseEntity::EmitSound( filter, pPlayer->entindex(),"TFTeam.LostZone" );
	}

	// Recalculate team's orders
	RecalcOrders();
}

//-----------------------------------------------------------------------------
// Purpose: Recalculate the potential resources
//-----------------------------------------------------------------------------
void CTFTeam::UpdatePotentialResources( void )
{
	// Set potential to current amount
	m_fPotentialResources = GetTeamResources();

	// This used to be used for collectors.
	// It could be updated to count all incoming resources in en-route resource boxes.
}

//-----------------------------------------------------------------------------
// Purpose: Return the amount of resources a player should get when joining this team
//-----------------------------------------------------------------------------
float CTFTeam::GetJoiningPlayerResources( void )
{
	// If we had our banks set recently, use that amount
	if ( gpGlobals->curtime < (m_flLastBankSetTime + 30.0) )
		return m_flLastBankSetAmount;

	if ( !GetNumPlayers() )
		return 0;

	// Otherwise, take the average of all the players on the team
	RecomputeTeamResources();
	return ( GetTeamResources() / GetNumPlayers() );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFTeam::SetRecentBankSet( float flResources )
{
	m_flLastBankSetAmount = flResources;
	m_flLastBankSetTime = gpGlobals->curtime;
}

//------------------------------------------------------------------------------------------------------------------
// TECHNOLOGY TREE
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFTeam::InitializeTechTree( void )
{
	m_pTechnologyTree = new CTechnologyTree( filesystem, GetTeamNumber() );

	// Now iterate through added techs and automatically make level 0 techs available
	for (int i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ )
	{
		CBaseTechnology *tech = m_pTechnologyTree->GetTechnology(i);
		if ( !tech )
			continue;

		if ( tech->GetLevel() == 0 )
		{
			EnableTechnology( tech );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CTechnologyTree *CTFTeam::GetTechnologyTree( void )
{
	return m_pTechnologyTree;
}

//-----------------------------------------------------------------------------
// Purpose: A new technology has been attained by this team. Give it to every player.
//-----------------------------------------------------------------------------
void CTFTeam::EnableTechnology( CBaseTechnology *technology, bool bStolen )
{
	CTeamFortress *rules = TFGameRules();
	if ( rules )
	{
		// Disable autoswitching if we are getting a weapon
		rules->SetAllowWeaponSwitch( false );
	}

	// Set the technology's resources to the costs
	// Needed because technologies can be enabled through other means than resource spending
	technology->ForceComplete();

	// Apply technology to team first.
	technology->AddTechnologyToTeam( this );

	// Iterate though players
	for (int i = 0; i < m_aPlayers.Count(); i++ )
	{
		CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)m_aPlayers[i];
		technology->AddTechnologyToPlayer( pPlayer );

		CSingleUserRecipientFilter filter( pPlayer );
		filter.MakeReliable();

		// Play the sound
		if (bStolen)
		{
			CBaseEntity::EmitSound( filter, pPlayer->entindex(), "TFTeam.ObtainStolenTechnology" );
		}
		else
		{
			if ( technology->GetSoundFile(0) && technology->GetSoundFile(0)[0] )
			{
				CBaseEntity::EmitSound( filter, pPlayer->entindex(), CHAN_STATIC,
					technology->GetSoundFile(0), VOL_NORM, ATTN_NONE );
			}
		}

		// Remove all the player's votes on this technology
		CBaseTechnology *pPreferredTech = m_pTechnologyTree->GetTechnology( pPlayer->GetPreferredTechnology() );
		if ( pPreferredTech && pPreferredTech == technology )
		{
			// Tell the player his preferred tech has been bought
			if (!bStolen)
			{
				CBaseEntity::EmitSound( filter, pPlayer->entindex(), "TFTeam.BoughtPreferredTechnology" );
			}

			pPlayer->SetPreferredTechnology( m_pTechnologyTree, -1 );
		}
	}

	// Let the team see if it wants to do anything with this specific technology
	GainedNewTechnology( technology );

	// Reenable autoswitching
	if ( rules )
	{
		rules->SetAllowWeaponSwitch( true );
	}
}

//-----------------------------------------------------------------------------
// Purpose: For debugging..
//-----------------------------------------------------------------------------
void CTFTeam::EnableAllTechnologies()
{
	for ( int i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ )
	{
		CBaseTechnology *technology = m_pTechnologyTree->GetTechnology(i);
		if ( !technology || technology->IsHidden() )
			continue;

		EnableTechnology( technology );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Called any time a player votes/changes preferences on the client
//-----------------------------------------------------------------------------
void CTFTeam::RecomputePreferences( void )
{
	// Zero total counters
	m_pTechnologyTree->ClearPreferenceCount();

	// Zero out all preferences and iterate through active players
	int i;
	for ( i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ )
	{
		CBaseTechnology *technology = m_pTechnologyTree->GetTechnology(i);
		if ( technology )
		{
			// Zero internal counters
			technology->ZeroPreferences();
		}
	}

	// Now loop through players and see what's preferred
	for ( i = 0; i < m_aPlayers.Count(); i++ )
	{
		CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)m_aPlayers[i];
		if ( !pPlayer )
			continue;
	
		int preferred =  pPlayer->GetPreferredTechnology();
		// No preference set, don't worry about this player
		if ( preferred == -1 )
			continue;

		if ( preferred < 0 || preferred >= MAX_TECHNOLOGIES )
		{
			Msg( "Player %s tried to set preference to out of range tech %i\n", preferred );
			continue;
		}

		// Reference technology
		CBaseTechnology *technology = m_pTechnologyTree->GetTechnology(preferred);
		Assert( technology );
		if ( !technology )
			continue;

		// Msg( "player %s prefers %s\n", STRING( pPlayer->pl->netname ), technology->GetPrintName() );

		// Add one vote
		technology->IncrementPreferences();
		// Add one vote to totals
		m_pTechnologyTree->IncrementPreferences();
	}

	// Any time preferences are changed/set, see if we should make any purchases immediately.
	RecomputePurchases();
}

//-----------------------------------------------------------------------------
// Purpose: Figure our how many resources we've got in the team
//-----------------------------------------------------------------------------
void CTFTeam::RecomputeTeamResources( void )
{
	// Recalculate the total amount of resources the team has
	m_fResources = 0.0f;
	for ( int i = 0; i < GetNumPlayers(); i++ )
	{
		m_fResources += ((CBaseTFPlayer*)GetPlayer(i))->GetBankResources();
	}

	UpdatePotentialResources();
}	

//-----------------------------------------------------------------------------
// Purpose: Attempt to spend resources according to player's preferences
//-----------------------------------------------------------------------------
void CTFTeam::RecomputePurchases( void )
{
	RecomputeTeamResources();

	// Cycle through all players, and spend their resources on the technologies they're voting for
	for ( int iPlayer = 0; iPlayer < m_aPlayers.Count(); iPlayer++ )
	{
		CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)GetPlayer( iPlayer );

		// See if he has any resources to spend on a tech
		if ( pPlayer->GetBankResources() <= 0 )
			continue;

		// Has he got a preffered tech?
		if ( pPlayer->GetPreferredTechnology() != -1 )
		{
			// Get the player's voted-for technology
			CBaseTechnology *pPreferredTech = m_pTechnologyTree->GetTechnology( pPlayer->GetPreferredTechnology() );
			if ( pPreferredTech && pPreferredTech->GetAvailable() == false )
			{
				if ( !pPreferredTech->GetResourceCost() )
					continue;

				// Try to spend resources on the tech
				int iResourcesSpent = min( pPlayer->GetBankResources(), pPreferredTech->GetResourceCost() - pPreferredTech->GetResourceLevel() );
				if ( pPreferredTech->IncreaseResourceLevel( iResourcesSpent ) )
				{
					// The technology's had enough resources spent to buy it, so enable it
					EnableTechnology( pPreferredTech );
					Msg( "%s bought %s\n",	GetName(), pPreferredTech->GetPrintName() );
				}

				// Reduce the player's bank
				if ( iResourcesSpent )
				{
					pPlayer->RemoveBankResources( iResourcesSpent );
				}
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Return true if the team owns the specified technology
//-----------------------------------------------------------------------------
bool CTFTeam::HasNamedTechnology( const char *name )
{
	// Look it up
	// FIXME:  This could be too slow, consider using #define'd/indexed names?
	CBaseTechnology *tech = m_pTechnologyTree->GetTechnology( name ); 
	if ( !tech )
		return false;
	if ( !tech->GetAvailable() )
		return false;

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: A new technology has been received by the team. Do anything specific to this technology here.
//-----------------------------------------------------------------------------
void CTFTeam::GainedNewTechnology( CBaseTechnology *pTechnology )
{
}


//-----------------------------------------------------------------------------
// Purpose: Called by the team's Think function
//-----------------------------------------------------------------------------
void CTFTeam::UpdateTechnologies( void )
{
	// Update clients
	UpdateTechnologyData();
}


//------------------------------------------------------------------------------------------------------------------
// PLAYERS
//-----------------------------------------------------------------------------
// Purpose: Add the specified player to this team. Remove them from their current team, if any.
//-----------------------------------------------------------------------------
void CTFTeam::AddPlayer( CBasePlayer *pPlayer )
{
	BaseClass::AddPlayer( pPlayer );
	
	// Give the player this team's technology
	for ( int i = 0; i < m_pTechnologyTree->GetNumberTechnologies(); i++ )
	{
		CBaseTechnology *technology = m_pTechnologyTree->GetTechnology(i);
		if ( !technology )
			continue;

		if ( technology->IsHidden() )
			continue;

		// Not yet available to team, skip
		if ( !technology->GetAvailable() )
			continue;

		// Add it.
		technology->AddTechnologyToPlayer( (CBaseTFPlayer*)pPlayer );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Clean up the player's objects when they leave
//-----------------------------------------------------------------------------
void CTFTeam::RemovePlayer( CBasePlayer *pPlayer )
{
	BaseClass::RemovePlayer( pPlayer );

	// Destroy all objects belonging to this player
	if ( tf_destroyobjects.GetFloat() )
	{
		// Work backwards through the list because objects remove themselves
		int iSize = m_aObjects.Count();
		for (int i = iSize-1; i >= 0; i--)
		{
			if ( (m_aObjects[i]->GetBuilder() == pPlayer) && (m_aObjects[i]->ShouldAutoRemove()) )
			{
				UTIL_Remove( m_aObjects[i] );
			}
		}
	}

	RecomputePreferences();
}

//-----------------------------------------------------------------------------
// Purpose: Return the number of team members of the specified class
//-----------------------------------------------------------------------------
int	CTFTeam::GetNumOfClass( TFClass iClass )
{
	int iNumber = 0;
	for ( int i = 0; i < GetNumPlayers(); i++ )
	{
		if ( ((CBaseTFPlayer*)GetPlayer(i))->IsClass(iClass) )
		{
			iNumber++;
		}
	}
	return iNumber;
}

//------------------------------------------------------------------------------------------------------------------
// RESOURCE BANK
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFTeam::InitializeTeamResources( void )
{
	m_fResources = 0.0f;
	m_fPotentialResources = 0.0f;
	m_bHaveZone = false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float CTFTeam::GetTeamResources( void )
{
	return m_fResources;
}

//-----------------------------------------------------------------------------
// Purpose: Add resources to this team
//-----------------------------------------------------------------------------
int CTFTeam::AddTeamResources( float fAmount, int nStat )
{
	fAmount = clamp(fAmount, 0, 9999.f);

	m_flTotalResourcesSoFar += fAmount;

	TFStats()->IncrementTeamStat( GetTeamNumber(), TF_TEAM_STAT_RESOURCES_COLLECTED, fAmount );

	// Divvy the resources out to the players
	int iAmountPerPlayer = Ceil2Int( fAmount / GetNumPlayers() );	// Yes, this does create some resources in the roundoff.
	for ( int i = 0; i < GetNumPlayers(); i++ )
	{
		CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)GetPlayer(i);
		pPlayer->AddBankResources( iAmountPerPlayer );
		TFStats()->IncrementPlayerStat( pPlayer, TF_PLAYER_STAT_RESOURCES_ACQUIRED, fAmount );

		if (nStat >= 0)
		{
			TFStats()->IncrementPlayerStat( pPlayer, (TFPlayerStatId_t)nStat, fAmount );
		}
	}

	ResourceLoadDeposited();

	return iAmountPerPlayer;
}

//-----------------------------------------------------------------------------
// Purpose: Give resources to the player
//-----------------------------------------------------------------------------
void CTFTeam::DonateResources( CBaseTFPlayer *pPlayer )
{
	int nPlayerCount = GetNumPlayers();
	if (nPlayerCount <= 1)
		return;

	int pResourceCount;
	int pResourcePerPlayer;
	int pDonationCount;

	bool bDonating = false;
	pResourceCount = pPlayer->GetBankResources();

	// Figure out how many resources per player to donate
	pResourcePerPlayer = pResourceCount / (nPlayerCount - 1); 
	if (pResourceCount % (nPlayerCount - 1) != 0)
		++pResourcePerPlayer;

	// Clamp to max amt per teammate for each hit...
	if (pResourcePerPlayer > RESOURCE_DONATION_AMT_PER_PLAYER)
		pResourcePerPlayer = RESOURCE_DONATION_AMT_PER_PLAYER;

	// Figure out if we are donating anything at all
	if (pResourceCount > 0)
		bDonating = true;
	if (!bDonating)
		return;

	// Now that we've figured how much to donate, do it!
	for ( int i = 0; i < nPlayerCount; i++ )
	{
		CBaseTFPlayer *pDest = (CBaseTFPlayer*)GetPlayer(i);
		if (pDest == pPlayer)
			continue;

		// The last guy(s) gets the scraps... too bad.
		int nCountToDonate = pResourceCount;
		if (nCountToDonate > pResourcePerPlayer)
			nCountToDonate = pResourcePerPlayer;
		pResourceCount -= nCountToDonate;
		pDonationCount = nCountToDonate;

		pPlayer->DonateResources( pDest, pDonationCount );
	}
}


//-----------------------------------------------------------------------------
// Purpose: New resources have just been dumped in the bank
//-----------------------------------------------------------------------------
void CTFTeam::ResourceLoadDeposited( void )
{
	// HACK TEST CODE
	// Remove after Resource Experiment!
	static int iIncrements = 250;
	if ( m_flTotalResourcesSoFar >= (m_iLastUpdateSentAt + iIncrements) )
	{
		while ( m_flTotalResourcesSoFar >= (m_iLastUpdateSentAt + iIncrements) )
		{
			m_iLastUpdateSentAt += iIncrements;
		}

		// Send a message to players
		CRecipientFilter filter;
		filter.SetEntityMessage( entindex(), "ResourceLoadDeposited" );

		MessageBegin( filter, 0 );
			WRITE_LONG( m_iLastUpdateSentAt );
		MessageEnd();
	}

	// Now see if we should buy anything
	RecomputePurchases();
}

//------------------------------------------------------------------------------------------------------------------
// RESUPPLY BEACONS
//-----------------------------------------------------------------------------
// Purpose: Add the specified resupply beacon to this team. 
//-----------------------------------------------------------------------------
void CTFTeam::AddResupply( CObjectResupply *pResupply )
{
	TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::AddResupply adding resupply %p to team %s\n", gpGlobals->curtime, 
		pResupply, GetName() ) );

	m_aResupplyBeacons.AddToTail( pResupply );
}

//-----------------------------------------------------------------------------
// Purpose: Remove this resupply beacon from the team
//-----------------------------------------------------------------------------
void CTFTeam::RemoveResupply( CObjectResupply *pResupply )
{
	TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::RemoveResupply remove resupply %p from team %s\n", gpGlobals->curtime, 
		pResupply, GetName() ) );

	// Now remove the beacon from our list
	m_aResupplyBeacons.FindAndRemove( pResupply );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CTFTeam::GetNumObjects( int iObjectType )
{
	// Asking for a count of a specific object type?
	if ( iObjectType > 0 )
	{
		int iCount = 0;
		for ( int i = 0; i < GetNumObjects(); i++ )
		{
			CBaseObject *pObject = GetObject(i);
			if ( pObject && pObject->GetType() == iObjectType )
			{
				iCount++;
			}
		}
		return iCount;
	}

	return m_aObjects.Count();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CBaseObject *CTFTeam::GetObject( int num )
{
 	Assert( num >= 0 && num < m_aObjects.Count() );
	return m_aObjects[ num ];
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CTFTeam::GetNumResupplies( void )
{
	return m_aResupplyBeacons.Count();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CObjectResupply *CTFTeam::GetResupply( int num )
{
	Assert( num >= 0 && num < m_aResupplyBeacons.Count() );
	return m_aResupplyBeacons[ num ];
}


bool CTFTeam::IsCoveredBySentryGun( const Vector &vPos )
{
	for( int i=0; i < m_aObjects.Count(); i++ )
	{
		CBaseObject *pObj = m_aObjects[i];
		
		if ( pObj->IsSentrygun() && vPos.DistTo( pObj->GetAbsOrigin() ) < OBJECT_COVERED_DIST )
			return true;

	}

	return false;
}


int CTFTeam::GetNumShieldWallsCoveringPosition( const Vector &vPos )
{
	int count = 0;

	for ( int i=0; i < m_aObjects.Count(); i++ )
	{
		CBaseObject *pObj = m_aObjects[i];
		
		if ( pObj->GetType() == OBJ_SHIELDWALL )
		{
			if ( vPos.DistTo( pObj->GetAbsOrigin() ) < OBJECT_COVERED_DIST )
				++count;
		}
	}

	return count;
}


int CTFTeam::GetNumResuppliesCoveringPosition( const Vector &vPos )
{
	int count = 0;

	for ( int i=0; i < m_aResupplyBeacons.Count(); i++ )
	{
		CBaseObject *pObj = m_aResupplyBeacons[i];
		if ( vPos.DistTo( pObj->GetAbsOrigin() ) < RESUPPLY_COVER_DIST )
			++count;
	}

	return count;
}


int CTFTeam::GetNumRespawnStationsCoveringPosition( const Vector &vPos )
{
	int count = 0;

	for ( int i=0; i < m_aObjects.Count(); i++ )
	{
		CBaseObject *pObj = m_aObjects[i];

		if ( pObj->GetType() == OBJ_RESPAWN_STATION )
		{
			if ( vPos.DistTo( pObj->GetAbsOrigin() ) < OBJECT_COVERED_DIST )
			{
				++count;
			}
		}
	}

	return count;
}


//------------------------------------------------------------------------------------------------------------------
// OBJECTS
//-----------------------------------------------------------------------------
// Purpose: Add the specified object to this team.
//-----------------------------------------------------------------------------
void CTFTeam::AddObject( CBaseObject *pObject )
{
	TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::AddObject adding object %p:%s to team %s\n", gpGlobals->curtime, 
		pObject, pObject->GetClassname(), GetName() ) );

	bool alreadyInList = IsObjectOnTeam( pObject );
	Assert( !alreadyInList );
	if ( !alreadyInList )
	{
		m_aObjects.AddToTail( pObject );
	}
}

//-----------------------------------------------------------------------------
// Returns true if the object is in the team's list of objects
//-----------------------------------------------------------------------------
bool CTFTeam::IsObjectOnTeam( CBaseObject *pObject ) const
{
	return ( m_aObjects.Find( pObject ) != -1 );
}


//-----------------------------------------------------------------------------
// Purpose: Remove this object from the team
//  Removes all references from all sublists as well
//-----------------------------------------------------------------------------
void CTFTeam::RemoveObject( CBaseObject *pObject )
{									   
	if ( m_aObjects.Count() <= 0 )
		return;

	if ( m_aObjects.Find( pObject ) != -1 )
	{
		TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::RemoveObject removing %p:%s from %s\n", gpGlobals->curtime, 
			pObject, pObject->GetClassname(), GetName() ) );

		m_aObjects.FindAndRemove( pObject );
	}
	else
	{
		TRACE_OBJECT( UTIL_VarArgs( "%0.2f CTFTeam::RemoveObject couldn't remove %p:%s from %s\n", gpGlobals->curtime, 
			pObject, pObject->GetClassname(), GetName() ) );
	}
}


//------------------------------------------------------------------------------------------------------------------
// ORDERS
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFTeam::InitializeOrders( void )
{
	m_flPersonalOrderUpdateTime = 0;
}

//-----------------------------------------------------------------------------
// Purpose: Add a new order to our list. If it already exists, bump it's priority to the new priority.
//-----------------------------------------------------------------------------
COrder* CTFTeam::AddOrder( 
	int iOrderType, 
	CBaseEntity *pTarget, 
	CBaseTFPlayer *pPlayer, 
	float flDistanceToRemove,
	float flLifetime,
	COrder *pNewOrder
	)
{
	// Remove any orders to the player.
	RemoveOrdersToPlayer( pPlayer );

	// The new system requires order class to be passed in.
	Assert( pNewOrder );

	// All the order create functions should just use new to create the order class,
	// then we'll attach the edict in here. There's no reason to use LINK_ENTITY_TO_CLASS
	// and CreateEntityByName.
	Assert( !pNewOrder->pev );
	pNewOrder->AttachEdict();
	
	pNewOrder->ChangeTeam( GetTeamNumber() );
	OrderHandle hOrder;
	hOrder = pNewOrder;
	m_aOrders.AddToTail( hOrder );

	// Update target
	pNewOrder->SetTarget( pTarget );
	pNewOrder->SetDistance( flDistanceToRemove );

	// Update lifetime.
	pNewOrder->SetLifetime( flLifetime );

	Assert( pPlayer->GetOrder() == NULL );

	pNewOrder->SetOwner( pPlayer );
	pPlayer->SetOrder( pNewOrder );

	// "New Order Received!"
	CSingleUserRecipientFilter filter( pPlayer );
	filter.MakeReliable();
	CBaseEntity::EmitSound( filter, pPlayer->entindex(),"TFTeam.AddOrder" );

	// Debug check.. it should never create an order with its termination conditions
	// already met.	
	Assert( !pNewOrder->Update() );

	return pNewOrder;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFTeam::RemoveOrder( COrder *pOrder )
{
	OrderHandle hOrder;
	hOrder = pOrder;

	m_aOrders.FindAndRemove( hOrder );
}

//-----------------------------------------------------------------------------
// Purpose: Recalculate the team's orders & their priorities
//-----------------------------------------------------------------------------
void CTFTeam::RecalcOrders( void )
{
	// Update all existing orders
	UpdateOrders();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFTeam::UpdateOrders( void )
{
	// Tell all our current orders to update themselves. Walk backwards because we may remove them.
	int iSize = m_aOrders.Count();
	for (int i = iSize-1; i >= 0; i--)
	{
		// Orders without owners should be removed
		bool bShouldRemove = ( 
			!m_aOrders[i] || 
			!m_aOrders[i]->GetOwner() || 
			m_aOrders[i]->Update() );
		if ( bShouldRemove )
		{
			COrder *pOrder = m_aOrders[i];
			m_aOrders.Remove( i );
			UTIL_Remove( pOrder );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: An event has just occurred that affects orders. Tell all our orders that
//			have the specified entity as a target.
//-----------------------------------------------------------------------------
void CTFTeam::UpdateOrdersOnEvent( COrderEvent_Base *pOrder )
{
	// Tell all our current orders to update themselves. Walk backwards because we may remove them.
	int iSize = m_aOrders.Count();
	for (int i = iSize-1; i >= 0; i--)
	{
		bool bShouldRemove = m_aOrders[i]->UpdateOnEvent( pOrder );
		if ( bShouldRemove )
		{
			COrder *pOrder = m_aOrders[i];
			m_aOrders.Remove( i );
			UTIL_Remove( pOrder );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Create personal orders for all the team's members
//-----------------------------------------------------------------------------
void CTFTeam::CreatePersonalOrders( void )
{
	// Create personal orders for each player
	for ( int i = 0; i < m_aPlayers.Count(); i++ )
	{
		CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)m_aPlayers[i];

		// Don't create orders for bots, undefined or dead people
		if ( !(pPlayer->GetFlags() & FL_FAKECLIENT) && pPlayer->IsAlive() && !pPlayer->IsClass( TFCLASS_UNDECIDED ) )
		{
			CreatePersonalOrder( pPlayer );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Create personal orders for specified player
//-----------------------------------------------------------------------------
void CTFTeam::CreatePersonalOrder( CBaseTFPlayer *pPlayer )
{
	// We still haven't made a personal order, so ask the class if it wants to
	if ( pPlayer->GetPlayerClass() )
	{
		pPlayer->GetPlayerClass()->CreatePersonalOrder();
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFTeam::RemoveOrdersToPlayer( CBaseTFPlayer *pPlayer )
{
	// Walk backwards because we're removing them.
	int iSize = m_aOrders.Count();
	for (int i = iSize-1; i >= 0; i--)
	{
		// Orders without owners should be removed
		if ( m_aOrders[i].Get() )
		{
			if( m_aOrders[i]->GetOwner() == pPlayer )
			{
				COrder *pOrder = m_aOrders[i];
				m_aOrders.Remove( i );
				
				pOrder->DetachFromPlayer();
				UTIL_Remove( pOrder );
			}
		}
		else
		{
			m_aOrders.Remove( i );
		}
	}

	pPlayer->SetOrder( NULL );
}


//-----------------------------------------------------------------------------
// Purpose: Count and return the number of orders of the type with the specified target
//-----------------------------------------------------------------------------
int CTFTeam::CountOrders( int flags, int iOrderType, CBaseEntity *pTarget, CBaseTFPlayer *pOwner )
{
	int iOrderCount = 0;

	// Count the number of global orders
	for ( int i = 0; i < m_aOrders.Count(); i++ )
	{
		COrder *pOrder = m_aOrders[i];

		if( flags & COUNTORDERS_TYPE )
			 if( pOrder->GetType() != iOrderType )
				continue;

		if( flags & COUNTORDERS_TARGET )
			if( pOrder->GetTargetEntity() != pTarget )
				continue;

		if( flags & COUNTORDERS_OWNER )
			if( pOrder->GetOwner() != pOwner )
				continue;
		
		// Ok, this order matches the criteria.
		iOrderCount++;
	}

	return iOrderCount;
}


int CTFTeam::CountOrdersOwnedByPlayer( CBaseTFPlayer *pPlayer )
{
	return CountOrders( COUNTORDERS_OWNER, 0, 0, pPlayer );
}


//------------------------------------------------------------------------------------------------------------------
// MESSAGES
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFTeam::ClearMessages( void )
{
	int iSize = m_aMessages.Count();
	for (int i = iSize-1; i >= 0; i--)
	{
		CTeamMessage *pMessage = m_aMessages[i];
		m_aMessages.Remove( i );
		delete pMessage;
	}

	m_aMessages.Purge();
}

//-----------------------------------------------------------------------------
// Purpose: Post a message of the specified type
//-----------------------------------------------------------------------------
void CTFTeam::PostMessage( int iMessageID, CBaseEntity *pEntity, char *sData )
{
	// First see if we've got this message in the queue already
	for ( int i = 0; i < m_aMessages.Count(); i++ )
	{
		CTeamMessage *pMessage = m_aMessages[i];
		if ( (pMessage->GetID() == iMessageID) && (pMessage->GetEntity() == pEntity) )
		{
			// Already in the queue, abort.
			return;
		}
	}

	// Create a new message and add it to my tail
	CTeamMessage *pMessage = CTeamMessage::Create( this, iMessageID, pEntity );
	if ( sData && sData[0] )
	{
		pMessage->SetData( sData );
	}
	m_aMessages.AddToTail( pMessage );

	// Tell the message to fire
	pMessage->FireMessage();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFTeam::UpdateMessages( void )
{
	// Go through my messages and kill any that have reached their TTL
	int iSize = m_aMessages.Count();
	for (int i = iSize-1; i >= 0; i--)
	{
		CTeamMessage *pMessage = m_aMessages[i];
		if ( gpGlobals->curtime > pMessage->GetTTL() )
		{
			m_aMessages.Remove( i );
			delete pMessage;
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Tell all our powerpacks to update their powered objects.
//			pPackToIgnore: Pack to ignore, because it's dying.
//			pObjectToTarget: An object looking for power, because it's being placed
//-----------------------------------------------------------------------------
void CTFTeam::UpdatePowerpacks( CObjectPowerPack *pPackToIgnore, CBaseObject *pObjectToTarget )
{
	for ( int i = 0; i < GetNumObjects(); i++ )
	{
		CBaseObject *pObject = GetObject(i);
		assert(pObject);
		if ( pObject == pPackToIgnore || pObject->GetType() != OBJ_POWERPACK )
			continue;

		((CObjectPowerPack*)pObject)->PowerNearbyObjects( pObjectToTarget );

		// Quit as soon as we've powered the specified one, if there is one
		if ( pObjectToTarget && pObjectToTarget->IsPowered() )
			break;
	}
}


//-----------------------------------------------------------------------------
// Purpose: Tell all our buff stations to update and look for objects.
//   Input:	pBuffStationToIgnore: Buff station to ignore, because it is dying
//          pObjectToTarget: an object looking for a buff station, because it is being placed
//-----------------------------------------------------------------------------
void CTFTeam::UpdateBuffStations( CObjectBuffStation *pBuffStationToIgnore, CBaseObject *pObjectToTarget, bool bPlacing )
{
	for ( int iObject = 0; iObject < GetNumObjects(); ++iObject )
	{
		CBaseObject *pObject = GetObject( iObject );
		assert( pObject );
	
		if ( pObject->GetType() != OBJ_BUFF_STATION )
			continue;

		CObjectBuffStation *pBuffStation = static_cast<CObjectBuffStation*>( pObject );
		if ( pBuffStation == pBuffStationToIgnore )
			continue;

		pBuffStation->BuffNearbyObjects( pObjectToTarget, bPlacing );

		// Quit as soon as we've powered the specified one, if there is one.
		if ( pObjectToTarget && pObjectToTarget->IsHookedAndBuffed() )
			break;
	}
}

//------------------------------------------------------------------------------------------------------------------
// UTILITY FUNCS
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CTFTeam* CTFTeam::GetEnemyTeam()
{
	// Look for nearby enemy objects we can capture.
	int iMyTeam = GetTeamNumber();
	if( iMyTeam == 0 )
		return NULL;

	int iEnemyTeam = !(iMyTeam - 1) + 1;
	return (CTFTeam*)GetGlobalTeam( iEnemyTeam );
}


