//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Medic's portable power generator
//
//=============================================================================//

#include "cbase.h"
#include "tf_obj_buff_station.h"
#include "tf_player.h"
#include "rope.h"
#include "rope_shared.h"
#include "entitylist.h"
#include "VGuiScreen.h"
#include "engine/IEngineSound.h"
#include "tf_team.h"

//=============================================================================
//
// Console Variables
//
static ConVar obj_buff_station_damage_modifier( "obj_buff_station_damage_modifier", "1.5", 0, "Scales the damage a player does while connected to the buff station." );
static ConVar obj_buff_station_heal_rate( "obj_buff_station_heal_rate", "10" );
static ConVar obj_buff_station_range( "obj_buff_station_range", "300" );
static ConVar obj_buff_station_obj_range( "obj_buff_station_obj_range", "800" );
static ConVar obj_buff_station_health( "obj_buff_station_health","100", FCVAR_NONE, "Buff Station health" );

//-----------------------------------------------------------------------------
// Buff Station defines
//-----------------------------------------------------------------------------
#define BUFF_STATION_MINS							Vector( -30, -30, 0  )
#define BUFF_STATION_MAXS							Vector(  30,  30, 50 )

#define BUFF_STATION_HUMAN_MODEL					"models/objects/human_obj_buffstation.mdl"
#define BUFF_STATION_HUMAN_ASSEMBLING_MODEL			"models/objects/human_obj_buffstation_build.mdl"
#define BUFF_STATION_ALIEN_MODEL					"models/objects/alien_obj_buffstation.mdl"
#define BUFF_STATION_ALIEN_ASSEMBLING_MODEL			"models/objects/alien_obj_buffstation_build.mdl"

#define BUFF_STATION_VGUI_SCREEN					"screen_obj_buffstation"

#define BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT		"BoostPlayerThink"
#define BUFF_STATION_BOOST_OBJECT_THINK_CONTEXT		"BoostObjectThink"
#define BUFF_STATION_BOOST_PLAYER_THINK_INTERVAL	0.1f
#define BUFF_STATION_BOOST_OBJECT_THINK_INTERVAL	2.0f

#define BUFF_STATION_BUFF_RANGE						( 600 * 600 )

//=============================================================================
//
// Data Description
//
BEGIN_DATADESC( CObjectBuffStation )
	DEFINE_INPUTFUNC( FIELD_VOID, "PlayerSpawned", InputPlayerSpawned ),
	DEFINE_INPUTFUNC( FIELD_VOID, "PlayerAttachedToGenerator", InputPlayerAttachedToGenerator ),
	DEFINE_INPUTFUNC( FIELD_VOID, "PlayerEnteredVehicle", InputPlayerSpawned ),	// NJS: Detach player from buff pack.
END_DATADESC()

//=============================================================================
//
// Server Class
//
IMPLEMENT_SERVERCLASS_ST( CObjectBuffStation, DT_ObjectBuffStation )
	SendPropInt( SENDINFO( m_nPlayerCount ), BUFF_STATION_MAX_PLAYER_BITS, SPROP_UNSIGNED ),
	SendPropArray( SendPropEHandle( SENDINFO_ARRAY( m_hPlayers ) ), m_hPlayers ),
	SendPropInt( SENDINFO( m_nObjectCount ), BUFF_STATION_MAX_OBJECT_BITS, SPROP_UNSIGNED ),
	SendPropArray( SendPropEHandle( SENDINFO_ARRAY( m_hObjects ) ), m_hObjects ),
END_SEND_TABLE()

//=============================================================================
//
// Linking and Precache
//
LINK_ENTITY_TO_CLASS( obj_buff_station, CObjectBuffStation );
PRECACHE_REGISTER( obj_buff_station );

// Backwards compatability...
LINK_ENTITY_TO_CLASS( obj_portable_power_generator, CObjectBuffStation );

