//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose: Base Object built by players
//
// $NoKeywords: $
//=============================================================================
#include "cbase.h"
#include "tf_player.h"
#include "tf_team.h"
#include "tf_obj.h"
#include "tf_basecombatweapon.h"
#include "rope.h"
#include "rope_shared.h"
#include "bone_setup.h"
#include "tf_func_resource.h"
#include "ndebugoverlay.h"
#include "rope_helpers.h"
#include "IEffects.h"
#include "vstdlib/random.h"
#include "vstdlib/strtools.h"
#include "basegrenade_shared.h"
#include "grenade_objectsapper.h"
#include "tf_stats.h"
#include "tf_gamerules.h"
#include "engine/IEngineSound.h"
#include "tf_obj_sentrygun.h"
#include "tf_obj_powerpack.h"
#include "tf_shareddefs.h"
#include "VGuiScreen.h"
#include "resource_chunk.h"
#include "hierarchy.h"
#include "tf_func_construction_yard.h"
#include "tf_func_no_build.h"
#include <KeyValues.h>
#include "team_messages.h"
#include "info_act.h"
#include "info_vehicle_bay.h"
#include "ihasbuildpoints.h"
#include "tf_obj_buff_station.h"
#include "info_buildpoint.h"
#include "utldict.h"
#include "filesystem.h"
#include "npcevent.h"
#include "tf_shareddefs.h"
#include "animation.h"
#include "terrainmodmgr.h"

// Control panels
#define SCREEN_OVERLAY_MATERIAL "vgui/screens/vgui_overlay"

#define ROPE_HANG_DIST	150


ConVar object_verbose( "object_verbose", "0", 0, "Debug object system." );
ConVar obj_damage_factor( "obj_damage_factor","0", FCVAR_NONE, "Factor applied to all damage done to objects" );
ConVar obj_child_damage_factor( "obj_child_damage_factor","0.25", FCVAR_NONE, "Factor applied to damage done to objects that are built on a buildpoint" );
ConVar tf_fastbuild("tf_fastbuild", "0", FCVAR_CHEAT);
ConVar tf_obj_ground_clearance( "tf_obj_ground_clearance", "60", 0, "Object corners can be this high above the ground" );

extern short g_sModelIndexFireball;

// Minimum distance between 2 objects to ensure player movement between them
#define MINIMUM_OBJECT_SAFE_DISTANCE		100

// Maximum number of a type of objects on a single resource zone
#define MAX_OBJECTS_PER_ZONE			1

// Time it takes a fully healed object to deteriorate
ConVar  object_deterioration_time( "object_deterioration_time", "30", 0, "Time it takes for a fully-healed object to deteriorate." );

// Time after taking damage that an object will still drop resources on death
#define MAX_DROP_TIME_AFTER_DAMAGE			5

#define OBJ_BASE_THINK_CONTEXT				"BaseObjectThink"
#define OBJ_LOSTPOWER_THINK_CONTEXT			"LostPowerThink"

BEGIN_DATADESC( CBaseObject )
	// keys 
	DEFINE_KEYFIELD_NOT_SAVED( CBaseObject, m_bInvulnerable,		FIELD_BOOLEAN, "Invulnerable" ),
	DEFINE_KEYFIELD_NOT_SAVED( CBaseObject, m_flRepairMultiplier,	FIELD_FLOAT, "RepairMult" ),
	DEFINE_KEYFIELD_NOT_SAVED( CBaseObject, m_iszUnderAttackSound,	FIELD_STRING, "AttackNotify" ), 
	DEFINE_KEYFIELD_NOT_SAVED( CBaseObject, m_flMinDisableHealth,	FIELD_FLOAT, "MinDisabledHealth" ),
	DEFINE_KEYFIELD_NOT_SAVED( CBaseObject, m_SolidToPlayers,		FIELD_INTEGER, "SolidToPlayer" ),
	DEFINE_KEYFIELD_NOT_SAVED( CBaseObject, m_iszDisabledModel,		FIELD_STRING, "DisabledModel" ),
	DEFINE_KEYFIELD_NOT_SAVED( CBaseObject, m_bCantDie,				FIELD_BOOLEAN, "CantDie" ),

	// Inputs
	DEFINE_INPUTFUNC( CBaseObject, FIELD_INTEGER, "SetHealth", InputSetHealth ),
	DEFINE_INPUTFUNC( CBaseObject, FIELD_INTEGER, "AddHealth", InputAddHealth ),
	DEFINE_INPUTFUNC( CBaseObject, FIELD_INTEGER, "RemoveHealth", InputRemoveHealth ),
	DEFINE_INPUTFUNC( CBaseObject, FIELD_INTEGER, "SetMinDisabledHealth", InputSetMinDisabledHealth ),
	DEFINE_INPUTFUNC( CBaseObject, FIELD_INTEGER, "SetSolidToPlayer", InputSetSolidToPlayer ),

	// Outputs
	DEFINE_OUTPUT( CBaseObject, m_OnDestroyed, "OnDestroyed" ),
	DEFINE_OUTPUT( CBaseObject, m_OnDamaged, "OnDamaged" ),
	DEFINE_OUTPUT( CBaseObject, m_OnRepaired, "OnRepaired" ),
	DEFINE_OUTPUT( CBaseObject, m_OnBecomingDisabled, "OnDisabled" ),
	DEFINE_OUTPUT( CBaseObject, m_OnBecomingReenabled, "OnReenabled" ),
	DEFINE_OUTPUT( CBaseObject, m_OnObjectHealthChanged, "OnObjectHealthChanged" )
END_DATADESC()


IMPLEMENT_SERVERCLASS_ST(CBaseObject, DT_BaseObject)
	SendPropInt(SENDINFO(m_iHealth), 13 ),
	SendPropInt(SENDINFO(m_iMaxHealth), 13 ),
	SendPropInt(SENDINFO(m_bHasSapper), 1, SPROP_UNSIGNED ),
	SendPropInt(SENDINFO(m_iObjectType),  6, SPROP_UNSIGNED ),
	SendPropInt(SENDINFO(m_bBuilding), 1, SPROP_UNSIGNED ),
	SendPropInt(SENDINFO(m_bPlacing), 1, SPROP_UNSIGNED ),
	SendPropFloat(SENDINFO(m_flPercentageConstructed), 8, 0, 0.0, 1.0f ),
	SendPropInt(SENDINFO(m_fObjectFlags), OF_BIT_COUNT, SPROP_UNSIGNED ),
	SendPropInt(SENDINFO(m_bDeteriorating), 1, SPROP_UNSIGNED ),
	SendPropEHandle(SENDINFO(m_hBuiltOnEntity)),
	SendPropInt( SENDINFO( m_takedamage ), 2, SPROP_UNSIGNED ),
	SendPropInt( SENDINFO( m_bDisabled ), 1, SPROP_UNSIGNED ),
	SendPropEHandle( SENDINFO( m_hBuilder ) ),
END_SEND_TABLE();


// This controls whether ropes attached to objects are transmitted or not. It's important that
// ropes aren't transmitted to guys who don't own them.
class CObjectRopeTransmitProxy : public CBaseTransmitProxy
{
public:
	CObjectRopeTransmitProxy( CBaseEntity *pRope ) : CBaseTransmitProxy( pRope )
	{
	}
	
	virtual bool ShouldTransmit( const edict_t *recipient, const void *pvs, int clientArea, bool bPrevShouldTransmitResult )
	{
		// Don't transmit the rope if it's not even visible.
		if ( !bPrevShouldTransmitResult )
			return false;

		// This proxy only wants to be active while one of the two objects is being placed.
		// When they're done being placed, the proxy goes away and the rope draws like normal.
		bool bAnyObjectPlacing = (m_hObj1 && m_hObj1->IsPlacing()) || (m_hObj2 && m_hObj2->IsPlacing());
		if ( !bAnyObjectPlacing )
		{
			Release();
			return bPrevShouldTransmitResult;
		}

		// Give control to whichever object is being placed.
		if ( m_hObj1 && m_hObj1->IsPlacing() )
			return m_hObj1->ShouldTransmit( recipient, pvs, clientArea );
		
		else if ( m_hObj2 && m_hObj2->IsPlacing() )
			return m_hObj2->ShouldTransmit( recipient, pvs, clientArea );
		
		else
			return true;
	}