//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CObjectBuffStation::CObjectBuffStation()
{
	// Verify networking data.
	COMPILE_TIME_ASSERT( BUFF_STATION_MAX_PLAYERS < ( 1 << BUFF_STATION_MAX_PLAYER_BITS ) );
	COMPILE_TIME_ASSERT( BUFF_STATION_MAX_PLAYERS >= ( 1 << ( BUFF_STATION_MAX_PLAYER_BITS - 1 ) ) );

	COMPILE_TIME_ASSERT( BUFF_STATION_MAX_OBJECTS < ( 1 << BUFF_STATION_MAX_OBJECT_BITS ) );
	COMPILE_TIME_ASSERT( BUFF_STATION_MAX_OBJECTS >= ( 1 << ( BUFF_STATION_MAX_OBJECT_BITS - 1 ) ) );

	// Uses the client-side animation system.
	UseClientSideAnimation();
}

//-----------------------------------------------------------------------------
// Purpose: Spawn
//-----------------------------------------------------------------------------
void CObjectBuffStation::Spawn()
{
	// This must be set before calling the base class spawn.
	m_iHealth = obj_buff_station_health.GetInt();

	BaseClass::Spawn();

	SetModel( BUFF_STATION_HUMAN_MODEL );
	SetSolid( SOLID_BBOX );

	SetType( OBJ_BUFF_STATION );
	UTIL_SetSize( this, BUFF_STATION_MINS, BUFF_STATION_MAXS );

	m_takedamage = DAMAGE_YES;

	// Initialize buff station attachment data.
	InitAttachmentData();

	// Thinking
	SetContextThink( BoostPlayerThink, 1.0f, BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT );
	SetContextThink( BoostObjectThink, 2.0f, BUFF_STATION_BOOST_OBJECT_THINK_CONTEXT );

	m_bBuilding = false;
}