	CHandle<CBaseObject> m_hObj1;
	CHandle<CBaseObject> m_hObj2;
};


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CBaseObject::CBaseObject()
{
	m_hPowerPack = NULL;
	m_iHealth = m_iMaxHealth = m_flHealth = 0;
	m_aRopes.Purge();
	m_flPercentageConstructed = 0;
	m_bPlacing = false;
	m_bBuilding = false;
	m_bInvulnerable = false;
	m_bCantDie = false;
	m_bDeteriorating = false;
	m_flRepairMultiplier = 1;
	m_hBuffStation = NULL;
	m_bBuffActivated = false;
	m_Activity = ACT_OBJ_IDLE;
	m_bDisabled = false;
	m_hVehicleBay = NULL;
	m_flLastRepairTime = 0;
	m_flNextRepairMultiplier = 0;
	m_flRepairedSinceLastTime = 0;
	m_iszUnderAttackSound = NULL_STRING;
	m_SolidToPlayers = SOLID_TO_PLAYER_USE_DEFAULT;
	m_iszDisabledModel = NULL_STRING;
	m_iszEnabledModel = NULL_STRING;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::UpdateOnRemove( void )
{
	m_bDying = true;

	// Remove anything left on me
	IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this);
	if ( pBPInterface && pBPInterface->GetNumObjectsOnMe() )
	{
		pBPInterface->RemoveAllObjects();
	}

	DestroyObject();
	
	if ( GetTeam() )
	{
		((CTFTeam*)GetTeam())->RemoveObject( this );
	}

	// Make sure the object isn't in either team's list of objects...
	Assert( !GetGlobalTFTeam(1)->IsObjectOnTeam( this ) );
	Assert( !GetGlobalTFTeam(2)->IsObjectOnTeam( this ) );

	// Chain at end to mimic destructor unwind order
	BaseClass::UpdateOnRemove();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseObject::ShouldTransmit( const edict_t *recipient, const void *pvs, int clientArea )
{
	// Always transmit to owner
	if ( GetBuilder() && recipient == GetBuilder()->edict() )
		return true;

	// Placement models only transmit to owners
	if ( IsPlacing() )
		return false;

	return BaseClass::ShouldTransmit( recipient, pvs, clientArea );
}


void CBaseObject::SetTransmit( CCheckTransmitInfo *pInfo )
{
	// Are we already marked for transmission?
	if ( pInfo->WillTransmit( entindex() ) )
		return;

	BaseClass::SetTransmit( pInfo );

	// Force our screens to be sent too.
	int nTeam = CBaseEntity::Instance( pInfo->m_pClientEnt )->GetTeamNumber();
	for ( int i=0; i < m_hScreens.Count(); i++ )
	{
		CVGuiScreen *pScreen = m_hScreens[i].Get();
		if ( pScreen && pScreen->IsVisibleToTeam( nTeam ) )
			pScreen->SetTransmit( pInfo );
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::Precache()
{
	PrecacheVGuiScreen( "screen_basic_with_disable" );

	if ( m_iszUnderAttackSound != NULL_STRING )
	{
		PrecacheSound( STRING(m_iszUnderAttackSound) );
	}
	PrecacheMaterial( SCREEN_OVERLAY_MATERIAL );

	if ( m_iszDisabledModel != NULL_STRING )
	{
		PrecacheModel( STRING( m_iszDisabledModel ) );
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::Spawn( void )
{
	Precache();

	SetSolidToPlayers( m_SolidToPlayers, true );

	m_bWasMapPlaced = false;
	m_bHasSapper = false;
	m_takedamage = DAMAGE_YES;
	m_flHealth = m_iMaxHealth = m_iHealth;
	m_iAmountPlayerPaidForMe = CalculateObjectCost( GetType(), 0, GetTeamNumber() );

	SetContextThink( BaseObjectThink, gpGlobals->curtime + 0.1, OBJ_BASE_THINK_CONTEXT );
	m_szAmmoName = NULL;
	NetworkStateManualMode( true );

	AddFlag( FL_OBJECT ); // So NPCs will notice it
	SetViewOffset( WorldSpaceCenter() - GetAbsOrigin() );

	// Don't take damage if we're invulnerable, and don't require power either
	if ( m_bInvulnerable )
	{
		m_takedamage = DAMAGE_NO;
		m_fObjectFlags |= OF_DOESNT_NEED_POWER;
		AddFlag( FL_NOTARGET );
	}

	// Cache off the normal model name
	m_iszEnabledModel = GetModelName();
}


//-----------------------------------------------------------------------------
// Returns information about the various control panels
//-----------------------------------------------------------------------------
void CBaseObject::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
{
	pPanelName = NULL;
}

//-----------------------------------------------------------------------------
// Returns information about the various control panels
//-----------------------------------------------------------------------------
void CBaseObject::GetControlPanelClassName( int nPanelIndex, const char *&pPanelName )
{
	pPanelName = "vgui_screen";
}

//-----------------------------------------------------------------------------
// This is called by the base object when it's time to spawn the control panels
//-----------------------------------------------------------------------------
void CBaseObject::SpawnControlPanels()
{
	char buf[64];

	// FIXME: Deal with dynamically resizing control panels?

	// If we're attached to an entity, spawn control panels on it instead of use
	CBaseAnimating *pEntityToSpawnOn = this;
	char *pOrgLL = "controlpanel%d_ll";
	char *pOrgUR = "controlpanel%d_ur";
	char *pAttachmentNameLL = pOrgLL;
	char *pAttachmentNameUR = pOrgUR;
	if ( IsBuiltOnAttachment() )
	{
		pEntityToSpawnOn = dynamic_cast<CBaseAnimating*>((CBaseEntity*)m_hBuiltOnEntity.Get());
		if ( pEntityToSpawnOn )
		{
			char sBuildPointLL[64];
			char sBuildPointUR[64];
			Q_snprintf( sBuildPointLL, 64, "bp%d_controlpanel%%d_ll", m_iBuiltOnPoint );
			Q_snprintf( sBuildPointUR, 64, "bp%d_controlpanel%%d_ur", m_iBuiltOnPoint );
			pAttachmentNameLL = sBuildPointLL;
			pAttachmentNameUR = sBuildPointUR;
		}
		else
		{
			pEntityToSpawnOn = this;
		}
	}

	Assert( pEntityToSpawnOn );

	// Lookup the attachment point...
	int nPanel;
	for ( nPanel = 0; true; ++nPanel )
	{
		Q_snprintf( buf, 64, pAttachmentNameLL, nPanel );
		int nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
		if (nLLAttachmentIndex <= 0)
		{
			// Try and use my panels then
			pEntityToSpawnOn = this;
			Q_snprintf( buf, 64, pOrgLL, nPanel );
			nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
			if (nLLAttachmentIndex <= 0)
				return;
		}

		Q_snprintf( buf, 64, pAttachmentNameUR, nPanel );
		int nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
		if (nURAttachmentIndex <= 0)
		{
			// Try and use my panels then
			Q_snprintf( buf, 64, pOrgUR, nPanel );
			nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
			if (nURAttachmentIndex <= 0)
				return;
		}

		const char *pScreenName;
		GetControlPanelInfo( nPanel, pScreenName );
		if (!pScreenName)
			continue;

		const char *pScreenClassname;
		GetControlPanelClassName( nPanel, pScreenClassname );
		if ( !pScreenClassname )
			continue;

		// Compute the screen size from the attachment points...
		matrix3x4_t	panelToWorld;
		pEntityToSpawnOn->GetAttachment( nLLAttachmentIndex, panelToWorld );

		matrix3x4_t	worldToPanel;
		MatrixInvert( panelToWorld, worldToPanel );

		// Now get the lower right position + transform into panel space
		Vector lr, lrlocal;
		pEntityToSpawnOn->GetAttachment( nURAttachmentIndex, panelToWorld );
		MatrixGetColumn( panelToWorld, 3, lr );
		VectorTransform( lr, worldToPanel, lrlocal );

		float flWidth = lrlocal.x;
		float flHeight = lrlocal.y;

		CVGuiScreen *pScreen = CreateVGuiScreen( pScreenClassname, pScreenName, pEntityToSpawnOn, this, nLLAttachmentIndex );
		pScreen->ChangeTeam( GetTeamNumber() );
		pScreen->SetActualSize( flWidth, flHeight );
		pScreen->SetActive( false );
		pScreen->MakeVisibleOnlyToTeammates( true );
		pScreen->SetOverlayMaterial( SCREEN_OVERLAY_MATERIAL );
		int nScreen = m_hScreens.AddToTail( );
		m_hScreens[nScreen].Set( pScreen );
	}
}


//-----------------------------------------------------------------------------
// Various commands sent by control panels
//-----------------------------------------------------------------------------
void CBaseObject::DismantleCommand( CBaseTFPlayer *pSender )
{
	if (CanBeRemovedBy( pSender ))
	{
		PickupObject();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::YawCommand( CBaseTFPlayer *pSender, float flYaw )
{
	if ( CanBeRotatedBy(pSender) )
	{
		QAngle angles = GetAbsAngles();

		angles.y = anglemod( flYaw );
		SetLocalAngles( ConvertAbsAnglesToLocal( angles ) );
		Teleport( NULL, &GetLocalAngles(), NULL );

		// Notify the object that it moved
		ObjectMoved();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::TakeControlCommand( CBaseTFPlayer *pSender )
{
	// Deteriorating objects can be bought
	if ( InSameTeam( pSender ) && IsDeteriorating() )
	{
		if ( ClassCanBuild( pSender->PlayerClass(), GetType() ) )
		{
			// Make sure he has the resources
			int iCost = CalculateObjectCost( GetType(), pSender->GetNumObjects( GetType() ), GetTeamNumber() );
			if ( pSender->GetBankResources() >= iCost )
			{
				pSender->RemoveBankResources( iCost );
				SetBuilder( pSender );
				pSender->AddObject( this );
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Handle commands sent from vgui panels on the client 
//-----------------------------------------------------------------------------
bool CBaseObject::ClientCommand( CBaseTFPlayer *pSender, const char *pCmd, ICommandArguments *pArg )
{
	if ( FStrEq( pCmd, "dismantle" ) )
	{
		DismantleCommand( pSender );
		return true;
	}

	if ( FStrEq( pCmd, "yaw" ) )
	{
		if ( pArg->Argc() == 2 )
		{
			float flYaw = atof( pArg->Argv(1) );
			YawCommand( pSender, flYaw );
		}
		return true;
	}

	if ( FStrEq( pCmd, "takecontrol" ) )
	{
		TakeControlCommand( pSender );
		return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::BaseObjectThink( void )
{
	SetNextThink( gpGlobals->curtime + 0.1, OBJ_BASE_THINK_CONTEXT );

	// Make sure animation is up to date
	DetermineAnimation();

	// Can't animate without a model
	if ( !(m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) )
	{
		StudioFrameAdvance();
	}

	/*
	ROBIN: Hierarchy should do this for us

	// If we were built on an attachment that's moved, update our position
	if ( !IsPlacing() && IsBuiltOnAttachment() )
	{
		IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>((CBaseEntity*)m_hBuiltOnEntity);
		Assert( pBPInterface );

		if ( pBPInterface->ShouldCheckForMovement() )
		{
			Vector vecOrigin;
			QAngle vecAngles;
			pBPInterface->GetBuildPoint( m_iBuiltOnPoint, vecOrigin, vecAngles );

			EntityMatrix vehicleToWorld, childMatrix;
			vehicleToWorld.InitFromEntity( GetMoveParent() ); // parent->world
			vecOrigin = vehicleToWorld.WorldToLocal( vecOrigin );

			if ( vecOrigin != GetLocalOrigin() )
			{
				Teleport( &vecOrigin, NULL, NULL );
			}
		}
	}
	*/

	// Do nothing while we're being placed
	if ( IsPlacing() )
	{
		for ( int i=0; i < m_aRopes.Count(); i++ )
		{
			if ( m_aRopes[i].Get() )
				m_aRopes[i]->SetupHangDistance( ROPE_HANG_DIST );
		}

		return;
	}

	// If we're deteriorating, keep going
	if ( IsDeteriorating() )
	{
		DeterioratingThink();
	}

	// If we're building, keep going
	if ( IsBuilding() )
	{
		BuildingThink();
		return;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CBaseTFPlayer *CBaseObject::GetOwner()
{ 
	return m_hBuilder; 
}


//-----------------------------------------------------------------------------
// Do we have to be built in a resource zone?
//-----------------------------------------------------------------------------
bool CBaseObject::MustBeBuiltInResourceZone( void ) const
{
	return (m_fObjectFlags & OF_MUST_BE_BUILT_IN_RESOURCE_ZONE) != 0;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseObject::MustBeBuiltInConstructionYard( ) const
{
	return (m_fObjectFlags & OF_MUST_BE_BUILT_IN_CONSTRUCTION_YARD) != 0;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseObject::MustNotBeBuiltInConstructionYard( void ) const
{
	return !MustBeBuiltInConstructionYard();
}

//-----------------------------------------------------------------------------
// Do we have to be built on an attachment point
//-----------------------------------------------------------------------------
bool CBaseObject::MustBeBuiltOnAttachmentPoint( void ) const
{
	return (m_fObjectFlags & OF_MUST_BE_BUILT_ON_ATTACHMENT) != 0;
}

//-----------------------------------------------------------------------------
// Purpose: Map placed objects need to setup here.
//-----------------------------------------------------------------------------
void CBaseObject::InitializeMapPlacedObject( void )
{
	m_bWasMapPlaced = true;
	m_fObjectFlags |= OF_CANNOT_BE_DISMANTLED;

	// If a map-placed object spawns child objects with their own control
	// panels, all of this lovely code will already have been run
	if (m_hBuiltOnEntity.Get())
		return;

	SetBuilder( NULL );

	// NOTE: We must spawn the control panels now, instead of during
	// Spawn, because until placement is started, we don't actually know
	// the position of the control panel because we don't know what it's
	// been attached to (could be a vehicle which supplies a different
	// place for the control panel)

	if ( !(m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) )
	{
		SpawnControlPanels();
	}

	SetHealth( GetMaxHealth() );

	AlignToGround( GetAbsOrigin() );
	FinishedBuilding();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::Activate( void )
{
	BaseClass::Activate();

	// Add myself to the team
	InitializeMapPlacedObject();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::SetBuilder( CBaseTFPlayer *pBuilder, bool moveobjects )
{
	TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::SetBuilder builder %s, moveobjects == %s\n", gpGlobals->curtime, 
		pBuilder ? STRING( pBuilder->pl.netname ) : "NULL", 
		moveobjects ? "true" : "false" ) );

	ChangeBuilder( pBuilder, moveobjects );
}


//-----------------------------------------------------------------------------
// Called when the builder rotates this object...
//-----------------------------------------------------------------------------
void CBaseObject::ObjectMoved( )
{
	NetworkStateChanged( );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int	CBaseObject::ObjectType( ) const
{
	return m_iObjectType;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::SetObjectCollisionBox( void )
{
	if ( !pev )
		return;

	// Objects never rotate from their original position, so don't compute a box big enough to hold rotation (i.e. pitch)
	Vector vecWorldMins, vecWorldMaxs;
	WorldSpaceAABB( &vecWorldMins, &vecWorldMaxs );
	vecWorldMins -= Vector( 1, 1, 1 );
	vecWorldMaxs += Vector( 1, 1, 1 );
	SetAbsMins( vecWorldMins );
	SetAbsMaxs( vecWorldMaxs );
}


//-----------------------------------------------------------------------------
// Purpose: Remove this object from it's team and mark for deletion
//-----------------------------------------------------------------------------
void CBaseObject::DestroyObject( void )
{
	TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::DestroyObject %p:%s\n", gpGlobals->curtime, this, GetClassname() ) );

	COrderEvent_ObjectDestroyed order( this );
	GlobalOrderEvent( &order );

	if ( GetBuilder() )
	{
		GetBuilder()->OwnedObjectDestroyed( this );
	}

	// Tell my powerpack that I'm gone
	if ( m_hPowerPack != NULL )
	{
		m_hPowerPack->UnPowerObject( this );
	}

	// Tell my power up source that I have been destroyed.
	if ( GetBuffStation() )
	{
		GetBuffStation()->DeBuffObject( this );
	}

	// Detach all my ropes
	int i;
	for ( i = 0; i < m_aRopes.Size(); i++ )
	{
		if ( m_aRopes[i] )
		{
			m_aRopes[i]->DieAtNextRest();
		}
	}

	UTIL_Remove( this );

	// Kill the control panels
	for ( i = m_hScreens.Count(); --i >= 0; )
	{
		DestroyVGuiScreen( m_hScreens[i].Get() );
	}
	m_hScreens.RemoveAll();
}

//-----------------------------------------------------------------------------
// Purpose: My builder's switched class/team, so start deteriorating.
//-----------------------------------------------------------------------------
void CBaseObject::StartDeteriorating( void )
{
	if ( tf_fastbuild.GetInt() )
		return;

	m_bDeteriorating = true;
	m_flStartedDeterioratingAt = gpGlobals->curtime;
	SetBuilder( NULL, true );
	NetworkStateChanged();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::StopDeteriorating( void )
{
	m_bDeteriorating = false;
	NetworkStateChanged();
}

//-----------------------------------------------------------------------------
// Purpose: Continue deterioration of this object
//-----------------------------------------------------------------------------
void CBaseObject::DeterioratingThink( void )
{
	// Calculate damage. The longer we've lasted, the faster we should go.
	float flDamage;
	float flDeteriorationTime = (gpGlobals->curtime - m_flStartedDeterioratingAt);
	// If we've lasted less than the base time, we want to take the base time to die
	flDamage = 0.1 * ( GetMaxHealth() / object_deterioration_time.GetFloat() ) * ceil(flDeteriorationTime / object_deterioration_time.GetFloat());
	// Hax0r the damage to get around the object damage reduction
	if ( obj_damage_factor.GetFloat() )
	{
		flDamage *= 1 / obj_damage_factor.GetFloat();
	}

	// Apply the damage
	OnTakeDamage( CTakeDamageInfo( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), flDamage, DMG_GENERIC ) );
}

//-----------------------------------------------------------------------------
// Purpose: Get the total time it will take to build this object
//-----------------------------------------------------------------------------
float CBaseObject::GetTotalTime( void )
{
	if (tf_fastbuild.GetInt())
		return 2.f;

	// If it's in a construction yard, don't take more than 5 seconds to build
  	if ( PointInConstructionYard( GetAbsOrigin() ) )
  	{
		if ( GetObjectInfo( ObjectType() )->m_flBuildTime > 5.0 )
  			return 5.0;
  	}

	return GetObjectInfo( ObjectType() )->m_flBuildTime;
}

//-----------------------------------------------------------------------------
// Purpose: Start placing the object
//-----------------------------------------------------------------------------
void CBaseObject::StartPlacement( CBaseTFPlayer *pPlayer )
{
	NetworkStateChanged();
	AddSolidFlags( FSOLID_NOT_SOLID );

	m_bPlacing = true;
	m_bBuilding = false;
	if ( pPlayer )
	{
		SetBuilder( pPlayer );
		ChangeTeam( pPlayer->GetTeamNumber() );
	}

	// Make it semi-transparent
	m_nRenderMode = kRenderTransAlpha; 
	SetRenderColorA( 128 );

	Relink();

	// Set my build size
	WorldSpaceAABB( &m_vecBuildMins, &m_vecBuildMaxs );
	m_vecBuildMins -= Vector( 4,4,0 );
	m_vecBuildMaxs += Vector( 4,4,0 );
	m_vecBuildMins -= GetAbsOrigin();
	m_vecBuildMaxs -= GetAbsOrigin();
}

//-----------------------------------------------------------------------------
// Purpose: Stop placing the object
//-----------------------------------------------------------------------------
void CBaseObject::StopPlacement( void )
{
	UTIL_Remove( this );
}

//-----------------------------------------------------------------------------
// Purpose: Find the nearest buildpoint on the specified entity
//-----------------------------------------------------------------------------
bool CBaseObject::FindNearestBuildPoint( CBaseEntity *pEntity, Vector vecBuildOrigin, float &flNearestPoint, Vector &vecNearestBuildPoint )
{
	bool bFoundPoint = false;

	IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(pEntity);
	Assert( pBPInterface );

	// Any empty buildpoints?
	for ( int i = 0; i < pBPInterface->GetNumBuildPoints(); i++ )
	{
		// Can this object build on this point?
		if ( pBPInterface->CanBuildObjectOnBuildPoint( i, GetType() ) )
		{
			// Close to this point?
			Vector vecBPOrigin;
			QAngle vecBPAngles;
			if ( pBPInterface->GetBuildPoint(i, vecBPOrigin, vecBPAngles) )
			{
				float flDist = (vecBPOrigin - vecBuildOrigin).Length();
				if ( flDist < min(flNearestPoint, pBPInterface->GetMaxSnapDistance( i )) )
				{
					flNearestPoint = flDist;
					vecNearestBuildPoint = vecBPOrigin;
					m_hBuiltOnEntity = pEntity;
					m_iBuiltOnPoint = i;

					// Set our angles to the buildpoint's angles
					SetAbsAngles( vecBPAngles );

					bFoundPoint = true;
				}
			}
		}
	}

	return bFoundPoint;
}

//-----------------------------------------------------------------------------
// Purpose: Calculate the placement model's position
//-----------------------------------------------------------------------------
bool CBaseObject::CalculatePlacement( CBaseTFPlayer *pPlayer )
{
	// Calculate build position
	Vector forward;
	QAngle vecAngles = vec3_angle;
	vecAngles.y = pPlayer->EyeAngles().y;
	SetLocalAngles( vecAngles );
	AngleVectors(vecAngles, &forward );

	// Adjust build distance based upon object size
	Vector2D xyDims;
	xyDims.x = max( fabs( m_vecBuildMins.x ), fabs( m_vecBuildMaxs.x ) );
	xyDims.y = max( fabs( m_vecBuildMins.y ), fabs( m_vecBuildMaxs.y ) );
	float flDistance = xyDims.Length() + 16; // small safety buffer
	Vector vecBuildOrigin = pPlayer->WorldSpaceCenter() + forward * flDistance;

	bool bSnappedToPoint = false;
	bool bShouldAttachToParent = false;

	// See if there are any nearby build positions to snap to
	Vector vecNearestBuildPoint = vec3_origin;
	float flNearestPoint = 9999;
	// First, look for nearby buildpoints on other objects
	for ( int i = 0; i < GetTFTeam()->GetNumObjects(); i++ )
	{
		CBaseObject *pObject = GetTFTeam()->GetObject(i);
		if ( pObject && !pObject->IsPlacing() )
		{
			if ( FindNearestBuildPoint( pObject, vecBuildOrigin, flNearestPoint, vecNearestBuildPoint ) )
			{
				bSnappedToPoint = true;

				// If I'm a vehicle, I'm being built on an MCV. Don't attach to the parent.
				if ( ShouldAttachToParent() )
				{
					bShouldAttachToParent = true;
				}
			}
		}
	}

	// If we're a vehicle, look for vehicle build points
	if ( IsAVehicle() )
	{
		CBaseEntity *pEntity = NULL;
		while ((pEntity = gEntList.FindEntityByClassname( pEntity, "info_vehicle_bay" )) != NULL)
		{
			if ( FindNearestBuildPoint( pEntity, vecBuildOrigin, flNearestPoint, vecNearestBuildPoint ) )
			{
				bSnappedToPoint = true;
			}
		}
	}

	// Check for resource zones for resource pumps
	if ( GetType() == OBJ_RESOURCEPUMP )
	{
		CBaseEntity *pEntity = NULL;
		while ((pEntity = gEntList.FindEntityByClassname( pEntity, "trigger_resourcezone" )) != NULL)
		{
			if ( FindNearestBuildPoint( pEntity, vecBuildOrigin, flNearestPoint, vecNearestBuildPoint ) )
			{
				bSnappedToPoint = true;
			}
		}
	}

	// See if there's any mapdefined build points near me
	int iCount = g_MapDefinedBuildPoints.Count();
	for ( i = 0; i < iCount; i++ )
	{
		if ( !InSameTeam(g_MapDefinedBuildPoints[i]) )
			continue;

		if ( FindNearestBuildPoint( g_MapDefinedBuildPoints[i], vecBuildOrigin, flNearestPoint, vecNearestBuildPoint ) )
		{
			bSnappedToPoint = true;
		}
	}

	// Upgrades become invisible if the player's not attaching them to a snap pint
	if ( IsAnUpgrade() )
	{
		if ( MustBeBuiltOnAttachmentPoint() && !bSnappedToPoint )
		{
			m_fEffects |= EF_NODRAW;
			return false;
		}
		else
		{
			m_fEffects &= ~EF_NODRAW;
		}
	}

	// Did we find a snap point?
	if ( bSnappedToPoint )
	{
		if ( bShouldAttachToParent )
		{
			AttachObjectToObject( m_hBuiltOnEntity.Get(), m_iBuiltOnPoint, vecNearestBuildPoint );
		}

		return CheckBuildOrigin( pPlayer, vecNearestBuildPoint, true );
	}

	// Clear out previous parent 
	if ( m_hBuiltOnEntity.Get() )
	{
		m_hBuiltOnEntity = NULL;
		m_iBuiltOnPoint = 0;
		SetParent( NULL );

		SetupUnattachedVersion();
	}

	// Check the build position
	return CheckBuildOrigin( pPlayer, vecBuildOrigin, false );
}


bool CBaseObject::VerifyCorner( const Vector &vBottomCenter, float xOffset, float yOffset )
{
	Vector vStart( vBottomCenter.x + xOffset, vBottomCenter.y + yOffset, vBottomCenter.z );
	
	trace_t tr;
	UTIL_TraceLine( 
		vStart, 
		vStart - Vector( 0, 0, tf_obj_ground_clearance.GetFloat() ), 
		MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
	
	return !tr.startsolid && tr.fraction < 1;
}


//-----------------------------------------------------------------------------
// Purpose: Check under a build point to ensure it's buildable on
//-----------------------------------------------------------------------------
bool CBaseObject::CheckBuildPoint( Vector vecPoint, Vector &vecTrace, Vector *vecOutPoint )
{
	trace_t tr;

	bool bClear = true;
	Vector vecEnd;

	// Ensure that this point isn't in a no-build zone:
	if( !tf_fastbuild.GetInt() && NoBuildPreventsBuild(this, vecPoint ) )
		bClear = false;

	// If the point isn't in solid, trace down until we find the ground
	if ( enginetrace->GetPointContents( vecPoint ) == CONTENTS_EMPTY )
	{
		vecEnd = vecPoint - vecTrace;
		UTIL_TraceLine( vecPoint, vecEnd, MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr);

		// Can't find ground to build on?
		if ( tr.fraction == 1.0 )
		{
			bClear = false;
		}
	} 
	else
	{
		// If the point's solid, trace up until we find empty air
		vecEnd = vecPoint + vecTrace;
		UTIL_TraceLine( vecPoint, vecEnd, MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr);

		// Can't find ground to build on?
		if ( tr.allsolid )
		{
			bClear = false;
		}
	}

	// FIXME: HACK! This is a test to try to make mud non-buildable!!
	surfacedata_t *pSurfaceProp = physprops->GetSurfaceData( tr.surface.surfaceProps );
	if (pSurfaceProp->maxSpeedFactor < 1.0f)
		bClear = false;

	if ( vecOutPoint )
	{
		*vecOutPoint = tr.endpos;
	}

	return bClear;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------

bool CBaseObject::CheckBuildOrigin( CBaseTFPlayer *pPlayer, const Vector &vecInitialBuildOrigin, bool bSnappedToPoint )
{
	// By default, use the vecBuildOrigin..
	bool bResult = true;
	m_vecBuildOrigin = vecInitialBuildOrigin;
	Vector vErrorOrigin = vecInitialBuildOrigin - (m_vecBuildMaxs - m_vecBuildMins) * 0.5f - m_vecBuildMins;

	// If we're snapping to a build point, don't bother performing area checks
	if ( !bSnappedToPoint )
	{
		Vector vBuildDims = m_vecBuildMaxs - m_vecBuildMins;
		Vector vHalfBuildDims = vBuildDims * 0.5;
		Vector vHalfBuildDimsXY( vHalfBuildDims.x, vHalfBuildDims.y, 0 );

		// Here, we start at the highest Z we'll allow for the top of the object. Then
		// we sweep an XY cross section downwards until it hits the ground.
		//
		// The rule is that the top of to box can't go lower than the player's feet, and the bottom of the
		// box can't go higher than the player's head.
		//
		// To simplify things in here, we treat the box as though it's symmetrical about all axes
		// (so mins = -maxs), then reoffset the box at the very end.
		Vector vHalfPlayerDims = (pPlayer->WorldAlignMaxs() - pPlayer->WorldAlignMins()) * 0.5f;
		float flBoxTopZ = pPlayer->WorldSpaceCenter().z + vHalfPlayerDims.z + vBuildDims.z;
		float flBoxBottomZ = pPlayer->WorldSpaceCenter().z - vHalfPlayerDims.z - vBuildDims.z;
		
		// First, find the ground (ie: where the bottom of the box goes).
		trace_t tr;
		float bottomZ = 0;
		int nIterations = 6;
		float topZ = flBoxTopZ;
		float topZInc = (flBoxBottomZ - flBoxTopZ) / (nIterations-1);
		for ( int iIteration = 0; iIteration < nIterations; iIteration++ )
		{
			UTIL_TraceHull( 
				Vector( m_vecBuildOrigin.x, m_vecBuildOrigin.y, topZ ), 
				Vector( m_vecBuildOrigin.x, m_vecBuildOrigin.y, flBoxBottomZ ), 
				-vHalfBuildDimsXY, vHalfBuildDimsXY, MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
			bottomZ = tr.endpos.z;

			// If there is no ground, then we can't place here.
			if ( tr.fraction == 1 )
			{
				m_vecBuildOrigin = vErrorOrigin;
				return false;
			}

			// If it started in solid, keep moving down.
			// Note that a working CGameTrace::fractionleftsolid would make this trivial, but it isn't
			// working now so we must resort to rubitry.
			if ( !tr.startsolid )
				break;

			topZ += topZInc;
		}
		
		if ( iIteration == nIterations )
		{
			m_vecBuildOrigin = vErrorOrigin;
			return false;
		}


		// Now see if the range we've got leaves us room for our box.
		if ( topZ - bottomZ < vBuildDims.z )
		{
			m_vecBuildOrigin = vErrorOrigin;
			return false;
		}

		// Verify that it's not on too much of a slope by seeing how far the corners are from the ground.
		Vector vBottomCenter( m_vecBuildOrigin.x, m_vecBuildOrigin.y, bottomZ );
		if ( !VerifyCorner( vBottomCenter, -vHalfBuildDims.x, -vHalfBuildDims.y ) ||
			 !VerifyCorner( vBottomCenter, +vHalfBuildDims.x, +vHalfBuildDims.y ) ||
			 !VerifyCorner( vBottomCenter, +vHalfBuildDims.x, -vHalfBuildDims.y ) ||
			 !VerifyCorner( vBottomCenter, -vHalfBuildDims.x, +vHalfBuildDims.y ) )
		{
			m_vecBuildOrigin = vErrorOrigin;
			return false;
		}

		// Ok, now we know the Z range where this box can fit.
		Vector vBottomLeft = m_vecBuildOrigin - vHalfBuildDims;
		vBottomLeft.z = bottomZ;
		m_vecBuildOrigin = vBottomLeft - m_vecBuildMins;
	}

	Vector vecForward, vecRight, vecUp;
	AngleVectors( GetLocalAngles(), &vecForward, &vecRight, &vecUp );
	AttemptToFindPower();
	AttemptToFindBuffStation();

	// Make sure construction yards don't screw us up (tf_fastbuild allows builds anywhere)
	if ( !tf_fastbuild.GetInt() && ConstructionYardPreventsBuild( this, m_vecBuildOrigin ))
		return false;

	// If we have to be attached to something, and we're not, abort
	if ( !tf_fastbuild.GetInt() && MustBeBuiltOnAttachmentPoint() && !bSnappedToPoint )
		return false;

	// Make sure there aren't any solid objects in the area
	if ( !bSnappedToPoint || IsAVehicle() )
	{
		if ( !(m_fObjectFlags & OF_DONT_PREVENT_BUILD_NEAR_OBJ) )
		{
			// Get a list of nearby entities
			CBaseEntity *pListOfNearbyEntities[100];
			int iNumberOfNearbyEntities = UTIL_EntitiesInSphere( pListOfNearbyEntities, 100, m_vecBuildOrigin, GetNearbyObjectCheckRadius(), 0 );
			for ( int i = 0; i < iNumberOfNearbyEntities; i++ )
			{
				CBaseEntity *pEntity = pListOfNearbyEntities[i];
				if ( pEntity->IsSolid( ) )
				{
					// Ignore shields..
					if ( pEntity->GetCollisionGroup() == TFCOLLISION_GROUP_SHIELD )
						continue;

					// Ignore func brushes
					// BUGBUG: Shouldn't this test against MOVETYPE_PUSH instead of SOLID_BSP?
					if ( pEntity->GetSolid() == SOLID_BSP )
						continue;

					// Ignore the player who's building
					if ( pEntity == GetBuilder() )
						continue;

					// YWB:  Ignore other players
					if ( pEntity->IsPlayer() )
						continue;

					// Ignore map placed objects
					if ( pEntity->GetTeamNumber() == 0 )
						continue;
				
					//NDebugOverlay::EntityBounds( pEntity, 0,255,0,8, 0.1 );
					//NDebugOverlay::Box( pEntity->GetAbsOrigin(), pEntity->GetAbsMins() - pEntity->GetAbsOrigin(), pEntity->GetAbsMaxs() - pEntity->GetAbsOrigin(), 255,0,0,8, 0.1 );
					return false;
				}

				// Sentryguns may be turtled, and non-solid
				if ( pEntity->Classify() == CLASS_MILITARY )
				{
					CObjectSentrygun *pSentry = dynamic_cast<CObjectSentrygun*>(pEntity);
					if ( pSentry && pSentry->IsTurtled() )
						return false;
				}
			}
		}
	}

	if ( !bSnappedToPoint )
	{
		AlignToGround( m_vecBuildOrigin );
	}

	return bResult;
}

//-----------------------------------------------------------------------------
// Purpose: Align myself to the ground below the specified point
//-----------------------------------------------------------------------------
void CBaseObject::AlignToGround( Vector vecOrigin )
{
	if ( !(m_fObjectFlags & OF_ALIGN_TO_GROUND) )
		return;

	trace_t tr;
	Vector vecWorldMins, vecWorldMaxs;
	WorldSpaceAABB( &vecWorldMins, &vecWorldMaxs );
	float flHeight = max( vecWorldMaxs.z - vecWorldMins.z, 60 );
	UTIL_TraceLine( vecOrigin, vecOrigin + Vector(0,0,-flHeight), MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
	if ( tr.fraction != 1.0 )
	{
		// Orient the *up* axis to be along the plane normal
		Vector perp( 1, 0, 0 );
		Vector forward, right;
		CrossProduct( perp, tr.plane.normal, forward );
		if (forward.LengthSqr() < 0.1f)
		{
			perp.Init( 0, 1, 0 );
			CrossProduct( perp, tr.plane.normal, forward );
		}
		VectorNormalize( forward );
		CrossProduct( tr.plane.normal, forward, right );

		VMatrix orientation( forward, right, tr.plane.normal );

		QAngle angles;
		MatrixToAngles( orientation, angles );
		SetAbsAngles( angles );
	}
}

//-----------------------------------------------------------------------------
// Exit points for mounted vehicles....
//-----------------------------------------------------------------------------
void CBaseObject::GetExitPoint( CBaseEntity *pPlayer, int nBuildPoint, Vector *pAbsPosition, QAngle *pAbsAngles )
{
	// Deal with hierarchy...
	IHasBuildPoints *pMount = dynamic_cast<IHasBuildPoints*>(GetMoveParent());
	if (pMount)
	{
		int nBuildPoint = pMount->FindObjectOnBuildPoint( this );
		if (nBuildPoint >= 0)
		{
			pMount->GetExitPoint( pPlayer, nBuildPoint, pAbsPosition, pAbsAngles );
			return;
		}
	}

	// FIXME: In future, we may well want to use specific exit attachments here...
	GetBuildPoint( nBuildPoint, *pAbsPosition, *pAbsAngles );

	// Move back along the forward direction a bit...
	Vector vecForward, vecUp;
	AngleVectors( *pAbsAngles, &vecForward, NULL, &vecUp );
	*pAbsPosition -= vecForward * 60;
	*pAbsPosition += vecUp * 30;

	// Now select a good spot to drop onto
	Vector vNewPos;
	if ( !EntityPlacementTest(pPlayer, *pAbsPosition, vNewPos, true) )
	{
		Warning("Can't find valid place to exit object.\n");
		return;
	}

	*pAbsPosition = vNewPos;
}


void CBaseObject::AdjustInitialBuildAngles()
{
}


//-----------------------------------------------------------------------------
// Purpose: Try and find power for this object during placement
//-----------------------------------------------------------------------------
void CBaseObject::AttemptToFindPower( void )
{
	// Human objects need power, so show the player if the current position will have power, but don't prevent building.
	if ( !CanPowerupEver( POWERUP_POWER ) )
		return;

	// If I have a powerpack, see if I'm unable to keep power, or not needed.
	// This is done before checking to see if the object needs power, because it may
	// have once needed power, but doesn't anymore (i.e. snapped to an attachment point)
	if ( m_hPowerPack )
	{
		m_hPowerPack->EnsureObjectPower( this );
	}

	// If I don't have a powerpack, or I just moved too far from it, look for a powerpack
	if ( !m_hPowerPack )
	{
		GetTFTeam()->UpdatePowerpacks( NULL, this );
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseObject::AttemptToFindBuffStation( void )
{
	// Check to see if this object can be connected to a buff station.
	if ( !CanBeHookedToBuffStation() )
		return;

	// We have already found a buff station, we want to use - check distances.
	if ( GetBuffStation() )
	{
		GetBuffStation()->CheckBuffConnection( this );
	}
	// Look for a buff station to use.
	else
	{
		GetTFTeam()->UpdateBuffStations( NULL, this, true );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Move the placement model to the current position. Return false if it's an invalid position
//-----------------------------------------------------------------------------
bool CBaseObject::UpdatePlacement( CBaseTFPlayer *pPlayer )
{
	bool placementOk = CalculatePlacement( pPlayer );
	if ( placementOk )
	{
		SetRenderColor( 255, 255, 255, GetRenderColor().a );
	}
	else
	{
		SetRenderColor( 255, 0, 0, GetRenderColor().a );
	}

	Teleport( &m_vecBuildOrigin, &GetLocalAngles(), NULL );

	return placementOk;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseObject::PreStartBuilding()
{
#if 0
	// This was part of the morph test.  We aren't currently doing anything
	// with it, but we may at some point.  

	// Morph the terrain underneath the object.
	CTerrainModParams morphParams;

	Vector vecCenter = GetAbsOrigin();
	morphParams.m_vCenter = vecCenter;

	Vector vecMorphMin, vecMorphMax;
	if ( VPhysicsGetObject() )
	{
		physcollision->CollideGetAABB( vecMorphMin, vecMorphMax, VPhysicsGetObject()->GetCollide(), GetAbsOrigin(), GetAbsAngles() );
	}

	vecMorphMin -= Vector( 75.0f, 75.0f, 300.0f );
	vecMorphMax += Vector( 75.0f, 75.0f, 0.0f );
	morphParams.m_vecMin = vecMorphMin;
	morphParams.m_vecMax = vecMorphMax;

	// Apply the TMod_AABB					
	TerrainMod_Apply( TMod_AABB, morphParams );
#endif

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Start building the object
//-----------------------------------------------------------------------------
bool CBaseObject::StartBuilding( CBaseEntity *pBuilder )
{
	// Need to add the object to the team now...
	CTFTeam *pTFTeam = ( CTFTeam * )GetGlobalTeam( GetTeamNumber() );

	// Deduct the cost from the player
	if ( pBuilder && pBuilder->IsPlayer() )
	{
		m_iAmountPlayerPaidForMe = ((CBaseTFPlayer*)pBuilder)->StartedBuildingObject( m_iObjectType );
		if ( !m_iAmountPlayerPaidForMe )
		{
			// Player couldn't afford to pay for me, so abort
			ClientPrint( (CBasePlayer*)pBuilder, HUD_PRINTCENTER, "Not enough resources.\n" );
			StopPlacement();
			return false;
		}
	}
	
	// Add this object to the team's list (because we couldn't add it during
	// placement mode)
	if ( pTFTeam && !pTFTeam->IsObjectOnTeam( this ) )
	{
		pTFTeam->AddObject( this );
	}

	m_bPlacing = false;
	m_bBuilding = true;
	SetHealth( OBJECT_CONSTRUCTION_STARTINGHEALTH );
	m_flPercentageConstructed = 0;

	NetworkStateChanged();

	// Compute a good fitting AABB since we know where this thing belongs
	if ( VPhysicsGetObject() && !IsBuiltOnAttachment() )
	{
		Vector absmins, absmaxs;		
		physcollision->CollideGetAABB( absmins, absmaxs, VPhysicsGetObject()->GetCollide(), GetAbsOrigin(), GetAbsAngles() );

		// This is required to get the client + server looking the same
		// since the client uses the mins to compute absmins + absmaxs
		SetCollisionBounds( absmins - GetAbsOrigin(), absmaxs - GetAbsOrigin() );
	}

	m_nRenderMode = kRenderNormal; 
	RemoveSolidFlags( FSOLID_NOT_SOLID );
	Relink();

	// NOTE: We must spawn the control panels now, instead of during
	// Spawn, because until placement is started, we don't actually know
	// the position of the control panel because we don't know what it's
	// been attached to (could be a vehicle which supplies a different
	// place for the control panel)
	// NOTE: We must also spawn it before FinishedBuilding can be called
	SpawnControlPanels();

	// Tell the object we've been built on that we exist
	if ( IsBuiltOnAttachment() && ShouldAttachToParent() )
	{
		IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>((CBaseEntity*)m_hBuiltOnEntity.Get());
		Assert( pBPInterface );
		pBPInterface->SetObjectOnBuildPoint( m_iBuiltOnPoint, this );
	}

	// Start the build animations
	m_flTotalConstructionTime = m_flConstructionTimeLeft = GetTotalTime();

	if ( pBuilder && pBuilder->IsPlayer() )
	{
		((CBaseTFPlayer*)pBuilder)->FinishedObject( this );
	}

	m_vecBuildOrigin = GetAbsOrigin();

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Continue construction of this object
//-----------------------------------------------------------------------------
void CBaseObject::BuildingThink( void )
{
	// Continue construction
	Repair( (GetMaxHealth() - OBJECT_CONSTRUCTION_STARTINGHEALTH) / m_flTotalConstructionTime * OBJECT_CONSTRUCTION_INTERVAL );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::AttemptToActivateBuffStation( void )
{
	if ( !GetBuffStation() )
		return;

	if ( GetBuffStation()->IsPlacing() || GetBuffStation()->IsBuilding() ||
		 !GetBuffStation()->IsPowered() )
		return;

	if ( m_bBuffActivated )
		return;

	BuffStationActivate();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::SetControlPanelsActive( bool bState )
{
	// Activate control panel screens
	for ( int i = m_hScreens.Count(); --i >= 0; )
	{
		if (m_hScreens[i].Get())
		{
			m_hScreens[i]->SetActive( bState );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::FinishedBuilding( void )
{
	SetControlPanelsActive( true );

	// Only make a shadow if the object doesn't use vphysics
	if (!VPhysicsGetObject())
	{
		VPhysicsInitStatic();
	}

	m_bBuilding = false;

	AttemptToGoActive();
	AttemptToActivateBuffStation();

	// We're done building, add in the stat...
	TFStats()->IncrementStat( (TFStatId_t)(TF_STAT_FIRST_OBJECT_BUILT + ObjectType()), 1 );

	// Spawn any objects on this one
	SpawnObjectPoints();

	// Let our vehicle bay know, if we have one
	if ( m_hVehicleBay )
	{
		m_hVehicleBay->FinishedBuildVehicle( this );
	}

	NetworkStateChanged();
}

//-----------------------------------------------------------------------------
// Purpose: Objects store health in hacky ways
//-----------------------------------------------------------------------------
void CBaseObject::SetHealth( float flHealth )
{
	if ( IsDisabled() )
	{
		if ( ( m_flMinDisableHealth != 0.0f && flHealth > m_flMinDisableHealth ) ||
			 ( flHealth > 1 ) )
		{
			// Reenable and fire output
			SetDisabled( false );

			m_OnBecomingReenabled.FireOutput( this, this );
		}
	}

	bool changed = m_flHealth != flHealth;

	m_flHealth = flHealth;
	m_iHealth = ceil(m_flHealth);

	// If we have a model, and a pose parameter, set the pose parameter to reflect our health
	if ( !(m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) )
	{
		if (LookupPoseParameter( "object_health") >= 0 && GetMaxHealth() > 0 )
		{
			SetPoseParameter( "object_health", 100 * ( GetHealth() / (float)GetMaxHealth() ) );
		}
	}

	if ( changed )
	{
		// Set value and fire output
		m_OnObjectHealthChanged.Set( m_flHealth, this, this );
	}

	NetworkStateChanged();
}

//-----------------------------------------------------------------------------
// Purpose: Powerup has just started
//-----------------------------------------------------------------------------
void CBaseObject::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier )
{
	switch( iPowerup )
	{
	case POWERUP_BOOST:
		{
			// Can we boost health further?
			if ( GetHealth() < GetMaxHealth() )
			{
				/*
				if ( (gpGlobals->curtime - m_flLastRepairTime) > 0.01 )
				{
					Msg("TOTAL REPAIR: %.2f in %.2f\n\n", m_flRepairedSinceLastTime, (gpGlobals->curtime - m_flLastRepairTime) );
				}

				if ( pAttacker->IsPlayer() )
				{
					CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pAttacker;
					Msg("(%.2f) %s repaired %s for %.2f health.\n", gpGlobals->curtime, STRING(pPlayer->pl.netname), GetClassname(), flAmount );
				}
				*/
				// Is this repair happening at the same time as other repairing on me?
				if ( (gpGlobals->curtime - m_flLastRepairTime) < 0.01 )
				{
					//Msg("  ->Reducing repair by %.2f\n", m_flNextRepairMultiplier );
					flAmount *= m_flNextRepairMultiplier;
					m_flNextRepairMultiplier *= 0.5;
				}
				else
				{
					m_flLastRepairTime = gpGlobals->curtime;
					m_flNextRepairMultiplier = 0.5;
					m_flRepairedSinceLastTime = 0;
				}

				//Msg("  REPAIRED: %.2f\n", flAmount );

				Repair( flAmount );

				m_flRepairedSinceLastTime += flAmount;
			}

			// Prevent callback to base class, since we handled it here
			return;
		}
		break;

	case POWERUP_POWER:
		{
			Assert( m_hPowerPack );
			AttemptToGoActive();
		}
		break;

	default:
		break;
	}

	BaseClass::PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier );
}

//-----------------------------------------------------------------------------
// Purpose: Powerup has just started
//-----------------------------------------------------------------------------
void CBaseObject::PowerupEnd( int iPowerup )
{
	switch( iPowerup )
	{
	case POWERUP_POWER:
		{
			OnGoInactive();
			m_hPowerPack = NULL;
		}
		break;

	default:
		break;
	}

	BaseClass::PowerupEnd( iPowerup );
}

//-----------------------------------------------------------------------------
// Purpose: Override base traceattack to prevent visible effects from team members shooting me
//-----------------------------------------------------------------------------
void CBaseObject::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr )
{
	// Prevent team damage here so blood doesn't appear
	if ( inputInfo.GetAttacker() )
	{
		if ( InSameTeam(inputInfo.GetAttacker()) )
			return;
	}

	float fVulnerableMultiplier = FindVulnerablePointMultiplier( ptr->hitgroup, ptr->hitbox ); 

	CTakeDamageInfo info = inputInfo;
	info.ScaleDamage( fVulnerableMultiplier );

	SpawnBlood( ptr->endpos, BloodColor(), info.GetDamage() );
	AddMultiDamage( info, this );
}

//-----------------------------------------------------------------------------
// Purpose: Prevent Team Damage
//-----------------------------------------------------------------------------
ConVar object_show_damage( "obj_show_damage", "0", 0, "Show all damage taken by objects." );
ConVar object_capture_damage( "obj_capture_damage", "0", 0, "Captures all damage taken by objects for dumping later." );

CUtlDict<int,int> g_DamageMap;

void Cmd_DamageDump_f(void)
{	
	CUtlDict<bool,int> g_UniqueColumns;

	// Build the unique columns:
	for( int idx = g_DamageMap.First(); idx != g_DamageMap.InvalidIndex(); idx = g_DamageMap.Next(idx) )
	{
		char* szColumnName = strchr(g_DamageMap.GetElementName(idx),',') + 1;

		int ColumnIdx = g_UniqueColumns.Find( szColumnName );

		if( ColumnIdx == g_UniqueColumns.InvalidIndex() ) 
		{
			g_UniqueColumns.Insert( szColumnName, false );
		}
	}

	// Dump the column names:
	FileHandle_t f = filesystem->Open("damage.txt","wt+");

	for( idx = g_UniqueColumns.First(); idx != g_UniqueColumns.InvalidIndex(); idx = g_UniqueColumns.Next(idx) )
	{
		filesystem->FPrintf(f,"\t%s",g_UniqueColumns.GetElementName(idx));
	}

	filesystem->FPrintf(f,"\n");

 
	CUtlDict<bool,int> g_CompletedRows;

	// Dump each row:
	bool bDidRow;

	do
	{
		bDidRow = false;

		for( idx = g_DamageMap.First(); idx != g_DamageMap.InvalidIndex(); idx = g_DamageMap.Next(idx) )
		{
			char szRowName[256];

			// Check the Row name of each entry to see if I've done this row yet.
			strcpy(szRowName, g_DamageMap.GetElementName(idx) );
			*strchr(szRowName,',') = '\0';

			char szRowNameComma[256];
			strcpy( szRowNameComma, szRowName );
			strcat( szRowNameComma, "," );

			if( g_CompletedRows.Find(szRowName) == g_CompletedRows.InvalidIndex() )
			{
				bDidRow = true;
				g_CompletedRows.Insert(szRowName,false);


				// Output the row name:
				filesystem->FPrintf(f,szRowName);

				for( int ColumnIdx = g_UniqueColumns.First(); ColumnIdx != g_UniqueColumns.InvalidIndex(); ColumnIdx = g_UniqueColumns.Next( ColumnIdx ) )
				{
					char szRowNameCommaColumn[256];
					strcpy( szRowNameCommaColumn, szRowNameComma );					
					strcat( szRowNameCommaColumn, g_UniqueColumns.GetElementName( ColumnIdx ) );

					int nDamageAmount = 0;
					// Fine to reuse idx since we are going to break anyways.
					for( idx = g_DamageMap.First(); idx != g_DamageMap.InvalidIndex(); idx = g_DamageMap.Next(idx) )
					{
						if( !stricmp( g_DamageMap.GetElementName(idx), szRowNameCommaColumn ) )
						{
							nDamageAmount = g_DamageMap[idx];
							break;
						}
					}

					filesystem->FPrintf(f,"\t%i",nDamageAmount);

				}

				filesystem->FPrintf(f,"\n");
				break;
			}
		}
		// Grab the row name:

	} while(bDidRow);

	// close the file:
	filesystem->Close(f);
}

static ConCommand obj_dump_damage( "obj_dump_damage", Cmd_DamageDump_f );

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void ReportDamage( const char* szInflictor, const char* szVictim, float fAmount, int nCurrent, int nMax )
{
	int iAmount = (int)fAmount;

	if( object_show_damage.GetBool() && iAmount )
	{
		Msg( "ShowDamage: Object %s taking %0.1f damage from %s ( %i / %i )\n", szVictim, fAmount, szInflictor, nCurrent, nMax );
	}

	if( object_capture_damage.GetBool() )
	{
		char szMangledKey[256];

		Q_snprintf(szMangledKey,sizeof(szMangledKey)/sizeof(szMangledKey[0]),"%s,%s",szInflictor,szVictim);
		int idx = g_DamageMap.Find( szMangledKey );

		if( idx == g_DamageMap.InvalidIndex() )
		{
			g_DamageMap.Insert( szMangledKey, iAmount );

		} else
		{
			g_DamageMap[idx] += iAmount;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Pass the specified amount of damage through to any objects I have built on me
//-----------------------------------------------------------------------------
bool CBaseObject::PassDamageOntoChildren( const CTakeDamageInfo &info, float *flDamageLeftOver )
{
	IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this);
	Assert( pBPInterface );

	float flDamage = info.GetDamage();

	// Double the amount of damage done (and get around the child damage modifier)
	flDamage *= 2;
	if ( obj_child_damage_factor.GetFloat() )
	{
		flDamage *= (1 / obj_child_damage_factor.GetFloat());
	}

	// Remove blast damage because child objects (well specifically upgrades)
	// want to ignore direct blast damage but still take damage from parent
	CTakeDamageInfo childInfo = info;
	childInfo.SetDamage( flDamage );
	childInfo.SetDamageType( info.GetDamageType() & (~DMG_BLAST) );

	CBaseEntity *pEntity = pBPInterface->GetFirstObjectOnMe();
	while ( pEntity )
	{
		Assert( pEntity->m_takedamage != DAMAGE_NO );
		// Do damage to the next object
		float flDamageTaken = pEntity->OnTakeDamage( childInfo );
		// If we didn't kill it, abort
		CBaseObject *pObject = dynamic_cast<CBaseObject*>(pEntity);
		if ( !pObject || !pObject->IsDying() )
		{
			char* szInflictor = "unknown";
			if( info.GetInflictor() )
				szInflictor = (char*)info.GetInflictor()->GetClassname();

			ReportDamage( szInflictor, GetClassname(), flDamageTaken, GetHealth(), GetMaxHealth() );

			*flDamageLeftOver = flDamage;
			return true;
		}
		// Reduce the damage and move on to the next
		flDamage -= flDamageTaken;
		pEntity = pBPInterface->GetFirstObjectOnMe();
	}

	*flDamageLeftOver = flDamage;
	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CBaseObject::OnTakeDamage( const CTakeDamageInfo &info )
{
	// Prevent damage if the game hasn't started yet
	if ( CurrentActIsAWaitingAct() )
		return 0;
	if ( !IsAlive() )
		return info.GetDamage();
	if (m_bInvulnerable)
		return 0;
	if ( m_takedamage == DAMAGE_NO )
		return 0;
	if ( IsPlacing() )
		return 0;

	// Check teams
	if ( info.GetAttacker() )
	{
		if ( InSameTeam(info.GetAttacker()) )
			return 0;
	}

	IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this);

	float flDamage = info.GetDamage();

	// Objects take half damage from bullets
	if ( info.GetDamageType() & DMG_BULLET )
	{
		flDamage *= 0.5;
	}

	// Objects build on other objects take less damage
	if ( !IsAnUpgrade() && GetParentObject() )
	{
		flDamage *= obj_child_damage_factor.GetFloat();
	}

	if (obj_damage_factor.GetFloat())
	{
		flDamage *= obj_damage_factor.GetFloat();
	}

	// Constructing objects take extra damage
	if ( IsBuilding() )
	{
		flDamage *= 3;
	}

	// If has min health, and damage would put it below min health disable it if not already disabled
	bool bShouldBeDisabled = false;
	if ( m_flMinDisableHealth != 0 && ( m_flHealth - flDamage ) < m_flMinDisableHealth )
	{
		bShouldBeDisabled = true;
	}
	else if ( pBPInterface && pBPInterface->GetNumObjectsOnMe() && (( m_flHealth - flDamage ) < 1) )
	{
		bShouldBeDisabled = true;
	}

	// Make sure we're disabled if we're supposed to be
	if ( bShouldBeDisabled )
	{
		// Remove any sappers on me
		if ( m_bCantDie )
		{
			RemoveAllSappers( this );
		}

		// Make sure this only fires first time we cross the threshold and go disabled
		if ( !IsDisabled() )
		{
			SetDisabled( true );
			m_OnBecomingDisabled.FireOutput( info.GetAttacker(), this );

			// Special case: If we have a min disabled health, and we're set to not die, immediately fall to 1 health
			if ( m_bCantDie )
			{
				SetHealth( 1 );
			}
		}
	}

	// If I have objects on me, I can't be destroyed until they're gone. Ditto if I can't be killed.
	bool bWillDieButCant = (m_bCantDie || pBPInterface->GetNumObjectsOnMe()) && (( m_flHealth - flDamage ) < 1);
	if ( bWillDieButCant )
	{
		// Soak up the damage it would take to drop us to 1 health
		flDamage = flDamage - m_flHealth;
		SetHealth( 1 );

		// Pass leftover damage 
		if ( flDamage )
		{
			if ( PassDamageOntoChildren( info, &flDamage ) )
				return flDamage;
		}
	}

	if ( flDamage )
	{
		// Recheck our death possibility, because our objects may have all been blown off us by now
		bWillDieButCant = (m_bCantDie || pBPInterface->GetNumObjectsOnMe()) && (( m_flHealth - flDamage ) < 1);
		if ( !bWillDieButCant )
		{
			// Reduce health
			SetHealth( m_flHealth - flDamage );
		}
	}

	m_OnDamaged.FireOutput(info.GetAttacker(), this);

	// Hurt by an enemy?
	if ( info.GetAttacker() && info.GetAttacker()->entindex() > 0 )
	{
		m_flLastRealDamage = gpGlobals->curtime;
	}

	if ( GetHealth() <= 0 )
	{
		if ( info.GetAttacker() )
		{
			TFStats()->IncrementTeamStat( info.GetAttacker()->GetTeamNumber(), TF_TEAM_STAT_DESTROYED_OBJECT_COUNT, 1 );
			TFStats()->IncrementPlayerStat( info.GetAttacker(), TF_PLAYER_STAT_DESTROYED_OBJECT_COUNT, 1 );
		}

		m_lifeState = LIFE_DEAD;
		m_OnDestroyed.FireOutput( info.GetAttacker(), this);
		Killed();
	}
	else
	{
		// Notify team about interesting stuff going on with this object
		if ( !(m_fObjectFlags & OF_SUPPRESS_NOTIFY_UNDER_ATTACK) && ( m_iszUnderAttackSound != NULL_STRING ) )
		{
			CTFTeam *pTeam = GetTFTeam();
			if ( pTeam )
			{
				Vector vecPosition = GetAbsOrigin();

				// Tell everyone on the team that this object's underattack
				CRecipientFilter myteam;
				myteam.MakeReliable();
				myteam.AddRecipientsByTeam( pTeam );
				UserMessageBegin( myteam, "MinimapPulse" );
					WRITE_VEC3COORD( vecPosition );
				MessageEnd();

				GetTFTeam()->PostMessage( TEAMMSG_CUSTOM_SOUND, NULL, (char*)STRING(m_iszUnderAttackSound) );
			}
		}
	}

	{
		char* szInflictor = "unknown";
		if( info.GetInflictor() )
			szInflictor = (char*)info.GetInflictor()->GetClassname();

		ReportDamage( szInflictor, GetClassname(), flDamage, GetHealth(), GetMaxHealth() );
	}

	return flDamage;
}

//-----------------------------------------------------------------------------
// Purpose: Get the time it will take to repair this object
//-----------------------------------------------------------------------------
float CBaseObject::GetRepairTime( void )
{
	// Can't be repaired while being constructed
	if ( IsBuilding() )
		return 0;

	int iRepairHealth = GetMaxHealth() - GetHealth();
	if ( iRepairHealth )
	{
		return ((float)iRepairHealth / OBJECT_REPAIR_RATE);
	}

	return 0;
}

//-----------------------------------------------------------------------------
// Purpose: Repair myself for the time period passed in. Return true if I'm fully repaired.
//-----------------------------------------------------------------------------
bool CBaseObject::UpdateRepair( float flRepairTime )
{
	return Repair( (flRepairTime * OBJECT_REPAIR_RATE) );
}

//-----------------------------------------------------------------------------
// Purpose: Repair / Help-Construct this object the specified amount
//-----------------------------------------------------------------------------
bool CBaseObject::Repair( float flHealth )
{
	// Multiply it by the repair rate
	flHealth *= m_flRepairMultiplier;
	if ( !flHealth )
		return false;

	if ( IsBuilding() )
	{
		if ( HasPowerup(POWERUP_EMP) )
			return false;

		// Reduce the construction time by the correct amount for the health passed in
		float flConstructionTime = flHealth / ((GetMaxHealth() - OBJECT_CONSTRUCTION_STARTINGHEALTH) / m_flTotalConstructionTime);
		m_flConstructionTimeLeft = max( 0, m_flConstructionTimeLeft - flConstructionTime);
		m_flConstructionTimeLeft = clamp( m_flConstructionTimeLeft, 0.0f, m_flTotalConstructionTime );
		m_flPercentageConstructed = 1 - (m_flConstructionTimeLeft / m_flTotalConstructionTime);
		m_flPercentageConstructed = clamp( m_flPercentageConstructed, 0.0f, 1.0f );

		// Increase health.
		SetHealth( min( GetMaxHealth(), m_flHealth + flHealth ) );

		// Return true if we're constructed now
		if ( m_flConstructionTimeLeft <= 0.0f )
		{
			FinishedBuilding();
			return true;
		}
	}
	else
	{
		// Return true if we're already fully healed
		if ( GetHealth() >= GetMaxHealth() )
			return true;

		// Increase health.
		SetHealth( min( GetMaxHealth(), m_flHealth + flHealth ) );

		m_OnRepaired.FireOutput( this, this);

		// Return true if we're fully healed now
		if ( GetHealth() == GetMaxHealth() )
			return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Object has been blown up. Drop resource chunks upto the value of my max health.
//-----------------------------------------------------------------------------
void CBaseObject::Killed( void )
{
	m_bDying = true;

	// Do an explosion.
	CPASFilter filter( GetAbsOrigin() );
	te->Explosion( 
		filter, 
		0.0,				
		&GetAbsOrigin(),
		g_sModelIndexFireball,
		5.4,		// radius
		15,
		TE_EXPLFLAG_NODLIGHTS,
		256,
		200);

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

	bool bDropResources = true;

	// Don't drop resources if I'm built out of brushes, or I'm an upgrade
	if ( m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL || IsAnUpgrade() )
	{
		bDropResources = false;
	}

	// Don't drop resources if I haven't taken damage from an enemy for a while (i.e. I've deteriorated instead)
	if ( gpGlobals->curtime > (m_flLastRealDamage + MAX_DROP_TIME_AFTER_DAMAGE) )
	{
		bDropResources = false;
	}

	// Drop resources based upon our base cost
	int iCost = CalculateObjectCost( GetType(), 0, GetTeamNumber() );
	iCost *= 0.5;
	if ( bDropResources && iCost )
	{
		// Convert value to chunks.
		int nProcessedChunks = 0;
		int nNormalChunks = 0;
		ConvertResourceValueToChunks( iCost, &nProcessedChunks, &nNormalChunks );

		// Make everything drop at least 1 chunk
		if ( !nProcessedChunks && !nNormalChunks )
		{
			nNormalChunks++;
		}

		// Drop processed chunks.
		int iChunk;
		for ( iChunk = 0; iChunk < nProcessedChunks; iChunk++ )
		{
			// Generate a random velocity vector.
			Vector vecVelocity = Vector( random->RandomFloat( -250,250 ), random->RandomFloat( -250,250 ), random->RandomFloat( 200,450 ) );

			// Create a processed chunk.
			CResourceChunk *pChunk = CResourceChunk::Create( true, vecOrigin, vecVelocity );
			pChunk->ChangeTeam( GetTeamNumber() );
		}

		// Drop normal chunks
		for ( iChunk = 0; iChunk < nNormalChunks; iChunk++ )
		{
			// Generate a random velocity vector.
			Vector vecVelocity = Vector( random->RandomFloat( -250,250 ), random->RandomFloat( -250,250 ), random->RandomFloat( 200,450 ) );

			// Create a processed chunk.
			CResourceChunk *pChunk = CResourceChunk::Create( false, vecOrigin, vecVelocity );
			pChunk->ChangeTeam( GetTeamNumber() );
		}

		TFStats()->IncrementTeamStat( GetTeamNumber(), TF_TEAM_STAT_RESOURCE_CHUNKS_DROPPED, (resource_chunk_processed_value.GetFloat() * nProcessedChunks) + (resource_chunk_value.GetFloat() * nNormalChunks) );
	}

	DetachObjectFromObject();

	UTIL_Remove( this );
}

//-----------------------------------------------------------------------------
// Purpose: Indicates this NPC's place in the relationship table.
//-----------------------------------------------------------------------------
Class_T	CBaseObject::Classify( void )
{
	return (CLASS_MILITARY);
}

//-----------------------------------------------------------------------------
// Purpose: Get the type of this object
//-----------------------------------------------------------------------------
int	CBaseObject::GetType()
{
	return m_iObjectType;
}

//-----------------------------------------------------------------------------
// Purpose: Get the builder of this object
//-----------------------------------------------------------------------------
CBaseTFPlayer *CBaseObject::GetBuilder( void )
{
	return m_hBuilder;
}

//-----------------------------------------------------------------------------
// Purpose: Get the original builder of this object
//			Used to get the builder of a deteriorating object
//-----------------------------------------------------------------------------
CBaseTFPlayer *CBaseObject::GetOriginalBuilder( void )
{
	return m_hOriginalBuilder;
}

//-----------------------------------------------------------------------------
// Purpose: Return true if the Owning CTeam should clean this object up automatically
//-----------------------------------------------------------------------------
bool CBaseObject::ShouldAutoRemove( void )
{
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: If the object's still being built, it's not usable
//-----------------------------------------------------------------------------
int	CBaseObject::ObjectCaps( void ) 
{ 
	if ( IsPlacing() )
		return 0;

	// If I'm being built, only allow +use if I have a sapper on me
	if ( IsBuilding() && !HasSapper() )
		return 0;

	return FCAP_ONOFF_USE; 
};

//-----------------------------------------------------------------------------
// Clean off the object of offensive material...
//-----------------------------------------------------------------------------
bool CBaseObject::RemoveEnemyAttachments( CBaseEntity *pActivator )
{
	bool bRemoved = false;

	// Sapper removal
	if ( pActivator->IsPlayer() )
	{
		if (HasSapper())
		{
			RemoveAllSappers( pActivator );
			bRemoved = true;
		}
	}

	return bRemoved;
}


//-----------------------------------------------------------------------------
// Object using!
//-----------------------------------------------------------------------------
void CBaseObject::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	// If we're friendly, pickup / remove sappers
	// If we're an enemy, plant a sapper
	if ( pActivator->IsPlayer() )
	{
		if ( InSameTeam( pActivator ) )
		{
			if ( useType == USE_ON )
			{
				// Some combat objects can be picked up
				if ( m_fObjectFlags & OF_CAN_BE_PICKED_UP )
				{
					if ( GetBuilder() == pActivator )
					{
						if ( GetBuilder()->GetPlayerClass()->ResupplyAmmoType( 1, m_szAmmoName ) )
						{
							PickupObject();
						}
						return;
					}
				}

				// Sapper removal
				if ( RemoveEnemyAttachments( pActivator ) )
					return;
			}
		}
		else
		{
			CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pActivator;

			// If we're already planting a sapper, abort
			if ( useType == USE_OFF || pPlayer->IsAttachingSapper() ) 
			{
				// Don't abort if we just started placing it. This is to catch people who'd like to +use toggle instead of hold down
				if ( pPlayer->GetSapperAttachmentTime() > 0.2 && pPlayer->IsAttachingSapper() )
				{
					pPlayer->StopAttaching();
				}
			}
			else if ( useType == USE_ON )
			{
				// Don't allow sappers to be planted on invulnerable objects
				if ( m_bInvulnerable )
					return;

				// If the object's already got a sapper from me on it, I can't put another
				if ( HasSapperFromPlayer( ((CBaseTFPlayer*)pActivator ) ) )
					return;

				Vector vecAiming;
				pPlayer->EyeVectors( &vecAiming );
				// Trace from the player to the object to find an attachment position
				trace_t tr;
				Vector vecStart = pPlayer->EyePosition();
				UTIL_TraceLine( vecStart, vecStart + (vecAiming * 256), MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr );
				if ( tr.fraction < 1.0 && tr.m_pEnt == this )
				{
					CGrenadeObjectSapper *sapper = CGrenadeObjectSapper::Create( tr.endpos, vecAiming, pPlayer, this );
					pPlayer->StartAttachingSapper( this, sapper );
				}
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Builder has picked up the object
//-----------------------------------------------------------------------------
void CBaseObject::PickupObject( void )
{
	// Tell the playerclass
	if ( GetBuilder() && GetBuilder()->GetPlayerClass() )
	{
		GetBuilder()->GetPlayerClass()->PickupObject( this );
	}

	UTIL_Remove( this );
}


//-----------------------------------------------------------------------------
// Purpose: Return true if the specified player's allowed to remove this object
//-----------------------------------------------------------------------------
bool CBaseObject::CanBeRemovedBy( CBaseTFPlayer *pPlayer )
{
	if ( m_fObjectFlags & OF_CANNOT_BE_DISMANTLED )
		return false;

	// If I'm a map-defined object, I'm not removable by anyone
	if ( WasMapPlaced() )
		return false;

	// If I have an owner, only he can remove me
	if ( GetBuilder() != pPlayer )
		return false;
	
	return true;
}


//-----------------------------------------------------------------------------
// Purpose: Return true if the specified player's allowed to rotate this object
//-----------------------------------------------------------------------------
bool CBaseObject::CanBeRotatedBy( CBaseTFPlayer *pPlayer )
{
	// If I'm a map-defined object, I'm not removable by anyone
	if ( WasMapPlaced() )
		return false;

	// If I have an owner, only he can remove me
	if ( GetBuilder() != pPlayer )
		return false;
	
	return true;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : iTeamNum - 
//-----------------------------------------------------------------------------
void CBaseObject::ChangeTeam( int iTeamNum )
{
	CTFTeam *pTeam = ( CTFTeam * )GetGlobalTeam( iTeamNum );
	CTFTeam *pExisting = ( CTFTeam * )GetTeam();

	TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::ChangeTeam old %s new %s\n", gpGlobals->curtime, 
		pExisting ? pExisting->GetName() : "NULL",
		pTeam ? pTeam->GetName() : "NULL" ) );

	// Already on this team
	if ( GetTeamNumber() == iTeamNum )
		return;

	if ( pExisting )
	{
		// Remove it from current team ( if it's in one ) and give it to new team
		pExisting->RemoveObject( this );
	}
		
	// Change to new team
	BaseClass::ChangeTeam( iTeamNum );
	
	// Add this object to the team's list
	// But only if we're not placing it
	if ( pTeam && (!m_bPlacing) )
	{
		pTeam->AddObject( this );
	}

	// Setup for our new team's model
	SetupTeamModel();
	CreateBuildPoints();
	CreateVulnerablePoints();

	// Alien buildings never need power
	if ( GetTeamNumber() == TEAM_ALIENS )
	{
		m_fObjectFlags |= OF_DOESNT_NEED_POWER;
	}

	GainedNewTechnology( NULL );

	NetworkStateChanged();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : const char
//-----------------------------------------------------------------------------
const char *CBaseObject::GetWeaponClassnameForObject( void )
{
	return NULL;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pNewOwner - 
//-----------------------------------------------------------------------------
void CBaseObject::AddItemsNeededForObject( CBaseTFPlayer *pNewOwner )
{
}

//-----------------------------------------------------------------------------
// Purpose: Derived classes might want to give the new builder the appropriate
//  items needed to own this object and move the objects owned over as well
// Input  : *pNewOwner - 
//-----------------------------------------------------------------------------
void CBaseObject::ChangeBuilder( CBaseTFPlayer *pNewBuilder, bool moveobjects )
{
	CBaseTFPlayer *oldBuilder = GetOwner();

	TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::ChangeBuilder old %s new %s, moveobjects\n", gpGlobals->curtime, 
		oldBuilder ? STRING( oldBuilder->pl.netname ) : "NULL",
		pNewBuilder ? STRING( pNewBuilder->pl.netname ) : "NULL",
		moveobjects ? "true" : "false" ) );

	// Store off original builder
	if ( GetOwner() )
	{
		m_hOriginalBuilder = GetOwner();
	}

	m_hBuilder = pNewBuilder;

	NetworkStateChanged( );

	if ( !moveobjects )
		return;

	if ( oldBuilder )
	{
		oldBuilder->OwnedObjectChangeTeam( this, pNewBuilder );
	}

	// For instance, if this is a mortar being added to a technician via subversion, then
	//  the "weapon_mortar" will be added to the player if the player doesn't have it.
	AddItemsNeededForObject( pNewBuilder );

	const char *classname = GetWeaponClassnameForObject();
	if ( !classname )
		return;

	TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::ChangeBuilder moving associated objects %s\n", gpGlobals->curtime, 
		classname ) );

	// Find the old player who owned a weapon that owned this object type and remove it 
	// Then add to current player under the approrpriate weapon ( same classname ) which
	//  should have been added in ChangeBuilder
	for ( int i = 1; i <= gpGlobals->maxClients; i++ )
	{
		CBaseTFPlayer *player = static_cast< CBaseTFPlayer *>( UTIL_PlayerByIndex( i ) );
		if ( !player )
			continue;

		// Cycle through weapons
		for ( int j = 0; j < player->WeaponCount(); j++ )
		{
			if ( !player->GetWeapon( j ) )
				continue;

			if ( !FClassnameIs( player->GetWeapon( j ), classname ) )
				continue;

			// Add to this player
			if ( player == pNewBuilder )
			{
				( ( CBaseTFCombatWeapon * )player->GetWeapon( j ))->AddAssociatedObject( this );
			}
			// Remove from any other
			else
			{
				( ( CBaseTFCombatWeapon * )player->GetWeapon( j ))->RemoveAssociatedObject( this );
			}
		}

	}
}


//-----------------------------------------------------------------------------
// Purpose: Return true if I have at least 1 sapper on me
//-----------------------------------------------------------------------------
bool CBaseObject::HasSapper( void )
{
	return ( m_hSappers.Size() > 0 );
}


//-----------------------------------------------------------------------------
// Purpose: Return true if the specified player has attached a sapper to me
//-----------------------------------------------------------------------------
bool CBaseObject::HasSapperFromPlayer( CBaseTFPlayer *pPlayer )
{
	for ( int i = 0; i < m_hSappers.Size(); i++ )
	{
		if ( m_hSappers[i] == NULL )
			continue;

		if ( m_hSappers[i]->GetOwner() == pPlayer )
			return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Add a sapper to this object
//-----------------------------------------------------------------------------
void CBaseObject::AddSapper( CGrenadeObjectSapper *pSapper )
{
	SapperHandle hSapper;
	hSapper = pSapper;
	m_hSappers.AddToTail( hSapper );
	m_bHasSapper = true;
	NetworkStateChanged();
}

//-----------------------------------------------------------------------------
// Purpose: Tell all sappers on this object to remove themselves 
//-----------------------------------------------------------------------------
void CBaseObject::RemoveAllSappers( CBaseEntity *pRemovingEntity )
{
	// Loop through all the sappers and fire a +use on them (backwards because list will change)
	int iSize = m_hSappers.Size();
	for (int i = iSize-1; i >= 0; i--)
	{
		m_hSappers[i]->Use( pRemovingEntity, pRemovingEntity, USE_TOGGLE, 0 );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Remove a sapper from this object
//-----------------------------------------------------------------------------
void CBaseObject::RemoveSapper( CGrenadeObjectSapper *pSapper )
{
	SapperHandle hSapper;
	hSapper = pSapper;
	m_hSappers.FindAndRemove( hSapper );
	m_bHasSapper = HasSapper();
}

//-----------------------------------------------------------------------------
// Purpose: My owner's just received a new technology, see if it affects me
//-----------------------------------------------------------------------------
void CBaseObject::GainedNewTechnology( CBaseTechnology *pTechnology )
{
	// Base object doesn't respond to tech
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pRecipient - 
//			*techname - 
//-----------------------------------------------------------------------------
void CBaseObject::GiveNamedTechnology( CBaseTFPlayer *pRecipient, const char *techname )
{
	CTFTeam *team = static_cast< CTFTeam * >( pRecipient->GetTeam() );
	if ( !team )
		return;

	CBaseTechnology *tech = team->m_pTechnologyTree->GetTechnology( techname );
	if ( tech )
	{
		team->EnableTechnology( tech, true );
	}
}


bool CBaseObject::ShowVGUIScreen( int panelIndex, bool bShow )
{
	Assert( panelIndex >= 0 && panelIndex < m_hScreens.Count() );
	if ( m_hScreens[panelIndex].Get() )
	{
		m_hScreens[panelIndex]->SetActive( bShow );
		return true;
	}
	else
	{
		return false;
	}
}

//-----------------------------------------------------------------------------
// Purpose: Returns true if this object was placed in the map, not built by a player
//-----------------------------------------------------------------------------
bool CBaseObject::WasMapPlaced( void )
{
	return m_bWasMapPlaced;
}

//-----------------------------------------------------------------------------
// Purpose: Find nearby objects on my team and connect to them
//-----------------------------------------------------------------------------
CRopeKeyframe *CBaseObject::ConnectCableTo( CBaseObject *pObject, int iLocalAttachment, int iTargetAttachment )
{
	// Connect to it
	CRopeKeyframe *pRope = CRopeKeyframe::Create( this, pObject, iLocalAttachment, iTargetAttachment );
	if ( pRope )
	{
		pRope->m_Width = 3;
		pRope->m_nSegments = ROPE_MAX_SEGMENTS;
		//pRope->m_RopeFlags |= (ROPE_RESIZE | ROPE_COLLIDE);
		pRope->EnableCollision();
		pRope->EnableWind( false );
		pRope->SetupHangDistance( ROPE_HANG_DIST );
		pRope->ActivateStartDirectionConstraints( true );
		pRope->ActivateEndDirectionConstraints( true );
	}

	// Add the rope to both Object's lists
	CHandle< CRopeKeyframe > hHandle;
	hHandle = pRope;
	m_aRopes.AddToTail( hHandle );
	pObject->m_aRopes.AddToTail( hHandle );

	// During placement, the rules for whether the rope is transmitted or not are 
	// tricky, so we make a proxy here to control it.
	if ( IsPlacing() || pObject->IsPlacing() )
	{
		CObjectRopeTransmitProxy *pProxy = new CObjectRopeTransmitProxy( pRope );
		pProxy->m_hObj1 = this;
		pProxy->m_hObj2 = pObject;
		pRope->SetTransmitProxy( pProxy );
	}

	return pRope;
}

//-----------------------------------------------------------------------------
// Purpose: Return true if I have a cable to the specified object
//-----------------------------------------------------------------------------
bool CBaseObject::HasCableTo( CBaseObject *pObject )
{
	for (int i = 0; i < m_aRopes.Size(); i++)
	{
		CHandle< CRopeKeyframe > hHandle;
		hHandle = m_aRopes[i];
		if ( hHandle )
		{
			if ( m_aRopes[i]->GetEndPoint() == pObject )
				return true;
		}
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Return an attachment point for a cable
//-----------------------------------------------------------------------------
int CBaseObject::GetCableAttachment( void )
{
	Vector vecOrigin, vecAngles;
	// If I already have a rope attached, try and use a different attachment point
	if ( m_aRopes.Size() )
	{
		// First, check to see if we've lost any ropes (this can happen because 
		// the other object it was attached to has been destroyed.
		int iSize = m_aRopes.Size();
		for (int i = iSize-1; i >= 0; i--)
		{
			CHandle< CRopeKeyframe > hHandle;
			hHandle = m_aRopes[i];
			if ( hHandle == NULL )
			{
				m_aRopes.Remove(i);
			}
		}

		// If I have enough connections, tell 'em I don't want no more
		if ( m_aRopes.Size() >= MAX_CABLE_CONNECTIONS )
			return -1;

		char sAttachment[32];
		Q_snprintf( sAttachment,sizeof(sAttachment), "cablepoint%d", m_aRopes.Size() + 1 );
		int iPoint = LookupAttachment( sAttachment );
		if ( iPoint > 0 )
			return iPoint;											
	}


	return LookupAttachment( "cablepoint1" );
}


//====================================================================================================================
// POWER PACKS
//====================================================================================================================
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::SetPowerPack( CObjectPowerPack *pPack )
{
	bool bHadPower = HasPowerup( POWERUP_POWER );
	CObjectPowerPack *pOldPack = m_hPowerPack;

	m_hPowerPack = pPack;

	// If it's placing, I don't get power yet
	if ( m_hPowerPack && !m_hPowerPack->IsPlacing() )
	{
		SetPowerup( POWERUP_POWER, true );
	}
	else
	{
		// Lose power in a second, to give any nearby powerpacks time to connect to me and replace the power
		if ( bHadPower )
		{
			SetContextThink( LostPowerThink, gpGlobals->curtime + 1.0, OBJ_LOSTPOWER_THINK_CONTEXT );
			if ( GetTFTeam() )
			{
				// Dirty hack to make powerpack think I need power
				m_iPowerups &= ~(1 << POWERUP_POWER);
				GetTFTeam()->UpdatePowerpacks( pOldPack, this );
			}
		}
		else
		{
			SetPowerup( POWERUP_POWER, false );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: We've lost power fully
//-----------------------------------------------------------------------------
void CBaseObject::LostPowerThink( void )
{
	// We may have found another powerpack
	if ( !m_hPowerPack )
	{
		// Dirty hack to get our powerup removed properly
		m_iPowerups |= (1 << POWERUP_POWER);
		SetPowerup( POWERUP_POWER, false );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Set the health of the object
//-----------------------------------------------------------------------------
void CBaseObject::InputSetHealth( inputdata_t &inputdata )
{
	m_iMaxHealth = inputdata.value.Int();
	SetHealth( m_iMaxHealth );
}

//-----------------------------------------------------------------------------
// Purpose: Add health to the object
//-----------------------------------------------------------------------------
void CBaseObject::InputAddHealth( inputdata_t &inputdata )
{
	int iHealth = inputdata.value.Int();
	SetHealth( min( GetMaxHealth(), m_flHealth + iHealth ) );
}

//-----------------------------------------------------------------------------
// Purpose: Remove health from the object
//-----------------------------------------------------------------------------
void CBaseObject::InputRemoveHealth( inputdata_t &inputdata )
{
	int iDamage = inputdata.value.Int();

	SetHealth( m_flHealth - iDamage );
	if ( GetHealth() <= 0 )
	{
		m_lifeState = LIFE_DEAD;
		m_OnDestroyed.FireOutput(this, this);
		Killed();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CBaseObject::InputSetMinDisabledHealth( inputdata_t &inputdata )
{
	float minhealth = inputdata.value.Float();

	bool wasdisabled = IsDisabled();

	if ( m_flHealth < minhealth )
	{
		SetDisabled( true );
		// NOTE:  This could theoretically add health, sigh.
		SetHealth( minhealth );

		// Disable it if not already disabled
		if ( !wasdisabled )
		{
			m_OnBecomingDisabled.FireOutput( inputdata.pActivator, this );
		}
	}
	else if ( wasdisabled && ( m_flHealth > minhealth ) )
	{
		SetDisabled( false );

		m_OnBecomingReenabled.FireOutput( inputdata.pActivator, this );
	}

	m_flMinDisableHealth = minhealth;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CBaseObject::InputSetSolidToPlayer( inputdata_t &inputdata )
{
	int ival = inputdata.value.Int();
	ival = clamp( ival, (int)SOLID_TO_PLAYER_USE_DEFAULT, (int)SOLID_TO_PLAYER_NO );
	OBJSOLIDTYPE stp = (OBJSOLIDTYPE)ival;
	SetSolidToPlayers( stp );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::PlayStartupAnimation( void )
{
	SetActivity( ACT_OBJ_STARTUP );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::DetermineAnimation( void )
{
	Activity desiredActivity = m_Activity;

	switch ( m_Activity )
	{
	default:
		{
			if ( IsPlacing() )
			{
				desiredActivity = ACT_OBJ_PLACING;
			}
			else if ( IsBuilding() )
			{
				desiredActivity = ACT_OBJ_ASSEMBLING;
			}
			/*
			TODO:
				ACT_OBJ_DISMANTLING;
				ACT_OBJ_DETERIORATING;
			*/
			else
			{
				desiredActivity = ACT_OBJ_RUNNING;
			}
		}
		break;
	case ACT_OBJ_STARTUP:
		{
			if ( IsActivityFinished() )
			{
				desiredActivity = ACT_OBJ_RUNNING;
			}
		}
		break;
	}

	if ( desiredActivity == m_Activity )
		return;

	SetActivity( desiredActivity );
}

//-----------------------------------------------------------------------------
// Purpose: Attach this object to the specified object
//-----------------------------------------------------------------------------

void CBaseObject::AttachObjectToObject( const CBaseEntity *pEntity, int iPoint, Vector &vecOrigin )
{
	SetupAttachedVersion();

	m_hBuiltOnEntity = pEntity;
	m_iBuiltOnPoint = iPoint;

	if ( m_hBuiltOnEntity.Get() )
	{
		// Parent ourselves to the object
		int iAttachment = 0;
		const IHasBuildPoints *pBPInterface = dynamic_cast<const IHasBuildPoints*>( pEntity );
		if ( pBPInterface )
			iAttachment = pBPInterface->GetBuildPointAttachmentIndex( iPoint );

		SetParent( m_hBuiltOnEntity.Get(), iAttachment );

		if ( iAttachment >= 1 )
		{
			// Stick right onto the attachment point.
			vecOrigin.Init();
			SetLocalOrigin( vecOrigin );
			SetLocalAngles( QAngle(0,0,0) );
		}
		else
		{
			SetAbsOrigin( vecOrigin );
			vecOrigin = GetLocalOrigin();
		}
	}

	Assert( m_hBuiltOnEntity == GetMoveParent() );
}

//-----------------------------------------------------------------------------
// Purpose: Detach this object from its parent, if it has one
//-----------------------------------------------------------------------------
void CBaseObject::DetachObjectFromObject( void )
{
	if ( !GetParentObject() )
		return;

	// Clear the build point
	IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(GetParentObject() );
	Assert( pBPInterface );
	pBPInterface->SetObjectOnBuildPoint( m_iBuiltOnPoint, NULL );

	SetParent( NULL );
	m_hBuiltOnEntity = NULL;
	m_iBuiltOnPoint = 0;
}


//-----------------------------------------------------------------------------
// Purpose: Spawn any objects specified inside the mdl
//-----------------------------------------------------------------------------
void CBaseObject::SpawnEntityOnBuildPoint( const char *pEntityName, int iAttachmentNumber )
{
	// Try and spawn the object
	CBaseEntity *pEntity = CreateEntityByName( pEntityName );
	if ( !pEntity )
		return;

	Vector vecOrigin;
	QAngle vecAngles;
	GetAttachment( iAttachmentNumber, vecOrigin, vecAngles );
	pEntity->SetAbsOrigin( vecOrigin );
	pEntity->SetAbsAngles( vecAngles );
	pEntity->Spawn();
	
	// If it's an object, finish setting it up
	CBaseObject *pObject = dynamic_cast<CBaseObject*>(pEntity);
	if ( !pObject )
		return;

	// Add a buildpoint here
	int iPoint = AddBuildPoint( iAttachmentNumber );
	AddValidObjectToBuildPoint( iPoint, pObject->GetType() );
	pObject->SetBuilder( GetBuilder() );
	pObject->ChangeTeam( GetTeamNumber() );
	pObject->SpawnControlPanels();
	pObject->SetHealth( pObject->GetMaxHealth() );
	pObject->FinishedBuilding();
	pObject->AttachObjectToObject( this, iPoint, vecOrigin );
	pObject->m_fObjectFlags |= OF_CANNOT_BE_DISMANTLED;
	pObject->AttemptToFindPower();

	IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this);
	Assert( pBPInterface );
	pBPInterface->SetObjectOnBuildPoint( iPoint, pObject );
}


//-----------------------------------------------------------------------------
// Purpose: Spawn any objects specified inside the mdl
//-----------------------------------------------------------------------------
void CBaseObject::SpawnObjectPoints( void )
{
	KeyValues *modelKeyValues = new KeyValues("");
	if ( !modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) )
	{
		modelKeyValues->deleteThis();
		return;
	}

	// Do we have a build point section?
	KeyValues *pkvAllObjectPoints = modelKeyValues->FindKey("object_points");
	if ( !pkvAllObjectPoints )
	{
		modelKeyValues->deleteThis();
		return;
	}

	// Start grabbing the sounds and slotting them in
	KeyValues *pkvObjectPoint;
	for ( pkvObjectPoint = pkvAllObjectPoints->GetFirstSubKey(); pkvObjectPoint; pkvObjectPoint = pkvObjectPoint->GetNextKey() )
	{
		// Find the attachment first
		const char *sAttachment = pkvObjectPoint->GetName();
		int iAttachmentNumber = LookupAttachment( sAttachment );
		if ( iAttachmentNumber == 0 )
		{
			Msg( "ERROR: Model %s specifies object point %s, but has no attachment named %s.\n", STRING(GetModelName()), pkvObjectPoint->GetString(), pkvObjectPoint->GetString() );
			continue;
		}

		// Now see what we're supposed to spawn there
		// The count check is because it seems wrong to emit multiple entities on the same point
		int nCount = 0;
		KeyValues *pkvObject;
		for ( pkvObject = pkvObjectPoint->GetFirstSubKey(); pkvObject; pkvObject = pkvObject->GetNextKey() )
		{
			SpawnEntityOnBuildPoint( pkvObject->GetName(), iAttachmentNumber );
			++nCount;
			Assert( nCount <= 1 );
		}
	}

	modelKeyValues->deleteThis();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseObject::CreateVulnerablePoints()
{
	KeyValues *modelKeyValues = new KeyValues("");
	if ( !modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) )
		return;

	// Do we have a build point section?
	KeyValues *pkvAllVulnerablePoints = modelKeyValues->FindKey("vulnerable_points");
	if ( !pkvAllVulnerablePoints )
		return;

	// Start grabbing the sounds and slotting them in
	KeyValues *pkvVulnerablePoint = pkvAllVulnerablePoints->GetFirstSubKey();
	while ( pkvVulnerablePoint )
	{
		AddVulnerablePoint( pkvVulnerablePoint->GetName(), pkvVulnerablePoint->GetFloat() );
		pkvVulnerablePoint = pkvVulnerablePoint->GetNextKey();
	}

}

ConVar obj_debug_vulnerable( "obj_debug_vulnerable","0", FCVAR_NONE, "Show vulnerable points" );

void CBaseObject::AddVulnerablePoint( const char* szName, float fMultiplier )
{
	// Make a new vulnerable point
	VulnerablePoint_t v;
	Q_memset(&v,0,sizeof(v));
	v.m_fDamageMultiplier = fMultiplier;

	int nSet, nBox;

	if( !LookupHitbox(szName, nSet, nBox) )
	{
		Msg( "Error: Vulnerable point on model %s unable to locate hitbox %s\n", STRING(GetModelName()), szName);
		return;
	}

	v.m_nSet = nSet;
	v.m_nBox = nBox;

	// Insert it into our list
	if( obj_debug_vulnerable.GetBool() )
	{
		Msg( "Vulnerable point %s on model %s added with a damage multiplier of %f. (%i, %i)\n", szName, STRING(GetModelName()), fMultiplier, nSet, nBox);
	}

	m_VulnerablePoints.AddToTail( v );
}


float CBaseObject::FindVulnerablePointMultiplier( int nGroup, int nBox ) 		
{
	for( int i=0; i < m_VulnerablePoints.Count(); i++ )
	{
		VulnerablePoint_t& v = m_VulnerablePoints[i];

		if( v.m_nBox == nBox /* && v.m_nSet == nGroup */)
		{
			if( obj_debug_vulnerable.GetBool() )
			{
				Msg("VulnerablePoint: %f\n",v.m_fDamageMultiplier);
			}
			return v.m_fDamageMultiplier;
		}
	}

	if( obj_debug_vulnerable.GetBool() )
	{
		Msg("Couldn't find vulnerable point: %i %i\n",nGroup,nBox);
	}
	return 1.f;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
QAngle CBaseObject::ConvertAbsAnglesToLocal( QAngle vecLocalAngles )
{
	if ( !GetMoveParent() )
		return vecLocalAngles;

	EntityMatrix vehicleToWorld, childMatrix;
	vehicleToWorld.InitFromEntity( GetMoveParent() ); // parent->world

	// Calculate the build point angles in vehicle space
	VMatrix attachmentToWorld;
	MatrixFromAngles( vecLocalAngles, attachmentToWorld );
	
	VMatrix worldToVehicle = vehicleToWorld.Transpose();
	VMatrix attachmentToVehicle;
	MatrixMultiply( worldToVehicle, attachmentToWorld, attachmentToVehicle );
	QAngle vecAbsAngles;
	MatrixToAngles( attachmentToVehicle, vecAbsAngles );

	return vecAbsAngles;
}

bool CBaseObject::IsSolidToPlayers( void ) const
{
	switch ( m_SolidToPlayers )
	{
	default:
		break;
	case SOLID_TO_PLAYER_USE_DEFAULT:
		{
			if ( GetObjectInfo( ObjectType() ) )
			{
				return GetObjectInfo( ObjectType() )->m_bSolidToPlayerMovement;
			}
		}
		break;
	case SOLID_TO_PLAYER_YES:
		return true;
	case SOLID_TO_PLAYER_NO:
		return false;
	}

	return false;
}

void CBaseObject::SetSolidToPlayers( OBJSOLIDTYPE stp, bool force )
{
	bool changed = stp != m_SolidToPlayers;
	m_SolidToPlayers = stp;

	if ( changed || force )
	{
		SetCollisionGroup( 
			IsSolidToPlayers() ? 
				TFCOLLISION_GROUP_OBJECT_SOLIDTOPLAYERMOVEMENT : 
				TFCOLLISION_GROUP_OBJECT );
		NetworkStateChanged();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Hooks to support swapping out model if the object is damaged
// Input  : bDisabled - 
//-----------------------------------------------------------------------------
void CBaseObject::SetDisabled( bool bDisabled )
{ 
	bool changed = m_bDisabled != bDisabled;
	m_bDisabled = bDisabled;

	// value changed and mapper specified a "disabled"/damaged model
	if ( changed && NULL_STRING != m_iszDisabledModel )
	{
		// Change model
		SetModel( STRING( m_bDisabled ? m_iszDisabledModel : m_iszEnabledModel ) );
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseObject::SetBuffStation( CObjectBuffStation *pBuffStation, bool bPlacing )	
{ 
	// Activate
	if ( pBuffStation && !GetBuffStation() && !bPlacing && !IsBuilding() && IsPowered() )
	{
		BuffStationActivate();
	}

	if ( GetBuffStation() && ( pBuffStation == GetBuffStation() ) && !m_bBuffActivated && !bPlacing && !IsBuilding() && IsPowered() )
	{
		BuffStationActivate();
	}

	// Deactivate
	if ( !pBuffStation && GetBuffStation() )
	{
		BuffStationDeactivate();
	}

	m_hBuffStation = pBuffStation; 
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CBaseObject::IsHookedAndBuffed( void )												
{ 
	if ( GetBuffStation() && HasPowerup( POWERUP_BOOST ) )
	{
		if ( GetBuffStation()->IsPowered() && !GetBuffStation()->IsPlacing() &&
			 !GetBuffStation()->IsBuilding() )
			return true;
	}

	return false;
}