//-----------------------------------------------------------------------------
// Purpose: Precache model, vgui elements, and sound.
//-----------------------------------------------------------------------------
void CObjectBuffStation::Precache()
{
	// Models
	PrecacheModel( BUFF_STATION_HUMAN_MODEL );
	PrecacheModel( BUFF_STATION_ALIEN_MODEL );
	
	// Build models
	PrecacheModel( BUFF_STATION_HUMAN_ASSEMBLING_MODEL );
	PrecacheModel( BUFF_STATION_ALIEN_ASSEMBLING_MODEL );
	
	// VGUI Screen
	PrecacheVGuiScreen( BUFF_STATION_VGUI_SCREEN );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectBuffStation::SetupTeamModel( void )
{
	if ( GetTeamNumber() == TEAM_HUMANS )
	{
		if ( m_bBuilding )
		{
			SetModel( BUFF_STATION_HUMAN_ASSEMBLING_MODEL );
		}
		else
		{
			SetModel( BUFF_STATION_HUMAN_MODEL );
		}
	}
	else
	{
		if ( m_bBuilding )
		{
			SetModel( BUFF_STATION_ALIEN_ASSEMBLING_MODEL );
		}
		else
		{
			SetModel( BUFF_STATION_ALIEN_MODEL );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Gets info about the control panels
//-----------------------------------------------------------------------------
void CObjectBuffStation::GetControlPanelInfo( int nControlPanelIndex, const char *&pPanelName )
{
	pPanelName = BUFF_STATION_VGUI_SCREEN;
}

//-----------------------------------------------------------------------------
// Purpose: Remove this object from it's team and mark it for deletion.
//-----------------------------------------------------------------------------
void CObjectBuffStation::DestroyObject( void )
{
	// Detach all players.
	for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ )
	{
		DetachPlayerByIndex( iPlayer );
	}

	// Detach all objects.
	for( int iObject = m_nObjectCount - 1; iObject >= 0; --iObject )
	{
		DetachObjectByIndex( iObject );
	}

	// Inform all other buff stations on this team to attempt to power object (cover for this one).
	if ( GetTFTeam() )
	{
		GetTFTeam()->UpdateBuffStations( this, NULL, false );
	}

	// We shouldn't get any more messages
	g_pNotify->ClearEntity( this );
	BaseClass::DestroyObject();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectBuffStation::OnGoInactive( void )
{
	BaseClass::OnGoInactive();

	// Detach all players.
	for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ )
	{
		CBaseTFPlayer *pPlayer = m_hPlayers[iPlayer].Get();
		if ( pPlayer )
		{
			ClientPrint( pPlayer, HUD_PRINTCENTER, "Lost power to Buff Station!" );
		}

		DetachPlayerByIndex( iPlayer );
	}

	// Detach all objects.
	for ( int iObject = m_nObjectCount - 1; iObject >= 0; --iObject )
	{
		DetachObjectByIndex( iObject );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Attach to players who touch me
//-----------------------------------------------------------------------------
void CObjectBuffStation::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	if ( useType == USE_ON )
	{
		// See if the activator is a player
		if ( !pActivator->IsPlayer() || !InSameTeam( pActivator ) || !pActivator->CanBePoweredUp() )
			return;

		CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>(pActivator);
		if ( pPlayer )
		{
			UpdatePlayerAttachment( pPlayer );
		}
	}

	BaseClass::Use( pActivator, pCaller, useType, value );
}

//-----------------------------------------------------------------------------
// Purpose: Handle commands sent from vgui panels on the client 
//-----------------------------------------------------------------------------
bool CObjectBuffStation::ClientCommand( CBaseTFPlayer *pPlayer, const char *pCmd, ICommandArguments *pArg )
{
	if ( FStrEq( pCmd, "toggle_connect" ) )
	{
		UpdatePlayerAttachment( pPlayer );
		return true;
	}

	return BaseClass::ClientCommand( pPlayer, pCmd, pArg );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectBuffStation::InitAttachmentData( void )
{
	// Initialize the attachment data.
	char szAttachName[13];

	m_nPlayerCount = 0;
	Q_strncpy( szAttachName, "playercable1", 13 );
	for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; ++iPlayer )
	{
		m_hPlayers.Set( iPlayer, NULL );

		szAttachName[11] = '1' + iPlayer;
		m_aPlayerAttachInfo[iPlayer].m_iAttachPoint = LookupAttachment( szAttachName );
	}

	m_nObjectCount = 0;
	Q_strncpy( szAttachName, "objectcable1", 13 );	
	for ( int iObject = 0; iObject < BUFF_STATION_MAX_OBJECTS; ++iObject )
	{
		m_hObjects.Set( iObject, NULL );

		szAttachName[11] = '1' + iObject;
		m_aObjectAttachInfo[iObject].m_iAttachPoint = LookupAttachment( szAttachName );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Create "Buff Station" cable (rope).
//-----------------------------------------------------------------------------
CRopeKeyframe *CObjectBuffStation::CreateRope( CBaseTFPlayer *pPlayer, int iAttachPoint )
{
	CRopeKeyframe *pRope = CRopeKeyframe::Create( this, pPlayer, iAttachPoint, 0 );
	if ( pRope )
	{
		pRope->m_RopeLength = obj_buff_station_range.GetFloat();
		pRope->m_Slack = 0.0f;
		pRope->m_Width = 2;
		pRope->m_nSegments = ROPE_MAX_SEGMENTS;
		pRope->m_RopeFlags |= ROPE_COLLIDE;
		pRope->EnablePlayerWeaponAttach( true );
		pRope->ActivateStartDirectionConstraints( true );
		if ( GetTeamNumber() == TEAM_HUMANS )
		{
			pRope->SetMaterial( "cable/human_buffcable.vmt" );
		}
		else
		{
			pRope->SetMaterial( "cable/alien_buffcable.vmt" );
		}
	}

	return pRope;
}

//-----------------------------------------------------------------------------
// Purpose: Create "Buff Station" cable (rope).
//-----------------------------------------------------------------------------
CRopeKeyframe *CObjectBuffStation::CreateRope( CBaseObject *pObject, int iAttachPoint, int iObjectAttachPoint )
{
	CRopeKeyframe *pRope = CRopeKeyframe::Create( this, pObject, iAttachPoint, iObjectAttachPoint );
	if ( pRope )
	{
		pRope->m_RopeLength = obj_buff_station_obj_range.GetFloat();
		pRope->m_Slack = 0.0f;
		pRope->m_Width = 2;
		pRope->m_nSegments = ROPE_MAX_SEGMENTS;
		pRope->m_RopeFlags |= ROPE_COLLIDE;
		pRope->EnablePlayerWeaponAttach( true );
		pRope->ActivateStartDirectionConstraints( true );
		if ( GetTeamNumber() == TEAM_HUMANS )
		{
			pRope->SetMaterial( "cable/human_buffcable.vmt" );
		}
		else
		{
			pRope->SetMaterial( "cable/alien_buffcable.vmt" );
		}
	}

	return pRope;
}

//-----------------------------------------------------------------------------
// Purpose: Is a particular player attached?
//-----------------------------------------------------------------------------
bool CObjectBuffStation::IsPlayerAttached( CBaseTFPlayer *pPlayer )
{
	for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ )
	{
		if ( m_hPlayers[iPlayer].Get() == pPlayer )
			return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Is a particular object attached?
//-----------------------------------------------------------------------------
bool CObjectBuffStation::IsObjectAttached( CBaseObject *pObject )
{
	for ( int iObject = 0; iObject < m_nObjectCount; ++iObject )
	{
		if ( m_hObjects[iObject].Get() == pObject )
			return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Attach the player to the "Buff Station."
//-----------------------------------------------------------------------------
void CObjectBuffStation::AttachPlayer( CBaseTFPlayer *pPlayer )
{
	// Player shouldn't already be attached.
	Assert( !IsPlayerAttached( pPlayer ) );

	// Check to see if the player is alive and on the correct team.
	if ( !pPlayer->IsAlive() || !pPlayer->InSameTeam( this ) )
		return;

	// Check attachment availability.
	if ( m_nPlayerCount == BUFF_STATION_MAX_PLAYERS )
	{
		// Unless the player is the owner he cannot connect.
		if ( pPlayer != GetOwner() )
			return;

		// Kick a non-owning player off.
		DetachPlayerByIndex( BUFF_STATION_MAX_PLAYERS - 1 );
	}

	// This will disconnect the player from other Buff Stations, and keep track of important player events.
	g_pNotify->ReportNamedEvent( pPlayer, "PlayerAttachedToGenerator" );
	g_pNotify->AddEntity( this, pPlayer );

	// Connect player.
	// Find the nearest empty slot
	int iNearest = BUFF_STATION_MAX_PLAYERS;
	float flNearestDist = 9999*9999;
	for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ )
	{
		if ( !m_hPlayers[iPlayer] )
		{
			Vector vecPoint;
			QAngle angPoint;
			GetAttachment( m_aPlayerAttachInfo[iPlayer].m_iAttachPoint, vecPoint, angPoint );
			float flDistance = ( vecPoint - pPlayer->GetAbsOrigin() ).LengthSqr();
			if ( flDistance < flNearestDist )
			{
				flNearestDist = flDistance;
				iNearest = iPlayer;
			}
		}
	}
	Assert( iNearest != BUFF_STATION_MAX_PLAYERS );

	m_hPlayers.Set( iNearest, pPlayer );
	m_aPlayerAttachInfo[iNearest].m_DamageModifier.SetModifier( obj_buff_station_damage_modifier.GetFloat() );
	m_aPlayerAttachInfo[iNearest].m_hRope = CreateRope( pPlayer, m_aPlayerAttachInfo[iNearest].m_iAttachPoint );
	m_nPlayerCount++;

	// Tell the player to constrain his movement.
	pPlayer->ActivateMovementConstraint( this, GetAbsOrigin(), obj_buff_station_range.GetFloat(), 75.0f, 0.15f );

	// Update think.
	if ( GetNextThink(BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT) > gpGlobals->curtime + BUFF_STATION_BOOST_PLAYER_THINK_INTERVAL )
	{
		SetNextThink( gpGlobals->curtime + BUFF_STATION_BOOST_PLAYER_THINK_INTERVAL, BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Detach the player from the "Buff Station."
//-----------------------------------------------------------------------------
void CObjectBuffStation::DetachPlayer( CBaseTFPlayer *pPlayer )
{
	for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ )
	{
		if ( m_hPlayers[iPlayer].Get() == pPlayer )
		{
			DetachPlayerByIndex( iPlayer );
			return;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Detach the player from the "Buff Station."
//-----------------------------------------------------------------------------
void CObjectBuffStation::DetachPlayerByIndex( int nIndex )
{
	// Valid index?
	Assert( nIndex < BUFF_STATION_MAX_PLAYERS );

	// Get the player.
	CBaseTFPlayer *pPlayer = m_hPlayers[nIndex].Get();
	if ( !pPlayer )
	{
		m_hPlayers.Set( nIndex, NULL );
		return;
	}

	// Remove the damage modifier.
	m_aPlayerAttachInfo[nIndex].m_DamageModifier.RemoveModifier();

	// Remove the rope (cable).
	if ( m_aPlayerAttachInfo[nIndex].m_hRope.Get() )
	{
		m_aPlayerAttachInfo[nIndex].m_hRope->DetachPoint( 1 );
		m_aPlayerAttachInfo[nIndex].m_hRope->DieAtNextRest();
	}

	// Unconstrain the player movement.
	pPlayer->DeactivateMovementConstraint();

	// Keep track of player events.
	g_pNotify->RemoveEntity( this, pPlayer );

	// Reduce player count.
	m_nPlayerCount--;
	m_hPlayers.Set( nIndex, NULL );
}

//-----------------------------------------------------------------------------
// Purpose: Attach the object to the "Buff Station."
//-----------------------------------------------------------------------------
void CObjectBuffStation::AttachObject( CBaseObject *pObject, bool bPlacing )
{
	// Check to see if the object is already attached.
	if ( IsObjectAttached( pObject ) )
		return;

	// Check to see if the object is on the correct team.
	if ( !pObject->InSameTeam( this ) )
		return;

	// Check to see if the object is already being buffed by another station.
	if ( pObject->IsHookedAndBuffed() )
		return;

	// Check attachment availability.
	if ( m_nObjectCount == BUFF_STATION_MAX_OBJECTS )
		return;

	// Attach cable to object - get the attachment point.
	int nObjectAttachPoint = pObject->LookupAttachment( "boostpoint" );
	if ( nObjectAttachPoint <= 0 )
		nObjectAttachPoint = 1;

	// Connect object.
	m_hObjects.Set( m_nObjectCount, pObject );
	m_aObjectAttachInfo[m_nObjectCount].m_DamageModifier.SetModifier( obj_buff_station_damage_modifier.GetFloat() );
	m_aObjectAttachInfo[m_nObjectCount].m_hRope = CreateRope( pObject, m_aObjectAttachInfo[m_nObjectCount].m_iAttachPoint, nObjectAttachPoint );
	m_nObjectCount += 1;

	// If we're placing, we're pretending to buff objects, but not really powering them
	pObject->SetBuffStation( this, bPlacing );

	// Update think.
	if ( GetNextThink(BUFF_STATION_BOOST_OBJECT_THINK_CONTEXT) > gpGlobals->curtime + BUFF_STATION_BOOST_OBJECT_THINK_INTERVAL )
	{
		SetNextThink( gpGlobals->curtime + BUFF_STATION_BOOST_OBJECT_THINK_INTERVAL, BUFF_STATION_BOOST_OBJECT_THINK_CONTEXT );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Detach the object from the "Buff Station."
//-----------------------------------------------------------------------------
void CObjectBuffStation::DetachObject( CBaseObject *pObject )
{
	for ( int iObject = 0; iObject < m_nObjectCount; ++iObject )
	{
		if ( m_hObjects[iObject].Get() == pObject )
		{
			DetachObjectByIndex( iObject );
			return;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Detach the object from the "Buff Station."
//-----------------------------------------------------------------------------
void CObjectBuffStation::DetachObjectByIndex( int nIndex )
{
	// Valid index?
	Assert( nIndex >= 0 );
	Assert( nIndex < m_nObjectCount );

	// Get the object.
	CBaseObject *pObject = m_hObjects[nIndex].Get();
	if ( !pObject )
		return;

	// Remove the damage modifier.
	m_aObjectAttachInfo[nIndex].m_DamageModifier.RemoveModifier();

	// Remove the rope (cable).
	if ( m_aObjectAttachInfo[nIndex].m_hRope.Get() )
	{
		m_aObjectAttachInfo[nIndex].m_hRope->DetachPoint( 1 );
		m_aObjectAttachInfo[nIndex].m_hRope->DieAtNextRest();
	}

	// Reduce object count.
	m_nObjectCount -= 1;

	// Set the object as unbuffed.
	pObject->SetBuffStation( NULL, false );

	// If the detached object wasn't the last object in the list, swap placement.
	if ( nIndex != m_nObjectCount )
	{
		SwapObjectAttachment( nIndex );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectBuffStation::UpdatePlayerAttachment( CBaseTFPlayer *pPlayer )
{
	// Valid player?
	if ( !pPlayer )
		return;

	// Attach/Detach (toggle).
	if ( IsPlayerAttached( pPlayer ) )
	{
		DetachPlayer( pPlayer );
	}
	else
	{
		// Check for power, do not attach to unpowered generator.
		if ( !IsPowered() )
		{
			ClientPrint( pPlayer, HUD_PRINTCENTER, "No power source for the Buff Station!" );
		}
		else
		{
			AttachPlayer( pPlayer );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectBuffStation::SwapObjectAttachment( int nIndex )
{
	bool bModifierActive = m_aObjectAttachInfo[m_nObjectCount].m_DamageModifier.GetCharacter() != NULL;
	m_aObjectAttachInfo[m_nObjectCount].m_DamageModifier.RemoveModifier();

	m_hObjects.Set( nIndex, m_hObjects[m_nObjectCount] );
	m_aObjectAttachInfo[nIndex] = m_aObjectAttachInfo[m_nObjectCount];

	if ( bModifierActive )
	{
		m_aObjectAttachInfo[nIndex].m_DamageModifier.AddModifierToEntity( m_hObjects[nIndex].Get() );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Input handler
//-----------------------------------------------------------------------------
void CObjectBuffStation::InputPlayerSpawned( inputdata_t &inputdata )
{
	if ( inputdata.pActivator->IsPlayer() )
	{
		CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>( inputdata.pActivator );
		if ( IsPlayerAttached( pPlayer ) )
		{
			DetachPlayer( pPlayer );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Input handler
//-----------------------------------------------------------------------------
void CObjectBuffStation::InputPlayerAttachedToGenerator( inputdata_t &inputdata )
{
	if ( inputdata.pActivator->IsPlayer() )
	{
		CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>( inputdata.pActivator );
		if ( IsPlayerAttached( pPlayer ) )
		{
			DetachPlayer( pPlayer );
		}
	}
}

//-----------------------------------------------------------------------------
// Boost those attached to me as long as I'm not EMPed
//-----------------------------------------------------------------------------
void CObjectBuffStation::BoostPlayerThink( void )
{
	// Are we emped?
	bool bIsEmped = HasPowerup( POWERUP_EMP );

	// Get range (squared = faster test).
	float flMaxRangeSq = obj_buff_station_range.GetFloat();
	flMaxRangeSq *= flMaxRangeSq;

	// Boost all attached players and objects.
	for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ )
	{
		// Clean up dangling pointers + dead players, subversion, disconnection
		CBaseTFPlayer *pPlayer = m_hPlayers[iPlayer].Get();
		if ( !pPlayer || !pPlayer->IsAlive() || !InSameTeam( pPlayer ) || !pPlayer->PlayerClass() )
		{
			DetachPlayerByIndex( iPlayer );
			continue;
		}

		// Check for out of range.
		float flDistSq = GetAbsOrigin().DistToSqr( pPlayer->GetAbsOrigin() ); 
		if ( flDistSq > flMaxRangeSq )
		{
			DetachPlayerByIndex( iPlayer );
			continue;
		}

		bool bBoosted = false;
		if ( !bIsEmped )
		{
			float flHealAmount = obj_buff_station_heal_rate.GetFloat() * BUFF_STATION_BOOST_PLAYER_THINK_INTERVAL;
			bBoosted = pPlayer->AttemptToPowerup( POWERUP_BOOST, 0, flHealAmount, this, &m_aPlayerAttachInfo[iPlayer].m_DamageModifier );
		}

		if ( !bBoosted )
		{
			m_aPlayerAttachInfo[iPlayer].m_DamageModifier.RemoveModifier();
		}
	}

	// Set next think time.
	if ( m_nPlayerCount > 0 )
	{
		SetNextThink( gpGlobals->curtime + BUFF_STATION_BOOST_PLAYER_THINK_INTERVAL, 
			          BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT );
	}
	else
	{
		SetNextThink( gpGlobals->curtime + 1.0f, BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT );
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectBuffStation::BoostObjectThink( void )
{
	// Set next boost object think time.
	SetNextThink( gpGlobals->curtime + BUFF_STATION_BOOST_OBJECT_THINK_INTERVAL, 
		          BUFF_STATION_BOOST_OBJECT_THINK_CONTEXT );

	// If we're emped, placing, or building, we're not ready to powerup
	if ( IsPlacing() || IsBuilding() || HasPowerup( POWERUP_EMP ) )
		return;

	float flMaxRangeSq = obj_buff_station_obj_range.GetFloat();
	flMaxRangeSq *= flMaxRangeSq;

	// Boost objects.
	for ( int iObject = m_nObjectCount; --iObject >= 0; )
	{
		CBaseObject *pObject = m_hObjects[iObject].Get();
		if ( !pObject || !InSameTeam( pObject ) )
		{
			DetachObjectByIndex( iObject );
			continue;
		}

		// Check for out of range.
		float flDistSq = GetAbsOrigin().DistToSqr( pObject->GetAbsOrigin() ); 
		if ( flDistSq > flMaxRangeSq )
		{
			DetachObjectByIndex( iObject );
			continue;
		}

		// Don't powerup it until it's finished building
		if ( pObject->IsPlacing() || pObject->IsBuilding() )
			continue;

		// Boost it
		if ( !pObject->AttemptToPowerup( POWERUP_BOOST, BUFF_STATION_BOOST_OBJECT_THINK_INTERVAL, 0, 
			                                  this, &m_aObjectAttachInfo[iObject].m_DamageModifier ) )
		{
			m_aObjectAttachInfo[iObject].m_DamageModifier.RemoveModifier();
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectBuffStation::DeBuffObject( CBaseObject *pObject )
{
	DetachObject( pObject );
}

//-----------------------------------------------------------------------------
// Purpose: Find nearby objects and buff them.
//-----------------------------------------------------------------------------
void CObjectBuffStation::BuffNearbyObjects( CBaseObject *pObjectToTarget, bool bPlacing )
{
	// ROBIN: Disabled object buffing for now
	return;

	// Check for a team.
	if ( !GetTFTeam() )
		return;

	// Am I ready to power anything?
	if ( IsBuilding() || ( !bPlacing && IsPlacing() ) )
		return;

	// Am I already full?
	if ( m_nObjectCount >= BUFF_STATION_MAX_OBJECTS )
		return;

	// Do we have a specific target?
	if ( pObjectToTarget )
	{
		if( !pObjectToTarget->CanBeHookedToBuffStation() || pObjectToTarget->GetBuffStation() )
			return;

		if ( IsWithinBuffRange( pObjectToTarget ) )
		{
			AttachObject( pObjectToTarget, bPlacing );
		}
	}
	else
	{
		// Find nearby objects 
		for ( int iObject = 0; iObject < GetTFTeam()->GetNumObjects(); iObject++ )
		{
			CBaseObject *pObject = GetTFTeam()->GetObject( iObject );
			assert(pObject);

			if ( pObject == this || !pObject->CanBeHookedToBuffStation() || pObject->GetBuffStation() )
				continue;

			// Make sure it's within range
			if ( IsWithinBuffRange( pObject ) )
			{
				AttachObject( pObject, bPlacing );

				// Am I now full?
				if ( m_nObjectCount >= BUFF_STATION_MAX_OBJECTS )
					break;
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Update buff connections on the fly while placing
//-----------------------------------------------------------------------------
bool CObjectBuffStation::CalculatePlacement( CBaseTFPlayer *pPlayer )
{
	bool bReturn = BaseClass::CalculatePlacement( pPlayer );

	// First, disconnect any connections that should break (too far away).
	for ( int iObject = m_nObjectCount - 1; iObject >= 0; --iObject )
	{
		if ( GetBuffedObject( iObject ) )
		{
			CheckBuffConnection( GetBuffedObject( iObject ) );
		}
	}

	// If we have any spare connections, look for nearby objects to buff
	if ( m_nObjectCount < BUFF_STATION_MAX_OBJECTS )
	{
		BuffNearbyObjects( NULL, true );
	}

	return bReturn;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectBuffStation::FinishedBuilding( void )
{
	BaseClass::FinishedBuilding();

	for( int iObject = 0; iObject < m_nObjectCount; ++iObject )
	{
		if ( GetBuffedObject( iObject ) )
		{
			GetBuffedObject( iObject )->SetBuffStation( this, false );
		}
	}

	BuffNearbyObjects( NULL, false );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectBuffStation::CheckBuffConnection( CBaseObject *pObject )
{
	if ( !pObject->CanBeHookedToBuffStation() )
		return;

	// Check to see if the object is within the buff range.
	if ( IsWithinBuffRange( pObject ) )
		return;

	// It's obscured, or out of range. Remove it.
	DetachObject( pObject );
}

//-----------------------------------------------------------------------------
// Purpose: Return true if this object is powerable
//-----------------------------------------------------------------------------
bool CObjectBuffStation::IsWithinBuffRange( CBaseObject *pObject )
{
	if ( ( pObject->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr() < BUFF_STATION_BUFF_RANGE )
	{
		// Can I see it?
		// Ignore things we're attached to
		trace_t tr;
		CTraceFilterWorldAndPropsOnly buffFilter;
		UTIL_TraceLine( WorldSpaceCenter(), pObject->WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, &buffFilter, &tr );
		CBaseEntity *pEntity = tr.m_pEnt;
		if ( ( tr.fraction == 1.0 ) || ( pEntity == pObject ) )
			return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : act - 
//-----------------------------------------------------------------------------
void CObjectBuffStation::OnActivityChanged( Activity act )
{
	BaseClass::OnActivityChanged( act );

	switch ( act )
	{
	case ACT_OBJ_ASSEMBLING:
		m_bBuilding = true;
		break;
	default:
		m_bBuilding = false;
		break;
	}

	SetupTeamModel();
}